File Coverage

blib/lib/Business/OnlinePayment/iTransact.pm
Criterion Covered Total %
statement 23 135 17.0
branch 0 32 0.0
condition 0 2 0.0
subroutine 8 12 66.6
pod 2 4 50.0
total 33 185 17.8


line stmt bran cond sub pod time code
1             ##############################################################################
2             # Business::OnlinePayment::iTransact
3             #
4             # Credit card transactions via iTransact's XML API Connection
5             #
6             # Refer to iTransact's documentation for more info
7             # https://itransact.com/support/toolkit/xml-connection/api/
8             #
9             # AUTHOR
10             #
11             # Bill Gerrard
12             #
13             # BUSINESS::ONLINEPAYMENT IMPLEMENTATION
14             #
15             # Bill Gerrard
16             #
17             # VERSION HISTORY
18             #
19             # + v1.01 03/24/2018 Added option to pass line item detail
20             # + v1.00 07/17/2017 Business::OnlinePayment implementation
21             #
22             # COPYRIGHT AND LICENSE
23             #
24             # Copyright (C) 2017-2018 Bill Gerrard
25             #
26             # This program is free software; you can redistribute it and/or modify
27             # it under the same terms as Perl itself, either Perl version 5.20.2 or,
28             # at your option, any later version of Perl 5 you may have available.
29             #
30             # Disclaimer of warranty: This program is provided by the copyright holder
31             # and contributors "As is" and without any express or implied warranties.
32             # The implied warranties of merchantability, fitness for a particular purpose,
33             # or non-infringement are disclaimed to the extent permitted by your local
34             # law. Unless required by law, no copyright holder or contributor will be
35             # liable for any direct, indirect, incidental, or consequential damages
36             # arising in any way out of the use of the package, even if advised of the
37             # possibility of such damage.
38             #
39             ################################################################################
40              
41             package Business::OnlinePayment::iTransact;
42              
43 1     1   56892 use 5.008004;
  1         3  
44 1     1   5 use strict;
  1         1  
  1         17  
45 1     1   4 use Carp;
  1         9  
  1         65  
46 1     1   438 use Business::OnlinePayment 3;
  1         2986  
  1         24  
47 1     1   409 use Business::OnlinePayment::HTTPS;
  1         20724  
  1         37  
48              
49             # iTransact specific prerequisites:
50 1     1   560 use XML::Writer;
  1         13333  
  1         33  
51 1     1   459 use XML::Hash::XS qw();
  1         966  
  1         22  
52 1     1   375 use Digest::HMAC_SHA1;
  1         3788  
  1         1003  
