1 package Business::OnlinePayment::InternetSecure;
9 use Net::SSLeay qw(make_form post_https);
10 use XML::Simple qw(xml_in xml_out);
12 use base qw(Business::OnlinePayment Exporter);
15 our $VERSION = '0.01';
18 use constant CARD_TYPES => {
21 AX => 'American Express',
26 # Convenience functions to avoid undefs and escape products strings
27 sub _def($) { defined $_[0] ? $_[0] : '' }
28 sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ }
34 $self->server('secure.internetsecure.com');
36 $self->path('/process.cgi');
39 receipt_number sales_order_number
42 avs_response cvv2_response
46 # OnlinePayment's remap_fields is buggy, so we simply rewrite it
49 my ($self, %map) = @_;
51 my %content = $self->content();
53 $content{$map{$_}} = delete $content{$_};
55 $self->content(%content);
58 # Combine get_fields and remap_fields for convenience
60 sub get_remap_fields {
61 my ($self, %map) = @_;
63 $self->remap_fields(reverse %map);
64 my %data = $self->get_fields(keys %map);
66 foreach (values %data) {
67 $_ = '' unless defined;
73 # Since there's no standard format for expiration dates, we try to do our best
76 my ($self, $str) = @_;
82 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
83 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
84 /^(\d\d)[.-](\d\d)$/) { # YY-MM
86 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
87 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
88 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
91 croak "Unable to parse expiration date: $str";
94 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
99 # Convert a single product into a product string
102 my ($self, $currency, $taxes, %data) = @_;
104 croak "Missing amount in product" unless defined $data{amount};
106 my @flags = ($currency);
108 $taxes = uc $data{taxes} if defined $data{taxes};
109 foreach (split ' ' => $taxes) {
110 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
114 if ($self->test_transaction) {
115 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
119 sprintf('%.2f' => $data{amount}),
120 $data{quantity} || 1,
121 _esc _def $data{sku},
122 _esc _def $data{description},
123 join('' => map "{$_}" => @flags),
127 # Generate the XML document for this transaction
132 my %content = $self->content;
134 $self->required_fields(qw(action card_number exp_date));
136 croak 'Unsupported transaction type'
137 if $content{type} && $content{type} !~
138 /^(Visa|MasterCard|American Express|Discover)$/i;
140 croak 'Unsupported action'
141 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
143 $content{currency} ||= 'CAD';
144 $content{currency} = uc $content{currency};
145 croak "Unknown currency code ", $content{currency}
146 unless $content{currency} =~ /^(CAD|USD)$/;
148 $content{taxes} ||= '';
149 $content{taxes} = uc $content{taxes};
151 my %data = $self->get_remap_fields(qw(
152 xxxCardNumber card_number
164 xxxShippingName ship_name
165 xxxShippingCompany ship_company
166 xxxShippingAddress ship_address
167 xxxShippingCity ship_city
168 xxxShippingProvince ship_state
169 xxxShippingPostal ship_zip
170 xxxShippingCountry ship_country
171 xxxShippingPhone ship_phone
172 xxxShippingEmail ship_email
175 $data{MerchantNumber} = $self->merchant_id;
177 $data{xxxCardNumber} =~ tr/ //d;
179 my ($y, $m) = $self->parse_expdate($content{exp_date});
180 $data{xxxCCYear} = sprintf '%.4u' => $y;
181 $data{xxxCCMonth} = sprintf '%.2u' => $m;
183 if (defined $content{cvv2} && $content{cvv2} ne '') {
185 $data{CVV2Indicator} = $content{cvv2};
188 $data{CVV2Indicator} = '';
191 if (ref $content{description}) {
192 $data{Products} = join '|' => map $self->prod_string(
196 @{ $content{description} };
198 $self->required_fields(qw(amount));
199 $data{Products} = $self->prod_string(
202 amount => $content{amount},
203 description => $content{description},
209 RootName => 'TranxRequest',
210 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
214 # Map the various fields from the response, and put their values into our
215 # object for retrieval.
218 my ($self, $data, %map) = @_;
220 while (my ($k, $v) = each %map) {
222 $self->$v($data->{$k});
226 # Parse the server's response and set various fields
229 my ($self, $response) = @_;
231 $self->server_response($response);
233 # FIXME: It's not quite clear whether there should be some decoding, or
234 # if the result is already utf8.)
236 $response = xml_in($response,
237 ForceArray => [qw(product flag)],
238 GroupTags => { qw(Products product flags flag) },
240 SuppressEmpty => undef,
243 my $code = $self->result_code($response->{Page});
244 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
246 $self->infuse($response, qw(
247 ReceiptNumber receipt_number
248 SalesOrderNumber sales_order_number
252 ApprovalCode authorization
253 Verbiage error_message
254 TotalAmount total_amount
255 AVSResponseCode avs_response
256 CVV2ResponseCode cvv2_response
259 $self->card_type(CARD_TYPES->{$self->card_type});
261 $self->{products_raw} = $response->{Products};
269 croak "Missing required argument 'merchant_id'"
270 unless defined $self->{merchant_id};
272 my ($page, $response, %headers) =
279 xxxRequestMode => 'X',
280 xxxRequestData => Encode::encode_utf8(
286 croak 'Error connecting to server' unless $page;
287 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
289 $self->parse_response($page);
300 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
304 use Business::OnlinePayment;
306 $txn = new Business::OnlinePayment 'InternetSecure',
307 merchant_id => '0000';
310 action => 'Normal Authorization',
313 card_number => '0000000000000000',
314 exp_date => '2004-07',
315 cvv2 => '000', # Optional
317 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
319 address => '123 Street',
320 city => 'Metropolis',
324 phone => '(555) 555-1212',
325 email => 'fbriere@fbriere.net',
327 description => 'Online purchase',
335 if ($txn->is_success) {
336 print "Card processed successfully: " . $tx->authorization . "\n";
338 print "Card was rejected: " . $tx->error_message . "\n";
343 Business::OnlinePayment::InternetSecure is an implementation of
344 L<Business::OnlinePayment> that allows for processing online credit card
345 payments through Internet Secure.
347 See L<Business::OnlinePayment> for more information about the generic
348 Business::OnlinePayment interface.
352 Object creation is done via L<Business::OnlinePayment>; see its manpage for
353 details. The I<merchant_id> processor option is required, and corresponds
354 to the merchant ID assigned to you by Internet Secure.
358 (See L<Business::OnlinePayment> for more methods.)
360 =head2 Before order submission
364 =item content( CONTENT )
366 Sets up the data prior to a transaction (overwriting any previous data by the
367 same occasion). CONTENT is an associative array (hash), containing some of
368 the following fields:
372 =item action (required)
374 What to do with the transaction. Only C<Normal Authorization> is supported
379 Transaction type, being one of the following:
387 =item - American Express
393 (This is actually ignored for the moment, and can be left blank or undefined.)
395 =item card_number (required)
397 Credit card number. Spaces are allowed, and will be automatically removed.
399 =item exp_date (required)
401 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
402 any syntax, this module is rather lax regarding what it will accept. The
403 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
408 Three- or four-digit verification code printed on the card. This can be left
409 blank or undefined, in which case no check will be performed. Whether or not a
410 transaction will be declined in case of a mismatch depends on the merchant
411 account configuration.
413 This number may be called Card Verification Value (CVV2), Card Validation
414 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
418 A short description of the purchase. See L<"Products list syntax"> for
419 an alternate syntax that allows a list of products to be specified.
423 Total amount to be billed, excluding taxes if they are to be added separately.
424 This field is required if B<description> is a string, and should be left
425 undefined if B<description> contains a list of products, as outlined in
426 L<"Products list syntax">.
430 Currency of all amounts for this order. This can currently be either
431 C<CAD> (default) or C<USD>.
435 Taxes to be added automatically. These should not be included in B<amount>;
436 they will be automatically added by Internet Secure later on.
438 Available taxes are C<GST>, C<PST> and C<HST>. Taxes can be combined by
439 separating them with spaces, such as C<GST HST>.
441 =item name / company / address / city / state / zip / country / phone / email
443 Facultative customer information. B<state> should be either a postal
444 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
445 be a two-letter code taken from ISO 3166-1.
451 =head2 After order submission
455 =item receipt_number() / sales_order_number()
457 Receipt number and sales order number of submitted order.
461 Total amount billed for this order, including taxes.
465 Cardholder's name. This is currently a mere copy of the B<name> field passed
470 Type of the credit card used for the submitted order, being one of the
479 =item - American Express
485 =item avs_response() / cvv2_response()
487 Results of the AVS and CVV2 checks. See the Internet Secure documentation for
488 the list of possible values.
495 =head2 Products list syntax
497 Optionally, the B<description> field of B<content()> can contain a reference
498 to an array of products, instead of a simple string. Each element of this
499 array represents a different product, and must be a reference to a hash with
500 the following fields:
506 Unit price of this product.
510 Ordered quantity of this product. This can be a decimal value.
514 Internal code for this product.
518 Description of this product
522 Taxes that should be automatically added to this product. If specified, this
523 overrides the B<taxes> field passed to B<content()>.
527 When using a products list, the B<amount> field passed to B<content()> should
531 =head2 Character encodings
548 L<Business::OnlinePayment>
552 Frederic Briere, E<lt>fbriere@fbriere.netE<gt>
554 =head1 COPYRIGHT AND LICENSE
556 Copyright (C) 2006 by Frederic Briere
558 This library is free software; you can redistribute it and/or modify
559 it under the same terms as Perl itself, either Perl version 5.8.4 or,
560 at your option, any later version of Perl 5 you may have available.