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
46 avs_response cvv2_response
49 # Just in case someone tries to call taxes() *before* submit()
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);
111 foreach (ref $data{taxes} ?
113 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'
141 if $content{type} && $content{type} !~
142 /^(Visa|MasterCard|American Express|Discover)$/i;
144 croak 'Unsupported action'
145 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
147 $content{currency} = uc($content{currency} || 'CAD');
148 croak "Unknown currency code ", $content{currency}
149 unless $content{currency} =~ /^(CAD|USD)$/;
151 $content{taxes} = uc($content{taxes} || '');
153 my %data = $self->get_remap_fields(qw(
154 xxxCard_Number card_number
166 xxxShippingName ship_name
167 xxxShippingCompany ship_company
168 xxxShippingAddress ship_address
169 xxxShippingCity ship_city
170 xxxShippingProvince ship_state
171 xxxShippingPostal ship_zip
172 xxxShippingCountry ship_country
173 xxxShippingPhone ship_phone
174 xxxShippingEmail ship_email
177 $data{MerchantNumber} = $self->merchant_id;
179 $data{xxxCard_Number} =~ tr/- //d;
180 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
182 my ($y, $m) = $self->parse_expdate($content{exp_date});
183 $data{xxxCCYear} = sprintf '%.4u' => $y;
184 $data{xxxCCMonth} = sprintf '%.2u' => $m;
186 if (defined $content{cvv2} && $content{cvv2} ne '') {
188 $data{CVV2Indicator} = $content{cvv2};
191 $data{CVV2Indicator} = '';
194 if (ref $content{description}) {
195 $data{Products} = join '|' => map $self->prod_string(
197 taxes => $content{taxes},
199 @{ $content{description} };
201 $self->required_fields(qw(amount));
202 $data{Products} = $self->prod_string(
204 taxes => $content{taxes},
205 amount => $content{amount},
206 description => $content{description},
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});
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 $taxes{ $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->taxes( { $self->extract_taxes($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
426 (This is actually ignored for the moment, and can be left blank or undefined.)
428 =item card_number (required)
430 Credit card number. Spaces and dashes are automatically removed.
432 =item exp_date (required)
434 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
435 any syntax, this module is rather lax regarding what it will accept. The
436 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
441 Three- or four-digit verification code printed on the card. This can be left
442 blank or undefined, in which case no check will be performed. Whether or not a
443 transaction will be declined in case of a mismatch depends on the merchant
444 account configuration.
446 This number may be called Card Verification Value (CVV2), Card Validation
447 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
451 A short description of the transaction. See L<"Products list syntax"> for
452 an alternate syntax that allows a list of products to be specified.
454 =item amount (usually required)
456 Total amount to be billed, excluding taxes if they are to be added separately
459 This field is required if B<description> is a string, but should be left
460 undefined if B<description> contains a list of products instead, as outlined
461 in L<"Products list syntax">.
465 Currency of all amounts for this order. This can currently be either
466 C<CAD> (default) or C<USD>.
470 Taxes to be added automatically to B<amount> by InternetSecure.
472 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
473 by concatenating them with spaces, such as C<GST HST>.
475 =item name / company / address / city / state / zip / country / phone / email
477 Customer information. B<country> should be a two-letter code taken from ISO
484 Submit the transaction to InternetSecure.
488 =head2 Post-submission methods
494 Returns true if the transaction was submitted successfully.
498 Response code returned by InternetSecure.
500 =item error_message()
502 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
503 should not rely on this to test whether a transaction was successful; use
504 B<is_success>() instead.)
506 =item receipt_number()
508 Receipt number (a string, actually) of this transaction, unique to all
509 InternetSecure transactions.
513 Sales order number of this transaction. This is a number, unique to each
514 merchant, which is incremented by 1 each time.
518 Universally Unique Identifier associated to this transaction. This is a
519 128-bit value returned as a 36-character string such as
520 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
523 B<guid>() is provided as an alias to this method.
525 =item authorization()
527 Authorization code for this transaction.
529 =item avs_response() / cvv2_response()
531 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
532 the list of possible values.
536 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
540 Total amount billed for this order, including taxes.
544 Returns a I<reference> to a hash that maps tax names (such as C<GST>) to the
545 amount that was billed for each.
549 Cardholder's name. This is currently a mere copy of the B<name> field passed
554 Type of the credit card used for the submitted order, being one of the
563 =item - American Express
575 =head2 Products list syntax
577 Optionally, the B<description> field of B<content>() can contain a reference
578 to an array of products, instead of a simple string. Each element of this
579 array represents a different product, and must be a reference to a hash with
580 the following fields:
586 Unit price of this product.
590 Ordered quantity of this product.
594 Internal code for this product.
598 Description of this product
602 Taxes that should be automatically added to this product. If specified, this
603 overrides the B<taxes> field passed to B<content>().
607 When using a products list, the B<amount> field passed to B<content>() should
611 =head2 Character encoding
613 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
614 characters are theoretically available when submitting information via
615 B<submit>(). (Further restrictions may be imposed by InternetSecure itself.)
617 When using non-ASCII characters, all data provided to B<submit>() should either
618 be in the current native encoding (typically latin-1, unless it was modified
619 via the C<encoding> pragma), or be decoded via the C<Encode> module.
620 Conversely, all data returned after calling B<submit>() will be automatically
631 L<Business::OnlinePayment>
635 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
637 =head1 COPYRIGHT AND LICENSE
639 Copyright (C) 2006 by Frédéric Brière
641 This library is free software; you can redistribute it and/or modify
642 it under the same terms as Perl itself, either Perl version 5.8.4 or,
643 at your option, any later version of Perl 5 you may have available.