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