Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / payment_gateway.pm
1 package FS::payment_gateway;
2
3 use strict;
4 use vars qw( @ISA $me $DEBUG );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::option_Common;
7 use FS::agent_payment_gateway;
8
9 @ISA = qw( FS::option_Common );
10 $me = '[ FS::payment_gateway ]';
11 $DEBUG=0;
12
13 =head1 NAME
14
15 FS::payment_gateway - Object methods for payment_gateway records
16
17 =head1 SYNOPSIS
18
19   use FS::payment_gateway;
20
21   $record = new FS::payment_gateway \%hash;
22   $record = new FS::payment_gateway { 'column' => 'value' };
23
24   $error = $record->insert;
25
26   $error = $new_record->replace($old_record);
27
28   $error = $record->delete;
29
30   $error = $record->check;
31
32 =head1 DESCRIPTION
33
34 An FS::payment_gateway object represents an payment gateway.
35 FS::payment_gateway inherits from FS::Record.  The following fields are
36 currently supported:
37
38 =over 4
39
40 =item gatewaynum - primary key
41
42 =item gateway_namespace - Business::OnlinePayment, Business::OnlineThirdPartyPayment, or Business::BatchPayment
43
44 =item gateway_module - Business::OnlinePayment:: module name
45
46 =item gateway_username - payment gateway username
47
48 =item gateway_password - payment gateway password
49
50 =item gateway_action - optional action or actions (multiple actions are separated with `,': for example: `Authorization Only, Post Authorization').  Defaults to `Normal Authorization'.
51
52 =item disabled - Disabled flag, empty or 'Y'
53
54 =item auto_resolve_status - For BatchPayment only, set to 'approve' to 
55 auto-approve unresolved payments after some number of days, 'reject' to 
56 auto-decline them, or null to do nothing.
57
58 =item auto_resolve_days - For BatchPayment, the number of days to wait before 
59 auto-resolving the batch.
60
61 =back
62
63 =head1 METHODS
64
65 =over 4
66
67 =item new HASHREF
68
69 Creates a new payment gateway.  To add the payment gateway to the database, see
70 L<"insert">.
71
72 Note that this stores the hash reference, not a distinct copy of the hash it
73 points to.  You can ask the object for a copy with the I<hash> method.
74
75 =cut
76
77 # the new method can be inherited from FS::Record, if a table method is defined
78
79 sub table { 'payment_gateway'; }
80
81 =item insert
82
83 Adds this record to the database.  If there is an error, returns the error,
84 otherwise returns false.
85
86 =cut
87
88 # the insert method can be inherited from FS::Record
89
90 =item delete
91
92 Delete this record from the database.
93
94 =cut
95
96 # the delete method can be inherited from FS::Record
97
98 =item replace OLD_RECORD
99
100 Replaces the OLD_RECORD with this one in the database.  If there is an error,
101 returns the error, otherwise returns false.
102
103 =cut
104
105 # the replace method can be inherited from FS::Record
106
107 =item check
108
109 Checks all fields to make sure this is a valid payment gateway.  If there is
110 an error, returns the error, otherwise returns false.  Called by the insert
111 and replace methods.
112
113 =cut
114
115 # the check method should currently be supplied - FS::Record contains some
116 # data checking routines
117
118 sub check {
119   my $self = shift;
120
121   my $error = 
122     $self->ut_numbern('gatewaynum')
123     || $self->ut_alpha('gateway_module')
124     || $self->ut_enum('gateway_namespace', ['Business::OnlinePayment',
125                                             'Business::OnlineThirdPartyPayment',
126                                             'Business::BatchPayment',
127                                            ] )
128     || $self->ut_textn('gateway_username')
129     || $self->ut_anything('gateway_password')
130     || $self->ut_textn('gateway_callback_url')  # a bit too permissive
131     || $self->ut_enum('disabled', [ '', 'Y' ] )
132     || $self->ut_enum('auto_resolve_status', [ '', 'approve', 'reject' ])
133     || $self->ut_numbern('auto_resolve_days')
134     #|| $self->ut_textn('gateway_action')
135   ;
136   return $error if $error;
137
138   if ( $self->gateway_namespace eq 'Business::BatchPayment' ) {
139     $self->gateway_action('Payment');
140   } elsif ( $self->gateway_action ) {
141     my @actions = split(/,\s*/, $self->gateway_action);
142     $self->gateway_action(
143       join( ',', map { /^(Normal Authorization|Authorization Only|Credit|Post Authorization)$/
144                          or return "Unknown action $_";
145                        $1
146                      }
147                      @actions
148           )
149    );
150   } else {
151     $self->gateway_action('Normal Authorization');
152   }
153
154   # this little kludge mimics FS::CGI::popurl
155   $self->gateway_callback_url($self->gateway_callback_url. '/')
156     if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ );
157
158   $self->SUPER::check;
159 }
160
161 =item agent_payment_gateway
162
163 Returns any agent overrides for this payment gateway.
164
165 =cut
166
167 sub agent_payment_gateway {
168   my $self = shift;
169   qsearch('agent_payment_gateway', { 'gatewaynum' => $self->gatewaynum } );
170 }
171
172 =item disable
173
174 Disables this payment gateway: deletes all associated agent_payment_gateway
175 overrides and sets the I<disabled> field to "B<Y>".
176
177 =cut
178
179 sub disable {
180   my $self = shift;
181
182   local $SIG{HUP} = 'IGNORE';
183   local $SIG{INT} = 'IGNORE';
184   local $SIG{QUIT} = 'IGNORE';
185   local $SIG{TERM} = 'IGNORE';
186   local $SIG{TSTP} = 'IGNORE';
187   local $SIG{PIPE} = 'IGNORE';
188
189   my $oldAutoCommit = $FS::UID::AutoCommit;
190   local $FS::UID::AutoCommit = 0;
191   my $dbh = dbh;
192
193   foreach my $agent_payment_gateway ( $self->agent_payment_gateway ) {
194     my $error = $agent_payment_gateway->delete;
195     if ( $error ) {
196       $dbh->rollback if $oldAutoCommit;
197       return "error deleting agent_payment_gateway override: $error";
198     }
199   }
200
201   $self->disabled('Y');
202   my $error = $self->replace();
203   if ( $error ) {
204     $dbh->rollback if $oldAutoCommit;
205     return "error disabling payment_gateway: $error";
206   }
207
208   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
209   '';
210
211 }
212
213 =item label
214
215 Returns a semi-friendly label for the gateway.
216
217 =cut
218
219 sub label {
220   my $self = shift;
221   $self->gatewaynum . ': ' . 
222   $self->gateway_username . '@' . 
223   $self->gateway_module
224 }
225
226 =item namespace_description
227
228 returns a friendly name for the namespace
229
230 =cut
231
232 my %namespace2description = (
233   '' => 'Direct',
234   'Business::OnlinePayment' => 'Direct',
235   'Business::OnlineThirdPartyPayment' => 'Hosted',
236   'Business::BatchPayment' => 'Batch',
237 );
238
239 sub namespace_description {
240   $namespace2description{shift->gateway_namespace} || 'Unknown';
241 }
242
243 =item batch_processor OPTIONS
244
245 For BatchPayment gateways only.  Returns a 
246 L<Business::BatchPayment::Processor> object to communicate with the 
247 gateway.
248
249 OPTIONS will be passed to the constructor, along with any gateway 
250 options in the database for this L<FS::payment_gateway>.  Useful things
251 to include there may include 'input' and 'output' (to direct transport
252 to files), 'debug', and 'test_mode'.
253
254 If the global 'business-batchpayment-test_transaction' flag is set, 
255 'test_mode' will be forced on, and gateways that don't support test
256 mode will be disabled.
257
258 =cut
259
260 sub batch_processor {
261   local $@;
262   my $self = shift;
263   my %opt = @_;
264   my $batch = $opt{batch};
265   my $output = $opt{output};
266   die 'gateway '.$self->gatewaynum.' is not a Business::BatchPayment gateway'
267     unless $self->gateway_namespace eq 'Business::BatchPayment';
268   eval "use Business::BatchPayment;";
269   die "couldn't load Business::BatchPayment: $@" if $@;
270
271   my $conf = new FS::Conf;
272   my $test_mode = $conf->exists('business-batchpayment-test_transaction');
273   $opt{'test_mode'} = 1 if $test_mode;
274
275   my $module = $self->gateway_module;
276   my $processor = eval { 
277     Business::BatchPayment->create($module, $self->options, %opt)
278   };
279   die "failed to create Business::BatchPayment::$module object: $@"
280     if $@;
281
282   die "$module does not support test mode"
283     if $test_mode and not $processor->does('Business::BatchPayment::TestMode');
284
285   return $processor;
286 }
287
288 # _upgrade_data
289 #
290 # Used by FS::Upgrade to migrate to a new database.
291 #
292 #
293
294 sub _upgrade_data {
295   my ($class, %opts) = @_;
296   my $dbh = dbh;
297
298   warn "$me upgrading $class\n" if $DEBUG;
299
300   foreach ( qsearch( 'payment_gateway', { 'gateway_namespace' => '' } ) ) {
301     $_->gateway_namespace('Business::OnlinePayment');  #defaulting
302     my $error = $_->replace;
303     die "$class had error during upgrade replacement: $error" if $error;
304   }
305 }
306
307 =back
308
309 =head1 BUGS
310
311 =head1 SEE ALSO
312
313 L<FS::Record>, schema.html from the base documentation.
314
315 =cut
316
317 1;
318