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, $taxes, %data) = @_;
113 croak "Missing amount in product" unless defined $data{amount};
115 my @flags = ($currency);
117 $taxes = uc $data{taxes} if defined $data{taxes};
118 foreach (split ' ' => $taxes) {
119 croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
123 if ($self->test_transaction) {
124 push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
128 sprintf('%.2f' => $data{amount}),
129 $data{quantity} || 1,
130 _esc _def $data{sku},
131 _esc _def $data{description},
132 join('' => map "{$_}" => @flags),
136 # Generate the XML document for this transaction
141 my %content = $self->content;
143 $self->required_fields(qw(action card_number exp_date));
145 croak 'Unsupported transaction type'
146 if $content{type} && $content{type} !~
147 /^(Visa|MasterCard|American Express|Discover)$/i;
149 croak 'Unsupported action'
150 unless $content{action} =~ /^Normal Authori[zs]ation$/i;
152 $content{currency} ||= 'CAD';
153 $content{currency} = uc $content{currency};
154 croak "Unknown currency code ", $content{currency}
155 unless $content{currency} =~ /^(CAD|USD)$/;
157 $content{taxes} ||= '';
158 $content{taxes} = uc $content{taxes};
160 my %data = $self->get_remap_fields(qw(
161 xxxCard_Number card_number
173 xxxShippingName ship_name
174 xxxShippingCompany ship_company
175 xxxShippingAddress ship_address
176 xxxShippingCity ship_city
177 xxxShippingProvince ship_state
178 xxxShippingPostal ship_zip
179 xxxShippingCountry ship_country
180 xxxShippingPhone ship_phone
181 xxxShippingEmail ship_email
184 $data{MerchantNumber} = $self->merchant_id;
186 $data{xxxCard_Number} =~ tr/ //d;
187 $data{xxxCard_Number} =~ s/^[0-36-9]/4/ if $self->test_transaction;
189 my ($y, $m) = $self->parse_expdate($content{exp_date});
190 $data{xxxCCYear} = sprintf '%.4u' => $y;
191 $data{xxxCCMonth} = sprintf '%.2u' => $m;
193 if (defined $content{cvv2} && $content{cvv2} ne '') {
195 $data{CVV2Indicator} = $content{cvv2};
198 $data{CVV2Indicator} = '';
201 if (ref $content{description}) {
202 $data{Products} = join '|' => map $self->prod_string(
206 @{ $content{description} };
208 $self->required_fields(qw(amount));
209 $data{Products} = $self->prod_string(
212 amount => $content{amount},
213 description => $content{description},
219 RootName => 'TranxRequest',
220 SuppressEmpty => undef,
221 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
225 # Map the various fields from the response, and put their values into our
226 # object for retrieval.
229 my ($self, $data, %map) = @_;
231 while (my ($k, $v) = each %map) {
233 $self->$v($data->{$k});
237 # Parse the server's response and set various fields
240 my ($self, $response) = @_;
242 $self->server_response($response);
244 $response = xml_in($response,
245 ForceArray => [qw(product flag)],
246 GroupTags => { qw(Products product flags flag) },
248 SuppressEmpty => undef,
251 my $code = $self->result_code($response->{Page});
252 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
254 $self->infuse($response, qw(
255 ReceiptNumber receipt_number
256 SalesOrderNumber sales_order_number
260 ApprovalCode authorization
261 Verbiage error_message
262 TotalAmount total_amount
263 AVSResponseCode avs_response
264 CVV2ResponseCode cvv2_response
267 # Completely undocumented field that sometimes override <Verbiage>
268 $self->error_message($response->{Error}) if $response->{Error};
270 $self->card_type(CARD_TYPES->{$self->card_type});
272 $self->{products_raw} = $response->{Products};
280 croak "Missing required argument 'merchant_id'"
281 unless defined $self->{merchant_id};
283 my ($page, $response, %headers) =
290 xxxRequestMode => 'X',
291 xxxRequestData => Encode::encode_utf8(
297 croak 'Error connecting to server' unless $page;
298 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
300 $self->parse_response($page);
311 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
315 use Business::OnlinePayment;
317 $txn = new Business::OnlinePayment 'InternetSecure',
318 merchant_id => '0000';
321 action => 'Normal Authorization',
324 card_number => '0000000000000000',
325 exp_date => '2004-07',
326 cvv2 => '000', # Optional
328 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
330 address => '123 Street',
331 city => 'Metropolis',
335 phone => '(555) 555-1212',
336 email => 'fbriere@fbriere.net',
338 description => 'Online purchase',
346 if ($txn->is_success) {
347 print "Card processed successfully: " . $tx->authorization . "\n";
349 print "Card was rejected: " . $tx->error_message . "\n";
354 Business::OnlinePayment::InternetSecure is an implementation of
355 L<Business::OnlinePayment> that allows for processing online credit card
356 payments through InternetSecure.
358 See L<Business::OnlinePayment> for more information about the generic
359 Business::OnlinePayment interface.
363 Object creation is done via L<Business::OnlinePayment>; see its manpage for
364 details. The I<merchant_id> processor option is required, and corresponds
365 to the merchant ID assigned to you by InternetSecure.
369 (See L<Business::OnlinePayment> for more methods.)
371 =head2 Before order submission
375 =item content( CONTENT )
377 Sets up the data prior to a transaction (overwriting any previous data by the
378 same occasion). CONTENT is an associative array (hash), containing some of
379 the following fields:
383 =item action (required)
385 What to do with the transaction. Only C<Normal Authorization> is supported
390 Transaction type, being one of the following:
398 =item - American Express
404 (This is actually ignored for the moment, and can be left blank or undefined.)
406 =item card_number (required)
408 Credit card number. Spaces are allowed, and will be automatically removed.
410 =item exp_date (required)
412 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
413 any syntax, this module is rather lax regarding what it will accept. The
414 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
419 Three- or four-digit verification code printed on the card. This can be left
420 blank or undefined, in which case no check will be performed. Whether or not a
421 transaction will be declined in case of a mismatch depends on the merchant
422 account configuration.
424 This number may be called Card Verification Value (CVV2), Card Validation
425 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
429 A short description of the purchase. See L<"Products list syntax"> for
430 an alternate syntax that allows a list of products to be specified.
434 Total amount to be billed, excluding taxes if they are to be added separately.
435 This field is required if B<description> is a string, and should be left
436 undefined if B<description> contains a list of products, as outlined in
437 L<"Products list syntax">.
441 Currency of all amounts for this order. This can currently be either
442 C<CAD> (default) or C<USD>.
446 Taxes to be added automatically. These should not be included in B<amount>;
447 they will be automatically added by InternetSecure later on.
449 Available taxes are C<GST>, C<PST> and C<HST>. Taxes can be combined by
450 separating them with spaces, such as C<GST HST>.
452 =item name / company / address / city / state / zip / country / phone / email
454 Facultative customer information. B<state> should be either a postal
455 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
456 be a two-letter code taken from ISO 3166-1.
462 =head2 After order submission
466 =item receipt_number() / sales_order_number()
468 Receipt number and sales order number of submitted order.
472 Total amount billed for this order, including taxes.
476 Cardholder's name. This is currently a mere copy of the B<name> field passed
481 Type of the credit card used for the submitted order, being one of the
490 =item - American Express
496 =item avs_response() / cvv2_response()
498 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
499 the list of possible values.
511 =head2 Products list syntax
513 Optionally, the B<description> field of B<content()> can contain a reference
514 to an array of products, instead of a simple string. Each element of this
515 array represents a different product, and must be a reference to a hash with
516 the following fields:
522 Unit price of this product.
526 Ordered quantity of this product. This can be a decimal value.
530 Internal code for this product.
534 Description of this product
538 Taxes that should be automatically added to this product. If specified, this
539 overrides the B<taxes> field passed to B<content()>.
543 When using a products list, the B<amount> field passed to B<content()> should
547 =head2 Character encoding
549 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
550 characters are theoretically available when submitting information via
551 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
553 When using non-ASCII characters, all data provided to B<submit()> should either
554 be in the current native encoding (typically latin-1, unless it was modified
555 via the C<encoding> pragma), or be decoded via the C<Encode> module.
556 Conversely, all data returned after calling B<submit()> will be automatically
567 L<Business::OnlinePayment>
571 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
573 =head1 COPYRIGHT AND LICENSE
575 Copyright (C) 2006 by Frédéric Brière
577 This library is free software; you can redistribute it and/or modify
578 it under the same terms as Perl itself, either Perl version 5.8.4 or,
579 at your option, any later version of Perl 5 you may have available.