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 get_fields now filters out undefs in 3.x. :(
49 my ($self, @fields) = @_;
51 my %content = $self->content;
53 my %new = map +($_ => $content{$_}), @fields;
58 # OnlinePayment's remap_fields is buggy, so we simply rewrite it
61 my ($self, %map) = @_;
63 my %content = $self->content();
65 $content{$map{$_}} = delete $content{$_};
67 $self->content(%content);
70 # Combine get_fields and remap_fields for convenience
72 sub get_remap_fields {
73 my ($self, %map) = @_;
75 $self->remap_fields(reverse %map);
76 my %data = $self->get_fields(keys %map);
78 foreach (values %data) {
79 $_ = '' unless defined;
85 # Since there's no standard format for expiration dates, we try to do our best
88 my ($self, $str) = @_;
94 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
95 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
96 /^(\d\d)[.-](\d\d)$/) { # YY-MM
98 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
99 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
100 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
103 croak "Unable to parse expiration date: $str";
106 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
111 # Convert a single product into a product string
114 my ($self, $currency, $taxes, %data) = @_;
116 croak "Missing amount in product" unless defined $data{amount};
118 my @flags = ($currency);
120 $taxes = uc $data{taxes} if defined $data{taxes};
121 foreach (split ' ' => $taxes) {
122 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
126 if ($self->test_transaction) {
127 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
131 sprintf('%.2f' => $data{amount}),
132 $data{quantity} || 1,
133 _esc _def $data{sku},
134 _esc _def $data{description},
135 join('' => map "{$_}" => @flags),
139 # Generate the XML document for this transaction
144 my %content = $self->content;
146 $self->required_fields(qw(action card_number exp_date));
148 croak 'Unsupported transaction type'
149 if $content{type} && $content{type} !~
150 /^(Visa|MasterCard|American Express|Discover)$/i;
152 croak 'Unsupported action'
153 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
155 $content{currency} ||= 'CAD';
156 $content{currency} = uc $content{currency};
157 croak "Unknown currency code ", $content{currency}
158 unless $content{currency} =~ /^(CAD|USD)$/;
160 $content{taxes} ||= '';
161 $content{taxes} = uc $content{taxes};
163 my %data = $self->get_remap_fields(qw(
164 xxxCardNumber card_number
176 xxxShippingName ship_name
177 xxxShippingCompany ship_company
178 xxxShippingAddress ship_address
179 xxxShippingCity ship_city
180 xxxShippingProvince ship_state
181 xxxShippingPostal ship_zip
182 xxxShippingCountry ship_country
183 xxxShippingPhone ship_phone
184 xxxShippingEmail ship_email
187 $data{MerchantNumber} = $self->merchant_id;
189 $data{xxxCardNumber} =~ tr/ //d;
191 my ($y, $m) = $self->parse_expdate($content{exp_date});
192 $data{xxxCCYear} = sprintf '%.4u' => $y;
193 $data{xxxCCMonth} = sprintf '%.2u' => $m;
195 if (defined $content{cvv2} && $content{cvv2} ne '') {
197 $data{CVV2Indicator} = $content{cvv2};
200 $data{CVV2Indicator} = '';
203 if (ref $content{description}) {
204 $data{Products} = join '|' => map $self->prod_string(
208 @{ $content{description} };
210 $self->required_fields(qw(amount));
211 $data{Products} = $self->prod_string(
214 amount => $content{amount},
215 description => $content{description},
221 RootName => 'TranxRequest',
222 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
226 # Map the various fields from the response, and put their values into our
227 # object for retrieval.
230 my ($self, $data, %map) = @_;
232 while (my ($k, $v) = each %map) {
234 $self->$v($data->{$k});
238 # Parse the server's response and set various fields
241 my ($self, $response) = @_;
243 $self->server_response($response);
245 $response = xml_in($response,
246 ForceArray => [qw(product flag)],
247 GroupTags => { qw(Products product flags flag) },
249 SuppressEmpty => undef,
252 my $code = $self->result_code($response->{Page});
253 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
255 $self->infuse($response, qw(
256 ReceiptNumber receipt_number
257 SalesOrderNumber sales_order_number
261 ApprovalCode authorization
262 Verbiage error_message
263 TotalAmount total_amount
264 AVSResponseCode avs_response
265 CVV2ResponseCode cvv2_response
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 Facultative customer information. B<state> should be either a postal
453 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
454 be a two-letter code taken from ISO 3166-1.
460 =head2 After order submission
464 =item receipt_number() / sales_order_number()
466 Receipt number and sales order number of submitted order.
470 Total amount billed for this order, including taxes.
474 Cardholder's name. This is currently a mere copy of the B<name> field passed
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. This can be a decimal value.
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.