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',
26 # Convenience functions to avoid undefs and escape products strings
27 sub _def($) { defined $_[0] ? $_[0] : '' }
28 sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ }
34 $self->server('secure.internetsecure.com');
36 $self->path('/process.cgi');
39 receipt_number sales_order_number
42 avs_response cvv2_response
46 # OnlinePayment's remap_fields is buggy, so we simply rewrite it
49 my ($self, %map) = @_;
51 my %content = $self->content();
53 $content{$map{$_}} = delete $content{$_};
55 $self->content(%content);
58 # Combine get_fields and remap_fields for convenience
60 sub get_remap_fields {
61 my ($self, %map) = @_;
63 $self->remap_fields(reverse %map);
64 my %data = $self->get_fields(keys %map);
66 foreach (values %data) {
67 $_ = '' unless defined;
73 # Since there's no standard format for expiration dates, we try to do our best
76 my ($self, $str) = @_;
82 if (/^(\d{4})\W(\d{1,2})$/ || # 2004.07 or 2004-7
83 /^(\d\d)\W(\d)$/ || # 04/7
84 /^(\d\d)[.-](\d\d)$/) { # 04-07
86 } elsif (/^(\d{1,2})\W(\d{4})$/ || # 07-2004 or 7/2004
87 /^(\d)\W(\d\d)$/ || # 7/04
88 /^(\d\d)\/(\d\d)$/) { # 07/04
91 croak "Unable to parse expiration date: $str";
94 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
99 # Convert a single product into a product string
102 my ($self, $currency, $taxes, %data) = @_;
104 croak "Missing amount in product" unless defined $data{amount};
106 my @flags = ($currency);
108 $taxes = uc $data{taxes} if defined $data{taxes};
109 foreach (split ' ' => $taxes) {
110 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
114 if ($self->test_transaction) {
115 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
119 sprintf('%.2f' => $data{amount}),
120 $data{quantity} || 1,
121 _esc _def $data{sku},
122 _esc _def $data{description},
123 join('' => map "{$_}" => @flags),
127 # Generate the XML document for this transaction
132 my %content = $self->content;
134 $self->required_fields(qw(action card_number exp_date));
136 croak 'Unsupported transaction type'
137 if $content{type} && $content{type} !~
138 /^(Visa|MasterCard|American Express|Discover)$/i;
140 croak 'Unsupported action'
141 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
143 $content{currency} ||= 'CAD';
144 $content{currency} = uc $content{currency};
145 croak "Unknown currency code ", $content{currency}
146 unless $content{currency} =~ /^(CAD|USD)$/;
148 $content{taxes} ||= '';
149 $content{taxes} = uc $content{taxes};
151 my %data = $self->get_remap_fields(qw(
152 xxxCardNumber 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{xxxCardNumber} =~ tr/ //d;
179 my ($y, $m) = $self->parse_expdate($content{exp_date});
180 $data{xxxCCYear} = sprintf '%.4u' => $y;
181 $data{xxxCCMonth} = sprintf '%.2u' => $m;
183 if (defined $content{cvv2} && $content{cvv2} ne '') {
185 $data{CVV2Indicator} = $content{cvv2};
188 $data{CVV2Indicator} = '';
191 if (ref $content{description}) {
192 $data{Products} = join '|' => map $self->prod_string(
196 @{ $content{description} };
198 $self->required_fields(qw(amount));
199 $data{Products} = $self->prod_string(
202 amount => $content{amount},
203 description => $content{description},
209 RootName => 'TranxRequest',
210 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
214 # Map the various fields from the response, and put their values into our
215 # object for retrieval.
218 my ($self, $data, %map) = @_;
220 while (my ($k, $v) = each %map) {
222 $self->$v($data->{$k});
226 # Parse the server's response and set various fields
229 my ($self, $response) = @_;
231 $self->server_response($response);
233 # (It's not quite clear whether there should be some decoding, or if
234 # the result is already utf8.)
236 $response = xml_in($response,
237 ForceArray => [qw(product flag)],
238 GroupTags => { qw(Products product flags flag) },
240 SuppressEmpty => undef,
243 my $code = $self->result_code($response->{Page});
244 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
246 $self->infuse($response, qw(
247 ReceiptNumber receipt_number
248 SalesOrderNumber sales_order_number
252 ApprovalCode authorization
253 Verbiage error_message
254 TotalAmount total_amount
255 AVSResponseCode avs_response
256 CVV2ResponseCode cvv2_response
259 $self->card_type(CARD_TYPES->{$self->card_type});
261 $self->{products_raw} = $response->{Products};
269 croak "Missing required argument 'merchant_id'"
270 unless defined $self->{merchant_id};
272 my ($page, $response, %headers) =
279 xxxRequestMode => 'X',
280 xxxRequestData => Encode::encode_utf8(
286 croak 'Error connecting to server' unless $page;
287 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
289 $self->parse_response($page);
300 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
304 use Business::OnlinePayment;
306 $txn = new Business::OnlinePayment 'InternetSecure',
307 merchant_id => '0000';
310 action => 'Normal Authorization',
313 card_number => '0000000000000000',
314 exp_date => '2004-07',
315 cvv2 => '000', # Optional
317 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
319 address => '123 Street',
320 city => 'Metropolis',
324 phone => '(555) 555-1212',
325 email => 'fbriere@fbriere.net',
327 description => 'Online purchase',
335 if ($txn->is_success) {
336 print "Card processed successfully: " . $tx->authorization . "\n";
338 print "Card was rejected: " . $tx->error_message . "\n";
343 Business::OnlinePayment::InternetSecure is an implementation of
344 L<Business::OnlinePayment> that allows for processing online credit card
345 payments through Internet Secure.
347 See L<Business::OnlinePayment> for more information about the generic
348 Business::OnlinePayment interface.
352 Object creation is done via L<Business::OnlinePayment>; see its manpage for
353 details. The I<merchant_id> processor option is required, and corresponds
354 to the merchant ID assigned to you by Internet Secure.
358 (See L<Business::OnlinePayment> for more methods.)
360 =head2 Before order submission
364 =item content( CONTENT )
366 Sets up the data prior to a transaction (overwriting any previous data by the
367 same occasion). CONTENT is an associative array (hash), containing some of
368 the following fields:
372 =item action (required)
374 What to do with the transaction. Only C<Normal Authorization> is supported
379 Transaction type, being one of the following:
387 =item - American Express
393 (This is actually ignored for the moment, and can be left blank or undefined.)
395 =item card_number (required)
397 Credit card number. Any spaces will be removed.
399 =item exp_date (required)
401 Credit card expiration date. Since L<Business::OnlinePayment> does not
402 specify any syntax, this module is rather lax in what it will accept. It is
403 recommended to use either I<YYYY-MM> or I<MM/YYYY>, to avoid any nasty
408 Three- or four-digit verification code printed on the card. This can be left
409 blank or undefined, in which case no check will be performed. Whether or not a
410 transaction will be declined in case of a mismatch depends on the merchant.
412 This number may be called Card Verification Value (CVV2), Card Validation
413 Code (CVC2) or Card Identification number (CID).
417 A short description of the purchase. See L<"Products list syntax"> for
418 an alternate syntax that allows a list of products to be specified.
422 Total amount to be billed, excluding taxes if they are to be added separately.
423 This field is required if B<description> is a string, and should be left
424 undefined if B<description> contains a list of products, as outlined in
425 L<"Products list syntax">.
429 Currency of all amounts for this order. This can currently be either
430 C<CAN> (default) or C<USD>.
434 Taxes to be added automatically. These should not be included in B<amount>;
435 they will be added by Internet Secure later on.
437 Available taxes are C<GST>, C<PST> and C<HST>; multiple taxes must be
440 =item name / company / address / city / state / zip / country / phone / email
442 Facultative customer information. B<state> should be either a postal
443 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
444 be a two-letter code taken from in ISO 3166-1.
450 =head2 After order submission
454 =item receipt_number() / sales_order_number()
456 Receipt number and sales order number of submitted order.
460 Total amount billed for this order, including taxes.
464 Cardholder's name. This is currently a mere copy of the B<name> field passed
469 Type of the credit card used for the submitted order, being one of the
478 =item - American Express
484 =item avs_response() / cvv2_response()
486 Results of the AVS and CVV2 checks. See the Internet Secure documentation for
487 the list of possible values.
494 =head2 Products list syntax
496 Optionally, the B<description> field of B<content()> can contain a reference
497 to an array of products, instead of a simple string. Each element of this
498 array represents a different product, and must be a reference to a hash with
499 the following fields:
505 Unit price of this product.
509 Ordered quantity of this product. This can be a decimal value.
513 Internal code for this product.
517 Description of this product
521 Taxes that should be automatically added to this product. If specified, this
522 overrides the B<taxes> field passed to B<content()>.
526 When using a products list, the B<amount> field passed to B<content()> should
530 =head2 Character encodings
547 L<Business::OnlinePayment>
551 Frederic Briere, E<lt>fbriere@fbriere.netE<gt>
553 =head1 COPYRIGHT AND LICENSE
555 Copyright (C) 2004 by Frederic Briere
557 This library is free software; you can redistribute it and/or modify
558 it under the same terms as Perl itself, either Perl version 5.8.4 or,
559 at your option, any later version of Perl 5 you may have available.