even more reliable multiple-payment/double-click/concurrent-payment-form protection
[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 );
6 use FS::payby;
7 use FS::payinfo_Mixin;
8 use FS::cust_main;
9 use FS::cust_pay;
10
11 @ISA = qw(FS::Record FS::payinfo_Mixin);
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 - primary key
44
45 =item custnum - customer (see L<FS::cust_main>)
46
47 =item paid - Amount of this payment
48
49 =item _date - specified as a UNIX timestamp; see L<perlfunc/"time">.  Also see
50 L<Time::Local> and L<Date::Parse> for conversion functions.
51
52 =item payby - Payment Type (See L<FS::payinfo_Mixin> for valid payby values)
53
54 =item payinfo - Payment Information (See L<FS::payinfo_Mixin> for data format)
55
56 =item paymask - Masked payinfo (See L<FS::payinfo_Mixin> for how this works)
57
58 =item paydate - Expiration date
59
60 =item payunique - Unique identifer to prevent duplicate transactions.
61
62 =item status - new (acquires basic lock on payunique), pending (transaction is pending with the gateway), authorized (only used for two-stage transactions that require a separate capture step), captured/declined (transaction completed with payment gateway, not yet recorded in the database), done (transaction recorded in database)
63
64 =item statustext - 
65
66 =cut
67
68 #=item cust_balance - 
69
70 =item paynum - 
71
72
73 =back
74
75 =head1 METHODS
76
77 =over 4
78
79 =item new HASHREF
80
81 Creates a new pending payment.  To add the pending payment to the database, see L<"insert">.
82
83 Note that this stores the hash reference, not a distinct copy of the hash it
84 points to.  You can ask the object for a copy with the I<hash> method.
85
86 =cut
87
88 # the new method can be inherited from FS::Record, if a table method is defined
89
90 sub table { 'cust_pay_pending'; }
91
92 =item insert
93
94 Adds this record to the database.  If there is an error, returns the error,
95 otherwise returns false.
96
97 =cut
98
99 # the insert method can be inherited from FS::Record
100
101 =item delete
102
103 Delete this record from the database.
104
105 =cut
106
107 # the delete method can be inherited from FS::Record
108
109 =item replace OLD_RECORD
110
111 Replaces the OLD_RECORD with this one in the database.  If there is an error,
112 returns the error, otherwise returns false.
113
114 =cut
115
116 # the replace method can be inherited from FS::Record
117
118 =item check
119
120 Checks all fields to make sure this is a valid pending payment.  If there is
121 an error, returns the error, otherwise returns false.  Called by the insert
122 and replace methods.
123
124 =cut
125
126 # the check method should currently be supplied - FS::Record contains some
127 # data checking routines
128
129 sub check {
130   my $self = shift;
131
132   my $error = 
133     $self->ut_numbern('paypendingnum')
134     || $self->ut_number('pendingnum')
135     || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
136     || $self->ut_money('paid')
137     || $self->ut_numbern('_date')
138     || $self->ut_textn('payunique')
139     || $self->ut_text('status')
140     #|| $self->ut_textn('statustext')
141     || $self->ut_anythingn('statustext')
142     #|| $self->ut_money('cust_balance')
143     || $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
144     || $self->payinfo_check() #payby/payinfo/paymask/paydate
145   ;
146   return $error if $error;
147
148   $self->_date(time) unless $self->_date;
149
150   # UNIQUE index should catch this too, without race conditions, but this
151   # should give a better error message the other 99.9% of the time...
152   if ( length($self->payunique) ) {
153     my $cust_pay_pending =
154       qsearchs('cust_pay_pending', { 'payunique' => $self->payunique } );
155     if ( $cust_pay_pending ) {
156       #well, it *could* be a better error message
157       return "duplicate transaction - a payment with unique identifer ".
158              $self->payunique. " already exists";
159     }
160   }
161
162   $self->SUPER::check;
163 }
164
165 =back
166
167 =head1 BUGS
168
169 =head1 SEE ALSO
170
171 L<FS::Record>, schema.html from the base documentation.
172
173 =cut
174
175 1;
176