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 order_number uuid guid
45 total_amount tax_amounts
46 avs_code cvv2_response
49 # Just in case someone tries to call tax_amounts() *before* submit()
50 $self->tax_amounts( {} );
53 # Backwards-compatible support for avs_response
54 sub avs_response { shift()->avs_code(@_) }
57 # Combine get_fields and remap_fields for convenience. Unlike OnlinePayment's
58 # remap_fields, this doesn't modify content(), and can therefore be called
59 # more than once. Also, unlike OnlinePayment's get_fields in 3.x, this doesn't
62 sub get_remap_fields {
63 my ($self, %map) = @_;
65 my %content = $self->content();
68 while (my ($to, $from) = each %map) {
69 $data{$to} = $content{$from};
75 # Since there's no standard format for expiration dates, we try to do our best
78 my ($self, $str) = @_;
84 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
85 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
86 /^(\d\d)[.-](\d\d)$/) { # YY-MM
88 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
89 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
90 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
93 croak "Unable to parse expiration date: $str";
96 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
101 # Convert a single product into a product string
104 my ($self, $currency, %data) = @_;
106 croak "Missing amount in product" unless defined $data{amount};
108 my @flags = ($currency);
111 if (ref $data{taxes}) {
112 @taxes = @{ $data{taxes} };
113 } elsif ($data{taxes}) {
114 @taxes = split ' ' => $data{taxes};
118 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/i;
122 if ($self->test_transaction) {
123 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
127 sprintf('%.2f' => $data{amount}),
128 $data{quantity} || 1,
129 _esc _def $data{sku},
130 _esc _def $data{description},
131 join('' => map "{$_}" => @flags),
135 # Generate the XML document for this transaction
140 my %content = $self->content;
142 # Backwards-compatible support for exp_date
143 if (exists $content{exp_date} && ! exists $content{expiration}) {
144 $content{expiration} = delete $content{exp_date};
145 $self->content(%content);
148 $self->required_fields(qw(action card_number expiration));
150 croak "Unsupported transaction type: $content{type}"
152 ! grep lc($content{type}) eq lc($_),
153 values %{+CARD_TYPES};
155 croak 'Unsupported action'
156 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
158 $content{currency} = uc($content{currency} || 'CAD');
159 croak "Unknown currency code ", $content{currency}
160 unless $content{currency} =~ /^(CAD|USD)$/;
162 my %data = $self->get_remap_fields(qw(
163 xxxCard_Number card_number
175 xxxShippingName ship_name
176 xxxShippingCompany ship_company
177 xxxShippingAddress ship_address
178 xxxShippingCity ship_city
179 xxxShippingProvince ship_state
180 xxxShippingPostal ship_zip
181 xxxShippingCountry ship_country
182 xxxShippingPhone ship_phone
183 xxxShippingEmail ship_email
186 $data{MerchantNumber} = $self->merchant_id;
188 $data{xxxCard_Number} =~ tr/- //d;
189 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
191 my ($y, $m) = $self->parse_expdate($content{expiration});
192 $data{xxxCCYear} = sprintf '%.4u' => $y;
193 $data{xxxCCMonth} = sprintf '%.2u' => $m;
195 if (defined $content{cvv2} && $content{cvv2} ne '') {
197 $data{CVV2Indicator} = $content{cvv2};
200 $data{CVV2Indicator} = '';
203 if (ref $content{description}) {
204 $data{Products} = join '|' => map $self->prod_string(
206 taxes => $content{taxes},
208 @{ $content{description} };
210 $self->required_fields(qw(amount));
211 $data{Products} = $self->prod_string(
213 taxes => $content{taxes},
214 amount => $content{amount},
215 description => $content{description},
219 # The encode() makes sure to a) strip off non-Latin-1 characters, and
220 # b) turn off the utf8 flag, which confuses XML::Simple
221 encode('ISO-8859-1', xml_out(\%data,
223 RootName => 'TranxRequest',
224 SuppressEmpty => undef,
225 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
229 # Map the various fields from the response, and put their values into our
230 # object for retrieval.
233 my ($self, $data, %map) = @_;
235 while (my ($k, $v) = each %map) {
237 $self->$k($data->{$v});
241 sub extract_tax_amounts {
242 my ($self, $response) = @_;
246 my $products = $response->{Products};
247 return unless $products;
249 foreach my $node (@$products) {
250 my $flags = $node->{flags};
252 grep($_ eq '{TAX}', @$flags) &&
253 grep($_ eq '{CALCULATED}', @$flags))
255 $tax_amounts{ $node->{code} } = $node->{subtotal};
262 # Parse the server's response and set various fields
265 my ($self, $response) = @_;
267 $self->server_response($response);
269 local $/ = "\n"; # Make sure to avoid bug #17687
271 $response = xml_in($response,
272 ForceArray => [qw(product flag)],
273 GroupTags => { qw(Products product flags flag) },
275 SuppressEmpty => undef,
278 $self->infuse($response,
279 result_code => 'Page',
280 error_message => 'Verbiage',
281 authorization => 'ApprovalCode',
282 avs_code => 'AVSResponseCode',
283 cvv2_response => 'CVV2ResponseCode',
285 receipt_number => 'ReceiptNumber',
286 order_number => 'SalesOrderNumber',
291 cardholder => 'xxxName',
292 card_type => 'CardType',
293 total_amount => 'TotalAmount',
296 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
298 # Completely undocumented field that sometimes override <Verbiage>
299 $self->error_message($response->{Error}) if $response->{Error};
301 # Delete error_message if transaction was successful
302 $self->error_message(undef) if $self->is_success;
304 $self->card_type(CARD_TYPES->{$self->card_type});
306 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
314 croak "Missing required argument 'merchant_id'"
315 unless defined $self->{merchant_id};
317 my ($page, $response, %headers) =
324 xxxRequestMode => 'X',
325 xxxRequestData => $self->to_xml,
329 croak 'Error connecting to server' unless $page;
330 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
332 # The response is marked UTF-8, but it's really Latin-1. Sigh.
333 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
335 $self->parse_response($page);
346 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
350 use Business::OnlinePayment;
352 $txn = new Business::OnlinePayment 'InternetSecure',
353 merchant_id => '0000';
356 action => 'Normal Authorization',
358 type => 'Visa', # Optional
359 card_number => '4111 1111 1111 1111',
360 expiration => '2004-07',
361 cvv2 => '000', # Optional
363 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
365 address => '123 Street',
366 city => 'Metropolis',
370 phone => '(555) 555-1212',
371 email => 'fbriere@fbriere.net',
376 description => 'Test transaction',
381 if ($txn->is_success) {
382 print "Card processed successfully: " . $tx->authorization . "\n";
384 print "Card was rejected: " . $tx->error_message . "\n";
389 C<Business::OnlinePayment::InternetSecure> is an implementation of
390 C<Business::OnlinePayment> that allows for processing online credit card
391 payments through InternetSecure.
393 See L<Business::OnlinePayment> for more information about the generic
394 Business::OnlinePayment interface.
398 Object creation is done via C<Business::OnlinePayment>; see its manpage for
399 details. The B<merchant_id> processor option is required, and corresponds
400 to the merchant ID assigned to you by InternetSecure.
404 =head2 Transaction setup and transmission
408 =item content( CONTENT )
410 Sets up the data prior to a transaction. CONTENT is an associative array
411 (hash), containing some of the following fields:
415 =item action (required)
417 What to do with the transaction. Only C<Normal Authorization> is supported
422 Transaction type, being one of the following:
430 =item - American Express
438 (This is actually ignored for the moment, and can be left blank or undefined.)
440 =item card_number (required)
442 Credit card number. Spaces and dashes are automatically removed.
444 =item expiration (required)
446 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
447 any syntax, this module is rather lax regarding what it will accept. The
448 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
453 Three- or four-digit verification code printed on the card. This can be left
454 blank or undefined, in which case no check will be performed. Whether or not a
455 transaction will be declined in case of a mismatch depends on the merchant
456 account configuration.
458 This number may be called Card Verification Value (CVV2), Card Validation
459 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
463 A short description of the transaction. See L<"Products list syntax"> for
464 an alternate syntax that allows a list of products to be specified.
466 =item amount (usually required)
468 Total amount to be billed, excluding taxes if they are to be added separately
471 This field is required if B<description> is a string, but should be left
472 undefined if B<description> contains a list of products instead, as outlined
473 in L<"Products list syntax">.
477 Currency of all amounts for this order. This can currently be either
478 C<CAD> (default) or C<USD>.
482 Taxes to be added automatically to B<amount> by InternetSecure. Available
483 taxes are C<GST>, C<PST> and C<HST>.
485 This argument can either be a single string of taxes concatenated with spaces
486 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
489 =item name / company / address / city / state / zip / country / phone / email
491 Customer information. B<country> should be a two-letter code taken from ISO
498 Submit the transaction to InternetSecure.
502 =head2 Post-submission methods
508 Returns true if the transaction was submitted successfully.
512 Response code returned by InternetSecure.
514 =item error_message()
516 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
517 should not rely on this to test whether a transaction was successful; use
518 B<is_success>() instead.)
520 =item receipt_number()
522 Receipt number (a string, actually) of this transaction, unique to all
523 InternetSecure transactions.
527 Sales order number of this transaction. This is a number, unique to each
528 merchant, which is incremented by 1 each time.
532 Universally Unique Identifier associated to this transaction. This is a
533 128-bit value returned as a 36-character string such as
534 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
537 B<guid>() is provided as an alias to this method.
539 =item authorization()
541 Authorization code for this transaction.
543 =item avs_code() / cvv2_response()
545 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
546 the list of possible values.
550 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
554 Total amount billed for this order, including taxes.
558 Returns a I<reference> to a hash that maps taxes, which were listed under the
559 B<taxes> argument to B<submit>(), to the amount that was calculated by
564 Cardholder's name. This is currently a mere copy of the B<name> field passed
569 Type of the credit card used for the submitted order, being one of the
578 =item - American Express
592 =head2 Products list syntax
594 Optionally, the B<description> field of B<content>() can contain a reference
595 to an array of products, instead of a simple string. Each element of this
596 array represents a different product, and must be a reference to a hash with
597 the following fields:
601 =item amount (required)
603 Unit price of this product.
607 Ordered quantity of this product.
611 Internal code for this product.
615 Description of this product
619 Taxes that should be automatically added to this product. If specified, this
620 overrides the B<taxes> field passed to B<content>().
624 When using a products list, the B<amount> field passed to B<content>() should
628 =head2 Character encoding
630 When using non-ASCII characters, all data provided to B<contents>() should
631 have been decoded beforehand via the C<Encode> module, unless your data is in
632 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
635 InternetSecure currently does not handle characters outside of ISO-8859-1, so
636 these will be replaced with C<?> before being transmitted.
646 L<Business::OnlinePayment>
650 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
652 =head1 COPYRIGHT AND LICENSE
654 Copyright (C) 2006 by Frédéric Brière
656 This library is free software; you can redistribute it and/or modify
657 it under the same terms as Perl itself, either Perl version 5.8.4 or,
658 at your option, any later version of Perl 5 you may have available.