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 CARD_TYPES => {
21 AX => 'American Express', # FIXME: AM?
27 # Convenience functions to avoid undefs and escape products strings
28 sub _def($) { defined $_[0] ? $_[0] : '' }
29 sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ }
35 $self->server('secure.internetsecure.com');
37 $self->path('/process.cgi');
40 receipt_number sales_number uuid guid
44 avs_response cvv2_response
48 # OnlinePayment's get_fields now filters out undefs in 3.x. :(
51 my ($self, @fields) = @_;
53 my %content = $self->content;
55 my %new = map +($_ => $content{$_}), @fields;
60 # Combine get_fields and remap_fields for convenience
62 sub get_remap_fields {
63 my ($self, %map) = @_;
65 $self->remap_fields(reverse %map);
66 my %data = $self->get_fields(keys %map);
71 # Since there's no standard format for expiration dates, we try to do our best
74 my ($self, $str) = @_;
80 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
81 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
82 /^(\d\d)[.-](\d\d)$/) { # YY-MM
84 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
85 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
86 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
89 croak "Unable to parse expiration date: $str";
92 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
97 # Convert a single product into a product string
100 my ($self, $currency, %data) = @_;
102 croak "Missing amount in product" unless defined $data{amount};
104 my @flags = ($currency);
106 foreach (split ' ' => uc($data{taxes} || '')) {
107 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
111 if ($self->test_transaction) {
112 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
116 sprintf('%.2f' => $data{amount}),
117 $data{quantity} || 1,
118 _esc _def $data{sku},
119 _esc _def $data{description},
120 join('' => map "{$_}" => @flags),
124 # Generate the XML document for this transaction
129 my %content = $self->content;
131 $self->required_fields(qw(action card_number exp_date));
133 croak 'Unsupported transaction type'
134 if $content{type} && $content{type} !~
135 /^(Visa|MasterCard|American Express|Discover)$/i;
137 croak 'Unsupported action'
138 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
140 $content{currency} = uc($content{currency} || 'CAD');
141 croak "Unknown currency code ", $content{currency}
142 unless $content{currency} =~ /^(CAD|USD)$/;
144 $content{taxes} = uc($content{taxes} || '');
146 my %data = $self->get_remap_fields(qw(
147 xxxCard_Number card_number
159 xxxShippingName ship_name
160 xxxShippingCompany ship_company
161 xxxShippingAddress ship_address
162 xxxShippingCity ship_city
163 xxxShippingProvince ship_state
164 xxxShippingPostal ship_zip
165 xxxShippingCountry ship_country
166 xxxShippingPhone ship_phone
167 xxxShippingEmail ship_email
170 $data{MerchantNumber} = $self->merchant_id;
172 $data{xxxCard_Number} =~ tr/- //d;
173 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
175 my ($y, $m) = $self->parse_expdate($content{exp_date});
176 $data{xxxCCYear} = sprintf '%.4u' => $y;
177 $data{xxxCCMonth} = sprintf '%.2u' => $m;
179 if (defined $content{cvv2} && $content{cvv2} ne '') {
181 $data{CVV2Indicator} = $content{cvv2};
184 $data{CVV2Indicator} = '';
187 if (ref $content{description}) {
188 $data{Products} = join '|' => map $self->prod_string(
190 taxes => $content{taxes},
192 @{ $content{description} };
194 $self->required_fields(qw(amount));
195 $data{Products} = $self->prod_string(
197 taxes => $content{taxes},
198 amount => $content{amount},
199 description => $content{description},
206 RootName => 'TranxRequest',
207 SuppressEmpty => undef,
208 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
212 # Map the various fields from the response, and put their values into our
213 # object for retrieval.
216 my ($self, $data, %map) = @_;
218 while (my ($k, $v) = each %map) {
220 $self->$k($data->{$v});
224 # Parse the server's response and set various fields
227 my ($self, $response) = @_;
229 $self->server_response($response);
231 local $/ = "\n"; # Make sure to avoid bug #17687
233 $response = xml_in($response,
234 ForceArray => [qw(product flag)],
235 GroupTags => { qw(Products product flags flag) },
237 SuppressEmpty => undef,
240 my $code = $self->result_code($response->{Page});
241 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
243 $self->infuse($response,
244 result_code => 'Page',
245 error_message => 'Verbiage',
246 authorization => 'ApprovalCode',
247 avs_response => 'AVSResponseCode',
248 cvv2_response => 'CVV2ResponseCode',
250 receipt_number => 'ReceiptNumber',
251 sales_number => 'SalesOrderNumber',
256 cardholder => 'xxxName',
257 card_type => 'CardType',
258 total_amount => 'TotalAmount',
261 # Completely undocumented field that sometimes override <Verbiage>
262 $self->error_message($response->{Error}) if $response->{Error};
264 # Delete error_message if transaction was successful
265 $self->error_message(undef) if $self->is_success;
267 $self->card_type(CARD_TYPES->{$self->card_type});
269 $self->{products_raw} = $response->{Products};
277 croak "Missing required argument 'merchant_id'"
278 unless defined $self->{merchant_id};
280 my ($page, $response, %headers) =
287 xxxRequestMode => 'X',
288 xxxRequestData => $self->to_xml,
292 croak 'Error connecting to server' unless $page;
293 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
295 # The response is marked UTF-8, but it's really Latin-1. Sigh.
296 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
298 $self->parse_response($page);
309 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
313 use Business::OnlinePayment;
315 $txn = new Business::OnlinePayment 'InternetSecure',
316 merchant_id => '0000';
319 action => 'Normal Authorization',
321 type => 'Visa', # Optional
322 card_number => '4111 1111 1111 1111',
323 exp_date => '2004-07',
324 cvv2 => '000', # Optional
326 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
328 address => '123 Street',
329 city => 'Metropolis',
333 phone => '(555) 555-1212',
334 email => 'fbriere@fbriere.net',
339 description => 'Test transaction',
344 if ($txn->is_success) {
345 print "Card processed successfully: " . $tx->authorization . "\n";
347 print "Card was rejected: " . $tx->error_message . "\n";
352 C<Business::OnlinePayment::InternetSecure> is an implementation of
353 C<Business::OnlinePayment> that allows for processing online credit card
354 payments through InternetSecure.
356 See L<Business::OnlinePayment> for more information about the generic
357 Business::OnlinePayment interface.
361 Object creation is done via C<Business::OnlinePayment>; see its manpage for
362 details. The B<merchant_id> processor option is required, and corresponds
363 to the merchant ID assigned to you by InternetSecure.
367 =head2 Transaction setup and transmission
371 =item content( CONTENT )
373 Sets up the data prior to a transaction. CONTENT is an associative array
374 (hash), containing some of the following fields:
378 =item action (required)
380 What to do with the transaction. Only C<Normal Authorization> is supported
385 Transaction type, being one of the following:
393 =item - American Express
399 (This is actually ignored for the moment, and can be left blank or undefined.)
401 =item card_number (required)
403 Credit card number. Spaces and dashes are automatically removed.
405 =item exp_date (required)
407 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
408 any syntax, this module is rather lax regarding what it will accept. The
409 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
414 Three- or four-digit verification code printed on the card. This can be left
415 blank or undefined, in which case no check will be performed. Whether or not a
416 transaction will be declined in case of a mismatch depends on the merchant
417 account configuration.
419 This number may be called Card Verification Value (CVV2), Card Validation
420 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
424 A short description of the transaction. See L<"Products list syntax"> for
425 an alternate syntax that allows a list of products to be specified.
427 =item amount (usually required)
429 Total amount to be billed, excluding taxes if they are to be added separately
432 This field is required if B<description> is a string, but should be left
433 undefined if B<description> contains a list of products instead, as outlined
434 in L<"Products list syntax">.
438 Currency of all amounts for this order. This can currently be either
439 C<CAD> (default) or C<USD>.
443 Taxes to be added automatically to B<amount> by InternetSecure.
445 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
446 by concatenating them with spaces, such as C<GST HST>.
448 =item name / company / address / city / state / zip / country / phone / email
450 Customer information. B<country> should be a two-letter code taken from ISO
457 Submit the transaction to InternetSecure.
461 =head2 Post-submission methods
467 Returns true if the transaction was submitted successfully.
471 Response code returned by InternetSecure.
473 =item error_message()
475 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
476 should not rely on this to test whether a transaction was successful; use
477 B<is_success>() instead.)
479 =item receipt_number()
481 Receipt number (a string, actually) of this transaction, unique to all
482 InternetSecure transactions.
486 Sales order number of this transaction. This is a number, unique to each
487 merchant, which is incremented by 1 each time.
491 Universally Unique Identifier associated to this transaction. This is a
492 128-bit value returned as a 36-character string such as
493 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
496 B<guid>() is provided as an alias to this method.
498 =item authorization()
500 Authorization code for this transaction.
502 =item avs_response() / cvv2_response()
504 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
505 the list of possible values.
509 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
513 Total amount billed for this order, including taxes.
517 Cardholder's name. This is currently a mere copy of the B<name> field passed
522 Type of the credit card used for the submitted order, being one of the
531 =item - American Express
547 =head2 Products list syntax
549 Optionally, the B<description> field of B<content>() can contain a reference
550 to an array of products, instead of a simple string. Each element of this
551 array represents a different product, and must be a reference to a hash with
552 the following fields:
558 Unit price of this product.
562 Ordered quantity of this product.
566 Internal code for this product.
570 Description of this product
574 Taxes that should be automatically added to this product. If specified, this
575 overrides the B<taxes> field passed to B<content>().
579 When using a products list, the B<amount> field passed to B<content>() should
583 =head2 Character encoding
585 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
586 characters are theoretically available when submitting information via
587 B<submit>(). (Further restrictions may be imposed by InternetSecure itself.)
589 When using non-ASCII characters, all data provided to B<submit>() should either
590 be in the current native encoding (typically latin-1, unless it was modified
591 via the C<encoding> pragma), or be decoded via the C<Encode> module.
592 Conversely, all data returned after calling B<submit>() will be automatically
603 L<Business::OnlinePayment>
607 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
609 =head1 COPYRIGHT AND LICENSE
611 Copyright (C) 2006 by Frédéric Brière
613 This library is free software; you can redistribute it and/or modify
614 it under the same terms as Perl itself, either Perl version 5.8.4 or,
615 at your option, any later version of Perl 5 you may have available.