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);
81 # Since there's no standard format for expiration dates, we try to do our best
84 my ($self, $str) = @_;
90 if (/^(\d{4})\W(\d{1,2})$/ || # YYYY.MM or YYYY-M
91 /^(\d\d)\W(\d)$/ || # YY/M or YY-M
92 /^(\d\d)[.-](\d\d)$/) { # YY-MM
94 } elsif (/^(\d{1,2})\W(\d{4})$/ || # MM-YYYY or M/YYYY
95 /^(\d)\W(\d\d)$/ || # M/YY or M-YY
96 /^(\d\d)\/?(\d\d)$/) { # MM/YY or MMYY
99 croak "Unable to parse expiration date: $str";
102 $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
107 # Convert a single product into a product string
110 my ($self, $currency, $taxes, %data) = @_;
112 croak "Missing amount in product" unless defined $data{amount};
114 my @flags = ($currency);
116 $taxes = uc $data{taxes} if defined $data{taxes};
117 foreach (split ' ' => $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 xxxCardNumber 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{xxxCardNumber} =~ tr/ //d;
187 my ($y, $m) = $self->parse_expdate($content{exp_date});
188 $data{xxxCCYear} = sprintf '%.4u' => $y;
189 $data{xxxCCMonth} = sprintf '%.2u' => $m;
191 if (defined $content{cvv2} && $content{cvv2} ne '') {
193 $data{CVV2Indicator} = $content{cvv2};
196 $data{CVV2Indicator} = '';
199 if (ref $content{description}) {
200 $data{Products} = join '|' => map $self->prod_string(
204 @{ $content{description} };
206 $self->required_fields(qw(amount));
207 $data{Products} = $self->prod_string(
210 amount => $content{amount},
211 description => $content{description},
217 RootName => 'TranxRequest',
218 SuppressEmpty => undef,
219 XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
223 # Map the various fields from the response, and put their values into our
224 # object for retrieval.
227 my ($self, $data, %map) = @_;
229 while (my ($k, $v) = each %map) {
231 $self->$v($data->{$k});
235 # Parse the server's response and set various fields
238 my ($self, $response) = @_;
240 $self->server_response($response);
242 $response = xml_in($response,
243 ForceArray => [qw(product flag)],
244 GroupTags => { qw(Products product flags flag) },
246 SuppressEmpty => undef,
249 my $code = $self->result_code($response->{Page});
250 $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
252 $self->infuse($response, qw(
253 ReceiptNumber receipt_number
254 SalesOrderNumber sales_order_number
258 ApprovalCode authorization
259 Verbiage error_message
260 TotalAmount total_amount
261 AVSResponseCode avs_response
262 CVV2ResponseCode cvv2_response
265 $self->card_type(CARD_TYPES->{$self->card_type});
267 $self->{products_raw} = $response->{Products};
275 croak "Missing required argument 'merchant_id'"
276 unless defined $self->{merchant_id};
278 my ($page, $response, %headers) =
285 xxxRequestMode => 'X',
286 xxxRequestData => Encode::encode_utf8(
292 croak 'Error connecting to server' unless $page;
293 croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
295 $self->parse_response($page);
306 Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
310 use Business::OnlinePayment;
312 $txn = new Business::OnlinePayment 'InternetSecure',
313 merchant_id => '0000';
316 action => 'Normal Authorization',
319 card_number => '0000000000000000',
320 exp_date => '2004-07',
321 cvv2 => '000', # Optional
323 name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
325 address => '123 Street',
326 city => 'Metropolis',
330 phone => '(555) 555-1212',
331 email => 'fbriere@fbriere.net',
333 description => 'Online purchase',
341 if ($txn->is_success) {
342 print "Card processed successfully: " . $tx->authorization . "\n";
344 print "Card was rejected: " . $tx->error_message . "\n";
349 Business::OnlinePayment::InternetSecure is an implementation of
350 L<Business::OnlinePayment> that allows for processing online credit card
351 payments through InternetSecure.
353 See L<Business::OnlinePayment> for more information about the generic
354 Business::OnlinePayment interface.
358 Object creation is done via L<Business::OnlinePayment>; see its manpage for
359 details. The I<merchant_id> processor option is required, and corresponds
360 to the merchant ID assigned to you by InternetSecure.
364 (See L<Business::OnlinePayment> for more methods.)
366 =head2 Before order submission
370 =item content( CONTENT )
372 Sets up the data prior to a transaction (overwriting any previous data by the
373 same occasion). CONTENT is an associative array (hash), containing some of
374 the following fields:
378 =item action (required)
380 What to do with the transaction. Only C<Normal Authorization> is supported
385 Transaction type, being one of the following:
393 =item - American Express
399 (This is actually ignored for the moment, and can be left blank or undefined.)
401 =item card_number (required)
403 Credit card number. Spaces are allowed, and will be automatically removed.
405 =item exp_date (required)
407 Credit card expiration date. Since L<Business::OnlinePayment> does not specify
408 any syntax, this module is rather lax regarding what it will accept. The
409 recommended syntax is I<YYYY-MM>, but forms such as I<MM/YYYY> or I<MMYY> are
414 Three- or four-digit verification code printed on the card. This can be left
415 blank or undefined, in which case no check will be performed. Whether or not a
416 transaction will be declined in case of a mismatch depends on the merchant
417 account configuration.
419 This number may be called Card Verification Value (CVV2), Card Validation
420 Code (CVC2) or Card Identification number (CID), depending on the card issuer.
424 A short description of the purchase. See L<"Products list syntax"> for
425 an alternate syntax that allows a list of products to be specified.
429 Total amount to be billed, excluding taxes if they are to be added separately.
430 This field is required if B<description> is a string, and should be left
431 undefined if B<description> contains a list of products, as outlined in
432 L<"Products list syntax">.
436 Currency of all amounts for this order. This can currently be either
437 C<CAD> (default) or C<USD>.
441 Taxes to be added automatically. These should not be included in B<amount>;
442 they will be automatically added by InternetSecure later on.
444 Available taxes are C<GST>, C<PST> and C<HST>. Taxes can be combined by
445 separating them with spaces, such as C<GST HST>.
447 =item name / company / address / city / state / zip / country / phone / email
449 Facultative customer information. B<state> should be either a postal
450 abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
451 be a two-letter code taken from ISO 3166-1.
457 =head2 After order submission
461 =item receipt_number() / sales_order_number()
463 Receipt number and sales order number of submitted order.
467 Total amount billed for this order, including taxes.
471 Cardholder's name. This is currently a mere copy of the B<name> field passed
476 Type of the credit card used for the submitted order, being one of the
485 =item - American Express
491 =item avs_response() / cvv2_response()
493 Results of the AVS and CVV2 checks. See the InternetSecure documentation for
494 the list of possible values.
506 =head2 Products list syntax
508 Optionally, the B<description> field of B<content()> can contain a reference
509 to an array of products, instead of a simple string. Each element of this
510 array represents a different product, and must be a reference to a hash with
511 the following fields:
517 Unit price of this product.
521 Ordered quantity of this product. This can be a decimal value.
525 Internal code for this product.
529 Description of this product
533 Taxes that should be automatically added to this product. If specified, this
534 overrides the B<taxes> field passed to B<content()>.
538 When using a products list, the B<amount> field passed to B<content()> should
542 =head2 Character encoding
544 Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
545 characters are theoretically available when submitting information via
546 B<submit()>. (Further restrictions may be imposed by InternetSecure itself.)
548 When using non-ASCII characters, all data provided to B<submit()> should either
549 be in the current native encoding (typically latin-1, unless it was modified
550 via the C<encoding> pragma), or be decoded via the C<Encode> module.
551 Conversely, all data returned after calling B<submit()> will be automatically
562 L<Business::OnlinePayment>
566 Frédéric Brière, E<lt>fbriere@fbriere.netE<gt>
568 =head1 COPYRIGHT AND LICENSE
570 Copyright (C) 2006 by Frédéric Brière
572 This library is free software; you can redistribute it and/or modify
573 it under the same terms as Perl itself, either Perl version 5.8.4 or,
574 at your option, any later version of Perl 5 you may have available.