summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2013-07-02 11:57:03 -0700
committerIvan Kohler <ivan@freeside.biz>2013-07-02 11:57:03 -0700
commit3564f619654c5cbf22fc2acbe7eff0c08308e859 (patch)
treeab2f7e2426d40fbab94ff6f5e35873c1e99f383b /FS
parentfb4a7232c2d703e0a6b8743521b77ae292bb1df8 (diff)
parent23db52e232596d87b97561f5f76f11668cbe33bd (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm48
-rw-r--r--FS/FS/Conf.pm8
-rw-r--r--FS/FS/Schema.pm3
-rw-r--r--FS/FS/agent.pm30
-rw-r--r--FS/FS/cust_main.pm1
-rw-r--r--FS/FS/cust_main/Billing_ThirdParty.pm266
-rw-r--r--FS/FS/cust_main/Packages.pm4
-rw-r--r--FS/FS/cust_pay.pm9
-rw-r--r--FS/FS/cust_pay_pending.pm131
-rw-r--r--FS/FS/payment_gateway.pm47
-rwxr-xr-xFS/bin/freeside-eftca-download9
11 files changed, 536 insertions, 20 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 88266da8e..77a4683e5 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -379,6 +379,8 @@ sub access_info {
$conf->exists('ticket_system-selfservice_edit_subject') &&
$cust_main->edit_subject;
+ $info->{'timeout'} = $conf->config('selfservice-timeout') || 3600;
+
return { %$info,
'custnum' => $custnum,
'access_pkgnum' => $session->{'pkgnum'},
@@ -830,7 +832,7 @@ sub payment_info {
'save_unchecked' => $conf->exists('selfservice-save_unchecked'),
- 'credit_card_surcharge_percentage' => $conf->config('credit-card-surcharge-percentage'),
+ 'credit_card_surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage')),
};
}
@@ -1252,6 +1254,50 @@ sub realtime_collect {
return { 'error' => '', amount => $amount, %$error };
}
+sub start_thirdparty {
+ my $p = shift;
+ my $session = _cache->get($p->{'session_id'})
+ or return { 'error' => "Can't resume session" }; #better error message
+ my $custnum = $session->{'custnum'};
+ my $cust_main = FS::cust_main->by_key($custnum);
+
+ my $amount = $p->{'amount'}
+ or return { error => 'no amount' };
+
+ my $result = $cust_main->create_payment(
+ 'method' => $p->{'method'},
+ 'amount' => $p->{'amount'},
+ 'pkgnum' => $session->{'pkgnum'},
+ 'session_id' => $p->{'session_id'},
+ );
+
+ if ( ref($result) ) { # hashref or error
+ return $result;
+ } else {
+ return { error => $result };
+ }
+}
+
+sub finish_thirdparty {
+ my $p = shift;
+ my $session_id = delete $p->{'session_id'};
+ my $session = _cache->get($session_id)
+ or return { 'error' => "Can't resume session" };
+ my $custnum = $session->{'custnum'};
+ my $cust_main = FS::cust_main->by_key($custnum);
+
+ if ( $p->{_cancel} ) {
+ # customer backed out of making a payment
+ return $cust_main->cancel_payment( $session_id );
+ }
+ my $result = $cust_main->execute_payment( $session_id, %$p );
+ if ( ref($result) ) {
+ return $result;
+ } else {
+ return { error => $result };
+ }
+}
+
sub process_payment_order_pkg {
my $p = shift;
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index c4cc015e2..42fe9ec4c 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2272,6 +2272,12 @@ and customer address. Include units.',
},
{
+ 'key' => 'selfservice-timeout',
+ 'section' => 'self-service',
+ 'description' => 'Timeout for the self-service login cookie, in seconds. Defaults to 1 hour.',
+ },
+
+ {
'key' => 'backend-realtime',
'section' => 'billing',
'description' => 'Run billing for backend signups immediately.',
@@ -3686,7 +3692,7 @@ and customer address. Include units.',
{
'key' => 'batchconfig-eft_canada',
'section' => 'billing',
- 'description' => 'Configuration for EFT Canada batching, four lines: 1. SFTP username, 2. SFTP password, 3. Transaction code, 4. Number of days to delay process date.',
+ 'description' => 'Configuration for EFT Canada batching, four lines: 1. SFTP username, 2. SFTP password, 3. Transaction code, 4. Number of days to delay process date. If you are using separate per-agent batches (batch-spoolagent), you must set this option separately for each agent, as the global setting will be ignored.',
'type' => 'textarea',
'per_agent' => 1,
},
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index c50207708..6df45e2b1 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1576,6 +1576,9 @@ sub tables_hashref {
#'cust_balance', @money_type, '', '',
'paynum', 'int', 'NULL', '', '', '',
'jobnum', 'bigint', 'NULL', '', '', '',
+ 'invnum', 'int', 'NULL', '', '', '',
+ 'manual', 'char', 'NULL', 1, '', '',
+ 'discount_term','int', 'NULL', '', '', '',
],
'primary_key' => 'paypendingnum',
'unique' => [ [ 'payunique' ] ],
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
index 109343aa3..57093e329 100644
--- a/FS/FS/agent.pm
+++ b/FS/FS/agent.pm
@@ -230,7 +230,8 @@ sub ticketing_queue {
Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
-Currently available options are I<nofatal>, I<invnum>, I<method>, and I<payinfo>.
+Currently available options are I<nofatal>, I<invnum>, I<method>,
+I<payinfo>, and I<thirdparty>.
If I<nofatal> is set, and no gateway is available, then the empty string
will be returned instead of throwing a fatal exception.
@@ -245,10 +246,34 @@ as well. Presently only 'CC', 'ECHECK', and 'PAYPAL' methods are meaningful.
When the I<method> is 'CC' then the card number in I<payinfo> can direct
this routine to route to a gateway suited for that type of card.
+If I<thirdparty> is set, the defined self-service payment gateway will
+be returned.
+
=cut
sub payment_gateway {
my ( $self, %options ) = @_;
+
+ my $conf = new FS::Conf;
+
+ if ( $options{thirdparty} ) {
+ # still a kludge, but it gets the job done
+ # and the 'cardtype' semantics don't really apply to thirdparty
+ # gateways because we have to choose a gateway without ever
+ # seeing the card number
+ my $gatewaynum =
+ $conf->config('selfservice-payment_gateway', $self->agentnum);
+ my $gateway = FS::payment_gateway->by_key($gatewaynum)
+ if $gatewaynum;
+
+ if ( $gateway ) {
+ return $gateway;
+ } elsif ( $options{'nofatal'} ) {
+ return '';
+ } else {
+ die "no third-party gateway configured\n";
+ }
+ }
my $taxclass = '';
if ( $options{invnum} ) {
@@ -276,8 +301,6 @@ sub payment_gateway {
$cardtype = cardtype($options{payinfo});
} elsif ( $options{method} eq 'ECHECK' ) {
$cardtype = 'ACH';
- } elsif ( $options{method} eq 'PAYPAL' ) {
- $cardtype = 'PayPal';
} else {
$cardtype = $options{method}
}
@@ -298,7 +321,6 @@ sub payment_gateway {
taxclass => '', } );
my $payment_gateway;
- my $conf = new FS::Conf;
if ( $override ) { #use a payment gateway override
$payment_gateway = $override->payment_gateway;
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 940b22a04..7c7c9e2b5 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -6,6 +6,7 @@ use base qw( FS::cust_main::Packages FS::cust_main::Status
FS::cust_main::NationalID
FS::cust_main::Billing FS::cust_main::Billing_Realtime
FS::cust_main::Billing_Discount
+ FS::cust_main::Billing_ThirdParty
FS::cust_main::Location
FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin
FS::geocode_Mixin FS::Quotable_Mixin
diff --git a/FS/FS/cust_main/Billing_ThirdParty.pm b/FS/FS/cust_main/Billing_ThirdParty.pm
new file mode 100644
index 000000000..faced8f2b
--- /dev/null
+++ b/FS/FS/cust_main/Billing_ThirdParty.pm
@@ -0,0 +1,266 @@
+package FS::cust_main::Billing_ThirdParty;
+
+use strict;
+use vars qw( $DEBUG $me );
+use FS::Record qw( qsearch qsearchs dbh );
+use FS::cust_pay;
+use FS::cust_pay_pending;
+
+$DEBUG = 0;
+$me = '[FS::cust_main::Billing_ThirdParty]';
+# arguably doesn't even belong under cust_main...
+
+=head1 METHODS
+
+=over 4
+
+=item create_payment OPTIONS
+
+Create a pending payment for a third-party gateway. OPTIONS must include:
+- method: a Business::OnlineThirdPartyPayment method argument. Currently
+ only supports PAYPAL.
+- amount: a decimal amount. Unlike in Billing_Realtime, there is NO default.
+- session_id: the customer's self-service session ID.
+
+and may optionally include:
+- invnum: the invoice that this payment will apply to
+- pkgnum: the package balance that this payment will apply to.
+- description: the transaction description for the gateway.
+- payip: the IP address the payment is initiated from
+
+On failure, returns a simple string error message. On success, returns
+a hashref of 'url' => the URL to redirect the user to to complete payment,
+and optionally 'post_params' => a hashref of name/value pairs to be POSTed
+to that URL.
+
+=cut
+
+my @methods = qw(PAYPAL CC);
+my %method2payby = ( 'PAYPAL' => 'PPAL', 'CC' => 'MCRD' );
+
+sub create_payment {
+ my $self = shift;
+ my %opt = @_;
+
+ # avoid duplicating this--we just need description and invnum
+ my $defaults;
+ $self->_bop_defaults($defaults);
+
+ my $method = $opt{'method'} or return 'method required';
+ my $amount = $opt{'amount'} or return 'amount required';
+ return "unknown method '$method'" unless grep {$_ eq $method} @methods;
+ return "amount must be > 0" unless $amount > 0;
+ return "session_id required" unless length($opt{'session_id'});
+
+ my $gateway = $self->agent->payment_gateway(
+ method => $method,
+ nofatal => 1,
+ thirdparty => 1,
+ );
+ return "no third-party gateway enabled for method $method" if !$gateway;
+
+ # create pending record
+ $self->select_for_update;
+ my @pending = qsearch('cust_pay_pending', {
+ 'custnum' => $self->custnum,
+ 'status' => { op=>'!=', value=>'done' }
+ });
+
+ # if there are pending payments in the 'thirdparty' state,
+ # we can safely remove them
+ foreach (@pending) {
+ if ( $_->status eq 'thirdparty' ) {
+ my $error = $_->delete;
+ return "Error deleting unfinished payment #".
+ $_->paypendingnum . ": $error\n" if $error;
+ } else {
+ return "A payment is already being processed for this customer.";
+ }
+ }
+
+ my $cpp = FS::cust_pay_pending->new({
+ 'custnum' => $self->custnum,
+ 'status' => 'new',
+ 'gatewaynum' => $gateway->gatewaynum,
+ 'paid' => sprintf('%.2f',$opt{'amount'}),
+ 'payby' => $method2payby{ $opt{'method'} },
+ 'pkgnum' => $opt{'pkgnum'},
+ 'invnum' => $opt{'invnum'} || $defaults->{'invnum'},
+ 'session_id' => $opt{'session_id'},
+ });
+
+ my $error = $cpp->insert;
+ return $error if $error;
+
+ my $transaction = $gateway->processor;
+ # Not included in this content hash:
+ # payinfo, paydate, paycvv, any kind of recurring billing indicator,
+ # paystate, paytype (account type), stateid, ss, payname
+ #
+ # Also, unlike bop_realtime, we don't allow the magical %options hash
+ # to override the customer's information. If they need to enter a
+ # different address or something for the billing provider, they can do
+ # that after the redirect.
+ my %content = (
+ 'action' => 'create',
+ 'description' => $opt{'description'} || $defaults->{'description'},
+ 'amount' => $amount,
+ 'customer_id' => $self->custnum,
+ 'email' => $self->invoicing_list_emailonly_scalar,
+ 'customer_ip' => $opt{'payip'},
+ 'first_name' => $self->first,
+ 'last_name' => $self->last,
+ 'address1' => $self->address1,
+ 'address2' => $self->address2,
+ 'city' => $self->city,
+ 'state' => $self->state,
+ 'zip' => $self->zip,
+ 'country' => $self->country,
+ 'phone' => ($self->daytime || $self->night),
+ );
+
+ {
+ local $@;
+ eval { $transaction->create(%content) };
+ if ( $@ ) {
+ warn "ERROR: Executing third-party payment:\n$@\n";
+ return { error => $@ };
+ }
+ }
+
+ if ($transaction->is_success) {
+ $cpp->status('thirdparty');
+ # for whatever is most identifiable as the "transaction ID"
+ $cpp->payinfo($transaction->token);
+ # for anything else the transaction needs to remember
+ $cpp->statustext($transaction->statustext);
+ $error = $cpp->replace;
+ return $error if $error;
+
+ return {url => $transaction->redirect,
+ post_params => $transaction->post_params};
+
+ } else {
+ $cpp->status('done');
+ $cpp->statustext($transaction->error_message);
+ $error = $cpp->replace;
+ return $error if $error;
+
+ return $transaction->error_message;
+ }
+
+}
+
+=item execute_payment SESSION_ID, PARAMS
+
+Complete the payment and get the status. Triggered from the return_url
+handler; PARAMS are all of the CGI parameters we received in the redirect.
+On failure, returns an error message. On success, returns a hashref of
+'paynum', 'paid', 'order_number', and 'auth'.
+
+=cut
+
+sub execute_payment {
+ my $self = shift;
+ my $session_id = shift;
+ my %params = @_;
+
+ my $cpp = qsearchs('cust_pay_pending', {
+ 'session_id' => uc($session_id),
+ 'custnum' => $self->custnum,
+ 'status' => 'thirdparty',
+ })
+ or return 'no payment in process for this session';
+
+ my $gateway = FS::payment_gateway->by_key( $cpp->gatewaynum );
+ my $transaction = $gateway->processor;
+ $transaction->token($cpp->payinfo);
+ $transaction->statustext($cpp->statustext);
+
+ {
+ local $@;
+ eval { $transaction->execute(%params) };
+ if ( $@ ) {
+ warn "ERROR: Executing third-party payment:\n$@\n";
+ return { error => $@ };
+ }
+ }
+
+ my $error;
+
+ if ( $transaction->is_success ) {
+
+ $error = $cpp->approve(
+ 'processor' => $gateway->gateway_module,
+ 'order_number' => $transaction->order_number,
+ 'auth' => $transaction->authorization,
+ 'payinfo' => '',
+ 'apply' => 1,
+ );
+ return $error if $error;
+
+ return {
+ 'paynum' => $cpp->paynum,
+ 'paid' => $cpp->paid,
+ 'order_number' => $transaction->order_number,
+ 'auth' => $transaction->authorization,
+ }
+
+ } else {
+
+ my $error = $gateway->gateway_module. " error: ".
+ $transaction->error_message;
+
+ my $jobnum = $cpp->jobnum;
+ if ( $jobnum ) {
+ my $placeholder = FS::queue->by_key($jobnum);
+
+ if ( $placeholder ) {
+ my $e = $placeholder->depended_delete || $placeholder->delete;
+ warn "error removing provisioning jobs after declined paypendingnum ".
+ $cpp->paypendingnum. ": $e\n\n"
+ if $e;
+ } else {
+ warn "error finding job $jobnum for declined paypendingnum ".
+ $cpp->paypendingnum. "\n\n";
+ }
+ }
+
+ # not needed here:
+ # the raw HTTP response thing when there's no error message
+ # decline notices (the customer has already seen the decline message)
+
+ # set the pending status
+ my $e = $cpp->decline($error);
+ if ( $e ) {
+ $e = "WARNING: payment declined but pending payment not resolved - ".
+ "error updating status for pendingnum :".$cpp->paypendingnum.
+ ": $e\n\n";
+ warn $e;
+ $error = "$e ($error)";
+ }
+
+ return $error;
+ }
+
+}
+
+=item cancel_payment SESSION_ID
+
+Cancel a pending payment attempt. This just cleans up the cust_pay_pending
+record.
+
+=cut
+
+sub cancel_payment {
+ my $self = shift;
+ my $session_id = shift;
+ my $cust_pay_pending = qsearchs('cust_pay_pending', {
+ 'session_id' => uc($session_id),
+ 'status' => 'thirdparty',
+ });
+ return { 'error' => $cust_pay_pending->delete };
+}
+
+1;
+
diff --git a/FS/FS/cust_main/Packages.pm b/FS/FS/cust_main/Packages.pm
index 8484df50e..152c496d1 100644
--- a/FS/FS/cust_main/Packages.pm
+++ b/FS/FS/cust_main/Packages.pm
@@ -183,7 +183,6 @@ sub order_pkg {
'pkglinknum' => $link->pkglinknum,
'custnum' => $self->custnum,
'main_pkgnum' => $cust_pkg->pkgnum,
- 'locationnum' => $cust_pkg->locationnum,
# try to prevent as many surprises as possible
'pkgbatch' => $cust_pkg->pkgbatch,
'start_date' => $cust_pkg->start_date,
@@ -196,7 +195,8 @@ sub order_pkg {
'waive_setup' => $cust_pkg->waive_setup,
'allow_pkgpart' => $opt->{'allow_pkgpart'},
});
- $error = $self->order_pkg('cust_pkg' => $pkg);
+ $error = $self->order_pkg('cust_pkg' => $pkg,
+ 'locationnum' => $cust_pkg->locationnum);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "inserting supplemental package: $error";
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index f6954a4bc..69f4c395a 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -190,6 +190,15 @@ A hash of optional arguments may be passed. Currently "manual" is supported.
If true, a payment receipt is sent instead of a statement when
'payment_receipt_email' configuration option is set.
+About the "manual" flag: Normally, if the 'payment_receipt' config option
+is set, and the customer has an invoice email address, inserting a payment
+causes a I<statement> to be emailed to the customer. If the payment is
+considered "manual" (or if the customer has no invoices), then it will
+instead send a I<payment receipt>. "manual" should be true whenever a
+payment is created directly from the web interface, from a user-initiated
+realtime payment, or from a third-party payment via self-service. It should
+be I<false> when creating a payment from a billing event or from a batch.
+
=cut
sub insert {
diff --git a/FS/FS/cust_pay_pending.pm b/FS/FS/cust_pay_pending.pm
index f03ed1f3a..8e29f08b6 100644
--- a/FS/FS/cust_pay_pending.pm
+++ b/FS/FS/cust_pay_pending.pm
@@ -128,8 +128,24 @@ Additional status information.
L<FS::payment_gateway> id.
-=item paynum -
+=item paynum
+Payment number (L<FS::cust_pay>) of the completed payment.
+
+=item invnum
+
+Invoice number (L<FS::cust_bill>) to try to apply this payment to.
+
+=item manual
+
+Flag for whether this is a "manual" payment (i.e. initiated through
+self-service or the back-office web interface, rather than from an event
+or a payment batch). "Manual" payments will cause the customer to be
+sent a payment receipt rather than a statement.
+
+=item discount_term
+
+Number of months the customer tried to prepay for.
=back
@@ -203,6 +219,9 @@ sub check {
|| $self->ut_hexn('session_id')
|| $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
|| $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum')
+ || $self->ut_foreign_keyn('invnum', 'cust_bill', 'invnum')
+ || $self->ut_flag('manual')
+ || $self->ut_numbern('discount_term')
|| $self->payinfo_check() #payby/payinfo/paymask/paydate
;
return $error if $error;
@@ -296,6 +315,116 @@ sub insert_cust_pay {
}
+=item approve OPTIONS
+
+Sets the status of this pending payment to "done" and creates a completed
+payment (L<FS::cust_pay>). This should be called when a realtime or
+third-party payment has been approved.
+
+OPTIONS may include any of 'processor', 'payinfo', 'discount_term', 'auth',
+and 'order_number' to set those fields on the completed payment, as well as
+'apply' to apply payments for this customer after inserting the new payment.
+
+=cut
+
+sub approve {
+ my $self = shift;
+ my %opt = @_;
+
+ my $dbh = dbh;
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ my $cust_pay = FS::cust_pay->new({
+ 'custnum' => $self->custnum,
+ 'invnum' => $self->invnum,
+ 'pkgnum' => $self->pkgnum,
+ 'paid' => $self->paid,
+ '_date' => '',
+ 'payby' => $self->payby,
+ 'payinfo' => $self->payinfo,
+ 'gatewaynum' => $self->gatewaynum,
+ });
+ foreach my $opt_field (qw(processor payinfo auth order_number))
+ {
+ $cust_pay->set($opt_field, $opt{$opt_field}) if exists $opt{$opt_field};
+ }
+
+ my %insert_opt = (
+ 'manual' => $self->manual,
+ 'discount_term' => $self->discount_term,
+ );
+ my $error = $cust_pay->insert( %insert_opt );
+ if ( $error ) {
+ # try it again without invnum or discount
+ # (both of those can make payments fail to insert, and at this point
+ # the payment is a done deal and MUST be recorded)
+ $self->invnum('');
+ my $error2 = $cust_pay->insert('manual' => $self->manual);
+ if ( $error2 ) {
+ # attempt to void the payment?
+ # no, we'll just stop digging at this point.
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: payment captured but not recorded - error inserting ".
+ "payment (". ($opt{processor} || $self->payby) .
+ ": $error2\n(previously tried insert with invnum#".$self->invnum.
+ ": $error)\npending payment saved as paypendingnum#".
+ $self->paypendingnum."\n\n";
+ warn $e;
+ return $e;
+ }
+ }
+ if ( my $jobnum = $self->jobnum ) {
+ my $placeholder = FS::queue->by_key($jobnum);
+ my $error;
+ if (!$placeholder) {
+ $error = "not found";
+ } else {
+ $error = $placeholder->delete;
+ }
+
+ if ($error) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: payment captured but could not delete job $jobnum ".
+ "for paypendingnum #" . $self->paypendingnum . ": $error\n\n";
+ warn $e;
+ return $e;
+ }
+ }
+
+ if ( $opt{'paynum_ref'} ) {
+ ${ $opt{'paynum_ref'} } = $cust_pay->paynum;
+ }
+
+ $self->status('done');
+ $self->statustext('captured');
+ $self->paynum($cust_pay->paynum);
+ my $cpp_done_err = $self->replace;
+
+ if ( $cpp_done_err ) {
+
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: payment captured but could not update pending status ".
+ "for paypendingnum ".$self->paypendingnum.": $cpp_done_err \n\n";
+ warn $e;
+ return $e;
+
+ } else {
+
+ # commit at this stage--we don't want to roll back if applying
+ # payments fails
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ if ( $opt{'apply'} ) {
+ my $apply_error = $self->apply_payments_and_credits;
+ if ( $apply_error ) {
+ warn "WARNING: error applying payment: $apply_error\n\n";
+ }
+ }
+ }
+ '';
+}
+
=item decline [ STATUSTEXT ]
Sets the status of this pending payment to "done" (with statustext
diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm
index e94a62cf4..68d841855 100644
--- a/FS/FS/payment_gateway.pm
+++ b/FS/FS/payment_gateway.pm
@@ -53,11 +53,11 @@ currently supported:
=item gateway_callback_url - For ThirdPartyPayment only, set to the URL that
the user should be redirected to on a successful payment. This will be sent
-as a transaction parameter (named "callback_url").
+as a transaction parameter named "return_url".
=item gateway_cancel_url - For ThirdPartyPayment only, set to the URL that
-the user should be redirected to if they cancel the transaction. PayPal
-requires this; other gateways ignore it.
+the user should be redirected to if they cancel the transaction. This will
+be sent as a transaction parameter named "cancel_url".
=item auto_resolve_status - For BatchPayment only, set to 'approve' to
auto-approve unresolved payments after some number of days, 'reject' to
@@ -277,10 +277,6 @@ sub batch_processor {
eval "use Business::BatchPayment;";
die "couldn't load Business::BatchPayment: $@" if $@;
- my $conf = new FS::Conf;
- my $test_mode = $conf->exists('business-batchpayment-test_transaction');
- $opt{'test_mode'} = 1 if $test_mode;
-
my $module = $self->gateway_module;
my $processor = eval {
Business::BatchPayment->create($module, $self->options, %opt)
@@ -289,11 +285,46 @@ sub batch_processor {
if $@;
die "$module does not support test mode"
- if $test_mode and not $processor->does('Business::BatchPayment::TestMode');
+ if $opt{'test_mode'}
+ and not $processor->does('Business::BatchPayment::TestMode');
return $processor;
}
+=item processor OPTIONS
+
+Loads the module for the processor and returns an instance of it.
+
+=cut
+
+sub processor {
+ local $@;
+ my $self = shift;
+ my %opt = @_;
+ foreach (qw(action username password)) {
+ if (length($self->get("gateway_$_"))) {
+ $opt{$_} = $self->get("gateway_$_");
+ }
+ }
+ $opt{'return_url'} = $self->gateway_callback_url;
+ $opt{'cancel_url'} = $self->gateway_cancel_url;
+
+ my $conf = new FS::Conf;
+ my $test_mode = $conf->exists('business-batchpayment-test_transaction');
+ $opt{'test_mode'} = 1 if $test_mode;
+
+ my $namespace = $self->gateway_namespace;
+ eval "use $namespace";
+ die "couldn't load $namespace: $@" if $@;
+
+ if ( $namespace eq 'Business::BatchPayment' ) {
+ # at some point we can merge these, but there's enough special behavior...
+ return $self->batch_processor(%opt);
+ } else {
+ return $namespace->new( $self->gateway_module, $self->options, %opt );
+ }
+}
+
# _upgrade_data
#
# Used by FS::Upgrade to migrate to a new database.
diff --git a/FS/bin/freeside-eftca-download b/FS/bin/freeside-eftca-download
index d54a724ab..1b7653cb3 100755
--- a/FS/bin/freeside-eftca-download
+++ b/FS/bin/freeside-eftca-download
@@ -52,7 +52,7 @@ my $conf = new FS::Conf;
my @agents;
if ( $conf->exists('batch-spoolagent') ) {
- @agents = qsearchs('agent', { 'disabled' => '' });
+ @agents = qsearch('agent', { 'disabled' => '' });
} else {
@agents = (1);
}
@@ -62,11 +62,14 @@ foreach my $agent (@agents) {
my @batchconf;
if ( $conf->exists('batch-spoolagent') ) {
@batchconf = $conf->config('batchconfig-eft_canada', $agent->agentnum, 1);
- next unless $batchconf[0];
+ if ( !length($batchconf[0]) ) {
+ warn "agent '".$agent->agent."' has no batchconfig-eft_canada setting; skipped.\n";
+ next;
+ }
} else {
@batchconf = $conf->config('batchconfig-eft_canada');
}
- # BIN, terminalID, merchantID, username, password
+ # user, password, transaction code, delay days
my $user = $batchconf[0] or die "no EFT Canada batch username configured\n";
my $pass = $batchconf[1] or die "no EFT Canada batch password configured\n";