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 SUCCESS_CODES => qw(2000 90000 900P1);
20 use constant CARD_TYPES => {
23 AX => 'American Express', # FIXME: AM?
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 # Combine get_fields and remap_fields for convenience
67 sub get_remap_fields {
68 my ($self, %map) = @_;
70 $self->remap_fields(reverse %map);
71 my %data = $self->get_fields(keys %map);
76 # Since there's no standard format for expiration dates, we try to do our best
79 my ($self, $str) = @_;
85 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
86 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
87 /^(\d\d)[.-](\d\d)$/) { # YY-MM
89 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
90 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
91 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
94 croak "Unable to parse expiration date: $str";
97 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
102 # Convert a single product into a product string
105 my ($self, $currency, %data) = @_;
107 croak "Missing amount in product" unless defined $data{amount};
109 my @flags = ($currency);
112 if (ref $data{taxes}) {
113 @taxes = @{ $data{taxes} };
114 } elsif ($data{taxes}) {
115 @taxes = split ' ' => $data{taxes};
119 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/i;
123 if ($self->test_transaction) {
124 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
128 sprintf('%.2f' => $data{amount}),
129 $data{quantity} || 1,
130 _esc _def $data{sku},
131 _esc _def $data{description},
132 join('' => map "{$_}" => @flags),
136 # Generate the XML document for this transaction
141 my %content = $self->content;
143 $self->required_fields(qw(action card_number exp_date));
145 croak 'Unsupported transaction type'
146 if $content{type} && $content{type} !~
147 /^(Visa|MasterCard|American Express|Discover)$/i;
149 croak 'Unsupported action'
150 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
152 $content{currency} = uc($content{currency} || 'CAD');
153 croak "Unknown currency code ", $content{currency}
154 unless $content{currency} =~ /^(CAD|USD)$/;
156 my %data = $self->get_remap_fields(qw(
157 xxxCard_Number card_number
169 xxxShippingName ship_name
170 xxxShippingCompany ship_company
171 xxxShippingAddress ship_address
172 xxxShippingCity ship_city
173 xxxShippingProvince ship_state
174 xxxShippingPostal ship_zip
175 xxxShippingCountry ship_country
176 xxxShippingPhone ship_phone
177 xxxShippingEmail ship_email
180 $data{MerchantNumber} = $self->merchant_id;
182 $data{xxxCard_Number} =~ tr/- //d;
183 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
185 my ($y, $m) = $self->parse_expdate($content{exp_date});
186 $data{xxxCCYear} = sprintf '%.4u' => $y;
187 $data{xxxCCMonth} = sprintf '%.2u' => $m;
189 if (defined $content{cvv2} && $content{cvv2} ne '') {
191 $data{CVV2Indicator} = $content{cvv2};
194 $data{CVV2Indicator} = '';
197 if (ref $content{description}) {
198 $data{Products} = join '|' => map $self->prod_string(
200 taxes => $content{taxes},
202 @{ $content{description} };
204 $self->required_fields(qw(amount));
205 $data{Products} = $self->prod_string(
207 taxes => $content{taxes},
208 amount => $content{amount},
209 description => $content{description},
216 RootName => 'TranxRequest',
217 SuppressEmpty => undef,
218 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
222 # Map the various fields from the response, and put their values into our
223 # object for retrieval.
226 my ($self, $data, %map) = @_;
228 while (my ($k, $v) = each %map) {
230 $self->$k($data->{$v});
234 sub extract_tax_amounts {
235 my ($self, $response) = @_;
239 my $products = $response->{Products};
240 return unless $products;
242 foreach my $node (@$products) {
243 my $flags = $node->{flags};
245 grep($_ eq '{TAX}', @$flags) &&
246 grep($_ eq '{CALCULATED}', @$flags))
248 $tax_amounts{ $node->{code} } = $node->{subtotal};
255 # Parse the server's response and set various fields
258 my ($self, $response) = @_;
260 $self->server_response($response);
262 local $/ = "\n"; # Make sure to avoid bug #17687
264 $response = xml_in($response,
265 ForceArray => [qw(product flag)],
266 GroupTags => { qw(Products product flags flag) },
268 SuppressEmpty => undef,
271 $self->infuse($response,
272 result_code => 'Page',
273 error_message => 'Verbiage',
274 authorization => 'ApprovalCode',
275 avs_response => 'AVSResponseCode',
276 cvv2_response => 'CVV2ResponseCode',
278 receipt_number => 'ReceiptNumber',
279 sales_number => 'SalesOrderNumber',
284 cardholder => 'xxxName',
285 card_type => 'CardType',
286 total_amount => 'TotalAmount',
289 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
291 # Completely undocumented field that sometimes override <Verbiage>
292 $self->error_message($response->{Error}) if $response->{Error};
294 # Delete error_message if transaction was successful
295 $self->error_message(undef) if $self->is_success;
297 $self->card_type(CARD_TYPES->{$self->card_type});
299 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
307 croak "Missing required argument 'merchant_id'"
308 unless defined $self->{merchant_id};
310 my ($page, $response, %headers) =
317 xxxRequestMode => 'X',
318 xxxRequestData => $self->to_xml,
322 croak 'Error connecting to server' unless $page;
323 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
325 # The response is marked UTF-8, but it's really Latin-1. Sigh.
326 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
328 $self->parse_response($page);
339 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
343 use Business::OnlinePayment;
345 $txn = new Business::OnlinePayment 'InternetSecure',
346 merchant_id => '0000';
349 action => 'Normal Authorization',
351 type => 'Visa', # Optional
352 card_number => '4111 1111 1111 1111',
353 exp_date => '2004-07',
354 cvv2 => '000', # Optional
356 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
358 address => '123 Street',
359 city => 'Metropolis',
363 phone => '(555) 555-1212',
364 email => 'fbriere@fbriere.net',
369 description => 'Test transaction',
374 if ($txn->is_success) {
375 print "Card processed successfully: " . $tx->authorization . "\n";
377 print "Card was rejected: " . $tx->error_message . "\n";
382 C<Business::OnlinePayment::InternetSecure> is an implementation of
383 C<Business::OnlinePayment> that allows for processing online credit card
384 payments through InternetSecure.
386 See L<Business::OnlinePayment> for more information about the generic
387 Business::OnlinePayment interface.
391 Object creation is done via C<Business::OnlinePayment>; see its manpage for
392 details. The B<merchant_id> processor option is required, and corresponds
393 to the merchant ID assigned to you by InternetSecure.
397 =head2 Transaction setup and transmission
401 =item content( CONTENT )
403 Sets up the data prior to a transaction. CONTENT is an associative array
404 (hash), containing some of the following fields:
408 =item action (required)
410 What to do with the transaction. Only C<Normal Authorization> is supported
415 Transaction type, being one of the following:
423 =item - American Express
429 (This is actually ignored for the moment, and can be left blank or undefined.)
431 =item card_number (required)
433 Credit card number. Spaces and dashes are automatically removed.
435 =item exp_date (required)
437 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
438 any syntax, this module is rather lax regarding what it will accept. The
439 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
444 Three- or four-digit verification code printed on the card. This can be left
445 blank or undefined, in which case no check will be performed. Whether or not a
446 transaction will be declined in case of a mismatch depends on the merchant
447 account configuration.
449 This number may be called Card Verification Value (CVV2), Card Validation
450 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
454 A short description of the transaction. See L<"Products list syntax"> for
455 an alternate syntax that allows a list of products to be specified.
457 =item amount (usually required)
459 Total amount to be billed, excluding taxes if they are to be added separately
462 This field is required if B<description> is a string, but should be left
463 undefined if B<description> contains a list of products instead, as outlined
464 in L<"Products list syntax">.
468 Currency of all amounts for this order. This can currently be either
469 C<CAD> (default) or C<USD>.
473 Taxes to be added automatically to B<amount> by InternetSecure. Available
474 taxes are C<GST>, C<PST> and C<HST>.
476 This argument can either be a single string of taxes concatenated with spaces
477 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
480 =item name / company / address / city / state / zip / country / phone / email
482 Customer information. B<country> should be a two-letter code taken from ISO
489 Submit the transaction to InternetSecure.
493 =head2 Post-submission methods
499 Returns true if the transaction was submitted successfully.
503 Response code returned by InternetSecure.
505 =item error_message()
507 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
508 should not rely on this to test whether a transaction was successful; use
509 B<is_success>() instead.)
511 =item receipt_number()
513 Receipt number (a string, actually) of this transaction, unique to all
514 InternetSecure transactions.
518 Sales order number of this transaction. This is a number, unique to each
519 merchant, which is incremented by 1 each time.
523 Universally Unique Identifier associated to this transaction. This is a
524 128-bit value returned as a 36-character string such as
525 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
528 B<guid>() is provided as an alias to this method.
530 =item authorization()
532 Authorization code for this transaction.
534 =item avs_response() / cvv2_response()
536 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
537 the list of possible values.
541 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
545 Total amount billed for this order, including taxes.
549 Returns a I<reference> to a hash that maps taxes, which were listed under the
550 B<taxes> argument to B<submit>(), to the amount that was calculated by
555 Cardholder's name. This is currently a mere copy of the B<name> field passed
560 Type of the credit card used for the submitted order, being one of the
569 =item - American Express
581 =head2 Products list syntax
583 Optionally, the B<description> field of B<content>() can contain a reference
584 to an array of products, instead of a simple string. Each element of this
585 array represents a different product, and must be a reference to a hash with
586 the following fields:
592 Unit price of this product.
596 Ordered quantity of this product.
600 Internal code for this product.
604 Description of this product
608 Taxes that should be automatically added to this product. If specified, this
609 overrides the B<taxes> field passed to B<content>().
613 When using a products list, the B<amount> field passed to B<content>() should
617 =head2 Character encoding
619 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
620 characters are theoretically available when submitting information via
621 B<submit>(). (Further restrictions may be imposed by InternetSecure itself.)
623 When using non-ASCII characters, all data provided to B<submit>() should either
624 be in the current native encoding (typically latin-1, unless it was modified
625 via the C<encoding> pragma), or be decoded via the C<Encode> module.
626 Conversely, all data returned after calling B<submit>() will be automatically
637 L<Business::OnlinePayment>
641 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
643 =head1 COPYRIGHT AND LICENSE
645 Copyright (C) 2006 by Frédéric Brière
647 This library is free software; you can redistribute it and/or modify
648 it under the same terms as Perl itself, either Perl version 5.8.4 or,
649 at your option, any later version of Perl 5 you may have available.