File Coverage

blib/lib/Business/OnlinePayment/Litle.pm
Criterion Covered Total %
statement 272 775 35.1
branch 84 334 25.1
condition 26 173 15.0
subroutine 34 54 62.9
pod 25 25 100.0
total 441 1361 32.4


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::Litle;
2              
3              
4 4     4   74060 use warnings;
  4         12  
  4         181  
5 4     4   29 use strict;
  4         12  
  4         117  
6              
7 4     4   362 use Business::OnlinePayment;
  4         2745  
  4         113  
8 4     4   1375 use Business::OnlinePayment::HTTPS;
  4         79123  
  4         157  
9 4     4   1731 use Business::OnlinePayment::Litle::ErrorCodes '%ERRORS';
  4         16  
  4         402  
10 4     4   25 use vars qw(@ISA $me $DEBUG);
  4         7  
  4         181  
11 4     4   1040 use MIME::Base64;
  4         1991  
  4         215  
12 4     4   1734 use HTTP::Tiny;
  4         140319  
  4         176  
13 4     4   1914 use XML::Writer;
  4         27690  
  4         118  
14 4     4   2415 use XML::Simple;
  4         32271  
  4         36  
15 4     4   387 use Tie::IxHash;
  4         10  
  4         118  
16 4     4   1658 use Business::CreditCard qw(cardtype);
  4         8640  
  4         274  
17 4     4   1278 use Data::Dumper;
  4         15910  
  4         950  
18 4     4   1302 use IO::String;
  4         9008  
  4         132  
19 4     4   37 use Carp qw(croak);
  4         32  
  4         1150  
20 4     4   1081 use Log::Scrubber qw(disable $SCRUBBER scrubber :Carp scrubber_add_scrubber);
  4         10678  
  4         22  
