Added backwards-compatible support for sales_number
[Business-OnlinePayment-InternetSecure.git] / InternetSecure.pm
index a7b1d0f..b6632ed 100755 (executable)
@@ -12,17 +12,17 @@ use XML::Simple qw(xml_in xml_out);
 use base qw(Business::OnlinePayment Exporter);
 
 
 use base qw(Business::OnlinePayment Exporter);
 
 
-our $VERSION = '0.01';
+our $VERSION = '0.03';
 
 
 use constant SUCCESS_CODES => qw(2000 90000 900P1);
 
 use constant CARD_TYPES => {
 
 
 use constant SUCCESS_CODES => qw(2000 90000 900P1);
 
 use constant CARD_TYPES => {
-                               VI => 'Visa',
+                               AM => 'American Express',
+                               JB => 'JCB',
                                MC => 'MasterCard',
                                MC => 'MasterCard',
-                               AX => 'American Express', # FIXME: AM?
                                NN => 'Discover',
                                NN => 'Discover',
-                               # JB?
+                               VI => 'Visa',
                        };
 
 
                        };
 
 
@@ -39,36 +39,36 @@ sub set_defaults {
        $self->path('/process.cgi');
 
        $self->build_subs(qw(
        $self->path('/process.cgi');
 
        $self->build_subs(qw(
-                               receipt_number  sales_number    uuid    guid
+                               receipt_number  order_number    uuid    guid
                                date
                                card_type       cardholder
                                date
                                card_type       cardholder
-                               total_amount    taxes
-                               avs_response    cvv2_response
+                               total_amount    tax_amounts
+                               avs_code        cvv2_response
                        ));
        
                        ));
        
-       # Just in case someone tries to call taxes() *before* submit()
-       $self->taxes( {} );
+       # Just in case someone tries to call tax_amounts() *before* submit()
+       $self->tax_amounts( {} );
 }
 
 }
 
-# OnlinePayment's get_fields now filters out undefs in 3.x. :(
-#
-sub get_fields {
-       my ($self, @fields) = @_;
-
-       my %content = $self->content;
+# Backwards-compatible support for renamed fields
+sub avs_response { shift()->avs_code(@_) }
+sub sales_number { shift()->order_number(@_) }
 
 
-       my %new = map +($_ => $content{$_}), @fields;
 
 
-       return %new;
-}
-
-# Combine get_fields and remap_fields for convenience
+# Combine get_fields and remap_fields for convenience.  Unlike OnlinePayment's
+# remap_fields, this doesn't modify content(), and can therefore be called
+# more than once.  Also, unlike OnlinePayment's get_fields in 3.x, this doesn't
+# exclude undefs.
 #
 sub get_remap_fields {
        my ($self, %map) = @_;
 
 #
 sub get_remap_fields {
        my ($self, %map) = @_;
 
-       $self->remap_fields(reverse %map);
-       my %data = $self->get_fields(keys %map);
+       my %content = $self->content();
+       my %data;
+
+       while (my ($to, $from) = each %map) {
+               $data{$to} = $content{$from};
+       }
 
        return %data;
 }
 
        return %data;
 }
@@ -108,9 +108,16 @@ sub prod_string {
 
        my @flags = ($currency);
 
 
        my @flags = ($currency);
 
-       foreach (split ' ' => uc($data{taxes} || '')) {
-               croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
-               push @flags, $_;
+       my @taxes;
+       if (ref $data{taxes}) {
+               @taxes = @{ $data{taxes} };
+       } elsif ($data{taxes}) {
+               @taxes = split ' ' => $data{taxes};
+       }
+
+       foreach (@taxes) {
+               croak "Unknown tax code $_" unless /^(GST|PST|HST)$/i;
+               push @flags, uc $_;
        }
 
        if ($self->test_transaction) {
        }
 
        if ($self->test_transaction) {
@@ -133,11 +140,18 @@ sub to_xml {
 
        my %content = $self->content;
 
 
        my %content = $self->content;
 
-       $self->required_fields(qw(action card_number exp_date));
+       # Backwards-compatible support for exp_date
+       if (exists $content{exp_date} && ! exists $content{expiration}) {
+               $content{expiration} = delete $content{exp_date};
+               $self->content(%content);
+       }
+
+       $self->required_fields(qw(action card_number expiration));
 
 
-       croak 'Unsupported transaction type'
-               if $content{type} && $content{type} !~
-                       /^(Visa|MasterCard|American Express|Discover)$/i;
+       croak "Unsupported transaction type: $content{type}"
+               if $content{type} &&
+                       ! grep lc($content{type}) eq lc($_),
+                               values %{+CARD_TYPES};
        
        croak 'Unsupported action'
                unless $content{action} =~ /^Normal Authori[zs]ation$/i;
        
        croak 'Unsupported action'
                unless $content{action} =~ /^Normal Authori[zs]ation$/i;
@@ -146,8 +160,6 @@ sub to_xml {
        croak "Unknown currency code ", $content{currency}
                unless $content{currency} =~ /^(CAD|USD)$/;
        
        croak "Unknown currency code ", $content{currency}
                unless $content{currency} =~ /^(CAD|USD)$/;
        
-       $content{taxes} = uc($content{taxes} || '');
-
        my %data = $self->get_remap_fields(qw(
                        xxxCard_Number          card_number
 
        my %data = $self->get_remap_fields(qw(
                        xxxCard_Number          card_number
 
@@ -177,7 +189,7 @@ sub to_xml {
        $data{xxxCard_Number} =~ tr/- //d;
        $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
 
        $data{xxxCard_Number} =~ tr/- //d;
        $data{xxxCard_Number} =~ s/^[^3-6]/4/ if $self->test_transaction;
 
-       my ($y, $m) = $self->parse_expdate($content{exp_date});
+       my ($y, $m) = $self->parse_expdate($content{expiration});
        $data{xxxCCYear} = sprintf '%.4u' => $y;
        $data{xxxCCMonth} = sprintf '%.2u' => $m;
 
        $data{xxxCCYear} = sprintf '%.4u' => $y;
        $data{xxxCCMonth} = sprintf '%.2u' => $m;
 
@@ -205,13 +217,14 @@ sub to_xml {
                                );
        }
 
                                );
        }
 
-       xml_out(\%data,
+       # The encode() makes sure to a) strip off non-Latin-1 characters, and
+       # b) turn off the utf8 flag, which confuses XML::Simple
+       encode('ISO-8859-1', xml_out(\%data,
                NoAttr          => 1,
                NoAttr          => 1,
-               NumericEscape   => 2,
                RootName        => 'TranxRequest',
                SuppressEmpty   => undef,
                XMLDecl         => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
                RootName        => 'TranxRequest',
                SuppressEmpty   => undef,
                XMLDecl         => '<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>',
-       );
+       ));
 }
 
 # Map the various fields from the response, and put their values into our
 }
 
 # Map the various fields from the response, and put their values into our
@@ -226,10 +239,10 @@ sub infuse {
        }
 }
 
        }
 }
 
-sub extract_taxes {
+sub extract_tax_amounts {
        my ($self, $response) = @_;
 
        my ($self, $response) = @_;
 
-       my %taxes;
+       my %tax_amounts;
 
        my $products = $response->{Products};
        return unless $products;
 
        my $products = $response->{Products};
        return unless $products;
@@ -240,11 +253,11 @@ sub extract_taxes {
                        grep($_ eq '{TAX}', @$flags) &&
                        grep($_ eq '{CALCULATED}', @$flags))
                {
                        grep($_ eq '{TAX}', @$flags) &&
                        grep($_ eq '{CALCULATED}', @$flags))
                {
-                       $taxes{ $node->{code} } = $node->{subtotal};
+                       $tax_amounts{ $node->{code} } = $node->{subtotal};
                }
        }
 
                }
        }
 
-       return %taxes;
+       return %tax_amounts;
 }
 
 # Parse the server's response and set various fields
 }
 
 # Parse the server's response and set various fields
@@ -267,11 +280,11 @@ sub parse_response {
                        result_code     => 'Page',
                        error_message   => 'Verbiage',
                        authorization   => 'ApprovalCode',
                        result_code     => 'Page',
                        error_message   => 'Verbiage',
                        authorization   => 'ApprovalCode',
-                       avs_response    => 'AVSResponseCode',
+                       avs_code        => 'AVSResponseCode',
                        cvv2_response   => 'CVV2ResponseCode',
 
                        receipt_number  => 'ReceiptNumber',
                        cvv2_response   => 'CVV2ResponseCode',
 
                        receipt_number  => 'ReceiptNumber',
-                       sales_number    => 'SalesOrderNumber',
+                       order_number    => 'SalesOrderNumber',
                        uuid            => 'GUID',
                        guid            => 'GUID',
 
                        uuid            => 'GUID',
                        guid            => 'GUID',
 
@@ -291,7 +304,7 @@ sub parse_response {
        
        $self->card_type(CARD_TYPES->{$self->card_type});
        
        
        $self->card_type(CARD_TYPES->{$self->card_type});
        
-       $self->taxes( { $self->extract_taxes($response) } );
+       $self->tax_amounts( { $self->extract_tax_amounts($response) } );
 
        return $self;
 }
 
        return $self;
 }
@@ -345,7 +358,7 @@ Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::O
 
        type            => 'Visa',                      # Optional
        card_number     => '4111 1111 1111 1111',
 
        type            => 'Visa',                      # Optional
        card_number     => '4111 1111 1111 1111',
-       exp_date        => '2004-07',
+       expiration      => '2004-07',
        cvv2            => '000',                       # Optional
 
        name            => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
        cvv2            => '000',                       # Optional
 
        name            => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
