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
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 # OnlinePayment's remap_fields is buggy, so we simply rewrite it
63 my ($self, %map) = @_;
65 my %content = $self->content();
67 $content{$map{$_}} = delete $content{$_};
69 $self->content(%content);
72 # Combine get_fields and remap_fields for convenience
74 sub get_remap_fields {
75 my ($self, %map) = @_;
77 $self->remap_fields(reverse %map);
78 my %data = $self->get_fields(keys %map);
83 # Since there's no standard format for expiration dates, we try to do our best
86 my ($self, $str) = @_;
92 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
93 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
94 /^(\d\d)[.-](\d\d)$/) { # YY-MM
96 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
97 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
98 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
101 croak "Unable to parse expiration date: $str";
104 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
109 # Convert a single product into a product string
112 my ($self, $currency, %data) = @_;
114 croak "Missing amount in product" unless defined $data{amount};
116 my @flags = ($currency);
118 foreach (split ' ' => uc($data{taxes} || '')) {
119 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
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 $content{taxes} = uc($content{taxes} || '');
158 my %data = $self->get_remap_fields(qw(
159 xxxCard_Number card_number
171 xxxShippingName ship_name
172 xxxShippingCompany ship_company
173 xxxShippingAddress ship_address
174 xxxShippingCity ship_city
175 xxxShippingProvince ship_state
176 xxxShippingPostal ship_zip
177 xxxShippingCountry ship_country
178 xxxShippingPhone ship_phone
179 xxxShippingEmail ship_email
182 $data{MerchantNumber} = $self->merchant_id;
184 $data{xxxCard_Number} =~ tr/ //d;
185 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
187 my ($y, $m) = $self->parse_expdate($content{exp_date});
188 $data{xxxCCYear} = sprintf '%.4u' => $y;
189 $data{xxxCCMonth} = sprintf '%.2u' => $m;
191 if (defined $content{cvv2} && $content{cvv2} ne '') {
193 $data{CVV2Indicator} = $content{cvv2};
196 $data{CVV2Indicator} = '';
199 if (ref $content{description}) {
200 $data{Products} = join '|' => map $self->prod_string(
202 taxes => $content{taxes},
204 @{ $content{description} };
206 $self->required_fields(qw(amount));
207 $data{Products} = $self->prod_string(
209 taxes => $content{taxes},
210 amount => $content{amount},
211 description => $content{description},
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->$v($data->{$k});
236 # Parse the server's response and set various fields
239 my ($self, $response) = @_;
241 $self->server_response($response);
243 local $/ = "\n"; # Make sure to avoid bug #17687
245 $response = xml_in($response,
246 ForceArray => [qw(product flag)],
247 GroupTags => { qw(Products product flags flag) },
249 SuppressEmpty => undef,
252 my $code = $self->result_code($response->{Page});
253 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
255 $self->infuse($response, qw(
256 ReceiptNumber receipt_number
257 SalesOrderNumber sales_number
262 ApprovalCode authorization
263 Verbiage error_message
264 TotalAmount total_amount
265 AVSResponseCode avs_response
266 CVV2ResponseCode cvv2_response
269 # Completely undocumented field that sometimes override <Verbiage>
270 $self->error_message($response->{Error}) if $response->{Error};
272 $self->card_type(CARD_TYPES->{$self->card_type});
274 $self->{products_raw} = $response->{Products};
282 croak "Missing required argument 'merchant_id'"
283 unless defined $self->{merchant_id};
285 my ($page, $response, %headers) =
292 xxxRequestMode => 'X',
293 xxxRequestData => $self->to_xml,
297 croak 'Error connecting to server' unless $page;
298 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
300 # The response is marked UTF-8, but it's really Latin-1. Sigh.
301 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
303 $self->parse_response($page);
314 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
318 use Business::OnlinePayment;
320 $txn = new Business::OnlinePayment 'InternetSecure',
321 merchant_id => '0000';
324 action => 'Normal Authorization',
327 card_number => '0000000000000000',
328 exp_date => '2004-07',
329 cvv2 => '000', # Optional
331 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
333 address => '123 Street',
334 city => 'Metropolis',
338 phone => '(555) 555-1212',
339 email => 'fbriere@fbriere.net',
341 description => 'Online purchase',
349 if ($txn->is_success) {
350 print "Card processed successfully: " . $tx->authorization . "\n";
352 print "Card was rejected: " . $tx->error_message . "\n";
357 Business::OnlinePayment::InternetSecure is an implementation of
358 L<Business::OnlinePayment> that allows for processing online credit card
359 payments through InternetSecure.
361 See L<Business::OnlinePayment> for more information about the generic
362 Business::OnlinePayment interface.
366 Object creation is done via L<Business::OnlinePayment>; see its manpage for
367 details. The I<merchant_id> processor option is required, and corresponds
368 to the merchant ID assigned to you by InternetSecure.
372 (Other methods are also available -- see L<Business::OnlinePayment> for more
375 =head2 Before order submission
379 =item content( CONTENT )
381 Sets up the data prior to a transaction (overwriting any previous data by the
382 same occasion). CONTENT is an associative array (hash), containing some of
383 the following fields:
387 =item action (required)
389 What to do with the transaction. Only C<Normal Authorization> is supported
394 Transaction type, being one of the following:
402 =item - American Express
408 (This is actually ignored for the moment, and can be left blank or undefined.)
410 =item card_number (required)
412 Credit card number. Spaces are allowed, and will be automatically removed.
414 =item exp_date (required)
416 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
417 any syntax, this module is rather lax regarding what it will accept. The
418 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
423 Three- or four-digit verification code printed on the card. This can be left
424 blank or undefined, in which case no check will be performed. Whether or not a
425 transaction will be declined in case of a mismatch depends on the merchant
426 account configuration.
428 This number may be called Card Verification Value (CVV2), Card Validation
429 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
433 A short description of the purchase. See L<"Products list syntax"> for
434 an alternate syntax that allows a list of products to be specified.
436 =item amount (usually required)
438 Total amount to be billed, excluding taxes if they are to be added separately
441 This field is required if B<description> is a string, but should be left
442 undefined if B<description> contains a list of products instead, as outlined
443 in L<"Products list syntax">.
447 Currency of all amounts for this order. This can currently be either
448 C<CAD> (default) or C<USD>.
452 Taxes to be added automatically to B<amount> by InternetSecure.
454 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
455 by concatenating them with spaces, such as C<GST HST>.
457 =item name / company / address / city / state / zip / country / phone / email
459 Customer information. B<Country> should be a two-letter code taken from ISO
466 =head2 After order submission
470 =item receipt_number()
472 Receipt number of this transaction; this is actually a string, unique to all
473 InternetSecure transactions.
477 Sales order number of this transaction. This is a number, unique to each
478 merchant, which is incremented by 1 each time.
482 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
486 Total amount billed for this order, including taxes.
490 Cardholder's name. This is currently a mere copy of the B<name> field passed
495 Type of the credit card used for the submitted order, being one of the
504 =item - American Express
510 =item avs_response() / cvv2_response()
512 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
513 the list of possible values.
525 =head2 Products list syntax
527 Optionally, the B<description> field of B<content()> can contain a reference
528 to an array of products, instead of a simple string. Each element of this
529 array represents a different product, and must be a reference to a hash with
530 the following fields:
536 Unit price of this product.
540 Ordered quantity of this product.
544 Internal code for this product.
548 Description of this product
552 Taxes that should be automatically added to this product. If specified, this
553 overrides the B<taxes> field passed to B<content()>.
557 When using a products list, the B<amount> field passed to B<content()> should
561 =head2 Character encoding
563 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
564 characters are theoretically available when submitting information via
565 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
567 When using non-ASCII characters, all data provided to B<submit()> should either
568 be in the current native encoding (typically latin-1, unless it was modified
569 via the C<encoding> pragma), or be decoded via the C<Encode> module.
570 Conversely, all data returned after calling B<submit()> will be automatically
581 L<Business::OnlinePayment>
585 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
587 =head1 COPYRIGHT AND LICENSE
589 Copyright (C) 2006 by Frédéric Brière
591 This library is free software; you can redistribute it and/or modify
592 it under the same terms as Perl itself, either Perl version 5.8.4 or,
593 at your option, any later version of Perl 5 you may have available.