update URL
[Business-OnlinePayment-FirstDataGlobalGateway.git] / lib / Business / OnlinePayment / FirstDataGlobalGateway.pm
1 package Business::OnlinePayment::FirstDataGlobalGateway;
2 use base qw( Business::OnlinePayment );
3
4 use warnings;
5 use strict;
6 use Data::Dumper;
7 use Business::CreditCard;
8 use SOAP::Lite; #+trace => 'all';
9 #SOAP::Lite->import(+trace=>'debug');
10
11 our $VERSION = '0.01';
12 $VERSION = eval $VERSION; # modperlstyle: convert the string into a number
13
14 our @alpha = ( 'a'..'z', 'A'..'Z', '0'..'9' );
15
16 our %failure_status = (
17   302 => 'nsf',
18   501 => 'pickup',
19   502 => 'stolen',
20   503 => 'stolen', # "Fraud/Security violation"
21   504 => 'blacklisted',
22   509 => 'nsf',
23   510 => 'nsf',
24   519 => 'blacklisted',
25   521 => 'nsf',
26   522 => 'expired',
27   530 => 'blacklisted',
28   534 => 'blacklisted',
29   # others are all "declined"
30 );
31   
32
33 sub _info {
34   {
35     'info_compat'       => '0.01',
36     'gateway_name'      => 'First Data Global Gateway e4',
37     'gateway_url'       => 'https://www.firstdata.com/en_us/products/merchants/ecommerce/online-payment-processing.html',
38     'module_version'    => $VERSION,
39     'supported_types'   => [ 'CC' ], #, 'ECHECK' ],
40     #'token_support'     => 1, # "Transarmor" is this, but not implemented yet
41     'test_transaction'  => 1,
42
43     'supported_actions' => [ 'Normal Authorization',
44                              'Authorization Only',
45                              'Post Authorization',
46                              'Credit',
47                              'Void',
48                            ],
49   };
50 }
51
52 sub set_defaults {
53     my $self = shift;
54     #my %opts = @_;
55
56     $self->build_subs(qw( order_number avs_code cvv2_response
57                           authorization failure_status result_code
58                           ));
59 }
60
61 sub map_fields {
62     my($self) = @_;
63
64     my %content = $self->content();
65
66     # TYPE MAP
67     my %types = ( 'visa'               => 'CC',
68                   'mastercard'         => 'CC',
69                   'american express'   => 'CC',
70                   'discover'           => 'CC',
71                   'check'              => 'ECHECK',
72                 );
73     $content{'type'} = $types{lc($content{'type'})} || $content{'type'};
74     $self->transaction_type($content{'type'});
75     
76     # ACTION MAP 
77     my $action = lc($content{'action'});
78     my %actions =
79       ( 'normal authorization' => '00', # Purchase
80         'authorization_only'   => '01', # 
81         'post authorization'   => '02', # Pre-Authorization Completion
82         # '' => '03', # Forced Post
83         'credit'               => '04', # Refund
84         # '' => '05', # Pre-Authorization Only
85         'void'                 => '13', # Void
86         #'reverse authorization' => '',
87
88         # '' => '07', # PayPal Order
89         # '' => '32', # Tagged Pre-Authorization Completion
90         # '' => '33', # Tagged Void
91         # '' => '34', # Tagged Refund
92         # '' => '83', # CashOut (ValueLink, v9 or higher end point only)
93         # '' => '85', # Activation (ValueLink, v9 or higher end point only)
94         # '' => '86', # Balance Inquiry (ValueLink, v9 or higher end point only)
95         # '' => '88', # Reload (ValueLink, v9 or higher end point only)
96         # '' => '89', # Deactivation (ValueLink, v9 or higher end point only)
97       );
98
99     $content{'action'} = $actions{$action} || $action;
100
101     # make sure there's a combined name
102     $content{name} ||= $content{first_name} . ' ' . $content{last_name};
103
104     # stuff it back into %content
105     $self->content(%content);
106
107 }
108
109 sub remap_fields {
110     my($self,%map) = @_;
111
112     my %content = $self->content();
113     foreach(keys %map) {
114         $content{$map{$_}} = $content{$_};
115     }
116     $self->content(%content);
117 }
118
119 sub submit {
120   my($self) = @_;
121
122   $self->map_fields;
123
124   $self->remap_fields(
125         'login'             => 'ExactID',
126         'password'          => 'Password',
127
128         'action'            => 'Transaction_Type',
129
130         'amount'            => 'DollarAmount',
131         'currency'          => 'Currency',
132         'card_number'       => 'Card_Number',
133         'track1'            => 'Track1',
134         'track2'            => 'Track2',
135         'expiration'        => 'Expiry_Date',
136         'name'              => 'CardHoldersName',
137         'cvv2'              => 'VerificationStr2',
138
139         'authorization'     => 'Authorization_Num',
140         'order_number'      => 'Reference_No',
141
142         'zip'               => 'ZipCode',
143         'tax'               => 'Tax1Amount',
144         'customer_id'       => 'Customer_Ref',
145         'customer_ip'       => 'Client_IP',
146         'email'             => 'Client_Email',
147
148         #account_type      => 'accountType',
149
150   );
151
152   my %content = $self->content();
153
154   $content{Expiry_Date} =~ s/\///;
155
156   $content{country} ||= 'US';
157
158   $content{VerificationStr1} =
159     join('|', map $content{$_}, qw( address zip city state country ));
160   $content{VerificationStr1} .= '|'. $content{'phone'}
161     if $content{'type'} eq 'ECHECK';
162
163   $content{CVD_Presence_Ind} = '1' if length($content{VerificationStr2});
164
165   $content{'Reference_No'} ||= join('', map $alpha[int(rand(62))], (1..20) );
166
167   #XXX this should be exposed as a standard B:OP field, not just recurring/no
168   if ( defined($content{'recurring_billing'})
169        && $content{'recurring_billing'} =~ /^[y1]/ ) {
170     $content{'Ecommerce_Flag'} = '2';
171   } else {
172     #$content{'Ecommerce_Flag'} = '1'; 7?  if there's an IP?
173   }
174
175   my $base_uri;
176   $base_uri = 'https://api.demo.globalgatewaye4.firstdata.com/transaction';
177
178   my $proxy = "$base_uri/v11";
179
180   my @transaction = map { SOAP::Data->name($_)->value( $content{$_} ) }
181   grep { defined($content{$_}) }
182   (qw(
183     ExactID Password Transaction_Type DollarAmount Card_Number Transaction_Tag
184     Track1 Track2 Authorization_Num Expiry_Date CardHoldersName
185     VerificationStr1 VerificationStr2 CVD_Presence_Ind Reference_No ZipCode
186     Tax1Amount Tax1Number Tax2Amount Tax2Number Customer_Ref Reference_3
187     Language Client_IP Client_Email User_Name Currency PartialRedemption
188     CAVV XID Ecommerce_Flag
189  ));
190    #TransarmorToken CardType EAN VirtualCard CardCost FraudSuspected
191    #CheckNumber CheckType BankAccountNumber BankRoutingNumber CustomerName
192    #CustomerIDType CustomerID
193
194   my $wsdl = "$proxy/wsdl";
195   my $client = SOAP::Lite->service($wsdl)->proxy($proxy)->readable(1);
196   my $action_prefix = 'http://secure2.e-xact.com/vplug-in/transaction/rpc-enc';
197   my $type_prefix = $action_prefix . '/encodedTypes';
198   $client->on_action( sub { $action_prefix . '/' . $_[1] } );
199   my $source = SOAP::Data->name('SendAndCommitSource')
200                          ->value(\@transaction)
201                          ->type("$type_prefix:Transaction");
202   local $@;
203   my $som = eval { $client->call('SendAndCommit', $source) };
204   die $@ if $@;
205   if ($som->fault) { # indicates a protocol error
206     die $som->faultstring;
207   }
208
209   $DB::single = 1;
210   $som->match('/Envelope/Body/SendAndCommitResponse/SendAndCommitResult');
211   my $result = $som->valueof; # hashref of the result properties
212   $self->is_success( $result->{Transaction_Approved} );
213   $self->authorization( $result->{Authorization_Num} );
214   $self->order_number( $result->{SequenceNo} );
215   $self->avs_code( $result->{AVS} );
216   $self->cvv2_response( $result->{CVV2} );
217
218   if (!$self->is_success) {
219     # note spelling of "EXact_Resp_Code"
220     if ($result->{EXact_Resp_Code} ne '00') {
221       # then there's something wrong with the transaction inputs
222       # (invalid card number, malformed amount, attempt to refund a 
223       # transaction that didn't happen, etc.)
224       $self->error_message($result->{EXact_Message});
225       $self->result_code($result->{EXact_Resp_Code});
226       $self->failure_status('');
227       # not a decline, as the transaction was never really detected
228     } else {
229       $self->error_message($result->{Bank_Message});
230       $self->result_code($result->{Bank_Resp_Code});
231       $self->failure_status(
232         $failure_status{$result->{Bank_Resp_Code}} || 'declined'
233       );
234     }
235   }
236 }
237
238 1;
239
240 __END__
241
242 =head1 NAME
243
244 Business::OnlinePayment::FirstDataGlobalGateway - First Data Global Gateway e4 backend for Business::OnlinePayment
245
246 =head1 SYNOPSIS
247
248   use Business::OnlinePayment;
249
250   my $tx =
251     new Business::OnlinePayment( 'FirstDataGlobalGateway' );
252
253   $tx->content(
254       login          => 'TEST88', # ExactID
255       password       => 'TEST88', #password 
256
257       type           => 'CC',
258       action         => 'Normal Authorization',
259       amount         => '1.00',
260
261       first_name     => 'Tofu',
262       last_name      => 'Beast',
263       address        => '123 Anystreet',
264       city           => 'Anywhere',
265       state          => 'UT',
266       zip            => '84058',
267
268       card_number    => '4111111111111111',
269       expiration     => '09/20',
270       cvv2           => '124',
271
272       #optional
273       customer_ip    => '1.2.3.4',
274   );
275   $tx->submit();
276
277   if($tx->is_success()) {
278       print "Card processed successfully: ".$tx->authorization."\n";
279   } else {
280       print "Card was rejected: ".$tx->error_message."\n";
281   }
282
283 =head1 SUPPORTED TRANSACTION TYPES
284
285 =head2 CC, Visa, MasterCard, American Express, Discover
286
287 Content required: type, login, action, amount, card_number, expiration.
288
289 =head2 (NOT YET) Check
290
291 Content required: type, login, action, amount, name, account_number, routing_code.
292
293 =head1 DESCRIPTION
294
295 For detailed information see L<Business::OnlinePayment>.
296
297 =head1 METHODS AND FUNCTIONS
298
299 See L<Business::OnlinePayment> for the complete list. The following methods either override the methods in L<Business::OnlinePayment> or provide additional functions.  
300
301 =head2 result_code
302
303 Returns the response error code.
304
305 =head2 error_message
306
307 Returns the response error number.
308
309 =head2 action
310
311 The following actions are valid
312
313   Normal Authorization
314   Authorization Only
315   Post Authorization
316   Credit
317   Void
318
319 =head1 COMPATIBILITY
320
321 Business::OnlinePayment::FirstDataGlobalGateway uses the v11 version of the API
322 at this time.
323
324 =head1 AUTHORS
325
326 Ivan Kohler <ivan-firstdataglobalgateway@freeside.biz>
327
328 =head1 SEE ALSO
329
330 perl(1). L<Business::OnlinePayment>.
331
332 =cut
333