line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Business::OnlinePayment::Litle; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
|
4
|
4
|
|
|
4
|
|
29124
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
117
|
|
5
|
4
|
|
|
4
|
|
20
|
use strict; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
67
|
|
6
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
438
|
use Business::OnlinePayment; |
|
4
|
|
|
|
|
2547
|
|
|
4
|
|
|
|
|
78
|
|
8
|
4
|
|
|
4
|
|
1570
|
use Business::OnlinePayment::HTTPS; |
|
4
|
|
|
|
|
69079
|
|
|
4
|
|
|
|
|
163
|
|
9
|
4
|
|
|
4
|
|
1817
|
use Business::OnlinePayment::Litle::ErrorCodes '%ERRORS'; |
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
401
|
|
10
|
4
|
|
|
4
|
|
29
|
use vars qw(@ISA $me $DEBUG); |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
203
|
|
11
|
4
|
|
|
4
|
|
1687
|
use MIME::Base64; |
|
4
|
|
|
|
|
1995
|
|
|
4
|
|
|
|
|
198
|
|
12
|
4
|
|
|
4
|
|
2306
|
use HTTP::Tiny; |
|
4
|
|
|
|
|
128420
|
|
|
4
|
|
|
|
|
137
|
|
13
|
4
|
|
|
4
|
|
2142
|
use XML::Writer; |
|
4
|
|
|
|
|
21593
|
|
|
4
|
|
|
|
|
109
|
|
14
|
4
|
|
|
4
|
|
2711
|
use XML::Simple; |
|
4
|
|
|
|
|
25978
|
|
|
4
|
|
|
|
|
31
|
|
15
|
4
|
|
|
4
|
|
282
|
use Tie::IxHash; |
|
4
|
|
|
|
|
11
|
|
|
4
|
|
|
|
|
103
|
|
16
|
4
|
|
|
4
|
|
1825
|
use Business::CreditCard qw(cardtype); |
|
4
|
|
|
|
|
5704
|
|
|
4
|
|
|
|
|
240
|
|
17
|
4
|
|
|
4
|
|
1574
|
use Data::Dumper; |
|
4
|
|
|
|
|
13806
|
|
|
4
|
|
|
|
|
219
|
|
18
|
4
|
|
|
4
|
|
1740
|
use IO::String; |
|
4
|
|
|
|
|
7383
|
|
|
4
|
|
|
|
|
123
|
|
19
|
4
|
|
|
4
|
|
78
|
use Carp qw(croak); |
|
4
|
|
|
|
|
31
|
|
|
4
|
|
|
|
|
216
|
|
20
|
4
|
|
|
4
|
|
1408
|
use Log::Scrubber qw(disable $SCRUBBER scrubber :Carp scrubber_add_scrubber); |
|
4
|
|
|
|
|
10749
|
|
|
4
|
|
|
|
|
25
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
@ISA = qw(Business::OnlinePayment::HTTPS); |
23
|
|
|
|
|
|
|
$me = 'Business::OnlinePayment::Litle'; |
24
|
|
|
|
|
|
|
$DEBUG = 0; |
25
|
|
|
|
|
|
|
our $VERSION = '0.957'; # 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
|
43
|
my ( $self, $val, $tf ) = @_; |
34
|
11
|
100
|
|
|
|
127
|
if ($val) { |
35
|
5
|
|
|
|
|
36
|
$self->{server_request} = scrubber $val; |
36
|
5
|
50
|
|
|
|
709
|
$self->server_request_dangerous($val,1) unless $tf; |
37
|
|
|
|
|
|
|
} |
38
|
11
|
|
|
|
|
29
|
return $self->{server_request}; |
39
|
|
|
|
|
|
|
} |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub server_request_dangerous { |
43
|
5
|
|
|
5
|
1
|
18
|
my ( $self, $val, $tf ) = @_; |
44
|
5
|
50
|
|
|
|
17
|
if ($val) { |
45
|
5
|
|
|
|
|
12
|
$self->{server_request_dangerous} = $val; |
46
|
5
|
50
|
|
|
|
20
|
$self->server_request($val,1) unless $tf; |
47
|
|
|
|
|
|
|
} |
48
|
5
|
|
|
|
|
13
|
return $self->{server_request_dangerous}; |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub server_response { |
53
|
11
|
|
|
11
|
1
|
39
|
my ( $self, $val, $tf ) = @_; |
54
|
11
|
100
|
|
|
|
43
|
if ($val) { |
55
|
5
|
|
|
|
|
28
|
$self->{server_response} = scrubber $val; |
56
|
5
|
50
|
|
|
|
589
|
$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
|
19
|
my ( $self, $val, $tf ) = @_; |
64
|
5
|
50
|
|
|
|
19
|
if ($val) { |
65
|
5
|
|
|
|
|
17
|
$self->{server_response_dangerous} = $val; |
66
|
5
|
50
|
|
|
|
22
|
$self->server_response($val,1) unless $tf; |
67
|
|
|
|
|
|
|
} |
68
|
5
|
|
|
|
|
34
|
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
|
6987
|
my $self = shift; |
96
|
8
|
|
|
|
|
29
|
my %opts = @_; |
97
|
|
|
|
|
|
|
|
98
|
8
|
|
|
|
|
48
|
$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
|
|
|
|
|
3235
|
$self->test_transaction(0); |
107
|
|
|
|
|
|
|
|
108
|
8
|
50
|
|
|
|
27
|
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
|
|
|
|
|
19
|
my %_defaults = (); |
115
|
8
|
|
|
|
|
25
|
foreach my $key ( keys %opts ) { |
116
|
8
|
50
|
|
|
|
35
|
$key =~ /^default_(\w*)$/ or next; |
117
|
8
|
|
|
|
|
31
|
$_defaults{$1} = $opts{$key}; |
118
|
8
|
|
|
|
|
21
|
delete $opts{$key}; |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
8
|
|
|
|
|
31
|
$self->{_scrubber} = \&_default_scrubber; |
122
|
8
|
100
|
|
|
|
24
|
if( defined $_defaults{'Scrubber'} ) { |
123
|
2
|
|
|
|
|
5
|
my $code = $_defaults{'Scrubber'}; |
124
|
2
|
100
|
|
|
|
6
|
if( ref($code) ne 'CODE' ) { |
125
|
1
|
|
|
|
|
33
|
warn('default_Scrubber is not a code ref'); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
else { |
128
|
1
|
|
|
|
|
2
|
$self->{_scrubber} = $code; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
} |
131
|
|
|
|
|
|
|
|
132
|
8
|
50
|
|
|
|
140
|
$self->api_version('8.1') unless $self->api_version; |
133
|
8
|
50
|
|
|
|
332
|
$self->batch_api_version('8.1') unless $self->batch_api_version; |
134
|
8
|
50
|
|
|
|
346
|
$self->chargeback_api_version('2.2') unless $self->chargeback_api_version; |
135
|
8
|
50
|
|
|
|
358
|
$self->xmlns('http://www.litle.com/schema') unless $self->xmlns; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
sub test_transaction { |
140
|
15
|
|
|
15
|
1
|
11249
|
my $self = shift; |
141
|
15
|
|
|
|
|
29
|
my $testMode = shift; |
142
|
15
|
50
|
0
|
|
|
48
|
if (! defined $testMode) { $testMode = $self->{'test_transaction'} || 0; } |
|
0
|
|
|
|
|
0
|
|
143
|
|
|
|
|
|
|
|
144
|
15
|
100
|
|
|
|
86
|
if (lc($testMode) eq 'sandbox') { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
145
|
6
|
|
|
|
|
17
|
$self->{'test_transaction'} = 'sandbox'; |
146
|
6
|
|
|
|
|
124
|
$self->verify_SSL(0); |
147
|
|
|
|
|
|
|
|
148
|
6
|
|
|
|
|
150
|
$self->server('www.testlitle.com'); |
149
|
6
|
|
|
|
|
174
|
$self->port('443'); |
150
|
6
|
|
|
|
|
143
|
$self->path('/sandbox/communicator/online'); |
151
|
|
|
|
|
|
|
|
152
|
6
|
|
|
|
|
155
|
$self->chargeback_server('service-postlive.litle.com'); # no sandbox exists, so fallback to certify |
153
|
6
|
|
|
|
|
148
|
$self->chargeback_port('443'); |
154
|
6
|
|
|
|
|
133
|
$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
|
|
|
|
|
6
|
$self->{'test_transaction'} = $testMode; |
180
|
1
|
|
|
|
|
18
|
$self->verify_SSL(0); |
181
|
|
|
|
|
|
|
|
182
|
1
|
|
|
|
|
23
|
$self->server('payments.vantivpostlive.com'); |
183
|
1
|
|
|
|
|
23
|
$self->port('443'); |
184
|
1
|
|
|
|
|
22
|
$self->path('/vap/communicator/online'); |
185
|
|
|
|
|
|
|
|
186
|
1
|
|
|
|
|
22
|
$self->chargeback_server('services.vantivpostlive.com'); |
187
|
1
|
|
|
|
|
22
|
$self->chargeback_port('443'); |
188
|
1
|
|
|
|
|
28
|
$self->chargeback_path('/services/communicator/chargebacks/webCommunicator'); |
189
|
|
|
|
|
|
|
} else { |
190
|
8
|
|
|
|
|
35
|
$self->{'test_transaction'} = 0; |
191
|
8
|
|
|
|
|
190
|
$self->verify_SSL(1); |
192
|
|
|
|
|
|
|
|
193
|
8
|
|
|
|
|
223
|
$self->server('payments.vantivcnp.com'); |
194
|
8
|
|
|
|
|
200
|
$self->port('443'); |
195
|
8
|
|
|
|
|
490
|
$self->path('/vap/communicator/online'); |
196
|
|
|
|
|
|
|
|
197
|
8
|
|
|
|
|
179
|
$self->chargeback_server('services.vantivcnp.com'); |
198
|
8
|
|
|
|
|
172
|
$self->chargeback_port('443'); |
199
|
8
|
|
|
|
|
176
|
$self->chargeback_path('/services/communicator/chargebacks/webCommunicator'); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
15
|
|
|
|
|
133
|
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
|
|
|
|
|
45
|
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
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
# AVS ONLY |
221
|
|
|
|
|
|
|
# Capture Given |
222
|
|
|
|
|
|
|
# Force Capture |
223
|
|
|
|
|
|
|
# |
224
|
|
|
|
|
|
|
); |
225
|
6
|
|
33
|
|
|
29
|
$content->{'TransactionType'} = $actions{$action} || $action; |
226
|
|
|
|
|
|
|
|
227
|
6
|
|
|
|
|
43
|
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
|
|
|
35
|
|| $content->{'type'} if $content->{'card_number'}; |
240
|
|
|
|
|
|
|
|
241
|
6
|
50
|
33
|
|
|
165
|
if ( $content->{recurring_billing} |
242
|
|
|
|
|
|
|
&& $content->{recurring_billing} eq 'YES' ) |
243
|
|
|
|
|
|
|
{ |
244
|
0
|
|
|
|
|
0
|
$content->{'orderSource'} = 'recurring'; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
else { |
247
|
6
|
|
|
|
|
16
|
$content->{'orderSource'} = 'ecommerce'; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
$content->{'customerType'} = |
250
|
6
|
50
|
|
|
|
20
|
$content->{'orderSource'} eq 'recurring' |
251
|
|
|
|
|
|
|
? 'Existing' |
252
|
|
|
|
|
|
|
: 'New'; # new/Existing |
253
|
|
|
|
|
|
|
|
254
|
6
|
|
|
|
|
15
|
$content->{'deliverytype'} = 'SVC'; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# stuff it back into %content |
257
|
6
|
50
|
33
|
|
|
43
|
if ( $content->{'products'} && ref( $content->{'products'} ) eq 'ARRAY' ) { |
258
|
6
|
|
|
|
|
12
|
my $count = 1; |
259
|
6
|
|
|
|
|
13
|
foreach ( @{ $content->{'products'} } ) { |
|
6
|
|
|
|
|
18
|
|
260
|
12
|
|
|
|
|
28
|
$_->{'itemSequenceNumber'} = $count++; |
261
|
|
|
|
|
|
|
} |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
6
|
50
|
0
|
|
|
26
|
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
|
|
|
|
|
15
|
$content->{'velocity_check'} = 'false'; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
6
|
50
|
0
|
|
|
21
|
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
|
|
|
|
|
13
|
$content->{'partial_auth'} = 'false'; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
6
|
|
|
|
|
12
|
$self->content( %{$content} ); |
|
6
|
|
|
|
|
49
|
|
281
|
6
|
|
|
|
|
424
|
return $content; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub format_misc_field { |
286
|
288
|
|
|
288
|
1
|
511
|
my ($self, $content, $trunc) = @_; |
287
|
|
|
|
|
|
|
|
288
|
288
|
100
|
|
|
|
663
|
if( defined $content->{ $trunc->[0] } ) { |
|
|
50
|
|
|
|
|
|
289
|
232
|
|
|
|
|
636
|
utf8::upgrade($content->{ $trunc->[0] }); |
290
|
232
|
|
|
|
|
482
|
my $len = length( $content->{ $trunc->[0] } ); |
291
|
232
|
50
|
100
|
|
|
1291
|
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
|
|
|
|
|
657
|
$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
|
147
|
my ($self, $data, $field) = @_; |
309
|
78
|
100
|
|
|
|
198
|
if (defined ( $data->{$field} ) ) { |
310
|
54
|
|
|
|
|
324
|
$data->{$field} = sprintf( "%.2f", $data->{$field} ); |
311
|
54
|
|
|
|
|
207
|
$data->{$field} =~ s/\.//g; |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
sub format_phone_field { |
317
|
6
|
|
|
6
|
1
|
15
|
my ($self, $data, $field) = @_; |
318
|
6
|
50
|
|
|
|
21
|
if (defined ( $data->{$field} ) ) { |
319
|
6
|
|
|
|
|
101
|
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
|
|
|
|
27
|
$data->{$field} =~ s/(\D)/$$convertPhone{lc($1)}||''/eg; |
|
12
|
|
|
|
|
118
|
|
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub map_request { |
335
|
6
|
|
|
6
|
1
|
17
|
my ( $self, $content ) = @_; |
336
|
|
|
|
|
|
|
|
337
|
6
|
|
|
|
|
27
|
$self->map_fields($content); |
338
|
|
|
|
|
|
|
|
339
|
6
|
|
|
|
|
15
|
my $action = $content->{'TransactionType'}; |
340
|
|
|
|
|
|
|
|
341
|
6
|
|
|
|
|
19
|
my @required_fields = qw(action type); |
342
|
|
|
|
|
|
|
|
343
|
6
|
|
|
|
|
46
|
$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
|
|
|
|
|
253
|
foreach my $field ( 'amount', 'salesTax', 'discountAmount', 'shippingAmount', 'dutyAmount' ) { |
348
|
30
|
|
|
|
|
72
|
$self->format_amount_field($content, $field); |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
# make sure the date is in MMYY format |
352
|
6
|
|
|
|
|
53
|
$content->{'expiration'} =~ s/^(\d{1,2})\D*\d*?(\d{2})$/$1$2/; |
353
|
|
|
|
|
|
|
|
354
|
6
|
50
|
|
|
|
33
|
if ( ! defined $content->{'description'} ) { $content->{'description'} = ''; } # schema req |
|
0
|
|
|
|
|
0
|
|
355
|
6
|
|
|
|
|
27
|
$content->{'description'} =~ s/[^\w\s\*\,\-\'\#\&\.]//g; |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# Litle pre 0.934 used token, however BOP likes card_token |
358
|
6
|
50
|
66
|
|
|
36
|
$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
|
|
|
|
|
31
|
$self->format_phone_field($content, 'company_phone'); |
362
|
|
|
|
|
|
|
|
363
|
6
|
|
33
|
|
|
47
|
$content->{'invoice_number_length_15'} ||= $content->{'invoice_number'}; # orderId = 25, invoiceReferenceNumber = 15 |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# put in a list of constraints |
366
|
6
|
|
|
|
|
151
|
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
|
|
|
|
|
23
|
foreach my $trunc ( @validate ) { |
414
|
204
|
|
|
|
|
428
|
$self->format_misc_field($content,$trunc); |
415
|
|
|
|
|
|
|
#warn "$trunc->[0] => ".($content->{ $trunc->[0] }||'')."\n" if $DEBUG; |
416
|
|
|
|
|
|
|
} |
417
|
|
|
|
|
|
|
|
418
|
6
|
|
|
|
|
41
|
tie my %billToAddress, 'Tie::IxHash', $self->_revmap_fields( |
419
|
|
|
|
|
|
|
content => $content, |
420
|
|
|
|
|
|
|
name => 'name', |
421
|
|
|
|
|
|
|
email => 'email', |
422
|
|
|
|
|
|
|
addressLine1 => 'address', |
423
|
|
|
|
|
|
|
city => 'city', |
424
|
|
|
|
|
|
|
state => 'state', |
425
|
|
|
|
|
|
|
zip => 'zip', |
426
|
|
|
|
|
|
|
country => 'country' |
427
|
|
|
|
|
|
|
, #TODO: will require validation to the spec, this field wont' work as is |
428
|
|
|
|
|
|
|
phone => 'phone', |
429
|
|
|
|
|
|
|
); |
430
|
|
|
|
|
|
|
|
431
|
6
|
|
|
|
|
598
|
tie my %shipToAddress, 'Tie::IxHash', $self->_revmap_fields( |
432
|
|
|
|
|
|
|
content => $content, |
433
|
|
|
|
|
|
|
name => 'ship_name', |
434
|
|
|
|
|
|
|
email => 'ship_email', |
435
|
|
|
|
|
|
|
addressLine1 => 'ship_address', |
436
|
|
|
|
|
|
|
city => 'ship_city', |
437
|
|
|
|
|
|
|
state => 'ship_state', |
438
|
|
|
|
|
|
|
zip => 'ship_zip', |
439
|
|
|
|
|
|
|
country => 'ship_country' |
440
|
|
|
|
|
|
|
, #TODO: will require validation to the spec, this field wont' work as is |
441
|
|
|
|
|
|
|
phone => 'ship_phone', |
442
|
|
|
|
|
|
|
); |
443
|
|
|
|
|
|
|
|
444
|
6
|
|
|
|
|
514
|
tie my %customerinfo, 'Tie::IxHash', |
445
|
|
|
|
|
|
|
$self->_revmap_fields( |
446
|
|
|
|
|
|
|
content => $content, |
447
|
|
|
|
|
|
|
customerType => 'customerType', |
448
|
|
|
|
|
|
|
); |
449
|
|
|
|
|
|
|
|
450
|
6
|
|
|
|
|
191
|
tie my %custombilling, 'Tie::IxHash', |
451
|
|
|
|
|
|
|
$self->_revmap_fields( |
452
|
|
|
|
|
|
|
content => $content, |
453
|
|
|
|
|
|
|
phone => 'company_phone', |
454
|
|
|
|
|
|
|
descriptor => 'description', |
455
|
|
|
|
|
|
|
); |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
## loop through product list and generate lineItemData for each |
458
|
|
|
|
|
|
|
# |
459
|
6
|
|
|
|
|
311
|
my @products = (); |
460
|
6
|
50
|
33
|
|
|
27
|
if( defined $content->{'products'} && scalar( @{ $content->{'products'} } ) < 100 ){ |
|
6
|
|
|
|
|
30
|
|
461
|
6
|
|
|
|
|
12
|
foreach my $prodOrig ( @{ $content->{'products'} } ) { |
|
6
|
|
|
|
|
21
|
|
462
|
|
|
|
|
|
|
# use a local copy of prod so that we do not have issues if they try to submit more then once. |
463
|
12
|
|
|
|
|
106
|
my %prod = %$prodOrig; |
464
|
12
|
|
|
|
|
33
|
foreach my $field ( 'tax','amount','totalwithtax','discount' ) { |
465
|
|
|
|
|
|
|
# Note: DO NOT format 'cost', it uses the decimal format |
466
|
48
|
|
|
|
|
109
|
$self->format_amount_field(\%prod, $field); |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
|
469
|
12
|
|
|
|
|
82
|
my @validate = ( |
470
|
|
|
|
|
|
|
# field, maxLen, minLen, errorOnLength, isRequired |
471
|
|
|
|
|
|
|
[ 'description', 26, 0, 0, 0 ], |
472
|
|
|
|
|
|
|
[ 'tax', 8, 0, 1, 0 ], |
473
|
|
|
|
|
|
|
[ 'amount', 8, 0, 1, 0 ], |
474
|
|
|
|
|
|
|
[ 'totalwithtax', 8, 0, 1, 0 ], |
475
|
|
|
|
|
|
|
[ 'discount', 8, 0, 1, 0 ], |
476
|
|
|
|
|
|
|
[ 'code', 12, 0, 0, 0 ], |
477
|
|
|
|
|
|
|
[ 'cost', 12, 0, 1, 0 ], |
478
|
|
|
|
|
|
|
); |
479
|
12
|
|
|
|
|
37
|
foreach my $trunc ( @validate ) { $self->format_misc_field(\%prod,$trunc); } |
|
84
|
|
|
|
|
181
|
|
480
|
|
|
|
|
|
|
|
481
|
12
|
|
|
|
|
47
|
tie my %lineitem, 'Tie::IxHash', |
482
|
|
|
|
|
|
|
$self->_revmap_fields( |
483
|
|
|
|
|
|
|
content => \%prod, |
484
|
|
|
|
|
|
|
itemSequenceNumber => 'itemSequenceNumber', |
485
|
|
|
|
|
|
|
itemDescription => 'description', |
486
|
|
|
|
|
|
|
productCode => 'code', |
487
|
|
|
|
|
|
|
quantity => 'quantity', |
488
|
|
|
|
|
|
|
unitOfMeasure => 'units', |
489
|
|
|
|
|
|
|
taxAmount => 'tax', |
490
|
|
|
|
|
|
|
lineItemTotal => 'amount', |
491
|
|
|
|
|
|
|
lineItemTotalWithTax => 'totalwithtax', |
492
|
|
|
|
|
|
|
itemDiscountAmount => 'discount', |
493
|
|
|
|
|
|
|
commodityCode => 'code', |
494
|
|
|
|
|
|
|
unitCost => 'cost', # This "amount" field uses decimals |
495
|
|
|
|
|
|
|
); |
496
|
12
|
|
|
|
|
1709
|
push @products, \%lineitem; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
} |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
# |
501
|
|
|
|
|
|
|
# |
502
|
6
|
|
|
|
|
35
|
tie my %enhanceddata, 'Tie::IxHash', $self->_revmap_fields( |
503
|
|
|
|
|
|
|
content => $content, |
504
|
|
|
|
|
|
|
customerReference => 'po_number', |
505
|
|
|
|
|
|
|
salesTax => 'salestax', |
506
|
|
|
|
|
|
|
discountAmount => 'discount', |
507
|
|
|
|
|
|
|
shippingAmount => 'shipping', |
508
|
|
|
|
|
|
|
dutyAmount => 'duty', |
509
|
|
|
|
|
|
|
invoiceReferenceNumber => 'invoice_number_length_15', |
510
|
|
|
|
|
|
|
orderDate => 'orderdate', |
511
|
|
|
|
|
|
|
lineItemData => \@products, |
512
|
|
|
|
|
|
|
); |
513
|
|
|
|
|
|
|
|
514
|
6
|
|
|
|
|
253
|
tie my %card, 'Tie::IxHash', $self->_revmap_fields( |
515
|
|
|
|
|
|
|
content => $content, |
516
|
|
|
|
|
|
|
type => 'card_type', |
517
|
|
|
|
|
|
|
number => 'card_number', |
518
|
|
|
|
|
|
|
expDate => 'expiration', |
519
|
|
|
|
|
|
|
cardValidationNum => 'cvv2', |
520
|
|
|
|
|
|
|
); |
521
|
|
|
|
|
|
|
|
522
|
6
|
|
|
|
|
341
|
tie my %token, 'Tie::IxHash', $self->_revmap_fields( |
523
|
|
|
|
|
|
|
content => $content, |
524
|
|
|
|
|
|
|
litleToken => 'card_token', |
525
|
|
|
|
|
|
|
expDate => 'expiration', |
526
|
|
|
|
|
|
|
cardValidationNum => 'cvv2', |
527
|
|
|
|
|
|
|
); |
528
|
|
|
|
|
|
|
|
529
|
6
|
|
|
|
|
276
|
tie my %processing, 'Tie::IxHash', $self->_revmap_fields( |
530
|
|
|
|
|
|
|
content => $content, |
531
|
|
|
|
|
|
|
bypassVelocityCheck => 'velocity_check', |
532
|
|
|
|
|
|
|
); |
533
|
|
|
|
|
|
|
|
534
|
6
|
|
|
|
|
204
|
tie my %cardholderauth, 'Tie::IxHash', |
535
|
|
|
|
|
|
|
$self->_revmap_fields( |
536
|
|
|
|
|
|
|
content => $content, |
537
|
|
|
|
|
|
|
authenticationValue => '3ds', |
538
|
|
|
|
|
|
|
authenticationTransactionId => 'visaverified', |
539
|
|
|
|
|
|
|
customerIpAddress => 'ip', |
540
|
|
|
|
|
|
|
authenticatedByMerchant => 'authenticated', |
541
|
|
|
|
|
|
|
); |
542
|
|
|
|
|
|
|
|
543
|
6
|
|
|
|
|
183
|
tie my %merchantdata, 'Tie::IxHash', |
544
|
|
|
|
|
|
|
$self->_revmap_fields( |
545
|
|
|
|
|
|
|
content => $content, |
546
|
|
|
|
|
|
|
affiliate => 'affiliate', |
547
|
|
|
|
|
|
|
merchantGroupingId => 'merchant_grouping_id', |
548
|
|
|
|
|
|
|
); |
549
|
|
|
|
|
|
|
|
550
|
6
|
|
|
|
|
187
|
tie my %recyclingrequest, 'Tie::IxHash', |
551
|
|
|
|
|
|
|
$self->_revmap_fields( |
552
|
|
|
|
|
|
|
content => $content, |
553
|
|
|
|
|
|
|
recycleBy => 'recycle_by', |
554
|
|
|
|
|
|
|
recycleId => 'recycle_id', |
555
|
|
|
|
|
|
|
); |
556
|
|
|
|
|
|
|
|
557
|
6
|
|
|
|
|
250
|
my %req; |
558
|
|
|
|
|
|
|
|
559
|
6
|
50
|
|
|
|
28
|
if ( $action eq 'registerTokenRequest' ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
560
|
0
|
0
|
0
|
|
|
0
|
croak 'missing card_number' if length($content->{'card_number'} || '') == 0; |
561
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', $self->_revmap_fields( |
562
|
|
|
|
|
|
|
content => $content, |
563
|
|
|
|
|
|
|
orderId => 'invoice_number', |
564
|
|
|
|
|
|
|
accountNumber => 'card_number', |
565
|
|
|
|
|
|
|
); |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
elsif ( $action eq 'sale' ) { |
568
|
5
|
100
|
100
|
|
|
252
|
croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0; |
569
|
|
|
|
|
|
|
tie %req, 'Tie::IxHash', $self->_revmap_fields( |
570
|
|
|
|
|
|
|
content => $content, |
571
|
|
|
|
|
|
|
orderId => 'invoice_number', |
572
|
|
|
|
|
|
|
amount => 'amount', |
573
|
|
|
|
|
|
|
orderSource => 'orderSource', |
574
|
|
|
|
|
|
|
billToAddress => \%billToAddress, |
575
|
|
|
|
|
|
|
card => $content->{'card_number'} ? \%card : {}, |
576
|
4
|
100
|
|
|
|
29
|
token => $content->{'card_token'} ? \%token : {}, |
|
|
100
|
|
|
|
|
|
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
#cardholderAuthentication => \%cardholderauth, |
579
|
|
|
|
|
|
|
customBilling => \%custombilling, |
580
|
|
|
|
|
|
|
enhancedData => \%enhanceddata, |
581
|
|
|
|
|
|
|
processingInstructions => \%processing, |
582
|
|
|
|
|
|
|
allowPartialAuth => 'partial_auth', |
583
|
|
|
|
|
|
|
merchantData => \%merchantdata, |
584
|
|
|
|
|
|
|
recyclingRequest => \%recyclingrequest, |
585
|
|
|
|
|
|
|
); |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
elsif ( $action eq 'authorization' ) { |
588
|
1
|
50
|
0
|
|
|
8
|
croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0; |
589
|
|
|
|
|
|
|
tie %req, 'Tie::IxHash', $self->_revmap_fields( |
590
|
|
|
|
|
|
|
content => $content, |
591
|
|
|
|
|
|
|
orderId => 'invoice_number', |
592
|
|
|
|
|
|
|
amount => 'amount', |
593
|
|
|
|
|
|
|
orderSource => 'orderSource', |
594
|
|
|
|
|
|
|
billToAddress => \%billToAddress, |
595
|
|
|
|
|
|
|
card => $content->{'card_number'} ? \%card : {}, |
596
|
1
|
50
|
|
|
|
10
|
token => $content->{'card_token'} ? \%token : {}, |
|
|
50
|
|
|
|
|
|
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
#cardholderAuthentication => \%cardholderauth, |
599
|
|
|
|
|
|
|
processingInstructions => \%processing, |
600
|
|
|
|
|
|
|
customBilling => \%custombilling, |
601
|
|
|
|
|
|
|
allowPartialAuth => 'partial_auth', |
602
|
|
|
|
|
|
|
merchantData => \%merchantdata, |
603
|
|
|
|
|
|
|
recyclingRequest => \%recyclingrequest, |
604
|
|
|
|
|
|
|
); |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
elsif ( $action eq 'capture' ) { |
607
|
0
|
|
|
|
|
0
|
push @required_fields, qw( order_number amount ); |
608
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', |
609
|
|
|
|
|
|
|
$self->_revmap_fields( |
610
|
|
|
|
|
|
|
content => $content, |
611
|
|
|
|
|
|
|
litleTxnId => 'order_number', |
612
|
|
|
|
|
|
|
amount => 'amount', |
613
|
|
|
|
|
|
|
enhancedData => \%enhanceddata, |
614
|
|
|
|
|
|
|
processingInstructions => \%processing, |
615
|
|
|
|
|
|
|
); |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
elsif ( $action eq 'credit' ) { |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
# IF there is a litleTxnId, it's a normal linked credit |
620
|
0
|
0
|
|
|
|
0
|
if( $content->{'order_number'} ){ |
621
|
0
|
|
|
|
|
0
|
push @required_fields, qw( order_number amount ); |
622
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', $self->_revmap_fields( |
623
|
|
|
|
|
|
|
content => $content, |
624
|
|
|
|
|
|
|
litleTxnId => 'order_number', |
625
|
|
|
|
|
|
|
amount => 'amount', |
626
|
|
|
|
|
|
|
customBilling => \%custombilling, |
627
|
|
|
|
|
|
|
processingInstructions => \%processing, |
628
|
|
|
|
|
|
|
); |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
# ELSE it's an unlinked, which requires different data |
631
|
|
|
|
|
|
|
else { |
632
|
0
|
0
|
0
|
|
|
0
|
croak 'missing card_token or card_number' if length($content->{'card_number'} || $content->{'card_token'} || '') == 0; |
633
|
0
|
|
|
|
|
0
|
push @required_fields, qw( invoice_number amount ); |
634
|
|
|
|
|
|
|
tie %req, 'Tie::IxHash', $self->_revmap_fields( |
635
|
|
|
|
|
|
|
content => $content, |
636
|
|
|
|
|
|
|
orderId => 'invoice_number', |
637
|
|
|
|
|
|
|
amount => 'amount', |
638
|
|
|
|
|
|
|
orderSource => 'orderSource', |
639
|
|
|
|
|
|
|
billToAddress => \%billToAddress, |
640
|
|
|
|
|
|
|
card => $content->{'card_number'} ? \%card : {}, |
641
|
0
|
0
|
|
|
|
0
|
token => $content->{'card_token'} ? \%token : {}, |
|
|
0
|
|
|
|
|
|
642
|
|
|
|
|
|
|
customBilling => \%custombilling, |
643
|
|
|
|
|
|
|
processingInstructions => \%processing, |
644
|
|
|
|
|
|
|
); |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
elsif ( $action eq 'void' ) { |
648
|
0
|
|
|
|
|
0
|
push @required_fields, qw( order_number ); |
649
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', |
650
|
|
|
|
|
|
|
$self->_revmap_fields( |
651
|
|
|
|
|
|
|
content => $content, |
652
|
|
|
|
|
|
|
litleTxnId => 'order_number', |
653
|
|
|
|
|
|
|
processingInstructions => \%processing, |
654
|
|
|
|
|
|
|
); |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
elsif ( $action eq 'authReversal' ) { |
657
|
0
|
|
|
|
|
0
|
push @required_fields, qw( order_number amount ); |
658
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', |
659
|
|
|
|
|
|
|
$self->_revmap_fields( |
660
|
|
|
|
|
|
|
content => $content, |
661
|
|
|
|
|
|
|
litleTxnId => 'order_number', |
662
|
|
|
|
|
|
|
amount => 'amount', |
663
|
|
|
|
|
|
|
); |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
elsif ( $action eq 'accountUpdate' ) { |
666
|
0
|
|
|
|
|
0
|
push @required_fields, qw( card_number expiration ); |
667
|
0
|
|
|
|
|
0
|
tie %req, 'Tie::IxHash', |
668
|
|
|
|
|
|
|
$self->_revmap_fields( |
669
|
|
|
|
|
|
|
content => $content, |
670
|
|
|
|
|
|
|
orderId => 'customer_id', |
671
|
|
|
|
|
|
|
card => \%card, |
672
|
|
|
|
|
|
|
); |
673
|
|
|
|
|
|
|
} |
674
|
|
|
|
|
|
|
|
675
|
5
|
|
|
|
|
742
|
$self->required_fields(@required_fields); |
676
|
5
|
|
|
|
|
336
|
return \%req; |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
sub submit { |
680
|
0
|
|
|
0
|
1
|
0
|
my ($self) = @_; |
681
|
|
|
|
|
|
|
|
682
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
683
|
0
|
|
|
|
|
0
|
$self->_litle_init; |
684
|
|
|
|
|
|
|
|
685
|
0
|
|
|
|
|
0
|
my %content = $self->content(); |
686
|
|
|
|
|
|
|
|
687
|
0
|
0
|
|
|
|
0
|
warn 'Pre processing: '.Dumper(\%content) if $DEBUG; |
688
|
0
|
|
|
|
|
0
|
my $req = $self->map_request( \%content ); |
689
|
0
|
0
|
|
|
|
0
|
warn 'Post processing: '.Dumper(\%content) if $DEBUG; |
690
|
0
|
|
|
|
|
0
|
my $post_data; |
691
|
|
|
|
|
|
|
|
692
|
0
|
|
|
|
|
0
|
my $writer = new XML::Writer( |
693
|
|
|
|
|
|
|
OUTPUT => \$post_data, |
694
|
|
|
|
|
|
|
DATA_MODE => 1, |
695
|
|
|
|
|
|
|
DATA_INDENT => 2, |
696
|
|
|
|
|
|
|
ENCODING => 'utf-8', |
697
|
|
|
|
|
|
|
); |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
## set the authentication data |
700
|
0
|
|
|
|
|
0
|
tie my %authentication, 'Tie::IxHash', |
701
|
|
|
|
|
|
|
$self->_revmap_fields( |
702
|
|
|
|
|
|
|
content => \%content, |
703
|
|
|
|
|
|
|
user => 'login', |
704
|
|
|
|
|
|
|
password => 'password', |
705
|
|
|
|
|
|
|
); |
706
|
|
|
|
|
|
|
|
707
|
0
|
0
|
|
|
|
0
|
warn Dumper($req) if $DEBUG; |
708
|
|
|
|
|
|
|
## Start the XML Document, parent tag |
709
|
0
|
|
|
|
|
0
|
$writer->xmlDecl(); |
710
|
|
|
|
|
|
|
$writer->startTag( |
711
|
|
|
|
|
|
|
"litleOnlineRequest", |
712
|
|
|
|
|
|
|
version => $self->api_version, |
713
|
|
|
|
|
|
|
xmlns => $self->xmlns, |
714
|
0
|
|
|
|
|
0
|
merchantId => $content{'merchantid'}, |
715
|
|
|
|
|
|
|
); |
716
|
|
|
|
|
|
|
|
717
|
0
|
|
|
|
|
0
|
$self->_xmlwrite( $writer, 'authentication', \%authentication ); |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
## partial capture modifier, odd location, because it modifies the start tag :( |
720
|
0
|
|
|
|
|
0
|
my %extra; |
721
|
0
|
0
|
|
|
|
0
|
if ($content{'TransactionType'} eq 'capture'){ |
722
|
0
|
0
|
|
|
|
0
|
$extra{'partial'} = $content{'partial'} ? 'true' : 'false'; |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
$writer->startTag( |
726
|
|
|
|
|
|
|
$content{'TransactionType'}, |
727
|
|
|
|
|
|
|
id => $content{'invoice_number'}, |
728
|
|
|
|
|
|
|
reportGroup => $content{'report_group'} || 'BOP', |
729
|
0
|
|
0
|
|
|
0
|
customerId => $content{'customer_id'} || 1, |
|
|
|
0
|
|
|
|
|
730
|
|
|
|
|
|
|
%extra, |
731
|
|
|
|
|
|
|
); |
732
|
0
|
|
|
|
|
0
|
foreach ( keys( %{$req} ) ) { |
|
0
|
|
|
|
|
0
|
|
733
|
0
|
|
|
|
|
0
|
$self->_xmlwrite( $writer, $_, $req->{$_} ); |
734
|
|
|
|
|
|
|
} |
735
|
|
|
|
|
|
|
|
736
|
0
|
|
|
|
|
0
|
$writer->endTag( $content{'TransactionType'} ); |
737
|
0
|
|
|
|
|
0
|
$writer->endTag("litleOnlineRequest"); |
738
|
0
|
|
|
|
|
0
|
$writer->end(); |
739
|
|
|
|
|
|
|
## END XML Generation |
740
|
|
|
|
|
|
|
|
741
|
0
|
|
|
|
|
0
|
$self->server_request( $post_data ); |
742
|
0
|
0
|
|
|
|
0
|
warn $self->server_request if $DEBUG; |
743
|
|
|
|
|
|
|
|
744
|
0
|
0
|
|
|
|
0
|
if ( $] ge '5.008' ) { |
745
|
|
|
|
|
|
|
# http_post expects data in this format |
746
|
0
|
0
|
|
|
|
0
|
utf8::encode($post_data) if utf8::is_utf8($post_data); |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
|
749
|
0
|
|
|
|
|
0
|
my ( $page, $status_code, %headers ) = $self->https_post( { 'Content-Type' => 'text/xml; charset=utf-8' } , $post_data); |
750
|
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
0
|
$self->server_response( $page ); |
752
|
0
|
0
|
|
|
|
0
|
warn Dumper $self->server_response, $status_code, \%headers if $DEBUG; |
753
|
|
|
|
|
|
|
|
754
|
0
|
|
|
|
|
0
|
my $response = $self->_parse_xml_response( $page, $status_code ); |
755
|
|
|
|
|
|
|
|
756
|
0
|
|
|
|
|
0
|
$content{'TransactionType'} =~ s/Request$//; # no clue why some of the types have a Request and some do not |
757
|
|
|
|
|
|
|
|
758
|
0
|
0
|
0
|
|
|
0
|
if ( exists( $response->{'response'} ) && $response->{'response'} == 1 ) { |
759
|
|
|
|
|
|
|
## parse error type error |
760
|
0
|
|
|
|
|
0
|
warn Dumper 'https://'.$self->server.':'.$self->port.$self->path,$response, $self->server_request; |
761
|
0
|
|
|
|
|
0
|
$self->error_message( $response->{'message'} ); |
762
|
0
|
|
|
|
|
0
|
return; |
763
|
|
|
|
|
|
|
} else { |
764
|
|
|
|
|
|
|
$self->error_message( |
765
|
|
|
|
|
|
|
$response->{ $content{'TransactionType'} . 'Response' } |
766
|
0
|
|
|
|
|
0
|
->{'message'} ); |
767
|
|
|
|
|
|
|
} |
768
|
0
|
|
|
|
|
0
|
$self->{_response} = $response; |
769
|
|
|
|
|
|
|
|
770
|
0
|
0
|
|
|
|
0
|
warn Dumper($response) if $DEBUG; |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
## Set up the data: |
773
|
0
|
|
|
|
|
0
|
my $resp = $response->{ $content{'TransactionType'} . 'Response' }; |
774
|
0
|
|
|
|
|
0
|
$self->{_response} = $resp; |
775
|
0
|
|
0
|
|
|
0
|
$self->card_token( $resp->{'litleToken'} || $resp->{'tokenResponse'}->{'litleToken'} || $content{'card_token'} || '' ); |
776
|
0
|
|
0
|
|
|
0
|
$self->order_number( $resp->{'litleTxnId'} || '' ); |
777
|
0
|
|
0
|
|
|
0
|
$self->result_code( $resp->{'response'} || '' ); |
778
|
0
|
0
|
|
|
|
0
|
$resp->{'authCode'} =~ s/\D//g if $resp->{'authCode'}; |
779
|
0
|
|
0
|
|
|
0
|
$self->authorization( $resp->{'authCode'} || '' ); |
780
|
0
|
|
0
|
|
|
0
|
$self->cvv2_response( $resp->{'fraudResult'}->{'cardValidationResult'} |
781
|
|
|
|
|
|
|
|| '' ); |
782
|
0
|
|
0
|
|
|
0
|
$self->avs_code( $resp->{'fraudResult'}->{'avsResult'} || '' ); |
783
|
0
|
0
|
0
|
|
|
0
|
if( $resp->{enhancedAuthResponse} |
|
|
|
0
|
|
|
|
|
784
|
|
|
|
|
|
|
&& $resp->{enhancedAuthResponse}->{fundingSource} |
785
|
|
|
|
|
|
|
&& $resp->{enhancedAuthResponse}->{fundingSource}->{type} eq 'PREPAID' ) { |
786
|
|
|
|
|
|
|
|
787
|
0
|
|
|
|
|
0
|
$self->is_prepaid(1); |
788
|
0
|
|
|
|
|
0
|
$self->prepaid_balance( $resp->{enhancedAuthResponse}->{fundingSource}->{availableBalance} ); |
789
|
|
|
|
|
|
|
} else { |
790
|
0
|
|
|
|
|
0
|
$self->is_prepaid(0); |
791
|
|
|
|
|
|
|
} |
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
#$self->is_dupe( $resp->{'duplicate'} ? 1 : 0 ); |
794
|
0
|
0
|
0
|
|
|
0
|
if( defined $resp->{'duplicate'} && $resp->{'duplicate'} eq 'true' ) { |
795
|
0
|
|
|
|
|
0
|
$self->is_duplicate(1); |
796
|
|
|
|
|
|
|
} |
797
|
|
|
|
|
|
|
else { |
798
|
0
|
|
|
|
|
0
|
$self->is_duplicate(0); |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
|
801
|
0
|
0
|
|
|
|
0
|
if( defined $resp->{tokenResponse} ) { |
802
|
0
|
|
|
|
|
0
|
$self->card_token($resp->{tokenResponse}->{litleToken}); |
803
|
0
|
|
|
|
|
0
|
$self->card_token_response($resp->{tokenResponse}->{tokenResponseCode}); |
804
|
0
|
|
|
|
|
0
|
$self->card_token_message($resp->{tokenResponse}->{tokenMessage}); |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
0
|
0
|
0
|
|
|
0
|
if( $resp->{enhancedAuthResponse} |
808
|
|
|
|
|
|
|
&& $resp->{enhancedAuthResponse}->{affluence} |
809
|
|
|
|
|
|
|
){ |
810
|
0
|
|
|
|
|
0
|
$self->get_affluence( $resp->{enhancedAuthResponse}->{affluence} ); |
811
|
|
|
|
|
|
|
} |
812
|
0
|
0
|
|
|
|
0
|
$self->is_success( $self->result_code() eq '000' ? 1 : 0 ); |
813
|
0
|
0
|
0
|
|
|
0
|
if( |
|
|
|
0
|
|
|
|
|
814
|
|
|
|
|
|
|
$self->result_code() eq '010' # Partial approval, if they chose that option |
815
|
|
|
|
|
|
|
|| ($self->result_code() eq '802' && $self->card_token) # Card is already a token |
816
|
|
|
|
|
|
|
) { |
817
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
##Failure Status for 3.0 users |
821
|
0
|
0
|
|
|
|
0
|
if ( !$self->is_success ) { |
822
|
|
|
|
|
|
|
my $f_status = |
823
|
|
|
|
|
|
|
$ERRORS{ $self->result_code }->{'failure'} |
824
|
0
|
0
|
|
|
|
0
|
? $ERRORS{ $self->result_code }->{'failure'} |
825
|
|
|
|
|
|
|
: 'decline'; |
826
|
0
|
|
|
|
|
0
|
$self->failure_status($f_status); |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
|
829
|
0
|
0
|
|
|
|
0
|
unless ( $self->is_success() ) { |
830
|
0
|
0
|
|
|
|
0
|
unless ( $self->error_message() ) { |
831
|
|
|
|
|
|
|
$self->error_message( "(HTTPS response: $status_code) " |
832
|
|
|
|
|
|
|
. "(HTTPS headers: " |
833
|
0
|
|
|
|
|
0
|
. join( ", ", map { "$_ => " . $headers{$_} } keys %headers ) |
|
0
|
|
|
|
|
0
|
|
834
|
|
|
|
|
|
|
. ") " |
835
|
|
|
|
|
|
|
. "(Raw HTTPS content: ".$self->server_response().")" ); |
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
} |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
sub chargeback_retrieve_support_doc { |
843
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
844
|
0
|
|
|
|
|
0
|
$self->_litle_support_doc('RETRIEVE'); |
845
|
0
|
0
|
|
|
|
0
|
if ($self->is_success) { $self->{'fileContent'} = $self->{'server_response_dangerous'}; } else { $self->{'fileContent'} = undef; } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
846
|
|
|
|
|
|
|
} |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
sub chargeback_delete_support_doc { |
850
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
851
|
0
|
|
|
|
|
0
|
$self->_litle_support_doc('DELETE' ); |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
sub chargeback_upload_support_doc { |
856
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
857
|
0
|
|
|
|
|
0
|
$self->_litle_support_doc('UPLOAD' ); |
858
|
|
|
|
|
|
|
} |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
sub chargeback_replace_support_doc { |
862
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
863
|
0
|
|
|
|
|
0
|
$self->_litle_support_doc('REPLACE' ); |
864
|
|
|
|
|
|
|
} |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
sub _litle_support_doc { |
867
|
0
|
|
|
0
|
|
0
|
my ( $self, $action ) = @_; |
868
|
|
|
|
|
|
|
|
869
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
870
|
0
|
|
|
|
|
0
|
$self->_litle_init; |
871
|
|
|
|
|
|
|
|
872
|
0
|
|
|
|
|
0
|
my %content = $self->content(); |
873
|
|
|
|
|
|
|
|
874
|
0
|
|
|
|
|
0
|
my $requiredargs = ['case_id','filename','merchantid']; |
875
|
0
|
0
|
|
|
|
0
|
if ($action =~ /(?:UPLOAD|REPLACE)/) { push @$requiredargs, 'filecontent', 'mimetype'; } |
|
0
|
|
|
|
|
0
|
|
876
|
0
|
|
|
|
|
0
|
foreach my $key (@$requiredargs) { |
877
|
0
|
0
|
|
|
|
0
|
croak "Missing arg $key" unless $content{$key}; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
|
880
|
0
|
|
|
|
|
0
|
my $actionRESTful = { |
881
|
|
|
|
|
|
|
'DELETE' => 'DELETE', |
882
|
|
|
|
|
|
|
'RETRIEVE' => 'GET', |
883
|
|
|
|
|
|
|
'UPLOAD' => 'POST', |
884
|
|
|
|
|
|
|
'REPLACE' => 'PUT', |
885
|
|
|
|
|
|
|
}; |
886
|
0
|
0
|
|
|
|
0
|
die "UNDEFINED ACTION: $action" unless defined $actionRESTful->{$action}; |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
{ |
889
|
4
|
|
|
4
|
|
15759
|
use bytes; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
27
|
|
|
0
|
|
|
|
|
0
|
|
890
|
0
|
0
|
|
|
|
0
|
if ( defined $content{'filecontent'} ) { |
891
|
0
|
0
|
|
|
|
0
|
if ( length($content{'filecontent'}) > 2097152 ) { # file limit of 2M |
892
|
0
|
|
|
|
|
0
|
my $msg = 'Filesize Exceeds Limit Of 2MB'; |
893
|
0
|
|
|
|
|
0
|
$self->result_code( 012 ); ## no critic |
894
|
0
|
|
|
|
|
0
|
$self->error_message( $msg ); |
895
|
0
|
|
|
|
|
0
|
croak $msg; |
896
|
|
|
|
|
|
|
} |
897
|
0
|
|
|
|
|
0
|
my $allowedTypes = { |
898
|
|
|
|
|
|
|
'application/pdf' => 1, |
899
|
|
|
|
|
|
|
'image/gif' => 1, |
900
|
|
|
|
|
|
|
'image/jpeg' => 1, |
901
|
|
|
|
|
|
|
'image/png' => 1, |
902
|
|
|
|
|
|
|
'image/tiff' => 1, |
903
|
|
|
|
|
|
|
}; |
904
|
0
|
0
|
0
|
|
|
0
|
if ( ! defined $allowedTypes->{$content{'mimetype'}||''} ) { |
905
|
0
|
|
|
|
|
0
|
croak "File must be one of PDF/GIF/JPG/PNG/TIFF".$content{'mimetype'}; |
906
|
|
|
|
|
|
|
} |
907
|
|
|
|
|
|
|
} |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
0
|
|
|
|
|
0
|
my $caseidURI = $content{'case_id'}; |
911
|
0
|
|
|
|
|
0
|
my $filenameURI = $content{'filename'}; |
912
|
0
|
|
|
|
|
0
|
my $merchantidURI = $content{'merchantid'}; |
913
|
0
|
|
|
|
|
0
|
foreach ( $caseidURI, $filenameURI, $merchantidURI ) { |
914
|
0
|
|
|
|
|
0
|
s/([^a-z0-9\.\-])/sprintf('%%%X',ord($1))/ige; |
|
0
|
|
|
|
|
0
|
|
915
|
|
|
|
|
|
|
} |
916
|
|
|
|
|
|
|
|
917
|
0
|
|
|
|
|
0
|
my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'//services/chargebacks/documents/'.$merchantidURI.'/'.$caseidURI.'/'.$filenameURI; |
918
|
|
|
|
|
|
|
my $response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request($actionRESTful->{$action}, $url, { |
919
|
|
|
|
|
|
|
headers => { |
920
|
|
|
|
|
|
|
'Authorization' => 'Basic ' . MIME::Base64::encode("$content{'login'}:$content{'password'}",''), |
921
|
|
|
|
|
|
|
'Content-Type' => $content{'mimetype'} || 'text/plain', |
922
|
|
|
|
|
|
|
}, |
923
|
0
|
|
0
|
|
|
0
|
content => $content{'filecontent'}, |
924
|
|
|
|
|
|
|
} ); |
925
|
|
|
|
|
|
|
|
926
|
0
|
|
|
|
|
0
|
$self->server_request( $content{'mimetype'} ); |
927
|
0
|
|
|
|
|
0
|
$self->server_response( $response->{'content'} ); |
928
|
|
|
|
|
|
|
|
929
|
0
|
0
|
0
|
|
|
0
|
if ( $action eq 'RETRIEVE' && $response->{'status'} =~ /^200/ && substr($response->{'content'},0,500) !~ /
|
|
|
|
0
|
|
|
|
|
930
|
|
|
|
|
|
|
# the RETRIEVE action returns the actual page as the file, rather then returning XML |
931
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
932
|
|
|
|
|
|
|
} else { |
933
|
0
|
|
|
|
|
0
|
my $xml_response = $self->_parse_xml_response( $response->{'content'}, $response->{'status'} ); |
934
|
|
|
|
|
|
|
|
935
|
0
|
0
|
0
|
|
|
0
|
if (defined $xml_response && defined $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'}) { |
936
|
0
|
0
|
|
|
|
0
|
$self->is_success( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'} eq '000' ? 1 : 0 ); |
937
|
0
|
|
|
|
|
0
|
$self->result_code( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseCode'} ); |
938
|
0
|
|
|
|
|
0
|
$self->error_message( $xml_response->{'ChargebackCase'}{'Document'}{'ResponseMessage'} ); |
939
|
|
|
|
|
|
|
} else { |
940
|
0
|
|
|
|
|
0
|
croak "UNRECOGNIZED RESULT: ".$self->server_response; |
941
|
|
|
|
|
|
|
} |
942
|
|
|
|
|
|
|
} |
943
|
|
|
|
|
|
|
} |
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
sub chargeback_list_support_docs { |
947
|
0
|
|
|
0
|
1
|
0
|
my ( $self ) = @_; |
948
|
|
|
|
|
|
|
|
949
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
950
|
0
|
|
|
|
|
0
|
$self->_litle_init; |
951
|
|
|
|
|
|
|
|
952
|
0
|
|
|
|
|
0
|
my %content = $self->content(); |
953
|
|
|
|
|
|
|
|
954
|
0
|
0
|
|
|
|
0
|
croak "Missing arg case_id" unless $content{'case_id'}; |
955
|
0
|
0
|
|
|
|
0
|
croak "Missing arg merchantid" unless $content{'merchantid'}; |
956
|
0
|
|
|
|
|
0
|
my $caseidURI = $content{'case_id'}; |
957
|
0
|
|
|
|
|
0
|
my $merchantidURI = $content{'merchantid'}; |
958
|
0
|
|
|
|
|
0
|
foreach ( $caseidURI, $merchantidURI ) { |
959
|
0
|
|
|
|
|
0
|
s/([^a-z0-9\.\-])/sprintf('%%%X',ord($1))/ige; |
|
0
|
|
|
|
|
0
|
|
960
|
|
|
|
|
|
|
} |
961
|
|
|
|
|
|
|
|
962
|
0
|
|
|
|
|
0
|
my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'//services/chargebacks/documents/'.$merchantidURI.'/'.$caseidURI.'/'; |
963
|
0
|
|
|
|
|
0
|
my $response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('GET', $url, { |
964
|
|
|
|
|
|
|
headers => { Authorization => 'Basic ' . MIME::Base64::encode("$content{'login'}:$content{'password'}",'') }, |
965
|
|
|
|
|
|
|
} ); |
966
|
|
|
|
|
|
|
|
967
|
0
|
|
|
|
|
0
|
$self->server_request( $url ); |
968
|
0
|
|
|
|
|
0
|
$self->server_response( $response->{'content'} ); |
969
|
|
|
|
|
|
|
|
970
|
0
|
|
|
|
|
0
|
my $xml_response = $self->_parse_xml_response( $response->{'content'}, $response->{'status'} ); |
971
|
|
|
|
|
|
|
|
972
|
0
|
0
|
0
|
|
|
0
|
if (defined $xml_response && $xml_response->{'ChargebackCase'}{'ResponseCode'}) { |
|
|
0
|
0
|
|
|
|
|
973
|
0
|
|
|
|
|
0
|
$self->result_code( $xml_response->{'ChargebackCase'}{'ResponseCode'} ); |
974
|
0
|
|
|
|
|
0
|
$self->error_message( $xml_response->{'ChargebackCase'}{'ResponseMessage'} ); |
975
|
|
|
|
|
|
|
} elsif (defined $xml_response && $xml_response->{'ChargebackCase'}{'DocumentEntry'}) { |
976
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
977
|
0
|
|
|
|
|
0
|
$self->result_code( '000' ); |
978
|
|
|
|
|
|
|
|
979
|
0
|
|
|
|
|
0
|
my $ref = $xml_response->{'ChargebackCase'}{'DocumentEntry'}; |
980
|
0
|
0
|
0
|
|
|
0
|
if (defined $ref->{'id'} && ref $ref->{'id'} eq '') { |
981
|
|
|
|
|
|
|
# XMLin does not parse the result properly for a single document. This fixes the single document format to match the multi-doc format |
982
|
0
|
|
|
|
|
0
|
$ref = { $ref->{'id'} => $ref }; |
983
|
|
|
|
|
|
|
} |
984
|
0
|
|
|
|
|
0
|
return $ref; |
985
|
|
|
|
|
|
|
} else { |
986
|
0
|
|
|
|
|
0
|
croak "UNRECOGNIZED RESULT: ".$self->server_response; |
987
|
|
|
|
|
|
|
} |
988
|
0
|
|
|
|
|
0
|
return {}; |
989
|
|
|
|
|
|
|
} |
990
|
|
|
|
|
|
|
|
991
|
|
|
|
|
|
|
sub _parse_xml_response { |
992
|
5
|
|
|
5
|
|
33
|
my ( $self, $page, $status_code ) = @_; |
993
|
5
|
|
|
|
|
18
|
my $response = {}; |
994
|
5
|
50
|
|
|
|
40
|
if ( $status_code =~ /^200/ ) { |
995
|
5
|
50
|
|
|
|
23
|
if ( ! eval { $response = XMLin($page); } ) { |
|
5
|
|
|
|
|
43
|
|
996
|
0
|
|
|
|
|
0
|
die "XML PARSING FAILURE: $@"; |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
} |
999
|
|
|
|
|
|
|
else { |
1000
|
0
|
|
|
|
|
0
|
$status_code =~ s/[\r\n\s]+$//; # remove newline so you can see the error in a linux console |
1001
|
0
|
0
|
|
|
|
0
|
if ( $status_code =~ /^(?:900|599)/ ) { $status_code .= ' - verify Litle has whitelisted your IP'; } |
|
0
|
|
|
|
|
0
|
|
1002
|
0
|
|
|
|
|
0
|
die "CONNECTION FAILURE: $status_code"; |
1003
|
|
|
|
|
|
|
} |
1004
|
5
|
|
|
|
|
152331
|
return $response; |
1005
|
|
|
|
|
|
|
} |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
sub _parse_batch_response { |
1008
|
0
|
|
|
0
|
|
0
|
my ( $self, $args ) = @_; |
1009
|
0
|
|
|
|
|
0
|
my @results; |
1010
|
0
|
|
|
|
|
0
|
my $resp = $self->{'batch_response'}; |
1011
|
0
|
|
|
|
|
0
|
$self->order_number( $resp->{'litleBatchId'} ); |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
#$self->invoice_number( $resp->{'id'} ); |
1014
|
|
|
|
|
|
|
my @result_types = |
1015
|
0
|
|
|
|
|
0
|
grep { $_ =~ m/Response$/ } |
1016
|
0
|
|
|
|
|
0
|
keys %{$resp}; ## get a list of result types in this batch |
|
0
|
|
|
|
|
0
|
|
1017
|
|
|
|
|
|
|
return { |
1018
|
0
|
|
|
|
|
0
|
'account_update' => $self->_get_update_response, |
1019
|
|
|
|
|
|
|
## do the other response types now |
1020
|
|
|
|
|
|
|
}; |
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
sub add_item { |
1025
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
1026
|
|
|
|
|
|
|
## do we want to render it now, or later? |
1027
|
0
|
|
|
|
|
0
|
push @{ $self->{'batch_entries'} }, shift; |
|
0
|
|
|
|
|
0
|
|
1028
|
|
|
|
|
|
|
} |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
sub create_batch { |
1032
|
0
|
|
|
0
|
1
|
0
|
my ( $self, %opts ) = @_; |
1033
|
|
|
|
|
|
|
|
1034
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
1035
|
0
|
|
|
|
|
0
|
$self->_litle_init(\%opts); |
1036
|
|
|
|
|
|
|
|
1037
|
0
|
0
|
0
|
|
|
0
|
if ( ! defined $self->{'batch_entries'} || scalar( @{ $self->{'batch_entries'} } ) < 1 ) { |
|
0
|
|
|
|
|
0
|
|
1038
|
0
|
|
|
|
|
0
|
$self->error_message('Cannot create an empty batch'); |
1039
|
0
|
|
|
|
|
0
|
return; |
1040
|
|
|
|
|
|
|
} |
1041
|
|
|
|
|
|
|
|
1042
|
0
|
|
|
|
|
0
|
my $post_data; |
1043
|
|
|
|
|
|
|
|
1044
|
0
|
|
|
|
|
0
|
my $writer = new XML::Writer( |
1045
|
|
|
|
|
|
|
OUTPUT => \$post_data, |
1046
|
|
|
|
|
|
|
DATA_MODE => 1, |
1047
|
|
|
|
|
|
|
DATA_INDENT => 2, |
1048
|
|
|
|
|
|
|
ENCODING => 'utf-8', |
1049
|
|
|
|
|
|
|
); |
1050
|
|
|
|
|
|
|
## set the authentication data |
1051
|
0
|
|
|
|
|
0
|
tie my %authentication, 'Tie::IxHash', |
1052
|
|
|
|
|
|
|
$self->_revmap_fields( |
1053
|
|
|
|
|
|
|
content => \%opts, |
1054
|
|
|
|
|
|
|
user => 'login', |
1055
|
|
|
|
|
|
|
password => 'password', |
1056
|
|
|
|
|
|
|
); |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
## Start the XML Document, parent tag |
1059
|
0
|
|
|
|
|
0
|
$writer->xmlDecl(); |
1060
|
|
|
|
|
|
|
$writer->startTag( |
1061
|
|
|
|
|
|
|
"litleRequest", |
1062
|
|
|
|
|
|
|
version => $self->batch_api_version, |
1063
|
|
|
|
|
|
|
xmlns => $self->xmlns, |
1064
|
0
|
|
0
|
|
|
0
|
id => $opts{'batch_id'} || time, |
1065
|
|
|
|
|
|
|
numBatchRequests => 1, #hardcoded for now, not doing multiple merchants |
1066
|
|
|
|
|
|
|
); |
1067
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
## authentication |
1069
|
0
|
|
|
|
|
0
|
$self->_xmlwrite( $writer, 'authentication', \%authentication ); |
1070
|
|
|
|
|
|
|
## batch Request tag |
1071
|
|
|
|
|
|
|
$writer->startTag( |
1072
|
|
|
|
|
|
|
'batchRequest', |
1073
|
|
|
|
|
|
|
id => $opts{'batch_id'} || time, |
1074
|
0
|
|
|
|
|
0
|
numAccountUpdates => scalar( @{ $self->{'batch_entries'} } ), |
1075
|
0
|
|
0
|
|
|
0
|
merchantId => $opts{'merchantid'}, |
1076
|
|
|
|
|
|
|
); |
1077
|
0
|
|
|
|
|
0
|
foreach my $entry ( @{ $self->{'batch_entries'} } ) { |
|
0
|
|
|
|
|
0
|
|
1078
|
0
|
|
|
|
|
0
|
$self->_litle_scrubber_add_card($entry->{'card_number'}); |
1079
|
0
|
|
|
|
|
0
|
my $req = $self->map_request( $entry ); |
1080
|
|
|
|
|
|
|
$writer->startTag( |
1081
|
|
|
|
|
|
|
$entry->{'TransactionType'}, |
1082
|
|
|
|
|
|
|
id => $entry->{'invoice_number'}, |
1083
|
|
|
|
|
|
|
reportGroup => $entry->{'report_group'} || 'BOP', |
1084
|
0
|
|
0
|
|
|
0
|
customerId => $entry->{'customer_id'} || 1, |
|
|
|
0
|
|
|
|
|
1085
|
|
|
|
|
|
|
); |
1086
|
0
|
|
|
|
|
0
|
foreach ( keys( %{$req} ) ) { |
|
0
|
|
|
|
|
0
|
|
1087
|
0
|
|
|
|
|
0
|
$self->_xmlwrite( $writer, $_, $req->{$_} ); |
1088
|
|
|
|
|
|
|
} |
1089
|
0
|
|
|
|
|
0
|
$writer->endTag( $entry->{'TransactionType'} ); |
1090
|
|
|
|
|
|
|
## need to also handle the action tag here, and custid info |
1091
|
|
|
|
|
|
|
} |
1092
|
0
|
|
|
|
|
0
|
$writer->endTag("batchRequest"); |
1093
|
0
|
|
|
|
|
0
|
$writer->endTag("litleRequest"); |
1094
|
0
|
|
|
|
|
0
|
$writer->end(); |
1095
|
|
|
|
|
|
|
## END XML Generation |
1096
|
|
|
|
|
|
|
|
1097
|
0
|
|
|
|
|
0
|
$self->server_request( $post_data ); |
1098
|
0
|
0
|
|
|
|
0
|
warn $self->server_request if $DEBUG; |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
#----- Send it |
1101
|
0
|
0
|
0
|
|
|
0
|
if ( $opts{'method'} && $opts{'method'} eq 'sftp' ) { #FTP |
|
|
0
|
0
|
|
|
|
|
1102
|
0
|
|
|
|
|
0
|
my $sftp = $self->_sftp_connect(\%opts,'inbound'); |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
## save the file out, can't put directly from var, and is multibyte, so issues from filehandle |
1105
|
0
|
|
0
|
|
|
0
|
my $filename = $opts{'batch_id'} || $opts{'login'} . "_" . time; |
1106
|
0
|
|
|
|
|
0
|
my $io = IO::String->new($post_data); |
1107
|
0
|
|
|
|
|
0
|
tie *IO, 'IO::String'; |
1108
|
|
|
|
|
|
|
|
1109
|
0
|
0
|
|
|
|
0
|
$sftp->put( $io, "$filename.prg" ) |
1110
|
|
|
|
|
|
|
or $self->_die("Cannot PUT $filename", $sftp->error); |
1111
|
0
|
0
|
|
|
|
0
|
$sftp->rename( "$filename.prg", |
1112
|
|
|
|
|
|
|
"$filename.asc" ) #once complete, you rename it, for pickup |
1113
|
|
|
|
|
|
|
or $self->die("Cannot RENAME file", $sftp->error); |
1114
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
1115
|
0
|
|
|
|
|
0
|
$self->server_response( $sftp->message ); |
1116
|
|
|
|
|
|
|
} |
1117
|
|
|
|
|
|
|
elsif ( $opts{'method'} && $opts{'method'} eq 'https' ) { #https post |
1118
|
0
|
|
|
|
|
0
|
$self->port('15000'); |
1119
|
0
|
|
|
|
|
0
|
$self->path('/'); |
1120
|
0
|
|
|
|
|
0
|
my ( $page, $status_code, %headers ) = |
1121
|
|
|
|
|
|
|
$self->https_post($post_data); |
1122
|
0
|
|
|
|
|
0
|
$self->server_response( $page ); |
1123
|
|
|
|
|
|
|
|
1124
|
0
|
0
|
|
|
|
0
|
warn Dumper [ $page, $status_code, \%headers ] if $DEBUG; |
1125
|
|
|
|
|
|
|
|
1126
|
0
|
|
|
|
|
0
|
my $response = {}; |
1127
|
0
|
0
|
|
|
|
0
|
if ( $status_code =~ /^200/ ) { |
1128
|
0
|
0
|
0
|
|
|
0
|
if ( ! eval { $response = XMLin($page); } ) { |
|
0
|
0
|
|
|
|
0
|
|
1129
|
0
|
|
|
|
|
0
|
$self->_die("XML PARSING FAILURE: $@"); |
1130
|
|
|
|
|
|
|
} |
1131
|
|
|
|
|
|
|
elsif ( exists( $response->{'response'} ) |
1132
|
|
|
|
|
|
|
&& $response->{'response'} == 1 ) |
1133
|
|
|
|
|
|
|
{ |
1134
|
|
|
|
|
|
|
## parse error type error |
1135
|
0
|
|
|
|
|
0
|
warn Dumper( $response, $self->server_request ); |
1136
|
0
|
|
|
|
|
0
|
$self->error_message( $response->{'message'} ); |
1137
|
0
|
|
|
|
|
0
|
return; |
1138
|
|
|
|
|
|
|
} |
1139
|
|
|
|
|
|
|
else { |
1140
|
|
|
|
|
|
|
$self->error_message( |
1141
|
0
|
|
|
|
|
0
|
$response->{'batchResponse'}->{'message'} ); |
1142
|
|
|
|
|
|
|
} |
1143
|
|
|
|
|
|
|
} |
1144
|
|
|
|
|
|
|
else { |
1145
|
0
|
|
|
|
|
0
|
$self->_die("CONNECTION FAILURE: $status_code"); |
1146
|
|
|
|
|
|
|
} |
1147
|
0
|
|
|
|
|
0
|
$self->{_response} = $response; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
##parse out the batch info as our general status |
1150
|
0
|
|
|
|
|
0
|
my $resp = $response->{'batchResponse'}; |
1151
|
0
|
|
|
|
|
0
|
$self->order_number( $resp->{'litleSessionId'} ); |
1152
|
0
|
|
|
|
|
0
|
$self->result_code( $response->{'response'} ); |
1153
|
0
|
0
|
|
|
|
0
|
$self->is_success( $response->{'response'} eq '0' ? 1 : 0 ); |
1154
|
|
|
|
|
|
|
|
1155
|
0
|
0
|
|
|
|
0
|
warn Dumper($response) if $DEBUG; |
1156
|
0
|
0
|
|
|
|
0
|
unless ( $self->is_success() ) { |
1157
|
0
|
0
|
|
|
|
0
|
unless ( $self->error_message() ) { |
1158
|
|
|
|
|
|
|
$self->error_message( |
1159
|
|
|
|
|
|
|
"(HTTPS response: $status_code) " |
1160
|
|
|
|
|
|
|
. "(HTTPS headers: " |
1161
|
|
|
|
|
|
|
. join( ", ", |
1162
|
0
|
|
|
|
|
0
|
map { "$_ => " . $headers{$_} } keys %headers ) |
|
0
|
|
|
|
|
0
|
|
1163
|
|
|
|
|
|
|
. ") " |
1164
|
|
|
|
|
|
|
. "(Raw HTTPS content: $page)" |
1165
|
|
|
|
|
|
|
); |
1166
|
|
|
|
|
|
|
} |
1167
|
|
|
|
|
|
|
} |
1168
|
0
|
0
|
|
|
|
0
|
if ( $self->is_success() ) { |
1169
|
0
|
|
|
|
|
0
|
$self->{'batch_response'} = $resp; |
1170
|
|
|
|
|
|
|
} |
1171
|
|
|
|
|
|
|
} |
1172
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
} |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
sub send_rfr { |
1177
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $args ) = @_; |
1178
|
|
|
|
|
|
|
|
1179
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
1180
|
0
|
|
|
|
|
0
|
$self->_litle_init($args); |
1181
|
|
|
|
|
|
|
|
1182
|
0
|
|
|
|
|
0
|
my $post_data; |
1183
|
0
|
|
|
|
|
0
|
my $writer = new XML::Writer( |
1184
|
|
|
|
|
|
|
OUTPUT => \$post_data, |
1185
|
|
|
|
|
|
|
DATA_MODE => 1, |
1186
|
|
|
|
|
|
|
DATA_INDENT => 2, |
1187
|
|
|
|
|
|
|
ENCODING => 'utf-8', |
1188
|
|
|
|
|
|
|
); |
1189
|
|
|
|
|
|
|
## set the authentication data |
1190
|
0
|
|
|
|
|
0
|
tie my %authentication, 'Tie::IxHash', |
1191
|
|
|
|
|
|
|
$self->_revmap_fields( |
1192
|
|
|
|
|
|
|
content => $args, |
1193
|
|
|
|
|
|
|
user => 'login', |
1194
|
|
|
|
|
|
|
password => 'password', |
1195
|
|
|
|
|
|
|
); |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
## Start the XML Document, parent tag |
1198
|
0
|
|
|
|
|
0
|
$writer->xmlDecl(); |
1199
|
0
|
|
|
|
|
0
|
$writer->startTag( |
1200
|
|
|
|
|
|
|
"litleRequest", |
1201
|
|
|
|
|
|
|
version => $self->batch_api_version, |
1202
|
|
|
|
|
|
|
xmlns => $self->xmlns, |
1203
|
|
|
|
|
|
|
numBatchRequests => 0, |
1204
|
|
|
|
|
|
|
); |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
## authentication |
1207
|
0
|
|
|
|
|
0
|
$self->_xmlwrite( $writer, 'authentication', \%authentication ); |
1208
|
|
|
|
|
|
|
## batch Request tag |
1209
|
0
|
|
|
|
|
0
|
$writer->startTag('RFRRequest'); |
1210
|
0
|
|
|
|
|
0
|
$writer->startTag('accountUpdateFileRequestData'); |
1211
|
0
|
|
|
|
|
0
|
$writer->startTag('merchantId'); |
1212
|
0
|
|
|
|
|
0
|
$writer->characters( $args->{'merchantid'} ); |
1213
|
0
|
|
|
|
|
0
|
$writer->endTag('merchantId'); |
1214
|
0
|
|
|
|
|
0
|
$writer->startTag('postDay'); |
1215
|
0
|
|
|
|
|
0
|
$writer->characters( $args->{'date'} ); |
1216
|
0
|
|
|
|
|
0
|
$writer->endTag('postDay'); |
1217
|
0
|
|
|
|
|
0
|
$writer->endTag('accountUpdateFileRequestData'); |
1218
|
0
|
|
|
|
|
0
|
$writer->endTag("RFRRequest"); |
1219
|
0
|
|
|
|
|
0
|
$writer->endTag("litleRequest"); |
1220
|
0
|
|
|
|
|
0
|
$writer->end(); |
1221
|
|
|
|
|
|
|
## END XML Generation |
1222
|
|
|
|
|
|
|
# |
1223
|
0
|
|
|
|
|
0
|
$self->port('15000'); |
1224
|
0
|
|
|
|
|
0
|
$self->path('/'); |
1225
|
0
|
|
|
|
|
0
|
my ( $page, $status_code, %headers ) = $self->https_post($post_data); |
1226
|
|
|
|
|
|
|
|
1227
|
0
|
|
|
|
|
0
|
$self->server_request( $post_data ); |
1228
|
0
|
|
|
|
|
0
|
$self->server_response( $page ); |
1229
|
0
|
0
|
|
|
|
0
|
warn $self->server_request if $DEBUG; |
1230
|
|
|
|
|
|
|
|
1231
|
0
|
0
|
|
|
|
0
|
warn Dumper [ $page, $status_code, \%headers ] if $DEBUG; |
1232
|
|
|
|
|
|
|
|
1233
|
0
|
|
|
|
|
0
|
my $response = {}; |
1234
|
0
|
0
|
|
|
|
0
|
if ( $status_code =~ /^200/ ) { |
1235
|
0
|
0
|
0
|
|
|
0
|
if ( ! eval { $response = XMLin($page); } ) { |
|
0
|
0
|
|
|
|
0
|
|
1236
|
0
|
|
|
|
|
0
|
die "XML PARSING FAILURE: $@"; |
1237
|
|
|
|
|
|
|
} |
1238
|
|
|
|
|
|
|
elsif ( exists( $response->{'response'} ) && $response->{'response'} == 1 ) |
1239
|
|
|
|
|
|
|
{ |
1240
|
|
|
|
|
|
|
## parse error type error |
1241
|
0
|
|
|
|
|
0
|
warn Dumper( $response, $self->server_request ); |
1242
|
0
|
|
|
|
|
0
|
$self->error_message( $response->{'message'} ); |
1243
|
0
|
|
|
|
|
0
|
return; |
1244
|
|
|
|
|
|
|
} |
1245
|
|
|
|
|
|
|
else { |
1246
|
0
|
|
|
|
|
0
|
$self->error_message( $response->{'RFRResponse'}->{'message'} ); |
1247
|
|
|
|
|
|
|
} |
1248
|
|
|
|
|
|
|
} |
1249
|
|
|
|
|
|
|
else { |
1250
|
0
|
|
|
|
|
0
|
die "CONNECTION FAILURE: $status_code"; |
1251
|
|
|
|
|
|
|
} |
1252
|
0
|
|
|
|
|
0
|
$self->{_response} = $response; |
1253
|
0
|
0
|
|
|
|
0
|
if ( $response->{'RFRResponse'} ) { |
1254
|
|
|
|
|
|
|
## litle returns an 'error' if the file is not done. So it's not ready yet. |
1255
|
0
|
|
|
|
|
0
|
$self->result_code( $response->{'RFRResponse'}->{'response'} ); |
1256
|
0
|
|
|
|
|
0
|
return; |
1257
|
|
|
|
|
|
|
} |
1258
|
|
|
|
|
|
|
else { |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
#if processed, it returns as a batch, so, success, and let get the details |
1261
|
0
|
|
|
|
|
0
|
my $resp = $response->{'batchResponse'}; |
1262
|
0
|
0
|
|
|
|
0
|
$self->is_success( $resp->{'response'} eq '000' ? 1 : 0 ); |
1263
|
0
|
|
|
|
|
0
|
$self->{'batch_response'} = $resp; |
1264
|
0
|
|
|
|
|
0
|
$self->_parse_batch_response; |
1265
|
|
|
|
|
|
|
} |
1266
|
|
|
|
|
|
|
} |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
sub _sftp_connect { |
1269
|
0
|
|
|
0
|
|
0
|
my ($self,$args,$dir) = @_; |
1270
|
0
|
0
|
|
|
|
0
|
$self->_die("Missing ftp_username") if ! $args->{'ftp_username'}; |
1271
|
0
|
0
|
|
|
|
0
|
$self->_die("Missing ftp_password") if ! $args->{'ftp_password'}; |
1272
|
0
|
|
|
|
|
0
|
require Net::SFTP::Foreign; |
1273
|
|
|
|
|
|
|
my $sftp = Net::SFTP::Foreign->new( |
1274
|
|
|
|
|
|
|
$self->server(), |
1275
|
|
|
|
|
|
|
timeout => $args->{'ftp_timeout'} || 90, |
1276
|
|
|
|
|
|
|
stderr_discard => 1, |
1277
|
|
|
|
|
|
|
user => $args->{'ftp_username'}, |
1278
|
0
|
|
0
|
|
|
0
|
password => $args->{'ftp_password'}, |
1279
|
|
|
|
|
|
|
); |
1280
|
0
|
0
|
|
|
|
0
|
$sftp->error and $self->_die("SSH connection failed: " . $sftp->error); |
1281
|
|
|
|
|
|
|
|
1282
|
0
|
0
|
|
|
|
0
|
if ($dir) { |
1283
|
0
|
0
|
|
|
|
0
|
$sftp->setcwd($dir) |
1284
|
|
|
|
|
|
|
or $self->_die("Cannot change working directory ", $sftp->error); |
1285
|
|
|
|
|
|
|
} |
1286
|
|
|
|
|
|
|
|
1287
|
0
|
|
|
|
|
0
|
return $sftp; |
1288
|
|
|
|
|
|
|
} |
1289
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
sub _die { |
1291
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1292
|
0
|
|
|
|
|
0
|
my $msg = join '', @_; |
1293
|
0
|
|
|
|
|
0
|
$self->is_success(0); |
1294
|
0
|
|
|
|
|
0
|
$self->error_message( $msg ); |
1295
|
0
|
|
|
|
|
0
|
die $msg."\n"; |
1296
|
|
|
|
|
|
|
} |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
sub retrieve_batch_list { |
1300
|
0
|
|
|
0
|
1
|
0
|
my ($self, %opts ) = @_; |
1301
|
|
|
|
|
|
|
|
1302
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
1303
|
0
|
|
|
|
|
0
|
$self->_litle_init(\%opts); |
1304
|
|
|
|
|
|
|
|
1305
|
0
|
|
|
|
|
0
|
my $sftp = $self->_sftp_connect(\%opts,'outbound'); |
1306
|
|
|
|
|
|
|
|
1307
|
0
|
0
|
|
|
|
0
|
my $ls = $sftp->ls( wanted => qr/\.asc$/ ) |
1308
|
|
|
|
|
|
|
or $self->_die("Cannot get directory listing ", $sftp->error); |
1309
|
|
|
|
|
|
|
|
1310
|
0
|
|
|
|
|
0
|
my @filenames = map {$_->{'filename'}} @{ $ls }; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1311
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
1312
|
0
|
|
|
|
|
0
|
return \@filenames; |
1313
|
|
|
|
|
|
|
} |
1314
|
|
|
|
|
|
|
|
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
sub retrieve_batch_delete { |
1317
|
0
|
|
|
0
|
1
|
0
|
my ( $self, %opts ) = @_; |
1318
|
|
|
|
|
|
|
|
1319
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
1320
|
0
|
|
|
|
|
0
|
$self->_litle_init(\%opts); |
1321
|
|
|
|
|
|
|
|
1322
|
0
|
0
|
|
|
|
0
|
$self->_die("Missing batch_id") if !$opts{'batch_id'}; |
1323
|
|
|
|
|
|
|
|
1324
|
0
|
|
|
|
|
0
|
my $sftp = $self->_sftp_connect(\%opts,'outbound'); |
1325
|
|
|
|
|
|
|
|
1326
|
0
|
|
|
|
|
0
|
my $filename = $opts{'batch_id'}; |
1327
|
0
|
0
|
|
|
|
0
|
$sftp->remove( $filename ) |
1328
|
|
|
|
|
|
|
or $self->_die("Cannot delete $filename: ", $sftp->error); |
1329
|
|
|
|
|
|
|
|
1330
|
0
|
|
|
|
|
0
|
$self->is_success(1); |
1331
|
|
|
|
|
|
|
} |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
|
1334
|
|
|
|
|
|
|
sub retrieve_batch { |
1335
|
0
|
|
|
0
|
1
|
0
|
my ( $self, %opts ) = @_; |
1336
|
|
|
|
|
|
|
|
1337
|
0
|
|
|
|
|
0
|
local $SCRUBBER=1; |
1338
|
0
|
|
|
|
|
0
|
$self->_litle_init(\%opts); |
1339
|
|
|
|
|
|
|
|
1340
|
0
|
0
|
|
|
|
0
|
$self->_die("Missing batch_id") if !$opts{'batch_id'}; |
1341
|
|
|
|
|
|
|
|
1342
|
0
|
|
|
|
|
0
|
my $post_data; |
1343
|
0
|
0
|
|
|
|
0
|
if ( $opts{'batch_return'} ) { |
1344
|
|
|
|
|
|
|
## passed in data structure |
1345
|
0
|
|
|
|
|
0
|
$post_data = $opts{'batch_return'}; |
1346
|
0
|
|
|
|
|
0
|
$self->server_request('Data was provided using batch_return option'); |
1347
|
|
|
|
|
|
|
} |
1348
|
|
|
|
|
|
|
else { |
1349
|
|
|
|
|
|
|
## go download a batch |
1350
|
0
|
|
|
|
|
0
|
my $sftp = $self->_sftp_connect(\%opts,'outbound'); |
1351
|
|
|
|
|
|
|
|
1352
|
0
|
|
|
|
|
0
|
my $filename = $opts{'batch_id'}; |
1353
|
0
|
|
|
|
|
0
|
$self->server_request('SFTP requesting file: '.$filename,1); |
1354
|
0
|
0
|
|
|
|
0
|
$post_data = $sftp->get_content( $filename ) |
1355
|
|
|
|
|
|
|
or $self->_die("Cannot GET $filename", $sftp->error); |
1356
|
|
|
|
|
|
|
} |
1357
|
0
|
|
|
|
|
0
|
$self->server_response_dangerous($post_data,1); |
1358
|
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); |
1359
|
|
|
|
|
|
|
|
1360
|
0
|
|
|
|
|
0
|
my $response = {}; |
1361
|
0
|
0
|
0
|
|
|
0
|
if ( ! eval { $response = XMLin($post_data, |
|
0
|
0
|
|
|
|
0
|
|
1362
|
|
|
|
|
|
|
ForceArray => [ 'accountUpdateResponse' ], |
1363
|
|
|
|
|
|
|
KeyAttr => '-id', |
1364
|
|
|
|
|
|
|
); } ) { |
1365
|
0
|
|
|
|
|
0
|
$self->_die("XML PARSING FAILURE: $@"); |
1366
|
|
|
|
|
|
|
} |
1367
|
|
|
|
|
|
|
elsif ( exists( $response->{'response'} ) && $response->{'response'} == 1 ) { |
1368
|
|
|
|
|
|
|
## parse error type error |
1369
|
0
|
|
|
|
|
0
|
warn Dumper( $response, $self->{'_post_data'} ); |
1370
|
0
|
|
0
|
|
|
0
|
$self->_die($response->{'message'} || 'No reason given'); |
1371
|
|
|
|
|
|
|
} |
1372
|
|
|
|
|
|
|
else { |
1373
|
|
|
|
|
|
|
## update the status |
1374
|
0
|
|
|
|
|
0
|
$self->error_message( $response->{'batchResponse'}->{'message'} ); |
1375
|
|
|
|
|
|
|
} |
1376
|
|
|
|
|
|
|
|
1377
|
0
|
|
|
|
|
0
|
$self->{_response} = $response; |
1378
|
0
|
|
|
|
|
0
|
my $resp = $response->{'batchResponse'}; |
1379
|
0
|
|
|
|
|
0
|
$self->order_number( $resp->{'litleSessionId'} ); |
1380
|
0
|
|
|
|
|
0
|
$self->result_code( $response->{'response'} ); |
1381
|
0
|
0
|
|
|
|
0
|
$self->is_success( $response->{'response'} eq '0' ? 1 : 0 ); |
1382
|
0
|
0
|
|
|
|
0
|
if ( $self->is_success() ) { |
1383
|
0
|
|
|
|
|
0
|
$self->{'batch_response'} = $resp; |
1384
|
0
|
|
|
|
|
0
|
return $self->_parse_batch_response; |
1385
|
|
|
|
|
|
|
} |
1386
|
|
|
|
|
|
|
} |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
sub _get_update_response { |
1389
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1390
|
0
|
|
|
|
|
0
|
require Business::OnlinePayment::Litle::UpdaterResponse; |
1391
|
0
|
|
|
|
|
0
|
my @response; |
1392
|
0
|
|
|
|
|
0
|
foreach |
1393
|
0
|
|
|
|
|
0
|
my $item ( @{ $self->{'batch_response'}->{'accountUpdateResponse'} } ) |
1394
|
|
|
|
|
|
|
{ |
1395
|
0
|
|
|
|
|
0
|
push @response, |
1396
|
|
|
|
|
|
|
Business::OnlinePayment::Litle::UpdaterResponse->new( $item ); |
1397
|
|
|
|
|
|
|
} |
1398
|
0
|
|
|
|
|
0
|
return \@response; |
1399
|
|
|
|
|
|
|
} |
1400
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
sub _revmap_fields { |
1402
|
88
|
|
|
88
|
|
160
|
my $self = shift; |
1403
|
88
|
|
|
|
|
335
|
tie my (%map), 'Tie::IxHash', @_; |
1404
|
88
|
|
|
|
|
7591
|
my %content; |
1405
|
88
|
50
|
33
|
|
|
298
|
if ( $map{'content'} && ref( $map{'content'} ) eq 'HASH' ) { |
1406
|
88
|
|
|
|
|
1445
|
%content = %{ delete( $map{'content'} ) }; |
|
88
|
|
|
|
|
308
|
|
1407
|
|
|
|
|
|
|
} |
1408
|
|
|
|
|
|
|
else { |
1409
|
0
|
|
|
|
|
0
|
warn "WARNING: This content has not been pre-processed with map_fields"; |
1410
|
0
|
|
|
|
|
0
|
%content = $self->content(); |
1411
|
|
|
|
|
|
|
} |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
map { |
1414
|
88
|
|
|
|
|
3590
|
my $value; |
|
459
|
|
|
|
|
2990
|
|
1415
|
459
|
100
|
|
|
|
1212
|
if ( ref( $map{$_} ) eq 'HASH' ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
1416
|
39
|
100
|
|
|
|
243
|
$value = $map{$_} if ( keys %{ $map{$_} } ); |
|
39
|
|
|
|
|
125
|
|
1417
|
|
|
|
|
|
|
} |
1418
|
|
|
|
|
|
|
elsif ( ref( $map{$_} ) eq 'ARRAY' ) { |
1419
|
6
|
|
|
|
|
71
|
$value = $map{$_}; |
1420
|
|
|
|
|
|
|
} |
1421
|
|
|
|
|
|
|
elsif ( ref( $map{$_} ) ) { |
1422
|
0
|
|
|
|
|
0
|
$value = ${ $map{$_} }; |
|
0
|
|
|
|
|
0
|
|
1423
|
|
|
|
|
|
|
} |
1424
|
|
|
|
|
|
|
elsif ( exists( $content{ $map{$_} } ) ) { |
1425
|
330
|
|
|
|
|
7031
|
$value = $content{ $map{$_} }; |
1426
|
|
|
|
|
|
|
} |
1427
|
|
|
|
|
|
|
|
1428
|
459
|
100
|
|
|
|
5079
|
if ( defined($value) ) { |
1429
|
370
|
|
|
|
|
1227
|
( $_ => $value ); |
1430
|
|
|
|
|
|
|
} |
1431
|
|
|
|
|
|
|
else { |
1432
|
89
|
|
|
|
|
346
|
(); |
1433
|
|
|
|
|
|
|
} |
1434
|
|
|
|
|
|
|
} ( keys %map ); |
1435
|
|
|
|
|
|
|
} |
1436
|
|
|
|
|
|
|
|
1437
|
|
|
|
|
|
|
sub _xmlwrite { |
1438
|
257
|
|
|
257
|
|
724
|
my ( $self, $writer, $item, $value ) = @_; |
1439
|
257
|
100
|
|
|
|
1777
|
if ( ref($value) eq 'HASH' ) { |
|
|
100
|
|
|
|
|
|
1440
|
47
|
50
|
|
|
|
146
|
my $attr = $value->{'attr'} ? $value->{'attr'} : {}; |
1441
|
47
|
|
|
|
|
326
|
$writer->startTag( $item, %{$attr} ); |
|
47
|
|
|
|
|
163
|
|
1442
|
47
|
|
|
|
|
2595
|
foreach ( keys(%$value) ) { |
1443
|
190
|
50
|
|
|
|
5004
|
next if $_ eq 'attr'; |
1444
|
190
|
|
|
|
|
1255
|
$self->_xmlwrite( $writer, $_, $value->{$_} ); |
1445
|
|
|
|
|
|
|
} |
1446
|
47
|
|
|
|
|
1300
|
$writer->endTag($item); |
1447
|
|
|
|
|
|
|
} |
1448
|
|
|
|
|
|
|
elsif ( ref($value) eq 'ARRAY' ) { |
1449
|
4
|
|
|
|
|
12
|
foreach ( @{$value} ) { |
|
4
|
|
|
|
|
13
|
|
1450
|
8
|
|
|
|
|
148
|
$self->_xmlwrite( $writer, $item, $_ ); |
1451
|
|
|
|
|
|
|
} |
1452
|
|
|
|
|
|
|
} |
1453
|
|
|
|
|
|
|
else { |
1454
|
206
|
|
|
|
|
602
|
$writer->startTag($item); |
1455
|
206
|
|
|
|
|
11619
|
$writer->characters($value); |
1456
|
206
|
|
|
|
|
4866
|
$writer->endTag($item); |
1457
|
|
|
|
|
|
|
} |
1458
|
|
|
|
|
|
|
} |
1459
|
|
|
|
|
|
|
|
1460
|
|
|
|
|
|
|
sub _default_scrubber { |
1461
|
9
|
|
|
9
|
|
870
|
my $cc = shift; |
1462
|
9
|
|
|
|
|
48
|
my $del = substr($cc,0,6).('X'x(length($cc)-10)).substr($cc,-4,4); # show first 6 and last 4 |
1463
|
9
|
|
|
|
|
49
|
return $del; |
1464
|
|
|
|
|
|
|
} |
1465
|
|
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
sub _litle_scrubber_add_card { |
1467
|
7
|
|
|
7
|
|
136
|
my ( $self, $cc ) = @_; |
1468
|
7
|
100
|
|
|
|
45
|
return if ! $cc; |
1469
|
5
|
|
|
|
|
15
|
my $scrubber = $self->{_scrubber}; |
1470
|
5
|
|
|
|
|
12
|
scrubber_add_scrubber({$cc=>&{$scrubber}($cc)}); |
|
5
|
|
|
|
|
19
|
|
1471
|
|
|
|
|
|
|
} |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
sub _litle_init { |
1474
|
6
|
|
|
6
|
|
20
|
my ( $self, $opts ) = @_; |
1475
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
# initialize/reset the reporting methods |
1477
|
6
|
|
|
|
|
128
|
$self->is_success(0); |
1478
|
6
|
|
|
|
|
81
|
$self->server_request(''); |
1479
|
6
|
|
|
|
|
29
|
$self->server_response(''); |
1480
|
6
|
|
|
|
|
123
|
$self->error_message(''); |
1481
|
|
|
|
|
|
|
|
1482
|
|
|
|
|
|
|
# some calls are passed via the content method, others are direct arguments... this way we cover both |
1483
|
6
|
|
|
|
|
61
|
my %content = $self->content(); |
1484
|
6
|
|
|
|
|
178
|
foreach my $ptr (\%content,$opts) { |
1485
|
12
|
100
|
|
|
|
81
|
next if ! $ptr; |
1486
|
|
|
|
|
|
|
scrubber_init({ |
1487
|
|
|
|
|
|
|
quotemeta($ptr->{'password'}||'')=>'DELETED', |
1488
|
|
|
|
|
|
|
quotemeta($ptr->{'ftp_password'}||'')=>'DELETED', |
1489
|
6
|
50
|
50
|
|
|
96
|
($ptr->{'cvv2'} ? '(?<=[^\d])'.quotemeta($ptr->{'cvv2'}).'(?=[^\d])' : '')=>'DELETED', |
|
|
|
50
|
|
|
|
|
1490
|
|
|
|
|
|
|
}); |
1491
|
6
|
|
|
|
|
1378
|
$self->_litle_scrubber_add_card($ptr->{'card_number'}); |
1492
|
|
|
|
|
|
|
} |
1493
|
|
|
|
|
|
|
} |
1494
|
|
|
|
|
|
|
|
1495
|
|
|
|
|
|
|
|
1496
|
|
|
|
|
|
|
sub chargeback_activity_request { |
1497
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
1498
|
|
|
|
|
|
|
|
1499
|
0
|
|
|
|
|
|
local $SCRUBBER=1; |
1500
|
0
|
|
|
|
|
|
$self->_litle_init; |
1501
|
|
|
|
|
|
|
|
1502
|
0
|
|
|
|
|
|
my $post_data; |
1503
|
0
|
|
|
|
|
|
my %content = $self->content(); |
1504
|
|
|
|
|
|
|
|
1505
|
|
|
|
|
|
|
## activity_date |
1506
|
|
|
|
|
|
|
## Type = Date; Format = YYYY-MM-DD |
1507
|
0
|
0
|
0
|
|
|
|
if ( ! $content{'activity_date'} || $content{'activity_date'} !~ m/^\d{4}-(\d{2})-(\d{2})$/ || $1 > 12 || $2 > 31) { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
1508
|
0
|
|
0
|
|
|
|
$self->_die("Invalid Date Pattern, YYYY-MM-DD required:" . ( $content{'activity_date'} || 'undef')); |
1509
|
|
|
|
|
|
|
} |
1510
|
|
|
|
|
|
|
# |
1511
|
|
|
|
|
|
|
## financials only [true,false] |
1512
|
|
|
|
|
|
|
# The financialOnly element is an optional child of the litleChargebackActivitiesRequest element. |
1513
|
|
|
|
|
|
|
# You use this flag in combination with the activityDate element to specify a request for chargeback financial activities that occurred on the specified date. |
1514
|
|
|
|
|
|
|
# A value of true returns only activities that had financial impact on the specified date. |
1515
|
|
|
|
|
|
|
# A value of false returns all activities on the specified date. |
1516
|
|
|
|
|
|
|
#Type = Boolean; Valid Values = true or false |
1517
|
0
|
|
|
|
|
|
my $financials; |
1518
|
0
|
0
|
|
|
|
|
if ( defined( $content{'financial_only'} ) ) { |
1519
|
0
|
0
|
|
|
|
|
$financials = $content{'financial_only'} ? 'true' : 'false'; |
1520
|
|
|
|
|
|
|
} |
1521
|
|
|
|
|
|
|
else { |
1522
|
0
|
|
|
|
|
|
$financials = 'false'; |
1523
|
|
|
|
|
|
|
} |
1524
|
|
|
|
|
|
|
|
1525
|
0
|
|
|
|
|
|
my $writer = new XML::Writer( |
1526
|
|
|
|
|
|
|
OUTPUT => \$post_data, |
1527
|
|
|
|
|
|
|
DATA_MODE => 1, |
1528
|
|
|
|
|
|
|
DATA_INDENT => 2, |
1529
|
|
|
|
|
|
|
ENCODING => 'utf-8', |
1530
|
|
|
|
|
|
|
); |
1531
|
|
|
|
|
|
|
## set the authentication data |
1532
|
0
|
|
|
|
|
|
tie my %authentication, 'Tie::IxHash', |
1533
|
|
|
|
|
|
|
$self->_revmap_fields( |
1534
|
|
|
|
|
|
|
content => \%content, |
1535
|
|
|
|
|
|
|
user => 'login', |
1536
|
|
|
|
|
|
|
password => 'password', |
1537
|
|
|
|
|
|
|
); |
1538
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
## Start the XML Document, parent tag |
1540
|
0
|
|
|
|
|
|
$writer->xmlDecl(); |
1541
|
0
|
|
|
|
|
|
$writer->startTag( |
1542
|
|
|
|
|
|
|
"litleChargebackActivitiesRequest", |
1543
|
|
|
|
|
|
|
version => $self->chargeback_api_version, |
1544
|
|
|
|
|
|
|
xmlns => $self->xmlns, |
1545
|
|
|
|
|
|
|
); |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
## authentication |
1548
|
0
|
|
|
|
|
|
$self->_xmlwrite( $writer, 'authentication', \%authentication ); |
1549
|
|
|
|
|
|
|
## batch Request tag |
1550
|
0
|
|
|
|
|
|
$writer->startTag('activityDate'); |
1551
|
0
|
|
|
|
|
|
$writer->characters( $content{'activity_date'} ); |
1552
|
0
|
|
|
|
|
|
$writer->endTag('activityDate'); |
1553
|
0
|
|
|
|
|
|
$writer->startTag('financialOnly'); |
1554
|
0
|
|
|
|
|
|
$writer->characters($financials); |
1555
|
0
|
|
|
|
|
|
$writer->endTag('financialOnly'); |
1556
|
0
|
|
|
|
|
|
$writer->endTag("litleChargebackActivitiesRequest"); |
1557
|
0
|
|
|
|
|
|
$writer->end(); |
1558
|
|
|
|
|
|
|
## END XML Generation |
1559
|
|
|
|
|
|
|
|
1560
|
0
|
|
|
|
|
|
$self->{'_post_data'} = $post_data; |
1561
|
0
|
0
|
|
|
|
|
warn $self->{'_post_data'} if $DEBUG; |
1562
|
|
|
|
|
|
|
#my ( $page, $status_code, %headers ) = $self->https_post( { 'Content-Type' => 'text/xml; charset=utf-8' } , $post_data); |
1563
|
0
|
|
|
|
|
|
my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'/'.$self->chargeback_path; |
1564
|
0
|
|
|
|
|
|
my $tiny_response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('POST', $url, { |
1565
|
|
|
|
|
|
|
headers => { 'Content-Type' => 'text/xml; charset=utf-8', }, |
1566
|
|
|
|
|
|
|
content => $post_data, |
1567
|
|
|
|
|
|
|
} ); |
1568
|
|
|
|
|
|
|
|
1569
|
0
|
|
|
|
|
|
my $page = $tiny_response->{'content'}; |
1570
|
0
|
|
|
|
|
|
$self->server_request( $post_data ); |
1571
|
0
|
|
|
|
|
|
$self->server_response( $page ); |
1572
|
0
|
|
|
|
|
|
my $status_code = $tiny_response->{'status'}; |
1573
|
0
|
|
|
|
|
|
my %headers = %{$tiny_response->{'headers'}}; |
|
0
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
|
1575
|
0
|
0
|
|
|
|
|
warn Dumper $page, $status_code, \%headers if $DEBUG; |
1576
|
|
|
|
|
|
|
|
1577
|
0
|
|
|
|
|
|
my $response = {}; |
1578
|
0
|
0
|
|
|
|
|
if ( $status_code =~ /^200/ ) { |
1579
|
|
|
|
|
|
|
## Failed to parse |
1580
|
0
|
0
|
0
|
|
|
|
if ( !eval { $response = XMLin($page, |
|
0
|
0
|
|
|
|
|
|
1581
|
|
|
|
|
|
|
ForceArray => [ 'caseActivity' ], |
1582
|
|
|
|
|
|
|
); } ) { |
1583
|
0
|
|
|
|
|
|
$self->_die("XML PARSING FAILURE: $@, $page"); |
1584
|
|
|
|
|
|
|
} ## well-formed failure message |
1585
|
|
|
|
|
|
|
elsif ( exists( $response->{'response'} ) |
1586
|
|
|
|
|
|
|
&& $response->{'response'} == 1 ) |
1587
|
|
|
|
|
|
|
{ |
1588
|
|
|
|
|
|
|
## parse error type error |
1589
|
0
|
|
|
|
|
|
warn Dumper( $response, $self->{'_post_data'} ); |
1590
|
0
|
|
|
|
|
|
$self->error_message( $response->{'message'} ); |
1591
|
0
|
|
|
|
|
|
return; |
1592
|
|
|
|
|
|
|
} ## success message |
1593
|
|
|
|
|
|
|
else { |
1594
|
|
|
|
|
|
|
$self->error_message( |
1595
|
0
|
|
|
|
|
|
$response->{'litleChargebackActivitiesResponse'}->{'message'} ); |
1596
|
|
|
|
|
|
|
} |
1597
|
|
|
|
|
|
|
} |
1598
|
|
|
|
|
|
|
else { |
1599
|
0
|
|
|
|
|
|
$status_code =~ s/[\r\n\s]+$// |
1600
|
|
|
|
|
|
|
; # remove newline so you can see the error in a linux console |
1601
|
0
|
0
|
|
|
|
|
if ( $status_code =~ /^(?:900|599)/ ) { |
1602
|
0
|
|
|
|
|
|
$status_code .= ' - verify Litle has whitelisted your IP'; |
1603
|
|
|
|
|
|
|
} |
1604
|
0
|
|
|
|
|
|
$self->_die("CONNECTION FAILURE: $status_code"); |
1605
|
|
|
|
|
|
|
} |
1606
|
0
|
|
|
|
|
|
$self->{_response} = $response; |
1607
|
|
|
|
|
|
|
|
1608
|
0
|
|
|
|
|
|
my @response_list; |
1609
|
0
|
|
|
|
|
|
require Business::OnlinePayment::Litle::ChargebackActivityResponse; |
1610
|
0
|
|
|
|
|
|
foreach my $case ( @{ $response->{caseActivity} } ) { |
|
0
|
|
|
|
|
|
|
1611
|
0
|
|
|
|
|
|
push @response_list, |
1612
|
|
|
|
|
|
|
Business::OnlinePayment::Litle::ChargebackActivityResponse->new($case); |
1613
|
|
|
|
|
|
|
} |
1614
|
|
|
|
|
|
|
|
1615
|
0
|
0
|
|
|
|
|
warn Dumper($response) if $DEBUG; |
1616
|
0
|
|
|
|
|
|
$self->is_success(1); |
1617
|
0
|
|
|
|
|
|
return \@response_list; |
1618
|
|
|
|
|
|
|
} |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
sub chargeback_update_request { |
1622
|
0
|
|
|
0
|
1
|
|
my ( $self ) = @_; |
1623
|
|
|
|
|
|
|
|
1624
|
0
|
|
|
|
|
|
local $SCRUBBER=1; |
1625
|
0
|
|
|
|
|
|
$self->_litle_init; |
1626
|
|
|
|
|
|
|
|
1627
|
0
|
|
|
|
|
|
my $post_data; |
1628
|
0
|
|
|
|
|
|
my %content = $self->content(); |
1629
|
|
|
|
|
|
|
|
1630
|
0
|
|
|
|
|
|
foreach my $key (qw(case_id merchant_activity_id activity )) { |
1631
|
|
|
|
|
|
|
## case_id |
1632
|
|
|
|
|
|
|
## merchant_activity_id |
1633
|
|
|
|
|
|
|
## activity |
1634
|
0
|
0
|
|
|
|
|
croak "Missing arg $key" unless $content{$key}; |
1635
|
|
|
|
|
|
|
} |
1636
|
|
|
|
|
|
|
|
1637
|
0
|
|
|
|
|
|
my $writer = new XML::Writer( |
1638
|
|
|
|
|
|
|
OUTPUT => \$post_data, |
1639
|
|
|
|
|
|
|
DATA_MODE => 1, |
1640
|
|
|
|
|
|
|
DATA_INDENT => 2, |
1641
|
|
|
|
|
|
|
ENCODING => 'utf-8', |
1642
|
|
|
|
|
|
|
); |
1643
|
|
|
|
|
|
|
## set the authentication data |
1644
|
0
|
|
|
|
|
|
tie my %authentication, 'Tie::IxHash', |
1645
|
|
|
|
|
|
|
$self->_revmap_fields( |
1646
|
|
|
|
|
|
|
content => \%content, |
1647
|
|
|
|
|
|
|
user => 'login', |
1648
|
|
|
|
|
|
|
password => 'password', |
1649
|
|
|
|
|
|
|
); |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
## Start the XML Document, parent tag |
1652
|
0
|
|
|
|
|
|
$writer->xmlDecl(); |
1653
|
0
|
|
|
|
|
|
$writer->startTag( |
1654
|
|
|
|
|
|
|
"litleChargebackUpdateRequest", |
1655
|
|
|
|
|
|
|
version => $self->chargeback_api_version, |
1656
|
|
|
|
|
|
|
xmlns => $self->xmlns, |
1657
|
|
|
|
|
|
|
); |
1658
|
|
|
|
|
|
|
|
1659
|
|
|
|
|
|
|
## authentication |
1660
|
0
|
|
|
|
|
|
$self->_xmlwrite( $writer, 'authentication', \%authentication ); |
1661
|
0
|
|
|
|
|
|
$writer->startTag('caseUpdate'); |
1662
|
0
|
|
|
|
|
|
$writer->startTag('caseId'); |
1663
|
0
|
|
|
|
|
|
$writer->characters( $content{'case_id'} ); |
1664
|
0
|
|
|
|
|
|
$writer->endTag('caseId'); |
1665
|
|
|
|
|
|
|
|
1666
|
0
|
|
|
|
|
|
$writer->startTag('merchantActivityId'); |
1667
|
0
|
|
|
|
|
|
$writer->characters( $content{'merchant_activity_id'} ); |
1668
|
0
|
|
|
|
|
|
$writer->endTag('merchantActivityId'); |
1669
|
|
|
|
|
|
|
|
1670
|
0
|
|
|
|
|
|
$writer->startTag('activity'); |
1671
|
0
|
|
|
|
|
|
$writer->characters( $content{'activity'} ); |
1672
|
0
|
|
|
|
|
|
$writer->endTag('activity'); |
1673
|
|
|
|
|
|
|
|
1674
|
0
|
|
|
|
|
|
$writer->endTag('caseUpdate'); |
1675
|
0
|
|
|
|
|
|
$writer->endTag("litleChargebackUpdateRequest"); |
1676
|
0
|
|
|
|
|
|
$writer->end(); |
1677
|
|
|
|
|
|
|
## END XML Generation |
1678
|
|
|
|
|
|
|
|
1679
|
0
|
|
|
|
|
|
$self->{'_post_data'} = $post_data; |
1680
|
0
|
0
|
|
|
|
|
warn $self->{'_post_data'} if $DEBUG; |
1681
|
|
|
|
|
|
|
#my ( $page, $status_code, %headers ) = $self->https_post($post_data); |
1682
|
0
|
|
|
|
|
|
my $url = 'https://'.$self->chargeback_server.':'.$self->chargeback_port.'/'.$self->chargeback_path; |
1683
|
0
|
|
|
|
|
|
my $tiny_response = HTTP::Tiny->new( verify_SSL=>$self->verify_SSL )->request('POST', $url, { |
1684
|
|
|
|
|
|
|
headers => { 'Content-Type' => 'text/xml; charset=utf-8', }, |
1685
|
|
|
|
|
|
|
content => $post_data, |
1686
|
|
|
|
|
|
|
} ); |
1687
|
|
|
|
|
|
|
|
1688
|
0
|
|
|
|
|
|
my $page = $tiny_response->{'content'}; |
1689
|
0
|
|
|
|
|
|
$self->server_response( $page ); |
1690
|
0
|
|
|
|
|
|
my $status_code = $tiny_response->{'status'}; |
1691
|
0
|
|
|
|
|
|
my %headers = %{$tiny_response->{'headers'}}; |
|
0
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
|
1693
|
0
|
0
|
|
|
|
|
warn Dumper $page, $status_code, \%headers if $DEBUG; |
1694
|
|
|
|
|
|
|
|
1695
|
0
|
|
|
|
|
|
my $response = {}; |
1696
|
0
|
0
|
|
|
|
|
if ( $status_code =~ /^200/ ) { |
1697
|
|
|
|
|
|
|
## Failed to parse |
1698
|
0
|
0
|
|
|
|
|
if ( !eval { $response = XMLin($page); } ) { |
|
0
|
|
|
|
|
|
|
1699
|
0
|
|
|
|
|
|
die "XML PARSING FAILURE: $@, $page"; |
1700
|
|
|
|
|
|
|
} ## well-formed failure message |
1701
|
0
|
|
|
|
|
|
$self->{_response} = $response; |
1702
|
0
|
0
|
|
|
|
|
if ( exists( $response->{'response'} ) ) { |
1703
|
|
|
|
|
|
|
## parse error type error |
1704
|
0
|
|
|
|
|
|
warn Dumper( $response, $self->{'_post_data'} ); |
1705
|
0
|
|
|
|
|
|
$self->result_code( $response->{'response'} ); # 0 - success, 1 invalid xml |
1706
|
0
|
|
|
|
|
|
$self->error_message( $response->{'message'} ); |
1707
|
0
|
|
|
|
|
|
$self->phoenixTxnId( $response->{'caseUpdateResponse'}{'phoenixTxnId'} ); |
1708
|
0
|
|
|
|
|
|
$self->is_success(1); |
1709
|
0
|
|
|
|
|
|
return $response->{'caseUpdateResponse'}{'phoenixTxnId'}; |
1710
|
|
|
|
|
|
|
} |
1711
|
|
|
|
|
|
|
else { |
1712
|
0
|
|
|
|
|
|
die "UNKNOWN XML RESULT: $page"; |
1713
|
|
|
|
|
|
|
} |
1714
|
|
|
|
|
|
|
} |
1715
|
|
|
|
|
|
|
else { |
1716
|
0
|
|
|
|
|
|
$status_code =~ s/[\r\n\s]+$// |
1717
|
|
|
|
|
|
|
; # remove newline so you can see the error in a linux console |
1718
|
0
|
0
|
|
|
|
|
if ( $status_code =~ /^(?:900|599)/ ) { |
1719
|
0
|
|
|
|
|
|
$status_code .= ' - verify Litle has whitelisted your IP'; |
1720
|
|
|
|
|
|
|
} |
1721
|
0
|
|
|
|
|
|
die "CONNECTION FAILURE: $status_code"; |
1722
|
|
|
|
|
|
|
} |
1723
|
|
|
|
|
|
|
} |
1724
|
|
|
|
|
|
|
|
1725
|
|
|
|
|
|
|
|
1726
|
|
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
1; # End of Business::OnlinePayment::Litle |
1728
|
|
|
|
|
|
|
|
1729
|
|
|
|
|
|
|
__END__ |