communigate provisioning phase 2: add svc_domain.trailer -> communigate TrailerText...
[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 =cut
124
125 #=item cust_balance - 
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
296
297 Sets the status of this pending pament to "done" (with statustext
298 "declined (manual)").
299
300 Currently only used when resolving pending payments manually.
301
302 =cut
303
304 sub decline {
305   my $self = shift;
306
307   #could send decline email too?  doesn't seem useful in manual resolution
308
309   $self->status('done');
310   $self->statustext("declined (manual)");
311   $self->replace;
312 }
313
314 # _upgrade_data
315 #
316 # Used by FS::Upgrade to migrate to a new database.
317
318 sub _upgrade_data {  #class method
319   my ($class, %opts) = @_;
320
321   my $sql =
322     "DELETE FROM cust_pay_pending WHERE status = 'new' AND _date < ".(time-600);
323
324   my $sth = dbh->prepare($sql) or die dbh->errstr;
325   $sth->execute or die $sth->errstr;
326
327 }
328
329 =back
330
331 =head1 BUGS
332
333 =head1 SEE ALSO
334
335 L<FS::Record>, schema.html from the base documentation.
336
337 =cut
338
339 1;
340