use strict;
use vars qw( $conf $DEBUG $me );
+use vars qw( $realtime_bop_decline_quiet ); #ugh
+use Data::Dumper;
+use Digest::MD5 qw(md5_base64);
+use Business::CreditCard 0.28;
use FS::UID qw( dbh );
use FS::Record qw( qsearch qsearchs );
+use FS::Misc qw( send_email );
use FS::payby;
use FS::cust_pay;
use FS::cust_pay_pending;
use FS::cust_refund;
-#$realtime_bop_decline_quiet = 0;
+$realtime_bop_decline_quiet = 0;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
=head1 SYNOPSIS
-=head1 DESCRIPTIONS
+=head1 DESCRIPTION
These methods are available on FS::cust_main objects.
sub realtime_collect {
my( $self, %options ) = @_;
+ local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+
if ( $DEBUG ) {
warn "$me realtime_collect:\n";
warn " $_ => $options{$_}\n" foreach keys %options;
I<depend_jobnum> allows payment capture to unlock export jobs
+I<discount_term> attempts to take a discount by prepaying for discount_term
+
(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
=cut
sub _payment_gateway {
my ($self, $options) = @_;
+ if ( $options->{'selfservice'} ) {
+ my $gatewaynum = FS::Conf->new->config('selfservice-payment_gateway');
+ if ( $gatewaynum ) {
+ return $options->{payment_gateway} ||=
+ qsearchs('payment_gateway', { gatewaynum => $gatewaynum });
+ }
+ }
+
$options->{payment_gateway} = $self->agent->payment_gateway( %$options )
unless exists($options->{payment_gateway});
sub realtime_bop {
my $self = shift;
+ local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+
my %options = ();
if (ref($_[0]) eq 'HASH') {
%options = %{$_[0]};
'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 ) {
+ my $error = $_->delete;
+ warn "error deleting stale pending payment ".$_->paypendingnum.": $error"
+ if $error; # not fatal, it will fail anyway
+ }
+ else {
+ push @still_pending, $_;
+ }
+ }
+ @pending = @still_pending;
+
return "A payment is already being processed for this customer (".
join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ).
"); $options{method} transaction aborted."
'customer_id' => $self->custnum,
%$bop_content,
'reference' => $cust_pay_pending->paypendingnum, #for now
+ 'callback_url' => $payment_gateway->gateway_callback_url,
'email' => $email,
%content, #after
);
sub _realtime_bop_result {
my( $self, $cust_pay_pending, $transaction, %options ) = @_;
+
+ local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+
if ( $DEBUG ) {
warn "$me _realtime_bop_result: pending transaction ".
$cust_pay_pending->paypendingnum. "\n";
'paybatch' => $paybatch,
'paydate' => $cust_pay_pending->paydate,
'pkgnum' => $cust_pay_pending->pkgnum,
+ 'discount_term' => $options{'discount_term'},
} );
#doesn't hurt to know, even though the dup check is in cust_pay_pending now
$cust_pay->payunique( $options{payunique} )
}
- if ( !$options{'quiet'} && !$FS::cust_main::realtime_bop_decline_quiet
- && $conf->exists('emaildecline')
+ if ( !$options{'quiet'} && !$realtime_bop_decline_quiet
+ && $conf->exists('emaildecline', $self->agentnum)
&& grep { $_ ne 'POST' } $self->invoicing_list
&& ! grep { $transaction->error_message =~ /$_/ }
- $conf->config('emaildecline-exclude')
+ $conf->config('emaildecline-exclude', $self->agentnum)
) {
# Send a decline alert to the customer.
sub realtime_botpp_capture {
my( $self, $cust_pay_pending, %options ) = @_;
+
+ local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+
if ( $DEBUG ) {
warn "$me realtime_botpp_capture: pending transaction $cust_pay_pending\n";
warn " $_ => $options{$_}\n" foreach keys %options;
my $method = FS::payby->payby2bop($cust_pay_pending->payby);
- my $payment_gateway = $cust_pay_pending->gatewaynum
- ? qsearchs( 'payment_gateway',
- { gatewaynum => $cust_pay_pending->gatewaynum }
+ my $payment_gateway;
+ my $gatewaynum = $cust_pay_pending->getfield('gatewaynum');
+ $payment_gateway = $gatewaynum ? qsearchs( 'payment_gateway',
+ { gatewaynum => $gatewaynum }
)
: $self->agent->payment_gateway( 'method' => $method,
# 'invnum' => $cust_pay_pending->invnum,
my $error =
$self->_realtime_bop_result( $cust_pay_pending, $transaction, %options );
- {
+ if ( $options{'apply'} ) {
+ my $apply_error = $self->apply_payments_and_credits;
+ if ( $apply_error ) {
+ warn "WARNING: error applying payment: $apply_error\n";
+ }
+ }
+
+ return {
bill_error => $error,
session_id => $cust_pay_pending->session_id,
}
sub realtime_refund_bop {
my $self = shift;
+ local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG;
+
my %options = ();
if (ref($_[0]) eq 'HASH') {
%options = %{$_[0]};