=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.
+Attempt to collect the customer's current balance with a realtime credit
+card, electronic check, or phone bill transaction (see realtime_bop() below).
-On failure returns an error message.
-
-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
}
}
+ if ( $options->{'fake_gatewaynum'} ) {
+ $options->{payment_gateway} =
+ qsearchs('payment_gateway',
+ { 'gatewaynum' => $options->{'fake_gatewaynum'}, }
+ );
+ }
+
$options->{payment_gateway} = $self->agent->payment_gateway( %$options )
unless exists($options->{payment_gateway});
}
$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} );
}
$options{method} = $method;
$options{amount} = $amount;
}
+
+
+ ###
+ # optional credit card surcharge
+ ###
+
+ 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');
+
+ # 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;
+ $options{'amount'} += $cc_surcharge;
+ $options{'amount'} = sprintf("%.2f", $options{'amount'}); # round (again)?
+ }
+ elsif($cc_surcharge_pct > 0) { # we're called not from event (i.e. from a
+ # payment screen), so consider the given
+ # amount as post-surcharge
+ $cc_surcharge = $options{'amount'} - ($options{'amount'} / ( 1 + $cc_surcharge_pct/100 ));
+ }
+ $cc_surcharge = sprintf("%.2f",$cc_surcharge) if $cc_surcharge > 0;
+ $options{'cc_surcharge'} = $cc_surcharge;
+
+
if ( $DEBUG ) {
warn "$me realtime_bop (new): $options{method} $options{amount}\n";
+ warn " cc_surcharge = $cc_surcharge\n";
warn " $_ => $options{$_}\n" foreach keys %options;
}
- return $self->fake_bop(%options) if $options{'fake'};
+ return $self->fake_bop(\%options) if $options{'fake'};
$self->_bop_defaults(\%options);
$content{bank_state} = exists($options{'paystate'})
? $options{'paystate'}
: $self->getfield('paystate');
- $content{account_type} = exists($options{'paytype'})
- ? uc($options{'paytype'}) || 'CHECKING'
- : uc($self->getfield('paytype')) || 'CHECKING';
+ $content{account_type}= (exists($options{'paytype'}) && $options{'paytype'})
+ ? uc($options{'paytype'})
+ : uc($self->getfield('paytype')) || 'PERSONAL CHECKING';
$content{account_name} = $self->getfield('first'). ' '.
$self->getfield('last');
#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
my $cust_pay_pending = new FS::cust_pay_pending {
'custnum' => $self->custnum,
- #'invnum' => $options{'invnum'},
'paid' => $options{amount},
'_date' => '',
'payby' => $bop_method2payby{$options{method}},
} );
$cust_pay->payunique( $options{payunique} ) if length($options{payunique});
+ if ( $DEBUG ) {
+ warn "fake_bop\n cust_pay: ". Dumper($cust_pay) . "\n options: ";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
if ( $error ) {
}
}
+ # have a CC surcharge portion --> one-time charge
+ if ( $options{'cc_surcharge'} > 0 ) {
+ # XXX: this whole block needs to be in a transaction?
+
+ my $invnum;
+ $invnum = $options{'invnum'} if $options{'invnum'};
+ unless ( $invnum ) { # probably from a payment screen
+ # do we have any open invoices? pick earliest
+ # uses the fact that cust_main->cust_bill sorts by date ascending
+ my @open = $self->open_cust_bill;
+ $invnum = $open[0]->invnum if scalar(@open);
+ }
+
+ unless ( $invnum ) { # still nothing? pick last closed invoice
+ # again uses fact that cust_main->cust_bill sorts by date ascending
+ my @closed = $self->cust_bill;
+ $invnum = $closed[$#closed]->invnum if scalar(@closed);
+ }
+
+ unless ( $invnum ) {
+ # XXX: unlikely case - pre-paying before any invoices generated
+ # what it should do is create a new invoice and pick it
+ warn 'CC SURCHARGE AND NO INVOICES PICKED TO APPLY IT!';
+ return '';
+ }
+
+ my $cust_pkg;
+ my $charge_error = $self->charge({
+ 'amount' => $options{'cc_surcharge'},
+ 'pkg' => 'Credit Card Surcharge',
+ 'setuptax' => 'Y',
+ 'cust_pkg_ref' => \$cust_pkg,
+ });
+ if($charge_error) {
+ warn 'Unable to add CC surcharge cust_pkg';
+ return '';
+ }
+
+ $cust_pkg->setup(time);
+ my $cp_error = $cust_pkg->replace;
+ if($cp_error) {
+ warn 'Unable to set setup time on cust_pkg for cc surcharge';
+ # but keep going...
+ }
+
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum });
+ unless ( $cust_bill ) {
+ warn "race condition + invoice deletion just happened";
+ return '';
+ }
+
+ my $grand_error =
+ $cust_bill->add_cc_surcharge($cust_pkg->pkgnum,$options{'cc_surcharge'});
+
+ warn "cannot add CC surcharge to invoice #$invnum: $grand_error"
+ if $grand_error;
+ }
+
return ''; #no error
}
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";