File Coverage

blib/lib/Business/OnlinePayment/Beanstream.pm
Criterion Covered Total %
statement 15 92 16.3
branch 0 22 0.0
condition 0 15 0.0
subroutine 5 12 41.6
pod 5 7 71.4
total 25 148 16.8


line stmt bran cond sub pod time code
1             package Business::OnlinePayment::Beanstream;
2              
3 1     1   708 use strict;
  1         2  
  1         35  
4 1     1   885 use URI::Escape;
  1         1526  
  1         80  
5 1     1   1066 use Business::OnlinePayment;
  1         3452  
  1         25  
6 1     1   946 use Business::OnlinePayment::HTTPS;
  1         25793  
  1         48  
7 1     1   11 use vars qw/@ISA $VERSION $DEBUG @EXPORT @EXPORT_OK/;
  1         1  
  1         1482  
8              
9             @ISA=qw(Exporter AutoLoader Business::OnlinePayment::HTTPS);
10             @EXPORT=qw();
11             @EXPORT_OK=qw();
12             $VERSION='0.02';
13             $DEBUG = 0;
14              
15             sub set_defaults{
16 0     0 0   my $self = shift;
17 0           $self->server('www.beanstream.com');
18 0           $self->port('443');
19 0           $self->path('/scripts/process_transaction.asp');
20              
21 0           $self->build_subs(qw( order_number avs_code ));
22             }
23              
24             sub map_fields{
25 0     0 0   my $self = shift;
26 0           my %content = $self->content();
27              
28 0           my %actions = ( 'normal authorization' => 'P',
29             'authorization only' => 'PA',
30             'post authorization' => 'PAC',
31             'credit' => 'R', # not really supported yet
32             );
33 0   0       $content{action} = $actions{lc $content{action}} || $content{action};
34 0           $content{requestType} = 'BACKEND';
35 0   0       $content{expiration} ||= $content{exp_date}; # backward-compatibility 0.01
36             # owner | company | name
37 0           $self->content(%content);
38             }
39              
40             sub remap_fields{
41 0     0 1   my ($self,%map) = @_;
42 0           my %content = $self->content();
43 0   0       for (keys %map){ $content{$map{$_}} = $content{$_} || '' }
  0            
44 0           $self->content(%content);
45             }
46              
47             sub get_fields{
48 0     0 1   my ($self,@fields) = @_;
49 0           my %content = $self->content();
50 0           my %new = ();
51              
52 0   0       for (@fields){ $new{$_} = $content{$_} || '' }
  0            
53              
54 0           return %new;
55             }
56            
57              
58             sub submit {
59 0     0 1   my $self = shift;
60              
61             # Re: test_transaction - test mode is set on/off in the merchant account
62             # settings. No info on convenient way to set test mode per transaction.
63              
64 0 0         if ($DEBUG > 3) {
65 0           my %params = $self->content;
66 0           warn join("\n", map { " $_ => $params{$_}" } keys %params );
  0            
67             }
68              
69 0           $self->map_fields(); # set values with special handling
70 0           $self->remap_fields( # rename keys
71             login => 'merchant_id',
72             action => 'trnType',
73             description => 'trnComments',
74             amount => 'trnAmount',
75             invoice_number => 'trnOrderNumber',
76             owner => 'trnCardOwner',
77             name => 'ordName',
78             address => 'ordAddress1',
79             city => 'ordCity',
80             state => 'ordProvince',
81             zip => 'ordPostalCode',
82             country => 'ordCountry',
83             phone => 'ordPhoneNumber',
84             email => 'ordEmailAddress',
85             card_number => 'trnCardNumber',
86             expiration => 'trnExpYear',
87             order_number => 'adjId',
88             );
89              
90             # Credits/Returns/Adjustments with Beanstream, are not currently supported.
91             # would req: login, inv-num, action/trnType, username, password, adjId, amt
92             # Yes Beanstream really require phone & email, for Sales/Purchases
93 0           my @required = qw/login amount invoice_number name address city
94             state zip country phone card_number
95             expiration owner/;
96 0           $self->required_fields(@required);
97            
98             # We should prepare some fields to posting, for instance ordAddress1 should be cutted and trnExpYear
99             # should be separated to trnExpMonth and trnExpYear
100            
101 0           my %content=$self->content();
102 0           my $address = $content{ordAddress1};
103 0           ($content{ordAddress1}, $content{ordAddress2}) = unpack 'A32 A*', $address;
104            
105 0           my $date = $content{trnExpYear};
106 0 0         ($content{trnExpMonth},$content{trnExpYear}) = ($date =~/\//) ?
107             split /\//, $date :
108             unpack 'A2 A2', $date;
109            
110 0           $self->content(%content);
111            
112             # Now we are ready to post request
113            
114 0           my %params = $self->get_fields( qw/merchant_id trnType trnComments
115             trnAmount trnOrderNumber trnCardNumber
116             trnExpYear trnExpMonth trnCardOwner
117             ordName ordAddress1 ordCity ordProvince
118             ordPostalCode ordCountry ordPhoneNumber
119             ordEmailAddress requestType/ );
120              
121 0 0         warn join("\n", map { " $_ => $params{$_}" } keys %params )
  0            
122             if $DEBUG > 3;
123              
124             # Send transaction to Beanstream
125 0           my ($page, $server_response, %headers) = $self->https_post( \%params );
126              
127             # Convert multi-line error to a single line.
128 0           $server_response =~ s/[\r\n]+/ /g;
129            
130             # Handling server response
131 0 0 0       if ($server_response != 200 and $server_response !~ /200 OK/) {
132             # Connection error
133 0           $self->is_success(0);
134 0   0       my $diag_message = $server_response || "connection error";
135 0 0         warn $diag_message if $DEBUG;
136 0           $self->result_code( $diag_message );
137 0           $self->error_message( $diag_message );
138             } else {
139              
140 0 0         if ($DEBUG > 3) {
141 0           warn $page; # how helpful are %headers?
142             }
143 0           $self->server_response($page);
144              
145 0           my %fields;
146 0           for my $pair (split /&/, $page) {
147 0           my ($key, $value) = split '=', $pair;
148 0           $fields{$key} = URI::Escape::uri_unescape($value);
149 0           $fields{$key} =~ tr/+/ /;
150             }
151 0 0         warn join("\n", map { " $_ => $fields{$_}" } keys %fields )
  0            
152             if $DEBUG > 2;
153              
154 0           $self->result_code($fields{messageId});
155             # Was messageId =~/^[129]$/, but 9 is not approved per Reporting-Guide,
156             # and there are approval codes in 61..70, 561.
157 0 0         if ($fields{trnApproved}) {
158 0           $self->is_success(1);
159 0           $self->authorization($fields{messageText});
160 0           $self->order_number($fields{trnId});
161             } else {
162 0           $self->is_success(0);
163 0 0         if ($fields{errorMessage}) {
164 0           $self->error_message($fields{errorMessage});
165             } else {
166 0           $self->error_message($fields{messageText});
167             }
168             }
169              
170             # avs_code - Process-Transaction-API-Guide.pdf 1.6.3
171 0           my %avsTable = (0 => '',
172             5 => 'E',
173             9 => 'E',
174             A => 'A',
175             B => 'A',
176             C => '',
177             D => 'Y',
178             E => 'E',
179             G => '',
180             I => '',
181             M => 'Y',
182             N => 'N',
183             P => 'Z',
184             R => 'R',
185             );
186 0           $self->avs_code($avsTable{$fields{avsAddrMatch}});
187              
188             }
189             }
190              
191             sub response_headers{
192 0     0 1   my ($self,%headers) = @_;
193 0 0         $self->{headers} = join "\n", map{"$_: $headers{$_}"} keys %headers
  0            
194             if %headers;
195 0           $self->{headers};
196             }
197              
198             sub response_code{
199 0     0 1   my ($self,$code) = @_;
200 0 0         $self->{code} = $code if $code;
201 0           $self->{code};
202             }
203              
204             ###
205             # That's all
206             #
207             1;
208              
209             __END__