1 package FS::cust_pay_pending;
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;
12 @ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record );
14 @encrypted_fields = ('payinfo');
15 sub nohistory_fields { ('payinfo'); }
19 FS::cust_pay_pending - Object methods for cust_pay_pending records
23 use FS::cust_pay_pending;
25 $record = new FS::cust_pay_pending \%hash;
26 $record = new FS::cust_pay_pending { 'column' => 'value' };
28 $error = $record->insert;
30 $error = $new_record->replace($old_record);
32 $error = $record->delete;
34 $error = $record->check;
38 An FS::cust_pay_pending object represents an pending payment. It reflects
39 local state through the multiple stages of processing a real-time transaction
40 with an external gateway. FS::cust_pay_pending inherits from FS::Record. The
41 following fields are currently supported:
51 Customer (see L<FS::cust_main>)
55 Amount of this payment
59 Specified as a UNIX timestamp; see L<perlfunc/"time">. Also see
60 L<Time::Local> and L<Date::Parse> for conversion functions.
64 Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
68 Payment Information (See L<FS::payinfo_Mixin> for data format)
72 Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
80 Unique identifer to prevent duplicate transactions.
84 Desired pkgnum when using experimental package balances.
88 Pending transaction status, one of the following:
94 Aquires basic lock on payunique
98 Transaction is pending with the gateway
102 Customer has been sent to an off-site payment gateway to complete processing
106 Only used for two-stage transactions that require a separate capture step
110 Transaction completed with payment gateway (sucessfully), not yet recorded in
115 Transaction completed with payment gateway (declined), not yet recorded in
120 Transaction recorded in database
126 Additional status information.
130 L<FS::payment_gateway> id.
143 Creates a new pending payment. To add the pending payment to the database, see L<"insert">.
145 Note that this stores the hash reference, not a distinct copy of the hash it
146 points to. You can ask the object for a copy with the I<hash> method.
150 # the new method can be inherited from FS::Record, if a table method is defined
152 sub table { 'cust_pay_pending'; }
156 Adds this record to the database. If there is an error, returns the error,
157 otherwise returns false.
161 # the insert method can be inherited from FS::Record
165 Delete this record from the database.
169 # the delete method can be inherited from FS::Record
171 =item replace OLD_RECORD
173 Replaces the OLD_RECORD with this one in the database. If there is an error,
174 returns the error, otherwise returns false.
178 # the replace method can be inherited from FS::Record
182 Checks all fields to make sure this is a valid pending payment. If there is
183 an error, returns the error, otherwise returns false. Called by the insert
188 # the check method should currently be supplied - FS::Record contains some
189 # data checking routines
195 $self->ut_numbern('paypendingnum')
196 || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
197 || $self->ut_money('paid')
198 || $self->ut_numbern('_date')
199 || $self->ut_textn('payunique')
200 || $self->ut_text('status')
201 #|| $self->ut_textn('statustext')
202 || $self->ut_anything('statustext')
203 #|| $self->ut_money('cust_balance')
204 || $self->ut_hexn('session_id')
205 || $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
206 || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
207 || $self->payinfo_check() #payby/payinfo/paymask/paydate
209 return $error if $error;
211 $self->_date(time) unless $self->_date;
213 # UNIQUE index should catch this too, without race conditions, but this
214 # should give a better error message the other 99.9% of the time...
215 if ( length($self->payunique) ) {
216 my $cust_pay_pending = qsearchs('cust_pay_pending', {
217 'payunique' => $self->payunique,
218 'paypendingnum' => { op=>'!=', value=>$self->paypendingnum },
220 if ( $cust_pay_pending ) {
221 #well, it *could* be a better error message
222 return "duplicate transaction - a payment with unique identifer ".
223 $self->payunique. " already exists";
232 Returns the associated L<FS::cust_main> record if any. Otherwise returns false.
238 qsearchs('cust_main', { custnum => $self->custnum } );
242 #these two are kind-of false laziness w/cust_main::realtime_bop
243 #(currently only used when resolving pending payments manually)
245 =item insert_cust_pay
247 Sets the status of this pending pament to "done" (with statustext
248 "captured (manual)"), and inserts a payment record (see L<FS::cust_pay>).
250 Currently only used when resolving pending payments manually.
254 sub insert_cust_pay {
257 my $cust_pay = new FS::cust_pay ( {
258 'custnum' => $self->custnum,
259 'paid' => $self->paid,
260 '_date' => $self->_date, #better than passing '' for now
261 'payby' => $self->payby,
262 'payinfo' => $self->payinfo,
263 'paybatch' => $self->paybatch,
264 'paydate' => $self->paydate,
267 my $oldAutoCommit = $FS::UID::AutoCommit;
268 local $FS::UID::AutoCommit = 0;
271 #start a transaction, insert the cust_pay and set cust_pay_pending.status to done in a single transction
273 my $error = $cust_pay->insert;#($options{'manual'} ? ( 'manual' => 1 ) : () );
277 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
281 $self->status('done');
282 $self->statustext('captured (manual)');
283 $self->paynum($cust_pay->paynum);
284 my $cpp_done_err = $self->replace;
286 if ( $cpp_done_err ) {
288 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
289 return $cpp_done_err;
293 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
300 =item decline [ STATUSTEXT ]
302 Sets the status of this pending payment to "done" (with statustext
303 "declined (manual)" unless otherwise specified).
305 Currently only used when resolving pending payments manually.
311 my $statustext = shift || "declined (manual)";
313 #could send decline email too? doesn't seem useful in manual resolution
315 $self->status('done');
316 $self->statustext($statustext);
322 # Used by FS::Upgrade to migrate to a new database.
324 sub _upgrade_data { #class method
325 my ($class, %opts) = @_;
328 "DELETE FROM cust_pay_pending WHERE status = 'new' AND _date < ".(time-600);
330 my $sth = dbh->prepare($sql) or die dbh->errstr;
331 $sth->execute or die $sth->errstr;
341 L<FS::Record>, schema.html from the base documentation.