line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# |
2
|
|
|
|
|
|
|
# $Id: PayPal.pm,v 1.5 2007/02/16 04:48:34 plobbes Exp $ |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Business::OnlinePayment::PayPal; |
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
1962
|
use 5.006; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
46
|
|
7
|
1
|
|
|
1
|
|
8
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
30
|
|
8
|
1
|
|
|
1
|
|
20
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
33
|
|
9
|
1
|
|
|
1
|
|
4
|
use base qw(Business::OnlinePayment); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
109
|
|
10
|
1
|
|
|
1
|
|
440
|
use Business::PayPal::API qw(DirectPayments); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = '0.11'; |
13
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Business::OnlinePayment::PayPal - PayPal backend for Business::OnlinePayment |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
use Business::OnlinePayment; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $tx = Business::OnlinePayment->new( |
24
|
|
|
|
|
|
|
"PayPal", |
25
|
|
|
|
|
|
|
"Username" => "my_api1.domain.tld", |
26
|
|
|
|
|
|
|
"Password" => "Xdkis9k3jDFk39fj29sD9", ## supplied by PayPal |
27
|
|
|
|
|
|
|
"Signature" => "f7d03YCpEjIF3s9Dk23F2...", ## supplied by PayPal |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
$tx->content( |
31
|
|
|
|
|
|
|
action => "Normal Authorization", |
32
|
|
|
|
|
|
|
amount => "19.95", |
33
|
|
|
|
|
|
|
type => "Visa", |
34
|
|
|
|
|
|
|
card_number => "4111111111111111", |
35
|
|
|
|
|
|
|
expiration => "01/10", |
36
|
|
|
|
|
|
|
cvv2 => "123", |
37
|
|
|
|
|
|
|
name => "John Doe", |
38
|
|
|
|
|
|
|
address => "123 My Street", |
39
|
|
|
|
|
|
|
city => "Chicago", |
40
|
|
|
|
|
|
|
state => "IL", |
41
|
|
|
|
|
|
|
zip => "61443", |
42
|
|
|
|
|
|
|
IPAddress => "10.0.0.1", |
43
|
|
|
|
|
|
|
); |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
$tx->test_transaction(1); |
46
|
|
|
|
|
|
|
$tx->submit; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
if ( $tx->is_success ) { |
49
|
|
|
|
|
|
|
print( |
50
|
|
|
|
|
|
|
"SUCCESS:\n", |
51
|
|
|
|
|
|
|
" CorrelationID: ", $tx->correlationid, "\n", |
52
|
|
|
|
|
|
|
" auth: ", $tx->authorization, "\n", |
53
|
|
|
|
|
|
|
" AVS code: ", $tx->avs_code, "\n", |
54
|
|
|
|
|
|
|
" CVV2 code: ", $tx->cvv2_code, "\n", |
55
|
|
|
|
|
|
|
); |
56
|
|
|
|
|
|
|
} |
57
|
|
|
|
|
|
|
else { |
58
|
|
|
|
|
|
|
print( |
59
|
|
|
|
|
|
|
"ERROR: ", $tx->error_message, "\n" |
60
|
|
|
|
|
|
|
); |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=head1 DESCRIPTION |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Business::OnlinePayment::PayPal is a plugin for using PayPal as a |
66
|
|
|
|
|
|
|
payment processor backend with the Business::OnlinePayment API. |
67
|
|
|
|
|
|
|
Specifically, this module uses PayPal's 'DoDirectPayment' operation |
68
|
|
|
|
|
|
|
which utilizes the 'DoDirectPaymentRequest' message type. |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
This module does not do any checks to be sure that all the required |
71
|
|
|
|
|
|
|
fields/arguments/attributes/values, per PayPal's WSDL/XSD, have been |
72
|
|
|
|
|
|
|
provided. In general, PayPal's service will catch errors and return |
73
|
|
|
|
|
|
|
relevant information. However when requests do not meet the minimum |
74
|
|
|
|
|
|
|
message format/structure requirements or if the request contains |
75
|
|
|
|
|
|
|
information not supported by the 'DoDirectPaymentRequest' very generic |
76
|
|
|
|
|
|
|
errors (i.e. PPBaseException) may be sent to STDERR by underlying |
77
|
|
|
|
|
|
|
modules and our response data structure may be completely empty. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Anyone using this module or any modules that talk to PayPal should |
80
|
|
|
|
|
|
|
familiarize themselves with the information available at PayPal's |
81
|
|
|
|
|
|
|
integration center. See the L section for links to useful |
82
|
|
|
|
|
|
|
reference material. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=head1 METHODS |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
The following methods exist for use with this module. |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=head2 Convenience methods |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=over 4 |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
=item authorization() |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
Provides access to the TransactionID returned in the PayPal results. |
95
|
|
|
|
|
|
|
This method is part of the Business::OnlinePayment "standard" API. |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=item transactionid() |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
This method is an alias for the L method. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=item correlationid() |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Provides access to the CorrelationID returned in the PayPal results. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=item order_number() |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
This method is an alias for the L method. It is |
108
|
|
|
|
|
|
|
provided for compatibility with the PayflowPro backend. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=item server_response() |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Provides access, via a hashref, to the results hash returned in the |
113
|
|
|
|
|
|
|
Business::PayPal::API results object returned by |
114
|
|
|
|
|
|
|
DoDirectPaymentRequest. This method is part of the |
115
|
|
|
|
|
|
|
Business::OnlinePayment "standard" API. |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=item result_code() |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Returns "" or the first ErrorCode returned from |
120
|
|
|
|
|
|
|
DoDirectPaymentRequest. This method is part of the |
121
|
|
|
|
|
|
|
Business::OnlinePayment "standard" API. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=item avs_code() |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
Returns the AVSCode returned from DoDirectPaymentRequest. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item cvv2_code() |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
Returns the CVV2Code returned from DoDirectPaymentRequest. |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=item is_success() |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Returns 1 or 0 on success or failure of DoDirectPaymentRequest. This |
134
|
|
|
|
|
|
|
method is part of the Business::OnlinePayment "standard" API. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item error_message() |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
Returns a string containing an error message, if any. This method is |
139
|
|
|
|
|
|
|
part of the Business::OnlinePayment "standard" API. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
=back |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=head2 set_defaults() |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
Creates accessor methods L, L, |
146
|
|
|
|
|
|
|
L and __map_fields_data (see L). |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub set_defaults { |
151
|
|
|
|
|
|
|
my $self = shift; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
$self->build_subs(qw(avs_code correlationid cvv2_code __map_fields_data)); |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
$self->__map_fields_data( |
156
|
|
|
|
|
|
|
{ |
157
|
|
|
|
|
|
|
PaymentAction => "action", |
158
|
|
|
|
|
|
|
OrderTotal => "amount", # Payment Detail |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# Credit Card |
161
|
|
|
|
|
|
|
CreditCardType => "type", |
162
|
|
|
|
|
|
|
CreditCardNumber => "card_number", |
163
|
|
|
|
|
|
|
CVV2 => undef, |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Card Owner / Payer Name |
166
|
|
|
|
|
|
|
Payer => "email", |
167
|
|
|
|
|
|
|
FirstName => "name", |
168
|
|
|
|
|
|
|
LastName => undef, |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Payer Address |
171
|
|
|
|
|
|
|
Street1 => "address", |
172
|
|
|
|
|
|
|
Street2 => undef, |
173
|
|
|
|
|
|
|
CityName => "city", |
174
|
|
|
|
|
|
|
StateOrProvince => "state", |
175
|
|
|
|
|
|
|
Country => "country", |
176
|
|
|
|
|
|
|
PostalCode => "zip", |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
sub transactionid { shift()->authorization(@_); } |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub order_number { shift()->correlationid(@_); } |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=head2 get_credentials() |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Get the credential information for Business::PayPal::API that was |
188
|
|
|
|
|
|
|
provided to Business::OnlinePayment::new(). The supported arguments |
189
|
|
|
|
|
|
|
are: |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=over 4 |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=item * Username Password PKCS12File PKCS12Password |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=item * Username Password CertFile KeyFile |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item * Username Password Signature |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=back |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Business::OnlinePayment::PayPal does not currently map arguments to |
202
|
|
|
|
|
|
|
new() from (standard?) names to the PayPal backend specific name. For |
203
|
|
|
|
|
|
|
example, if the argument "login" were passed to new() the module could |
204
|
|
|
|
|
|
|
potentially try to identify that and map that to "Username". |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
NOTE: This requirement/capability seems to be more of a |
207
|
|
|
|
|
|
|
Business::OnlinePayment issue than a backend issue and it isn't clear |
208
|
|
|
|
|
|
|
if behavior like this is needed in this module so I will wait for user |
209
|
|
|
|
|
|
|
feedback to determine if we need/want to implement this. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=cut |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub get_credentials { |
214
|
|
|
|
|
|
|
my $self = shift; |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
my %credentials; |
217
|
|
|
|
|
|
|
my @cred_vars = ( |
218
|
|
|
|
|
|
|
[qw(PKCS12File PKCS12Password)], |
219
|
|
|
|
|
|
|
[qw(CertFile KeyFile)], [qw(Signature)], |
220
|
|
|
|
|
|
|
); |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
foreach my $aref (@cred_vars) { |
223
|
|
|
|
|
|
|
my $need = 0; |
224
|
|
|
|
|
|
|
my @vars = ( qw(Username Password), @$aref ); |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
foreach my $var (@vars) { |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# HACK: Business::OnlinePayment makes method lower case |
229
|
|
|
|
|
|
|
my $method = lc($var); |
230
|
|
|
|
|
|
|
if ( $self->can($method) ) { |
231
|
|
|
|
|
|
|
$credentials{$var} = $self->$method; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
else { |
234
|
|
|
|
|
|
|
$need++; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
if ($need) { |
239
|
|
|
|
|
|
|
undef %credentials; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
else { |
242
|
|
|
|
|
|
|
last; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
} |
245
|
|
|
|
|
|
|
return %credentials; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
=head2 get_request_data() |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
Return a hash %data with all the data from content() that we will try |
251
|
|
|
|
|
|
|
to use in our request to PayPal. Tasks performed: |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
=over 4 |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
=item * |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
Remove unsupported values from our hash (i.e. description fax login |
258
|
|
|
|
|
|
|
password phone). |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=item * |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
Translate the value in "action" if necessary, from |
263
|
|
|
|
|
|
|
Business::OnlinePayment names to names used by PayPal. Translations |
264
|
|
|
|
|
|
|
used are: |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
"normal authorization" => "Sale" |
267
|
|
|
|
|
|
|
"authorization only" => "Authorization" |
268
|
|
|
|
|
|
|
"void" => "None" |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item * |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
Translate the value in "type" if necessary, from |
273
|
|
|
|
|
|
|
Business::OnlinePayment names to names used by PayPal. See |
274
|
|
|
|
|
|
|
L for details. |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=item * |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
If necessary, separate ExpMonth and ExpYear values from the single |
279
|
|
|
|
|
|
|
"standard" Business::OnlinePayment "expiration" field. See |
280
|
|
|
|
|
|
|
L for details. |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item * |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Call get_remap_fields to map content() into the %data that we will |
285
|
|
|
|
|
|
|
pass to PayPal. All fields not "mapped" will be passed AS-IS. The |
286
|
|
|
|
|
|
|
mapping used is (map hashref stored in __map_fields_data()): |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
PaymentAction => "action" |
289
|
|
|
|
|
|
|
# Payment Detail |
290
|
|
|
|
|
|
|
OrderTotal => "amount" |
291
|
|
|
|
|
|
|
# Credit Card |
292
|
|
|
|
|
|
|
CreditCardType => "type" |
293
|
|
|
|
|
|
|
CreditCardNumber => "card_number" |
294
|
|
|
|
|
|
|
CVV2 => undef |
295
|
|
|
|
|
|
|
# Card Owner / Payer Name |
296
|
|
|
|
|
|
|
Payer => "email" |
297
|
|
|
|
|
|
|
FirstName => "name" |
298
|
|
|
|
|
|
|
LastName => undef |
299
|
|
|
|
|
|
|
# Payer Address |
300
|
|
|
|
|
|
|
Street1 => "address" |
301
|
|
|
|
|
|
|
Street2 => undef |
302
|
|
|
|
|
|
|
CityName => "city" |
303
|
|
|
|
|
|
|
StateOrProvince => "state" |
304
|
|
|
|
|
|
|
Country => "country" |
305
|
|
|
|
|
|
|
PostalCode => "zip" |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
NOTE: an 'undef' on the right hand side means that field will be |
308
|
|
|
|
|
|
|
looked for as the mixed-case name specified on the left and also as an |
309
|
|
|
|
|
|
|
all lower-case name). |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=back |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=cut |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
sub get_request_data { |
316
|
|
|
|
|
|
|
my $self = shift; |
317
|
|
|
|
|
|
|
my %content = $self->content; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
return () unless (%content); |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
# remove some unsupported content |
322
|
|
|
|
|
|
|
# others? description, invoice_number, customer_id |
323
|
|
|
|
|
|
|
delete @content{qw(description fax login password phone)}; |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# action: map "standard" names to supported as needed |
326
|
|
|
|
|
|
|
if ( $content{action} ) { |
327
|
|
|
|
|
|
|
my $act = lc( $content{action} ); |
328
|
|
|
|
|
|
|
my %actions = ( |
329
|
|
|
|
|
|
|
"normal authorization" => "Sale", |
330
|
|
|
|
|
|
|
"authorization only" => "Authorization", |
331
|
|
|
|
|
|
|
"void" => "None", |
332
|
|
|
|
|
|
|
); |
333
|
|
|
|
|
|
|
$content{action} = $actions{$act} || $content{action}; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# type: translate to supported CreditCardType values |
337
|
|
|
|
|
|
|
if ( $content{type} ) { |
338
|
|
|
|
|
|
|
my $type = $content{type}; |
339
|
|
|
|
|
|
|
$content{type} = $self->normalize_creditcardtype($type) || $type; |
340
|
|
|
|
|
|
|
} |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# expiration: need separate month and year values |
343
|
|
|
|
|
|
|
if ( $content{expiration} |
344
|
|
|
|
|
|
|
and ( !$content{ExpMonth} or !$content{ExpYear} ) ) |
345
|
|
|
|
|
|
|
{ |
346
|
|
|
|
|
|
|
my $exp = $content{expiration}; |
347
|
|
|
|
|
|
|
delete $content{expiration}; |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# we only set ExpMonth/ExpYear if they aren't already set |
350
|
|
|
|
|
|
|
my ( $y, $m ) = $self->parse_expiration($exp); |
351
|
|
|
|
|
|
|
if ( $m and !$content{ExpMonth} ) { |
352
|
|
|
|
|
|
|
$content{ExpMonth} = $m; |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
if ( $y and !$content{ExpYear} ) { |
355
|
|
|
|
|
|
|
$content{ExpYear} = $y; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
my %data = $self->get_remap_fields( |
360
|
|
|
|
|
|
|
content => \%content, |
361
|
|
|
|
|
|
|
map => $self->__map_fields_data, |
362
|
|
|
|
|
|
|
); |
363
|
|
|
|
|
|
|
return %data; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=head2 submit() |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
Method that overrides the superclass stub. This method performs the |
369
|
|
|
|
|
|
|
following tasks: |
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
=over 4 |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=item * |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
Get credentials to be used for authentication with PayPal by calling |
376
|
|
|
|
|
|
|
L. |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item * |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
Get request data to be passed to PayPal by calling |
381
|
|
|
|
|
|
|
L. |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=item * |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
Connect to PayPal and perform a DirectPaymentRequest. The request |
386
|
|
|
|
|
|
|
will be run in test mode (i.e. go to PayPal's "sandbox") if |
387
|
|
|
|
|
|
|
test_transaction() returns true. NOTE: I believe PayPal automatically |
388
|
|
|
|
|
|
|
does AVS checking if possible. |
389
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
=item * |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
Store the entire response in server_response(). |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=item * |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
Set result_code() to "" or the first ErrorCode in Errors (if present). |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=item * |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
Set avs_code() to the response AVSCode. |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
=item * |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
Set cvv2_code() to the response CVV2Code. |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=item * |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Set is_success() to 1 or 0, indicating if the transaction was |
409
|
|
|
|
|
|
|
successful or not. |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
=item * |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
On success, set authorization() with the value of TransactionID. On |
414
|
|
|
|
|
|
|
failure, set error_message() with a string containing all ErrorCode |
415
|
|
|
|
|
|
|
and LongMessage values joined together. |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=back |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=cut |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
sub submit { |
422
|
|
|
|
|
|
|
my $self = shift; |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
my %credentials = $self->get_credentials; |
425
|
|
|
|
|
|
|
my %request = $self->get_request_data; |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
my $pp = |
428
|
|
|
|
|
|
|
Business::PayPal::API->new( %credentials, |
429
|
|
|
|
|
|
|
sandbox => $self->test_transaction, ); |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
my %resp = $pp->DoDirectPaymentRequest(%request); |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
$self->server_response( \%resp ); |
434
|
|
|
|
|
|
|
$self->result_code( $resp{Errors} ? $resp{Errors}->[0]->{ErrorCode} : "" ); |
435
|
|
|
|
|
|
|
$self->avs_code( $resp{AVSCode} ); |
436
|
|
|
|
|
|
|
$self->cvv2_code( $resp{CVV2Code} ); |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
if ( $resp{Ack} and $resp{Ack} eq "Success" ) { |
439
|
|
|
|
|
|
|
$self->is_success(1); |
440
|
|
|
|
|
|
|
$self->authorization( $resp{TransactionID} ); |
441
|
|
|
|
|
|
|
$self->correlationid( $resp{CorrelationID} ); |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
else { |
444
|
|
|
|
|
|
|
$self->is_success(0); |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
if ( $resp{Errors} and @{ $resp{Errors} } ) { |
448
|
|
|
|
|
|
|
my $error = join( "; ", |
449
|
|
|
|
|
|
|
map { $_->{ErrorCode} . ": " . $_->{LongMessage} } |
450
|
|
|
|
|
|
|
@{ $resp{Errors} } ); |
451
|
|
|
|
|
|
|
$self->error_message($error); |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
return $self->is_success; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=head2 get_remap_fields() |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
Options: |
460
|
|
|
|
|
|
|
content => $href (default: { $self->content } ) |
461
|
|
|
|
|
|
|
map => $href (default: { } ) |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
Combines some of the functionality of get_fields and remap_fields for |
464
|
|
|
|
|
|
|
convenience and also extends/alters their behavior. Unlike |
465
|
|
|
|
|
|
|
Business::OnlinePayment::remap_fields, this doesn't modify content(), |
466
|
|
|
|
|
|
|
and can therefore be called more than once. Also, unlike |
467
|
|
|
|
|
|
|
Business::OnlinePayment::get_fields in 3.x, this doesn't exclude |
468
|
|
|
|
|
|
|
fields content with a value of undef. |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=cut |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
sub get_remap_fields { |
473
|
|
|
|
|
|
|
my ( $self, %opt ) = @_; |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
my $content = $opt{content} || { $self->content }; |
476
|
|
|
|
|
|
|
my $map = $opt{map} || {}; |
477
|
|
|
|
|
|
|
my %data; |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
while ( my ( $to, $from ) = each %$map ) { |
480
|
|
|
|
|
|
|
my $tolc = lc($to); |
481
|
|
|
|
|
|
|
my $v; |
482
|
|
|
|
|
|
|
if ( defined $from ) { |
483
|
|
|
|
|
|
|
$v = $content->{$from}; |
484
|
|
|
|
|
|
|
delete $content->{$from}; |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
$v ||= $content->{$to} || $content->{$tolc}; |
487
|
|
|
|
|
|
|
delete @$content{ $to, $tolc }; |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
if ( defined $v ) { |
490
|
|
|
|
|
|
|
$data{$to} = $v; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
%data = ( %$content, %data ); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
return %data; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
=head2 normalize_creditcardtype() |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Attempt to normalize the credit card type to names supported by |
502
|
|
|
|
|
|
|
PayPal. If the module is unable to identify the given type it leaves |
503
|
|
|
|
|
|
|
the value AS-IS and leaves it to PayPal to do what it can with the |
504
|
|
|
|
|
|
|
data given. Supported card types are: |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
Visa | MasterCard | Discover | Amex |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
Translations used are: |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
/^vis/i => "Visa" |
511
|
|
|
|
|
|
|
/^mas/i => "MasterCard" |
512
|
|
|
|
|
|
|
/^ame/i => "Amex" |
513
|
|
|
|
|
|
|
/^dis/i => "Discover" |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=cut |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
sub normalize_creditcardtype { |
518
|
|
|
|
|
|
|
my ( $self, $cctype ) = @_; |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
if ( $cctype =~ /^vis/i ) { $cctype = "Visa"; } |
521
|
|
|
|
|
|
|
elsif ( $cctype =~ /^mas/i ) { $cctype = "MasterCard"; } |
522
|
|
|
|
|
|
|
elsif ( $cctype =~ /^ame/i ) { $cctype = "Amex"; } |
523
|
|
|
|
|
|
|
elsif ( $cctype =~ /^dis/i ) { $cctype = "Discover"; } |
524
|
|
|
|
|
|
|
else { |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
# Credit Card type '$cctype' not known |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
return ($cctype); |
529
|
|
|
|
|
|
|
} |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
=head2 parse_expiration() |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
Business::OnlinePayment documents the use of a single expiration or |
534
|
|
|
|
|
|
|
exp_date value. However PayPal requires separate values for both the |
535
|
|
|
|
|
|
|
month and year. There are multiple formates that expiration dates are |
536
|
|
|
|
|
|
|
often specified in so, we try to our best to handle them all. |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
The following formats are supported: |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
YYYY[.-]MM, YYYY[.-]M, YY[-/]M, YY[.-]MM |
541
|
|
|
|
|
|
|
MM[-/]YYYY, M[-/]YYYY, M[-/]YY, MM/YY, MMYY |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
NOTE: this method is based on the parse_exp method found in |
544
|
|
|
|
|
|
|
L. |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
If an unrecognized format is encountered this method it will return an |
547
|
|
|
|
|
|
|
empty list and leave it to PayPal to do what it can with the data |
548
|
|
|
|
|
|
|
given. To avoid having this module attempt to parse 'expiration' |
549
|
|
|
|
|
|
|
explicitly set ExpMonth and ExpYear in content(). |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=cut |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub parse_expiration { |
554
|
|
|
|
|
|
|
my ( $self, $exp ) = @_; |
555
|
|
|
|
|
|
|
my ( $y, $m ); |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
return () unless ($exp); |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
if ( |
560
|
|
|
|
|
|
|
$exp =~ /^(\d{4})[.-](\d{1,2})$/ || # YYYY[.-]MM or YYYY[.-]M |
561
|
|
|
|
|
|
|
$exp =~ /^(\d\d)[-\/](\d)$/ || # YY[-/]M |
562
|
|
|
|
|
|
|
$exp =~ /^(\d\d)[.-](\d\d)$/ |
563
|
|
|
|
|
|
|
) # YY[.-]MM |
564
|
|
|
|
|
|
|
{ |
565
|
|
|
|
|
|
|
( $y, $m ) = ( $1, $2 ); |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
elsif ( |
568
|
|
|
|
|
|
|
$exp =~ /^(\d{1,2})[-\/](\d{4})$/ || # MM[-/]YYYY or M[-/]YYYY |
569
|
|
|
|
|
|
|
$exp =~ /^(\d)[-\/](\d\d)$/ || # M[-/]YY |
570
|
|
|
|
|
|
|
$exp =~ /^(\d\d)\/?(\d\d)$/ |
571
|
|
|
|
|
|
|
) # MM/YY or MMYY |
572
|
|
|
|
|
|
|
{ |
573
|
|
|
|
|
|
|
( $y, $m ) = ( $2, $1 ); |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
else { |
576
|
|
|
|
|
|
|
return (); # unable to parse expiration date '$exp' |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
# HACK: add the current century - 1 |
580
|
|
|
|
|
|
|
if ( $y < 100 ) { |
581
|
|
|
|
|
|
|
$y += int( ( ( localtime(time) )[5] + 1900 ) / 100 ) * 100; |
582
|
|
|
|
|
|
|
} |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
return ( $y, sprintf( "%02.0f", $m ) ); |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
1; |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
__END__ |