@@ -419,6 +432,8 @@ Transaction type, being one of the following:
 
 =item - Discover
 
 
 =item - Discover
 
+=item - JCB
+
 =back
 
 (This is actually ignored for the moment, and can be left blank or undefined.)
 =back
 
 (This is actually ignored for the moment, and can be left blank or undefined.)
@@ -427,7 +442,7 @@ Transaction type, being one of the following:
 
 Credit card number.  Spaces and dashes are automatically removed.
 
 
 Credit card number.  Spaces and dashes are automatically removed.
 
-=item exp_date (required)
+=item expiration (required)
 
 Credit card expiration date.  Since C<Business::OnlinePayment> does not specify
 any syntax, this module is rather lax regarding what it will accept.  The
 
 Credit card expiration date.  Since C<Business::OnlinePayment> does not specify
 any syntax, this module is rather lax regarding what it will accept.  The
@@ -465,10 +480,12 @@ C<CAD> (default) or C<USD>.
 
 =item taxes
 
 
 =item taxes
 
-Taxes to be added automatically to B<amount> by InternetSecure.
+Taxes to be added automatically to B<amount> by InternetSecure.  Available
+taxes are C<GST>, C<PST> and C<HST>.
 
 
-Available taxes are C<GST>, C<PST> and C<HST>.  Multiple taxes can specified
-by concatenating them with spaces, such as C<GST HST>.
+This argument can either be a single string of taxes concatenated with spaces
+(such as C<GST PST>), or a reference to an array of taxes (such as C<[ "GST",
+"PST" ]>).
 
 =item name / company / address / city / state / zip / country / phone / email
 
 
 =item name / company / address / city / state / zip / country / phone / email
 
@@ -506,7 +523,7 @@ B<is_success>() instead.)
 Receipt number (a string, actually) of this transaction, unique to all
 InternetSecure transactions.
 
 Receipt number (a string, actually) of this transaction, unique to all
 InternetSecure transactions.
 
-=item sales_number()
+=item order_number()
 
 Sales order number of this transaction.  This is a number, unique to each
 merchant, which is incremented by 1 each time.
 
 Sales order number of this transaction.  This is a number, unique to each
 merchant, which is incremented by 1 each time.
@@ -524,7 +541,7 @@ B<guid>() is provided as an alias to this method.
 
 Authorization code for this transaction.
 
 
 Authorization code for this transaction.
 
-=item avs_response() / cvv2_response()
+=item avs_code() / cvv2_response()
 
 Results of the AVS and CVV2 checks.  See the InternetSecure documentation for
 the list of possible values.
 
 Results of the AVS and CVV2 checks.  See the InternetSecure documentation for
 the list of possible values.
@@ -537,10 +554,11 @@ Date and time of the transaction.  Format is C<YYYY/MM/DD hh:mm:ss>.
 
 Total amount billed for this order, including taxes.
 
 
 Total amount billed for this order, including taxes.
 
-=item taxes()
+=item tax_amounts()
 
 
-Returns a I<reference> to a hash that maps tax names (such as C<GST>) to the
-amount that was billed for each.
+Returns a I<reference> to a hash that maps taxes, which were listed under the
+B<taxes> argument to B<submit>(), to the amount that was calculated by
+InternetSecure.
 
 =item cardholder()
 
 
 =item cardholder()
 
@@ -562,6 +580,8 @@ following:
 
 =item - Discover
 
 
 =item - Discover
 
+=item - JCB
+
 =back
 
 
 =back
 
 
@@ -579,7 +599,7 @@ the following fields:
 
 =over 4
 
 
 =over 4
 
-=item amount
+=item amount (required)
 
 Unit price of this product.
 
 
 Unit price of this product.
 
@@ -608,15 +628,13 @@ be left undefined.
 
 =head2 Character encoding
 
 
 =head2 Character encoding
 
-Since communication to/from InternetSecure is encoded with UTF-8, all Unicode
-characters are theoretically available when submitting information via
-B<submit>().  (Further restrictions may be imposed by InternetSecure itself.)
+When using non-ASCII characters, all data provided to B<contents>() should
+have been decoded beforehand via the C<Encode> module, unless your data is in
+ISO-8859-1 and you haven't meddled with the C<encoding> pragma.  (Please
+don't.)
 
 
-When using non-ASCII characters, all data provided to B<submit>() should either
-be in the current native encoding (typically latin-1, unless it was modified
-via the C<encoding> pragma), or be decoded via the C<Encode> module.
-Conversely, all data returned after calling B<submit>() will be automatically
-decoded.
+InternetSecure currently does not handle characters outside of ISO-8859-1, so
+these will be replaced with C<?> before being transmitted.
 
 
 =head1 EXPORT
 
 
 =head1 EXPORT