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})$/ || # YYYY.MM or YYYY-M
83 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
84 /^(\d\d)[.-](\d\d)$/) { # YY-MM
86 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
87 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
88 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
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 $response = xml_in($response,
234 ForceArray => [qw(product flag)],
235 GroupTags => { qw(Products product flags flag) },
237 SuppressEmpty => undef,
240 my $code = $self->result_code($response->{Page});
241 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
243 $self->infuse($response, qw(
244 ReceiptNumber receipt_number
245 SalesOrderNumber sales_order_number
249 ApprovalCode authorization
250 Verbiage error_message
251 TotalAmount total_amount
252 AVSResponseCode avs_response
253 CVV2ResponseCode cvv2_response
256 $self->card_type(CARD_TYPES->{$self->card_type});
258 $self->{products_raw} = $response->{Products};
266 croak "Missing required argument 'merchant_id'"
267 unless defined $self->{merchant_id};
269 my ($page, $response, %headers) =
276 xxxRequestMode => 'X',
277 xxxRequestData => Encode::encode_utf8(
283 croak 'Error connecting to server' unless $page;
284 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
286 $self->parse_response($page);
297 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
301 use Business::OnlinePayment;
303 $txn = new Business::OnlinePayment 'InternetSecure',
304 merchant_id => '0000';
307 action => 'Normal Authorization',
310 card_number => '0000000000000000',
311 exp_date => '2004-07',
312 cvv2 => '000', # Optional
314 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
316 address => '123 Street',
317 city => 'Metropolis',
321 phone => '(555) 555-1212',
322 email => 'fbriere@fbriere.net',
324 description => 'Online purchase',
332 if ($txn->is_success) {
333 print "Card processed successfully: " . $tx->authorization . "\n";
335 print "Card was rejected: " . $tx->error_message . "\n";
340 Business::OnlinePayment::InternetSecure is an implementation of
341 L<Business::OnlinePayment> that allows for processing online credit card
342 payments through InternetSecure.
344 See L<Business::OnlinePayment> for more information about the generic
345 Business::OnlinePayment interface.
349 Object creation is done via L<Business::OnlinePayment>; see its manpage for
350 details. The I<merchant_id> processor option is required, and corresponds
351 to the merchant ID assigned to you by InternetSecure.
355 (See L<Business::OnlinePayment> for more methods.)
357 =head2 Before order submission
361 =item content( CONTENT )
363 Sets up the data prior to a transaction (overwriting any previous data by the
364 same occasion). CONTENT is an associative array (hash), containing some of
365 the following fields:
369 =item action (required)
371 What to do with the transaction. Only C<Normal Authorization> is supported
376 Transaction type, being one of the following:
384 =item - American Express
390 (This is actually ignored for the moment, and can be left blank or undefined.)
392 =item card_number (required)
394 Credit card number. Spaces are allowed, and will be automatically removed.
396 =item exp_date (required)
398 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
399 any syntax, this module is rather lax regarding what it will accept. The
400 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
405 Three- or four-digit verification code printed on the card. This can be left
406 blank or undefined, in which case no check will be performed. Whether or not a
407 transaction will be declined in case of a mismatch depends on the merchant
408 account configuration.
410 This number may be called Card Verification Value (CVV2), Card Validation
411 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
415 A short description of the purchase. See L<"Products list syntax"> for
416 an alternate syntax that allows a list of products to be specified.
420 Total amount to be billed, excluding taxes if they are to be added separately.
421 This field is required if B<description> is a string, and should be left
422 undefined if B<description> contains a list of products, as outlined in
423 L<"Products list syntax">.
427 Currency of all amounts for this order. This can currently be either
428 C<CAD> (default) or C<USD>.
432 Taxes to be added automatically. These should not be included in B<amount>;
433 they will be automatically added by InternetSecure later on.
435 Available taxes are C<GST>, C<PST> and C<HST>. Taxes can be combined by
436 separating them with spaces, such as C<GST HST>.
438 =item name / company / address / city / state / zip / country / phone / email
440 Facultative customer information. B<state> should be either a postal
441 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
442 be a two-letter code taken from ISO 3166-1.
448 =head2 After order submission
452 =item receipt_number() / sales_order_number()
454 Receipt number and sales order number of submitted order.
458 Total amount billed for this order, including taxes.
462 Cardholder's name. This is currently a mere copy of the B<name> field passed
467 Type of the credit card used for the submitted order, being one of the
476 =item - American Express
482 =item avs_response() / cvv2_response()
484 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
485 the list of possible values.
497 =head2 Products list syntax
499 Optionally, the B<description> field of B<content()> can contain a reference
500 to an array of products, instead of a simple string. Each element of this
501 array represents a different product, and must be a reference to a hash with
502 the following fields:
508 Unit price of this product.
512 Ordered quantity of this product. This can be a decimal value.
516 Internal code for this product.
520 Description of this product
524 Taxes that should be automatically added to this product. If specified, this
525 overrides the B<taxes> field passed to B<content()>.
529 When using a products list, the B<amount> field passed to B<content()> should
533 =head2 Character encoding
535 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
536 characters are theoretically available when submitting information via
537 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
539 When using non-ASCII characters, all data provided to B<submit()> should either
540 be in the current native encoding (typically latin-1, unless it was modified
541 via the C<encoding> pragma), or be decoded via the C<Encode> module.
542 Conversely, all data returned after calling B<submit()> will be automatically
553 L<Business::OnlinePayment>
557 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
559 =head1 COPYRIGHT AND LICENSE
561 Copyright (C) 2006 by Frédéric Brière
563 This library is free software; you can redistribute it and/or modify
564 it under the same terms as Perl itself, either Perl version 5.8.4 or,
565 at your option, any later version of Perl 5 you may have available.