line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Business::3DSecure::Cardinal; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
1638
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
11016
|
|
4
|
2
|
|
|
2
|
|
23
|
use warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
75
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
1135
|
use Business::3DSecure; |
|
2
|
|
|
|
|
1181
|
|
|
2
|
|
|
|
|
91
|
|
7
|
2
|
|
|
2
|
|
10
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
199
|
|
8
|
2
|
|
|
2
|
|
1809
|
use Error qw( try ); |
|
2
|
|
|
|
|
11381
|
|
|
2
|
|
|
|
|
31
|
|
9
|
2
|
|
|
2
|
|
2718
|
use LWP::UserAgent; |
|
2
|
|
|
|
|
133692
|
|
|
2
|
|
|
|
|
87
|
|
10
|
2
|
|
|
2
|
|
2380
|
use SOAP::Lite; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK ); |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
require Exporter; |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
@ISA = qw( Exporter AutoLoader Business::3DSecure ); |
17
|
|
|
|
|
|
|
@EXPORT = qw(); |
18
|
|
|
|
|
|
|
@EXPORT_OK = qw(); |
19
|
|
|
|
|
|
|
$VERSION = '0.06'; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# constants |
22
|
|
|
|
|
|
|
use constant TIMEOUT => '10'; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# Transaction type map |
25
|
|
|
|
|
|
|
use constant ACTIONS => ( 'cmpi_lookup', 'cmpi_authenticate' ); |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
use constant RECOVERABLE_ERRORS => ( |
28
|
|
|
|
|
|
|
350, 1001, 1002, 1051, 1055, 1060, 1085, 1120, 1130, 1140, |
29
|
|
|
|
|
|
|
1150, 1160, 1355, 1360, 1380, 1390, 1400, 1710, 1752, 1755, |
30
|
|
|
|
|
|
|
1789, 2001, 2003, 2006, 2007, 2009, 2010, 4000, 4020, 4240, |
31
|
|
|
|
|
|
|
4243, 4245, 4268, 4310, 4375, 4400, 4770, 4780, 4790, 4800, |
32
|
|
|
|
|
|
|
4810, 4820, 4930, 4951, 4963, 4965 |
33
|
|
|
|
|
|
|
); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
use constant ERRORS => { |
36
|
|
|
|
|
|
|
6000 => "General Error Communicating with MAPS Server" , |
37
|
|
|
|
|
|
|
6010 => "Failed to connect() to server via socket connection" , |
38
|
|
|
|
|
|
|
6020 => "Failed Parse of Response XML Message Returned From the MPI Server - Socket Communication" , |
39
|
|
|
|
|
|
|
6030 => "Failed Parse of Response XML Message Returned From the MPI Server - HTTP Communication" , |
40
|
|
|
|
|
|
|
6040 => "Failed Parse of Response XML Message Returned From the MPI Server - HTTPS Communication" , |
41
|
|
|
|
|
|
|
6050 => "Failed to initialize socket connection" , |
42
|
|
|
|
|
|
|
6060 => "Error Communicating with MAPS Server, No Response Message Received - Socket Communication" , |
43
|
|
|
|
|
|
|
6070 => "The URL to the MAPS Server does not use a recognized protocol (https required)" , |
44
|
|
|
|
|
|
|
6080 => "Error Communicating with MAPS Server, Error Response - HTTP Communication" , |
45
|
|
|
|
|
|
|
6090 => "Error Communicating with MAPS Server, Error Response - HTTPS Communication" , |
46
|
|
|
|
|
|
|
6100 => "Unable to Verify Trusted Server" , |
47
|
|
|
|
|
|
|
6110 => "Unable to Establish a SSL Context" , |
48
|
|
|
|
|
|
|
6120 => "Unable to Establish a SSL Connection" , |
49
|
|
|
|
|
|
|
6130 => "Error extract the underlying file descriptor" , |
50
|
|
|
|
|
|
|
6140 => "Error establishing Network Connection" , |
51
|
|
|
|
|
|
|
6150 => "Error during SSL Read of Reponse Data" , |
52
|
|
|
|
|
|
|
6160 => "Unable to Establish a Socket Connection for SSL connectivity" , |
53
|
|
|
|
|
|
|
6170 => "Unable to capture a Socket for SSL connectivity" , |
54
|
|
|
|
|
|
|
9999 => "DOLLAR AMOUNT ERROR: TWO DECIMALS NEEDED", |
55
|
|
|
|
|
|
|
}; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# fields required for different transaction types |
58
|
|
|
|
|
|
|
use constant REQUIRED_FIELDS => { |
59
|
|
|
|
|
|
|
cmpi_lookup => [ qw{ MsgType Version ProcessorId MerchantId TransactionPwd TransactionType RawAmount PurchaseAmount PurchaseCurrency PAN PANExpr OrderNumber } ], |
60
|
|
|
|
|
|
|
cmpi_authenticate => [ qw{ MsgType Version ProcessorId MerchantId TransactionId PAResPayload } ], |
61
|
|
|
|
|
|
|
}; |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# optional fields for different transaction types |
64
|
|
|
|
|
|
|
use constant OPTIONAL_FIELDS => { |
65
|
|
|
|
|
|
|
cmpi_lookup => [ qw{ OrderDescription UserAgent BrowserHeader Recurring RecurringFrequency RecurringEnd Installment AcquirerPassword EMail IPAddress BillingFirstName BillingMiddleName BillingLastName BillingAddress1 BillingAddress2 BillingCity BillingState BillingPostalCode BillingCountryCode BillingPhone BillingAltPhone ShippingFirstName ShippingMiddleName ShippingLastName ShippingAddress1 ShippingAddress2 ShippingCity ShippingState ShippingPostalCode ShippingCountryCode ShippingPhone ShippingAltPhone Item_Name_X Item_Desc_X Item_Price_X Item_Quantity_X Item_SKU_X} ], |
66
|
|
|
|
|
|
|
cmpi_authenticate =>[qw{ NONE }], |
67
|
|
|
|
|
|
|
}; |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
use constant REMAP => { |
70
|
|
|
|
|
|
|
version => 'Version', |
71
|
|
|
|
|
|
|
action => 'MsgType', |
72
|
|
|
|
|
|
|
password => 'TransactionPwd', |
73
|
|
|
|
|
|
|
trans_type => 'TransactionType', |
74
|
|
|
|
|
|
|
vendor => 'ProcessorId', |
75
|
|
|
|
|
|
|
brand => 'MerchantId', |
76
|
|
|
|
|
|
|
amount => 'PurchaseAmount', |
77
|
|
|
|
|
|
|
currency => 'PurchaseCurrency', |
78
|
|
|
|
|
|
|
cc_num => 'PAN', |
79
|
|
|
|
|
|
|
ordernum => 'OrderNumber', |
80
|
|
|
|
|
|
|
auth_result => 'PAResPayload', |
81
|
|
|
|
|
|
|
auth_id => 'TransactionId', |
82
|
|
|
|
|
|
|
}; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub set_defaults |
85
|
|
|
|
|
|
|
{ |
86
|
|
|
|
|
|
|
my $self = shift; |
87
|
|
|
|
|
|
|
$self->build_subs( qw( cavv eci enrolled error_desc error_num authorized verified unparsed_response auth_request auth_id issuer_url ) ); |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub submit |
91
|
|
|
|
|
|
|
{ |
92
|
|
|
|
|
|
|
my ( $self ) = @_; |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
$self->{ _content }->{ action } = "cmpi_" . lc( $self->{ _content }->{ action } ); |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
my $action = $self->{ _content }->{ action }; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
unless ( grep /$action/, ACTIONS ) |
99
|
|
|
|
|
|
|
{ |
100
|
|
|
|
|
|
|
Carp::croak( $self->{ processor } . " can't handle transaction type: " . $action ); |
101
|
|
|
|
|
|
|
} |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
$self->map_fields(); |
104
|
|
|
|
|
|
|
$self->remap_fields(); |
105
|
|
|
|
|
|
|
$self->required_fields( @{ REQUIRED_FIELDS->{ $action } } ); |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
unless ( $self->{ _content }->{ amount_error } ) |
108
|
|
|
|
|
|
|
{ |
109
|
|
|
|
|
|
|
# get data ready to send |
110
|
|
|
|
|
|
|
my %post_data = $self->get_fields( @{ REQUIRED_FIELDS->{ $action } }, @{ OPTIONAL_FIELDS->{ $action } } ); |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
$self->{ _post_data } = \%post_data; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
my $xmlMsg = ""; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
while( my ( $tagname, $tagvalue ) = each %post_data ) |
117
|
|
|
|
|
|
|
{ |
118
|
|
|
|
|
|
|
$tagvalue =~ s/&/&/g; |
119
|
|
|
|
|
|
|
$tagvalue =~ s/</g; |
120
|
|
|
|
|
|
|
$xmlMsg .= "<" . $tagname . ">" . $tagvalue . "" . $tagname . ">"; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
$xmlMsg .= ""; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
my $ua = LWP::UserAgent->new(); |
126
|
|
|
|
|
|
|
$ua->timeout( TIMEOUT ) ; |
127
|
|
|
|
|
|
|
$ua->cookie_jar( { } ); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
my $response = $ua->post( $self->{ _content }->{ transaction_url }, [ 'cmpi_msg' => $xmlMsg ] ); |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
$self->is_success( 0 ); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# if post to Cardinal was successful |
134
|
|
|
|
|
|
|
if ( $response->is_success ) |
135
|
|
|
|
|
|
|
{ |
136
|
|
|
|
|
|
|
$self->unparsed_response( $response->content ); |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
my $som = SOAP::Deserializer->deserialize( $response->content ); |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
$self->{ _response }->{ $_->name } = $_->value foreach ( $som->dataof( "//CardinalMPI/*" ) ); |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
#if defined use the errors above otherwise load straigh in from server |
143
|
|
|
|
|
|
|
if ( defined ERRORS->{ $self->{ _response }->{ ErrorNo } } ) |
144
|
|
|
|
|
|
|
{ |
145
|
|
|
|
|
|
|
$self->error_num( $self->{ _response }->{ ErrorNo } ) ; |
146
|
|
|
|
|
|
|
$self->error_desc( ERRORS->{ $self->error_num } ) ; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
else |
149
|
|
|
|
|
|
|
{ |
150
|
|
|
|
|
|
|
$self->error_num( $self->{ _response }->{ ErrorNo } ) ; |
151
|
|
|
|
|
|
|
$self->error_desc( "ERROR NOT RECOGNIZED:" . $self->{ _response }->{ ErrorDesc } ) ; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
if ( !$self->{ _response } ) |
155
|
|
|
|
|
|
|
{ |
156
|
|
|
|
|
|
|
$self->error_num( 6040 ); |
157
|
|
|
|
|
|
|
$self->error_desc( ERRORS->{ 6040 } ); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
$self->eci( 0 ); |
161
|
|
|
|
|
|
|
$self->is_success( 1 ); |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
if ( $action eq 'cmpi_lookup' ) |
164
|
|
|
|
|
|
|
{ |
165
|
|
|
|
|
|
|
$self->auth_request( $self->{ _response }->{ Payload } ) ; |
166
|
|
|
|
|
|
|
$self->auth_id( $self->{ _response }->{ TransactionId } ) ; |
167
|
|
|
|
|
|
|
$self->issuer_url( $self->{ _response }->{ ACSUrl } ) ; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
my $enrolled = uc $self->{ _response }->{ Enrolled } eq 'Y' ? 1 : 0; |
170
|
|
|
|
|
|
|
$self->enrolled( $enrolled ); |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
elsif ( $action eq 'cmpi_authenticate' ) |
174
|
|
|
|
|
|
|
{ |
175
|
|
|
|
|
|
|
$self->cavv( $self->{ _response }->{ Cavv } ); |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# possible success are Y U or A this SHOULD be made farther up, in client code |
178
|
|
|
|
|
|
|
my $authorized = uc $self->{ _response }->{ PAResStatus } eq 'N' ? 0 : 1; |
179
|
|
|
|
|
|
|
$self->authorized( $authorized ); |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $verified = uc $self->{ _response }->{ SignatureVerification } eq 'Y' ? 1 : 0; |
182
|
|
|
|
|
|
|
$self->verified( $verified ); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
# EciFlag |
185
|
|
|
|
|
|
|
my $eci = $self->{ _response }->{ EciFlag } ne '02' ? 0 : 1; |
186
|
|
|
|
|
|
|
$self->eci( $eci ); |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
else |
190
|
|
|
|
|
|
|
{ |
191
|
|
|
|
|
|
|
# post was unsuccessful |
192
|
|
|
|
|
|
|
$self->error_num( 6090 ); |
193
|
|
|
|
|
|
|
$self->error_desc( ERRORS->{ 6090 } ); |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
} |
197
|
|
|
|
|
|
|
else |
198
|
|
|
|
|
|
|
{ |
199
|
|
|
|
|
|
|
# amount error |
200
|
|
|
|
|
|
|
$self->error_num( 9999 ); |
201
|
|
|
|
|
|
|
$self->error_desc( ERRORS->{ 9999 } ); |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub map_fields |
206
|
|
|
|
|
|
|
{ |
207
|
|
|
|
|
|
|
my ( $self ) = @_; |
208
|
|
|
|
|
|
|
my %content = $self->content(); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
if ( $content{ action } ) |
211
|
|
|
|
|
|
|
{ |
212
|
|
|
|
|
|
|
if ( defined $content{ amount } ) |
213
|
|
|
|
|
|
|
{ |
214
|
|
|
|
|
|
|
my @amount = split( '\.' , $content{ amount } ); |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
$content{ amount_error } = $content{ amount } if length $amount[ 1 ] != 2; |
217
|
|
|
|
|
|
|
$content{ RawAmount } = $content{ amount }; |
218
|
|
|
|
|
|
|
$content{ RawAmount } =~ s/\.//; |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
if ( defined $content{ cc_expmonth } && defined $content{ cc_expyear } ) |
222
|
|
|
|
|
|
|
{ |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
# it will only be 2 or 4 |
225
|
|
|
|
|
|
|
if ( length( $content{ cc_expyear } ) == 4 ) |
226
|
|
|
|
|
|
|
{ |
227
|
|
|
|
|
|
|
$content{ cc_expyear } = substr $content{ cc_expyear }, 2, 4; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
$content{ PANExpr } = $content{ cc_expyear } . $content{ cc_expmonth }; |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
# stuff it back into %content |
235
|
|
|
|
|
|
|
$self->content( %content ); |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub remap_fields |
239
|
|
|
|
|
|
|
{ |
240
|
|
|
|
|
|
|
my ( $self ) = @_; |
241
|
|
|
|
|
|
|
my %content = $self->content(); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
foreach( keys %{ ( REMAP ) } ) |
244
|
|
|
|
|
|
|
{ |
245
|
|
|
|
|
|
|
$content{ REMAP->{ $_ } } = $content{ $_ } ; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
$self->content( %content ); |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub get_fields |
252
|
|
|
|
|
|
|
{ |
253
|
|
|
|
|
|
|
my ( $self, @fields ) = @_; |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
my %content = $self->content(); |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
my %new = (); |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
$new{ $_ } = $content{ $_ } foreach( grep defined $content{ $_ }, @fields ); |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
return %new; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub is_recoverable_error |
265
|
|
|
|
|
|
|
{ |
266
|
|
|
|
|
|
|
my $self = shift; |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
my $error_num = $self->error_num(); |
269
|
|
|
|
|
|
|
return ( grep /$error_num/, RECOVERABLE_ERRORS ? 1 : 0 ); |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub error |
273
|
|
|
|
|
|
|
{ |
274
|
|
|
|
|
|
|
my $self = shift; |
275
|
|
|
|
|
|
|
return $self->error_num() != 0; |
276
|
|
|
|
|
|
|
} |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
1; |
279
|
|
|
|
|
|
|
__END__ |