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 => {
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 # 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: $content{type}"
147 ! grep lc($content{type}) eq lc($_),
148 values %{+CARD_TYPES};
150 croak 'Unsupported action'
151 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
153 $content{currency} = uc($content{currency} || 'CAD');
154 croak "Unknown currency code ", $content{currency}
155 unless $content{currency} =~ /^(CAD|USD)$/;
157 my %data = $self->get_remap_fields(qw(
158 xxxCard_Number card_number
170 xxxShippingName ship_name
171 xxxShippingCompany ship_company
172 xxxShippingAddress ship_address
173 xxxShippingCity ship_city
174 xxxShippingProvince ship_state
175 xxxShippingPostal ship_zip
176 xxxShippingCountry ship_country
177 xxxShippingPhone ship_phone
178 xxxShippingEmail ship_email
181 $data{MerchantNumber} = $self->merchant_id;
183 $data{xxxCard_Number} =~ tr/- //d;
184 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
186 my ($y, $m) = $self->parse_expdate($content{exp_date});
187 $data{xxxCCYear} = sprintf '%.4u' => $y;
188 $data{xxxCCMonth} = sprintf '%.2u' => $m;
190 if (defined $content{cvv2} && $content{cvv2} ne '') {
192 $data{CVV2Indicator} = $content{cvv2};
195 $data{CVV2Indicator} = '';
198 if (ref $content{description}) {
199 $data{Products} = join '|' => map $self->prod_string(
201 taxes => $content{taxes},
203 @{ $content{description} };
205 $self->required_fields(qw(amount));
206 $data{Products} = $self->prod_string(
208 taxes => $content{taxes},
209 amount => $content{amount},
210 description => $content{description},
214 # The encode() makes sure to a) strip off non-Latin-1 characters, and
215 # b) turn off the utf8 flag, which confuses XML::Simple
216 encode('ISO-8859-1', xml_out(\%data,
218 RootName => 'TranxRequest',
219 SuppressEmpty => undef,
220 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
224 # Map the various fields from the response, and put their values into our
225 # object for retrieval.
228 my ($self, $data, %map) = @_;
230 while (my ($k, $v) = each %map) {
232 $self->$k($data->{$v});
236 sub extract_tax_amounts {
237 my ($self, $response) = @_;
241 my $products = $response->{Products};
242 return unless $products;
244 foreach my $node (@$products) {
245 my $flags = $node->{flags};
247 grep($_ eq '{TAX}', @$flags) &&
248 grep($_ eq '{CALCULATED}', @$flags))
250 $tax_amounts{ $node->{code} } = $node->{subtotal};
257 # Parse the server's response and set various fields
260 my ($self, $response) = @_;
262 $self->server_response($response);
264 local $/ = "\n"; # Make sure to avoid bug #17687
266 $response = xml_in($response,
267 ForceArray => [qw(product flag)],
268 GroupTags => { qw(Products product flags flag) },
270 SuppressEmpty => undef,
273 $self->infuse($response,
274 result_code => 'Page',
275 error_message => 'Verbiage',
276 authorization => 'ApprovalCode',
277 avs_response => 'AVSResponseCode',
278 cvv2_response => 'CVV2ResponseCode',
280 receipt_number => 'ReceiptNumber',
281 sales_number => 'SalesOrderNumber',
286 cardholder => 'xxxName',
287 card_type => 'CardType',
288 total_amount => 'TotalAmount',
291 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
293 # Completely undocumented field that sometimes override <Verbiage>
294 $self->error_message($response->{Error}) if $response->{Error};
296 # Delete error_message if transaction was successful
297 $self->error_message(undef) if $self->is_success;
299 $self->card_type(CARD_TYPES->{$self->card_type});
301 $self->tax_amounts( { $self->extract_tax_amounts($response) } );
309 croak "Missing required argument 'merchant_id'"
310 unless defined $self->{merchant_id};
312 my ($page, $response, %headers) =
319 xxxRequestMode => 'X',
320 xxxRequestData => $self->to_xml,
324 croak 'Error connecting to server' unless $page;
325 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
327 # The response is marked UTF-8, but it's really Latin-1. Sigh.
328 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
330 $self->parse_response($page);
341 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
345 use Business::OnlinePayment;
347 $txn = new Business::OnlinePayment 'InternetSecure',
348 merchant_id => '0000';
351 action => 'Normal Authorization',
353 type => 'Visa', # Optional
354 card_number => '4111 1111 1111 1111',
355 exp_date => '2004-07',
356 cvv2 => '000', # Optional
358 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
360 address => '123 Street',
361 city => 'Metropolis',
365 phone => '(555) 555-1212',
366 email => 'fbriere@fbriere.net',
371 description => 'Test transaction',
376 if ($txn->is_success) {
377 print "Card processed successfully: " . $tx->authorization . "\n";
379 print "Card was rejected: " . $tx->error_message . "\n";
384 C<Business::OnlinePayment::InternetSecure> is an implementation of
385 C<Business::OnlinePayment> that allows for processing online credit card
386 payments through InternetSecure.
388 See L<Business::OnlinePayment> for more information about the generic
389 Business::OnlinePayment interface.
393 Object creation is done via C<Business::OnlinePayment>; see its manpage for
394 details. The B<merchant_id> processor option is required, and corresponds
395 to the merchant ID assigned to you by InternetSecure.
399 =head2 Transaction setup and transmission
403 =item content( CONTENT )
405 Sets up the data prior to a transaction. CONTENT is an associative array
406 (hash), containing some of the following fields:
410 =item action (required)
412 What to do with the transaction. Only C<Normal Authorization> is supported
417 Transaction type, being one of the following:
425 =item - American Express
433 (This is actually ignored for the moment, and can be left blank or undefined.)
435 =item card_number (required)
437 Credit card number. Spaces and dashes are automatically removed.
439 =item exp_date (required)
441 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
442 any syntax, this module is rather lax regarding what it will accept. The
443 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
448 Three- or four-digit verification code printed on the card. This can be left
449 blank or undefined, in which case no check will be performed. Whether or not a
450 transaction will be declined in case of a mismatch depends on the merchant
451 account configuration.
453 This number may be called Card Verification Value (CVV2), Card Validation
454 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
458 A short description of the transaction. See L<"Products list syntax"> for
459 an alternate syntax that allows a list of products to be specified.
461 =item amount (usually required)
463 Total amount to be billed, excluding taxes if they are to be added separately
466 This field is required if B<description> is a string, but should be left
467 undefined if B<description> contains a list of products instead, as outlined
468 in L<"Products list syntax">.
472 Currency of all amounts for this order. This can currently be either
473 C<CAD> (default) or C<USD>.
477 Taxes to be added automatically to B<amount> by InternetSecure. Available
478 taxes are C<GST>, C<PST> and C<HST>.
480 This argument can either be a single string of taxes concatenated with spaces
481 (such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
484 =item name / company / address / city / state / zip / country / phone / email
486 Customer information. B<country> should be a two-letter code taken from ISO
493 Submit the transaction to InternetSecure.
497 =head2 Post-submission methods
503 Returns true if the transaction was submitted successfully.
507 Response code returned by InternetSecure.
509 =item error_message()
511 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
512 should not rely on this to test whether a transaction was successful; use
513 B<is_success>() instead.)
515 =item receipt_number()
517 Receipt number (a string, actually) of this transaction, unique to all
518 InternetSecure transactions.
522 Sales order number of this transaction. This is a number, unique to each
523 merchant, which is incremented by 1 each time.
527 Universally Unique Identifier associated to this transaction. This is a
528 128-bit value returned as a 36-character string such as
529 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
532 B<guid>() is provided as an alias to this method.
534 =item authorization()
536 Authorization code for this transaction.
538 =item avs_response() / cvv2_response()
540 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
541 the list of possible values.
545 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
549 Total amount billed for this order, including taxes.
553 Returns a I<reference> to a hash that maps taxes, which were listed under the
554 B<taxes> argument to B<submit>(), to the amount that was calculated by
559 Cardholder's name. This is currently a mere copy of the B<name> field passed
564 Type of the credit card used for the submitted order, being one of the
573 =item - American Express
587 =head2 Products list syntax
589 Optionally, the B<description> field of B<content>() can contain a reference
590 to an array of products, instead of a simple string. Each element of this
591 array represents a different product, and must be a reference to a hash with
592 the following fields:
596 =item amount (required)
598 Unit price of this product.
602 Ordered quantity of this product.
606 Internal code for this product.
610 Description of this product
614 Taxes that should be automatically added to this product. If specified, this
615 overrides the B<taxes> field passed to B<content>().
619 When using a products list, the B<amount> field passed to B<content>() should
623 =head2 Character encoding
625 When using non-ASCII characters, all data provided to B<contents>() should
626 have been decoded beforehand via the C<Encode> module, unless your data is in
627 ISO-8859-1 and you haven't meddled with the C<encoding> pragma. (Please
630 InternetSecure currently does not handle characters outside of ISO-8859-1, so
631 these will be replaced with C<?> before being transmitted.
641 L<Business::OnlinePayment>
645 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
647 =head1 COPYRIGHT AND LICENSE
649 Copyright (C) 2006 by Frédéric Brière
651 This library is free software; you can redistribute it and/or modify
652 it under the same terms as Perl itself, either Perl version 5.8.4 or,
653 at your option, any later version of Perl 5 you may have available.