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
50 # OnlinePayment's get_fields now filters out undefs in 3.x. :(
53 my ($self, @fields) = @_;
55 my %content = $self->content;
57 my %new = map +($_ => $content{$_}), @fields;
62 # Combine get_fields and remap_fields for convenience
64 sub get_remap_fields {
65 my ($self, %map) = @_;
67 $self->remap_fields(reverse %map);
68 my %data = $self->get_fields(keys %map);
73 # Since there's no standard format for expiration dates, we try to do our best
76 my ($self, $str) = @_;
82 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
83 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
84 /^(\d\d)[.-](\d\d)$/) { # YY-MM
86 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
87 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
88 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
91 croak "Unable to parse expiration date: $str";
94 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
99 # Convert a single product into a product string
102 my ($self, $currency, %data) = @_;
104 croak "Missing amount in product" unless defined $data{amount};
106 my @flags = ($currency);
108 foreach (split ' ' => uc($data{taxes} || '')) {
109 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
113 if ($self->test_transaction) {
114 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
118 sprintf('%.2f' => $data{amount}),
119 $data{quantity} || 1,
120 _esc _def $data{sku},
121 _esc _def $data{description},
122 join('' => map "{$_}" => @flags),
126 # Generate the XML document for this transaction
131 my %content = $self->content;
133 $self->required_fields(qw(action card_number exp_date));
135 croak 'Unsupported transaction type'
136 if $content{type} && $content{type} !~
137 /^(Visa|MasterCard|American Express|Discover)$/i;
139 croak 'Unsupported action'
140 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
142 $content{currency} = uc($content{currency} || 'CAD');
143 croak "Unknown currency code ", $content{currency}
144 unless $content{currency} =~ /^(CAD|USD)$/;
146 $content{taxes} = uc($content{taxes} || '');
148 my %data = $self->get_remap_fields(qw(
149 xxxCard_Number card_number
161 xxxShippingName ship_name
162 xxxShippingCompany ship_company
163 xxxShippingAddress ship_address
164 xxxShippingCity ship_city
165 xxxShippingProvince ship_state
166 xxxShippingPostal ship_zip
167 xxxShippingCountry ship_country
168 xxxShippingPhone ship_phone
169 xxxShippingEmail ship_email
172 $data{MerchantNumber} = $self->merchant_id;
174 $data{xxxCard_Number} =~ tr/- //d;
175 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
177 my ($y, $m) = $self->parse_expdate($content{exp_date});
178 $data{xxxCCYear} = sprintf '%.4u' => $y;
179 $data{xxxCCMonth} = sprintf '%.2u' => $m;
181 if (defined $content{cvv2} && $content{cvv2} ne '') {
183 $data{CVV2Indicator} = $content{cvv2};
186 $data{CVV2Indicator} = '';
189 if (ref $content{description}) {
190 $data{Products} = join '|' => map $self->prod_string(
192 taxes => $content{taxes},
194 @{ $content{description} };
196 $self->required_fields(qw(amount));
197 $data{Products} = $self->prod_string(
199 taxes => $content{taxes},
200 amount => $content{amount},
201 description => $content{description},
208 RootName => 'TranxRequest',
209 SuppressEmpty => undef,
210 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
214 # Map the various fields from the response, and put their values into our
215 # object for retrieval.
218 my ($self, $data, %map) = @_;
220 while (my ($k, $v) = each %map) {
222 $self->$k($data->{$v});
226 # Parse the server's response and set various fields
229 my ($self, $response) = @_;
231 $self->server_response($response);
233 local $/ = "\n"; # Make sure to avoid bug #17687
235 $response = xml_in($response,
236 ForceArray => [qw(product flag)],
237 GroupTags => { qw(Products product flags flag) },
239 SuppressEmpty => undef,
242 $self->infuse($response,
243 result_code => 'Page',
244 error_message => 'Verbiage',
245 authorization => 'ApprovalCode',
246 avs_response => 'AVSResponseCode',
247 cvv2_response => 'CVV2ResponseCode',
249 receipt_number => 'ReceiptNumber',
250 sales_number => 'SalesOrderNumber',
255 cardholder => 'xxxName',
256 card_type => 'CardType',
257 total_amount => 'TotalAmount',
260 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
262 # Completely undocumented field that sometimes override <Verbiage>
263 $self->error_message($response->{Error}) if $response->{Error};
265 # Delete error_message if transaction was successful
266 $self->error_message(undef) if $self->is_success;
268 $self->card_type(CARD_TYPES->{$self->card_type});
270 $self->{products_raw} = $response->{Products};
278 croak "Missing required argument 'merchant_id'"
279 unless defined $self->{merchant_id};
281 my ($page, $response, %headers) =
288 xxxRequestMode => 'X',
289 xxxRequestData => $self->to_xml,
293 croak 'Error connecting to server' unless $page;
294 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
296 # The response is marked UTF-8, but it's really Latin-1. Sigh.
297 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
299 $self->parse_response($page);
310 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
314 use Business::OnlinePayment;
316 $txn = new Business::OnlinePayment 'InternetSecure',
317 merchant_id => '0000';
320 action => 'Normal Authorization',
322 type => 'Visa', # Optional
323 card_number => '4111 1111 1111 1111',
324 exp_date => '2004-07',
325 cvv2 => '000', # Optional
327 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
329 address => '123 Street',
330 city => 'Metropolis',
334 phone => '(555) 555-1212',
335 email => 'fbriere@fbriere.net',
340 description => 'Test transaction',
345 if ($txn->is_success) {
346 print "Card processed successfully: " . $tx->authorization . "\n";
348 print "Card was rejected: " . $tx->error_message . "\n";
353 C<Business::OnlinePayment::InternetSecure> is an implementation of
354 C<Business::OnlinePayment> that allows for processing online credit card
355 payments through InternetSecure.
357 See L<Business::OnlinePayment> for more information about the generic
358 Business::OnlinePayment interface.
362 Object creation is done via C<Business::OnlinePayment>; see its manpage for
363 details. The B<merchant_id> processor option is required, and corresponds
364 to the merchant ID assigned to you by InternetSecure.
368 =head2 Transaction setup and transmission
372 =item content( CONTENT )
374 Sets up the data prior to a transaction. CONTENT is an associative array
375 (hash), containing some of the following fields:
379 =item action (required)
381 What to do with the transaction. Only C<Normal Authorization> is supported
386 Transaction type, being one of the following:
394 =item - American Express
400 (This is actually ignored for the moment, and can be left blank or undefined.)
402 =item card_number (required)
404 Credit card number. Spaces and dashes are automatically removed.
406 =item exp_date (required)
408 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
409 any syntax, this module is rather lax regarding what it will accept. The
410 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
415 Three- or four-digit verification code printed on the card. This can be left
416 blank or undefined, in which case no check will be performed. Whether or not a
417 transaction will be declined in case of a mismatch depends on the merchant
418 account configuration.
420 This number may be called Card Verification Value (CVV2), Card Validation
421 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
425 A short description of the transaction. See L<"Products list syntax"> for
426 an alternate syntax that allows a list of products to be specified.
428 =item amount (usually required)
430 Total amount to be billed, excluding taxes if they are to be added separately
433 This field is required if B<description> is a string, but should be left
434 undefined if B<description> contains a list of products instead, as outlined
435 in L<"Products list syntax">.
439 Currency of all amounts for this order. This can currently be either
440 C<CAD> (default) or C<USD>.
444 Taxes to be added automatically to B<amount> by InternetSecure.
446 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
447 by concatenating them with spaces, such as C<GST HST>.
449 =item name / company / address / city / state / zip / country / phone / email
451 Customer information. B<country> should be a two-letter code taken from ISO
458 Submit the transaction to InternetSecure.
462 =head2 Post-submission methods
468 Returns true if the transaction was submitted successfully.
472 Response code returned by InternetSecure.
474 =item error_message()
476 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
477 should not rely on this to test whether a transaction was successful; use
478 B<is_success>() instead.)
480 =item receipt_number()
482 Receipt number (a string, actually) of this transaction, unique to all
483 InternetSecure transactions.
487 Sales order number of this transaction. This is a number, unique to each
488 merchant, which is incremented by 1 each time.
492 Universally Unique Identifier associated to this transaction. This is a
493 128-bit value returned as a 36-character string such as
494 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
497 B<guid>() is provided as an alias to this method.
499 =item authorization()
501 Authorization code for this transaction.
503 =item avs_response() / cvv2_response()
505 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
506 the list of possible values.
510 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
514 Total amount billed for this order, including taxes.
518 Cardholder's name. This is currently a mere copy of the B<name> field passed
523 Type of the credit card used for the submitted order, being one of the
532 =item - American Express
548 =head2 Products list syntax
550 Optionally, the B<description> field of B<content>() can contain a reference
551 to an array of products, instead of a simple string. Each element of this
552 array represents a different product, and must be a reference to a hash with
553 the following fields:
559 Unit price of this product.
563 Ordered quantity of this product.
567 Internal code for this product.
571 Description of this product
575 Taxes that should be automatically added to this product. If specified, this
576 overrides the B<taxes> field passed to B<content>().
580 When using a products list, the B<amount> field passed to B<content>() should
584 =head2 Character encoding
586 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
587 characters are theoretically available when submitting information via
588 B<submit>(). (Further restrictions may be imposed by InternetSecure itself.)
590 When using non-ASCII characters, all data provided to B<submit>() should either
591 be in the current native encoding (typically latin-1, unless it was modified
592 via the C<encoding> pragma), or be decoded via the C<Encode> module.
593 Conversely, all data returned after calling B<submit>() will be automatically
604 L<Business::OnlinePayment>
608 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
610 =head1 COPYRIGHT AND LICENSE
612 Copyright (C) 2006 by Frédéric Brière
614 This library is free software; you can redistribute it and/or modify
615 it under the same terms as Perl itself, either Perl version 5.8.4 or,
616 at your option, any later version of Perl 5 you may have available.