=item realtime_collect [ OPTION => VALUE ... ]
-Runs a realtime credit card, ACH (electronic check) or phone bill transaction
-via a Business::OnlinePayment or Business::OnlineThirdPartyPayment realtime
-gateway. See L<http://420.am/business-onlinepayment> and
-L<http://420.am/business-onlinethirdpartypayment> for supported gateways.
-
-On failure returns an error message.
+Attempt to collect the customer's current balance with a realtime credit
+card, electronic check, or phone bill transaction (see realtime_bop() below).
-Returns false or a hashref upon success. The hashref contains keys popup_url reference, and collectitems. The first is a URL to which a browser should be redirected for completion of collection. The second is a reference id for the transaction suitable for the end user. The collectitems is a reference to a list of name value pairs suitable for assigning to a html form and posted to popup_url.
+Returns the result of realtime_bop(): nothing, an error message, or a
+hashref of state information for a third-party transaction.
Available options are: I<method>, I<amount>, I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>, I<pkgnum>
option, or "Internet services" if that is unset.
If an I<invnum> is specified, this payment (if successful) is applied to the
-specified invoice. If you don't specify an I<invnum> you might want to
-call the B<apply_payments> method or set the I<apply> option.
+specified invoice.
-I<apply> can be set to true to apply a resulting payment.
+I<apply> will automatically apply a resulting payment.
-I<quiet> can be set true to surpress email decline notices.
+I<quiet> can be set true to suppress email decline notices.
I<paynum_ref> can be set to a scalar reference. It will be filled in with the
resulting paynum, if any.
option, or "Internet services" if that is unset.
If an I<invnum> is specified, this payment (if successful) is applied to the
-specified invoice. If you don't specify an I<invnum> you might want to
-call the B<apply_payments> method or set the I<apply> option.
+specified invoice. If the customer has exactly one open invoice, that
+invoice number will be assumed. If you don't specify an I<invnum> you might
+want to call the B<apply_payments> method or set the I<apply> option.
I<apply> can be set to true to apply a resulting payment.
I<discount_term> attempts to take a discount by prepaying for discount_term
+A direct (Business::OnlinePayment) transaction will return nothing on success,
+or an error message on failure.
+
+A third-party transaction will return a hashref containing:
+
+- popup_url: the URL to which a browser should be redirected to complete
+ the transaction.
+- collectitems: an arrayref of name-value pairs to be posted to popup_url.
+- reference: a reference ID for the transaction, to show the customer.
+
(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
=cut
}
$options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} );
- $options->{invnum} ||= '';
+
+ # Default invoice number if the customer has exactly one open invoice.
+ if( ! $options->{'invnum'} ) {
+ $options->{'invnum'} = '';
+ my @open = $self->open_cust_bill;
+ $options->{'invnum'} = $open[0]->invnum if scalar(@open) == 1;
+ }
+
$options->{payname} = $self->payname unless exists( $options->{payname} );
}
#check the balance
return "The customer's balance has changed; $options{method} transaction aborted."
if $self->balance < $balance;
- #&& $self->balance < $options{amount}; #might as well anyway?
#also check and make sure there aren't *other* pending payments for this cust
'custnum' => $self->custnum,
'status' => { op=>'!=', value=>'done' }
});
- # This is a problem. A self-service third party payment that fails somehow
- # can't be retried, EVER, until someone manually clears it. Totally
- # arbitrary fix: if the existing payment is more than two minutes old,
- # kill it. This doesn't limit how long it can take the pending payment
- # to complete, only how long it will obstruct new payments.
- my @still_pending;
- foreach (@pending) {
- if ( time - $_->_date > 120 ) {
+
+ #for third-party payments only, remove pending payments if they're in the
+ #'thirdparty' (waiting for customer action) state.
+ if ( $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+ foreach ( grep { $_->status eq 'thirdparty' } @pending ) {
my $error = $_->delete;
- warn "error deleting stale pending payment ".$_->paypendingnum.": $error"
- if $error; # not fatal, it will fail anyway
- }
- else {
- push @still_pending, $_;
+ warn "error deleting unfinished third-party payment ".
+ $_->paypendingnum . ": $error\n"
+ if $error;
}
+ @pending = grep { $_->status ne 'thirdparty' } @pending;
}
- @pending = @still_pending;
return "A payment is already being processed for this customer (".
join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ).
my $cust_pay_pending = new FS::cust_pay_pending {
'custnum' => $self->custnum,
- #'invnum' => $options{'invnum'},
'paid' => $options{amount},
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
if ( $transaction->is_success() && $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+ $cust_pay_pending->status('thirdparty');
+ my $cpp_err = $cust_pay_pending->replace;
+ return { error => $cpp_err } if $cpp_err;
return { reference => $cust_pay_pending->paypendingnum,
map { $_ => $transaction->$_ } qw ( popup_url collectitems ) };
my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
$cust_pay->invnum(''); #try again with no specific invnum
+ $cust_pay->paynum('');
my $error2 = $cust_pay->insert( $options{'manual'} ?
( 'manual' => 1 ) : ()
);
if ( $error2 ) {
# gah. but at least we have a record of the state we had to abort in
# from cust_pay_pending now.
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
my $e = "WARNING: $options{method} captured but payment not recorded -".
" error inserting payment (". $payment_gateway->gateway_module.
"): $error2".
my $error = $placeholder->depended_delete;
$error ||= $placeholder->delete;
warn "error removing provisioning jobs after declined paypendingnum ".
- $cust_pay_pending->paypendingnum. "\n";
+ $cust_pay_pending->paypendingnum. ": $error\n";
} else {
my $e = "error finding job $jobnum for declined paypendingnum ".
$cust_pay_pending->paypendingnum. "\n";