copyright the company too :)
[Business-OnlinePayment-GlobalPayments.git] / GlobalPayments.pm
1 package Business::OnlinePayment::GlobalPayments;
2
3 use warnings;
4 use strict;
5 use Carp qw(croak);
6 use vars qw($VERSION $DEBUG @ISA $me);
7 use base 'Business::OnlinePayment::HTTPS';
8 use XML::Simple 'XMLin'; # for parsing reply
9
10 $VERSION = 0.02;
11 $DEBUG = 0;
12 $me = __PACKAGE__;
13
14 my %trans_type = (
15   'normal authorization' => 'Sale',
16   'authorization only'   => 'Auth',
17   'post authorization'   => 'Force',
18   'void'                 => 'Void',
19   'credit'               => 'Return',
20 );
21
22 my %cc_fields = (
23   'GlobalUserName'  => 'login',
24   'GlobalPassword'  => 'password',
25   'TransType'       => sub { my %c = @_; $trans_type{ lc($c{action}) } },
26   'CardNum'         => 'card_number',
27   'ExpDate'         => sub { my %c = @_; join('', split /\D/,$c{'expiration'}) },
28   'MagData'         => 'track2',
29   'NameOnCard'      => sub { my %c = @_; $c{'first_name'} . ' ' . $c{'last_name'} },
30   'Amount'          => 'amount',
31   'InvNum'          => 'invoice_number',
32   'Zip'             => 'zip',
33   'Street'          => 'address',
34   'CVNum'           => 'cvv2',
35   'PNRef'           => 'order_number',
36   'ExtData'         => \&ext_data,
37 );
38
39 sub ext_data {
40   my %c = @_; # = $self->{_content}
41   my $ext_data = '';
42   if($c{'authorization'}) {
43     $ext_data .= '<AuthCode>'.$c{'authorization'}.'</AuthCode>';
44   }
45   if($c{'force_duplicate'}) { # set to any true value
46     $ext_data .= '<Force>T</Force>';
47   }
48   return $ext_data;
49 }
50
51 my %required_fields = (
52   'All'    => [ qw(GlobalUserName GlobalPassword TransType) ],
53   'Sale'   => [ qw(CardNum ExpDate Amount) ],
54   'Auth'   => [ qw(CardNum ExpDate Amount) ],
55   'Force'  => [ ],
56   'Void'   => [ 'PNRef' ],
57   'Return' => [ ],
58   'Return.blind' => [ qw(CardNum ExpDate Amount) ],
59 );
60
61 sub set_defaults {
62   my $self = shift;
63   $self->port(443);
64   $self->path('/GlobalPay/transact.asmx/ProcessCreditCard');
65   $self->build_subs('domain', 'avs_code', 'cvv2_response' );
66
67   return;
68 }
69
70 sub remap_fields {
71   my ($self, %map) = @_;
72   my %content = $self->content();
73
74   foreach (keys(%map)) {
75     if(ref($map{$_}) eq 'CODE') {
76       $content{$_} = $map{$_}->(%content);
77     }
78     else {
79       $content{$_} = $content{$map{$_}} if defined( $content{$map{$_}} );
80     }
81   }
82
83   if(lc($content{'action'}) eq 'post authorization') {
84     # GlobalPayments uses this transaction type to complete an authorized 
85     # transaction, given either its PNRef (if it was authorized by an Auth 
86     # transaction to the gateway) or its AuthCode (if it was authorized by 
87     # telephone).
88     if(!exists($content{'PNRef'}) and !exists($content{'authorization'})) {
89       croak("missing required field(s): PNRef or AuthCode\n");
90     }
91   }
92
93   $self->content(%content);
94   return;
95 }
96
97 sub submit {
98   my $self = shift;
99   my $content = $self->{_content};
100   $DB::single = 1 if $DEBUG;
101   
102   $self->setup_test if $self->test_transaction();
103
104   die "missing required option: domain\n" if !$self->domain();
105   $self->server($self->domain() . '.globalpay.com');
106
107   $self->remap_fields(%cc_fields);
108
109   my $action = $content->{'TransType'} or
110     croak "unknown action: '".$content->{'action'}."'\n";
111   $self->required_fields(@{ $required_fields{'All'} });
112   $self->required_fields(@{ $required_fields{$action} });
113
114   if($action eq 'Return' and !exists($content->{'PNRef'})) {
115     # This handles the case where a credit is ordered "blind", without
116     # an order_number.  Card information must be supplied.  Allowing 
117     # these is somewhat risky, and can be disabled at the account level 
118     # by the "Require Original PNRef" flag.
119     $self->required_fields(@{ $required_fields{'Return.blind'} });
120   }
121   
122   tie my %request, 'Tie::IxHash', 
123     map { $_ => $self->{_content}->{$_} } keys(%cc_fields);
124
125   $Business::OnlinePayment::HTTPS::DEBUG = $DEBUG;
126   $DB::single = 1 if $DEBUG;
127   my ($page, $response, %headers) = $self->https_post(\%request);
128
129   $self->server_response($page);
130   $self->is_success(0);
131   if(not $response =~ /^200/) {
132     $self->error_message("Connection failed: '$response'\n");
133     return;
134   }
135   my $data = XMLin($page);
136   if(!$data or !exists($data->{'Result'})) {
137     $self->error_message("Malformed server response: '$page'\n");
138     return;
139   }
140   $self->result_code($data->{'Result'});
141   $self->avs_code($data->{'GetAVSResult'});
142   $self->cvv2_response($data->{'GetCVResult'});
143   if($data->{'Result'} != 0) {
144     $self->error_message($data->{'Message'});
145     return;
146   }
147   else {
148     $self->is_success(1);
149     $self->authorization($data->{'AuthCode'});
150     $self->order_number($data->{'PNRef'});
151     return;
152   }
153 }
154
155 sub setup_test {
156   my $self = shift;
157   $self->domain('certapia');
158 # For test card information, see Global Transport API documentation.
159 }
160
161
162 =head1 NAME
163
164 Business::OnlinePayment::GlobalPayments - Global Transport backend for Business::OnlinePayment
165
166 =head1 SYNOPSIS
167
168 =head2 Initialization
169
170   my $trans = new Business::OnlinePayment('GlobalPayments',
171     domain => 'mymerchant' # Your account rep will supply this
172   );
173
174 =head2 Sale transaction
175
176   $trans->content(
177     login           => 'login',
178     password        => 'password',
179     type            => 'CC',
180     card_number     => '5500000000000004',
181     expiration      => '0211',
182     cvv2            => '255',
183     invoice_number  => '123321',
184     first_name      => 'Joe',
185     last_name       => 'Schmoe',
186     address         => '123 Anystreet',
187     city            => 'Sacramento',
188     state           => 'CA',
189     zip             => '95824',
190     action          => 'normal authorization',
191     amount          => '24.99'
192   );
193
194 =head2 Processing
195
196   $trans->submit;
197   if($trans->is_approved) {
198     print "Approved\n",
199           "Authorization: ", $trans->authorization, "\n",
200           "Order ID: ", $trans->order_number, "\n"
201   }
202   else {
203     print "Failed: ".$trans->error_message;
204   }
205
206 =head2 Void transaction
207   (or Return (credit) for full amount of original sale)
208
209   $trans->content(
210     login           => 'login',
211     password        => 'password',
212     action          => 'void', # or 'credit' for a Return
213     order_number    => '1001245',
214   );
215   $trans->submit;
216
217 =head1 NOTES
218
219 The following transaction types are supported:
220   Normal Authorization
221   Authorization Only
222   Post Authorization
223   Credit
224   Void
225
226 For Post Authorization, Credit, and Void, I<order_number> should be set to 
227 the order_number of the previous transaction.
228
229 Alternately, Post Authorization can be sent with I<authorization> set to an 
230 auth code obtained by telephone.  Similarly, Credit can be sent with credit 
231 account information instead of an I<order_number>.
232
233 By default, Global Transport will reject duplicate transactions (identical 
234 card number, expiration date, and amount) sent on the same day.  This can be 
235 overridden by setting I<force_duplicate> => 1.
236
237 =head1 AUTHOR
238
239 Mark Wells <mark@freeside.biz>
240
241 =head1 SUPPORT
242
243 Support for commercial users is available from Freeside Internet Services, 
244 Inc. <http://www.freeside.biz>
245
246 =head1 COPYRIGHT & LICENSE
247
248 Copyright 2009 Mark Wells, all rights reserved.
249
250 This program is free software; you can redistribute it and/or modify it
251 under the same terms as Perl itself.
252
253
254 =cut
255
256 1; # End of Business::OnlinePayment::GlobalPayments