--- /dev/null
+package Business::OnlinePayment::InternetSecure;
+
+use 5.008;
+use strict;
+use warnings;
+
+use Carp;
+use Encode;
+use Net::SSLeay qw(make_form post_https);
+use XML::Simple qw(xml_in xml_out);
+
+use base qw(Business::OnlinePayment Exporter);
+
+
+our $VERSION = '0.01';
+
+
+use constant CARD_TYPES => {
+ VI => 'Visa',
+ MC => 'MasterCard',
+ AX => 'American Express',
+ NN => 'Discover',
+ };
+
+
+# Convenience functions to avoid undefs and escape products strings
+sub _def($) { defined $_[0] ? $_[0] : '' }
+sub _esc($) { local $_ = shift; tr/|:/ /s; tr/"`/'/s; return $_ }
+
+
+sub set_defaults {
+ my ($self) = @_;
+
+ $self->server('secure.internetsecure.com');
+ $self->port(443);
+ $self->path('/process.cgi');
+
+ $self->build_subs(qw(
+ receipt_number sales_order_number
+ cardholder card_type
+ total_amount
+ avs_response cvv2_response
+ ));
+}
+
+# OnlinePayment's remap_fields is buggy, so we simply rewrite it
+#
+sub remap_fields {
+ my ($self, %map) = @_;
+
+ my %content = $self->content();
+ foreach (keys %map) {
+ $content{$map{$_}} = delete $content{$_};
+ }
+ $self->content(%content);
+}
+
+# Combine get_fields and remap_fields for convenience
+#
+sub get_remap_fields {
+ my ($self, %map) = @_;
+
+ $self->remap_fields(reverse %map);
+ my %data = $self->get_fields(keys %map);
+
+ foreach (values %data) {
+ $_ = '' unless defined;
+ }
+
+ return %data;
+}
+
+# Since there's no standard format for expiration dates, we try to do our best
+#
+sub parse_expdate {
+ my ($self, $str) = @_;
+
+ local $_ = $str;
+
+ my ($y, $m);
+
+ if (/^(\d{4})\W(\d{1,2})$/ || # 2004.07 or 2004-7
+ /^(\d\d)\W(\d)$/ || # 04/7
+ /^(\d\d)[.-](\d\d)$/) { # 04-07
+ ($y, $m) = ($1, $2);
+ } elsif (/^(\d{1,2})\W(\d{4})$/ || # 07-2004 or 7/2004
+ /^(\d)\W(\d\d)$/ || # 7/04
+ /^(\d\d)\/(\d\d)$/) { # 07/04
+ ($y, $m) = ($2, $1);
+ } else {
+ croak "Unable to parse expiration date: $str";
+ }
+
+ $y += 2000 if $y < 2000; # Aren't we glad Y2K is behind us?
+
+ return ($y, $m);
+}
+
+# Convert a single product into a product string
+#
+sub prod_string {
+ my ($self, $currency, $taxes, %data) = @_;
+
+ croak "Missing amount in product" unless defined $data{amount};
+
+ my @flags = ($currency);
+
+ $taxes = uc $data{taxes} if defined $data{taxes};
+ foreach (split ' ' => $taxes) {
+ croak "Unknown tax code $_" unless /^(GST|PST|HST)$/;
+ push @flags, $_;
+ }
+
+ if ($self->test_transaction) {
+ push @flags, $self->test_transaction < 0 ? 'TESTD' : 'TEST';
+ }
+
+ return join '::' =>
+ sprintf('%.2f' => $data{amount}),
+ $data{quantity} || 1,
+ _esc _def $data{sku},
+ _esc _def $data{description},
+ join('' => map "{$_}" => @flags),
+ ;
+}
+
+# Generate the XML document for this transaction
+#
+sub to_xml {
+ my ($self) = @_;
+
+ my %content = $self->content;
+
+ $self->required_fields(qw(action card_number exp_date));
+
+ croak 'Unsupported transaction type'
+ if $content{type} && $content{type} !~
+ /^(Visa|MasterCard|American Express|Discover)$/i;
+
+ croak 'Unsupported action'
+ unless $content{action} =~ /^Normal Authori[zs]ation$/i;
+
+ $content{currency} ||= 'CAD';
+ $content{currency} = uc $content{currency};
+ croak "Unknown currency code ", $content{currency}
+ unless $content{currency} =~ /^(CAD|USD)$/;
+
+ $content{taxes} ||= '';
+ $content{taxes} = uc $content{taxes};
+
+ my %data = $self->get_remap_fields(qw(
+ xxxCardNumber card_number
+
+ xxxName name
+ xxxCompany company
+ xxxAddress address
+ xxxCity city
+ xxxProvince state
+ xxxPostal zip
+ xxxCountry country
+ xxxPhone phone
+ xxxEmail email
+
+ xxxShippingName ship_name
+ xxxShippingCompany ship_company
+ xxxShippingAddress ship_address
+ xxxShippingCity ship_city
+ xxxShippingProvince ship_state
+ xxxShippingPostal ship_zip
+ xxxShippingCountry ship_country
+ xxxShippingPhone ship_phone
+ xxxShippingEmail ship_email
+ ));
+
+ $data{MerchantNumber} = $self->merchant_id;
+
+ $data{xxxCardNumber} =~ tr/ //d;
+
+ my ($y, $m) = $self->parse_expdate($content{exp_date});
+ $data{xxxCCYear} = sprintf '%.4u' => $y;
+ $data{xxxCCMonth} = sprintf '%.2u' => $m;
+
+ if (defined $content{cvv2} && $content{cvv2} ne '') {
+ $data{CVV2} = 1;
+ $data{CVV2Indicator} = $content{cvv2};
+ } else {
+ $data{CVV2} = 0;
+ $data{CVV2Indicator} = '';
+ }
+
+ if (ref $content{description}) {
+ $data{Products} = join '|' => map $self->prod_string(
+ $content{currency},
+ $content{taxes},
+ %$_),
+ @{ $content{description} };
+ } else {
+ $self->required_fields(qw(amount));
+ $data{Products} = $self->prod_string(
+ $content{currency},
+ $content{taxes},
+ amount => $content{amount},
+ description => $content{description},
+ );
+ }
+
+ xml_out(\%data,
+ NoAttr => 1,
+ RootName => 'TranxRequest',
+ XMLDecl => '<?xml version="1.0" encoding="utf-8" standalone="yes"?>',
+ );
+}
+
+# Map the various fields from the response, and put their values into our
+# object for retrieval.
+#
+sub infuse {
+ my ($self, $data, %map) = @_;
+
+ while (my ($k, $v) = each %map) {
+ no strict 'refs';
+ $self->$v($data->{$k});
+ }
+}
+
+# Parse the server's response and set various fields
+#
+sub parse_response {
+ my ($self, $response) = @_;
+
+ $self->server_response($response);
+
+ # (It's not quite clear whether there should be some decoding, or if
+ # the result is already utf8.)
+
+ $response = xml_in($response,
+ ForceArray => [qw(product flag)],
+ GroupTags => { qw(Products product flags flag) },
+ KeyAttr => [],
+ SuppressEmpty => undef,
+ );
+
+ my $code = $self->result_code($response->{Page});
+ $self->is_success($code eq '2000' || $code eq '90000' || $code eq '900P1');
+
+ $self->infuse($response, qw(
+ ReceiptNumber receipt_number
+ SalesOrderNumber sales_order_number
+ xxxName cardholder
+ CardType card_type
+ Page result_code
+ ApprovalCode authorization
+ Verbiage error_message
+ TotalAmount total_amount
+ AVSResponseCode avs_response
+ CVV2ResponseCode cvv2_response
+ ));
+
+ $self->card_type(CARD_TYPES->{$self->card_type});
+
+ $self->{products_raw} = $response->{Products};
+
+ return $self;
+}
+
+sub submit {
+ my ($self) = @_;
+
+ croak "Missing required argument 'merchant_id'"
+ unless defined $self->{merchant_id};
+
+ my ($page, $response, %headers) =
+ post_https(
+ $self->server,
+ $self->port,
+ $self->path,
+ undef,
+ make_form(
+ xxxRequestMode => 'X',
+ xxxRequestData => Encode::encode_utf8(
+ $self->to_xml
+ ),
+ )
+ );
+
+ croak 'Error connecting to server' unless $page;
+ croak 'Server responded, but not in XML' unless $page =~ /^<\?xml/;
+
+ $self->parse_response($page);
+}
+
+
+1;
+
+__END__
+
+
+=head1 NAME
+
+Business::OnlinePayment::InternetSecure - InternetSecure backend for Business::OnlinePayment
+
+=head1 SYNOPSIS
+
+ use Business::OnlinePayment;
+
+ $txn = new Business::OnlinePayment 'InternetSecure',
+ merchant_id => '0000';
+
+ $txn->content(
+ action => 'Normal Authorization',
+
+ type => 'Visa',
+ card_number => '0000000000000000',
+ exp_date => '2004-07',
+ cvv2 => '000', # Optional
+
+ name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
+ company => '',
+ address => '123 Street',
+ city => 'Metropolis',
+ state => 'ZZ',
+ zip => 'A1A 1A1',
+ country => 'CA',
+ phone => '(555) 555-1212',
+ email => 'fbriere@fbriere.net',
+
+ description => 'Online purchase',
+ amount => 49.95,
+ currency => 'CAD',
+ taxes => 'GST PST',
+ );
+
+ $txn->submit;
+
+ if ($txn->is_success) {
+ print "Card processed successfully: " . $tx->authorization . "\n";
+ } else {
+ print "Card was rejected: " . $tx->error_message . "\n";
+ }
+
+=head1 DESCRIPTION
+
+Business::OnlinePayment::InternetSecure is an implementation of
+L<Business::OnlinePayment> that allows for processing online credit card
+payments through Internet Secure.
+
+See L<Business::OnlinePayment> for more information about the generic
+Business::OnlinePayment interface.
+
+=head1 CREATOR
+
+Object creation is done via L<Business::OnlinePayment>; see its manpage for
+details. The I<merchant_id> processor option is required, and corresponds
+to the merchant ID assigned to you by Internet Secure.
+
+=head1 METHODS
+
+(See L<Business::OnlinePayment> for more methods.)
+
+=head2 Before order submission
+
+=over 4
+
+=item content( CONTENT )
+
+Sets up the data prior to a transaction (overwriting any previous data by the
+same occasion). CONTENT is an associative array (hash), containing some of
+the following fields:
+
+=over 4
+
+=item action (required)
+
+What to do with the transaction. Only C<Normal Authorization> is supported
+for the moment.
+
+=item type
+
+Transaction type, being one of the following:
+
+=over 4
+
+=item - Visa
+
+=item - MasterCard
+
+=item - American Express
+
+=item - Discover
+
+=back
+
+(This is actually ignored for the moment, and can be left blank or undefined.)
+
+=item card_number (required)
+
+Credit card number. Any spaces will be removed.
+
+=item exp_date (required)
+
+Credit card expiration date. Since L<Business::OnlinePayment> does not
+specify any syntax, this module is rather lax in what it will accept. It is
+recommended to use either I<YYYY-MM> or I<MM/YYYY>, to avoid any nasty
+surprises.
+
+=item cvv2
+
+Three- or four-digit verification code printed on the card. This can be left
+blank or undefined, in which case no check will be performed. Whether or not a
+transaction will be declined in case of a mismatch depends on the merchant.
+
+This number may be called Card Verification Value (CVV2), Card Validation
+Code (CVC2) or Card Identification number (CID).
+
+=item description
+
+A short description of the purchase. See L<"Products list syntax"> for
+an alternate syntax that allows a list of products to be specified.
+
+=item amount
+
+Total amount to be billed, excluding taxes if they are to be added separately.
+This field is required if B<description> is a string, and should be left
+undefined if B<description> contains a list of products, as outlined in
+L<"Products list syntax">.
+
+=item currency
+
+Currency of all amounts for this order. This can currently be either
+C<CAN> (default) or C<USD>.
+
+=item taxes
+
+Taxes to be added automatically. These should not be included in B<amount>;
+they will be added by Internet Secure later on.
+
+Available taxes are C<GST>, C<PST> and C<HST>; multiple taxes must be
+separated by spaces.
+
+=item name / company / address / city / state / zip / country / phone / email
+
+Facultative customer information. B<state> should be either a postal
+abbreviation or a two-letter code taken from ISO 3166-2, and B<country> should
+be a two-letter code taken from in ISO 3166-1.
+
+=back
+
+=back
+
+=head2 After order submission
+
+=over 4
+
+=item receipt_number() / sales_order_number()
+
+Receipt number and sales order number of submitted order.
+
+=item total_amount()
+
+Total amount billed for this order, including taxes.
+
+=item cardholder()
+
+Cardholder's name. This is currently a mere copy of the B<name> field passed
+to B<submit()>.
+
+=item card_type()
+
+Type of the credit card used for the submitted order, being one of the
+following:
+
+=over 4
+
+=item - Visa
+
+=item - MasterCard
+
+=item - American Express
+
+=item - Discover
+
+=back
+
+=item avs_response() / cvv2_response()
+
+Results of the AVS and CVV2 checks. See the Internet Secure documentation for
+the list of possible values.
+
+=back
+
+
+=head1 NOTES
+
+=head2 Products list syntax
+
+Optionally, the B<description> field of B<content()> can contain a reference
+to an array of products, instead of a simple string. Each element of this
+array represents a different product, and must be a reference to a hash with
+the following fields:
+
+=over 4
+
+=item amount
+
+Unit price of this product.
+
+=item quantity
+
+Ordered quantity of this product. This can be a decimal value.
+
+=item sku
+
+Internal code for this product.
+
+=item description
+
+Description of this product
+
+=item taxes
+
+Taxes that should be automatically added to this product. If specified, this
+overrides the B<taxes> field passed to B<content()>.
+
+=back
+
+When using a products list, the B<amount> field passed to B<content()> should
+be left undefined.
+
+
+=head2 Character encodings
+
+...
+
+
+=head2 products_raw
+
+...
+
+
+=head1 EXPORT
+
+None by default.
+
+
+=head1 SEE ALSO
+
+L<Business::OnlinePayment>
+
+=head1 AUTHOR
+
+Frederic Briere, E<lt>fbriere@fbriere.netE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2004 by Frederic Briere
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.8.4 or,
+at your option, any later version of Perl 5 you may have available.
+
+
+=cut
--- /dev/null
+use Test::More tests => 4 + 2;
+
+BEGIN { use_ok('Business::OnlinePayment') };
+BEGIN { use_ok('Business::OnlinePayment::InternetSecure') };
+BEGIN { use_ok('XML::Simple', qw(xml_in)) };
+BEGIN { use_ok('Encode') };
+
+use charnames ':full'; # Why doesn't this work with use_ok?
+
+use constant TRANSACTIONS => (
+ {
+ _test => 0,
+
+ action => 'Normal Authorization',
+
+ type => 'Visa',
+ card_number => '0000000000000000',
+ exp_date => '2004-07',
+ cvv2 => '000',
+
+ name => "Fr\N{LATIN SMALL LETTER E WITH ACUTE}d\N{LATIN SMALL LETTER E WITH ACUTE}ric Bri\N{LATIN SMALL LETTER E WITH GRAVE}re",
+ company => '',
+ address => '123 Street',
+ city => 'Metropolis',
+ state => 'ZZ',
+ zip => 'A1A 1A1',
+ country => 'CA',
+ phone => '(555) 555-1212',
+ email => 'fbriere@fbriere.net',
+
+ amount => undef,
+ currency => 'CAD',
+ taxes => 'GST PST',
+
+ description => [
+ {
+ amount => 9.99,
+ quantity => 5,
+ sku => 'a:001',
+ description => 'Some product',
+ },
+ {
+ amount => 5.65,
+ description => 'Shipping',
+ },
+ {
+ amount => 10.00,
+ description => 'Some HST example',
+ taxes => 'HST',
+ },
+ ],
+ },
+ {
+ _test => 1,
+
+ action => 'Normal Authorization',
+
+ type => 'Visa',
+ card_number => '0000000000000000',
+ exp_date => '7/2004',
+
+ name => "Fr\x{e9}d\x{e9}ric Bri\x{e8}re",
+
+ amount => 12.95,
+ currency => 'USD',
+ taxes => '',
+ description => "Box o' goodies",
+ },
+);
+
+
+my $txn = new Business::OnlinePayment 'InternetSecure', merchant_id => '0000';
+
+$/ = '';
+foreach (TRANSACTIONS) {
+ $txn->test_transaction(delete $_->{_test});
+ $txn->content(%$_);
+ is_deeply(
+ xml_in(Encode::encode('utf8', $txn->to_xml)),
+ xml_in(scalar <DATA>)
+ );
+}
+
+
+__DATA__
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<TranxRequest>
+ <MerchantNumber>0000</MerchantNumber>
+ <xxxCardNumber>0000000000000000</xxxCardNumber>
+ <xxxCCMonth>07</xxxCCMonth>
+ <xxxCCYear>2004</xxxCCYear>
+ <CVV2>1</CVV2>
+ <CVV2Indicator>000</CVV2Indicator>
+ <Products>9.99::5::a 001::Some product::{CAD}{GST}{PST}|5.65::1::::Shipping::{CAD}{GST}{PST}|10.00::1::::Some HST example::{CAD}{HST}</Products>
+ <xxxName>Frédéric Brière</xxxName>
+ <xxxCompany></xxxCompany>
+ <xxxAddress>123 Street</xxxAddress>
+ <xxxCity>Metropolis</xxxCity>
+ <xxxProvince>ZZ</xxxProvince>
+ <xxxPostal>A1A 1A1</xxxPostal>
+ <xxxCountry>CA</xxxCountry>
+ <xxxPhone>(555) 555-1212</xxxPhone>
+ <xxxEmail>fbriere@fbriere.net</xxxEmail>
+ <xxxShippingName></xxxShippingName>
+ <xxxShippingCompany></xxxShippingCompany>
+ <xxxShippingAddress></xxxShippingAddress>
+ <xxxShippingCity></xxxShippingCity>
+ <xxxShippingProvince></xxxShippingProvince>
+ <xxxShippingPostal></xxxShippingPostal>
+ <xxxShippingCountry></xxxShippingCountry>
+ <xxxShippingPhone></xxxShippingPhone>
+ <xxxShippingEmail></xxxShippingEmail>
+</TranxRequest>
+
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<TranxRequest>
+ <MerchantNumber>0000</MerchantNumber>
+ <xxxCardNumber>0000000000000000</xxxCardNumber>
+ <xxxCCMonth>07</xxxCCMonth>
+ <xxxCCYear>2004</xxxCCYear>
+ <CVV2>0</CVV2>
+ <CVV2Indicator></CVV2Indicator>
+ <Products>12.95::1::::Box o' goodies::{USD}{TEST}</Products>
+ <xxxName>Frédéric Brière</xxxName>
+ <xxxCompany></xxxCompany>
+ <xxxAddress></xxxAddress>
+ <xxxCity></xxxCity>
+ <xxxProvince></xxxProvince>
+ <xxxPostal></xxxPostal>
+ <xxxCountry></xxxCountry>
+ <xxxPhone></xxxPhone>
+ <xxxEmail></xxxEmail>
+ <xxxShippingName></xxxShippingName>
+ <xxxShippingCompany></xxxShippingCompany>
+ <xxxShippingAddress></xxxShippingAddress>
+ <xxxShippingCity></xxxShippingCity>
+ <xxxShippingProvince></xxxShippingProvince>
+ <xxxShippingPostal></xxxShippingPostal>
+ <xxxShippingCountry></xxxShippingCountry>
+ <xxxShippingPhone></xxxShippingPhone>
+ <xxxShippingEmail></xxxShippingEmail>
+</TranxRequest>
+
--- /dev/null
+use Test::More tests => 1 + 2 * 5;
+
+BEGIN { use_ok('Business::OnlinePayment') };
+
+
+use constant FIELDS => (qw( result_code authorization total_amount ));
+
+use constant RESULTS => (
+ [ 1, '2000', 'T00000', 3.88 ],
+ [ 0, '98e05', undef, 3.88 ],
+ );
+
+
+my $txn = new Business::OnlinePayment 'InternetSecure', merchant_id => '0000';
+
+$/ = '';
+foreach (RESULTS) {
+ my @results = @$_;
+
+ my $xml = <DATA>;
+ $txn->parse_response($xml);
+
+ is($txn->server_response, $xml, 'server_response');
+
+ if (shift @results) {
+ ok($txn->is_success, 'expecting success');
+ } else {
+ ok(!$txn->is_success, 'expecting failure');
+ }
+
+ foreach (FIELDS) {
+ no strict 'refs';
+ is($txn->$_, shift @results, $_);
+ }
+}
+
+
+__DATA__
+<?xml version="1.0" encoding="UTF-8"?>
+<TranxResponse>
+ <MerchantNumber>4994</MerchantNumber>
+ <ReceiptNumber>1096019995.5012</ReceiptNumber>
+ <SalesOrderNumber>0</SalesOrderNumber>
+ <xxxName>John Smith</xxxName>
+ <Date>2003/12/17 09:59:58</Date>
+ <CardType>Test Card Number</CardType>
+ <Page>2000</Page>
+ <ApprovalCode>T00000</ApprovalCode>
+ <Verbiage>Test Approved</Verbiage>
+ <TotalAmount>3.88</TotalAmount>
+ <Products>
+ <product>
+ <code>001</code>
+ <description>Test Product 1</description>
+ <quantity>1</quantity>
+ <price>3.10</price>
+ <subtotal>3.10</subtotal>
+ <flags>
+ <flag>{USD}</flag>
+ <flag>{GST}</flag>
+ <flag>{TEST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>010</code>
+ <description>Test Product 2</description>
+ <quantity>1</quantity>
+ <price>0.20</price>
+ <subtotal>0.20</subtotal>
+ <flags>
+ <flag>{GST}</flag>
+ <flag>{TEST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>020</code>
+ <description>Test Product 3</description>
+ <quantity>1</quantity>
+ <price>0.33</price>
+ <subtotal>0.33</subtotal>
+ <flags>
+ <flag>{GST}</flag>
+ <flag>{TEST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>GST</code>
+ <description>Canadian GST Charged</description>
+ <quantity>1</quantity>
+ <price>0.25</price>
+ <subtotal>0.25</subtotal>
+ <flags>
+ <flag>{TAX}</flag>
+ <flag>{CALCULATED}</flag>
+ </flags>
+ </product>
+ </Products>
+ <DoubleColonProducts>3.10::1::001::Test Product 1::{USD}{GST}{TEST}|0.20::1::010::Test Product 2::{GST}{TEST}|0.33::1::020::Test Product 3::{GST}{TEST}|0.25::1::GST::Canadian GST Charged::{TAX}{CALCULATED}</DoubleColonProducts>
+ <AVSResponseCode />
+ <CVV2ResponseCode />
+</TranxResponse>
+
+<?xml version="1.0" encoding="UTF-8"?>
+<TranxResponse>
+ <MerchantNumber>4994</MerchantNumber>
+ <ReceiptNumber>1096021915.5853</ReceiptNumber>
+ <SalesOrderNumber>729</SalesOrderNumber>
+ <xxxName>John Smith</xxxName>
+ <Date>2003/12/17 10:31:58</Date>
+ <CardType>VI</CardType>
+ <Page>98e05</Page>
+ <ApprovalCode />
+ <Verbiage>Incorrect Card Number - Please Retry</Verbiage>
+ <TotalAmount>3.88</TotalAmount>
+ <Products>
+ <product>
+ <code>001</code>
+ <description>Test Product 1</description>
+ <quantity>1</quantity>
+ <price>3.10</price>
+ <subtotal>3.10</subtotal>
+ <flags>
+ <flag>{USD}</flag>
+ <flag>{GST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>010</code>
+ <description>Test Product 2</description>
+ <quantity>1</quantity>
+ <price>0.20</price>
+ <subtotal>0.20</subtotal>
+ <flags>
+ <flag>{GST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>020</code>
+ <description>Test Product 3</description>
+ <quantity>1</quantity>
+ <price>0.33</price>
+ <subtotal>0.33</subtotal>
+ <flags>
+ <flag>{GST}</flag>
+ </flags>
+ </product>
+ <product>
+ <code>GST</code>
+ <description>Canadian GST Charged</description>
+ <quantity>1</quantity>
+ <price>0.25</price>
+ <subtotal>0.25</subtotal>
+ <flags>
+ <flag>{TAX}</flag>
+ <flag>{CALCULATED}</flag>
+ </flags>
+ </product>
+ </Products>
+ <DoubleColonProducts>3.10::1::001::Test Product 1::{USD}{GST}|0.20::1::010::Test Product 2::{GST}|0.33::1::020::Test Product 3::{GST}|0.25::1::GST::Canadian GST Charged::{TAX}{CALCULATED}</DoubleColonProducts>
+ <AVSResponseCode />
+ <CVV2ResponseCode />
+</TranxResponse>
+