0.02, finally
[Business-OnlinePayment-PaymentsGateway.git] / PaymentsGateway.pm
1 package Business::OnlinePayment::PaymentsGateway;
2
3 use strict;
4 use Carp;
5 use Business::OnlinePayment;
6 use Net::SSLeay qw(sslcat);
7 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $DEBUG);
8
9 @ISA = qw( Business::OnlinePayment );
10 $VERSION = '0.02';
11
12 $DEBUG = 0;
13
14 my %pg_response_code = (
15   'A01' => 'Transaction approved/completed',
16   'U01' => 'Merchant not allowed to access customer account',
17   'U02' => 'Customer account is in the ACH Direct "known bad" list',
18   'U03' => 'Merchant daily limit exceeded',
19   'U04' => 'Merchant monthly limit exceeded',
20   'U05' => 'AVS state/zipcode check failed',
21   'U06' => 'AVS state/area code check failed',
22   'U07' => 'AVS anonymous email check failed',
23   'U08' => 'Account has more transactions than the merchant\'s daily velocity'.
24            ' limit allows for',
25   'U09' => 'Account has more transactions than the merchant\'s velocity'.
26            ' window allows for',
27   'U10' => 'Transaction has the same attributes as another transaction'.
28            ' within the time set by the merchant',
29   'U11' => '(RECUR TRANS NOT FOUND) Transaction types 40-42 only',
30   'U12' => 'Original transaction not voidable or capture-able',
31   'U13' => 'Transaction to be voided or captured was not found',
32   'U14' => 'void/capture and original transaction types do not agree (CC/EFT)',
33   'U18' => 'Void or Capture failed',
34   #'U19' => 'Account ABA number if invalid',
35   'U19' => 'Account ABA number is invalid',
36   'U20' => 'Credit card number is invalid',
37   'U21' => 'Date is malformed',
38   'U22' => 'Swipe data is malformed',
39   'U23' => 'Malformed expiration date',
40   'U51' => 'Merchant is not "live"',
41   'U52' => 'Merchant not approved for transaction type (CC or EFT)',
42   'U53' => 'Transaction amount exceeds merchant\'s per transaction limit',
43   'U54' => 'Merchant\'s configuration requires updating - call customer'.
44            ' support',
45   'U80' => 'Transaction was declined due to preauthorization (ATM Verify)'.
46            ' result',
47   'U84' => 'Preauthorizer not responding',
48   'U85' => 'Preauthorizer error',
49   'U83' => 'Transaction was declined due to authorizer declination',
50   'U84' => 'Authorizer not responding',
51   'U85' => 'Authorizer error',
52   'U86' => 'Authorizer AVS check failed',
53   'F01' => 'Required field is missing',
54   'F03' => 'Name is not recognized',
55   'F04' => 'Value is not allowed',
56   'F05' => 'Field is repeated in message',
57   'F07' => 'Fields cannot both be present',
58   #'E10' => 'Merchant id or password in incorrect',
59   'E10' => 'Merchant id or password is incorrect',
60   'E20' => 'Transaction message not received (I/O flush required?)',
61   'E90' => 'Originating IP not on merchant\'s approved IP list',
62   'E99' => 'An unspecified error has occurred',
63 );
64
65 sub set_defaults {
66   my $self = shift;
67   $self->server('paymentsgateway.net');
68   $self->port( 5050 );
69 }
70
71 sub map_fields {
72   my $self = shift;
73   my %content = $self->content();
74
75   #ACTION MAP
76   my %actions = (
77     'normal authorization' => 0,
78     'authorization only'   => 1,
79     'post authorization'   => 2,
80     'credit'               => 3,
81   );
82
83   my %types = (
84     'visa'             => 10,
85     'mastercard'       => 10,
86     'american express' => 10,
87     'discover'         => 10,
88     'cc'               => 10,
89     'check'            => 20,
90     'echeck'           => 20,
91   );
92
93   #pg_type/action = action + type  
94
95   $self->transaction_type( $actions{ lc($content{'action'}) } 
96                            + $types{ lc($content{'type'  }) }    );
97
98   #$self->content(%content);
99 }
100
101 sub revmap_fields {
102     my($self, %map) = @_;
103     my %content = $self->content();
104     foreach(keys %map) {
105         $content{$_} = ref($map{$_})
106                          ? ${ $map{$_} }
107                          : $content{$map{$_}};
108     }
109     $self->content(%content);
110 }
111
112 sub submit {
113   my $self = shift;
114   $self->map_fields();
115
116   #my %content = $self->content();
117
118   $self->revmap_fields( 
119     'PG_MERCHANT_ID'                   => 'login',
120     'pg_password'                      => 'password',
121     'pg_transaction_type'              => \($self->transaction_type()),
122     #'pg_merchant_data_1'
123     #...
124     #'pg_merchant_data_9'
125     'pg_total_amount'                  => 'amount',
126     #'pg_sales_tax_amount'
127     'pg_consumer_id'                   => 'customer_id',
128     'ecom_consumerorderid'             => 'invoice_number', #???
129     #'ecom_walletid'                    =>
130     'pg_billto_postal_name_company'    => 'company', #????
131     'ecom_billto_postal_name_first'    => 'first_name', #????
132     'ecom_billto_postal_name_last'     => 'last_name', # ????
133     'ecom_billto_postal_street_line1'  => 'address',
134     #'ecom_billto_postal_street_line2'
135     'ecom_billto_postal_city'          => 'city',
136     'ecom_billto_postal_stateprov'     => 'state',
137     'ecom_billto_postal_postalcode'    => 'zip',
138     'ecom_billto_postal_countrycode'   => 'country',
139     'ecom_billto_telecom_phone_number' => 'phone',
140     'ecom_billto_online_email'         => 'email',
141     #'pg_billto_ssn'
142     #'pg_billto_dl_number'
143     #'pg_billto_dl_state'
144     'ecom_payment_check_trn'           => 'routing_code',
145     'ecom_payment_check_account'       => 'account_number',
146     'ecom_payment_check_account_type'  => \'C', #checking
147     #'ecom_payment_check_checkno'       =>
148   );
149   my %content = $self->content();
150
151   # name (first_name & last_name ) ?
152   # fax
153
154   # card_number exp_date
155
156   #account_number routing_code bank_name
157
158   my @fields = (
159     qw( PG_MERCHANT_ID pg_password pg_transaction_type ),
160     ( map { "pg_merchant_$_" } ( 1 .. 9 ) ),
161     qw( pg_total_amount pg_sales_tax_amount pg_consumer_id
162         ecom_consumerorderid ecom_walletid
163         pg_billto_postal_name_company
164         ecom_billto_postal_name_first ecom_billto_postal_name_last
165         ecom_billto_postal_street_line1
166         ecom_billto_postal_street_line2
167         ecom_billto_postal_city ecom_billto_postal_stateprov
168         ecom_billto_postal_postalcode ecom_billto_postal_countrycode
169         ecom_billto_telecom_phone_number ecom_billto_online_email
170         pg_billto_ssn pg_billto_dl_number pg_billto_dl_state
171     )
172   );
173
174   if ( $content{'type'} =~ /^e?check$/i ) {
175     push @fields, qw( ecom_payment_check_trn
176                       ecom_payment_check_account
177                       ecom_payment_check_account_type );
178   } else {
179     croak $content{'type'}. ' not (yet) supported';
180   }
181
182   my $request = join("\n", map { "$_=". $content{$_} }
183                            grep { defined($content{$_}) && $content{$_} ne '' }
184                            @fields                     ).
185                 "\nendofdata\n";
186
187   warn $request if $DEBUG;
188
189   warn "TEST: ". $self->test_transaction(). "\n" if $DEBUG;
190
191   $self->port( $self->port() + 1000 ) if $self->test_transaction();
192
193   warn "SERVER ". $self->server(). "\n" if $DEBUG;
194   warn "PORT ". $self->port(). "\n" if $DEBUG;
195
196   my $reply = sslcat( $self->server(), $self->port(), $request );
197   die "no reply from server" unless $reply;
198
199   warn "reply from server: $reply\n" if $DEBUG;
200
201   my %response = map { /^(\w+)=(.*)$/ or /^(endofdata)()$/
202                          or warn "can't parse response line: $_";
203                        ($1, $2);
204                      } split(/\n/, $reply);
205
206   if ( $response{'pg_response_type'} eq 'A' ) {
207     $self->is_success(1);
208     $self->result_code($response{'pg_response_code'});
209     $self->authorization($response{'pg_authorization_code'});
210   } else {
211     $self->is_success(0);
212     $self->result_code($response{'pg_response_code'});
213     $self->error_message( $pg_response_code{$response{'pg_response_code'}}.
214                           ': '. $response{'pg_response_description'} );
215   }
216 }
217
218 1;
219
220 __END__
221
222 =head1 NAME
223
224 Business::OnlinePayment::PaymentsGateway - PaymentsGateway.Net backend for Business::OnlinePayment
225
226 =head1 SYNOPSIS
227
228   use Business::OnlinePayment;
229
230   my $tx = new Business::OnlinePayment("PaymentsGateway");
231   $tx->content(
232       type           => 'CHECK',
233       login          => 'test',
234       password       => 'test',
235       action         => 'Normal Authorization',
236       description    => 'Business::OnlinePayment test',
237       amount         => '49.95',
238       invoice_number => '100100',
239       name           => 'Tofu Beast',
240       account_number => '12345',
241       routing_code   => '123456789',
242       bank_name      => 'First National Test Bank',
243   );
244   $tx->submit();
245
246   if($tx->is_success()) {
247       print "Card processed successfully: ".$tx->authorization."\n";
248   } else {
249       print "Card was rejected: ".$tx->error_message."\n";
250   }
251
252 =head1 DESCRIPTION
253
254 For detailed information see L<Business::OnlinePayment>.
255
256 =head1 NOTE
257
258 This module only implements 'ECHECK' (ACH) transactions at this time.  Credit
259 card transactions are not (yet) supported.
260
261 =head1 COMPATIBILITY
262
263 This module implements the interface documented in the
264 "PaymentsGateway.net Integration Guide, Version 2.1, September 2002"
265
266 =head1 AUTHOR
267
268 Ivan Kohler <ivan-paymentsgateway@420.am>
269
270 =head1 SEE ALSO
271
272 perl(1). L<Business::OnlinePayment>
273
274 =cut
275