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__ |