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 $self->required_fields(qw(action card_number exp_date));
140 croak "Unsupported transaction type: $content{type}"
142 ! grep lc($content{type}) eq lc($_),
143 values %{+CARD_TYPES};
145 croak 'Unsupported action'
146 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
148 $content{currency} = uc($content{currency} || 'CAD');
149 croak "Unknown currency code ", $content{currency}
150 unless $content{currency} =~ /^(CAD|USD)$/;
152 my %data = $self->get_remap_fields(qw(
153 xxxCard_Number card_number
165 xxxShippingName ship_name
166 xxxShippingCompany ship_company
167 xxxShippingAddress ship_address
168 xxxShippingCity ship_city
169 xxxShippingProvince ship_state
170 xxxShippingPostal ship_zip
171 xxxShippingCountry ship_country
172 xxxShippingPhone ship_phone
173 xxxShippingEmail ship_email
176 $data{MerchantNumber} = $self->merchant_id;
178 $data{xxxCard_Number} =~ tr/- //d;
179 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
181 my ($y, $m) = $self->parse_expdate($content{exp_date});
182 $data{xxxCCYear} = sprintf '%.4u' => $y;
183 $data{xxxCCMonth} = sprintf '%.2u' => $m;
185 if (defined $content{cvv2} && $content{cvv2} ne '') {
187 $data{CVV2Indicator} = $content{cvv2};
190 $data{CVV2Indicator} = '';
193 if (ref $content{description}) {
194 $data{Products} = join '|' => map $self->prod_string(
196 taxes => $content{taxes},
198 @{ $content{description} };
200 $self->required_fields(qw(amount));
201 $data{Products} = $self->prod_string(
203 taxes => $content{taxes},
204 amount => $content{amount},
205 description => $content{description},
209 # The encode() makes sure to a) strip off non-Latin-1 characters, and
210 # b) turn off the utf8 flag, which confuses XML::Simple
211 encode('ISO-8859-1', xml_out(\%data,
213 RootName => 'TranxRequest',
214 SuppressEmpty => undef,
215 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
219 # Map the various fields from the response, and put their values into our
220 # object for retrieval.
223 my ($self, $data, %map) = @_;
225 while (my ($k, $v) = each %map) {
227 $self->$k($data->{$v});
231 sub extract_tax_amounts {
232 my ($self, $response) = @_;
236 my $products = $response->{Products};
237 return unless $products;
239 foreach my $node (@$products) {
240 my $flags = $node->{flags};
242 grep($_ eq '{TAX}', @$flags) &&
243 grep($_ eq '{CALCULATED}', @$flags))
245 $tax_amounts{ $node->{code} } = $node->{subtotal};
252 # Parse the server's response and set various fields
255 my ($self, $response) = @_;
257 $self->server_response($response);
259 local $/ = "\n"; # Make sure to avoid bug #17687
261 $response = xml_in($response,
262 ForceArray => [qw(product flag)],
263 GroupTags => { qw(Products product flags flag) },
265 SuppressEmpty => undef,
268 $self->infuse($response,
269 result_code => 'Page',
270 error_message => 'Verbiage',
271 authorization => 'ApprovalCode',
272 avs_response => 'AVSResponseCode',
273 cvv2_response => 'CVV2ResponseCode',
275 receipt_number => 'ReceiptNumber',
276 sales_number => 'SalesOrderNumber',
281 cardholder => 'xxxName',
282 card_type => 'CardType',
283 total_amount => 'TotalAmount',
286 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
288 # Completely undocumented field that sometimes override <Verbiage>
289 $self->error_message($response->{Error}) if $response->{Error};
291 # Delete error_message if transaction was successful
292 $self->error_message(undef) if $self->is_success;
294 $self->card_type(CARD_TYPES->{$self->card_type});
296 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
304 croak "Missing required argument 'merchant_id'"
305 unless defined $self->{merchant_id};
307 my ($page, $response, %headers) =
314 xxxRequestMode => 'X',
315 xxxRequestData => $self->to_xml,
319 croak 'Error connecting to server' unless $page;
320 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
322 # The response is marked UTF-8, but it's really Latin-1. Sigh.
323 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
325 $self->parse_response($page);
336 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
340 use Business::OnlinePayment;
342 $txn = new Business::OnlinePayment 'InternetSecure',
343 merchant_id => '0000';
346 action => 'Normal Authorization',
348 type => 'Visa', # Optional
349 card_number => '4111 1111 1111 1111',
350 exp_date => '2004-07',
351 cvv2 => '000', # Optional
353 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
355 address => '123 Street',
356 city => 'Metropolis',
360 phone => '(555) 555-1212',
361 email => 'fbriere@fbriere.net',
366 description => 'Test transaction',
371 if ($txn->is_success) {
372 print "Card processed successfully: " . $tx->authorization . "\n";
374 print "Card was rejected: " . $tx->error_message . "\n";
379 C<Business::OnlinePayment::InternetSecure> is an implementation of
380 C<Business::OnlinePayment> that allows for processing online credit card
381 payments through InternetSecure.
383 See L<Business::OnlinePayment> for more information about the generic
384 Business::OnlinePayment interface.
388 Object creation is done via C<Business::OnlinePayment>; see its manpage for
389 details. The B<merchant_id> processor option is required, and corresponds
390 to the merchant ID assigned to you by InternetSecure.
394 =head2 Transaction setup and transmission
398 =item content( CONTENT )
400 Sets up the data prior to a transaction. CONTENT is an associative array
401 (hash), containing some of the following fields:
405 =item action (required)
407 What to do with the transaction. Only C<Normal Authorization> is supported
412 Transaction type, being one of the following:
420 =item - American Express
428 (This is actually ignored for the moment, and can be left blank or undefined.)
430 =item card_number (required)
432 Credit card number. Spaces and dashes are automatically removed.
434 =item exp_date (required)
436 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
437 any syntax, this module is rather lax regarding what it will accept. The
438 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
443 Three- or four-digit verification code printed on the card. This can be left
444 blank or undefined, in which case no check will be performed. Whether or not a
445 transaction will be declined in case of a mismatch depends on the merchant
446 account configuration.
448 This number may be called Card Verification Value (CVV2), Card Validation
449 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
453 A short description of the transaction. See L<"Products list syntax"> for
454 an alternate syntax that allows a list of products to be specified.
456 =item amount (usually required)
458 Total amount to be billed, excluding taxes if they are to be added separately
461 This field is required if B<description> is a string, but should be left
462 undefined if B<description> contains a list of products instead, as outlined
463 in L<"Products list syntax">.
467 Currency of all amounts for this order. This can currently be either
468 C<CAD> (default) or C<USD>.
472 Taxes to be added automatically to B<amount> by InternetSecure. Available
473 taxes are C<GST>, C<PST> and C<HST>.
475 This argument can either be a single string of taxes concatenated with spaces
476 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
479 =item name / company / address / city / state / zip / country / phone / email
481 Customer information. B<country> should be a two-letter code taken from ISO
488 Submit the transaction to InternetSecure.
492 =head2 Post-submission methods
498 Returns true if the transaction was submitted successfully.
502 Response code returned by InternetSecure.
504 =item error_message()
506 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
507 should not rely on this to test whether a transaction was successful; use
508 B<is_success>() instead.)
510 =item receipt_number()
512 Receipt number (a string, actually) of this transaction, unique to all
513 InternetSecure transactions.
517 Sales order number of this transaction. This is a number, unique to each
518 merchant, which is incremented by 1 each time.
522 Universally Unique Identifier associated to this transaction. This is a
523 128-bit value returned as a 36-character string such as
524 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
527 B<guid>() is provided as an alias to this method.
529 =item authorization()
531 Authorization code for this transaction.
533 =item avs_response() / cvv2_response()
535 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
536 the list of possible values.
540 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
544 Total amount billed for this order, including taxes.
548 Returns a I<reference> to a hash that maps taxes, which were listed under the
549 B<taxes> argument to B<submit>(), to the amount that was calculated by
554 Cardholder's name. This is currently a mere copy of the B<name> field passed
559 Type of the credit card used for the submitted order, being one of the
568 =item - American Express
582 =head2 Products list syntax
584 Optionally, the B<description> field of B<content>() can contain a reference
585 to an array of products, instead of a simple string. Each element of this
586 array represents a different product, and must be a reference to a hash with
587 the following fields:
591 =item amount (required)
593 Unit price of this product.
597 Ordered quantity of this product.
601 Internal code for this product.
605 Description of this product
609 Taxes that should be automatically added to this product. If specified, this
610 overrides the B<taxes> field passed to B<content>().
614 When using a products list, the B<amount> field passed to B<content>() should
618 =head2 Character encoding
620 When using non-ASCII characters, all data provided to B<contents>() should
621 have been decoded beforehand via the C<Encode> module, unless your data is in
622 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
625 InternetSecure currently does not handle characters outside of ISO-8859-1, so
626 these will be replaced with C<?> before being transmitted.
636 L<Business::OnlinePayment>
640 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
642 =head1 COPYRIGHT AND LICENSE
644 Copyright (C) 2006 by Frédéric Brière
646 This library is free software; you can redistribute it and/or modify
647 it under the same terms as Perl itself, either Perl version 5.8.4 or,
648 at your option, any later version of Perl 5 you may have available.