use FS::payby;
use FS::cust_pay;
use FS::cust_pay_pending;
+use FS::cust_bill_pay;
use FS::cust_refund;
use FS::banned_pay;
Required arguments in the hashref are I<method>, and I<amount>
-Available methods are: I<CC>, I<ECHECK> and I<LEC>
+Available methods are: I<CC>, I<ECHECK>, I<LEC>, and I<PAYPAL>
Available optional arguments are: I<description>, I<invnum>, I<apply>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
}
}
- $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} );
+ unless ( exists( $options->{'payinfo'} ) ) {
+ $options->{'payinfo'} = $self->payinfo;
+ $options->{'paymask'} = $self->paymask;
+ }
# Default invoice number if the customer has exactly one open invoice.
if( ! $options->{'invnum'} ) {
? $options->{country}
: $self->country;
- $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/
+ #3.0 is a good a time as any to get rid of this... add a config to pass it
+ # if anyone still needs it
+ #$content{referer} = 'http://cleanwhisker.420.am/';
+
$content{phone} = $self->daytime || $self->night;
my $currency = $conf->exists('business-onlinepayment-currency')
'CC' => 'CARD',
'ECHECK' => 'CHEK',
'LEC' => 'LECB',
+ 'PAYPAL' => 'PPAL',
);
sub realtime_bop {
my $cc_surcharge = 0;
my $cc_surcharge_pct = 0;
$cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage')
- if $conf->config('credit-card-surcharge-percentage');
-
+ if $conf->config('credit-card-surcharge-percentage')
+ && $options{method} eq 'CC';
+
# always add cc surcharge if called from event
if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) {
$cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100;
if ( $DEBUG ) {
warn "$me realtime_bop (new): $options{method} $options{amount}\n";
warn " cc_surcharge = $cc_surcharge\n";
+ }
+ if ( $DEBUG > 2 ) {
warn " $_ => $options{$_}\n" foreach keys %options;
}
(exists($options{'paytype'}) && $options{'paytype'})
? uc($options{'paytype'})
: uc($self->getfield('paytype')) || 'PERSONAL CHECKING';
- $content{account_name} = $self->getfield('first'). ' '.
- $self->getfield('last');
+
+ if ( $content{account_type} =~ /BUSINESS/i && $self->company ) {
+ $content{account_name} = $self->company;
+ } else {
+ $content{account_name} = $self->getfield('first'). ' '.
+ $self->getfield('last');
+ }
$content{customer_org} = $self->company ? 'B' : 'I';
$content{state_id} = exists($options{'stateid'})
? $options{'balance'}
: $self->balance;
+ warn "claiming mutex on customer ". $self->custnum. "\n" if $DEBUG > 1;
$self->select_for_update; #mutex ... just until we get our pending record in
+ warn "obtained mutex on customer ". $self->custnum. "\n" if $DEBUG > 1;
#the checks here are intended to catch concurrent payments
#double-form-submission prevention is taken care of in cust_pay_pending::check
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
'payinfo' => $options{payinfo},
+ 'paymask' => $options{paymask},
'paydate' => $paydate,
'recurring_billing' => $content{recurring_billing},
'pkgnum' => $options{'pkgnum'},
};
$cust_pay_pending->payunique( $options{payunique} )
if defined($options{payunique}) && length($options{payunique});
+
+ warn "inserting cust_pay_pending record for customer ". $self->custnum. "\n"
+ if $DEBUG > 1;
my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted
return $cpp_new_err if $cpp_new_err;
+ warn "inserted cust_pay_pending record for customer ". $self->custnum. "\n"
+ if $DEBUG > 1;
+ warn Dumper($cust_pay_pending) if $DEBUG > 2;
+
my( $action1, $action2 ) =
split( /\s*\,\s*/, $payment_gateway->gateway_action );
%$bop_content,
'reference' => $cust_pay_pending->paypendingnum, #for now
'callback_url' => $payment_gateway->gateway_callback_url,
+ 'cancel_url' => $payment_gateway->gateway_cancel_url,
'email' => $email,
%content, #after
);
if ( $transaction->can('card_token') && $transaction->card_token ) {
- $self->card_token($transaction->card_token);
-
if ( $options{'payinfo'} eq $self->payinfo ) {
$self->payinfo($transaction->card_token);
my $error = $self->replace;
return "Error: No error; test failure requested with fake_failure";
}
- #my $paybatch = '';
- #if ( $payment_gateway->gatewaynum ) { # agent override
- # $paybatch = $payment_gateway->gatewaynum. '-';
- #}
- #
- #$paybatch .= "$processor:". $transaction->authorization;
- #
- #$paybatch .= ':'. $transaction->order_number
- # if $transaction->can('order_number')
- # && length($transaction->order_number);
-
- my $paybatch = 'FakeProcessor:54:32';
-
my $cust_pay = new FS::cust_pay ( {
'custnum' => $self->custnum,
'invnum' => $options{'invnum'},
'payby' => $bop_method2payby{$options{method}},
#'payinfo' => $payinfo,
'payinfo' => '4111111111111111',
- 'paybatch' => $paybatch,
#'paydate' => $paydate,
'paydate' => '2012-05-01',
+ 'processor' => 'FakeProcessor',
+ 'auth' => '54',
+ 'order_number' => '32',
} );
$cust_pay->payunique( $options{payunique} ) if length($options{payunique});
if ( $transaction->is_success() ) {
- my $paybatch = '';
- if ( $payment_gateway->gatewaynum ) { # agent override
- $paybatch = $payment_gateway->gatewaynum. '-';
- }
-
- $paybatch .= $payment_gateway->gateway_module. ":".
- $transaction->authorization;
-
- $paybatch .= ':'. $transaction->order_number
- if $transaction->can('order_number')
- && length($transaction->order_number);
+ my $order_number = $transaction->order_number
+ if $transaction->can('order_number');
my $cust_pay = new FS::cust_pay ( {
'custnum' => $self->custnum,
'_date' => '',
'payby' => $cust_pay_pending->payby,
'payinfo' => $options{'payinfo'},
- 'paybatch' => $paybatch,
+ 'paymask' => $options{'paymask'} || $cust_pay_pending->paymask,
'paydate' => $cust_pay_pending->paydate,
'pkgnum' => $cust_pay_pending->pkgnum,
- 'discount_term' => $options{'discount_term'},
+ 'discount_term' => $options{'discount_term'},
+ 'gatewaynum' => ($payment_gateway->gatewaynum || ''),
+ 'processor' => $payment_gateway->gateway_module,
+ 'auth' => $transaction->authorization,
+ 'order_number' => $order_number || '',
+
} );
#doesn't hurt to know, even though the dup check is in cust_pay_pending now
$cust_pay->payunique( $options{payunique} )
};
my $error = send_email(
- 'from' => $conf->config('invoice_from', $self->agentnum ),
+ 'from' => $conf->invoice_from_full( $self->agentnum ),
'to' => [ grep { $_ ne 'POST' } $self->invoicing_list ],
'subject' => 'Your payment could not be processed',
'body' => [ $template->fill_in(HASH => $templ_hash) ],
'amount' => $cust_pay_pending->paid,
#'invoice_number' => $options{'invnum'},
'customer_id' => $self->custnum,
- 'referer' => 'http://cleanwhisker.420.am/',
+
+ #3.0 is a good a time as any to get rid of this... add a config to pass it
+ # if anyone still needs it
+ #'referer' => 'http://cleanwhisker.420.am/',
+
'reference' => $cust_pay_pending->paypendingnum,
'email' => $email,
'phone' => $self->daytime || $self->night,
Available methods are: I<CC>, I<ECHECK> and I<LEC>
-Available options are: I<amount>, I<reason>, I<paynum>, I<paydate>
+Available options are: I<amount>, I<reasonnum>, I<paynum>, I<paydate>
Most gateways require a reference to an original payment transaction to refund,
so you probably need to specify a I<paynum>.
I<amount> defaults to the original amount of the payment if not specified.
-I<reason> specifies a reason for the refund.
+I<reasonnum> specifies a reason for the refund.
I<paydate> specifies the expiration date for a credit card overriding the
value from the customer record or the payment record. Specified as yyyy-mm-dd
$options{method} = $method;
}
+ my ($reason, $reason_text);
+ if ( $options{'reasonnum'} ) {
+ # do this here, because we need the plain text reason string in case we
+ # void the payment
+ $reason = FS::reason->by_key($options{'reasonnum'});
+ $reason_text = $reason->reason;
+ } else {
+ # support old 'reason' string parameter in case it's still used,
+ # or else set a default
+ $reason_text = $options{'reason'} || 'card or ACH refund';
+ local $@;
+ $reason = FS::reason->new_or_existing(
+ reason => $reason_text,
+ type => 'Refund reason',
+ class => 'F',
+ );
+ if ($@) {
+ return "failed to add refund reason: $@";
+ }
+ }
+
if ( $DEBUG ) {
warn "$me realtime_refund_bop (new): $options{method} refund\n";
warn " $_ => $options{$_}\n" foreach keys %options;
}
+ my %content = ();
+
###
# look up the original payment and optionally a gateway for that payment
###
my( $processor, $login, $password, @bop_options, $namespace ) ;
my( $auth, $order_number ) = ( '', '', '' );
+ my $gatewaynum = '';
if ( $options{'paynum'} ) {
or return "Unknown paynum $options{'paynum'}";
$amount ||= $cust_pay->paid;
- $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
- or return "Can't parse paybatch for paynum $options{'paynum'}: ".
- $cust_pay->paybatch;
- my $gatewaynum = '';
- ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
+ my @cust_bill_pay = qsearch('cust_bill_pay', { paynum=>$cust_pay->paynum });
+ $content{'invoice_number'} = $cust_bill_pay[0]->invnum if @cust_bill_pay;
+
+ if ( $cust_pay->get('processor') ) {
+ ($gatewaynum, $processor, $auth, $order_number) =
+ (
+ $cust_pay->gatewaynum,
+ $cust_pay->processor,
+ $cust_pay->auth,
+ $cust_pay->order_number,
+ );
+ } else {
+ # this payment wasn't upgraded, which probably means this won't work,
+ # but try it anyway
+ $cust_pay->paybatch =~ /^((\d+)\-)?(\w+):\s*([\w\-\/ ]*)(:([\w\-]+))?$/
+ or return "Can't parse paybatch for paynum $options{'paynum'}: ".
+ $cust_pay->paybatch;
+ ( $gatewaynum, $processor, $auth, $order_number ) = ( $2, $3, $4, $6 );
+ }
if ( $gatewaynum ) { #gateway for the payment to be refunded
eval "use $namespace";
die $@ if $@;
- my %content = (
+ %content = (
+ %content,
'type' => $options{method},
'login' => $login,
'password' => $password,
'order_number' => $order_number,
'amount' => $amount,
- 'referer' => 'http://cleanwhisker.420.am/', #XXX fix referer :/
+
+ #3.0 is a good a time as any to get rid of this... add a config to pass it
+ # if anyone still needs it
+ #'referer' => 'http://cleanwhisker.420.am/',
);
$content{authorization} = $auth
if length($auth); #echeck/ACH transactions have an order # but no auth
#(at least with authorize.net)
+ my $currency = $conf->exists('business-onlinepayment-currency')
+ && $conf->config('business-onlinepayment-currency');
+ $content{currency} = $currency if $currency;
+
my $disable_void_after;
if ($conf->exists('disable_void_after')
&& $conf->config('disable_void_after') =~ /^(\d+)$/) {
if $conf->exists('business-onlinepayment-test_transaction');
$void->submit();
if ( $void->is_success ) {
- my $error = $cust_pay->void($options{'reason'});
+ my $error = $cust_pay->void($reason_text);
if ( $error ) {
# gah, even with transactions.
my $e = 'WARNING: Card/ACH voided but database not updated - '.
return "$processor error: ". $refund->error_message
unless $refund->is_success();
- my $paybatch = "$processor:". $refund->authorization;
- $paybatch .= ':'. $refund->order_number
- if $refund->can('order_number') && $refund->order_number;
+ $order_number = $refund->order_number if $refund->can('order_number');
+ # change this to just use $cust_pay->delete_cust_bill_pay?
while ( $cust_pay && $cust_pay->unapplied < $amount ) {
my @cust_bill_pay = $cust_pay->cust_bill_pay;
last unless @cust_bill_pay;
my $cust_refund = new FS::cust_refund ( {
'custnum' => $self->custnum,
'paynum' => $options{'paynum'},
+ 'source_paynum' => $options{'paynum'},
'refund' => $amount,
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
'payinfo' => $payinfo,
- 'paybatch' => $paybatch,
- 'reason' => $options{'reason'} || 'card or ACH refund',
+ 'reasonnum' => $reason->reasonnum,
+ 'gatewaynum' => $gatewaynum, # may be null
+ 'processor' => $processor,
+ 'auth' => $refund->authorization,
+ 'order_number' => $order_number,
} );
my $error = $cust_refund->insert;
if ( $error ) {
$cust_refund->paynum(''); #try again with no specific paynum
+ $cust_refund->source_paynum('');
my $error2 = $cust_refund->insert;
if ( $error2 ) {
# gah, even with transactions.