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
43 avs_response cvv2_response
47 # OnlinePayment's get_fields now filters out undefs in 3.x. :(
50 my ($self, @fields) = @_;
52 my %content = $self->content;
54 my %new = map +($_ => $content{$_}), @fields;
59 # OnlinePayment's remap_fields is buggy, so we simply rewrite it
62 my ($self, %map) = @_;
64 my %content = $self->content();
66 $content{$map{$_}} = delete $content{$_};
68 $self->content(%content);
71 # Combine get_fields and remap_fields for convenience
73 sub get_remap_fields {
74 my ($self, %map) = @_;
76 $self->remap_fields(reverse %map);
77 my %data = $self->get_fields(keys %map);
82 # Since there's no standard format for expiration dates, we try to do our best
85 my ($self, $str) = @_;
91 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
92 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
93 /^(\d\d)[.-](\d\d)$/) { # YY-MM
95 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
96 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
97 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
100 croak "Unable to parse expiration date: $str";
103 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
108 # Convert a single product into a product string
111 my ($self, $currency, %data) = @_;
113 croak "Missing amount in product" unless defined $data{amount};
115 my @flags = ($currency);
117 foreach (split ' ' => uc($data{taxes} || '')) {
118 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
122 if ($self->test_transaction) {
123 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
127 sprintf('%.2f' => $data{amount}),
128 $data{quantity} || 1,
129 _esc _def $data{sku},
130 _esc _def $data{description},
131 join('' => map "{$_}" => @flags),
135 # Generate the XML document for this transaction
140 my %content = $self->content;
142 $self->required_fields(qw(action card_number exp_date));
144 croak 'Unsupported transaction type'
145 if $content{type} && $content{type} !~
146 /^(Visa|MasterCard|American Express|Discover)$/i;
148 croak 'Unsupported action'
149 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
151 $content{currency} = uc($content{currency} || 'CAD');
152 croak "Unknown currency code ", $content{currency}
153 unless $content{currency} =~ /^(CAD|USD)$/;
155 $content{taxes} = uc($content{taxes} || '');
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/^[0-36-9]/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},
216 RootName => 'TranxRequest',
217 SuppressEmpty => undef,
218 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
222 # Map the various fields from the response, and put their values into our
223 # object for retrieval.
226 my ($self, $data, %map) = @_;
228 while (my ($k, $v) = each %map) {
230 $self->$v($data->{$k});
234 # Parse the server's response and set various fields
237 my ($self, $response) = @_;
239 $self->server_response($response);
241 $response = xml_in($response,
242 ForceArray => [qw(product flag)],
243 GroupTags => { qw(Products product flags flag) },
245 SuppressEmpty => undef,
248 my $code = $self->result_code($response->{Page});
249 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
251 $self->infuse($response, qw(
252 ReceiptNumber receipt_number
253 SalesOrderNumber sales_number
256 ApprovalCode authorization
257 Verbiage error_message
258 TotalAmount total_amount
259 AVSResponseCode avs_response
260 CVV2ResponseCode cvv2_response
263 # Completely undocumented field that sometimes override <Verbiage>
264 $self->error_message($response->{Error}) if $response->{Error};
266 $self->card_type(CARD_TYPES->{$self->card_type});
268 $self->{products_raw} = $response->{Products};
276 croak "Missing required argument 'merchant_id'"
277 unless defined $self->{merchant_id};
279 my ($page, $response, %headers) =
286 xxxRequestMode => 'X',
287 xxxRequestData => Encode::encode_utf8(
293 croak 'Error connecting to server' unless $page;
294 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
296 $self->parse_response($page);
307 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
311 use Business::OnlinePayment;
313 $txn = new Business::OnlinePayment 'InternetSecure',
314 merchant_id => '0000';
317 action => 'Normal Authorization',
320 card_number => '0000000000000000',
321 exp_date => '2004-07',
322 cvv2 => '000', # Optional
324 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
326 address => '123 Street',
327 city => 'Metropolis',
331 phone => '(555) 555-1212',
332 email => 'fbriere@fbriere.net',
334 description => 'Online purchase',
342 if ($txn->is_success) {
343 print "Card processed successfully: " . $tx->authorization . "\n";
345 print "Card was rejected: " . $tx->error_message . "\n";
350 Business::OnlinePayment::InternetSecure is an implementation of
351 L<Business::OnlinePayment> that allows for processing online credit card
352 payments through InternetSecure.
354 See L<Business::OnlinePayment> for more information about the generic
355 Business::OnlinePayment interface.
359 Object creation is done via L<Business::OnlinePayment>; see its manpage for
360 details. The I<merchant_id> processor option is required, and corresponds
361 to the merchant ID assigned to you by InternetSecure.
365 (Other methods are also available -- see L<Business::OnlinePayment> for more
368 =head2 Before order submission
372 =item content( CONTENT )
374 Sets up the data prior to a transaction (overwriting any previous data by the
375 same occasion). CONTENT is an associative array (hash), containing some of
376 the following fields:
380 =item action (required)
382 What to do with the transaction. Only C<Normal Authorization> is supported
387 Transaction type, being one of the following:
395 =item - American Express
401 (This is actually ignored for the moment, and can be left blank or undefined.)
403 =item card_number (required)
405 Credit card number. Spaces are allowed, and will be automatically removed.
407 =item exp_date (required)
409 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
410 any syntax, this module is rather lax regarding what it will accept. The
411 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
416 Three- or four-digit verification code printed on the card. This can be left
417 blank or undefined, in which case no check will be performed. Whether or not a
418 transaction will be declined in case of a mismatch depends on the merchant
419 account configuration.
421 This number may be called Card Verification Value (CVV2), Card Validation
422 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
426 A short description of the purchase. See L<"Products list syntax"> for
427 an alternate syntax that allows a list of products to be specified.
429 =item amount (usually required)
431 Total amount to be billed, excluding taxes if they are to be added separately
434 This field is required if B<description> is a string, but should be left
435 undefined if B<description> contains a list of products instead, as outlined
436 in L<"Products list syntax">.
440 Currency of all amounts for this order. This can currently be either
441 C<CAD> (default) or C<USD>.
445 Taxes to be added automatically to B<amount> by InternetSecure.
447 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
448 by concatenating them with spaces, such as C<GST HST>.
450 =item name / company / address / city / state / zip / country / phone / email
452 Customer information. B<Country> should be a two-letter code taken from ISO
459 =head2 After order submission
463 =item receipt_number()
465 Receipt number of this transaction; this is actually a string, unique to all
466 InternetSecure transactions.
470 Sales order number of this transaction. This is a number, unique to each
471 merchant, which is incremented by 1 each time.
475 Total amount billed for this order, including taxes.
479 Type of the credit card used for the submitted order, being one of the
488 =item - American Express
494 =item avs_response() / cvv2_response()
496 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
497 the list of possible values.
509 =head2 Products list syntax
511 Optionally, the B<description> field of B<content()> can contain a reference
512 to an array of products, instead of a simple string. Each element of this
513 array represents a different product, and must be a reference to a hash with
514 the following fields:
520 Unit price of this product.
524 Ordered quantity of this product.
528 Internal code for this product.
532 Description of this product
536 Taxes that should be automatically added to this product. If specified, this
537 overrides the B<taxes> field passed to B<content()>.
541 When using a products list, the B<amount> field passed to B<content()> should
545 =head2 Character encoding
547 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
548 characters are theoretically available when submitting information via
549 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
551 When using non-ASCII characters, all data provided to B<submit()> should either
552 be in the current native encoding (typically latin-1, unless it was modified
553 via the C<encoding> pragma), or be decoded via the C<Encode> module.
554 Conversely, all data returned after calling B<submit()> will be automatically
565 L<Business::OnlinePayment>
569 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
571 =head1 COPYRIGHT AND LICENSE
573 Copyright (C) 2006 by Frédéric Brière
575 This library is free software; you can redistribute it and/or modify
576 it under the same terms as Perl itself, either Perl version 5.8.4 or,
577 at your option, any later version of Perl 5 you may have available.