21              
22             @ISA = qw(Business::OnlinePayment::HTTPS);
23             $me = 'Business::OnlinePayment::Litle';
24             $DEBUG = 0;
25             our $VERSION = '0.958'; # VERSION
26              
27             # PODNAME: Business::OnlinePayment::Litle
28              
29             # ABSTRACT: Business::OnlinePayment::Litle - Vantiv (was Litle & Co.) Backend for Business::OnlinePayment
30              
31              
32             sub server_request {
33 11     11 1 62 my ( $self, $val, $tf ) = @_;
34 11 100       37 if ($val) {
35 5         39 $self->{server_request} = scrubber $val;
36 5 50       818 $self->server_request_dangerous($val,1) unless $tf;
37             }
38 11         20 return $self->{server_request};
39             }
40              
41              
42             sub server_request_dangerous {
43 5     5 1 21 my ( $self, $val, $tf ) = @_;
44 5 50       14 if ($val) {
45 5         16 $self->{server_request_dangerous} = $val;
46 5 50       24 $self->server_request($val,1) unless $tf;
47             }
48 5         17 return $self->{server_request_dangerous};
49             }
50              
51              
52             sub server_response {
53 11     11 1 35 my ( $self, $val, $tf ) = @_;
54 11 100       35 if ($val) {
55 5         27 $self->{server_response} = scrubber $val;
56 5 50       534 $self->server_response_dangerous($val,1) unless $tf;
57             }
58 11         28 return $self->{server_response};
59             }
60              
61              
62             sub server_response_dangerous {
63 5     5 1 22 my ( $self, $val, $tf ) = @_;
64 5 50       20 if ($val) {
65 5         16 $self->{server_response_dangerous} = $val;
66 5 50       32 $self->server_response($val,1) unless $tf;
67             }
68 5         12 return $self->{server_response_dangerous};
69             }
70              
71              
72              
73             sub _info {
74             return {
75 0     0   0 info_compat => '0.01',
76             gateway_name => 'Litle',
77             gateway_url => 'http://www.vantiv.com',
78             module_version => $VERSION,
79             supported_types => ['CC'],
80             supported_actions => {
81             CC => [
82             'Normal Authorization',
83             'Post Authorization',
84             'Authorization Only',
85             'Credit',
86             'Void',
87             'Auth Reversal',
88             ],
89             },
90             };
91             }
92              
93              
94             sub set_defaults {
95 8     8 1 6846 my $self = shift;
96 8         23 my %opts = @_;
97              
98 8         50 $self->build_subs(
99             qw( order_number md5 avs_code cvv2_response card_token
100             cavv_response api_version xmlns failure_status batch_api_version chargeback_api_version
101             is_prepaid prepaid_balance get_affluence chargeback_server chargeback_port chargeback_path
102             verify_SSL phoenixTxnId is_duplicate card_token card_token_response card_token_message
103             )
104             );
105              
106 8         2921 $self->test_transaction(0);
107              
108 8 50       22 if ( $opts{debug} ) {
109 0         0 $self->debug( $opts{debug} );
110 0         0 delete $opts{debug};
111             }
112              
113             ## load in the defaults
114 8         16 my %_defaults = ();
115 8         25 foreach my $key ( keys %opts ) {
116 8 50       32 $key =~ /^default_(\w*)$/ or next;
117 8         25 $_defaults{$1} = $opts{$key};
118 8         18 delete $opts{$key};
119             }
120              
121 8         20 $self->{_scrubber} = \&_default_scrubber;
122 8 100       19 if( defined $_defaults{'Scrubber'} ) {
123 2         4 my $code = $_defaults{'Scrubber'};
124 2 100       5 if( ref($code) ne 'CODE' ) {
125 1         32 warn('default_Scrubber is not a code ref');
126             }
127             else {
128 1         3 $self->{_scrubber} = $code;
129             }
130             }
131              
132 8 50       135 $self->api_version('11.0') unless $self->api_version;
133 8 50       318 $self->batch_api_version('11.0') unless $self->batch_api_version;
134 8 50       295 $self->chargeback_api_version('2.2') unless $self->chargeback_api_version;
135 8 50       327 $self->xmlns('http://www.litle.com/schema') unless $self->xmlns;
136             }
137              
138              
139             sub test_transaction {
140 15     15 1 12077 my $self = shift;
141 15         30 my $testMode = shift;
142 15 50 0     49 if (! defined $testMode) { $testMode = $self->{'test_transaction'} || 0; }
  0         0  
143              
144 15 100       80 if (lc($testMode) eq 'sandbox') {
    50          
    50          
    100          
145 6         14 $self->{'test_transaction'} = 'sandbox';
146 6         118 $self->verify_SSL(0);
147              
148 6         137 $self->server('www.testvantivcnp.com');
149 6         130 $self->port('443');
150 6         129 $self->path('/sandbox/communicator/online');
151              
152 6         141 $self->chargeback_server('services.vantivpostlive.com'); # no sandbox exists, so fallback to certify
153 6         164 $self->chargeback_port('443');
154 6         154 $self->chargeback_path('/services/communicator/chargebacks/webCommunicator');
155             } elsif (lc($testMode) eq 'localhost') {
156             # this allows the user to create a local web server to do generic testing with
157 0         0 $self->{'test_transaction'} = 'localhost';
158 0         0 $self->verify_SSL(0);
159              
160 0         0 $self->server('localhost');
161 0         0 $self->port('443');
162 0         0 $self->path('/sandbox/communicator/online');
163              
164 0         0 $self->chargeback_server('localhost');
165 0         0 $self->chargeback_port('443');
166 0         0 $self->chargeback_path('/services/communicator/chargebacks/webCommunicator');
167             } elsif (lc($testMode) eq 'prelive') {
168 0         0 $self->{'test_transaction'} = $testMode;
169 0         0 $self->verify_SSL(0);
170              
171 0         0 $self->server('payments.vantivprelive.com');
172 0         0 $self->port('443');
173 0         0 $self->path('/vap/communicator/online');
174              
175 0         0 $self->chargeback_server('services.vantivprelive.com');
176 0         0 $self->chargeback_port('443');
177 0         0 $self->chargeback_path('/services/communicator/chargebacks/webCommunicator');
178             } elsif ($testMode) {
179 1         8 $self->{'test_transaction'} = $testMode;
180 1         28 $self->verify_SSL(0);
181              
182 1         36 $self->server('payments.vantivpostlive.com');
183 1         35 $self->port('443');
184 1         34 $self->path('/vap/communicator/online');
185              
186 1         34 $self->chargeback_server('services.vantivpostlive.com');
187 1         35 $self->chargeback_port('443');
188 1         34 $self->chargeback_path('/services/communicator/chargebacks/webCommunicator');
189             } else {
190 8         31 $self->{'test_transaction'} = 0;
191 8         185 $self->verify_SSL(1);
192              
193 8         205 $self->server('payments.vantivcnp.com');
194 8         189 $self->port('443');
195 8         180 $self->path('/vap/communicator/online');
196              
197 8         176 $self->chargeback_server('services.vantivcnp.com');
198 8         187 $self->chargeback_port('443');
199 8         170 $self->chargeback_path('/services/communicator/chargebacks/webCommunicator');
200             }
201              
202 15         124 return $self->{'test_transaction'};
203             }
204              
205              
206             sub map_fields {
207 6     6 1 16 my ( $self, $content ) = @_;
208              
209 6         16 my $action = lc( $content->{'action'} );
210 6         46 my %actions = (
211             'normal authorization' => 'sale',
212             'authorization only' => 'authorization',
213             'post authorization' => 'capture',
214             'void' => 'void',
215             'credit' => 'credit',
216             'auth reversal' => 'authReversal',
217             'account update' => 'accountUpdate',
218             'tokenize' => 'registerTokenRequest',
219             'force capture' => 'force_capture',
220              
221             # AVS ONLY
222             # Capture Given
223             #
224             );
225 6   33     23 $content->{'TransactionType'} = $actions{$action} || $action;
226              
227 6         37 my $type_translate = {
228             'VISA card' => 'VI',
229             'MasterCard' => 'MC',
230             'Discover card' => 'DI',
231             'American Express card' => 'AX',
232             'Diner\'s Club/Carte Blanche' => 'DI',
233             'JCB' => 'DI',
234             'China Union Pay' => 'DI',
235             };
236              
237             $content->{'card_type'} =
238             $type_translate->{ cardtype( $content->{'card_number'} ) }
239 6 100 33     29 || $content->{'type'} if $content->{'card_number'};
240              
241 6 50 33     148 if ( $content->{recurring_billing}
242             && $content->{recurring_billing} eq 'YES' )
243             {
244 0         0 $content->{'orderSource'} = 'recurring';
245             }
246             else {
247 6         12 $content->{'orderSource'} = 'ecommerce';
248             }
249             $content->{'customerType'} =
250 6 50       18 $content->{'orderSource'} eq 'recurring'
251             ? 'Existing'
252             : 'New'; # new/Existing
253              
254 6         13 $content->{'deliverytype'} = 'SVC';
255              
256             # stuff it back into %content
257 6 50 33     38 if ( $content->{'products'} && ref( $content->{'products'} ) eq 'ARRAY' ) {
258 6         13 my $count = 1;
259 6         11 foreach ( @{ $content->{'products'} } ) {
  6         16  
260 12         30 $_->{'itemSequenceNumber'} = $count++;
261             }
262             }
263              
264 6 50 0     22 if( $content->{'velocity_check'} && (
      33        
265             $content->{'velocity_check'} != 0
266             && $content->{'velocity_check'} !~ m/false/i ) ) {
267 0         0 $content->{'velocity_check'} = 'true';
268             } else {
269 6         12 $content->{'velocity_check'} = 'false';
270             }
271              
272 6 50 0     18 if( $content->{'partial_auth'} && (
      33        
273             $content->{'partial_auth'} != 0
274             && $content->{'partial_auth'} !~ m/false/i ) ) {
275 0         0 $content->{'partial_auth'} = 'true';
276             } else {
277 6         12 $content->{'partial_auth'} = 'false';
278             }
279              
280 6         11 $self->content( %{$content} );
  6         48  
281 6         373 return $content;
282             }
283              
284              
285             sub format_misc_field {
286 288     288 1 420 my ($self, $content, $trunc) = @_;
287              
288 288 100       534 if( defined $content->{ $trunc->[0] } ) {
    50          
289 232         532 utf8::upgrade($content->{ $trunc->[0] });
290 232         429 my $len = length( $content->{ $trunc->[0] } );
291 232 50 100     834 if ( $trunc->[3] && $trunc->[2] && $len != 0 && $len < $trunc->[2] ) {
    50 100        
      66        
      66        
      33        
292             # Zero is a valid length (mostly for cvv2 value)
293 0         0 croak "$trunc->[0] has too few characters";
294             }
295             elsif ( $trunc->[3] && $trunc->[1] && $len > $trunc->[1] ) {
296 0         0 croak "$trunc->[0] has too many characters";
297             }
298 232         565 $content->{ $trunc->[0] } = substr($content->{ $trunc->[0] } , 0, $trunc->[1] );
299             #warn "$trunc->[0] => $len => $content->{ $trunc->[0] }\n" if $DEBUG;
300             }
301             elsif ( $trunc->[4] ) {
302 0         0 croak "$trunc->[0] is required";
303             }
304             }
305              
306              
307             sub format_amount_field {
308 78     78 1 138 my ($self, $data, $field) = @_;
309 78 100       159 if (defined ( $data->{$field} ) ) {
310 54         301 $data->{$field} = sprintf( "%.2f", $data->{$field} );
311 54         201 $data->{$field} =~ s/\.//g;
312             }
313             }
314              
315              
316             sub format_phone_field {
317 6     6 1 18 my ($self, $data, $field) = @_;
318 6 50       17 if (defined ( $data->{$field} ) ) {
319 6         96 my $convertPhone = {
320             'a' => 2, 'b' => 2, 'c' => 2,
321             'd' => 3, 'e' => 3, 'f' => 3,
322             'g' => 4, 'h' => 4, 'i' => 4,
323             'j' => 5, 'k' => 5, 'l' => 5,
324             'm' => 6, 'n' => 6, 'o' => 6,
325             'p' => 7, 'q' => 7, 'r' => 7, 's' => 7,
326             't' => 8, 'u' => 8, 'v' => 8,
327             'w' => 9, 'x' => 9, 'y' => 9, 'z' => 9,
328             };
329 6 50       34 $data->{$field} =~ s/(\D)/$$convertPhone{lc($1)}||''/eg;
  12         75  
330             }
331             }
332              
333              
334             sub map_request {
335 6     6 1 13 my ( $self, $content ) = @_;
336              
337 6         24 $self->map_fields($content);
338              
339 6         12 my $action = $content->{'TransactionType'};
340              
341 6         18 my @required_fields = qw(action type);
342              
343 6         34 $self->required_fields(@required_fields);
344              
345             # for tabbing
346             # set dollar amounts to the required format (eg $5.00 should be 500)
347 6         203 foreach my $field ( 'amount', 'salesTax', 'discountAmount', 'shippingAmount', 'dutyAmount' ) {
348 30         67 $self->format_amount_field($content, $field);
349             }
350              
351             # make sure the date is in MMYY format
352 6         60 $content->{'expiration'} =~ s/^(\d{1,2})\D*\d*?(\d{2})$/$1$2/;
353              
354 6 50       23 if ( ! defined $content->{'description'} ) { $content->{'description'} = ''; } # schema req
  0         0  
355 6         20 $content->{'description'} =~ s/[^\w\s\*\,\-\'\#\&\.]//g;
356              
357             # Litle pre 0.934 used token, however BOP likes card_token
358 6 50 66     31 $content->{'card_token'} = $content->{'token'} if ! defined $content->{'card_token'} && defined $content->{'card_token'};
359              
360             # only numbers are allowed in company_phone
361 6         33 $self->format_phone_field($content, 'company_phone');
362              
363 6   33     33 $content->{'invoice_number_length_15'} ||= $content->{'invoice_number'}; # orderId = 25, invoiceReferenceNumber = 15
364              
365             # put in a list of constraints
366 6         145 my @validate = (
367             # field, maxLen, minLen, errorOnLength, isRequired
368             [ 'name', 100, 0, 0, 0 ],
369             [ 'email', 100, 0, 0, 0 ],
370             [ 'address', 35, 0, 0, 0 ],
371             [ 'city', 35, 0, 0, 0 ],
372             [ 'state', 30, 0, 0, 0 ], # 30 is allowed, but it should be the 2 char code
373             [ 'zip', 20, 0, 0, 0 ],
374             [ 'country', 3, 0, 0, 0 ], # should use iso 3166-1 2 char code
375             [ 'phone', 20, 0, 0, 0 ],
376              
377             [ 'ship_name', 100, 0, 0, 0 ],
378             [ 'ship_email', 100, 0, 0, 0 ],
379             [ 'ship_address',35, 0, 0, 0 ],
380             [ 'ship_city', 35, 0, 0, 0 ],
381             [ 'ship_state', 30, 0, 0, 0 ], # 30 is allowed, but it should be the 2 char code
382             [ 'ship_zip', 20, 0, 0, 0 ],
383             [ 'ship_country', 3, 0, 0, 0 ], # should use iso 3166-1 2 char code
384             [ 'ship_phone', 20, 0, 0, 0 ],
385              
386             #[ 'customerType',13, 0, 0, 0 ],
387              
388             ['company_phone',13, 0, 0, 0 ],
389             [ 'description', 25, 0, 0, 0 ],
390              
391             [ 'po_number', 17, 0, 0, 0 ],
392             [ 'salestax', 8, 0, 1, 0 ],
393             [ 'discount', 8, 0, 1, 0 ],
394             [ 'shipping', 8, 0, 1, 0 ],
395             [ 'duty', 8, 0, 1, 0 ],
396             ['invoice_number',25, 0, 0, 0 ],
397             ['invoice_number_length_15',15,0, 0, 0 ],
398             [ 'orderdate', 10, 0, 0, 0 ], # YYYY-MM-DD
399              
400             [ 'recycle_by', 8, 0, 0, 0 ],
401             [ 'recycle_id', 25, 0, 0, 0 ],
402              
403             [ 'affiliate', 25, 0, 0, 0 ],
404              
405             [ 'card_type', 2, 2, 1, 0 ],
406             [ 'card_number', 25, 13, 1, 0 ],
407             [ 'expiration', 4, 4, 1, 0 ], # MMYY
408             [ 'cvv2', 4, 3, 1, 0 ],
409             # 'card_token' does not have a documented limit
410              
411             [ 'customer_id', 25, 0, 0, 0 ],
412             );
413 6         15 foreach my $trunc ( @validate ) {
414 204         348 $self->format_misc_field($content,$trunc);
415             #warn "$trunc->[0] => ".($content->{ $trunc->[0] }||'')."\n" if $DEBUG;
416             }
417              
418 6         42 tie my %customer_info, 'Tie::IxHash', $self->_revmap_fields(
419             content => $content,
420             ssn => 'ssn',
421             dob => 'dob',
422             customerRegistrationDate => 'registration_date',
423             customerType => 'customer_type',
424             incomeAmount => 'income_amount',
425             incomeCurrency => 'income_currency',
426             employerName => 'employer_name',
427             customerWorkTelephone => 'work_phone',
428             residenceStatus => 'residence_status',
429             yearsAtResidence => 'residence_years',
430             yearsAtEmployer => 'employer_years',
431             );
432              
433 6         87 tie my %billToAddress, 'Tie::IxHash', $self->_revmap_fields(
434             content => $content,
435             name => 'name',
436             email => 'email',
437             addressLine1 => 'address',
438             city => 'city',
439             state => 'state',
440             zip => 'zip',
441             country => 'country'
442             , #TODO: will require validation to the spec, this field wont' work as is
443             phone => 'phone',
444             );
445              
446 6         566 tie my %shipToAddress, 'Tie::IxHash', $self->_revmap_fields(
447             content => $content,
448             name => 'ship_name',
449             addressLine1 => 'ship_address',
450             addressLine2 => 'ship_address2',
451             addressLine3 => 'ship_address3',
452             city => 'ship_city',
453             state => 'ship_state',
454             zip => 'ship_zip',
455             country => 'ship_country'
456             , #TODO: will require validation to the spec, this field wont' work as is
457             email => 'ship_email',
458             phone => 'ship_phone',
459             );
460              
461 6         445 tie my %customerinfo, 'Tie::IxHash',
462             $self->_revmap_fields(
463             content => $content,
464             customerType => 'customerType',
465             );
466              
467 6         165 tie my %custombilling, 'Tie::IxHash',
468             $self->_revmap_fields(
469             content => $content,
470             phone => 'company_phone',
471             descriptor => 'description',
472             #url => 'url',
473             );
474              
475             ## loop through product list and generate lineItemData for each
476             #
477 6         227 my @products = ();
478 6 50 33     26 if( defined $content->{'products'} && scalar( @{ $content->{'products'} } ) < 100 ){
  6         19  
479 6         11 foreach my $prodOrig ( @{ $content->{'products'} } ) {
  6         19  
480             # use a local copy of prod so that we do not have issues if they try to submit more then once.
481 12         86 my %prod = %$prodOrig;
482 12         31 foreach my $field ( 'tax','amount','totalwithtax','discount' ) {
483             # Note: DO NOT format 'cost', it uses the decimal format
484 48         124 $self->format_amount_field(\%prod, $field);
485             }
486              
487 12         63 my @validate = (
488             # field, maxLen, minLen, errorOnLength, isRequired
489             [ 'description', 26, 0, 0, 0 ],
490             [ 'tax', 8, 0, 1, 0 ],
491             [ 'amount', 8, 0, 1, 0 ],
492             [ 'totalwithtax', 8, 0, 1, 0 ],
493             [ 'discount', 8, 0, 1, 0 ],
494             [ 'code', 12, 0, 0, 0 ],
495             [ 'cost', 12, 0, 1, 0 ],
496             );
497 12         23 foreach my $trunc ( @validate ) { $self->format_misc_field(\%prod,$trunc); }
  84         141  
498              
499 12         33 tie my %lineitem, 'Tie::IxHash',
500             $self->_revmap_fields(
501             content => \%prod,
502             itemSequenceNumber => 'itemSequenceNumber',
503             itemDescription => 'description',
504             productCode => 'code',
505             quantity => 'quantity',
506             unitOfMeasure => 'units',
507             taxAmount => 'tax',
508             lineItemTotal => 'amount',
509             lineItemTotalWithTax => 'totalwithtax',
510             itemDiscountAmount => 'discount',
511             commodityCode => 'code',
512             unitCost => 'cost', # This "amount" field uses decimals
513             );
514 12         1532 push @products, \%lineitem;
515             }
516             }
517              
518 6         21 tie my %filtering, 'Tie::IxHash', $self->_revmap_fields(
519             content => $content,
520             prepaid => 'filter_prepaid',
521             international => 'filter_international',
522             chargeback => 'filter_chargeback',
523             );
524              
525 6         80 tie my %healthcaresub, 'Tie::IxHash', $self->_revmap_fields(
526             content => $content,
527             totalHealthcareAmount => 'amount_healthcare',
528             RxAmount => 'amount_medications',
529             visionAmount => 'amount_vision',
530             clinicOtherAmount => 'amount_clinic',
531             dentalAmount => 'amount_dental',
532             );
533              
534 6         85 tie my %healthcare, 'Tie::IxHash', $self->_revmap_fields(
535             content => $content,
536             healthcareAmounts => \%healthcaresub,
537             IIASFlag => 'healthcare_flag',
538             );
539              
540 6         95 tie my %amexaggregator, 'Tie::IxHash', $self->_revmap_fields(
541             content => $content,
542             sellerId => 'amex_seller_id',
543             sellerMerchantCategoryCode => 'amex_merch_code',
544             );
545              
546 6         92 tie my %detailtax, 'Tie::IxHash', $self->_revmap_fields(
547             content => $content,
548             taxIncludedInTotal => 'tax_in_total',
549             taxAmount => 'tax_amount',
550             taxRate => 'tax_rate',
551             taxTypeIdentifier => 'tax_type',
552             cardAcceptorTaxId => 'tax_id',
553             );
554             #
555             #
556 6         99 tie my %enhanceddata, 'Tie::IxHash', $self->_revmap_fields(
557             content => $content,
558             customerReference => 'po_number',
559             salesTax => 'salestax',
560             deliveryType => 'deliverytype',
561             taxExempt => 'tax_exempt',
562             discountAmount => 'discount',
563             shippingAmount => 'shipping',
564             dutyAmount => 'duty',
565             shipFromPostalCode => 'company_zip',
566             destinationPostalCode => 'ship_zip',
567             destinationCountryCode => 'ship_country',
568             invoiceReferenceNumber => 'invoice_number_length_15',
569             orderDate => 'orderdate',
570             detailTax => \%detailtax,
571             lineItemData => \@products,
572             );
573              
574 6         548 tie my %card, 'Tie::IxHash', $self->_revmap_fields(
575             content => $content,
576             type => 'card_type',
577             number => 'card_number',
578             expDate => 'expiration',
579             cardValidationNum => 'cvv2',
580             pin => 'pin',
581             );
582              
583 6         365 tie my %token, 'Tie::IxHash', $self->_revmap_fields(
584             content => $content,
585             litleToken => 'card_token',
586             expDate => 'expiration',
587             cardValidationNum => 'cvv2',
588             );
589              
590 6         290 tie my %sepadirect, 'Tie::IxHash', $self->_revmap_fields(
591             content => $content,
592             mandateProvider => 'sepa_mandate_provider',
593             sequenceType => 'sepa_sequence_type',
594             mandateReference => 'sepa_mandate_reference',
595             mandateUrl => 'sepa_mandate_url',
596             mandateSignatureDate => 'sepa_mandate_signature_date',
597             iban => 'sepa_iban',
598             preferredLanguage => 'sepa_language',
599             );
600            
601 6         86 tie my %ideal, 'Tie::IxHash', $self->_revmap_fields(
602             content => $content,
603             preferredLanguage => 'ideal_language',
604             );
605              
606 6         84 tie my %processing, 'Tie::IxHash', $self->_revmap_fields(
607             content => $content,
608             bypassVelocityCheck => 'velocity_check',
609             );
610              
611 6         265 tie my %pos, 'Tie::IxHash', $self->_revmap_fields(
612             content => $content,
613             capability => 'pos_capability',
614             entryMode => 'pos_entry_mode',
615             cardholderId => 'pos_cardholder_id',
616             terminalId => 'pos_terminal_id',
617             catLevel => 'pos_cat_level',
618             #For CAT (Cardholder Activated Terminal) transactions, the capability element must be set to magstripe, the cardholderId element must be set to nopin, and the catLevel element must be set to self service.
619             );
620              
621 6         93 tie my %cardholderauth, 'Tie::IxHash',
622             $self->_revmap_fields(
623             content => $content,
624             authenticationValue => '3ds',
625             authenticationTransactionId => 'visaverified',
626             customerIpAddress => 'ip',
627             authenticatedByMerchant => 'authenticated',
628             );
629              
630 6         202 tie my %merchantdata, 'Tie::IxHash',
631             $self->_revmap_fields(
632             content => $content,
633             affiliate => 'affiliate',
634             merchantGroupingId => 'merchant_grouping_id',
635             );
636              
637 6         191 tie my %recyclingrequest, 'Tie::IxHash',
638             $self->_revmap_fields(
639             content => $content,
640             recycleBy => 'recycle_by',
641             recycleId => 'recycle_id',
642             );
643              
644 6         279 tie my %recurringRequest, 'Tie::IxHash',
645             $self->_revmap_fields(
646             content => $content,
647             planCode => 'recurring_plan_code',
648             numberOfPayments => 'recurring_number_of_payments',
649             startDate => 'recurring_start_date',
650             amount => 'recurring_amount',
651             );
652            
653 6         89 tie my %advancedfraud, 'Tie::IxHash',
654             $self->_revmap_fields(
655             content => $content,
656             threatMetrixSessionId => 'threatMetrixSessionId',
657             customAttribute1 => 'advanced_fraud_customAttribute1',
658             customAttribute2 => 'advanced_fraud_customAttribute2',
659             customAttribute3 => 'advanced_fraud_customAttribute3',
660             customAttribute4 => 'advanced_fraud_customAttribute4',
661             customAttribute5 => 'advanced_fraud_customAttribute5',
662             );
663              
664 6         86 tie my %wallet, 'Tie::IxHash',
665             $self->_revmap_fields(
666             content => $content,
667             walletSourceType => 'wallet_source_type',
668             walletSourceTypeId => 'wallet_source_type_id',
669             );
670              
671 6         81 my %req;
672              
673 6 50       59 if ( $action eq 'registerTokenRequest' ) {
    100          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
674 0 0 0     0 croak 'missing card_number' if length($content->{'card_number'} || '') == 0;
675 0         0 tie %req, 'Tie::IxHash', $self->_revmap_fields(
676             content => $content,
677             orderId => 'invoice_number',
678             accountNumber => 'card_number',
679             );
680             }
681             elsif ( $action eq 'sale' ) {
682 5 100 100     208 croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0;
683             tie %req, 'Tie::IxHash', $self->_revmap_fields(
684             content => $content,
685             orderId => 'invoice_number',
686             amount => 'amount',
687             secondaryAmount => 'secondary_amount',
688             orderSource => 'orderSource',
689             customerInfo => \%customer_info, # PP only
690             billToAddress => \%billToAddress,
691             shipToAddress => \%shipToAddress,
692             card => $content->{'card_number'} ? \%card : {},
693 4 100       45 token => $content->{'card_token'} ? \%token : {},
    100          
694             #[||||||
695             #|] (Choice)
696             sepaDirectDebit => \%sepadirect,
697             ideal => \%ideal,
698             cardholderAuthentication => \%cardholderauth,
699             customBilling => \%custombilling,
700             taxType => 'tax_type', # payment|fee
701             enhancedData => \%enhanceddata,
702             processingInstructions => \%processing,
703             amexAggregatorData => \%amexaggregator,
704             allowPartialAuth => 'partial_auth',
705             healthcareIIAS => \%healthcare,
706             filtering => \%filtering,
707             merchantData => \%merchantdata,
708             recyclingRequest => \%recyclingrequest,
709             fraudFilterOverride => 'filter_fraud_override',
710             recurringRequest => \%recurringRequest,
711             debtRepayment => 'debt_repayment',
712             advancedFraudChecks => \%advancedfraud,
713             wallet => \%wallet,
714             processingType => 'processing_type',
715             originalNetworkTransactionId => 'original_network_transaction_id',
716             originalTransactionAmount => 'original_transaction_amount',
717             );
718             }
719             elsif ( $action eq 'authorization' ) {
720 1 50 0     5 croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0;
721             tie %req, 'Tie::IxHash', $self->_revmap_fields(
722             content => $content,
723             orderId => 'invoice_number',
724             amount => 'amount',
725             secondaryAmount => 'secondary_amount',
726             orderSource => 'orderSource',
727             customerInfo => \%customer_info, # PP only
728             billToAddress => \%billToAddress,
729             shipToAddress => \%shipToAddress,
730             card => $content->{'card_number'} ? \%card : {},
731 1 50       13 token => $content->{'card_token'} ? \%token : {},
    50          
732              
733             cardholderAuthentication => \%cardholderauth,
734             processingInstructions => \%processing,
735             pos => \%pos,
736             customBilling => \%custombilling,
737             taxType => 'tax_type', # payment|fee
738             enhancedData => \%enhanceddata,
739             amexAggregatorData => \%amexaggregator,
740             allowPartialAuth => 'partial_auth',
741             healthcareIIAS => \%healthcare,
742             filtering => \%filtering,
743             merchantData => \%merchantdata,
744             recyclingRequest => \%recyclingrequest,
745             fraudFilterOverride => 'filter_fraud_override',
746             recurringRequest => \%recurringRequest,
747             debtRepayment => 'debt_repayment',
748             advancedFraudChecks => \%advancedfraud,
749             wallet => \%wallet,
750             processingType => 'processing_type',
751             originalNetworkTransactionId => 'original_network_transaction_id',
752             originalTransactionAmount => 'original_transaction_amount',
753              
754             );
755             }
756             elsif ( $action eq 'capture' ) {
757 0         0 push @required_fields, qw( order_number amount );
758 0         0 tie %req, 'Tie::IxHash',
759             $self->_revmap_fields(
760             # partial is an element of the start tag, so located in the header
761             content => $content,
762             litleTxnId => 'order_number',
763             amount => 'amount',
764             surchargeAmount => 'surcharge_amount',
765             enhancedData => \%enhanceddata,
766             processingInstructions => \%processing,
767             payPalOrderComplete => 'paypal_order_complete',
768             pin => 'pin',
769             );
770             }
771             elsif ( $action eq 'force_capture' ) {
772             ## ARE YOU SURE YOU WANT TO DO THIS?
773             # Seriously, force captures are like running up the pirate flag, check with your Vantiv rep
774 0         0 push @required_fields, qw( order_number amount );
775             tie %req, 'Tie::IxHash',
776             $self->_revmap_fields(
777             # partial is an element of the start tag, so located in the header
778             content => $content,
779             litleTxnId => 'order_number',
780             amount => 'amount',
781             secondaryAmount => 'secondary_amount',
782             orderSource => 'orderSource',
783             billToAddress => \%billToAddress,
784             card => $content->{'card_number'} ? \%card : {},
785 0 0       0 token => $content->{'card_token'} ? \%token : {},
    0          
786             customBilling => \%custombilling,
787             taxType => 'tax_type', # payment|fee
788             enhancedData => \%enhanceddata,
789             processingInstructions => \%processing,
790             amexAggregatorData => \%amexaggregator,
791             merchantData => \%merchantdata,
792             debtRepayment => 'debt_repayment',
793             processingType => 'processing_type',
794             );
795             }
796             elsif ( $action eq 'credit' ) {
797              
798             # IF there is a litleTxnId, it's a normal linked credit
799 0 0       0 if( $content->{'order_number'} ){
800 0         0 push @required_fields, qw( order_number amount );
801 0         0 tie %req, 'Tie::IxHash', $self->_revmap_fields(
802             content => $content,
803             litleTxnId => 'order_number',
804             amount => 'amount',
805             secondaryAmount => 'secondary_amount',
806             customBilling => \%custombilling,
807             enhancedData => \%enhanceddata,
808             processingInstructions => \%processing,
809             actionReason => 'action_reason', # ENUM(SUSPECT_FRAUD) only option atm
810             );
811             }
812             # ELSE it's an unlinked, which requires different data
813             else {
814 0 0 0     0 croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0;
815 0         0 push @required_fields, qw( invoice_number amount );
816             tie %req, 'Tie::IxHash', $self->_revmap_fields(
817             content => $content,
818             orderId => 'invoice_number',
819             amount => 'amount',
820             orderSource => 'orderSource',
821             billToAddress => \%billToAddress,
822             card => $content->{'card_number'} ? \%card : {},
823 0 0       0 token => $content->{'card_token'} ? \%token : {},
    0          
824             customBilling => \%custombilling,
825             taxType => 'tax_type',
826             enhancedData => \%enhanceddata,
827             processingInstructions => \%processing,
828             pos => \%pos,
829             amexAggregatorData => \%amexaggregator,
830             merchantData => \%merchantdata,
831             actionReason => 'action_reason', # ENUM(SUSPECT_FRAUD) only option atm
832             );
833             }
834             }
835             elsif ( $action eq 'void' ) {
836 0         0 push @required_fields, qw( order_number );
837 0         0 tie %req, 'Tie::IxHash',
838             $self->_revmap_fields(
839             content => $content,
840             litleTxnId => 'order_number',
841             processingInstructions => \%processing,
842             );
843             }
844             elsif ( $action eq 'authReversal' ) {
845 0         0 push @required_fields, qw( order_number amount );
846 0         0 tie %req, 'Tie::IxHash',
847             $self->_revmap_fields(
848             content => $content,
849             litleTxnId => 'order_number',
850             amount => 'amount',
851             actionReason => 'action_reason', # ENUM(SUSPECT_FRAUD) only option atm
852             );
853             }
854             elsif ( $action eq 'accountUpdate' ) {
855 0         0 push @required_fields, qw( card_number expiration );
856 0         0 tie %req, 'Tie::IxHash',
857             $self->_revmap_fields(
858             content => $content,
859             orderId => 'customer_id',
860             card => \%card,
861             );
862             }
863              
864 5         948 $self->required_fields(@required_fields);
865 5         344 return \%req;
866             }
867              
868             sub submit {
869 0     0 1 0 my ($self) = @_;
870              
871 0         0 local $SCRUBBER=1;
872 0         0 $self->_litle_init;
873              
874 0         0 my %content = $self->content();
875              
876 0 0       0 warn 'Pre processing: '.Dumper(\%content) if $DEBUG;
877 0         0 my $req = $self->map_request( \%content );
878 0 0       0 warn 'Post processing: '.Dumper(\%content) if $DEBUG;
879 0         0 my $post_data;
880              
881 0         0 my $writer = XML::Writer->new(
882             OUTPUT => \$post_data,
883             DATA_MODE => 1,
884             DATA_INDENT => 2,
885             ENCODING => 'utf-8',
886             );
887              
888             ## set the authentication data
889 0         0 tie my %authentication, 'Tie::IxHash',
890             $self->_revmap_fields(
891             content => \%content,
892             user => 'login',
893             password => 'password',
894             );
895              
896 0 0       0 warn Dumper($req) if $DEBUG;
897             ## Start the XML Document, parent tag
898 0         0 $writer->xmlDecl();
899             $writer->startTag(
900             "litleOnlineRequest",
901             version => $self->api_version,
902             xmlns => $self->xmlns,
903 0         0 merchantId => $content{'merchantid'},
904             );
905              
906 0         0 $self->_xmlwrite( $writer, 'authentication', \%authentication );
907              
908             ## partial capture modifier, odd location, because it modifies the start tag :(
909 0         0 my %extra;
910 0 0       0 if ($content{'TransactionType'} eq 'capture'){
911 0 0       0 $extra{'partial'} = $content{'partial'} ? 'true' : 'false';
912             }
913              
914             $writer->startTag(
915             $content{'TransactionType'},
916             id => $content{'invoice_number'},
917             reportGroup => $content{'report_group'} || 'BOP',
918 0   0     0 customerId => $content{'customer_id'} || 1,
      0        
919             %extra,
920             );
921 0         0 foreach ( keys( %{$req} ) ) {
  0         0  
922 0         0 $self->_xmlwrite( $writer, $_, $req->{$_} );
923             }
924              
925 0         0 $writer->endTag( $content{'TransactionType'} );
926 0         0 $writer->endTag("litleOnlineRequest");
927 0         0 $writer->end();
928             ## END XML Generation
929              
930 0         0 $self->server_request( $post_data );
931 0 0       0 warn $self->server_request if $DEBUG;
932              
933 0 0       0 if ( $] ge '5.008' ) {
934             # http_post expects data in this format
935 0 0       0 utf8::encode($post_data) if utf8::is_utf8($post_data);
936             }
937              
938 0         0 my ( $page, $status_code, %headers ) = $self->https_post( { 'Content-Type' => 'text/xml; charset=utf-8' } , $post_data);
939              
940 0         0 $self->server_response( $page );
941 0 0       0 warn Dumper $self->server_response, $status_code, \%headers if $DEBUG;
942              
943 0         0 my $response = $self->_parse_xml_response( $page, $status_code );
944              
945 0         0 $content{'TransactionType'} =~ s/Request$//; # no clue why some of the types have a Request and some do not
946              
947 0 0 0     0 if ( exists( $response->{'response'} ) && $response->{'response'} == 1 ) {
948             ## parse error type error
949 0         0 warn Dumper 'https://'.$self->server.':'.$self->port.$self->path,$response, $self->server_request;
950 0         0 $self->error_message( $response->{'message'} );
951 0         0 return;
952             } else {
953             $self->error_message(
954             $response->{ $content{'TransactionType'} . 'Response' }
955 0         0 ->{'message'} );
956             }
957 0         0 $self->{_response} = $response;
958              
959 0 0       0 warn Dumper($response) if $DEBUG;
960              
961             ## Set up the data:
962 0         0 my $resp = $response->{ $content{'TransactionType'} . 'Response' };
963 0         0 $self->{_response} = $resp;
964 0   0     0 $self->card_token( $resp->{'litleToken'} || $resp->{'tokenResponse'}->{'litleToken'} || $content{'card_token'} || '' );
965 0   0     0 $self->order_number( $resp->{'litleTxnId'} || '' );
966 0   0     0 $self->result_code( $resp->{'response'} || '' );
967 0 0       0 $resp->{'authCode'} =~ s/\D//g if $resp->{'authCode'};
968 0   0     0 $self->authorization( $resp->{'authCode'} || '' );
969 0   0     0 $self->cvv2_response( $resp->{'fraudResult'}->{'cardValidationResult'}
970             || '' );
971 0   0     0 $self->avs_code( $resp->{'fraudResult'}->{'avsResult'} || '' );
972 0 0 0     0 if( $resp->{enhancedAuthResponse}
      0        
973             && $resp->{enhancedAuthResponse}->{fundingSource}
974             && $resp->{enhancedAuthResponse}->{fundingSource}->{type} eq 'PREPAID' ) {
975              
976 0         0 $self->is_prepaid(1);
977 0         0 $self->prepaid_balance( $resp->{enhancedAuthResponse}->{fundingSource}->{availableBalance} );
978             } else {
979 0         0 $self->is_prepaid(0);
980             }
981              
982             #$self->is_dupe( $resp->{'duplicate'} ? 1 : 0 );
983 0 0 0     0 if( defined $resp->{'duplicate'} && $resp->{'duplicate'} eq 'true' ) {
984 0         0 $self->is_duplicate(1);
985             }
986             else {
987 0         0 $self->is_duplicate(0);
988             }
989              
990 0 0       0 if( defined $resp->{tokenResponse} ) {
991 0         0 $self->card_token($resp->{tokenResponse}->{litleToken});
992 0         0 $self->card_token_response($resp->{tokenResponse}->{tokenResponseCode});
993 0         0 $self->card_token_message($resp->{tokenResponse}->{tokenMessage});
994             }
995              
996 0 0 0     0 if( $resp->{enhancedAuthResponse}
997             && $resp->{enhancedAuthResponse}->{affluence}
998             ){
999 0         0 $self->get_affluence( $resp->{enhancedAuthResponse}->{affluence} );
1000             }
1001 0 0       0 $self->is_success( $self->result_code() eq '000' ? 1 : 0 );
1002 0 0 0     0 if(
      0        
1003             $self->result_code() eq '010' # Partial approval, if they chose that option
1004             || ($self->result_code() eq '802' && $self->card_token) # Card is already a token
1005             ) {
1006 0         0 $self->is_success(1);
1007             }
1008              
1009             ##Failure Status for 3.0 users
1010 0 0       0 if ( !$self->is_success ) {
1011             my $f_status =
1012             $ERRORS{ $self->result_code }->{'failure'}
1013 0 0       0 ? $ERRORS{ $self->result_code }->{'failure'}
1014             : 'decline';
1015 0         0 $self->failure_status($f_status);
1016             }
1017              
1018 0 0       0 unless ( $self->is_success() ) {
1019 0 0       0 unless ( $self->error_message() ) {
1020             $self->error_message( "(HTTPS response: $status_code) "
1021             . "(HTTPS headers: "
1022 0         0 . join( ", ", map { "$_ => " . $headers{$_} } keys %headers )
  0         0  
1023             . ") "
1024             . "(Raw HTTPS content: ".$self->server_response().")" );
1025             }
1026             }
1027              
1028             }
1029              
1030              
1031             sub chargeback_retrieve_support_doc {
1032 0     0 1 0 my ( $self ) = @_;
1033 0         0 $self->_litle_support_doc('RETRIEVE');
1034 0 0       0 if ($self->is_success) { $self->{'fileContent'} = $self->{'server_response_dangerous'}; } else { $self->{'fileContent'} = undef; }
  0         0  
  0         0  
1035             }
1036              
1037              
1038             sub chargeback_delete_support_doc {
1039 0     0 1 0 my ( $self ) = @_;
1040 0         0 $self->_litle_support_doc('DELETE' );
1041             }
1042              
1043              
1044             sub chargeback_upload_support_doc {
1045 0     0 1 0 my ( $self ) = @_;
1046 0         0 $self->_litle_support_doc('UPLOAD' );
1047             }
1048              
1049              
1050             sub chargeback_replace_support_doc {
1051 0     0 1 0 my ( $self ) = @_;
1052 0         0 $self->_litle_support_doc('REPLACE' );
1053             }
1054              
1055             sub _litle_support_doc {
1056 0     0   0 my ( $self, $action ) = @_;
1057              
1058 0         0 local $SCRUBBER=1;
1059 0         0 $self->_litle_init;
1060              
1061 0         0 my %content = $self->content();
1062              
1063 0         0 my $requiredargs = ['case_id','filename','merchantid'];
1064 0 0       0 if ($action =~ /(?:UPLOAD|REPLACE)/) { push @$requiredargs, 'filecontent', 'mimetype'; }
  0         0  
1065 0         0 foreach my $key (@$requiredargs) {
1066 0 0       0 croak "Missing arg $key" unless $content{$key};
1067             }
1068              
1069 0         0 my $actionRESTful = {
1070             'DELETE' => 'DELETE',
1071             'RETRIEVE' => 'GET',
1072             'UPLOAD' => 'POST',
1073             'REPLACE' => 'PUT',
1074             };
1075 0 0       0 die "UNDEFINED ACTION: $action" unless defined $actionRESTful->{$action};
1076              
1077             {
1078 4     4   15694 use bytes;
  4         10  
  4         26  
  0         0  
1079 0 0       0 if ( defined $content{'filecontent'} ) {
1080 0 0       0 if ( length($content{'filecontent'}) > 2097152 ) { # file limit of 2M
1081 0         0 my $msg = 'Filesize Exceeds Limit Of 2MB';
1082 0         0 $self->result_code( 012 ); ## no critic
1083 0         0 $self->error_message( $msg );
1084 0         0 croak $msg;
1085             }
1086 0         0 my $allowedTypes = {
1087             'application/pdf' => 1,
1088             'image/gif' => 1,
1089             'image/jpeg' => 1,
1090             'image/png' => 1,
1091             'image/tiff' => 1,
1092             };
1093 0 0 0     0 if ( ! defined $allowedTypes->{$content{'mimetype'}||''} ) {
1094 0         0 croak "File must be one of PDF/GIF/JPG/PNG/TIFF".$content{'mimetype'};
1095             }
1096             }
1097             }
1098              
1099 0         0 my $caseidURI = $content{'case_id'};
1100 0         0 my $filenameURI = $content{'filename'};
1101 0         0 my $merchantidURI = $content{'merchantid'};
1102 0         0 foreach ( $caseidURI, $filenameURI, $merchantidURI ) {
1103 0         0 s/([^a-z0-9\.\-])/sprintf('%%%X',ord($1))/ige;
  0         0  
1104             }
1105              
1106 0         0 my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'//services/chargebacks/documents/'.$merchantidURI.'/'.$caseidURI.'/'.$filenameURI;
1107             my $response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request($actionRESTful->{$action}, $url, {
1108             headers => {
1109             'Authorization' => 'Basic ' . MIME::Base64::encode("$content{'login'}:$content{'password'}",''),
1110             'Content-Type' => $content{'mimetype'} || 'text/plain',
1111             },
1112 0   0     0 content => $content{'filecontent'},
1113             } );
1114              
1115 0         0 $self->server_request( $content{'mimetype'} );
1116 0         0 $self->server_response( $response->{'content'} );
1117              
1118 0 0 0     0 if ( $action eq 'RETRIEVE' && $response->{'status'} =~ /^200/ && substr($response->{'content'},0,500) !~ /
      0        
1119             # the RETRIEVE action returns the actual page as the file, rather then returning XML
1120 0         0 $self->is_success(1);
1121             } else {
1122 0         0 my $xml_response = $self->_parse_xml_response( $response->{'content'}, $response->{'status'} );
1123              
1124 0 0 0     0 if (defined $xml_response && defined $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'}) {
1125 0 0       0 $self->is_success( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'} eq '000' ? 1 : 0 );
1126 0         0 $self->result_code( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'} );
1127 0         0 $self->error_message( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseMessage'} );
1128             } else {
1129 0         0 croak "UNRECOGNIZED RESULT: ".$self->server_response;
1130             }
1131             }
1132             }
1133              
1134              
1135             sub chargeback_list_support_docs {
1136 0     0 1 0 my ( $self ) = @_;
1137              
1138 0         0 local $SCRUBBER=1;
1139 0         0 $self->_litle_init;
1140              
1141 0         0 my %content = $self->content();
1142              
1143 0 0       0 croak "Missing arg case_id" unless $content{'case_id'};
1144 0 0       0 croak "Missing arg merchantid" unless $content{'merchantid'};
1145 0         0 my $caseidURI = $content{'case_id'};
1146 0         0 my $merchantidURI = $content{'merchantid'};
1147 0         0 foreach ( $caseidURI, $merchantidURI ) {
1148 0         0 s/([^a-z0-9\.\-])/sprintf('%%%X',ord($1))/ige;
  0         0  
1149             }
1150              
1151 0         0 my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'//services/chargebacks/documents/'.$merchantidURI.'/'.$caseidURI.'/';
1152 0         0 my $response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('GET', $url, {
1153             headers => { Authorization => 'Basic ' . MIME::Base64::encode("$content{'login'}:$content{'password'}",'') },
1154             } );
1155              
1156 0         0 $self->server_request( $url );
1157 0         0 $self->server_response( $response->{'content'} );
1158              
1159 0         0 my $xml_response = $self->_parse_xml_response( $response->{'content'}, $response->{'status'} );
1160              
1161 0 0 0     0 if (defined $xml_response && $xml_response->{'ChargebackCase'}{'ResponseCode'}) {
    0 0        
1162 0         0 $self->result_code( $xml_response->{'ChargebackCase'}{'ResponseCode'} );
1163 0         0 $self->error_message( $xml_response->{'ChargebackCase'}{'ResponseMessage'} );
1164             } elsif (defined $xml_response && $xml_response->{'ChargebackCase'}{'DocumentEntry'}) {
1165 0         0 $self->is_success(1);
1166 0         0 $self->result_code( '000' );
1167              
1168 0         0 my $ref = $xml_response->{'ChargebackCase'}{'DocumentEntry'};
1169 0 0 0     0 if (defined $ref->{'id'} && ref $ref->{'id'} eq '') {
1170             # XMLin does not parse the result properly for a single document. This fixes the single document format to match the multi-doc format
1171 0         0 $ref = { $ref->{'id'} => $ref };
1172             }
1173 0         0 return $ref;
1174             } else {
1175 0         0 croak "UNRECOGNIZED RESULT: ".$self->server_response;
1176             }
1177 0         0 return {};
1178             }
1179              
1180             sub _parse_xml_response {
1181 5     5   19 my ( $self, $page, $status_code ) = @_;
1182 5         17 my $response = {};
1183 5 50       32 if ( $status_code =~ /^200/ ) {
1184 5 50       14 if ( ! eval { $response = XMLin($page); } ) {
  5         38  
1185 0         0 die "XML PARSING FAILURE: $@";
1186             }
1187             }
1188             else {
1189 0         0 $status_code =~ s/[\r\n\s]+$//; # remove newline so you can see the error in a linux console
1190 0 0       0 if ( $status_code =~ /^(?:900|599)/ ) { $status_code .= ' - verify Litle has whitelisted your IP'; }
  0         0  
1191 0         0 die "CONNECTION FAILURE: $status_code";
1192             }
1193 5         130579 return $response;
1194             }
1195              
1196             sub _parse_batch_response {
1197 0     0   0 my ( $self, $args ) = @_;
1198 0         0 my @results;
1199 0         0 my $resp = $self->{'batch_response'};
1200 0         0 $self->order_number( $resp->{'litleBatchId'} );
1201              
1202             #$self->invoice_number( $resp->{'id'} );
1203             my @result_types =
1204 0         0 grep { $_ =~ m/Response$/ }
1205 0         0 keys %{$resp}; ## get a list of result types in this batch
  0         0  
1206             return {
1207 0         0 'account_update' => $self->_get_update_response,
1208             ## do the other response types now
1209             };
1210             }
1211              
1212              
1213             sub add_item {
1214 0     0 1 0 my $self = shift;
1215             ## do we want to render it now, or later?
1216 0         0 push @{ $self->{'batch_entries'} }, shift;
  0         0  
1217             }
1218              
1219              
1220             sub create_batch {
1221 0     0 1 0 my ( $self, %opts ) = @_;
1222              
1223 0         0 local $SCRUBBER=1;
1224 0         0 $self->_litle_init(\%opts);
1225              
1226 0 0 0     0 if ( ! defined $self->{'batch_entries'} || scalar( @{ $self->{'batch_entries'} } ) < 1 ) {
  0         0  
1227 0         0 $self->error_message('Cannot create an empty batch');
1228 0         0 return;
1229             }
1230              
1231 0         0 my $post_data;
1232              
1233 0         0 my $writer = XML::Writer(
1234             OUTPUT => \$post_data,
1235             DATA_MODE => 1,
1236             DATA_INDENT => 2,
1237             ENCODING => 'utf-8',
1238             );
1239             ## set the authentication data
1240 0         0 tie my %authentication, 'Tie::IxHash',
1241             $self->_revmap_fields(
1242             content => \%opts,
1243             user => 'login',
1244             password => 'password',
1245             );
1246              
1247             ## Start the XML Document, parent tag
1248 0         0 $writer->xmlDecl();
1249             $writer->startTag(
1250             "litleRequest",
1251             version => $self->batch_api_version,
1252             xmlns => $self->xmlns,
1253 0   0     0 id => $opts{'batch_id'} || time,
1254             numBatchRequests => 1, #hardcoded for now, not doing multiple merchants
1255             );
1256              
1257             ## authentication
1258 0         0 $self->_xmlwrite( $writer, 'authentication', \%authentication );
1259             ## batch Request tag
1260             $writer->startTag(
1261             'batchRequest',
1262             id => $opts{'batch_id'} || time,
1263 0         0 numAccountUpdates => scalar( @{ $self->{'batch_entries'} } ),
1264 0   0     0 merchantId => $opts{'merchantid'},
1265             );
1266 0         0 foreach my $entry ( @{ $self->{'batch_entries'} } ) {
  0         0  
1267 0         0 $self->_litle_scrubber_add_card($entry->{'card_number'});
1268 0         0 my $req = $self->map_request( $entry );
1269             $writer->startTag(
1270             $entry->{'TransactionType'},
1271             id => $entry->{'invoice_number'},
1272             reportGroup => $entry->{'report_group'} || 'BOP',
1273 0   0     0 customerId => $entry->{'customer_id'} || 1,
      0        
1274             );
1275 0         0 foreach ( keys( %{$req} ) ) {
  0         0  
1276 0         0 $self->_xmlwrite( $writer, $_, $req->{$_} );
1277             }
1278 0         0 $writer->endTag( $entry->{'TransactionType'} );
1279             ## need to also handle the action tag here, and custid info
1280             }
1281 0         0 $writer->endTag("batchRequest");
1282 0         0 $writer->endTag("litleRequest");
1283 0         0 $writer->end();
1284             ## END XML Generation
1285              
1286 0         0 $self->server_request( $post_data );
1287 0 0       0 warn $self->server_request if $DEBUG;
1288              
1289             #----- Send it
1290 0 0 0     0 if ( $opts{'method'} && $opts{'method'} eq 'sftp' ) { #FTP
    0 0        
1291 0         0 my $sftp = $self->_sftp_connect(\%opts,'inbound');
1292              
1293             ## save the file out, can't put directly from var, and is multibyte, so issues from filehandle
1294 0   0     0 my $filename = $opts{'batch_id'} || $opts{'login'} . "_" . time;
1295 0         0 my $io = IO::String->new($post_data);
1296 0         0 tie *IO, 'IO::String';
1297              
1298 0 0       0 $sftp->put( $io, "$filename.prg" )
1299             or $self->_die("Cannot PUT $filename", $sftp->error);
1300 0 0       0 $sftp->rename( "$filename.prg",
1301             "$filename.asc" ) #once complete, you rename it, for pickup
1302             or $self->die("Cannot RENAME file", $sftp->error);
1303 0         0 $self->is_success(1);
1304 0         0 $self->server_response( $sftp->message );
1305             }
1306             elsif ( $opts{'method'} && $opts{'method'} eq 'https' ) { #https post
1307 0         0 $self->port('15000');
1308 0         0 $self->path('/');
1309 0         0 my ( $page, $status_code, %headers ) =
1310             $self->https_post($post_data);
1311 0         0 $self->server_response( $page );
1312              
1313 0 0       0 warn Dumper [ $page, $status_code, \%headers ] if $DEBUG;
1314              
1315 0         0 my $response = {};
1316 0 0       0 if ( $status_code =~ /^200/ ) {
1317 0 0 0     0 if ( ! eval { $response = XMLin($page); } ) {
  0 0       0  
1318 0         0 $self->_die("XML PARSING FAILURE: $@");
1319             }
1320             elsif ( exists( $response->{'response'} )
1321             && $response->{'response'} == 1 )
1322             {
1323             ## parse error type error
1324 0         0 warn Dumper( $response, $self->server_request );
1325 0         0 $self->error_message( $response->{'message'} );
1326 0         0 return;
1327             }
1328             else {
1329             $self->error_message(
1330 0         0 $response->{'batchResponse'}->{'message'} );
1331             }
1332             }
1333             else {
1334 0         0 $self->_die("CONNECTION FAILURE: $status_code");
1335             }
1336 0         0 $self->{_response} = $response;
1337              
1338             ##parse out the batch info as our general status
1339 0         0 my $resp = $response->{'batchResponse'};
1340 0         0 $self->order_number( $resp->{'litleSessionId'} );
1341 0         0 $self->result_code( $response->{'response'} );
1342 0 0       0 $self->is_success( $response->{'response'} eq '0' ? 1 : 0 );
1343              
1344 0 0       0 warn Dumper($response) if $DEBUG;
1345 0 0       0 unless ( $self->is_success() ) {
1346 0 0       0 unless ( $self->error_message() ) {
1347             $self->error_message(
1348             "(HTTPS response: $status_code) "
1349             . "(HTTPS headers: "
1350             . join( ", ",
1351 0         0 map { "$_ => " . $headers{$_} } keys %headers )
  0         0  
1352             . ") "
1353             . "(Raw HTTPS content: $page)"
1354             );
1355             }
1356             }
1357 0 0       0 if ( $self->is_success() ) {
1358 0         0 $self->{'batch_response'} = $resp;
1359             }
1360             }
1361              
1362             }
1363              
1364              
1365             sub send_rfr {
1366 0     0 1 0 my ( $self, $args ) = @_;
1367              
1368 0         0 local $SCRUBBER=1;
1369 0         0 $self->_litle_init($args);
1370              
1371 0         0 my $post_data;
1372 0         0 my $writer = XML::Writer->new(
1373             OUTPUT => \$post_data,
1374             DATA_MODE => 1,
1375             DATA_INDENT => 2,
1376             ENCODING => 'utf-8',
1377             );
1378             ## set the authentication data
1379 0         0 tie my %authentication, 'Tie::IxHash',
1380             $self->_revmap_fields(
1381             content => $args,
1382             user => 'login',
1383             password => 'password',
1384             );
1385              
1386             ## Start the XML Document, parent tag
1387 0         0 $writer->xmlDecl();
1388 0         0 $writer->startTag(
1389             "litleRequest",
1390             version => $self->batch_api_version,
1391             xmlns => $self->xmlns,
1392             numBatchRequests => 0,
1393             );
1394              
1395             ## authentication
1396 0         0 $self->_xmlwrite( $writer, 'authentication', \%authentication );
1397             ## batch Request tag
1398 0         0 $writer->startTag('RFRRequest');
1399 0         0 $writer->startTag('accountUpdateFileRequestData');
1400 0         0 $writer->startTag('merchantId');
1401 0         0 $writer->characters( $args->{'merchantid'} );
1402 0         0 $writer->endTag('merchantId');
1403 0         0 $writer->startTag('postDay');
1404 0         0 $writer->characters( $args->{'date'} );
1405 0         0 $writer->endTag('postDay');
1406 0         0 $writer->endTag('accountUpdateFileRequestData');
1407 0         0 $writer->endTag("RFRRequest");
1408 0         0 $writer->endTag("litleRequest");
1409 0         0 $writer->end();
1410             ## END XML Generation
1411             #
1412 0         0 $self->port('15000');
1413 0         0 $self->path('/');
1414 0         0 my ( $page, $status_code, %headers ) = $self->https_post($post_data);
1415              
1416 0         0 $self->server_request( $post_data );
1417 0         0 $self->server_response( $page );
1418 0 0       0 warn $self->server_request if $DEBUG;
1419              
1420 0 0       0 warn Dumper [ $page, $status_code, \%headers ] if $DEBUG;
1421              
1422 0         0 my $response = {};
1423 0 0       0 if ( $status_code =~ /^200/ ) {
1424 0 0 0     0 if ( ! eval { $response = XMLin($page); } ) {
  0 0       0  
1425 0         0 die "XML PARSING FAILURE: $@";
1426             }
1427             elsif ( exists( $response->{'response'} ) && $response->{'response'} == 1 )
1428             {
1429             ## parse error type error
1430 0         0 warn Dumper( $response, $self->server_request );
1431 0         0 $self->error_message( $response->{'message'} );
1432 0         0 return;
1433             }
1434             else {
1435 0         0 $self->error_message( $response->{'RFRResponse'}->{'message'} );
1436             }
1437             }
1438             else {
1439 0         0 die "CONNECTION FAILURE: $status_code";
1440             }
1441 0         0 $self->{_response} = $response;
1442 0 0       0 if ( $response->{'RFRResponse'} ) {
1443             ## litle returns an 'error' if the file is not done. So it's not ready yet.
1444 0         0 $self->result_code( $response->{'RFRResponse'}->{'response'} );
1445 0         0 return;
1446             }
1447             else {
1448              
1449             #if processed, it returns as a batch, so, success, and let get the details
1450 0         0 my $resp = $response->{'batchResponse'};
1451 0 0       0 $self->is_success( $resp->{'response'} eq '000' ? 1 : 0 );
1452 0         0 $self->{'batch_response'} = $resp;
1453 0         0 $self->_parse_batch_response;
1454             }
1455             }
1456              
1457             sub _sftp_connect {
1458 0     0   0 my ($self,$args,$dir) = @_;
1459 0 0       0 $self->_die("Missing ftp_username") if ! $args->{'ftp_username'};
1460 0 0       0 $self->_die("Missing ftp_password") if ! $args->{'ftp_password'};
1461 0         0 require Net::SFTP::Foreign;
1462             my $sftp = Net::SFTP::Foreign->new(
1463             $self->server(),
1464             timeout => $args->{'ftp_timeout'} || 90,
1465             stderr_discard => 1,
1466             user => $args->{'ftp_username'},
1467 0   0     0 password => $args->{'ftp_password'},
1468             );
1469 0 0       0 $sftp->error and $self->_die("SSH connection failed: " . $sftp->error);
1470              
1471 0 0       0 if ($dir) {
1472 0 0       0 $sftp->setcwd($dir)
1473             or $self->_die("Cannot change working directory ", $sftp->error);
1474             }
1475              
1476 0         0 return $sftp;
1477             }
1478              
1479             sub _die {
1480 0     0   0 my $self = shift;
1481 0         0 my $msg = join '', @_;
1482 0         0 $self->is_success(0);
1483 0         0 $self->error_message( $msg );
1484 0         0 die $msg."\n";
1485             }
1486              
1487              
1488             sub retrieve_batch_list {
1489 0     0 1 0 my ($self, %opts ) = @_;
1490              
1491 0         0 local $SCRUBBER=1;
1492 0         0 $self->_litle_init(\%opts);
1493              
1494 0         0 my $sftp = $self->_sftp_connect(\%opts,'outbound');
1495              
1496 0 0       0 my $ls = $sftp->ls( wanted => qr/\.asc$/ )
1497             or $self->_die("Cannot get directory listing ", $sftp->error);
1498              
1499 0         0 my @filenames = map {$_->{'filename'}} @{ $ls };
  0         0  
  0         0  
1500 0         0 $self->is_success(1);
1501 0         0 return \@filenames;
1502             }
1503              
1504              
1505             sub retrieve_batch_delete {
1506 0     0 1 0 my ( $self, %opts ) = @_;
1507              
1508 0         0 local $SCRUBBER=1;
1509 0         0 $self->_litle_init(\%opts);
1510              
1511 0 0       0 $self->_die("Missing batch_id") if !$opts{'batch_id'};
1512              
1513 0         0 my $sftp = $self->_sftp_connect(\%opts,'outbound');
1514              
1515 0         0 my $filename = $opts{'batch_id'};
1516 0 0       0 $sftp->remove( $filename )
1517             or $self->_die("Cannot delete $filename: ", $sftp->error);
1518              
1519 0         0 $self->is_success(1);
1520             }
1521              
1522              
1523             sub retrieve_batch {
1524 0     0 1 0 my ( $self, %opts ) = @_;
1525              
1526 0         0 local $SCRUBBER=1;
1527 0         0 $self->_litle_init(\%opts);
1528              
1529 0 0       0 $self->_die("Missing batch_id") if !$opts{'batch_id'};
1530              
1531 0         0 my $post_data;
1532 0 0       0 if ( $opts{'batch_return'} ) {
1533             ## passed in data structure
1534 0         0 $post_data = $opts{'batch_return'};
1535 0         0 $self->server_request('Data was provided using batch_return option');
1536             }
1537             else {
1538             ## go download a batch
1539 0         0 my $sftp = $self->_sftp_connect(\%opts,'outbound');
1540              
1541 0         0 my $filename = $opts{'batch_id'};
1542 0         0 $self->server_request('SFTP requesting file: '.$filename,1);
1543 0 0       0 $post_data = $sftp->get_content( $filename )
1544             or $self->_die("Cannot GET $filename", $sftp->error);
1545             }
1546 0         0 $self->server_response_dangerous($post_data,1);
1547 0         0 $self->server_response('Litle scrubber not initialized yet, see server_response_dangerous for a copy of the server response. Please note it may contain data that is not appropriate to store.',1);
1548              
1549 0         0 my $response = {};
1550 0 0 0     0 if ( ! eval { $response = XMLin($post_data,
  0 0       0  
1551             ForceArray => [ 'accountUpdateResponse' ],
1552             KeyAttr => '-id',
1553             ); } ) {
1554 0         0 $self->_die("XML PARSING FAILURE: $@");
1555             }
1556             elsif ( exists( $response->{'response'} ) && $response->{'response'} == 1 ) {
1557             ## parse error type error
1558 0         0 warn Dumper( $response, $self->{'_post_data'} );
1559 0   0     0 $self->_die($response->{'message'} || 'No reason given');
1560             }
1561             else {
1562             ## update the status
1563 0         0 $self->error_message( $response->{'batchResponse'}->{'message'} );
1564             }
1565              
1566 0         0 $self->{_response} = $response;
1567 0         0 my $resp = $response->{'batchResponse'};
1568 0         0 $self->order_number( $resp->{'litleSessionId'} );
1569 0         0 $self->result_code( $response->{'response'} );
1570 0 0       0 $self->is_success( $response->{'response'} eq '0' ? 1 : 0 );
1571 0 0       0 if ( $self->is_success() ) {
1572 0         0 $self->{'batch_response'} = $resp;
1573 0         0 return $self->_parse_batch_response;
1574             }
1575             }
1576              
1577             sub _get_update_response {
1578 0     0   0 my $self = shift;
1579 0         0 require Business::OnlinePayment::Litle::UpdaterResponse;
1580 0         0 my @response;
1581 0         0 foreach
1582 0         0 my $item ( @{ $self->{'batch_response'}->{'accountUpdateResponse'} } )
1583             {
1584 0         0 push @response,
1585             Business::OnlinePayment::Litle::UpdaterResponse->new( $item );
1586             }
1587 0         0 return \@response;
1588             }
1589              
1590             sub _revmap_fields {
1591 160     160   264 my $self = shift;
1592 160         461 tie my (%map), 'Tie::IxHash', @_;
1593 160         22607 my %content;
1594 160 50 33     518 if ( $map{'content'} && ref( $map{'content'} ) eq 'HASH' ) {
1595 160         2046 %content = %{ delete( $map{'content'} ) };
  160         454  
1596             }
1597             else {
1598 0         0 warn "WARNING: This content has not been pre-processed with map_fields ";
1599 0         0 %content = $self->content();
1600             }
1601              
1602             map {
1603 160         7093 my $value;
  921         5452  
1604 921 100       2316 if ( ref( $map{$_} ) eq 'HASH' ) {
    100          
    50          
    100          
1605 106 100       654 $value = $map{$_} if ( keys %{ $map{$_} } );
  106         559  
1606             }
1607             elsif ( ref( $map{$_} ) eq 'ARRAY' ) {
1608 6         60 $value = $map{$_};
1609             }
1610             elsif ( ref( $map{$_} ) ) {
1611 0         0 $value = ${ $map{$_} };
  0         0  
1612             }
1613             elsif ( exists( $content{ $map{$_} } ) ) {
1614 348         6747 $value = $content{ $map{$_} };
1615             }
1616              
1617 921 100       14029 if ( defined($value) ) {
1618 399         1311 ( $_ => $value );
1619             }
1620             else {
1621 522         1738 ();
1622             }
1623             } ( keys %map );
1624             }
1625              
1626             sub _xmlwrite {
1627 344     344   1213 my ( $self, $writer, $item, $value ) = @_;
1628 344 100       2805 if ( ref($value) eq 'HASH' ) {
    100          
1629 60 50       220 my $attr = $value->{'attr'} ? $value->{'attr'} : {};
1630 60         471 $writer->startTag( $item, %{$attr} );
  60         255  
1631 60         4073 foreach ( keys(%$value) ) {
1632 264 50       8019 next if $_ eq 'attr';
1633 264         911 $self->_xmlwrite( $writer, $_, $value->{$_} );
1634             }
1635 60         1990 $writer->endTag($item);
1636             }
1637             elsif ( ref($value) eq 'ARRAY' ) {
1638 5         14 foreach ( @{$value} ) {
  5         17  
1639 10         211 $self->_xmlwrite( $writer, $item, $_ );
1640             }
1641             }
1642             else {
1643 279         894 $writer->startTag($item);
1644 279         17925 $writer->characters($value);
1645 279         7573 $writer->endTag($item);
1646             }
1647             }
1648              
1649             sub _default_scrubber {
1650 9     9   859 my $cc = shift;
1651 9         42 my $del = substr($cc,0,6).('X'x(length($cc)-10)).substr($cc,-4,4); # show first 6 and last 4
1652 9         50 return $del;
1653             }
1654              
1655             sub _litle_scrubber_add_card {
1656 7     7   95 my ( $self, $cc ) = @_;
1657 7 100       24 return if ! $cc;
1658 5         14 my $scrubber = $self->{_scrubber};
1659 5         12 scrubber_add_scrubber({$cc=>&{$scrubber}($cc)});
  5         12  
1660             }
1661              
1662             sub _litle_init {
1663 6     6   17 my ( $self, $opts ) = @_;
1664              
1665             # initialize/reset the reporting methods
1666 6         126 $self->is_success(0);
1667 6         61 $self->server_request('');
1668 6         25 $self->server_response('');
1669 6         117 $self->error_message('');
1670              
1671             # some calls are passed via the content method, others are direct arguments... this way we cover both
1672 6         58 my %content = $self->content();
1673 6         166 foreach my $ptr (\%content,$opts) {
1674 12 100       70 next if ! $ptr;
1675             scrubber_init({
1676             quotemeta($ptr->{'password'}||'')=>'DELETED',
1677             quotemeta($ptr->{'ftp_password'}||'')=>'DELETED',
1678 6 50 50     87 ($ptr->{'cvv2'} ? '(?<=[^\d])'.quotemeta($ptr->{'cvv2'}).'(?=[^\d])' : '')=>'DELETED',
      50        
1679             });
1680 6         1601 $self->_litle_scrubber_add_card($ptr->{'card_number'});
1681             }
1682             }
1683              
1684              
1685             sub chargeback_activity_request {
1686 0     0 1   my ( $self ) = @_;
1687              
1688 0           local $SCRUBBER=1;
1689 0           $self->_litle_init;
1690              
1691 0           my $post_data;
1692 0           my %content = $self->content();
1693              
1694             ## activity_date
1695             ## Type = Date; Format = YYYY-MM-DD
1696 0 0 0       if ( ! $content{'activity_date'} || $content{'activity_date'} !~ m/^\d{4}-(\d{2})-(\d{2})$/ || $1 > 12 || $2 > 31) {
      0        
      0        
1697 0   0       $self->_die("Invalid Date Pattern, YYYY-MM-DD required:" . ( $content{'activity_date'} || 'undef'));
1698             }
1699             #
1700             ## financials only [true,false]
1701             # The financialOnly element is an optional child of the litleChargebackActivitiesRequest element.
1702             # You use this flag in combination with the activityDate element to specify a request for chargeback financial activities that occurred on the specified date.
1703             # A value of true returns only activities that had financial impact on the specified date.
1704             # A value of false returns all activities on the specified date.
1705             #Type = Boolean; Valid Values = true or false
1706 0           my $financials;
1707 0 0         if ( defined( $content{'financial_only'} ) ) {
1708 0 0         $financials = $content{'financial_only'} ? 'true' : 'false';
1709             }
1710             else {
1711 0           $financials = 'false';
1712             }
1713              
1714 0           my $writer = XML::Writer->new(
1715             OUTPUT => \$post_data,
1716             DATA_MODE => 1,
1717             DATA_INDENT => 2,
1718             ENCODING => 'utf-8',
1719             );
1720             ## set the authentication data
1721 0           tie my %authentication, 'Tie::IxHash',
1722             $self->_revmap_fields(
1723             content => \%content,
1724             user => 'login',
1725             password => 'password',
1726             );
1727              
1728             ## Start the XML Document, parent tag
1729 0           $writer->xmlDecl();
1730 0           $writer->startTag(
1731             "litleChargebackActivitiesRequest",
1732             version => $self->chargeback_api_version,
1733             xmlns => $self->xmlns,
1734             );
1735              
1736             ## authentication
1737 0           $self->_xmlwrite( $writer, 'authentication', \%authentication );
1738             ## batch Request tag
1739 0           $writer->startTag('activityDate');
1740 0           $writer->characters( $content{'activity_date'} );
1741 0           $writer->endTag('activityDate');
1742 0           $writer->startTag('financialOnly');
1743 0           $writer->characters($financials);
1744 0           $writer->endTag('financialOnly');
1745 0           $writer->endTag("litleChargebackActivitiesRequest");
1746 0           $writer->end();
1747             ## END XML Generation
1748              
1749 0           $self->{'_post_data'} = $post_data;
1750 0 0         warn $self->{'_post_data'} if $DEBUG;
1751             #my ( $page, $status_code, %headers ) = $self->https_post( { 'Content-Type' => 'text/xml; charset=utf-8' } , $post_data);
1752 0           my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'/'.$self->chargeback_path;
1753 0           my $tiny_response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('POST', $url, {
1754             headers => { 'Content-Type' => 'text/xml; charset=utf-8', },
1755             content => $post_data,
1756             } );
1757              
1758 0           my $page = $tiny_response->{'content'};
1759 0           $self->server_request( $post_data );
1760 0           $self->server_response( $page );
1761 0           my $status_code = $tiny_response->{'status'};
1762 0           my %headers = %{$tiny_response->{'headers'}};
  0            
1763              
1764 0 0         warn Dumper $page, $status_code, \%headers if $DEBUG;
1765              
1766 0           my $response = {};
1767 0 0         if ( $status_code =~ /^200/ ) {
1768             ## Failed to parse
1769 0 0 0       if ( !eval { $response = XMLin($page,
  0 0          
1770             ForceArray => [ 'caseActivity' ],
1771             ); } ) {
1772 0           $self->_die("XML PARSING FAILURE: $@, $page");
1773             } ## well-formed failure message
1774             elsif ( exists( $response->{'response'} )
1775             && $response->{'response'} == 1 )
1776             {
1777             ## parse error type error
1778 0           warn Dumper( $response, $self->{'_post_data'} );
1779 0           $self->error_message( $response->{'message'} );
1780 0           return;
1781             } ## success message
1782             else {
1783             $self->error_message(
1784 0           $response->{'litleChargebackActivitiesResponse'}->{'message'} );
1785             }
1786             }
1787             else {
1788 0           $status_code =~ s/[\r\n\s]+$//
1789             ; # remove newline so you can see the error in a linux console
1790 0 0         if ( $status_code =~ /^(?:900|599)/ ) {
1791 0           $status_code .= ' - verify Litle has whitelisted your IP';
1792             }
1793 0           $self->_die("CONNECTION FAILURE: $status_code");
1794             }
1795 0           $self->{_response} = $response;
1796              
1797 0           my @response_list;
1798 0           require Business::OnlinePayment::Litle::ChargebackActivityResponse;
1799 0           foreach my $case ( @{ $response->{caseActivity} } ) {
  0            
1800 0           push @response_list,
1801             Business::OnlinePayment::Litle::ChargebackActivityResponse->new($case);
1802             }
1803              
1804 0 0         warn Dumper($response) if $DEBUG;
1805 0           $self->is_success(1);
1806 0           return \@response_list;
1807             }
1808              
1809              
1810             sub chargeback_update_request {
1811 0     0 1   my ( $self ) = @_;
1812              
1813 0           local $SCRUBBER=1;
1814 0           $self->_litle_init;
1815              
1816 0           my $post_data;
1817 0           my %content = $self->content();
1818              
1819 0           foreach my $key (qw(case_id merchant_activity_id activity )) {
1820             ## case_id
1821             ## merchant_activity_id
1822             ## activity
1823 0 0         croak "Missing arg $key" unless $content{$key};
1824             }
1825              
1826 0           my $writer = XML::Writer->new(
1827             OUTPUT => \$post_data,
1828             DATA_MODE => 1,
1829             DATA_INDENT => 2,
1830             ENCODING => 'utf-8',
1831             );
1832             ## set the authentication data
1833 0           tie my %authentication, 'Tie::IxHash',
1834             $self->_revmap_fields(
1835             content => \%content,
1836             user => 'login',
1837             password => 'password',
1838             );
1839              
1840             ## Start the XML Document, parent tag
1841 0           $writer->xmlDecl();
1842 0           $writer->startTag(
1843             "litleChargebackUpdateRequest",
1844             version => $self->chargeback_api_version,
1845             xmlns => $self->xmlns,
1846             );
1847              
1848             ## authentication
1849 0           $self->_xmlwrite( $writer, 'authentication', \%authentication );
1850 0           $writer->startTag('caseUpdate');
1851 0           $writer->startTag('caseId');
1852 0           $writer->characters( $content{'case_id'} );
1853 0           $writer->endTag('caseId');
1854              
1855 0           $writer->startTag('merchantActivityId');
1856 0           $writer->characters( $content{'merchant_activity_id'} );
1857 0           $writer->endTag('merchantActivityId');
1858              
1859 0           $writer->startTag('activity');
1860 0           $writer->characters( $content{'activity'} );
1861 0           $writer->endTag('activity');
1862              
1863 0           $writer->endTag('caseUpdate');
1864 0           $writer->endTag("litleChargebackUpdateRequest");
1865 0           $writer->end();
1866             ## END XML Generation
1867              
1868 0           $self->{'_post_data'} = $post_data;
1869 0 0         warn $self->{'_post_data'} if $DEBUG;
1870             #my ( $page, $status_code, %headers ) = $self->https_post($post_data);
1871 0           my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'/'.$self->chargeback_path;
1872 0           my $tiny_response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('POST', $url, {
1873             headers => { 'Content-Type' => 'text/xml; charset=utf-8', },
1874             content => $post_data,
1875             } );
1876              
1877 0           my $page = $tiny_response->{'content'};
1878 0           $self->server_response( $page );
1879 0           my $status_code = $tiny_response->{'status'};
1880 0           my %headers = %{$tiny_response->{'headers'}};
  0            
1881              
1882 0 0         warn Dumper $page, $status_code, \%headers if $DEBUG;
1883              
1884 0           my $response = {};
1885 0 0         if ( $status_code =~ /^200/ ) {
1886             ## Failed to parse
1887 0 0         if ( !eval { $response = XMLin($page); } ) {
  0            
1888 0           die "XML PARSING FAILURE: $@, $page";
1889             } ## well-formed failure message
1890 0           $self->{_response} = $response;
1891 0 0         if ( exists( $response->{'response'} ) ) {
1892             ## parse error type error
1893 0           warn Dumper( $response, $self->{'_post_data'} );
1894 0           $self->result_code( $response->{'response'} ); # 0 - success, 1 invalid xml
1895 0           $self->error_message( $response->{'message'} );
1896 0           $self->phoenixTxnId( $response->{'caseUpdateResponse'}{'phoenixTxnId'} );
1897 0           $self->is_success(1);
1898 0           return $response->{'caseUpdateResponse'}{'phoenixTxnId'};
1899             }
1900             else {
1901 0           die "UNKNOWN XML RESULT: $page";
1902             }
1903             }
1904             else {
1905 0           $status_code =~ s/[\r\n\s]+$//
1906             ; # remove newline so you can see the error in a linux console
1907 0 0         if ( $status_code =~ /^(?:900|599)/ ) {
1908 0           $status_code .= ' - verify Litle has whitelisted your IP';
1909             }
1910 0           die "CONNECTION FAILURE: $status_code";
1911             }
1912             }
1913              
1914              
1915              
1916             1; # End of Business::OnlinePayment::Litle
1917              
1918             __END__