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.05';
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 renamed fields
54 sub avs_response { shift()->avs_code(@_) }
55 sub sales_number { shift()->order_number(@_) }
58 # Combine get_fields and remap_fields for convenience. Unlike OnlinePayment's
59 # remap_fields, this doesn't modify content(), and can therefore be called
60 # more than once. Also, unlike OnlinePayment's get_fields in 3.x, this doesn't
63 sub get_remap_fields {
64 my ($self, %map) = @_;
66 my %content = $self->content();
69 while (my ($to, $from) = each %map) {
70 $data{$to} = $content{$from};
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 # Backwards-compatible support for exp_date
144 if (exists $content{exp_date} && ! exists $content{expiration}) {
145 $content{expiration} = delete $content{exp_date};
146 $self->content(%content);
149 $self->required_fields(qw(action card_number expiration));
151 croak "Unsupported transaction type: $content{type}"
153 ! grep lc($content{type}) eq lc($_),
154 values %{+CARD_TYPES}, 'CC';
156 croak 'Unsupported action'
157 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
159 $content{currency} = uc($content{currency} || 'CAD');
160 croak "Unknown currency code ", $content{currency}
161 unless $content{currency} =~ /^(CAD|USD)$/;
163 my %data = $self->get_remap_fields(qw(
164 xxxCard_Number card_number
176 xxxShippingName ship_name
177 xxxShippingCompany ship_company
178 xxxShippingAddress ship_address
179 xxxShippingCity ship_city
180 xxxShippingProvince ship_state
181 xxxShippingPostal ship_zip
182 xxxShippingCountry ship_country
183 xxxShippingPhone ship_phone
184 xxxShippingEmail ship_email
187 $data{MerchantNumber} = $self->merchant_id;
189 $data{xxxCard_Number} =~ tr/- //d;
190 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
192 my ($y, $m) = $self->parse_expdate($content{expiration});
193 $data{xxxCCYear} = sprintf '%.4u' => $y;
194 $data{xxxCCMonth} = sprintf '%.2u' => $m;
196 if (defined $content{cvv2} && $content{cvv2} ne '') {
198 $data{CVV2Indicator} = $content{cvv2};
201 $data{CVV2Indicator} = '';
204 if (ref $content{description}) {
205 $data{Products} = join '|' => map $self->prod_string(
207 taxes => $content{taxes},
209 @{ $content{description} };
211 $self->required_fields(qw(amount));
212 $data{Products} = $self->prod_string(
214 taxes => $content{taxes},
215 amount => $content{amount},
216 description => $content{description},
220 # The encode() makes sure to a) strip off non-Latin-1 characters, and
221 # b) turn off the utf8 flag, which confuses XML::Simple
222 encode('ISO-8859-1', xml_out(\%data,
224 RootName => 'TranxRequest',
225 SuppressEmpty => undef,
226 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
230 # Map the various fields from the response, and put their values into our
231 # object for retrieval.
234 my ($self, $data, %map) = @_;
236 while (my ($k, $v) = each %map) {
238 $self->$k($data->{$v});
242 sub extract_tax_amounts {
243 my ($self, $response) = @_;
247 my $products = $response->{Products};
248 return unless $products;
250 foreach my $node (@$products) {
251 my $flags = $node->{flags};
253 grep($_ eq '{TAX}', @$flags) &&
254 grep($_ eq '{CALCULATED}', @$flags))
256 $tax_amounts{ $node->{code} } = $node->{subtotal};
263 # Parse the server's response and set various fields
266 my ($self, $response) = @_;
268 $self->server_response($response);
270 local $/ = "\n"; # Make sure to avoid bug #17687
272 $response = xml_in($response,
273 ForceArray => [qw(product flag)],
274 GroupTags => { qw(Products product flags flag) },
276 SuppressEmpty => undef,
279 $self->infuse($response,
280 result_code => 'Page',
281 error_message => 'Verbiage',
282 authorization => 'ApprovalCode',
283 avs_code => 'AVSResponseCode',
284 cvv2_response => 'CVV2ResponseCode',
286 receipt_number => 'ReceiptNumber',
287 order_number => 'SalesOrderNumber',
292 cardholder => 'xxxName',
293 card_type => 'CardType',
294 total_amount => 'TotalAmount',
297 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
299 # Completely undocumented field that sometimes override <Verbiage>
300 $self->error_message($response->{Error}) if $response->{Error};
302 # Delete error_message if transaction was successful
303 $self->error_message(undef) if $self->is_success;
305 $self->card_type(CARD_TYPES->{$self->card_type});
307 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
315 croak "Missing required argument 'merchant_id'"
316 unless defined $self->{merchant_id};
318 my ($page, $response, %headers) =
325 xxxRequestMode => 'X',
326 xxxRequestData => $self->to_xml,
330 croak 'Error connecting to server' unless $page;
331 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
333 # The response is marked UTF-8, but it's really Latin-1. Sigh.
334 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
336 $self->parse_response($page);
347 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
351 use Business::OnlinePayment;
353 $txn = new Business::OnlinePayment 'InternetSecure',
354 merchant_id => '0000';
357 action => 'Normal Authorization',
359 type => 'Visa', # Optional
360 card_number => '4111 1111 1111 1111',
361 expiration => '2004-07',
362 cvv2 => '000', # Optional
364 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
366 address => '123 Street',
367 city => 'Metropolis',
371 phone => '(555) 555-1212',
372 email => 'fbriere@fbriere.net',
377 description => 'Test transaction',
382 if ($txn->is_success) {
383 print "Card processed successfully: " . $tx->authorization . "\n";
385 print "Card was rejected: " . $tx->error_message . "\n";
390 C<Business::OnlinePayment::InternetSecure> is an implementation of
391 C<Business::OnlinePayment> that allows for processing online credit card
392 payments through InternetSecure.
394 See L<Business::OnlinePayment> for more information about the generic
395 Business::OnlinePayment interface.
399 Object creation is done via C<Business::OnlinePayment>; see its manpage for
400 details. The B<merchant_id> processor option is required, and corresponds
401 to the merchant ID assigned to you by InternetSecure.
405 =head2 Transaction setup and transmission
409 =item content( CONTENT )
411 Sets up the data prior to a transaction. CONTENT is an associative array
412 (hash), containing some of the following fields:
416 =item action (required)
418 What to do with the transaction. Only C<Normal Authorization> is supported
423 Transaction type, being one of the following:
431 =item - American Express
441 (This is actually ignored for the moment, and can be left blank or undefined.)
443 =item card_number (required)
445 Credit card number. Spaces and dashes are automatically removed.
447 =item expiration (required)
449 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
450 any syntax, this module is rather lax regarding what it will accept. The
451 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
456 Three- or four-digit verification code printed on the card. This can be left
457 blank or undefined, in which case no check will be performed. Whether or not a
458 transaction will be declined in case of a mismatch depends on the merchant
459 account configuration.
461 This number may be called Card Verification Value (CVV2), Card Validation
462 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
466 A short description of the transaction. See L<"Products list syntax"> for
467 an alternate syntax that allows a list of products to be specified.
469 =item amount (usually required)
471 Total amount to be billed, excluding taxes if they are to be added separately
474 This field is required if B<description> is a string, but should be left
475 undefined if B<description> contains a list of products instead, as outlined
476 in L<"Products list syntax">.
480 Currency of all amounts for this order. This can currently be either
481 C<CAD> (default) or C<USD>.
485 Taxes to be added automatically to B<amount> by InternetSecure. Available
486 taxes are C<GST>, C<PST> and C<HST>.
488 This argument can either be a single string of taxes concatenated with spaces
489 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
492 =item name / company / address / city / state / zip / country / phone / email
494 Customer information. B<country> should be a two-letter code taken from ISO
501 Submit the transaction to InternetSecure.
505 =head2 Post-submission methods
511 Returns true if the transaction was submitted successfully.
515 Response code returned by InternetSecure.
517 =item error_message()
519 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
520 should not rely on this to test whether a transaction was successful; use
521 B<is_success>() instead.)
523 =item receipt_number()
525 Receipt number (a string, actually) of this transaction, unique to all
526 InternetSecure transactions.
530 Sales order number of this transaction. This is a number, unique to each
531 merchant, which is incremented by 1 each time.
535 Universally Unique Identifier associated to this transaction. This is a
536 128-bit value returned as a 36-character string such as
537 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
540 B<guid>() is provided as an alias to this method.
542 =item authorization()
544 Authorization code for this transaction.
546 =item avs_code() / cvv2_response()
548 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
549 the list of possible values.
553 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
557 Total amount billed for this order, including taxes.
561 Returns a I<reference> to a hash that maps taxes, which were listed under the
562 B<taxes> argument to B<submit>(), to the amount that was calculated by
567 Cardholder's name. This is currently a mere copy of the B<name> field passed
572 Type of the credit card used for the submitted order, being one of the
581 =item - American Express
595 =head2 Products list syntax
597 Optionally, the B<description> field of B<content>() can contain a reference
598 to an array of products, instead of a simple string. Each element of this
599 array represents a different product, and must be a reference to a hash with
600 the following fields:
604 =item amount (required)
606 Unit price of this product.
610 Ordered quantity of this product.
614 Internal code for this product.
618 Description of this product
622 Taxes that should be automatically added to this product. If specified, this
623 overrides the B<taxes> field passed to B<content>().
627 When using a products list, the B<amount> field passed to B<content>() should
631 =head2 Character encoding
633 When using non-ASCII characters, all data provided to B<contents>() should
634 have been decoded beforehand via the C<Encode> module, unless your data is in
635 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
638 InternetSecure currently does not handle characters outside of ISO-8859-1, so
639 these will be replaced with C<?> before being transmitted.
649 L<Business::OnlinePayment>
653 Original author: Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>. Please don't
654 bother Frédéric with emails about this module.
656 Currentuly (minimally) maintained by Ivan Kohler. See
657 http://rt.cpan.org/Public/Bug/Report.html?Queue=Business-OnlinePayment-InternetSecure to submit patches and bug reports.
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.