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},
213 # The encode() makes sure to a) strip off non-Latin-1 characters, and
214 # b) turn off the utf8 flag, which confuses XML::Simple
215 encode('ISO-8859-1', xml_out(\%data,
217 RootName => 'TranxRequest',
218 SuppressEmpty => undef,
219 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
223 # Map the various fields from the response, and put their values into our
224 # object for retrieval.
227 my ($self, $data, %map) = @_;
229 while (my ($k, $v) = each %map) {
231 $self->$k($data->{$v});
235 sub extract_tax_amounts {
236 my ($self, $response) = @_;
240 my $products = $response->{Products};
241 return unless $products;
243 foreach my $node (@$products) {
244 my $flags = $node->{flags};
246 grep($_ eq '{TAX}', @$flags) &&
247 grep($_ eq '{CALCULATED}', @$flags))
249 $tax_amounts{ $node->{code} } = $node->{subtotal};
256 # Parse the server's response and set various fields
259 my ($self, $response) = @_;
261 $self->server_response($response);
263 local $/ = "\n"; # Make sure to avoid bug #17687
265 $response = xml_in($response,
266 ForceArray => [qw(product flag)],
267 GroupTags => { qw(Products product flags flag) },
269 SuppressEmpty => undef,
272 $self->infuse($response,
273 result_code => 'Page',
274 error_message => 'Verbiage',
275 authorization => 'ApprovalCode',
276 avs_response => 'AVSResponseCode',
277 cvv2_response => 'CVV2ResponseCode',
279 receipt_number => 'ReceiptNumber',
280 sales_number => 'SalesOrderNumber',
285 cardholder => 'xxxName',
286 card_type => 'CardType',
287 total_amount => 'TotalAmount',
290 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
292 # Completely undocumented field that sometimes override <Verbiage>
293 $self->error_message($response->{Error}) if $response->{Error};
295 # Delete error_message if transaction was successful
296 $self->error_message(undef) if $self->is_success;
298 $self->card_type(CARD_TYPES->{$self->card_type});
300 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
308 croak "Missing required argument 'merchant_id'"
309 unless defined $self->{merchant_id};
311 my ($page, $response, %headers) =
318 xxxRequestMode => 'X',
319 xxxRequestData => $self->to_xml,
323 croak 'Error connecting to server' unless $page;
324 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
326 # The response is marked UTF-8, but it's really Latin-1. Sigh.
327 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
329 $self->parse_response($page);
340 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
344 use Business::OnlinePayment;
346 $txn = new Business::OnlinePayment 'InternetSecure',
347 merchant_id => '0000';
350 action => 'Normal Authorization',
352 type => 'Visa', # Optional
353 card_number => '4111 1111 1111 1111',
354 exp_date => '2004-07',
355 cvv2 => '000', # Optional
357 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
359 address => '123 Street',
360 city => 'Metropolis',
364 phone => '(555) 555-1212',
365 email => 'fbriere@fbriere.net',
370 description => 'Test transaction',
375 if ($txn->is_success) {
376 print "Card processed successfully: " . $tx->authorization . "\n";
378 print "Card was rejected: " . $tx->error_message . "\n";
383 C<Business::OnlinePayment::InternetSecure> is an implementation of
384 C<Business::OnlinePayment> that allows for processing online credit card
385 payments through InternetSecure.
387 See L<Business::OnlinePayment> for more information about the generic
388 Business::OnlinePayment interface.
392 Object creation is done via C<Business::OnlinePayment>; see its manpage for
393 details. The B<merchant_id> processor option is required, and corresponds
394 to the merchant ID assigned to you by InternetSecure.
398 =head2 Transaction setup and transmission
402 =item content( CONTENT )
404 Sets up the data prior to a transaction. CONTENT is an associative array
405 (hash), containing some of the following fields:
409 =item action (required)
411 What to do with the transaction. Only C<Normal Authorization> is supported
416 Transaction type, being one of the following:
424 =item - American Express
430 (This is actually ignored for the moment, and can be left blank or undefined.)
432 =item card_number (required)
434 Credit card number. Spaces and dashes are automatically removed.
436 =item exp_date (required)
438 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
439 any syntax, this module is rather lax regarding what it will accept. The
440 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
445 Three- or four-digit verification code printed on the card. This can be left
446 blank or undefined, in which case no check will be performed. Whether or not a
447 transaction will be declined in case of a mismatch depends on the merchant
448 account configuration.
450 This number may be called Card Verification Value (CVV2), Card Validation
451 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
455 A short description of the transaction. See L<"Products list syntax"> for
456 an alternate syntax that allows a list of products to be specified.
458 =item amount (usually required)
460 Total amount to be billed, excluding taxes if they are to be added separately
463 This field is required if B<description> is a string, but should be left
464 undefined if B<description> contains a list of products instead, as outlined
465 in L<"Products list syntax">.
469 Currency of all amounts for this order. This can currently be either
470 C<CAD> (default) or C<USD>.
474 Taxes to be added automatically to B<amount> by InternetSecure. Available
475 taxes are C<GST>, C<PST> and C<HST>.
477 This argument can either be a single string of taxes concatenated with spaces
478 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
481 =item name / company / address / city / state / zip / country / phone / email
483 Customer information. B<country> should be a two-letter code taken from ISO
490 Submit the transaction to InternetSecure.
494 =head2 Post-submission methods
500 Returns true if the transaction was submitted successfully.
504 Response code returned by InternetSecure.
506 =item error_message()
508 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
509 should not rely on this to test whether a transaction was successful; use
510 B<is_success>() instead.)
512 =item receipt_number()
514 Receipt number (a string, actually) of this transaction, unique to all
515 InternetSecure transactions.
519 Sales order number of this transaction. This is a number, unique to each
520 merchant, which is incremented by 1 each time.
524 Universally Unique Identifier associated to this transaction. This is a
525 128-bit value returned as a 36-character string such as
526 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
529 B<guid>() is provided as an alias to this method.
531 =item authorization()
533 Authorization code for this transaction.
535 =item avs_response() / cvv2_response()
537 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
538 the list of possible values.
542 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
546 Total amount billed for this order, including taxes.
550 Returns a I<reference> to a hash that maps taxes, which were listed under the
551 B<taxes> argument to B<submit>(), to the amount that was calculated by
556 Cardholder's name. This is currently a mere copy of the B<name> field passed
561 Type of the credit card used for the submitted order, being one of the
570 =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.