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.02';
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 # OnlinePayment's get_fields now filters out undefs in 3.x. :(
56 my ($self, @fields) = @_;
58 my %content = $self->content;
60 my %new = map +($_ => $content{$_}), @fields;
65 # OnlinePayment's remap_fields is buggy in 2.x; this is copied from 3.x
68 my ($self, %map) = @_;
70 my %content = $self->content();
72 $content{$map{$_}} = delete $content{$_};
74 $self->content(%content);
77 # Combine get_fields and remap_fields for convenience
79 sub get_remap_fields {
80 my ($self, %map) = @_;
82 $self->remap_fields(reverse %map);
83 my %data = $self->get_fields(keys %map);
88 # Since there's no standard format for expiration dates, we try to do our best
91 my ($self, $str) = @_;
97 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
98 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
99 /^(\d\d)[.-](\d\d)$/) { # YY-MM
101 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
102 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
103 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
106 croak "Unable to parse expiration date: $str";
109 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
114 # Convert a single product into a product string
117 my ($self, $currency, %data) = @_;
119 croak "Missing amount in product" unless defined $data{amount};
121 my @flags = ($currency);
124 if (ref $data{taxes}) {
125 @taxes = @{ $data{taxes} };
126 } elsif ($data{taxes}) {
127 @taxes = split ' ' => $data{taxes};
131 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/i;
135 if ($self->test_transaction) {
136 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
140 sprintf('%.2f' => $data{amount}),
141 $data{quantity} || 1,
142 _esc _def $data{sku},
143 _esc _def $data{description},
144 join('' => map "{$_}" => @flags),
148 # Generate the XML document for this transaction
153 my %content = $self->content;
155 $self->required_fields(qw(action card_number exp_date));
157 croak "Unsupported transaction type: $content{type}"
159 ! grep lc($content{type}) eq lc($_),
160 values %{+CARD_TYPES};
162 croak 'Unsupported action'
163 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
165 $content{currency} = uc($content{currency} || 'CAD');
166 croak "Unknown currency code ", $content{currency}
167 unless $content{currency} =~ /^(CAD|USD)$/;
169 my %data = $self->get_remap_fields(qw(
170 xxxCard_Number card_number
182 xxxShippingName ship_name
183 xxxShippingCompany ship_company
184 xxxShippingAddress ship_address
185 xxxShippingCity ship_city
186 xxxShippingProvince ship_state
187 xxxShippingPostal ship_zip
188 xxxShippingCountry ship_country
189 xxxShippingPhone ship_phone
190 xxxShippingEmail ship_email
193 $data{MerchantNumber} = $self->merchant_id;
195 $data{xxxCard_Number} =~ tr/- //d;
196 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
198 my ($y, $m) = $self->parse_expdate($content{exp_date});
199 $data{xxxCCYear} = sprintf '%.4u' => $y;
200 $data{xxxCCMonth} = sprintf '%.2u' => $m;
202 if (defined $content{cvv2} && $content{cvv2} ne '') {
204 $data{CVV2Indicator} = $content{cvv2};
207 $data{CVV2Indicator} = '';
210 if (ref $content{description}) {
211 $data{Products} = join '|' => map $self->prod_string(
213 taxes => $content{taxes},
215 @{ $content{description} };
217 $self->required_fields(qw(amount));
218 $data{Products} = $self->prod_string(
220 taxes => $content{taxes},
221 amount => $content{amount},
222 description => $content{description},
226 # The encode() makes sure to a) strip off non-Latin-1 characters, and
227 # b) turn off the utf8 flag, which confuses XML::Simple
228 encode('ISO-8859-1', xml_out(\%data,
230 RootName => 'TranxRequest',
231 SuppressEmpty => undef,
232 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
236 # Map the various fields from the response, and put their values into our
237 # object for retrieval.
240 my ($self, $data, %map) = @_;
242 while (my ($k, $v) = each %map) {
244 $self->$k($data->{$v});
248 sub extract_tax_amounts {
249 my ($self, $response) = @_;
253 my $products = $response->{Products};
254 return unless $products;
256 foreach my $node (@$products) {
257 my $flags = $node->{flags};
259 grep($_ eq '{TAX}', @$flags) &&
260 grep($_ eq '{CALCULATED}', @$flags))
262 $tax_amounts{ $node->{code} } = $node->{subtotal};
269 # Parse the server's response and set various fields
272 my ($self, $response) = @_;
274 $self->server_response($response);
276 local $/ = "\n"; # Make sure to avoid bug #17687
278 $response = xml_in($response,
279 ForceArray => [qw(product flag)],
280 GroupTags => { qw(Products product flags flag) },
282 SuppressEmpty => undef,
285 $self->infuse($response,
286 result_code => 'Page',
287 error_message => 'Verbiage',
288 authorization => 'ApprovalCode',
289 avs_response => 'AVSResponseCode',
290 cvv2_response => 'CVV2ResponseCode',
292 receipt_number => 'ReceiptNumber',
293 sales_number => 'SalesOrderNumber',
298 cardholder => 'xxxName',
299 card_type => 'CardType',
300 total_amount => 'TotalAmount',
303 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
305 # Completely undocumented field that sometimes override <Verbiage>
306 $self->error_message($response->{Error}) if $response->{Error};
308 # Delete error_message if transaction was successful
309 $self->error_message(undef) if $self->is_success;
311 $self->card_type(CARD_TYPES->{$self->card_type});
313 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
321 croak "Missing required argument 'merchant_id'"
322 unless defined $self->{merchant_id};
324 my ($page, $response, %headers) =
331 xxxRequestMode => 'X',
332 xxxRequestData => $self->to_xml,
336 croak 'Error connecting to server' unless $page;
337 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
339 # The response is marked UTF-8, but it's really Latin-1. Sigh.
340 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
342 $self->parse_response($page);
353 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
357 use Business::OnlinePayment;
359 $txn = new Business::OnlinePayment 'InternetSecure',
360 merchant_id => '0000';
363 action => 'Normal Authorization',
365 type => 'Visa', # Optional
366 card_number => '4111 1111 1111 1111',
367 exp_date => '2004-07',
368 cvv2 => '000', # Optional
370 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
372 address => '123 Street',
373 city => 'Metropolis',
377 phone => '(555) 555-1212',
378 email => 'fbriere@fbriere.net',
383 description => 'Test transaction',
388 if ($txn->is_success) {
389 print "Card processed successfully: " . $tx->authorization . "\n";
391 print "Card was rejected: " . $tx->error_message . "\n";
396 C<Business::OnlinePayment::InternetSecure> is an implementation of
397 C<Business::OnlinePayment> that allows for processing online credit card
398 payments through InternetSecure.
400 See L<Business::OnlinePayment> for more information about the generic
401 Business::OnlinePayment interface.
405 Object creation is done via C<Business::OnlinePayment>; see its manpage for
406 details. The B<merchant_id> processor option is required, and corresponds
407 to the merchant ID assigned to you by InternetSecure.
411 =head2 Transaction setup and transmission
415 =item content( CONTENT )
417 Sets up the data prior to a transaction. CONTENT is an associative array
418 (hash), containing some of the following fields:
422 =item action (required)
424 What to do with the transaction. Only C<Normal Authorization> is supported
429 Transaction type, being one of the following:
437 =item - American Express
445 (This is actually ignored for the moment, and can be left blank or undefined.)
447 =item card_number (required)
449 Credit card number. Spaces and dashes are automatically removed.
451 =item exp_date (required)
453 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
454 any syntax, this module is rather lax regarding what it will accept. The
455 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
460 Three- or four-digit verification code printed on the card. This can be left
461 blank or undefined, in which case no check will be performed. Whether or not a
462 transaction will be declined in case of a mismatch depends on the merchant
463 account configuration.
465 This number may be called Card Verification Value (CVV2), Card Validation
466 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
470 A short description of the transaction. See L<"Products list syntax"> for
471 an alternate syntax that allows a list of products to be specified.
473 =item amount (usually required)
475 Total amount to be billed, excluding taxes if they are to be added separately
478 This field is required if B<description> is a string, but should be left
479 undefined if B<description> contains a list of products instead, as outlined
480 in L<"Products list syntax">.
484 Currency of all amounts for this order. This can currently be either
485 C<CAD> (default) or C<USD>.
489 Taxes to be added automatically to B<amount> by InternetSecure. Available
490 taxes are C<GST>, C<PST> and C<HST>.
492 This argument can either be a single string of taxes concatenated with spaces
493 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
496 =item name / company / address / city / state / zip / country / phone / email
498 Customer information. B<country> should be a two-letter code taken from ISO
505 Submit the transaction to InternetSecure.
509 =head2 Post-submission methods
515 Returns true if the transaction was submitted successfully.
519 Response code returned by InternetSecure.
521 =item error_message()
523 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
524 should not rely on this to test whether a transaction was successful; use
525 B<is_success>() instead.)
527 =item receipt_number()
529 Receipt number (a string, actually) of this transaction, unique to all
530 InternetSecure transactions.
534 Sales order number of this transaction. This is a number, unique to each
535 merchant, which is incremented by 1 each time.
539 Universally Unique Identifier associated to this transaction. This is a
540 128-bit value returned as a 36-character string such as
541 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
544 B<guid>() is provided as an alias to this method.
546 =item authorization()
548 Authorization code for this transaction.
550 =item avs_response() / cvv2_response()
552 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
553 the list of possible values.
557 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
561 Total amount billed for this order, including taxes.
565 Returns a I<reference> to a hash that maps taxes, which were listed under the
566 B<taxes> argument to B<submit>(), to the amount that was calculated by
571 Cardholder's name. This is currently a mere copy of the B<name> field passed
576 Type of the credit card used for the submitted order, being one of the
585 =item - American Express
599 =head2 Products list syntax
601 Optionally, the B<description> field of B<content>() can contain a reference
602 to an array of products, instead of a simple string. Each element of this
603 array represents a different product, and must be a reference to a hash with
604 the following fields:
608 =item amount (required)
610 Unit price of this product.
614 Ordered quantity of this product.
618 Internal code for this product.
622 Description of this product
626 Taxes that should be automatically added to this product. If specified, this
627 overrides the B<taxes> field passed to B<content>().
631 When using a products list, the B<amount> field passed to B<content>() should
635 =head2 Character encoding
637 When using non-ASCII characters, all data provided to B<contents>() should
638 have been decoded beforehand via the C<Encode> module, unless your data is in
639 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
642 InternetSecure currently does not handle characters outside of ISO-8859-1, so
643 these will be replaced with C<?> before being transmitted.
653 L<Business::OnlinePayment>
657 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
659 =head1 COPYRIGHT AND LICENSE
661 Copyright (C) 2006 by Frédéric Brière
663 This library is free software; you can redistribute it and/or modify
664 it under the same terms as Perl itself, either Perl version 5.8.4 or,
665 at your option, any later version of Perl 5 you may have available.