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
49 # Just in case someone tries to call taxes() *before* submit()
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);
111 foreach (split ' ' => uc($data{taxes} || '')) {
112 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
116 if ($self->test_transaction) {
117 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
121 sprintf('%.2f' => $data{amount}),
122 $data{quantity} || 1,
123 _esc _def $data{sku},
124 _esc _def $data{description},
125 join('' => map "{$_}" => @flags),
129 # Generate the XML document for this transaction
134 my %content = $self->content;
136 $self->required_fields(qw(action card_number exp_date));
138 croak 'Unsupported transaction type'
139 if $content{type} && $content{type} !~
140 /^(Visa|MasterCard|American Express|Discover)$/i;
142 croak 'Unsupported action'
143 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
145 $content{currency} = uc($content{currency} || 'CAD');
146 croak "Unknown currency code ", $content{currency}
147 unless $content{currency} =~ /^(CAD|USD)$/;
149 $content{taxes} = uc($content{taxes} || '');
151 my %data = $self->get_remap_fields(qw(
152 xxxCard_Number card_number
164 xxxShippingName ship_name
165 xxxShippingCompany ship_company
166 xxxShippingAddress ship_address
167 xxxShippingCity ship_city
168 xxxShippingProvince ship_state
169 xxxShippingPostal ship_zip
170 xxxShippingCountry ship_country
171 xxxShippingPhone ship_phone
172 xxxShippingEmail ship_email
175 $data{MerchantNumber} = $self->merchant_id;
177 $data{xxxCard_Number} =~ tr/- //d;
178 $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
180 my ($y, $m) = $self->parse_expdate($content{exp_date});
181 $data{xxxCCYear} = sprintf '%.4u' => $y;
182 $data{xxxCCMonth} = sprintf '%.2u' => $m;
184 if (defined $content{cvv2} && $content{cvv2} ne '') {
186 $data{CVV2Indicator} = $content{cvv2};
189 $data{CVV2Indicator} = '';
192 if (ref $content{description}) {
193 $data{Products} = join '|' => map $self->prod_string(
195 taxes => $content{taxes},
197 @{ $content{description} };
199 $self->required_fields(qw(amount));
200 $data{Products} = $self->prod_string(
202 taxes => $content{taxes},
203 amount => $content{amount},
204 description => $content{description},
211 RootName => 'TranxRequest',
212 SuppressEmpty => undef,
213 XMLDecl => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
217 # Map the various fields from the response, and put their values into our
218 # object for retrieval.
221 my ($self, $data, %map) = @_;
223 while (my ($k, $v) = each %map) {
225 $self->$k($data->{$v});
230 my ($self, $response) = @_;
234 my $products = $response->{Products};
235 return unless $products;
237 foreach my $node (@$products) {
238 my $flags = $node->{flags};
240 grep($_ eq '{TAX}', @$flags) &&
241 grep($_ eq '{CALCULATED}', @$flags))
243 $taxes{ $node->{code} } = $node->{subtotal};
250 # Parse the server's response and set various fields
253 my ($self, $response) = @_;
255 $self->server_response($response);
257 local $/ = "\n"; # Make sure to avoid bug #17687
259 $response = xml_in($response,
260 ForceArray => [qw(product flag)],
261 GroupTags => { qw(Products product flags flag) },
263 SuppressEmpty => undef,
266 $self->infuse($response,
267 result_code => 'Page',
268 error_message => 'Verbiage',
269 authorization => 'ApprovalCode',
270 avs_response => 'AVSResponseCode',
271 cvv2_response => 'CVV2ResponseCode',
273 receipt_number => 'ReceiptNumber',
274 sales_number => 'SalesOrderNumber',
279 cardholder => 'xxxName',
280 card_type => 'CardType',
281 total_amount => 'TotalAmount',
284 $self->is_success(scalar grep $self->result_code eq $_, SUCCESS_CODES);
286 # Completely undocumented field that sometimes override <Verbiage>
287 $self->error_message($response->{Error}) if $response->{Error};
289 # Delete error_message if transaction was successful
290 $self->error_message(undef) if $self->is_success;
292 $self->card_type(CARD_TYPES->{$self->card_type});
294 $self->taxes( { $self->extract_taxes($response) } );
302 croak "Missing required argument 'merchant_id'"
303 unless defined $self->{merchant_id};
305 my ($page, $response, %headers) =
312 xxxRequestMode => 'X',
313 xxxRequestData => $self->to_xml,
317 croak 'Error connecting to server' unless $page;
318 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
320 # The response is marked UTF-8, but it's really Latin-1. Sigh.
321 $page =~ s/^(<\?xml.*?) encoding="utf-8"/$1 encoding="iso-8859-1"/si;
323 $self->parse_response($page);
334 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
338 use Business::OnlinePayment;
340 $txn = new Business::OnlinePayment 'InternetSecure',
341 merchant_id => '0000';
344 action => 'Normal Authorization',
346 type => 'Visa', # Optional
347 card_number => '4111 1111 1111 1111',
348 exp_date => '2004-07',
349 cvv2 => '000', # Optional
351 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
353 address => '123 Street',
354 city => 'Metropolis',
358 phone => '(555) 555-1212',
359 email => 'fbriere@fbriere.net',
364 description => 'Test transaction',
369 if ($txn->is_success) {
370 print "Card processed successfully: " . $tx->authorization . "\n";
372 print "Card was rejected: " . $tx->error_message . "\n";
377 C<Business::OnlinePayment::InternetSecure> is an implementation of
378 C<Business::OnlinePayment> that allows for processing online credit card
379 payments through InternetSecure.
381 See L<Business::OnlinePayment> for more information about the generic
382 Business::OnlinePayment interface.
386 Object creation is done via C<Business::OnlinePayment>; see its manpage for
387 details. The B<merchant_id> processor option is required, and corresponds
388 to the merchant ID assigned to you by InternetSecure.
392 =head2 Transaction setup and transmission
396 =item content( CONTENT )
398 Sets up the data prior to a transaction. CONTENT is an associative array
399 (hash), containing some of the following fields:
403 =item action (required)
405 What to do with the transaction. Only C<Normal Authorization> is supported
410 Transaction type, being one of the following:
418 =item - American Express
424 (This is actually ignored for the moment, and can be left blank or undefined.)
426 =item card_number (required)
428 Credit card number. Spaces and dashes are automatically removed.
430 =item exp_date (required)
432 Credit card expiration date. Since C<Business::OnlinePayment> does not specify
433 any syntax, this module is rather lax regarding what it will accept. The
434 recommended syntax is C<YYYY-MM>, but forms such as C<MM/YYYY> or C<MMYY> are
439 Three- or four-digit verification code printed on the card. This can be left
440 blank or undefined, in which case no check will be performed. Whether or not a
441 transaction will be declined in case of a mismatch depends on the merchant
442 account configuration.
444 This number may be called Card Verification Value (CVV2), Card Validation
445 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
449 A short description of the transaction. See L<"Products list syntax"> for
450 an alternate syntax that allows a list of products to be specified.
452 =item amount (usually required)
454 Total amount to be billed, excluding taxes if they are to be added separately
457 This field is required if B<description> is a string, but should be left
458 undefined if B<description> contains a list of products instead, as outlined
459 in L<"Products list syntax">.
463 Currency of all amounts for this order. This can currently be either
464 C<CAD> (default) or C<USD>.
468 Taxes to be added automatically to B<amount> by InternetSecure.
470 Available taxes are C<GST>, C<PST> and C<HST>. Multiple taxes can specified
471 by concatenating them with spaces, such as C<GST HST>.
473 =item name / company / address / city / state / zip / country / phone / email
475 Customer information. B<country> should be a two-letter code taken from ISO
482 Submit the transaction to InternetSecure.
486 =head2 Post-submission methods
492 Returns true if the transaction was submitted successfully.
496 Response code returned by InternetSecure.
498 =item error_message()
500 Error message if the transaction was unsuccessful; C<undef> otherwise. (You
501 should not rely on this to test whether a transaction was successful; use
502 B<is_success>() instead.)
504 =item receipt_number()
506 Receipt number (a string, actually) of this transaction, unique to all
507 InternetSecure transactions.
511 Sales order number of this transaction. This is a number, unique to each
512 merchant, which is incremented by 1 each time.
516 Universally Unique Identifier associated to this transaction. This is a
517 128-bit value returned as a 36-character string such as
518 C<f81d4fae-7dec-11d0-a765-00a0c91e6bf6>. See RFC 4122 for more details on
521 B<guid>() is provided as an alias to this method.
523 =item authorization()
525 Authorization code for this transaction.
527 =item avs_response() / cvv2_response()
529 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
530 the list of possible values.
534 Date and time of the transaction. Format is C<YYYY/MM/DD hh:mm:ss>.
538 Total amount billed for this order, including taxes.
542 Returns a I<reference> to a hash that maps tax names (such as C<GST>) to the
543 amount that was billed for each.
547 Cardholder's name. This is currently a mere copy of the B<name> field passed
552 Type of the credit card used for the submitted order, being one of the
561 =item - American Express
573 =head2 Products list syntax
575 Optionally, the B<description> field of B<content>() can contain a reference
576 to an array of products, instead of a simple string. Each element of this
577 array represents a different product, and must be a reference to a hash with
578 the following fields:
584 Unit price of this product.
588 Ordered quantity of this product.
592 Internal code for this product.
596 Description of this product
600 Taxes that should be automatically added to this product. If specified, this
601 overrides the B<taxes> field passed to B<content>().
605 When using a products list, the B<amount> field passed to B<content>() should
609 =head2 Character encoding
611 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
612 characters are theoretically available when submitting information via
613 B<submit>(). (Further restrictions may be imposed by InternetSecure itself.)
615 When using non-ASCII characters, all data provided to B<submit>() should either
616 be in the current native encoding (typically latin-1, unless it was modified
617 via the C<encoding> pragma), or be decoded via the C<Encode> module.
618 Conversely, all data returned after calling B<submit>() will be automatically
629 L<Business::OnlinePayment>
633 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
635 =head1 COPYRIGHT AND LICENSE
637 Copyright (C) 2006 by Frédéric Brière
639 This library is free software; you can redistribute it and/or modify
640 it under the same terms as Perl itself, either Perl version 5.8.4 or,
641 at your option, any later version of Perl 5 you may have available.