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_order_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} ||= 'CAD';
152 $content{currency} = uc $content{currency};
153 croak "Unknown currency code ", $content{currency}
154 unless $content{currency} =~ /^(CAD|USD)$/;
156 $content{taxes} ||= '';
157 $content{taxes} = uc $content{taxes};
159 my %data = $self->get_remap_fields(qw(
160 xxxCard_Number card_number
172 xxxShippingName ship_name
173 xxxShippingCompany ship_company
174 xxxShippingAddress ship_address
175 xxxShippingCity ship_city
176 xxxShippingProvince ship_state
177 xxxShippingPostal ship_zip
178 xxxShippingCountry ship_country
179 xxxShippingPhone ship_phone
180 xxxShippingEmail ship_email
183 $data{MerchantNumber} = $self->merchant_id;
185 $data{xxxCard_Number} =~ tr/ //d;
186 $data{xxxCard_Number} =~ s/^[0-36-9]/4/ if $self->test_transaction;
188 my ($y, $m) = $self->parse_expdate($content{exp_date});
189 $data{xxxCCYear} = sprintf '%.4u' => $y;
190 $data{xxxCCMonth} = sprintf '%.2u' => $m;
192 if (defined $content{cvv2} && $content{cvv2} ne '') {
194 $data{CVV2Indicator} = $content{cvv2};
197 $data{CVV2Indicator} = '';
200 if (ref $content{description}) {
201 $data{Products} = join '|' => map $self->prod_string(
203 taxes => $content{taxes},
205 @{ $content{description} };
207 $self->required_fields(qw(amount));
208 $data{Products} = $self->prod_string(
210 taxes => $content{taxes},
211 amount => $content{amount},
212 description => $content{description},
218 RootName => 'TranxRequest',
219 SuppressEmpty => undef,
220 XMLDecl => '<?xml version="1.0" encoding="utf-8" 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 $response = xml_in($response,
244 ForceArray => [qw(product flag)],
245 GroupTags => { qw(Products product flags flag) },
247 SuppressEmpty => undef,
250 my $code = $self->result_code($response->{Page});
251 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
253 $self->infuse($response, qw(
254 ReceiptNumber receipt_number
255 SalesOrderNumber sales_order_number
258 ApprovalCode authorization
259 Verbiage error_message
260 TotalAmount total_amount
261 AVSResponseCode avs_response
262 CVV2ResponseCode cvv2_response
265 # Completely undocumented field that sometimes override <Verbiage>
266 $self->error_message($response->{Error}) if $response->{Error};
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 => Encode::encode_utf8(
295 croak 'Error connecting to server' unless $page;
296 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
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',
322 card_number => '0000000000000000',
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',
336 description => 'Online purchase',
344 if ($txn->is_success) {
345 print "Card processed successfully: " . $tx->authorization . "\n";
347 print "Card was rejected: " . $tx->error_message . "\n";
352 Business::OnlinePayment::InternetSecure is an implementation of
353 L<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 L<Business::OnlinePayment>; see its manpage for
362 details. The I<merchant_id> processor option is required, and corresponds
363 to the merchant ID assigned to you by InternetSecure.
367 (See L<Business::OnlinePayment> for more methods.)
369 =head2 Before order submission
373 =item content( CONTENT )
375 Sets up the data prior to a transaction (overwriting any previous data by the
376 same occasion). CONTENT is an associative array (hash), containing some of
377 the following fields:
381 =item action (required)
383 What to do with the transaction. Only C<Normal Authorization> is supported
388 Transaction type, being one of the following:
396 =item - American Express
402 (This is actually ignored for the moment, and can be left blank or undefined.)
404 =item card_number (required)
406 Credit card number. Spaces are allowed, and will be automatically removed.
408 =item exp_date (required)
410 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
411 any syntax, this module is rather lax regarding what it will accept. The
412 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
417 Three- or four-digit verification code printed on the card. This can be left
418 blank or undefined, in which case no check will be performed. Whether or not a
419 transaction will be declined in case of a mismatch depends on the merchant
420 account configuration.
422 This number may be called Card Verification Value (CVV2), Card Validation
423 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
427 A short description of the purchase. See L<"Products list syntax"> for
428 an alternate syntax that allows a list of products to be specified.
432 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, and should be left
434 undefined if B<description> contains a list of products, as outlined in
435 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. These should not be included in B<amount>;
445 they will be automatically added by InternetSecure later on.
447 Available taxes are C<GST>, C<PST> and C<HST>. Taxes can be combined by
448 separating 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() / sales_order_number()
465 Receipt number and sales order number of submitted order.
469 Total amount billed for this order, including taxes.
473 Type of the credit card used for the submitted order, being one of the
482 =item - American Express
488 =item avs_response() / cvv2_response()
490 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
491 the list of possible values.
503 =head2 Products list syntax
505 Optionally, the B<description> field of B<content()> can contain a reference
506 to an array of products, instead of a simple string. Each element of this
507 array represents a different product, and must be a reference to a hash with
508 the following fields:
514 Unit price of this product.
518 Ordered quantity of this product.
522 Internal code for this product.
526 Description of this product
530 Taxes that should be automatically added to this product. If specified, this
531 overrides the B<taxes> field passed to B<content()>.
535 When using a products list, the B<amount> field passed to B<content()> should
539 =head2 Character encoding
541 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
542 characters are theoretically available when submitting information via
543 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
545 When using non-ASCII characters, all data provided to B<submit()> should either
546 be in the current native encoding (typically latin-1, unless it was modified
547 via the C<encoding> pragma), or be decoded via the C<Encode> module.
548 Conversely, all data returned after calling B<submit()> will be automatically
559 L<Business::OnlinePayment>
563 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
565 =head1 COPYRIGHT AND LICENSE
567 Copyright (C) 2006 by Frédéric Brière
569 This library is free software; you can redistribute it and/or modify
570 it under the same terms as Perl itself, either Perl version 5.8.4 or,
571 at your option, any later version of Perl 5 you may have available.