53              
54             our @ISA = qw(Business::OnlinePayment::HTTPS);
55             our $VERSION = '1.01';
56              
57             sub set_defaults {
58 0     0 0   my $self = shift;
59              
60 0           $self->server('secure.itransact.com');
61 0           $self->port('443');
62 0           $self->path('/cgi-bin/rc/xmltrans2.cgi');
63              
64 0           $self->build_subs(qw(
65             avs_category timestamp total card_type
66             error_category warning_message auth_amount
67             ));
68             }
69              
70             sub map_fields {
71 0     0 0   my ( $self ) = @_;
72 0           my %content = $self->content();
73              
74 0           my $action = lc( $content{'action'} );
75 0           my %map = (
76             'normal authorization' => 'AuthTransaction',
77             #'authorization only' => 'AuthTransaction-PreAuth',
78             #'avs only' => 'AuthTransaction-AVSOnly',
79             ## 'credit' => 'TranCredTransaction',
80             ## 'post authorization' => 'PostAuthTransaction',
81             ## 'void' => 'VoidTransaction',
82             );
83 0 0         $content{'action_type'} = $map{$action}
84             or croak 'Invalid action: '. $action;
85              
86 0           $self->transaction_type($content{'type'});
87              
88 0           $self->content( %content );
89             }
90              
91             sub submit {
92 0     0 1   my($self) = @_;
93              
94 0           $self->map_fields();
95              
96 0           my %c = $self->content();
97              
98             # check required fields: if no items array, amount/description required
99 0           my @required_fields = qw(type action
100             first_name last_name card_number expiration);
101 0 0         unless ( defined $c{'items'} ) {
102 0           push @required_fields, 'description';
103 0           push @required_fields, 'amount';
104             }
105 0           $self->required_fields( @required_fields );
106              
107 0 0         if ( lc( $c{'type'} ) =~ /^e?check$/i ) {
108 0           $self->is_success(0);
109 0           $self->error_message('Business::OnlinePayment::iTransact does not currently support checks');
110 0           return;
111             }
112              
113 0           my $action = $c{action_type};
114              
115             ### start XML generation
116              
117 0           my $writer = XML::Writer->new( OUTPUT => 'self' );
118              
119 0           $writer->startTag($action);
120              
121 0           $writer->startTag('TransactionControl');
122 0           $writer->dataElement('SendCustomerEmail', $self->email_customer);
123 0           $writer->dataElement('SendMerchantEmail', $self->email_merchant);
124 0 0         $writer->dataElement('TestMode', $self->{test_mode} ? 'TRUE' : 'FALSE');
125 0           $writer->endTag('TransactionControl');
126              
127 0           $writer->startTag('CustomerData');
128 0           $writer->dataElement('CustId', $c{customer_id});
129 0           $writer->dataElement('Email', $c{email});
130 0           $writer->startTag('BillingAddress');
131 0           $writer->dataElement('Address1', $c{address});
132 0           $writer->dataElement('FirstName', $c{first_name});
133 0           $writer->dataElement('LastName', $c{last_name});
134 0           $writer->dataElement('City', $c{city});
135 0           $writer->dataElement('State', $c{state});
136 0           $writer->dataElement('Zip', $c{zip});
137 0           $writer->dataElement('Country', $c{country});
138 0           $writer->dataElement('Phone', $c{phone});
139 0           $writer->endTag('BillingAddress');
140 0           $writer->endTag('CustomerData');
141              
142 0 0         if (lc $c{type} eq 'cc') {
143              
144             # expiration date
145             $c{'expiration'} =~ /^(\d+)\D+\d*(\d{2})$/
146 0 0         or croak "unparsable expiration ". $c{expiration};
147 0           my( $month, $year ) = ( $1, $2 );
148 0 0         $month = '0'. $month if $month =~ /^\d$/;
149 0 0         $year = '20' . $year if length($year) == 2;
150              
151 0           $writer->startTag('AccountInfo');
152 0           $writer->startTag('CardAccount');
153 0           $writer->dataElement('AccountNumber', $c{card_number});
154 0           $writer->dataElement('ExpirationMonth', $month);
155 0           $writer->dataElement('ExpirationYear', $year);
156 0           $writer->dataElement('CVVNumber', $c{cvv2});
157 0           $writer->endTag('CardAccount');
158 0           $writer->endTag('AccountInfo');
159              
160             }
161              
162 0 0         if ( defined($c{'items'}) ) {
163 0           $writer->startTag('OrderItems');
164 0           foreach my $itm ( @{$c{'items'}} ) {
  0            
165 0           $writer->startTag('Item');
166 0           foreach my $key ( qw( description qty cost ) ) {
167 0           $writer->dataElement(ucfirst($key), $itm->{$key});
168             }
169 0           $writer->endTag('Item');
170             }
171 0           $writer->endTag('OrderItems');
172             } else {
173 0           $writer->dataElement('Description', $c{description});
174 0           $writer->dataElement('Total', $c{amount});
175             }
176              
177 0           $writer->endTag($action); # root
178              
179 0           my $xml_payload = $writer->to_string;
180              
181             ### payload built, now generate payload signature
182            
183 0           my $hmac = Digest::HMAC_SHA1->new($self->password);
184 0           $hmac->add($xml_payload);
185 0           my $payload_signature = $hmac->b64digest . '='; # add closing '=' to signature
186 0           my $username = $self->login;
187              
188 0           my $xml_request = qq{
189            
190            
191             $username
192             $payload_signature
193            
194             $xml_payload
195            
196             };
197              
198             ### end XML generation
199              
200 0           my ( $page, $status_code, %headers ) =
201             $self->https_post( { 'Content-Type' => 'text/xml; charset=utf-8' } , $xml_request);
202              
203 0           my $response = {};
204 0 0         if ( $status_code =~ /^200/ ) {
205 0 0         if ( ! eval {
206 0           my $conv = XML::Hash::XS->new(utf8 => 0, encoding => 'utf-8');
207 0           my $hash = $conv->xml2hash($page);
208 0           $response = $hash->{TransactionResponse}->{TransactionResult};
209             } ) {
210 0           die "XML PARSING FAILURE: $@";
211             }
212             }
213             else {
214 0           $status_code =~ s/[\r\n\s]+$//; # remove newline so you can see the error in a linux console
215 0           die "CONNECTION FAILURE: $status_code";
216             }
217              
218 0           $self->server_response( $page );
219 0           $self->authorization( $response->{AuthCode} );
220 0           $self->order_number( $response->{XID} );
221 0           $self->avs_code( $response->{AVSResponse} );
222 0           $self->cvv2_response( $response->{CVV2Response} );
223             # iTransact specific return fields
224 0           $self->avs_category( $response->{AVSCategory} );
225 0           $self->timestamp( $response->{TimeStamp} );
226 0           $self->total( $response->{Total} );
227 0           $self->auth_amount( $response->{AuthAmount} );
228 0           $self->card_type( $response->{CardName} );
229              
230 0 0         if ($response->{Status} eq 'ok') { #success
231 0           $self->is_success(1);
232             } else {
233 0           $self->is_success(0);
234 0           $self->error_message( $response->{ErrorMessage} );
235 0           $self->error_category( $response->{ErrorCategory} );
236             }
237              
238             }
239              
240             sub test_transaction {
241 0     0 1   my $self = shift;
242 0           my $testMode = shift;
243 0 0 0       if (! defined $testMode) { $testMode = $self->{test_mode} || 0; }
  0            
244              
245 0 0         $testMode = 1 if uc $testMode eq 'TRUE';
246 0 0         $testMode = 0 if uc $testMode eq 'FALSE';
247              
248 0 0         if ($testMode) {
249 0           $self->{test_mode} = 1;
250             } else {
251 0           $self->{test_mode} = 0;
252             }
253              
254 0           return $self->{test_mode};
255             }
256              
257             1;
258              
259             __END__