merge webpay support in with autoselection of old realtime_bop and realtime_refund_bop
[freeside.git] / FS / FS / cust_pay_pending.pm
1 package FS::cust_pay_pending;
2
3 use strict;
4 use vars qw( @ISA  @encrypted_fields );
5 use FS::Record qw( qsearch qsearchs dbh ); #dbh for _upgrade_data
6 use FS::payinfo_transaction_Mixin;
7 use FS::cust_main_Mixin;
8 use FS::cust_main;
9 use FS::cust_pay;
10
11 @ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record );
12
13 @encrypted_fields = ('payinfo');
14
15 =head1 NAME
16
17 FS::cust_pay_pending - Object methods for cust_pay_pending records
18
19 =head1 SYNOPSIS
20
21   use FS::cust_pay_pending;
22
23   $record = new FS::cust_pay_pending \%hash;
24   $record = new FS::cust_pay_pending { 'column' => 'value' };
25
26   $error = $record->insert;
27
28   $error = $new_record->replace($old_record);
29
30   $error = $record->delete;
31
32   $error = $record->check;
33
34 =head1 DESCRIPTION
35
36 An FS::cust_pay_pending object represents an pending payment.  It reflects 
37 local state through the multiple stages of processing a real-time transaction
38 with an external gateway.  FS::cust_pay_pending inherits from FS::Record.  The
39 following fields are currently supported:
40
41 =over 4
42
43 =item paypendingnum
44
45 Primary key
46
47 =item custnum
48
49 Customer (see L<FS::cust_main>)
50
51 =item paid
52
53 Amount of this payment
54
55 =item _date
56
57 Specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
58 L<Time::Local> and L<Date::Parse> for conversion functions.
59
60 =item payby
61
62 Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
63
64 =item payinfo
65
66 Payment Information (See L<FS::payinfo_Mixin> for data format)
67
68 =item paymask
69
70 Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
71
72 =item paydate
73
74 Expiration date
75
76 =item payunique
77
78 Unique identifer to prevent duplicate transactions.
79
80 =item status
81
82 Pending transaction status, one of the following:
83
84 =over 4
85
86 =item new
87
88 Aquires basic lock on payunique
89
90 =item pending
91
92 Transaction is pending with the gateway
93
94 =item authorized
95
96 Only used for two-stage transactions that require a separate capture step
97
98 =item captured
99
100 Transaction completed with payment gateway (sucessfully), not yet recorded in
101 the database
102
103 =item declined
104
105 Transaction completed with payment gateway (declined), not yet recorded in
106 the database
107
108 =item done
109
110 Transaction recorded in database
111
112 =back
113
114 =item statustext
115
116 Additional status information.
117
118 =cut
119
120 #=item cust_balance - 
121
122 =item paynum - 
123
124
125 =back
126
127 =head1 METHODS
128
129 =over 4
130
131 =item new HASHREF
132
133 Creates a new pending payment.  To add the pending payment to the database, see L<"insert">.
134
135 Note that this stores the hash reference, not a distinct copy of the hash it
136 points to.  You can ask the object for a copy with the I<hash> method.
137
138 =cut
139
140 # the new method can be inherited from FS::Record, if a table method is defined
141
142 sub table { 'cust_pay_pending'; }
143
144 =item insert
145
146 Adds this record to the database.  If there is an error, returns the error,
147 otherwise returns false.
148
149 =cut
150
151 # the insert method can be inherited from FS::Record
152
153 =item delete
154
155 Delete this record from the database.
156
157 =cut
158
159 # the delete method can be inherited from FS::Record
160
161 =item replace OLD_RECORD
162
163 Replaces the OLD_RECORD with this one in the database.  If there is an error,
164 returns the error, otherwise returns false.
165
166 =cut
167
168 # the replace method can be inherited from FS::Record
169
170 =item check
171
172 Checks all fields to make sure this is a valid pending payment.  If there is
173 an error, returns the error, otherwise returns false.  Called by the insert
174 and replace methods.
175
176 =cut
177
178 # the check method should currently be supplied - FS::Record contains some
179 # data checking routines
180
181 sub check {
182   my $self = shift;
183
184   my $error = 
185     $self->ut_numbern('paypendingnum')
186     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
187     || $self->ut_money('paid')
188     || $self->ut_numbern('_date')
189     || $self->ut_textn('payunique')
190     || $self->ut_text('status')
191     #|| $self->ut_textn('statustext')
192     || $self->ut_anything('statustext')
193     #|| $self->ut_money('cust_balance')
194     || $self->ut_hexn('session_id')
195     || $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
196     || $self->payinfo_check() #payby/payinfo/paymask/paydate
197   ;
198   return $error if $error;
199
200   $self->_date(time) unless $self->_date;
201
202   # UNIQUE index should catch this too, without race conditions, but this
203   # should give a better error message the other 99.9% of the time...
204   if ( length($self->payunique) ) {
205     my $cust_pay_pending = qsearchs('cust_pay_pending', {
206       'payunique'     => $self->payunique,
207       'paypendingnum' => { op=>'!=', value=>$self->paypendingnum },
208     });
209     if ( $cust_pay_pending ) {
210       #well, it *could* be a better error message
211       return "duplicate transaction - a payment with unique identifer ".
212              $self->payunique. " already exists";
213     }
214   }
215
216   $self->SUPER::check;
217 }
218
219 =item cust_main
220
221 Returns the associated L<FS::cust_main> record if any.  Otherwise returns false.
222
223 =cut
224
225 sub cust_main {
226   my $self = shift;
227   qsearchs('cust_main', { custnum => $self->custnum } );
228 }
229
230
231 #these two are kind-of false laziness w/cust_main::realtime_bop
232 #(currently only used when resolving pending payments manually)
233
234 =item insert_cust_pay
235
236 Sets the status of this pending pament to "done" (with statustext
237 "captured (manual)"), and inserts a payment record (see L<FS::cust_pay>).
238
239 Currently only used when resolving pending payments manually.
240
241 =cut
242
243 sub insert_cust_pay {
244   my $self = shift;
245
246   my $cust_pay = new FS::cust_pay ( {
247      'custnum'  => $self->custnum,
248      'paid'     => $self->paid,
249      '_date'    => $self->_date, #better than passing '' for now
250      'payby'    => $self->payby,
251      'payinfo'  => $self->payinfo,
252      'paybatch' => $self->paybatch,
253      'paydate'  => $self->paydate,
254   } );
255
256   my $oldAutoCommit = $FS::UID::AutoCommit;
257   local $FS::UID::AutoCommit = 0;
258   my $dbh = dbh;
259
260   #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
261
262   my $error = $cust_pay->insert;#($options{'manual'} ? ( 'manual' => 1 ) : () );
263
264   if ( $error ) {
265     # gah.
266     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
267     return $error;
268   }
269
270   $self->status('done');
271   $self->statustext('captured (manual)');
272   $self->paynum($cust_pay->paynum);
273   my $cpp_done_err = $self->replace;
274
275   if ( $cpp_done_err ) {
276
277     $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
278     return $cpp_done_err;
279
280   } else {
281
282     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
283     return ''; #no error
284
285   }
286
287 }
288
289 =item decline
290
291 Sets the status of this pending pament to "done" (with statustext
292 "declined (manual)").
293
294 Currently only used when resolving pending payments manually.
295
296 =cut
297
298 sub decline {
299   my $self = shift;
300
301   #could send decline email too?  doesn't seem useful in manual resolution
302
303   $self->status('done');
304   $self->statustext("declined (manual)");
305   $self->replace;
306 }
307
308 # _upgrade_data
309 #
310 # Used by FS::Upgrade to migrate to a new database.
311
312 sub _upgrade_data {  #class method
313   my ($class, %opts) = @_;
314
315   my $sql =
316     "DELETE FROM cust_pay_pending WHERE status = 'new' AND _date < ".(time-600);
317
318   my $sth = dbh->prepare($sql) or die dbh->errstr;
319   $sth->execute or die $sth->errstr;
320
321 }
322
323 =back
324
325 =head1 BUGS
326
327 =head1 SEE ALSO
328
329 L<FS::Record>, schema.html from the base documentation.
330
331 =cut
332
333 1;
334