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.03';
18 use constant SUCCESS_CODES => qw(2000 90000 900P1);
20 use constant CARD_TYPES => {
21 AM => 'American Express',
29 # Convenience functions to avoid undefs and escape products strings
30 sub _def($) { defined $_[0] ? $_[0] : '' }
31 sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ }
37 $self->server('secure.internetsecure.com');
39 $self->path('/process.cgi');
42 receipt_number sales_number uuid guid
45 total_amount tax_amounts
46 avs_response cvv2_response
49 # Just in case someone tries to call tax_amounts() *before* submit()
50 $self->tax_amounts( {} );
53 # Combine get_fields and remap_fields for convenience. Unlike OnlinePayment's
54 # remap_fields, this doesn't modify content(), and can therefore be called
55 # more than once. Also, unlike OnlinePayment's get_fields in 3.x, this doesn't
58 sub get_remap_fields {
59 my ($self, %map) = @_;
61 my %content = $self->content();
64 while (my ($to, $from) = each %map) {
65 $data{$to} = $content{$from};
71 # Since there's no standard format for expiration dates, we try to do our best
74 my ($self, $str) = @_;
80 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
81 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
82 /^(\d\d)[.-](\d\d)$/) { # YY-MM
84 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
85 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
86 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
89 croak "Unable to parse expiration date: $str";
92 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
97 # Convert a single product into a product string
100 my ($self, $currency, %data) = @_;
102 croak "Missing amount in product" unless defined $data{amount};
104 my @flags = ($currency);
107 if (ref $data{taxes}) {
108 @taxes = @{ $data{taxes} };
109 } elsif ($data{taxes}) {
110 @taxes = split ' ' => $data{taxes};
114 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/i;
118 if ($self->test_transaction) {
119 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
123 sprintf('%.2f' => $data{amount}),
124 $data{quantity} || 1,
125 _esc _def $data{sku},
126 _esc _def $data{description},
127 join('' => map "{$_}" => @flags),
131 # Generate the XML document for this transaction
136 my %content = $self->content;
138 # Backwards-compatible support for exp_date
139 if (exists $content{exp_date} && ! exists $content{expiration}) {
140 $content{expiration} = delete $content{exp_date};
141 $self->content(%content);
144 $self->required_fields(qw(action card_number expiration));
146 croak "Unsupported transaction type: $content{type}"
148 ! grep lc($content{type}) eq lc($_),
149 values %{+CARD_TYPES};
151 croak 'Unsupported action'
152 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
154 $content{currency} = uc($content{currency} || 'CAD');
155 croak "Unknown currency code ", $content{currency}
156 unless $content{currency} =~ /^(CAD|USD)$/;
158 my %data = $self->get_remap_fields(qw(
159 xxxCard_Number card_number
171 xxxShippingName ship_name
172 xxxShippingCompany ship_company
173 xxxShippingAddress ship_address
174 xxxShippingCity ship_city
175 xxxShippingProvince ship_state
176 xxxShippingPostal ship_zip
177 xxxShippingCountry ship_country
178 xxxShippingPhone ship_phone
179 xxxShippingEmail ship_email
182 $data{MerchantNumber} = $self->merchant_id;
184 $data{xxxCard_Number} =~ tr/- //d;
185 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
187 my ($y, $m) = $self->parse_expdate($content{expiration});
188 $data{xxxCCYear} = sprintf '%.4u' => $y;
189 $data{xxxCCMonth} = sprintf '%.2u' => $m;
191 if (defined $content{cvv2} && $content{cvv2} ne '') {
193 $data{CVV2Indicator} = $content{cvv2};
196 $data{CVV2Indicator} = '';
199 if (ref $content{description}) {
200 $data{Products} = join '|' => map $self->prod_string(
202 taxes => $content{taxes},
204 @{ $content{description} };
206 $self->required_fields(qw(amount));
207 $data{Products} = $self->prod_string(
209 taxes => $content{taxes},
210 amount => $content{amount},
211 description => $content{description},
215 # The encode() makes sure to a) strip off non-Latin-1 characters, and
216 # b) turn off the utf8 flag, which confuses XML::Simple
217 encode('ISO-8859-1', xml_out(\%data,
219 RootName => 'TranxRequest',
220 SuppressEmpty => undef,
221 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
225 # Map the various fields from the response, and put their values into our
226 # object for retrieval.
229 my ($self, $data, %map) = @_;
231 while (my ($k, $v) = each %map) {
233 $self->$k($data->{$v});
237 sub extract_tax_amounts {
238 my ($self, $response) = @_;
242 my $products = $response->{Products};
243 return unless $products;
245 foreach my $node (@$products) {
246 my $flags = $node->{flags};
248 grep($_ eq '{TAX}', @$flags) &&
249 grep($_ eq '{CALCULATED}', @$flags))
251 $tax_amounts{ $node->{code} } = $node->{subtotal};
258 # Parse the server's response and set various fields
261 my ($self, $response) = @_;
263 $self->server_response($response);
265 local $/ = "\n"; # Make sure to avoid bug #17687
267 $response = xml_in($response,
268 ForceArray => [qw(product flag)],
269 GroupTags => { qw(Products product flags flag) },
271 SuppressEmpty => undef,
274 $self->infuse($response,
275 result_code => 'Page',
276 error_message => 'Verbiage',
277 authorization => 'ApprovalCode',
278 avs_response => 'AVSResponseCode',
279 cvv2_response => 'CVV2ResponseCode',
281 receipt_number => 'ReceiptNumber',
282 sales_number => 'SalesOrderNumber',
287 cardholder => 'xxxName',
288 card_type => 'CardType',
289 total_amount => 'TotalAmount',
292 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
294 # Completely undocumented field that sometimes override <Verbiage>
295 $self->error_message($response->{Error}) if $response->{Error};
297 # Delete error_message if transaction was successful
298 $self->error_message(undef) if $self->is_success;
300 $self->card_type(CARD_TYPES->{$self->card_type});
302 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
310 croak "Missing required argument 'merchant_id'"
311 unless defined $self->{merchant_id};
313 my ($page, $response, %headers) =
320 xxxRequestMode => 'X',
321 xxxRequestData => $self->to_xml,
325 croak 'Error connecting to server' unless $page;
326 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
328 # The response is marked UTF-8, but it's really Latin-1. Sigh.
329 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
331 $self->parse_response($page);
342 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
346 use Business::OnlinePayment;
348 $txn = new Business::OnlinePayment 'InternetSecure',
349 merchant_id => '0000';
352 action => 'Normal Authorization',
354 type => 'Visa', # Optional
355 card_number => '4111 1111 1111 1111',
356 expiration => '2004-07',
357 cvv2 => '000', # Optional
359 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
361 address => '123 Street',
362 city => 'Metropolis',
366 phone => '(555) 555-1212',
367 email => 'fbriere@fbriere.net',
372 description => 'Test transaction',
377 if ($txn->is_success) {
378 print "Card processed successfully: " . $tx->authorization . "\n";
380 print "Card was rejected: " . $tx->error_message . "\n";
385 C<Business::OnlinePayment::InternetSecure> is an implementation of
386 C<Business::OnlinePayment> that allows for processing online credit card
387 payments through InternetSecure.
389 See L<Business::OnlinePayment> for more information about the generic
390 Business::OnlinePayment interface.
394 Object creation is done via C<Business::OnlinePayment>; see its manpage for
395 details. The B<merchant_id> processor option is required, and corresponds
396 to the merchant ID assigned to you by InternetSecure.
400 =head2 Transaction setup and transmission
404 =item content( CONTENT )
406 Sets up the data prior to a transaction. CONTENT is an associative array
407 (hash), containing some of the following fields:
411 =item action (required)
413 What to do with the transaction. Only C<Normal Authorization> is supported
418 Transaction type, being one of the following:
426 =item - American Express
434 (This is actually ignored for the moment, and can be left blank or undefined.)
436 =item card_number (required)
438 Credit card number. Spaces and dashes are automatically removed.
440 =item expiration (required)
442 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
443 any syntax, this module is rather lax regarding what it will accept. The
444 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
449 Three- or four-digit verification code printed on the card. This can be left
450 blank or undefined, in which case no check will be performed. Whether or not a
451 transaction will be declined in case of a mismatch depends on the merchant
452 account configuration.
454 This number may be called Card Verification Value (CVV2), Card Validation
455 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
459 A short description of the transaction. See L<"Products list syntax"> for
460 an alternate syntax that allows a list of products to be specified.
462 =item amount (usually required)
464 Total amount to be billed, excluding taxes if they are to be added separately
467 This field is required if B<description> is a string, but should be left
468 undefined if B<description> contains a list of products instead, as outlined
469 in L<"Products list syntax">.
473 Currency of all amounts for this order. This can currently be either
474 C<CAD> (default) or C<USD>.
478 Taxes to be added automatically to B<amount> by InternetSecure. Available
479 taxes are C<GST>, C<PST> and C<HST>.
481 This argument can either be a single string of taxes concatenated with spaces
482 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
485 =item name / company / address / city / state / zip / country / phone / email
487 Customer information. B<country> should be a two-letter code taken from ISO
494 Submit the transaction to InternetSecure.
498 =head2 Post-submission methods
504 Returns true if the transaction was submitted successfully.
508 Response code returned by InternetSecure.
510 =item error_message()
512 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
513 should not rely on this to test whether a transaction was successful; use
514 B<is_success>() instead.)
516 =item receipt_number()
518 Receipt number (a string, actually) of this transaction, unique to all
519 InternetSecure transactions.
523 Sales order number of this transaction. This is a number, unique to each
524 merchant, which is incremented by 1 each time.
528 Universally Unique Identifier associated to this transaction. This is a
529 128-bit value returned as a 36-character string such as
530 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
533 B<guid>() is provided as an alias to this method.
535 =item authorization()
537 Authorization code for this transaction.
539 =item avs_response() / cvv2_response()
541 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
542 the list of possible values.
546 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
550 Total amount billed for this order, including taxes.
554 Returns a I<reference> to a hash that maps taxes, which were listed under the
555 B<taxes> argument to B<submit>(), to the amount that was calculated by
560 Cardholder's name. This is currently a mere copy of the B<name> field passed
565 Type of the credit card used for the submitted order, being one of the
574 =item - American Express
588 =head2 Products list syntax
590 Optionally, the B<description> field of B<content>() can contain a reference
591 to an array of products, instead of a simple string. Each element of this
592 array represents a different product, and must be a reference to a hash with
593 the following fields:
597 =item amount (required)
599 Unit price of this product.
603 Ordered quantity of this product.
607 Internal code for this product.
611 Description of this product
615 Taxes that should be automatically added to this product. If specified, this
616 overrides the B<taxes> field passed to B<content>().
620 When using a products list, the B<amount> field passed to B<content>() should
624 =head2 Character encoding
626 When using non-ASCII characters, all data provided to B<contents>() should
627 have been decoded beforehand via the C<Encode> module, unless your data is in
628 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
631 InternetSecure currently does not handle characters outside of ISO-8859-1, so
632 these will be replaced with C<?> before being transmitted.
642 L<Business::OnlinePayment>
646 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
648 =head1 COPYRIGHT AND LICENSE
650 Copyright (C) 2006 by Frédéric Brière
652 This library is free software; you can redistribute it and/or modify
653 it under the same terms as Perl itself, either Perl version 5.8.4 or,
654 at your option, any later version of Perl 5 you may have available.