line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Business::OnlinePayment::WorldPay; |
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
39424
|
use 5.008008; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
54
|
|
4
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
33
|
|
5
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
14
|
|
|
1
|
|
|
|
|
27
|
|
6
|
1
|
|
|
1
|
|
6
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
128
|
|
7
|
|
|
|
|
|
|
|
8
|
1
|
|
|
1
|
|
854
|
use Template; # construct XML requests |
|
1
|
|
|
|
|
1055105
|
|
|
1
|
|
|
|
|
33
|
|
9
|
1
|
|
|
1
|
|
509
|
use XML::TreeBuilder; # parse XML responses |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
use Net::SSLeay qw(post_https make_headers); # submit requests |
12
|
|
|
|
|
|
|
use MIME::Base64; # basic authentication |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use base qw(Exporter Business::OnlinePayment); # Exporter just for $VERSION checking |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '1.00'; |
18
|
|
|
|
|
|
|
our $DEBUG_FH = \*STDERR; # debugging output destination |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# From the "Submitting Transactions in the Direct Model" document |
22
|
|
|
|
|
|
|
# and the WorldPay DTD: http://dtd.worldpay.com/paymentService_v1.dtd |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
my %Payment_Method = ( |
25
|
|
|
|
|
|
|
'visa' => { |
26
|
|
|
|
|
|
|
paymentType => 'VISA-SSL', |
27
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
28
|
|
|
|
|
|
|
}, |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
'amex' => { |
31
|
|
|
|
|
|
|
paymentType => 'AMEX-SSL', |
32
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
33
|
|
|
|
|
|
|
}, |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
'mastercard' => { |
36
|
|
|
|
|
|
|
paymentType => 'ECMC-SSL', |
37
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
38
|
|
|
|
|
|
|
}, |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
'diners card' => { |
41
|
|
|
|
|
|
|
paymentType => 'DINERS-SSL', |
42
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
43
|
|
|
|
|
|
|
}, |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
'solo card' => { |
46
|
|
|
|
|
|
|
paymentType => 'SOLO_GB-SSL', |
47
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# from DTD: (issueNumber | (startDate, issueNumber?) ) |
50
|
|
|
|
|
|
|
# either issue number or start date must be included |
51
|
|
|
|
|
|
|
# have to handle as a special case in sub submit |
52
|
|
|
|
|
|
|
}, |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
'maestro' => { |
55
|
|
|
|
|
|
|
paymentType => 'MAESTRO-SSL', |
56
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# from DTD: startDate?, issueNumber? |
59
|
|
|
|
|
|
|
# issue number and start date both optional (?) |
60
|
|
|
|
|
|
|
}, |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
'elv' => { |
63
|
|
|
|
|
|
|
paymentType => 'ELV-SSL', |
64
|
|
|
|
|
|
|
_required => [ qw/account_holder_name bank_account_number bank_name |
65
|
|
|
|
|
|
|
bank_location bank_location_id/ ], |
66
|
|
|
|
|
|
|
}, |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
'jcb card' => { |
69
|
|
|
|
|
|
|
paymentType => 'JCB-SSL', |
70
|
|
|
|
|
|
|
_required => [ qw/card_number exp_date name/ ], |
71
|
|
|
|
|
|
|
}, |
72
|
|
|
|
|
|
|
); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
$Payment_Method{diners} = $Payment_Method{'diners card'}; |
75
|
|
|
|
|
|
|
$Payment_Method{jcb} = $Payment_Method{'jcb card'}; |
76
|
|
|
|
|
|
|
$Payment_Method{solo} = $Payment_Method{'solo card'}; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
my %Server = ( |
80
|
|
|
|
|
|
|
live => { |
81
|
|
|
|
|
|
|
server => 'secure.ims.worldpay.com', |
82
|
|
|
|
|
|
|
path => '/jsp/merchant/xml/paymentService.jsp', |
83
|
|
|
|
|
|
|
port => 443, |
84
|
|
|
|
|
|
|
}, |
85
|
|
|
|
|
|
|
test => { |
86
|
|
|
|
|
|
|
server => 'secure-test.wp3.rbsworldpay.com', |
87
|
|
|
|
|
|
|
path => '/jsp/merchant/xml/paymentService.jsp', |
88
|
|
|
|
|
|
|
port => 443, |
89
|
|
|
|
|
|
|
}, |
90
|
|
|
|
|
|
|
); |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub debug |
94
|
|
|
|
|
|
|
{ |
95
|
|
|
|
|
|
|
my ($self, $value, $filename) = @_; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
$self->{debug} = $value if defined $value; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
if ( $filename ) { |
100
|
|
|
|
|
|
|
if (open(my $fh, ">>", $filename)) { |
101
|
|
|
|
|
|
|
my $old_fh = select($fh); |
102
|
|
|
|
|
|
|
$| = 1; # output autoflush |
103
|
|
|
|
|
|
|
select($old_fh); |
104
|
|
|
|
|
|
|
$DEBUG_FH = $fh; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
else { |
107
|
|
|
|
|
|
|
carp "cannot open debugging file $filename: $!\n"; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
return $self->{debug}; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub set_server |
116
|
|
|
|
|
|
|
{ |
117
|
|
|
|
|
|
|
my ($self, $server_type) = @_; |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
$self->{'_server'} = $server_type; # 'live' or 'test' |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
$self->server( $Server{$server_type}->{'server'} ); |
122
|
|
|
|
|
|
|
$self->path( $Server{$server_type}->{'path'} ); |
123
|
|
|
|
|
|
|
$self->port( $Server{$server_type}->{'port'} ); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
sub set_defaults # called by B::OP constructor |
128
|
|
|
|
|
|
|
{ |
129
|
|
|
|
|
|
|
my $self = shift; |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$self->{'_content'} = {}; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
_launder_envariables(); # clean up WORLDPAY_* variables |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# B::OP creates the following accessors: |
136
|
|
|
|
|
|
|
# server, port, path, test_transaction, transaction_type, |
137
|
|
|
|
|
|
|
# server_response, is_success, authorization, |
138
|
|
|
|
|
|
|
# result_code, error_message, |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# let's create some more: |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
$self->build_subs( |
143
|
|
|
|
|
|
|
qw/ |
144
|
|
|
|
|
|
|
installation login password version currency action |
145
|
|
|
|
|
|
|
cvv_response avs_response risk_score last_event |
146
|
|
|
|
|
|
|
/ |
147
|
|
|
|
|
|
|
); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$self->test_transaction(0); # default to live server |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
$self->version('1.4'); # paymentService "version" attribute |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
$self->action('payment'); # default action is payment |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
$self->currency('EUR'); # default value for currencyCode |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
if ($ENV{WORLDPAY_DEBUG}) { |
158
|
|
|
|
|
|
|
if ($ENV{WORLDPAY_DEBUG} =~ /^\d+$/) { |
159
|
|
|
|
|
|
|
$self->debug( 1 ); |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
else { |
162
|
|
|
|
|
|
|
$self->debug( 1, $ENV{WORLDPAY_DEBUG} ); |
163
|
|
|
|
|
|
|
} |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub submit |
169
|
|
|
|
|
|
|
{ |
170
|
|
|
|
|
|
|
my $self = shift; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
my %content = $self->content; |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
if ($content{action}) { |
175
|
|
|
|
|
|
|
$content{action} = 'payment' |
176
|
|
|
|
|
|
|
if $content{action} =~ /^ \s* normal \s+ authori[zs]ation \s* $/ix; |
177
|
|
|
|
|
|
|
$self->action( lc $content{action} ); |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
################ initialize object ################ |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# standard B::OP attributes |
183
|
|
|
|
|
|
|
$self->$_( '' ) for qw/is_success |
184
|
|
|
|
|
|
|
authorization |
185
|
|
|
|
|
|
|
result_code |
186
|
|
|
|
|
|
|
error_message |
187
|
|
|
|
|
|
|
server |
188
|
|
|
|
|
|
|
port |
189
|
|
|
|
|
|
|
path |
190
|
|
|
|
|
|
|
server_response/; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
$self->transaction_type( '' ) unless $self->transaction_type; |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# additional B::OP::WorldPay attributes |
195
|
|
|
|
|
|
|
$self->$_( '' ) for qw/last_event |
196
|
|
|
|
|
|
|
cvv_response |
197
|
|
|
|
|
|
|
avs_response |
198
|
|
|
|
|
|
|
risk_score/; |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
$self->set_server( $self->test_transaction ? 'test' : 'live' ); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
$self->installation( $content{installation} || $ENV{WORLDPAY_INSTALLATION_ID} ); |
203
|
|
|
|
|
|
|
$self->login( $content{login} || $ENV{WORLDPAY_MERCHANT_CODE} ); |
204
|
|
|
|
|
|
|
$self->password( $content{password} || $ENV{WORLDPAY_XML_PASSWORD} ); |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
if ($self->debug) { |
207
|
|
|
|
|
|
|
print $DEBUG_FH "\n"; |
208
|
|
|
|
|
|
|
print $DEBUG_FH '*' x 80, "\n\n"; |
209
|
|
|
|
|
|
|
print $DEBUG_FH "installation: ", $self->installation, "\n"; |
210
|
|
|
|
|
|
|
print $DEBUG_FH "login: ", $self->login, "\n"; |
211
|
|
|
|
|
|
|
print $DEBUG_FH "password: ", $self->password, "\n"; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
##### get template & variables for xml request #### |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my ($xml_template, $template_vars); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
my $action = $self->action; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
if ($action eq 'status' || $action eq 'cancel' || $action eq 'cancel_or_refund') { |
221
|
|
|
|
|
|
|
($xml_template, $template_vars) = $self->status_inquiry_or_cancel; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
elsif ( $action eq 'refund' ) { |
224
|
|
|
|
|
|
|
($xml_template, $template_vars) = $self->refund; |
225
|
|
|
|
|
|
|
} |
226
|
|
|
|
|
|
|
elsif ($Payment_Method{lc $content{type}}{paymentType} eq 'ELV-SSL') { |
227
|
|
|
|
|
|
|
($xml_template, $template_vars) = $self->elv_payment; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
else { |
230
|
|
|
|
|
|
|
($xml_template, $template_vars) = $self->payment; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
############### create XML request ################ |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
my $post_data_xml; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
my $tt = Template->new(); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
$tt->process( \$xml_template, $template_vars, \$post_data_xml ) || |
240
|
|
|
|
|
|
|
croak $tt->error(); |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
if ($self->debug) { |
243
|
|
|
|
|
|
|
print $DEBUG_FH "\n", "=" x 80, "\n"; |
244
|
|
|
|
|
|
|
print $DEBUG_FH " " x 35, "REQUEST:\n"; |
245
|
|
|
|
|
|
|
print $DEBUG_FH "=" x 80, "\n\n"; |
246
|
|
|
|
|
|
|
print $DEBUG_FH "post_data_xml = \n$post_data_xml\n"; |
247
|
|
|
|
|
|
|
print $DEBUG_FH "POST_HTTPS: ", $self->server . $self->path, "\n"; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
################# submit request ################## |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
my ($page, $response, %headers) = post_https( |
253
|
|
|
|
|
|
|
$self->server, |
254
|
|
|
|
|
|
|
$self->port, |
255
|
|
|
|
|
|
|
$self->path, |
256
|
|
|
|
|
|
|
make_headers( |
257
|
|
|
|
|
|
|
Authorization => 'Basic ' . MIME::Base64::encode( |
258
|
|
|
|
|
|
|
$self->login . ":" . $self->password, '' |
259
|
|
|
|
|
|
|
) |
260
|
|
|
|
|
|
|
), |
261
|
|
|
|
|
|
|
$post_data_xml, |
262
|
|
|
|
|
|
|
); |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
################ examine response ################# |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
if ($self->debug) { |
267
|
|
|
|
|
|
|
my $prettier_xml; |
268
|
|
|
|
|
|
|
($prettier_xml = $page) =~ s/>>\n
|
269
|
|
|
|
|
|
|
print $DEBUG_FH "\n", "=" x 80, "\n"; |
270
|
|
|
|
|
|
|
print $DEBUG_FH " " x 35, "RESPONSE:\n"; |
271
|
|
|
|
|
|
|
print $DEBUG_FH "=" x 80, "\n\n"; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
print $DEBUG_FH "$response\n\n"; |
274
|
|
|
|
|
|
|
print $DEBUG_FH "HEADERS:\n"; |
275
|
|
|
|
|
|
|
print $DEBUG_FH " $_: $headers{$_}\n" for sort keys %headers; |
276
|
|
|
|
|
|
|
print $DEBUG_FH "\nXML:\n$prettier_xml\n"; |
277
|
|
|
|
|
|
|
print $DEBUG_FH "-" x 80, "\n\n"; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
$self->server_response( $response ); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
if ($self->server_response =~ /\b401 Authorization Required/) { |
283
|
|
|
|
|
|
|
$self->is_success(0); |
284
|
|
|
|
|
|
|
$self->error_message('Authorization Required'); |
285
|
|
|
|
|
|
|
return; |
286
|
|
|
|
|
|
|
} |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
unless ($page && $self->server_response =~ /\b200 OK/) { |
289
|
|
|
|
|
|
|
$self->is_success(0); |
290
|
|
|
|
|
|
|
$self->error_message( |
291
|
|
|
|
|
|
|
'There was a problem communicating with the payment server. ' . |
292
|
|
|
|
|
|
|
'Please try again.' |
293
|
|
|
|
|
|
|
); |
294
|
|
|
|
|
|
|
return; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
############### parse XML response ################ |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
my $xml = XML::TreeBuilder->new; |
300
|
|
|
|
|
|
|
$xml->parse($page); |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
if ($self->debug) { |
303
|
|
|
|
|
|
|
print $DEBUG_FH "XML::TreeBuilder dump:\n"; |
304
|
|
|
|
|
|
|
$xml->dump($DEBUG_FH); |
305
|
|
|
|
|
|
|
print $DEBUG_FH "\n"; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# structure of the XML response varies |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
my ( |
311
|
|
|
|
|
|
|
$error, $last_event, $return_code, $cvc_result, $avs_response, $risk, $ok, |
312
|
|
|
|
|
|
|
); |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# response for various errors: |
315
|
|
|
|
|
|
|
if ($error = $xml->find('error')) { |
316
|
|
|
|
|
|
|
$self->is_success(0); |
317
|
|
|
|
|
|
|
$self->authorization( 'ERROR' ); |
318
|
|
|
|
|
|
|
$self->error_message( $error->as_text ); |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# response for payment requests or status inquiries: |
322
|
|
|
|
|
|
|
elsif ($last_event = $xml->find('lastEvent')) { # response for payments |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
$self->last_event( $last_event->as_text ); |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
if ($last_event->as_text eq 'AUTHORISED') { # UK spelling |
327
|
|
|
|
|
|
|
$self->is_success(1); |
328
|
|
|
|
|
|
|
$self->authorization( 'AUTHORIZED' ); # switching to US spelling |
329
|
|
|
|
|
|
|
$self->last_event( 'AUTHORIZED' ); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
elsif ($last_event->as_text eq 'REFUSED') { |
333
|
|
|
|
|
|
|
if ($return_code = $xml->find('ISO8583ReturnCode')) { |
334
|
|
|
|
|
|
|
$self->is_success(0); |
335
|
|
|
|
|
|
|
$self->authorization( 'REFUSED' ); |
336
|
|
|
|
|
|
|
$self->result_code( $return_code->attr('code') ); |
337
|
|
|
|
|
|
|
$self->error_message( $return_code->attr('description') ); |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
# if needed, can provide special handling for other last_event values: |
342
|
|
|
|
|
|
|
# CANCELLED, CAPTURED, SETTLED, SETTLED_BY_MERCHANT, |
343
|
|
|
|
|
|
|
# SENT_FOR_REFUND, REFUNDED_BY_MERCHANT, CHARGED_BACK |
344
|
|
|
|
|
|
|
# |
345
|
|
|
|
|
|
|
# elsif ($last_event->as_text eq 'XXX') { |
346
|
|
|
|
|
|
|
# } |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
else { |
349
|
|
|
|
|
|
|
$self->is_success(1); |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# get CVCResultCode description if present |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
if ($cvc_result = $xml->find('CVCResultCode')) { |
355
|
|
|
|
|
|
|
$self->cvv_response( $cvc_result->attr('description') ); |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
# get AVSResultCode description if present |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
if ($avs_response = $xml->find('AVSResultCode')) { |
361
|
|
|
|
|
|
|
$self->avs_response( $avs_response->attr('description') ); |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# get riskScore if present |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
if ($risk = $xml->find('riskScore')) { |
367
|
|
|
|
|
|
|
$self->risk_score( $risk->attr('value') ); |
368
|
|
|
|
|
|
|
} |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
# response for cancel request: |
373
|
|
|
|
|
|
|
elsif ( $ok = $xml->find('ok') and $ok->find('cancelReceived') ) { |
374
|
|
|
|
|
|
|
$self->is_success(1); |
375
|
|
|
|
|
|
|
$self->authorization( 'AUTHORIZED' ); |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# response for refund request: |
379
|
|
|
|
|
|
|
elsif ( $ok = $xml->find('ok') and $ok->find('refundReceived') ) { |
380
|
|
|
|
|
|
|
$self->is_success(1); |
381
|
|
|
|
|
|
|
$self->authorization( 'AUTHORIZED' ); |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# response for UNDOCUMENTED cancelOrRefund request: |
385
|
|
|
|
|
|
|
elsif ( $ok = $xml->find('ok') and $ok->find('voidReceived') ) { |
386
|
|
|
|
|
|
|
$self->is_success(1); |
387
|
|
|
|
|
|
|
$self->authorization( 'AUTHORIZED' ); |
388
|
|
|
|
|
|
|
} |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
$xml->delete; |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
if ($self->debug) { |
393
|
|
|
|
|
|
|
print $DEBUG_FH "\n", "=" x 80, "\n"; |
394
|
|
|
|
|
|
|
print $DEBUG_FH " " x 35, "RESULTS:\n"; |
395
|
|
|
|
|
|
|
print $DEBUG_FH "=" x 80, "\n\n"; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
print $DEBUG_FH "Standard B::OP attributes:\n"; |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
print $DEBUG_FH "test_transaction = ", $self->test_transaction, "\n"; |
400
|
|
|
|
|
|
|
print $DEBUG_FH "transaction_type = ", $self->transaction_type, "\n"; |
401
|
|
|
|
|
|
|
print $DEBUG_FH "is_success = ", $self->is_success, "\n"; |
402
|
|
|
|
|
|
|
print $DEBUG_FH "authorization = ", $self->authorization, "\n"; |
403
|
|
|
|
|
|
|
print $DEBUG_FH "result_code = ", $self->result_code, "\n"; |
404
|
|
|
|
|
|
|
print $DEBUG_FH "error_message = ", $self->error_message, "\n"; |
405
|
|
|
|
|
|
|
print $DEBUG_FH "server = ", $self->server, "\n"; |
406
|
|
|
|
|
|
|
print $DEBUG_FH "port = ", $self->port, "\n"; |
407
|
|
|
|
|
|
|
print $DEBUG_FH "path = ", $self->path, "\n"; |
408
|
|
|
|
|
|
|
print $DEBUG_FH "server_response = ", $self->server_response, "\n\n"; |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
print $DEBUG_FH "Additional B::OP::WorldPay attributes:\n"; |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
print $DEBUG_FH "cvv_response = ", $self->cvv_response, "\n"; |
413
|
|
|
|
|
|
|
print $DEBUG_FH "avs_response = ", $self->avs_response, "\n"; |
414
|
|
|
|
|
|
|
print $DEBUG_FH "risk_score = ", $self->risk_score, "\n\n"; |
415
|
|
|
|
|
|
|
print $DEBUG_FH "last_event = ", $self->last_event, "\n\n"; |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
print $DEBUG_FH "-" x 80, "\n\n"; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub payment |
423
|
|
|
|
|
|
|
{ |
424
|
|
|
|
|
|
|
my $self = shift; |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
my %content = $self->content; |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
$self->required_fields( 'type' ); |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
croak("unrecognized credit card type: $content{type}\n") unless $Payment_Method{lc $content{type}}; |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
if ($content{type} =~ /^solo/i) { |
433
|
|
|
|
|
|
|
croak("missing required field issue_number or start_date") |
434
|
|
|
|
|
|
|
unless exists $content{issue_number} || exists $content{start_date}; |
435
|
|
|
|
|
|
|
} |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
my @required_fields = @{ $Payment_Method{ lc $content{type} }{_required} }; |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
$self->required_fields( @required_fields ); |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
########### initialize template variables ######### |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
$self->currency( $content{currency} ) if $content{currency}; |
444
|
|
|
|
|
|
|
my $currency = $self->currency; |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
my $amount_value = $content{amount}; |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
# amount must be specified with an "exponent" |
449
|
|
|
|
|
|
|
# Example: $123.45 would be specified as value = "12345", exponent = "2" |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
my $amount_exponent = 2; # works for all but Indonesian Rupiah |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
if ($amount_value) { |
454
|
|
|
|
|
|
|
$amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45 |
455
|
|
|
|
|
|
|
$amount_value =~ s/,//g; # 12,345.46 => 12345.46 |
456
|
|
|
|
|
|
|
$amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00 |
457
|
|
|
|
|
|
|
$amount_value =~ s/\.//g; # 123.00 => 12300 |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# strip non-digits from card number |
461
|
|
|
|
|
|
|
my $card_number = ''; |
462
|
|
|
|
|
|
|
if ( $content{card_number} ) { |
463
|
|
|
|
|
|
|
( $card_number = $content{card_number} ) =~ s/\D//g; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# separate month and year values for expiryDate |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
my ($exp_month, $exp_year); |
469
|
|
|
|
|
|
|
if ( $content{exp_date} ) { |
470
|
|
|
|
|
|
|
($exp_month, $exp_year) = split /\//, $content{exp_date}; |
471
|
|
|
|
|
|
|
$exp_month = sprintf "%02d", $exp_month; |
472
|
|
|
|
|
|
|
$exp_year = $exp_year < 100 ? 2000 + $exp_year : $exp_year; # Y3K problem |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# Solo & Maestro cards may have startDate |
476
|
|
|
|
|
|
|
# separate month and year values for startDate |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
my ($start_month, $start_year); |
479
|
|
|
|
|
|
|
if ($content{start_date}) { |
480
|
|
|
|
|
|
|
($start_month, $start_year) = split /\//, $content{start_date}; |
481
|
|
|
|
|
|
|
$start_month = sprintf "%02d", $start_month; |
482
|
|
|
|
|
|
|
$start_year = $start_year < 100 ? 2000 + $start_year : $start_year; # Y3K problem |
483
|
|
|
|
|
|
|
} |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
# get first and last names from cardHolderName |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
my ($first_name, $last_name); |
488
|
|
|
|
|
|
|
if ($content{name}) { |
489
|
|
|
|
|
|
|
my @names = split ' ', $content{name}; |
490
|
|
|
|
|
|
|
$last_name = pop @names; |
491
|
|
|
|
|
|
|
$first_name = join ' ' => @names; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# WorldPay suggests putting the whole address in the street field |
495
|
|
|
|
|
|
|
# with the exception of the postal and country codes |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
my @address; |
498
|
|
|
|
|
|
|
push @address, $content{address} if $content{address}; |
499
|
|
|
|
|
|
|
push @address, $content{city} if $content{city}; |
500
|
|
|
|
|
|
|
push @address, $content{state} if $content{state}; |
501
|
|
|
|
|
|
|
my $street = join ', ' => @address; |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
########### get XML template for request ########## |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
my $xml_template = _get_xml_template( $self->action ); |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
########### initialize data for template ########## |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
my $template_vars = { |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
merchantCode => $self->login, |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
version => $self->version, |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
orderCode => $content{order_number}, |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
installationId => $self->installation, |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
description => $content{description}, |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
amount => { |
522
|
|
|
|
|
|
|
value => $amount_value, |
523
|
|
|
|
|
|
|
currencyCode => $self->currency, |
524
|
|
|
|
|
|
|
exponent => $amount_exponent, |
525
|
|
|
|
|
|
|
}, |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
paymentDetails => { |
528
|
|
|
|
|
|
|
paymentType => $Payment_Method{lc $content{type}}{paymentType}, |
529
|
|
|
|
|
|
|
action => 'AUTHORISE', |
530
|
|
|
|
|
|
|
cardNumber => $card_number, |
531
|
|
|
|
|
|
|
expiryDate => { |
532
|
|
|
|
|
|
|
month => $exp_month, |
533
|
|
|
|
|
|
|
year => $exp_year, |
534
|
|
|
|
|
|
|
}, |
535
|
|
|
|
|
|
|
cardHolderName => $content{name}, |
536
|
|
|
|
|
|
|
issueNumber => $content{issue_number}, |
537
|
|
|
|
|
|
|
startDate => { |
538
|
|
|
|
|
|
|
month => $start_month, |
539
|
|
|
|
|
|
|
year => $start_year, |
540
|
|
|
|
|
|
|
}, |
541
|
|
|
|
|
|
|
cvc => $content{cvc}, |
542
|
|
|
|
|
|
|
cardAddress => { |
543
|
|
|
|
|
|
|
firstName => $content{first_name}, |
544
|
|
|
|
|
|
|
lastName => $content{last_name}, |
545
|
|
|
|
|
|
|
street => $street, |
546
|
|
|
|
|
|
|
postalCode => $content{zip}, |
547
|
|
|
|
|
|
|
countryCode => $content{country}, |
548
|
|
|
|
|
|
|
telephoneNumber => $content{phone}, |
549
|
|
|
|
|
|
|
}, |
550
|
|
|
|
|
|
|
}, |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
# required for 3D secure authentication |
553
|
|
|
|
|
|
|
session => { |
554
|
|
|
|
|
|
|
shopperIPAddress => $content{ip_address}, |
555
|
|
|
|
|
|
|
id => $content{session_id}, |
556
|
|
|
|
|
|
|
}, |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
# required for 3D secure authentication |
559
|
|
|
|
|
|
|
shopper => { |
560
|
|
|
|
|
|
|
acceptHeader => $content{accept_header}, |
561
|
|
|
|
|
|
|
userAgentHeader => $content{user_agent}, |
562
|
|
|
|
|
|
|
}, |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
}; |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
return ($xml_template, $template_vars); |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
sub elv_payment |
571
|
|
|
|
|
|
|
{ |
572
|
|
|
|
|
|
|
my $self = shift; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
my %content = $self->content; |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
$self->required_fields( 'type' ); |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
my @required_fields = @{ $Payment_Method{ lc $content{type} }{_required} }; |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
$self->required_fields( @required_fields ); |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
########### initialize template variables ######### |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
$self->currency( $content{currency} ) if $content{currency}; |
585
|
|
|
|
|
|
|
my $currency = $self->currency; |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
my $amount_value = $content{amount}; |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
# amount must be specified with an "exponent" |
590
|
|
|
|
|
|
|
# Example: $123.45 would be specified as value = "12345", exponent = "2" |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
my $amount_exponent = 2; # works for all but Indonesian Rupiah |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
if ($amount_value) { |
595
|
|
|
|
|
|
|
$amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45 |
596
|
|
|
|
|
|
|
$amount_value =~ s/,//g; # 12,345.46 => 12345.46 |
597
|
|
|
|
|
|
|
$amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00 |
598
|
|
|
|
|
|
|
$amount_value =~ s/\.//g; # 123.00 => 12300 |
599
|
|
|
|
|
|
|
} |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# strip non-digits from bank account number |
602
|
|
|
|
|
|
|
my $bank_account_number = ''; |
603
|
|
|
|
|
|
|
if ( $content{bank_account_number} ) { |
604
|
|
|
|
|
|
|
( $bank_account_number = $content{bank_account_number} ) =~ s/\D//g; |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
########### get XML template for request ########## |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
my $xml_template = _get_xml_template( $self->action, 'ELV-SSL' ); |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
########### initialize data for template ########## |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
my $template_vars = { |
614
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
merchantCode => $self->login, |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
version => $self->version, |
618
|
|
|
|
|
|
|
|
619
|
|
|
|
|
|
|
orderCode => $content{order_number}, |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
installationId => $self->installation, |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
description => $content{description}, |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
amount => { |
626
|
|
|
|
|
|
|
value => $amount_value, |
627
|
|
|
|
|
|
|
currencyCode => $self->currency, |
628
|
|
|
|
|
|
|
exponent => $amount_exponent, |
629
|
|
|
|
|
|
|
}, |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
paymentDetails => { |
632
|
|
|
|
|
|
|
paymentType => $Payment_Method{lc $content{type}}{paymentType}, |
633
|
|
|
|
|
|
|
action => 'AUTHORISE', |
634
|
|
|
|
|
|
|
bankAccountNr => $bank_account_number, |
635
|
|
|
|
|
|
|
bankName => $content{bank_name}, |
636
|
|
|
|
|
|
|
accountHolderName => $content{account_holder_name}, |
637
|
|
|
|
|
|
|
bankLocation => $content{bank_location}, |
638
|
|
|
|
|
|
|
bankLocationId => $content{bank_location_id}, |
639
|
|
|
|
|
|
|
}, |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
# required for 3D secure authentication |
642
|
|
|
|
|
|
|
session => { |
643
|
|
|
|
|
|
|
shopperIPAddress => $content{ip_address}, |
644
|
|
|
|
|
|
|
id => $content{session_id}, |
645
|
|
|
|
|
|
|
}, |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# required for 3D secure authentication |
648
|
|
|
|
|
|
|
shopper => { |
649
|
|
|
|
|
|
|
acceptHeader => $content{accept_header}, |
650
|
|
|
|
|
|
|
userAgentHeader => $content{user_agent}, |
651
|
|
|
|
|
|
|
}, |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
}; |
654
|
|
|
|
|
|
|
|
655
|
|
|
|
|
|
|
return ($xml_template, $template_vars); |
656
|
|
|
|
|
|
|
} |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
sub status_inquiry_or_cancel |
660
|
|
|
|
|
|
|
{ |
661
|
|
|
|
|
|
|
my $self = shift; |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
my %content = $self->content; |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
$self->required_fields( qw/order_number/ ); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
########### get XML template for request ########## |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
my $xml_template = _get_xml_template( $self->action ); |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
########### initialize data for template ########## |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
my $template_vars = { |
674
|
|
|
|
|
|
|
merchantCode => $self->login, |
675
|
|
|
|
|
|
|
version => $self->version, |
676
|
|
|
|
|
|
|
orderCode => $content{order_number}, |
677
|
|
|
|
|
|
|
}; |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
return ($xml_template, $template_vars); |
680
|
|
|
|
|
|
|
} |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
sub refund |
684
|
|
|
|
|
|
|
{ |
685
|
|
|
|
|
|
|
my $self = shift; |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
my %content = $self->content; |
688
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
$self->required_fields( qw/order_number amount currency/ ); |
690
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
########### initialize template variables ######### |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
$self->currency( $content{currency} ) if $content{currency}; |
694
|
|
|
|
|
|
|
my $currency = $self->currency; |
695
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
my $amount_value = $content{amount}; |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# amount must be specified with an "exponent" |
699
|
|
|
|
|
|
|
# Example: $123.45 would be specified as value = "12345", exponent = "2" |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
my $amount_exponent = 2; # works for all but Indonesian Rupiah |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
$amount_value =~ s/,(?=\d\d$)/./g; # 123,45 => 123.45 |
704
|
|
|
|
|
|
|
$amount_value =~ s/,//g; # 12,345.46 => 12345.46 |
705
|
|
|
|
|
|
|
$amount_value = sprintf "%.2f", $amount_value; # 123 => 123.00 |
706
|
|
|
|
|
|
|
$amount_value =~ s/\.//g; # 123.00 => 12300 |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
########### get XML template for request ########## |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
my $xml_template = _get_xml_template( $self->action ); |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
########### initialize data for template ########## |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
my $template_vars = { |
715
|
|
|
|
|
|
|
merchantCode => $self->login, |
716
|
|
|
|
|
|
|
version => $self->version, |
717
|
|
|
|
|
|
|
orderCode => $content{order_number}, |
718
|
|
|
|
|
|
|
amount => { |
719
|
|
|
|
|
|
|
value => $amount_value, |
720
|
|
|
|
|
|
|
currencyCode => $self->currency, |
721
|
|
|
|
|
|
|
exponent => $amount_exponent, |
722
|
|
|
|
|
|
|
}, |
723
|
|
|
|
|
|
|
}; |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
return ($xml_template, $template_vars); |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
sub _launder_envariables |
730
|
|
|
|
|
|
|
{ |
731
|
|
|
|
|
|
|
my ($installation_id, $merchant_code, $xml_password, $debug); |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
if ( $ENV{WORLDPAY_INSTALLATION_ID} ) { # must be all digits |
734
|
|
|
|
|
|
|
( $installation_id ) = $ENV{WORLDPAY_INSTALLATION_ID} =~ m/^ ( \d+ ) $/x; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
if ( $ENV{WORLDPAY_MERCHANT_CODE} ) { # must be alphanumeric |
738
|
|
|
|
|
|
|
( $merchant_code ) = $ENV{WORLDPAY_MERCHANT_CODE} =~ m/^ ( \w+ ) $/x; |
739
|
|
|
|
|
|
|
} |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
if ( $ENV{WORLDPAY_XML_PASSWORD} ) { # must be alphanumeric |
742
|
|
|
|
|
|
|
( $xml_password ) = $ENV{WORLDPAY_XML_PASSWORD} =~ m/^ ( \w+ ) $/x; |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
if ( $ENV{WORLDPAY_DEBUG} ) { # digits or pathname |
746
|
|
|
|
|
|
|
( $debug ) = $ENV{WORLDPAY_DEBUG} =~ m{^ ( \d+ | [-/.\w]+ ) $}x; |
747
|
|
|
|
|
|
|
} |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
@ENV{qw/ |
750
|
|
|
|
|
|
|
WORLDPAY_INSTALLATION_ID |
751
|
|
|
|
|
|
|
WORLDPAY_MERCHANT_CODE |
752
|
|
|
|
|
|
|
WORLDPAY_XML_PASSWORD |
753
|
|
|
|
|
|
|
WORLDPAY_DEBUG |
754
|
|
|
|
|
|
|
/} = ( |
755
|
|
|
|
|
|
|
$installation_id || '', |
756
|
|
|
|
|
|
|
$merchant_code || '', |
757
|
|
|
|
|
|
|
$xml_password || '', |
758
|
|
|
|
|
|
|
$debug || '', |
759
|
|
|
|
|
|
|
); |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
return; |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
sub required_fields |
766
|
|
|
|
|
|
|
{ |
767
|
|
|
|
|
|
|
my($self, @fields) = @_; |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
my %content = $self->content(); |
770
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
foreach (@fields) { |
772
|
|
|
|
|
|
|
# croak("missing required field $_") unless exists $content{$_}; # standard B::OP check |
773
|
|
|
|
|
|
|
croak("missing required field $_") unless $content{$_}; # modified for B::OP::WorldPay |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
} |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
sub _get_xml_template |
779
|
|
|
|
|
|
|
{ |
780
|
|
|
|
|
|
|
my ($action, $payment_type) = @_;; |
781
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
my ($payment_xml, $elv_payment_xml, $inquiry_xml, $cancel_xml, $refund_xml, $cancel_or_refund_xml); |
783
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
$payment_xml = <<'EOS'; |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
788
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
[% description %] |
794
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
[%- IF orderContent -%] |
796
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
[%- END %] |
798
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
<[% paymentDetails.paymentType %]> |
800
|
|
|
|
|
|
|
[% paymentDetails.cardNumber %] |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
[% paymentDetails.cardHolderName %] |
805
|
|
|
|
|
|
|
[%- IF paymentDetails.startDate.month && paymentDetails.startDate.year %] |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
[%- END -%] |
810
|
|
|
|
|
|
|
[%- IF paymentDetails.issueNumber %] |
811
|
|
|
|
|
|
|
[% paymentDetails.issueNumber %] |
812
|
|
|
|
|
|
|
[%- END -%] |
813
|
|
|
|
|
|
|
[%- IF paymentDetails.cvc %] |
814
|
|
|
|
|
|
|
[% paymentDetails.cvc %] |
815
|
|
|
|
|
|
|
[%- END -%] |
816
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.firstName || |
817
|
|
|
|
|
|
|
paymentDetails.cardAddress.lastName || |
818
|
|
|
|
|
|
|
paymentDetails.cardAddress.street || |
819
|
|
|
|
|
|
|
paymentDetails.cardAddress.postalCode || |
820
|
|
|
|
|
|
|
paymentDetails.cardAddress.countryCode || |
821
|
|
|
|
|
|
|
paymentDetails.cardAddress.telephoneNumber |
822
|
|
|
|
|
|
|
%] |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.firstName %] |
826
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.firstName %] |
827
|
|
|
|
|
|
|
[%- END -%] |
828
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.lastName %] |
829
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.lastName %] |
830
|
|
|
|
|
|
|
[%- END -%] |
831
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.street %] |
832
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.street %] |
833
|
|
|
|
|
|
|
[%- END -%] |
834
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.postalCode.defined %] |
835
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.postalCode %] |
836
|
|
|
|
|
|
|
[%- END -%] |
837
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.countryCode %] |
838
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.countryCode %] |
839
|
|
|
|
|
|
|
[%- END -%] |
840
|
|
|
|
|
|
|
[%- IF paymentDetails.cardAddress.telephoneNumber %] |
841
|
|
|
|
|
|
|
[% paymentDetails.cardAddress.telephoneNumber %] |
842
|
|
|
|
|
|
|
[%- END %] |
843
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
[%- END %] |
846
|
|
|
|
|
|
|
[% paymentDetails.paymentType %]> |
847
|
|
|
|
|
|
|
[%- IF session.shopperIPAddress && session.id %] |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
[%- END %] |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
[%- IF shopper.acceptHeader && shopper.userAgentHeader %] |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
[% shopper.acceptHeader %] |
855
|
|
|
|
|
|
|
[% shopper.userAgentHeader %] |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
[%- END %] |
859
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
EOS |
863
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
$elv_payment_xml = <<'EOS'; |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
868
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
869
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
[% description %] |
874
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
[%- IF orderContent -%] |
876
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
[%- END %] |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
[% paymentDetails.accountHolderName %] |
881
|
|
|
|
|
|
|
[% paymentDetails.bankAccountNr %] |
882
|
|
|
|
|
|
|
[% paymentDetails.bankName %] |
883
|
|
|
|
|
|
|
[% paymentDetails.bankLocation %] |
884
|
|
|
|
|
|
|
[% paymentDetails.bankLocationId %] |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
|
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
EOS |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
$inquiry_xml = <<'EOS'; |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
896
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
EOS |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
$cancel_xml = <<'EOS'; |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
909
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
EOS |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
$refund_xml = <<'EOS'; |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
924
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
value="[% amount.value %]" |
932
|
|
|
|
|
|
|
currencyCode="[% amount.currencyCode %]" |
933
|
|
|
|
|
|
|
exponent="[% amount.exponent %]" |
934
|
|
|
|
|
|
|
debitCreditIndicator="credit" |
935
|
|
|
|
|
|
|
/> |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
EOS |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
$cancel_or_refund_xml = <<'EOS'; |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
"-//WorldPay/DTD WorldPay PaymentService v1//EN" |
946
|
|
|
|
|
|
|
"http://dtd.wp3.rbsworldpay.com/paymentService_v1.dtd"> |
947
|
|
|
|
|
|
|
|
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
EOS |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
if ($action eq 'status') { |
958
|
|
|
|
|
|
|
return $inquiry_xml; |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
elsif ($action eq 'cancel') { |
961
|
|
|
|
|
|
|
return $cancel_xml; |
962
|
|
|
|
|
|
|
} |
963
|
|
|
|
|
|
|
elsif ($action eq 'refund') { |
964
|
|
|
|
|
|
|
return $refund_xml; |
965
|
|
|
|
|
|
|
} |
966
|
|
|
|
|
|
|
elsif ($action eq 'cancel_or_refund') { |
967
|
|
|
|
|
|
|
return $cancel_or_refund_xml; |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
elsif (defined $payment_type && $payment_type eq 'ELV-SSL') { |
970
|
|
|
|
|
|
|
return $elv_payment_xml; |
971
|
|
|
|
|
|
|
} |
972
|
|
|
|
|
|
|
else { |
973
|
|
|
|
|
|
|
return $payment_xml; |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
} |
976
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
1; |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
=head1 NAME |
980
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
Business::OnlinePayment::WorldPay - RBS WorldPay interface for Business::OnlinePayment |
982
|
|
|
|
|
|
|
|
983
|
|
|
|
|
|
|
=head1 SYNOPSIS |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
use Business::OnlinePayment; |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
my $tx = Business::OnlinePayment->new("WorldPay"); |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
$tx->content( |
990
|
|
|
|
|
|
|
installation => '12345', |
991
|
|
|
|
|
|
|
login => 'testdrive', |
992
|
|
|
|
|
|
|
password => 'xyzzy', |
993
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
type => 'Visa', |
995
|
|
|
|
|
|
|
action => 'payment', # 'status', 'cancel', 'refund', 'cancel_or_refund' |
996
|
|
|
|
|
|
|
description => '20 English Roses', |
997
|
|
|
|
|
|
|
amount => '49.95', |
998
|
|
|
|
|
|
|
currency => 'GBP', |
999
|
|
|
|
|
|
|
order_number => 'A00100', |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
name => 'Claire Voyance', |
1002
|
|
|
|
|
|
|
address => '123 Disk Drive', |
1003
|
|
|
|
|
|
|
city => 'Anywhere', |
1004
|
|
|
|
|
|
|
state => 'DE', |
1005
|
|
|
|
|
|
|
zipcode => '19808', |
1006
|
|
|
|
|
|
|
country => 'US', |
1007
|
|
|
|
|
|
|
phone => '201-555-1212', |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
card_number => '4007000000027', |
1010
|
|
|
|
|
|
|
exp_date => '09/10', |
1011
|
|
|
|
|
|
|
start_date => '01/03', |
1012
|
|
|
|
|
|
|
issue_number => '002', |
1013
|
|
|
|
|
|
|
cvc => '377', |
1014
|
|
|
|
|
|
|
); |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
# ELV payment: |
1017
|
|
|
|
|
|
|
$tx->content( |
1018
|
|
|
|
|
|
|
installation => '12345', |
1019
|
|
|
|
|
|
|
login => 'testdrive', |
1020
|
|
|
|
|
|
|
password => 'xyzzy', |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
type => 'ELV', |
1023
|
|
|
|
|
|
|
action => 'payment', |
1024
|
|
|
|
|
|
|
description => '20 English Roses', |
1025
|
|
|
|
|
|
|
amount => '49.95', |
1026
|
|
|
|
|
|
|
currency => 'GBP', |
1027
|
|
|
|
|
|
|
order_number => 'A00100', |
1028
|
|
|
|
|
|
|
|
1029
|
|
|
|
|
|
|
###################################### |
1030
|
|
|
|
|
|
|
account_holder_name => 'Claire Voyance', |
1031
|
|
|
|
|
|
|
bank_account_number => '92441196', |
1032
|
|
|
|
|
|
|
bank_name => 'Bundesbank', |
1033
|
|
|
|
|
|
|
bank_location => 'Berline', |
1034
|
|
|
|
|
|
|
bank_location_id => '20030000', |
1035
|
|
|
|
|
|
|
###################################### |
1036
|
|
|
|
|
|
|
); |
1037
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
$tx->set_server('test'); # 'live' (default) or 'test' |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
$tx->debug(1); # debugging to STDERR |
1041
|
|
|
|
|
|
|
$tx->debug(1, "/tmp/debugging.outfile"); # debugging output to file |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
$tx->test_transaction(1); # another way to set server to test |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
$tx->submit(); |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
print "server_response = ", $tx->server_response, "\n"; |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
print "is_success = ", $tx->is_success, "\n"; |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
print "authorization = ", $tx->authorization, "\n"; |
1052
|
|
|
|
|
|
|
print "error_message = ", $tx->error_message, "\n\n"; |
1053
|
|
|
|
|
|
|
|
1054
|
|
|
|
|
|
|
print "result_code = ", $tx->result_code, "\n"; |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
print "cvv_response = ", $tx->cvv_response, "\n"; |
1057
|
|
|
|
|
|
|
print "avs_response = ", $tx->avs_response, "\n"; |
1058
|
|
|
|
|
|
|
print "risk_score = ", $tx->risk_score, "\n"; |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
if ($tx->is_success) { |
1061
|
|
|
|
|
|
|
print "Card processed successfully: " . $tx->authorization . "\n"; |
1062
|
|
|
|
|
|
|
} |
1063
|
|
|
|
|
|
|
else { |
1064
|
|
|
|
|
|
|
print "Card was rejected: " . $tx->error_message . "\n"; |
1065
|
|
|
|
|
|
|
} |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
=cut |
1068
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
=head1 DESCRIPTION |
1070
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
This module subclasses Business::OnlinePayment to provide a basic merchant |
1072
|
|
|
|
|
|
|
processing interface for submitting transactions as XML requests in the |
1073
|
|
|
|
|
|
|
direct model provided by RBS WorldPay. |
1074
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
L |
1076
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
L |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
It currently implements payments, cancellations, refunds, and payment status inquiries. |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
Orders submitted to the RBS WorldPay system are required to be valid XML |
1082
|
|
|
|
|
|
|
files as specified in their Document Type Definition (DTD): |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
L |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=head1 BUGS |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
Please report any bugs or feature requests to |
1089
|
|
|
|
|
|
|
C, or through the web interface at |
1090
|
|
|
|
|
|
|
L. |
1091
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on your |
1092
|
|
|
|
|
|
|
bug as I make changes. |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
=head1 SUPPORT |
1095
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
perldoc Business::OnlinePayment::WorldPay |
1099
|
|
|
|
|
|
|
|
1100
|
|
|
|
|
|
|
You can also look for information at: |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
=over 4 |
1103
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
L |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
=item * AnnoCPAN: Annotated CPAN documentation |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
L |
1111
|
|
|
|
|
|
|
|
1112
|
|
|
|
|
|
|
=item * CPAN Ratings |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
L |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
=item * Search CPAN |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
L |
1119
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
=back |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
=head1 SEE ALSO |
1123
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
L |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
=head1 AUTHOR |
1127
|
|
|
|
|
|
|
|
1128
|
|
|
|
|
|
|
Paul Grassie, Epaul.grassie@ardishealth.comE |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
Copyright (C) 2010 Paul Grassie, Ardis Health, http://www.ardishealth.com. All Rights Reserved. |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
1135
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.8.8 or, |
1136
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |