1package Business::OnlinePayment::PaymentsGateway;
2
3use strict;
4use Carp;
5use Business::OnlinePayment;
6use Net::SSLeay qw(sslcat);
7use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $DEBUG);
8
9@ISA = qw( Business::OnlinePayment );
10$VERSION = '0.02';
11
12$DEBUG = 0;
13
14my %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
65sub set_defaults {
66  my $self = shift;
67  $self->server('paymentsgateway.net');
68  $self->port( 5050 );
69}
70
71sub 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
101sub 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
112sub 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
2181;
219
220__END__
221
222=head1 NAME
223
224Business::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
254For detailed information see L<Business::OnlinePayment>.
255
256=head1 NOTE
257
258This module only implements 'ECHECK' (ACH) transactions at this time.  Credit
259card transactions are not (yet) supported.
260
261=head1 COMPATIBILITY
262
263This module implements the interface documented in the
264"PaymentsGateway.net Integration Guide, Version 2.1, September 2002"
265
266=head1 AUTHOR
267
268Ivan Kohler <ivan-paymentsgateway@420.am>
269
270=head1 SEE ALSO
271
272perl(1). L<Business::OnlinePayment>
273
274=cut
275
276