'paytypes' => [ @FS::cust_main::paytypes ],
'paybys' => [ $conf->config('signup_server-payby') ],
+ 'cust_paybys' => [ map { FS::payby->payby2payment($_) }
+ $conf->config('signup_server-payby')
+ ],
'stateid_label' => FS::Msgcat::_gettext('stateid'),
'stateid_state_label' => FS::Msgcat::_gettext('stateid_state'),
my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
or return { 'error' => "unknown custnum $custnum" };
+ $return{hide_payment_fields} =
+ [
+ map { FS::payby->realtime($_) &&
+ $cust_main
+ ->agent
+ ->payment_gateway( 'method' => FS::payby->payby2bop($_) )
+ ->gateway_namespace
+ eq 'Business::OnlineThirdPartyPayment'
+ }
+ @{ $return{cust_paybys} }
+ ];
+
$return{balance} = $cust_main->balance;
$return{payname} = $cust_main->payname
}
+sub realtime_collect {
+
+ 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 = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ my $error = $cust_main->realtime_collect( 'method' => $p->{'method'},
+ 'session_id' => $p->{'session_id'},
+ );
+ return { 'error' => $error } unless ref( $error );
+
+ return { 'error' => '', amount => $cust_main->balance, %$error };
+}
+
sub process_payment_order_pkg {
my $p = shift;
use Tie::RefHash;
use FS::Conf;
use FS::Record qw(qsearch qsearchs dbdef);
+use FS::CGI qw(popurl);
use FS::Msgcat qw(gettext);
use FS::Misc qw(card_types);
use FS::ClientAPI_SessionCache;
use FS::acct_snarf;
use FS::queue;
use FS::reg_code;
+use FS::payby;
$DEBUG = 0;
$me = '[FS::ClientAPI::Signup]';
if ( $agentnum ) {
+ warn "$me setting agent-specific payment flag\n" if $DEBUG > 1;
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum } );
+ warn "$me has agent $agent\n" if $DEBUG > 1;
+ if ( $agent ) { #else complain loudly?
+ $signup_info->{'hide_payment_fields'} = [];
+ foreach my $payby (@{$signup_info->{payby}}) {
+ warn "$me checking $payby payment fields\n" if $DEBUG > 1;
+ my $hide = 0;
+ if (FS::payby->realtime($payby)) {
+ my $payment_gateway =
+ $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby) );
+ if ($payment_gateway->gateway_namespace eq
+ 'Business::OnlineThirdPartyPayment'
+ ) {
+ warn "$me hiding $payby payment fields\n" if $DEBUG > 1;
+ $hide = 1;
+ }
+ }
+ push @{$signup_info->{'hide_payment_fields'}}, $hide;
+ }
+ }
+ warn "$me done setting agent-specific payment flag\n" if $DEBUG > 1;
+
warn "$me setting agent-specific package list\n" if $DEBUG > 1;
$signup_info->{'part_pkg'} = $signup_info->{'agentnum2part_pkg'}{$agentnum}
unless @{ $signup_info->{'part_pkg'} };
unless grep { $_ eq $packet->{'payby'} }
$conf->config('signup_server-payby');
+ if (FS::payby->realtime($packet->{payby})) {
+ my $payby = $packet->{payby};
+
+ my $agent = qsearchs('agent', { 'agentnum' => $agentnum });
+ return { 'error' => "Unknown reseller" }
+ unless $agent;
+
+ my $payment_gateway =
+ $agent->payment_gateway( 'method' => FS::payby->payby2bop($payby) );
+
+ if ($payment_gateway->gateway_namespace eq
+ 'Business::OnlineThirdPartyPayment'
+ ) {
+ $cust_main->payby('BILL'); # MCRD better?
+ }
+ }
+
$cust_main->payinfo($cust_main->daytime)
if $cust_main->payby eq 'LECB' && ! $cust_main->payinfo;
# " new customer: $bill_error"
# if $bill_error;
- $bill_error = $cust_main->collect('realtime' => 1);
+ $bill_error = $cust_main->realtime_collect(
+ method => FS::payby->payby2bop( $packet->{payby} ),
+ depend_jobnum => $placeholder->jobnum,
+ );
#warn "[fs_signup_server] error collecting from new customer: $bill_error"
# if $bill_error;
+ if ($bill_error && ref($bill_error) eq 'HASH') {
+ return { 'error' => '_collect',
+ ( map { $_ => $bill_error->{$_} }
+ qw(popup_url reference collectitems)
+ ),
+ amount => $cust_main->balance,
+ };
+ }
+
if ( $cust_main->balance > 0 ) {
#this makes sense. credit is "un-doing" the invoice
}
+sub capture_payment {
+ my $packet = shift;
+
+ warn "$me capture_payment called on $packet\n" if $DEBUG;
+
+ ###
+ # identify processor/gateway from called back URL
+ ###
+
+ my $conf = new FS::Conf;
+
+ my $url = $packet->{url};
+ my $payment_gateway =
+ qsearchs('payment_gateway', { 'gateway_callback_url' => popurl(0, $url) } );
+
+ unless ($payment_gateway) {
+
+ my ( $processor, $login, $password, $action, @bop_options ) =
+ $conf->config('business-onlinepayment');
+ $action ||= 'normal authorization';
+ pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
+
+ $payment_gateway = new FS::payment_gateway( {
+ gateway_namespace => $conf->config('business-onlinepayment-namespace'),
+ gateway_module => $processor,
+ gateway_username => $login,
+ gateway_password => $password,
+ gateway_action => $action,
+ options => [ ( @bop_options ) ],
+ });
+
+ }
+
+ die "No real-time third party processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n*"
+ unless $payment_gateway->gateway_namespace eq 'Business::OnlineThirdPartyPayment';
+
+ ###
+ # locate pending transaction
+ ###
+
+ eval "use Business::OnlineThirdPartyPayment";
+ die $@ if $@;
+
+ my $transaction =
+ new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
+ @{ [ $payment_gateway->options ] },
+ );
+
+ my $paypendingnum = $transaction->reference($packet->{data});
+
+ my $cust_pay_pending =
+ qsearchs('cust_pay_pending', { paypendingnum => $paypendingnum } );
+
+ unless ($cust_pay_pending) {
+ my $bill_error = "No payment is being processed with id $paypendingnum".
+ "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ if ($cust_pay_pending->status ne 'pending') {
+ my $bill_error = "Payment with id $paypendingnum is not pending, but ".
+ $cust_pay_pending->status. "; Transaction aborted.";
+ return { error => '_decline', bill_error => $bill_error };
+ }
+
+ my $cust_main = $cust_pay_pending->cust_main;
+ my $bill_error =
+ $cust_main->realtime_botpp_capture( $cust_pay_pending, %{$packet->{data}} );
+
+ return { 'error' => ( $bill_error->{bill_error} ? '_decline' : '' ),
+ %$bill_error,
+ };
+
+}
+
1;
use FS::ConfItem;
use FS::ConfDefaults;
use FS::Conf_compat17;
+use FS::payby;
use FS::conf;
use FS::Record qw(qsearch qsearchs);
use FS::UID qw(dbh datasrc use_confcompat);
},
{
+ 'key' => 'business-onlinepayment-namespace',
+ 'section' => 'billing',
+ 'description' => 'Specifies which perl module namespace (which group of collection routines) is used by default.',
+ 'type' => 'select',
+ 'select_hash' => [
+ 'Business::OnlinePayment' => 'Direct API (Business::OnlinePayment)',
+ 'Business::OnlineThirdPartyPayment' => 'Web API (Business::ThirdPartyPayment)',
+ ],
+ },
+
+ {
'key' => 'business-onlinepayment-description',
'section' => 'billing',
'description' => 'String passed as the description field to <a href="http://search.cpan.org/search?mode=module&query=Business%3A%3AOnlinePayment">Business::OnlinePayment</a>. Evaluated as a double-quoted perl string, with the following variables available: <code>$agent</code> (the agent name), and <code>$pkgs</code> (a comma-separated list of packages for which these charges apply)',
'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage
'status', 'varchar', '', $char_d, '', '',
+ 'session_id', 'varchar', 'NULL', $char_d, '', '', #only need 32
'statustext', 'text', 'NULL', '', '', '',
'gatewaynum', 'int', 'NULL', '', '', '',
#'cust_balance', @money_type, '', '',
'paynum', 'int', 'NULL', '', '', '',
+ 'jobnum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'paypendingnum',
'unique' => [ [ 'payunique' ] ],
'payment_gateway' => {
'columns' => [
'gatewaynum', 'serial', '', '', '', '',
+ 'gateway_namespace','varchar', 'NULL', $char_d, '', '',
'gateway_module', 'varchar', '', $char_d, '', '',
'gateway_username', 'varchar', 'NULL', $char_d, '', '',
'gateway_password', 'varchar', 'NULL', $char_d, '', '',
'gateway_action', 'varchar', 'NULL', $char_d, '', '',
+ 'gateway_callback_url', 'varchar', 'NULL', $char_d, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
'primary_key' => 'gatewaynum',
use strict;
use vars qw( @ISA );
#use Crypt::YAPassGen;
+use Business::CreditCard 0.28;
use FS::Record qw( dbh qsearch qsearchs );
use FS::cust_main;
use FS::cust_pkg;
use FS::agent_type;
use FS::reg_code;
use FS::TicketSystem;
+use FS::Conf;
@ISA = qw( FS::m2m_Common FS::Record );
FS::TicketSystem->queue($self->ticketing_queueid);
};
+=item payment_gateway [ OPTION => VALUE, ... ]
+
+Returns a payment gateway object (see L<FS::payment_gateway>) for this agent.
+
+Currently available options are I<invnum>, I<method>, and I<payinfo>.
+
+If I<invnum> is set to the number of an invoice (see L<FS::cust_bill>) then
+an attempt will be made to select a gateway suited for the taxes paid on
+the invoice.
+
+The I<method> and I<payinfo> options can be used to influence the choice
+as well. Presently only 'CC' and 'ECHECK' 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.
+
+=cut
+
+sub payment_gateway {
+ my ( $self, %options ) = @_;
+
+ my $taxclass = '';
+ if ( $options{invnum} ) {
+ my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{invnum} } );
+ die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
+ my @taxclasses =
+ map { $_->part_pkg->taxclass }
+ grep { $_ }
+ map { $_->cust_pkg }
+ $cust_bill->cust_bill_pkg;
+ unless ( grep { $taxclasses[0] ne $_ } @taxclasses ) { #unless there are
+ #different taxclasses $taxclass = $taxclasses[0];
+ }
+ }
+
+ #look for an agent gateway override first
+ my $cardtype;
+ if ( $options{method} && $options{method} eq 'CC' ) {
+ $cardtype = cardtype($options{payinfo});
+ } elsif ( $options{method} && $options{method} eq 'ECHECK' ) {
+ $cardtype = 'ACH';
+ } else {
+ $cardtype = $options{method} || '';
+ }
+
+ my $override =
+ qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => $cardtype,
+ taxclass => $taxclass, } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => '',
+ taxclass => $taxclass, } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => $cardtype,
+ taxclass => '', } )
+ || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
+ cardtype => '',
+ taxclass => '', } );
+
+ my $payment_gateway = new FS::payment_gateway;
+ if ( $override ) { #use a payment gateway override
+
+ $payment_gateway = $override->payment_gateway;
+
+ } else { #use the standard settings from the config
+ # the standard settings from the config could be moved to a null agent
+ # agent_payment_gateway referenced payment_gateway
+
+ my $conf = new FS::Conf;
+ die "Real-time processing not enabled\n"
+ unless $conf->exists('business-onlinepayment');
+
+ #load up config
+ my $bop_config = 'business-onlinepayment';
+ $bop_config .= '-ach'
+ if ( $options{method}
+ && $options{method} =~ /^(ECHECK|CHEK)$/
+ && $conf->exists($bop_config. '-ach')
+ );
+ my ( $processor, $login, $password, $action, @bop_options ) =
+ $conf->config($bop_config);
+ $action ||= 'normal authorization';
+ pop @bop_options if scalar(@bop_options) % 2 && $bop_options[-1] =~ /^\s*$/;
+ die "No real-time processor is enabled - ".
+ "did you set the business-onlinepayment configuration value?\n"
+ unless $processor;
+
+ $payment_gateway->gateway_namespace( $conf->config('business-onlinepayment-namespace') ||
+ 'Business::OnlinePayment');
+ $payment_gateway->gateway_module($processor);
+ $payment_gateway->gateway_username($login);
+ $payment_gateway->gateway_password($password);
+ $payment_gateway->gateway_action($action);
+ $payment_gateway->set('options', [ @bop_options ]);
+
+ }
+
+ $payment_gateway;
+}
+
=item num_prospect_cust_main
Returns the number of prospects (customers with no packages ever ordered) for
}
-=item realtime_bop METHOD AMOUNT [ OPTION => VALUE ... ]
+=item realtime_collect [ OPTION => VALUE ... ]
Runs a realtime credit card, ACH (electronic check) or phone bill transaction
-via a Business::OnlinePayment realtime gateway. See
-L<http://420.am/business-onlinepayment> for supported gateways.
+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.
-Available methods are: I<CC>, I<ECHECK> and I<LEC>
+On failure returns an error message.
-Available options are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>
+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.
+
+Available options are: I<method>, I<amount>, I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
+
+I<method> is one of: I<CC>, I<ECHECK> and I<LEC>. If none is specified
+then it is deduced from the customer record.
+
+If no I<amount> is specified, then the customer balance is used.
The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
I<payunique> is a unique identifier for this payment.
-(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
+I<session_id> is a session identifier associated with this payment.
+
+I<depend_jobnum> allows payment capture to unlock export jobs
=cut
-sub realtime_bop {
- my( $self, $method, $amount, %options ) = @_;
+sub realtime_collect {
+ my( $self, %options ) = @_;
+
if ( $DEBUG ) {
- warn "$me realtime_bop: $method $amount\n";
+ warn "$me realtime_collect:\n";
warn " $_ => $options{$_}\n" foreach keys %options;
}
- $options{'description'} ||= 'Internet services';
+ $options{amount} = $self->balance unless exists( $options{amount} );
+ $options{method} = FS::payby->payby2bop($self->payby)
+ unless exists( $options{method} );
- return $self->fake_bop($method, $amount, %options) if $options{'fake'};
+ return $self->realtime_bop({%options});
- eval "use Business::OnlinePayment";
- die $@ if $@;
+}
+
+=item realtime_bop { [ ARG => VALUE ... ] }
+
+Runs a realtime credit card, ACH (electronic check) or phone bill transaction
+via a Business::OnlinePayment realtime gateway. See
+L<http://420.am/business-onlinepayment> for supported gateways.
+
+Required arguments in the hashref are I<method>, and I<amount>
+
+Available methods are: I<CC>, I<ECHECK> and I<LEC>
+
+Available optional arguments are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>, I<session_id>
- my $payinfo = exists($options{'payinfo'})
- ? $options{'payinfo'}
- : $self->payinfo;
+The additional options I<payname>, I<address1>, I<address2>, I<city>, I<state>,
+I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
+if set, will override the value from the customer record.
+
+I<description> is a free-text field passed to the gateway. It defaults to
+"Internet services".
+
+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.
+
+I<quiet> can be set true to surpress email decline notices.
+
+I<paynum_ref> can be set to a scalar reference. It will be filled in with the
+resulting paynum, if any.
+
+I<payunique> is a unique identifier for this payment.
+
+I<session_id> is a session identifier associated with this payment.
+
+I<depend_jobnum> allows payment capture to unlock export jobs
+
+(moved from cust_bill) (probably should get realtime_{card,ach,lec} here too)
+
+=cut
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
+# some helper routines
+sub _payment_gateway {
+ my ($self, $options) = @_;
+
+ $options->{payment_gateway} = $self->agent->payment_gateway( %$options )
+ unless exists($options->{payment_gateway});
+
+ $options->{payment_gateway};
+}
+
+sub _bop_auth {
+ my ($self, $options) = @_;
+
+ (
+ 'login' => $options->{payment_gateway}->gateway_username,
+ 'password' => $options->{payment_gateway}->gateway_password,
);
+}
- ###
- # check for banned credit card/ACH
- ###
+sub _bop_options {
+ my ($self, $options) = @_;
- my $ban = qsearchs('banned_pay', {
- 'payby' => $method2payby{$method},
- 'payinfo' => md5_base64($payinfo),
- } );
- return "Banned credit card" if $ban;
+ $options->{payment_gateway}->gatewaynum
+ ? $options->{payment_gateway}->options
+ : @{ $options->{payment_gateway}->get('options') };
+}
- ###
- # select a gateway
- ###
+sub _bop_defaults {
+ my ($self, $options) = @_;
- my $taxclass = '';
- if ( $options{'invnum'} ) {
- my $cust_bill = qsearchs('cust_bill', { 'invnum' => $options{'invnum'} } );
- die "invnum ". $options{'invnum'}. " not found" unless $cust_bill;
- my @taxclasses =
- map { $_->part_pkg->taxclass }
- grep { $_ }
- map { $_->cust_pkg }
- $cust_bill->cust_bill_pkg;
- unless ( grep { $taxclasses[0] ne $_ } @taxclasses ) { #unless there are
- #different taxclasses
- $taxclass = $taxclasses[0];
- }
- }
+ $options->{description} ||= 'Internet services';
+ $options->{payinfo} = $self->payinfo unless exists( $options->{payinfo} );
+ $options->{invnum} ||= '';
+ $options->{payname} = $self->payname unless exists( $options->{payname} );
+}
+
+sub _bop_content {
+ my ($self, $options) = @_;
+ my %content = ();
+
+ $content{address} = exists($options->{'address1'})
+ ? $options->{'address1'}
+ : $self->address1;
+ my $address2 = exists($options->{'address2'})
+ ? $options->{'address2'}
+ : $self->address2;
+ $content{address} .= ", ". $address2 if length($address2);
+
+ my $payip = exists($options->{'payip'}) ? $options->{'payip'} : $self->payip;
+ $content{customer_ip} = $payip if length($payip);
- #look for an agent gateway override first
- my $cardtype;
- if ( $method eq 'CC' ) {
- $cardtype = cardtype($payinfo);
- } elsif ( $method eq 'ECHECK' ) {
- $cardtype = 'ACH';
+ $content{invoice_number} = $options->{'invnum'}
+ if exists($options->{'invnum'}) && length($options->{'invnum'});
+
+ $content{email_customer} =
+ ( $conf->exists('business-onlinepayment-email_customer')
+ || $conf->exists('business-onlinepayment-email-override') );
+
+ $content{payfirst} = $self->getfield('first');
+ $content{paylast} = $self->getfield('last');
+
+ $content{account_name} = "$content{payfirst} $content{paylast}"
+ if $options->{method} eq 'ECHECK';
+
+ $content{name} = $options->{payname};
+ $content{name} = $content{account_name} if exists($content{account_name});
+
+ $content{city} = exists($options->{city})
+ ? $options->{city}
+ : $self->city;
+ $content{state} = exists($options->{state})
+ ? $options->{state}
+ : $self->state;
+ $content{zip} = exists($options->{zip})
+ ? $options->{'zip'}
+ : $self->zip;
+ $content{country} = exists($options->{country})
+ ? $options->{country}
+ : $self->country;
+ $content{referer} = 'http://cleanwhisker.420.am/'; #XXX fix referer :/
+ $content{phone} = $self->daytime || $self->night;
+
+ (%content);
+}
+
+my %bop_method2payby = (
+ 'CC' => 'CARD',
+ 'ECHECK' => 'CHEK',
+ 'LEC' => 'LECB',
+);
+
+sub realtime_bop {
+ my $self = shift;
+
+ my %options = ();
+ if (ref($_[0]) eq 'HASH') {
+ %options = %{$_[0]};
} else {
- $cardtype = $method;
+ my ( $method, $amount ) = ( shift, shift );
+ %options = @_;
+ $options{method} = $method;
+ $options{amount} = $amount;
+ }
+
+ if ( $DEBUG ) {
+ warn "$me realtime_bop: $options{method} $options{amount}\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
}
- my $override =
- qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => $taxclass, } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => $taxclass, } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => '', } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => '', } );
+ return $self->fake_bop(%options) if $options{'fake'};
- my $payment_gateway = '';
- my( $processor, $login, $password, $action, @bop_options );
- if ( $override ) { #use a payment gateway override
+ $self->_bop_defaults(\%options);
- $payment_gateway = $override->payment_gateway;
+ ###
+ # select a gateway
+ ###
- $processor = $payment_gateway->gateway_module;
- $login = $payment_gateway->gateway_username;
- $password = $payment_gateway->gateway_password;
- $action = $payment_gateway->gateway_action;
- @bop_options = $payment_gateway->options;
+ my $payment_gateway = $self->_payment_gateway( \%options );
+ my $namespace = $payment_gateway->gateway_namespace;
- } else { #use the standard settings from the config
+ eval "use $namespace";
+ die $@ if $@;
- ( $processor, $login, $password, $action, @bop_options ) =
- $self->default_payment_gateway($method);
+ ###
+ # check for banned credit card/ACH
+ ###
- }
+ my $ban = qsearchs('banned_pay', {
+ 'payby' => $bop_method2payby{$options{method}},
+ 'payinfo' => md5_base64($options{payinfo}),
+ } );
+ return "Banned credit card" if $ban;
###
# massage data
###
- my $address = exists($options{'address1'})
- ? $options{'address1'}
- : $self->address1;
- my $address2 = exists($options{'address2'})
- ? $options{'address2'}
- : $self->address2;
- $address .= ", ". $address2 if length($address2);
-
- my $o_payname = exists($options{'payname'})
- ? $options{'payname'}
- : $self->payname;
- my($payname, $payfirst, $paylast);
- if ( $o_payname && $method ne 'ECHECK' ) {
- ($payname = $o_payname) =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
- or return "Illegal payname $payname";
- ($payfirst, $paylast) = ($1, $2);
- } else {
- $payfirst = $self->getfield('first');
- $paylast = $self->getfield('last');
- $payname = "$payfirst $paylast";
+ my (%bop_content) = $self->_bop_content(\%options);
+
+ if ( $options{method} ne 'ECHECK' ) {
+ $options{payname} =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
+ or return "Illegal payname $options{payname}";
+ ($bop_content{payfirst}, $bop_content{paylast}) = ($1, $2);
}
my @invoicing_list = $self->invoicing_list_emailonly;
? $conf->config('business-onlinepayment-email-override')
: $invoicing_list[0];
- my %content = ();
-
- my $payip = exists($options{'payip'})
- ? $options{'payip'}
- : $self->payip;
- $content{customer_ip} = $payip
- if length($payip);
-
- $content{invoice_number} = $options{'invnum'}
- if exists($options{'invnum'}) && length($options{'invnum'});
-
- $content{email_customer} =
- ( $conf->exists('business-onlinepayment-email_customer')
- || $conf->exists('business-onlinepayment-email-override') );
-
my $paydate = '';
- if ( $method eq 'CC' ) {
+ my %content = ();
+ if ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'CC' ) {
- $content{card_number} = $payinfo;
+ $content{card_number} = $options{payinfo};
$paydate = exists($options{'paydate'})
? $options{'paydate'}
: $self->paydate;
$content{recurring_billing} = 'YES'
if qsearch('cust_pay', { 'custnum' => $self->custnum,
'payby' => 'CARD',
- 'payinfo' => $payinfo,
+ 'payinfo' => $options{payinfo},
} )
|| qsearch('cust_pay', { 'custnum' => $self->custnum,
'payby' => 'CARD',
- 'paymask' => $self->mask_payinfo('CARD', $payinfo),
+ 'paymask' => $self->mask_payinfo('CARD', $options{payinfo}),
} );
- } elsif ( $method eq 'ECHECK' ) {
+ } elsif ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'ECHECK' ){
( $content{account_number}, $content{routing_code} ) =
- split('@', $payinfo);
- $content{bank_name} = $o_payname;
+ split('@', $options{payinfo});
+ $content{bank_name} = $options{payname};
$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_name} = $payname;
$content{customer_org} = $self->company ? 'B' : 'I';
$content{state_id} = exists($options{'stateid'})
? $options{'stateid'}
$content{customer_ssn} = exists($options{'ss'})
? $options{'ss'}
: $self->ss;
- } elsif ( $method eq 'LEC' ) {
- $content{phone} = $payinfo;
+ } elsif ( $namespace eq 'Business::OnlinePayment' && $options{method} eq 'LEC' ) {
+ $content{phone} = $options{payinfo};
+ } elsif ( $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+ #move along
+ } else {
+ #die an evil death
}
###
#double-form-submission prevention is taken care of in cust_pay_pending::check
#check the balance
- return "The customer's balance has changed; $method transaction aborted."
+ return "The customer's balance has changed; $options{method} transaction aborted."
if $self->balance < $balance;
- #&& $self->balance < $amount; #might as well anyway?
+ #&& $self->balance < $options{amount}; #might as well anyway?
#also check and make sure there aren't *other* pending payments for this cust
});
return "A payment is already being processed for this customer (".
join(', ', map 'paypendingnum '. $_->paypendingnum, @pending ).
- "); $method transaction aborted."
+ "); $options{method} transaction aborted."
if scalar(@pending);
#okay, good to go, if we're a duplicate, cust_pay_pending will kick us out
my $cust_pay_pending = new FS::cust_pay_pending {
'custnum' => $self->custnum,
#'invnum' => $options{'invnum'},
- 'paid' => $amount,
+ 'paid' => $options{amount},
'_date' => '',
- 'payby' => $method2payby{$method},
- 'payinfo' => $payinfo,
+ 'payby' => $bop_method2payby{$options{method}},
+ 'payinfo' => $options{payinfo},
'paydate' => $paydate,
'status' => 'new',
- 'gatewaynum' => ( $payment_gateway ? $payment_gateway->gatewaynum : '' ),
+ 'gatewaynum' => $payment_gateway->gatewaynum || '',
+ 'session_id' => $options{session_id} || '',
+ 'jobnum' => $options{depend_jobnum} || '',
};
$cust_pay_pending->payunique( $options{payunique} )
if defined($options{payunique}) && length($options{payunique});
my $cpp_new_err = $cust_pay_pending->insert; #mutex lost when this is inserted
return $cpp_new_err if $cpp_new_err;
- my( $action1, $action2 ) = split(/\s*\,\s*/, $action );
+ my( $action1, $action2 ) =
+ split( /\s*\,\s*/, $payment_gateway->gateway_action );
+
+ my $transaction = new $namespace( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
- my $transaction = new Business::OnlinePayment( $processor, @bop_options );
$transaction->content(
- 'type' => $method,
- 'login' => $login,
- 'password' => $password,
+ 'type' => $options{method},
+ $self->_bop_auth(\%options),
'action' => $action1,
'description' => $options{'description'},
- 'amount' => $amount,
+ 'amount' => $options{amount},
#'invoice_number' => $options{'invnum'},
'customer_id' => $self->custnum,
- 'last_name' => $paylast,
- 'first_name' => $payfirst,
- 'name' => $payname,
- 'address' => $address,
- 'city' => ( exists($options{'city'})
- ? $options{'city'}
- : $self->city ),
- 'state' => ( exists($options{'state'})
- ? $options{'state'}
- : $self->state ),
- 'zip' => ( exists($options{'zip'})
- ? $options{'zip'}
- : $self->zip ),
- 'country' => ( exists($options{'country'})
- ? $options{'country'}
- : $self->country ),
- 'referer' => 'http://cleanwhisker.420.am/', #XXX fix referer :/
+ %bop_content,
+ 'reference' => $cust_pay_pending->paypendingnum, #for now
'email' => $email,
- 'phone' => $self->daytime || $self->night,
%content, #after
);
}
}
- if ( $transaction->is_success() && $action2 ) {
+ if ( $transaction->is_success() && $namespace eq 'Business::OnlineThirdPartyPayment' ) {
+
+ return { reference => $cust_pay_pending->paypendingnum,
+ map { $_ => $transaction->$_ } qw ( popup_url collectitems ) };
+
+ } elsif ( $transaction->is_success() && $action2 ) {
$cust_pay_pending->status('authorized');
my $cpp_authorized_err = $cust_pay_pending->replace;
: '';
my $capture =
- new Business::OnlinePayment( $processor, @bop_options );
+ new Business::OnlinePayment( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
my %capture = (
%content,
- type => $method,
+ type => $options{method},
action => $action2,
- login => $login,
- password => $password,
+ $self->_bop_auth(\%options),
order_number => $ordernum,
- amount => $amount,
+ amount => $options{amount},
authorization => $auth,
description => $options{'description'},
);
}
- $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
- my $cpp_captured_err = $cust_pay_pending->replace;
- return $cpp_captured_err if $cpp_captured_err;
-
###
# remove paycvv after initial transaction
###
# correctly
if ( defined $self->dbdef_table->column('paycvv')
&& length($self->paycvv)
- && ! grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save')
+ && ! grep { $_ eq cardtype($options{payinfo}) } $conf->config('cvv-save')
) {
my $error = $self->remove_cvv;
if ( $error ) {
# result handling
###
+ $self->_realtime_bop_result( $cust_pay_pending, $transaction, %options );
+
+}
+
+=item fake_bop
+
+=cut
+
+sub fake_bop {
+ my $self = shift;
+
+ my %options = ();
+ if (ref($_[0]) eq 'HASH') {
+ %options = %{$_[0]};
+ } else {
+ my ( $method, $amount ) = ( shift, shift );
+ %options = @_;
+ $options{method} = $method;
+ $options{amount} = $amount;
+ }
+
+ if ( $options{'fake_failure'} ) {
+ 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'},
+ 'paid' => $options{amount},
+ '_date' => '',
+ 'payby' => $bop_method2payby{$options{method}},
+ #'payinfo' => $payinfo,
+ 'payinfo' => '4111111111111111',
+ 'paybatch' => $paybatch,
+ #'paydate' => $paydate,
+ 'paydate' => '2012-05-01',
+ } );
+ $cust_pay->payunique( $options{payunique} ) if length($options{payunique});
+
+ my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
+
+ if ( $error ) {
+ $cust_pay->invnum(''); #try again with no specific invnum
+ my $error2 = $cust_pay->insert( $options{'manual'} ?
+ ( 'manual' => 1 ) : ()
+ );
+ if ( $error2 ) {
+ # gah, even with transactions.
+ my $e = 'WARNING: Card/ACH debited but database not updated - '.
+ "error inserting (fake!) payment: $error2".
+ " (previously tried insert with invnum #$options{'invnum'}" .
+ ": $error )";
+ warn $e;
+ return $e;
+ }
+ }
+
+ if ( $options{'paynum_ref'} ) {
+ ${ $options{'paynum_ref'} } = $cust_pay->paynum;
+ }
+
+ return ''; #no error
+
+}
+
+
+# item _realtime_bop_result CUST_PAY_PENDING, BOP_OBJECT [ OPTION => VALUE ... ]
+#
+# Wraps up processing of a realtime credit card, ACH (electronic check) or
+# phone bill transaction.
+
+sub _realtime_bop_result {
+ my( $self, $cust_pay_pending, $transaction, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$me _realtime_bop_result: pending transaction ".
+ $cust_pay_pending->paypendingnum. "\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
+ }
+
+ my $payment_gateway = $options{payment_gateway}
+ or return "no payment gateway in arguments to _realtime_bop_result";
+
+ $cust_pay_pending->status($transaction->is_success() ? 'captured' : 'declined');
+ my $cpp_captured_err = $cust_pay_pending->replace;
+ return $cpp_captured_err if $cpp_captured_err;
+
if ( $transaction->is_success() ) {
my $paybatch = '';
- if ( $payment_gateway ) { # agent override
+ if ( $payment_gateway->gatewaynum ) { # agent override
$paybatch = $payment_gateway->gatewaynum. '-';
}
- $paybatch .= "$processor:". $transaction->authorization;
+ $paybatch .= $payment_gateway->gateway_module. ":".
+ $transaction->authorization;
$paybatch .= ':'. $transaction->order_number
if $transaction->can('order_number')
my $cust_pay = new FS::cust_pay ( {
'custnum' => $self->custnum,
'invnum' => $options{'invnum'},
- 'paid' => $amount,
+ 'paid' => $cust_pay_pending->paid,
'_date' => '',
- 'payby' => $method2payby{$method},
- 'payinfo' => $payinfo,
+ 'payby' => $cust_pay_pending->payby,
+ #'payinfo' => $payinfo,
'paybatch' => $paybatch,
- 'paydate' => $paydate,
+ 'paydate' => $cust_pay_pending->paydate,
} );
#doesn't hurt to know, even though the dup check is in cust_pay_pending now
$cust_pay->payunique( $options{payunique} )
if ( $error2 ) {
# gah. but at least we have a record of the state we had to abort in
# from cust_pay_pending now.
- my $e = "WARNING: $method captured but payment not recorded - ".
- "error inserting payment ($processor): $error2".
+ my $e = "WARNING: $options{method} captured but payment not recorded -".
+ " error inserting payment (". $payment_gateway->gateway_module.
+ "): $error2".
" (previously tried insert with invnum #$options{'invnum'}" .
": $error ) - pending payment saved as paypendingnum ".
$cust_pay_pending->paypendingnum. "\n";
}
}
+ my $jobnum = $cust_pay_pending->jobnum;
+ if ( $jobnum ) {
+ my $placeholder = qsearchs( 'queue', { 'jobnum' => $jobnum } );
+
+ unless ( $placeholder ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: $options{method} captured but job $jobnum not ".
+ "found for paypendingnum ". $cust_pay_pending->paypendingnum. "\n";
+ warn $e;
+ return $e;
+ }
+
+ $error = $placeholder->delete;
+
+ if ( $error ) {
+ $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+ my $e = "WARNING: $options{method} captured but could not delete ".
+ "job $jobnum for paypendingnum ".
+ $cust_pay_pending->paypendingnum. ": $error\n";
+ warn $e;
+ return $e;
+ }
+
+ }
+
if ( $options{'paynum_ref'} ) {
${ $options{'paynum_ref'} } = $cust_pay->paynum;
}
if ( $cpp_done_err ) {
$dbh->rollback or die $dbh->errstr if $oldAutoCommit;
- my $e = "WARNING: $method captured but payment not recorded - ".
+ my $e = "WARNING: $options{method} captured but payment not recorded - ".
"error updating status for paypendingnum ".
$cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
warn $e;
} else {
- my $perror = "$processor error: ". $transaction->error_message;
+ my $perror = $payment_gateway->gateway_module. " error: ".
+ $transaction->error_message;
+
+ my $jobnum = $cust_pay_pending->jobnum;
+ if ( $jobnum ) {
+ my $placeholder = qsearchs( 'queue', { 'jobnum' => $jobnum } );
+
+ if ( $placeholder ) {
+ my $error = $placeholder->depended_delete;
+ $error ||= $placeholder->delete;
+ warn "error removing provisioning jobs after declined paypendingnum ".
+ $cust_pay_pending->paypendingnum. "\n";
+ } else {
+ my $e = "error finding job $jobnum for declined paypendingnum ".
+ $cust_pay_pending->paypendingnum. "\n";
+ warn $e;
+ }
+ }
+
unless ( $transaction->error_message ) {
my $t_response;
};
} else {
$t_response .=
- "No additional debugging information available for $processor";
+ "No additional debugging information available for ".
+ $payment_gateway->gateway_module;
}
- $perror .= "No error_message returned from $processor -- ".
+ $perror .= "No error_message returned from ".
+ $payment_gateway->gateway_module. " -- ".
( ref($t_response) ? Dumper($t_response) : $t_response );
}
$cust_pay_pending->statustext("declined: $perror");
my $cpp_done_err = $cust_pay_pending->replace;
if ( $cpp_done_err ) {
- my $e = "WARNING: $method declined but pending payment not resolved - ".
- "error updating status for paypendingnum ".
+ my $e = "WARNING: $options{method} declined but pending payment not ".
+ "resolved - error updating status for paypendingnum ".
$cust_pay_pending->paypendingnum. ": $cpp_done_err \n";
warn $e;
$perror = "$e ($perror)";
}
-=item fake_bop
+=item realtime_botpp_capture CUST_PAY_PENDING [ OPTION => VALUE ... ]
-=cut
+Verifies successful third party processing of a realtime credit card,
+ACH (electronic check) or phone bill transaction via a
+Business::OnlineThirdPartyPayment realtime gateway. See
+L<http://420.am/business-onlinethirdpartypayment> for supported gateways.
-sub fake_bop {
- my( $self, $method, $amount, %options ) = @_;
+Available options are: I<description>, I<invnum>, I<quiet>, I<paynum_ref>, I<payunique>
- if ( $options{'fake_failure'} ) {
- return "Error: No error; test failure requested with fake_failure";
- }
+The additional options I<payname>, I<city>, I<state>,
+I<zip>, I<payinfo> and I<paydate> are also available. Any of these options,
+if set, will override the value from the customer record.
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
- );
+I<description> is a free-text field passed to the gateway. It defaults to
+"Internet services".
- #my $paybatch = '';
- #if ( $payment_gateway ) { # agent override
- # $paybatch = $payment_gateway->gatewaynum. '-';
- #}
- #
- #$paybatch .= "$processor:". $transaction->authorization;
- #
- #$paybatch .= ':'. $transaction->order_number
- # if $transaction->can('order_number')
- # && length($transaction->order_number);
+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.
- my $paybatch = 'FakeProcessor:54:32';
+I<quiet> can be set true to surpress email decline notices.
- my $cust_pay = new FS::cust_pay ( {
- 'custnum' => $self->custnum,
- 'invnum' => $options{'invnum'},
- 'paid' => $amount,
- '_date' => '',
- 'payby' => $method2payby{$method},
- #'payinfo' => $payinfo,
- 'payinfo' => '4111111111111111',
- 'paybatch' => $paybatch,
- #'paydate' => $paydate,
- 'paydate' => '2012-05-01',
- } );
- $cust_pay->payunique( $options{payunique} ) if length($options{payunique});
+I<paynum_ref> can be set to a scalar reference. It will be filled in with the
+resulting paynum, if any.
- my $error = $cust_pay->insert($options{'manual'} ? ( 'manual' => 1 ) : () );
+I<payunique> is a unique identifier for this payment.
- if ( $error ) {
- $cust_pay->invnum(''); #try again with no specific invnum
- my $error2 = $cust_pay->insert( $options{'manual'} ?
- ( 'manual' => 1 ) : ()
- );
- if ( $error2 ) {
- # gah, even with transactions.
- my $e = 'WARNING: Card/ACH debited but database not updated - '.
- "error inserting (fake!) payment: $error2".
- " (previously tried insert with invnum #$options{'invnum'}" .
- ": $error )";
- warn $e;
- return $e;
- }
+Returns a hashref containing elements bill_error (which will be undefined
+upon success) and session_id of any associated session.
+
+=cut
+
+sub realtime_botpp_capture {
+ my( $self, $cust_pay_pending, %options ) = @_;
+ if ( $DEBUG ) {
+ warn "$me realtime_botpp_capture: pending transaction $cust_pay_pending\n";
+ warn " $_ => $options{$_}\n" foreach keys %options;
}
- if ( $options{'paynum_ref'} ) {
- ${ $options{'paynum_ref'} } = $cust_pay->paynum;
+ eval "use Business::OnlineThirdPartyPayment";
+ die $@ if $@;
+
+ ###
+ # select the gateway
+ ###
+
+ my $method = FS::payby->payby2bop($cust_pay_pending->payby);
+
+ my $payment_gateway = $cust_pay_pending->gatewaynum
+ ? qsearchs( 'payment_gateway',
+ { gatewaynum => $cust_pay_pending->gatewaynum }
+ )
+ : $self->agent->payment_gateway( 'method' => $method,
+ # 'invnum' => $cust_pay_pending->invnum,
+ # 'payinfo' => $cust_pay_pending->payinfo,
+ );
+
+ $options{payment_gateway} = $payment_gateway; # for the helper subs
+
+ ###
+ # massage data
+ ###
+
+ my @invoicing_list = $self->invoicing_list_emailonly;
+ if ( $conf->exists('emailinvoiceautoalways')
+ || $conf->exists('emailinvoiceauto') && ! @invoicing_list
+ || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) {
+ push @invoicing_list, $self->all_emails;
}
- return ''; #no error
+ my $email = ($conf->exists('business-onlinepayment-email-override'))
+ ? $conf->config('business-onlinepayment-email-override')
+ : $invoicing_list[0];
+
+ my %content = ();
+
+ $content{email_customer} =
+ ( $conf->exists('business-onlinepayment-email_customer')
+ || $conf->exists('business-onlinepayment-email-override') );
+
+ ###
+ # run transaction(s)
+ ###
+
+ my $transaction =
+ new Business::OnlineThirdPartyPayment( $payment_gateway->gateway_module,
+ $self->_bop_options(\%options),
+ );
+
+ $transaction->reference({ %options });
+
+ $transaction->content(
+ 'type' => $method,
+ $self->_bop_auth(\%options),
+ 'action' => 'Post Authorization',
+ 'description' => $options{'description'},
+ 'amount' => $cust_pay_pending->paid,
+ #'invoice_number' => $options{'invnum'},
+ 'customer_id' => $self->custnum,
+ 'referer' => 'http://cleanwhisker.420.am/',
+ 'reference' => $cust_pay_pending->paypendingnum,
+ 'email' => $email,
+ 'phone' => $self->daytime || $self->night,
+ %content, #after
+ # plus whatever is required for bogus capture avoidance
+ );
+
+ $transaction->submit();
+
+ my $error =
+ $self->_realtime_bop_result( $cust_pay_pending, $transaction, %options );
+
+ {
+ bill_error => $error,
+ session_id => $cust_pay_pending->session_id,
+ }
}
-=item default_payment_gateway
+=item default_payment_gateway DEPRECATED -- use agent->payment_gateway
=cut
die "Real-time processing not enabled\n"
unless $conf->exists('business-onlinepayment');
+ warn "default_payment_gateway deprecated -- use agent->payment_gateway\n";
+
#load up config
my $bop_config = 'business-onlinepayment';
$bop_config .= '-ach'
#some false laziness w/realtime_bop, not enough to make it worth merging
#but some useful small subs should be pulled out
sub realtime_refund_bop {
- my( $self, $method, %options ) = @_;
+ my $self = shift;
+
+ my %options = ();
+ if (ref($_[0]) ne 'HASH') {
+ %options = %{$_[0]};
+ } else {
+ my $method = shift;
+ %options = @_;
+ $options{method} = $method;
+ }
+
if ( $DEBUG ) {
- warn "$me realtime_refund_bop: $method refund\n";
+ warn "$me realtime_refund_bop: $options{method} refund\n";
warn " $_ => $options{$_}\n" foreach keys %options;
}
- eval "use Business::OnlinePayment";
- die $@ if $@;
-
###
# look up the original payment and optionally a gateway for that payment
###
my $cust_pay = '';
my $amount = $options{'amount'};
- my( $processor, $login, $password, @bop_options ) ;
+ my( $processor, $login, $password, @bop_options, $namespace ) ;
my( $auth, $order_number ) = ( '', '', '' );
if ( $options{'paynum'} ) {
$processor = $payment_gateway->gateway_module;
$login = $payment_gateway->gateway_username;
$password = $payment_gateway->gateway_password;
+ $namespace = $payment_gateway->gateway_namespace;
@bop_options = $payment_gateway->options;
} else { #try the default gateway
- my( $conf_processor, $unused_action );
- ( $conf_processor, $login, $password, $unused_action, @bop_options ) =
- $self->default_payment_gateway($method);
+ my $conf_processor;
+ my $payment_gateway =
+ $self->agent->payment_gateway('method' => $options{method});
+
+ ( $conf_processor, $login, $password, $namespace ) =
+ map { my $method = "gateway_$_"; $payment_gateway->$method }
+ qw( module username password namespace );
+
+ @bop_options = $payment_gateway->gatewaynum
+ ? $payment_gateway->options
+ : @{ $payment_gateway->get('options') };
return "processor of payment $options{'paynum'} $processor does not".
" match default processor $conf_processor"
} else { # didn't specify a paynum, so look for agent gateway overrides
# like a normal transaction
-
- my $cardtype;
- if ( $method eq 'CC' ) {
- $cardtype = cardtype($self->payinfo);
- } elsif ( $method eq 'ECHECK' ) {
- $cardtype = 'ACH';
- } else {
- $cardtype = $method;
- }
- my $override =
- qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => $cardtype,
- taxclass => '', } )
- || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum,
- cardtype => '',
- taxclass => '', } );
-
- if ( $override ) { #use a payment gateway override
- my $payment_gateway = $override->payment_gateway;
-
- $processor = $payment_gateway->gateway_module;
- $login = $payment_gateway->gateway_username;
- $password = $payment_gateway->gateway_password;
- #$action = $payment_gateway->gateway_action;
- @bop_options = $payment_gateway->options;
+ my $payment_gateway =
+ $self->agent->payment_gateway( 'method' => $options{method},
+ #'payinfo' => $payinfo,
+ );
+ my( $processor, $login, $password, $namespace ) =
+ map { my $method = "gateway_$_"; $payment_gateway->$method }
+ qw( module username password namespace );
- } else { #use the standard settings from the config
-
- my $unused_action;
- ( $processor, $login, $password, $unused_action, @bop_options ) =
- $self->default_payment_gateway($method);
-
- }
+ my @bop_options = $payment_gateway->gatewaynum
+ ? $payment_gateway->options
+ : @{ $payment_gateway->get('options') };
}
return "neither amount nor paynum specified" unless $amount;
+ eval "use $namespace";
+ die $@ if $@;
+
my %content = (
- 'type' => $method,
+ 'type' => $options{method},
'login' => $login,
'password' => $password,
'order_number' => $order_number,
$address .= ", ". $self->address2 if $self->address2;
my($payname, $payfirst, $paylast);
- if ( $self->payname && $method ne 'ECHECK' ) {
+ if ( $self->payname && $options{method} ne 'ECHECK' ) {
$payname = $self->payname;
$payname =~ /^\s*([\w \,\.\-\']*)?\s+([\w\,\.\-\']+)\s*$/
or return "Illegal payname $payname";
if length($payip);
my $payinfo = '';
- if ( $method eq 'CC' ) {
+ if ( $options{method} eq 'CC' ) {
if ( $cust_pay ) {
$content{card_number} = $payinfo = $cust_pay->payinfo;
$content{expiration} = "$2/$1";
}
- } elsif ( $method eq 'ECHECK' ) {
+ } elsif ( $options{method} eq 'ECHECK' ) {
if ( $cust_pay ) {
$payinfo = $cust_pay->payinfo;
$content{account_name} = $payname;
$content{customer_org} = $self->company ? 'B' : 'I';
$content{customer_ssn} = $self->ss;
- } elsif ( $method eq 'LEC' ) {
+ } elsif ( $options{method} eq 'LEC' ) {
$content{phone} = $payinfo = $self->payinfo;
}
return "$processor error: ". $refund->error_message
unless $refund->is_success();
- my %method2payby = (
- 'CC' => 'CARD',
- 'ECHECK' => 'CHEK',
- 'LEC' => 'LECB',
- );
-
my $paybatch = "$processor:". $refund->authorization;
$paybatch .= ':'. $refund->order_number
if $refund->can('order_number') && $refund->order_number;
'paynum' => $options{'paynum'},
'refund' => $amount,
'_date' => '',
- 'payby' => $method2payby{$method},
+ 'payby' => $bop_method2payby{$options{method}},
'payinfo' => $payinfo,
'paybatch' => $paybatch,
'reason' => $options{'reason'} || 'card or ACH refund',
#|| $self->ut_textn('statustext')
|| $self->ut_anything('statustext')
#|| $self->ut_money('cust_balance')
+ || $self->ut_hexn('session_id')
|| $self->ut_foreign_keyn('paynum', 'cust_pay', 'paynum' )
|| $self->payinfo_check() #payby/payinfo/paymask/paydate
;
$self->SUPER::check;
}
+=item cust_main
+
+Returns the associated L<FS::cust_main> record if any. Otherwise returns false.
+
+=cut
+
+sub cust_main {
+ my $self = shift;
+ qsearchs('cust_main', { custnum => $self->custnum } );
+}
+
+
#these two are kind-of false laziness w/cust_main::realtime_bop
#(currently only used when resolving pending payments manually)
sub check {
my $self = shift;
- $self->locationnum('')
- if defined($self->locationnum) && length($self->locationnum)
- && ( $self->locationnum == 0 || $self->locationnum == -1 );
+ $self->locationnum('') if !$self->locationnum || $self->locationnum == -1;
my $error =
$self->ut_numbern('pkgnum')
tinyname => 'card',
shortname => 'Credit card',
longname => 'Credit card (automatic)',
+ realtime => 1,
},
'DCRD' => {
tinyname => 'card',
shortname => 'Credit card',
longname => 'Credit card (on-demand)',
cust_pay => 'CARD', #this is a customer type only, payments are CARD...
+ realtime => 1,
},
'CHEK' => {
tinyname => 'check',
shortname => 'Electronic check',
longname => 'Electronic check (automatic)',
+ realtime => 1,
},
'DCHK' => {
tinyname => 'check',
shortname => 'Electronic check',
longname => 'Electronic check (on-demand)',
cust_pay => 'CHEK', #this is a customer type only, payments are CHEK...
+ realtime => 1,
},
'LECB' => {
tinyname => 'phone bill',
shortname => 'Phone bill billing',
longname => 'Phone bill billing',
+ realtime => 1,
},
'BILL' => {
tinyname => 'billing',
return 1;
}
+sub realtime { # can use realtime payment facilities
+ my( $self, $payby ) = @_;
+
+ return 0 unless $hash{$payby};
+ return 0 unless exists( $hash{$payby}->{realtime} );
+
+ return $hash{$payby}->{realtime};
+}
+
sub payby2longname {
my $self = shift;
map { $_ => $hash{$_}->{longname} } $self->payby;
%payby2bop = (
'CARD' => 'CC',
'CHEK' => 'ECHECK',
+ 'MCRD' => 'CC',
);
sub payby2bop {
package FS::payment_gateway;
use strict;
-use vars qw( @ISA );
+use vars qw( @ISA $me $DEBUG );
use FS::Record qw( qsearch qsearchs dbh );
use FS::option_Common;
use FS::agent_payment_gateway;
@ISA = qw( FS::option_Common );
+$me = '[ FS::payment_gateway ]';
+$DEBUG=0;
=head1 NAME
=item gatewaynum - primary key
+=item gateway_namespace - Business::OnlinePayment or Business::OnlineThirdPartyPayment
+
=item gateway_module - Business::OnlinePayment:: module name
=item gateway_username - payment gateway username
my $error =
$self->ut_numbern('gatewaynum')
|| $self->ut_alpha('gateway_module')
+ || $self->ut_enum('gateway_namespace', ['Business::OnlinePayment',
+ 'Business::OnlineThirdPartyPayment',
+ ] )
|| $self->ut_textn('gateway_username')
|| $self->ut_anything('gateway_password')
+ || $self->ut_textn('gateway_callback_url') # a bit too permissive
|| $self->ut_enum('disabled', [ '', 'Y' ] )
#|| $self->ut_textn('gateway_action')
;
$self->gateway_action('Normal Authorization');
}
+ # this little kludge mimics FS::CGI::popurl
+ $self->gateway_callback_url($self->gateway_callback_url. '/')
+ if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ );
+
$self->SUPER::check;
}
}
+=item namespace_description
+
+returns a friendly name for the namespace
+
+=cut
+
+my %namespace2description = (
+ '' => 'Direct',
+ 'Business::OnlinePayment' => 'Direct',
+ 'Business::OnlineThirdPartyPayment' => 'Hosted',
+);
+
+sub namespace_description {
+ $namespace2description{shift->gateway_namespace} || 'Unknown';
+}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+#
+#
+
+sub _upgrade_data {
+ my ($class, %opts) = @_;
+ my $dbh = dbh;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ foreach ( qsearch( 'payment_gateway', { 'gateway_namespace' => '' } ) ) {
+ $_->gateway_namespace('Business::OnlinePayment'); #defaulting
+ my $error = $_->replace;
+ die "$class had error during upgrade replacement: $error" if $error;
+ }
+}
+
=back
=head1 BUGS
'process_payment_order_pkg' => 'MyAccount/process_payment_order_pkg',
'process_payment_order_renew' => 'MyAccount/process_payment_order_renew',
'process_prepay' => 'MyAccount/process_prepay',
+ 'realtime_collect' => 'MyAccount/realtime_collect',
'list_pkgs' => 'MyAccount/list_pkgs', #add to ss (added?)
'list_svcs' => 'MyAccount/list_svcs', #add to ss (added?)
'list_svc_usage' => 'MyAccount/list_svc_usage',
'signup_info' => 'Signup/signup_info',
'domain_select_hash' => 'Signup/domain_select_hash', # expose?
'new_customer' => 'Signup/new_customer',
+ 'capture_payment' => 'Signup/capture_payment',
'agent_login' => 'Agent/agent_login',
'agent_logout' => 'Agent/agent_logout',
'agent_info' => 'Agent/agent_info',
'LECB' => qq/Phone Bill Billing/,
'BILL' => qq/Billing/,
'COMP' => qq/Complimentary/,
+ 'PREP' => qq/Prepaid Card/,
'PREPAY' => qq/Prepaid Card/,
);
tie my %options, 'Tie::IxHash', ();
- foreach my $payby_option ( @paybys ) {
+ foreach my $payby_option ( grep { exists( $payby_index{$_} ) } @paybys ) {
$options{$payby_option} = $payby_index{$payby_option};
}
$options{$payby} = $payby_index{$payby}
--- /dev/null
+<HTML><HEAD><TITLE>My Account</TITLE></HEAD>
+<BODY BGCOLOR="#eeeeee"><FONT SIZE=5>MyAccount</FONT><BR><BR>
+<SCRIPT TYPE="text/javascript">
+ function popcollect() {
+ overlib( OLiframeContent('<%= $popup_url %>', 336, 550, 'Secure Payment Area', 0, 'auto' ), CAPTION, 'Pay now', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close' );
+ return false;
+ }
+</SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_draggable.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_crossframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="iframecontentmws.js"></SCRIPT>
+<%= $url = "$selfurl?session=$session_id;action="; ''; %>
+<%= include('myaccount_menu') %>
+<TD VALIGN="top">
+<FONT SIZE=4>Pay now</FONT><BR><BR>
+
+<%= if ( $error ) {
+ $OUT .= qq!<FONT SIZE="+1" COLOR="#ff0000">$error</FONT><BR><BR>!;
+}else{
+ $OUT .= <<EOF;
+ You are about to contact our payment processor to pay $amount.<BR><BR>
+ Your transaction reference number is $reference <BR><BR>
+ <FORM NAME="collect_popper" method="post" action="javascript:void(0)" onSubmit="popcollect()">
+EOF
+
+ my %itemhash = @collectitems;
+ foreach my $input (keys %itemhash) {
+ $OUT .= qq!<INPUT NAME="$input" TYPE="hidden" VALUE="$itemhash{$input}">!;
+ }
+
+ $OUT .= qq!<INPUT NAME="submit" type="submit" value="Pay now">!;
+ $OUT .= qq!</FORM>!;
+}
+%>
+</TD></TR></TABLE>
+</BODY></HTML>
<%= $small_custview %>
<BR>
<%= if ( $balance > 0 ) {
- $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR><BR>!;
+ if (scalar(grep $_, @hide_payment_field)) {
+ $OUT .= qq! <B><A HREF="${url}make_payment">Make a payment</A></B><BR><BR>!;
+ } else {
+ $OUT .= qq! <B><A HREF="${url}make_thirdparty_payment&payby_method=CC">Make a payment</A></B><BR><BR>!;
+ }
} %>
<%=
if ( @open_invoices ) {
if ( 1 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy
- push @menu, (
- { title=>'Recharge my account with a credit card',
- url=>'make_payment', indent=>2 },
- { title=>'Recharge my account with a check',
- url=>'make_ach_payment', indent=>2 },
- { title=>'Recharge my account with a prepaid card',
- url=>'recharge_prepay', indent=>2 },
- );
+ #XXXFIXME still a bit sloppy for multi-gateway of differing namespace
+ my $i = 0;
+ while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CARD/; $i++ }
+ if ( $cust_paybys[$i] =~ /^CARD/ ) {
+ push @menu, { title => 'Recharge my account with a credit card',
+ url => $hide_payment_fields[$i]
+ ? 'make_thirdparty_payment&payby_method=CC'
+ : 'make_payment',
+ indent => 2,
+ }
+ }
+
+ $i = 0;
+ while($i < scalar(@cust_paybys)) { last if $cust_paybys[$i] =~ /^CHEK/; $i++ }
+ if ( $cust_paybys[$i] =~ /^CHEK/ ) {
+ push @menu, { title => 'Recharge my account with a check',
+ url => $hide_payment_field[$i]
+ ? 'make_thirdparty_payment&payby_method=ECHECK'
+ : 'make_ach_payment',
+ indent => 2,
+ }
+ }
+
+ push @menu, { title => 'Recharge my account with a prepaid card',
+ url => 'recharge_prepay',
+ indent => 2,
+ }
+ if grep(/^PREP/, @cust_paybys);
}
use Date::Format;
use Number::Format 1.50;
use FS::SelfService qw( login_info login customer_info edit_info invoice
- payment_info process_payment
+ payment_info process_payment realtime_collect
process_prepay
list_pkgs order_pkg signup_info order_recharge
part_svc_info provision_acct provision_external
#order|pw_list XXX ???
$cgi->param('action') =~
- /^(myaccount|view_invoice|make_payment|make_ach_payment|payment_results|ach_payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|change_pay|process_change_bill|process_change_ship|process_change_pay|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|view_support_details|change_password|process_change_password)$/
+ /^(myaccount|view_invoice|make_payment|make_ach_payment|make_thirdparty_payment|payment_results|ach_payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|change_pay|process_change_bill|process_change_ship|process_change_pay|customer_order_pkg|process_order_pkg|customer_change_pkg|process_change_pkg|process_order_recharge|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|view_usage|view_usage_details|view_support_details|change_password|process_change_password)$/
or die "unknown action ". $cgi->param('action');
my $action = $1;
do_template($action, {
'session_id' => $session_id,
'action' => $action, #so the menu knows what tab we're on...
+ %{ payment_info( 'session_id' => $session_id ) }, # cust_paybys for the menu
%{$result}
});
}
+sub make_thirdparty_payment {
+ $cgi->param('payby_method') =~ /^(CC|ECHECK)$/
+ or die "illegal payby method";
+ realtime_collect( 'session_id' => $session_id, 'method' => $1 );
+}
+
sub recharge_prepay {
customer_info( 'session_id' => $session_id );
}
$ieak_file $ieak_template
$signup_html $signup_template
$success_html $success_template
+ $collect_html $collect_template
$decline_html $decline_template
);
use subs qw( print_form print_okay print_decline
- success_default decline_default
+ success_default collect_default decline_default
);
use CGI;
#use CGI::Carp qw(fatalsToBrowser);
$success_html = -e 'success.html'
? 'success.html'
: '/usr/local/freeside/success.html';
+$collect_html = -e 'collect.html'
+ ? 'collect.html'
+ : '/usr/local/freeside/collect.html';
$decline_html = -e 'decline.html'
? 'decline.html'
: '/usr/local/freeside/decline.html';
or die $Text::Template::ERROR;
}
+if ( -e $collect_html ) {
+ my $collect_txt = Text::Template::_load_text($collect_html)
+ or die $Text::Template::ERROR;
+ $collect_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $collect_txt = $1;
+ $collect_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $collect_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $collect_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &collect_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
if ( -e $decline_html ) {
my $decline_txt = Text::Template::_load_text($decline_html)
or die $Text::Template::ERROR;
'reg_code' => uc(scalar($cgi->param('reg_code'))),
);
-if ( ( defined($cgi->param('magic')) && $cgi->param('magic') eq 'process' )
- || ( defined($cgi->param('action')) && $cgi->param('action') eq 'process_signup' )
- ) {
+my $magic = $cgi->param('magic') || '';
+my $action = $cgi->param('action') || '';
+
+if ( $magic eq 'process' || $action eq 'process_signup' ) {
$error = '';
if ( $error eq '_decline' ) {
print_decline();
+ } elsif ( $error eq '_collect' ) {
+ map { $cgi->param($_, $rv->{$_}) }
+ qw( popup_url reference collectitems amount );
+ print_collect();
} elsif ( $error ) {
#fudge the snarf info
no strict 'refs';
);
}
+} elsif ( $magic eq 'success' || $action eq 'success' ) {
+
+ $cgi->param('username', 'username'); #hmmm temp kludge
+ $cgi->param('_password', 'password');
+ print_okay( map { /^([\w ]+)$/ ? ( $_ => $1 ) : () } $cgi->param ); #hmmm
+
+} elsif ( $magic eq 'decline' || $action eq 'decline' ) {
+
+ print_decline();
+
} else {
$error = '';
print_form;
);
}
+sub print_collect {
+
+ $error = "Error: $error" if $error;
+
+ my $r = {
+ $cgi->Vars,
+ %{$init_data},
+ 'error' => $error,
+ };
+
+ $r->{pkgpart} ||= $r->{default_pkgpart};
+
+ $r->{referral_custnum} = $r->{'ref'};
+ $r->{self_url} = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $collect_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
sub print_decline {
print $cgi->header( '-expires' => 'now' ),
$decline_template->fill_in();
END
}
+sub collect_default { #html to use if there is a collect phase
+ <<'END';
+<HTML><HEAD><TITLE>Pay now</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Pay now</FONT><BR><BR>
+<SCRIPT TYPE="text/javascript">
+ function popcollect() {
+ overlib( OLiframeContent('<%= $popup_url %>', 336, 550, 'Secure Payment Area', 0, 'auto' ), CAPTION, 'Pay now', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK, BGCOLOR, '#333399', CGCOLOR, '#333399', CLOSETEXT, 'Close' );
+ return false;
+ }
+</SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_iframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_draggable.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="overlibmws_crossframe.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="iframecontentmws.js"></SCRIPT>
+You are about to contact our payment processor to pay <%= $amount %> for
+<%= $pkg %>.<BR><BR>
+Your transaction reference number is <%= $reference %><BR><BR>
+<FORM NAME="collect_popper" method="post" action="javascript:void(0)" onSubmit="popcollect()">
+<%=
+ my %itemhash = @collectitems;
+ foreach my $input (keys %itemhash) {
+ $OUT .= qq!<INPUT NAME="$input" TYPE="hidden" VALUE="$itemhash{$input}">!;
+ }
+%>
+<INPUT NAME="submit" type="submit" value="Pay now">
+</FORM>
+</BODY></HTML>
+END
+}
+
sub decline_default { #html to use if there is a decline
<<'END';
<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
form_name => 'dummy',
html_between => '</td></tr></table>',
form_action => 'dummy.cgi',
- layer_callback => sub { my $layer = shift; return $paybychecked{$layer}. '</TABLE>'; },
+ layer_callback => sub { my $layer = shift; return ( shift @hide_payment_fields ? '' : $paybychecked{$layer} ) . '</TABLE>'; },
)->html;
--- /dev/null
+#!/usr/bin/perl -T
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw( $cgi $self_url $error
+ $verify_html $verify_template
+ $success_html $success_template
+ $decline_html $decline_template
+ );
+
+use subs qw( print_verify print_okay print_decline
+ verify_default success_default decline_default
+ );
+use CGI;
+use Text::Template;
+use FS::SelfService qw( capture_payment );
+
+$verify_html = -e 'verify.html'
+ ? 'verify.html'
+ : '/usr/local/freeside/verify.html';
+$success_html = -e 'verify_success.html'
+ ? 'success.html'
+ : '/usr/local/freeside/success.html';
+$decline_html = -e 'verify_decline.html'
+ ? 'decline.html'
+ : '/usr/local/freeside/decline.html';
+
+
+if ( -e $verify_html ) {
+ my $verify_txt = Text::Template::_load_text($verify_html)
+ or die $Text::Template::ERROR;
+ $verify_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $verify_txt = $1;
+ $verify_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $verify_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $verify_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &verify_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
+if ( -e $success_html ) {
+ my $success_txt = Text::Template::_load_text($success_html)
+ or die $Text::Template::ERROR;
+ $success_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $success_txt = $1;
+ $success_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $success_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $success_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &success_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
+if ( -e $decline_html ) {
+ my $decline_txt = Text::Template::_load_text($decline_html)
+ or die $Text::Template::ERROR;
+ $decline_txt =~ /^(.*)$/s; #untaint the template source - it's trusted
+ $decline_txt = $1;
+ $decline_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => $decline_txt,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+} else {
+ $decline_template = new Text::Template ( TYPE => 'STRING',
+ SOURCE => &decline_default,
+ DELIMITERS => [ '<%=', '%>' ],
+ )
+ or die $Text::Template::ERROR;
+}
+
+$cgi = new CGI;
+
+my $rv = capture_payment(
+ data => { map { $_ => scalar($cgi->param($_)) } $cgi->param },
+ url => $cgi->self_url,
+);
+
+$error = $rv->{error};
+
+if ( $error eq '_decline' ) {
+ print_decline();
+} elsif ( $error ) {
+ print_verify();
+} else {
+ print_okay(%$rv);
+}
+
+
+sub print_verify {
+
+ $error = "Error: $error" if $error;
+
+ my $r = { $cgi->Vars, 'error' => $error };
+
+ $r->{self_url} = $cgi->self_url;
+
+ print $cgi->header( '-expires' => 'now' ),
+ $verify_template->fill_in( PACKAGE => 'FS::SelfService::_signupcgi',
+ HASH => $r
+ );
+}
+
+sub print_decline {
+ print $cgi->header( '-expires' => 'now' ),
+ $decline_template->fill_in();
+}
+
+sub print_okay {
+ my %param = @_;
+
+ my @success_url = split '/', $cgi->url(-path);
+ pop @success_url;
+
+ my $success_url = join '/', @success_url;
+ if ($param{session_id}) {
+ my $session_id = lc($param{session_id});
+ $success_url .= "/selfservice.cgi?action=myaccount&session=$session_id";
+ } else {
+ $success_url .= '/signup.cgi?action=success';
+ }
+
+ print $cgi->header( '-expires' => 'now' ),
+ $success_template->fill_in( HASH => { success_url => $success_url } );
+}
+
+sub success_default { #html to use if you don't specify a success file
+ <<'END';
+<HTML><HEAD><TITLE>Signup successful</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Signup successful</FONT><BR><BR>
+Thanks for signing up!
+<BR><BR>
+<SCRIPT TYPE="text/javascript">
+ window.top.location="<%= $success_url %>";
+</SCRIPT>
+</BODY></HTML>
+END
+}
+
+sub verify_default { #html to use for verification response
+ <<'END';
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account. Please contact customer
+support.
+</BODY></HTML>
+END
+}
+
+sub decline_default { #html to use if there is a decline
+ <<'END';
+<HTML><HEAD><TITLE>Processing error</TITLE></HEAD>
+<BODY BGCOLOR="#e8e8e8"><FONT SIZE=7>Processing error</FONT><BR><BR>
+There has been an error processing your account. Please contact customer
+support.
+</BODY></HTML>
+END
+}
+
+# subs for the templates...
+
+package FS::SelfService::_signupcgi;
+use HTML::Entities;
+
},
'count_query' => $count_query,
'header' => [ '#',
+ 'Type',
'Gateway',
'Username',
'Password',
'Action',
+ 'URL',
'Options',
],
'fields' => [ 'gatewaynum',
+ 'namespace_description',
$gateway_sub,
'gateway_username',
sub { ' - '; },
'gateway_action',
+ 'gateway_callback_url',
$options_sub,
],
)
-<% include("/elements/header.html","$action Payment gateway", menubar(
- 'View all payment gateways' => $p. 'browse/payment_gateway.html',
-)) %>
-
-<% include('/elements/error.html') %>
-
-<FORM ACTION="<%popurl(1)%>process/payment_gateway.html" METHOD=POST>
-<INPUT TYPE="hidden" NAME="gatewaynum" VALUE="<% $payment_gateway->gatewaynum %>">
-Gateway #<% $payment_gateway->gatewaynum || "(NEW)" %>
-
-<% ntable('#cccccc', 2, '') %>
-
-<TR>
- <TH ALIGN="right">Gateway: </TH>
- <TD>
-% if ( $payment_gateway->gatewaynum ) {
-
-
- <% $payment_gateway->gateway_module %>
- <INPUT TYPE="hidden" NAME="gateway_module" VALUE="<% $payment_gateway->gateway_module %>">
-% } else {
-
-
- <SELECT NAME="gateway_module" SIZE=1>
-% foreach my $module ( qw(
-% 2CheckOut
-% AuthorizeNet
-% BankOfAmerica
-% Beanstream
-% Capstone
-% Cardstream
-% CashCow
-% CyberSource
-% eSec
-% eSelectPlus
-% Exact
-% iAuthorizer
-% IPaymentTPG
-% Jettis
-% LinkPoint
-% MerchantCommerce
-% Network1Financial
-% OCV
-% OpenECHO
-% PayConnect
-% PayflowPro
-% PaymentsGateway
-% PXPost
-% SecureHostingUPG
-% Skipjack
-% StGeorge
-% SurePay
-% TCLink
-% TransactionCentral
-% TransFirsteLink
-% VirtualNet
-% ) ) {
-%
-
- <OPTION VALUE="<% $module %>"><% $module %>
-% }
-
- </SELECT>
+<% include( 'elements/edit.html',
+ 'table' => 'payment_gateway',
+ 'name_singular' => 'Payment gateway',
+ 'viewall_dir' => 'browse',
+ 'fields' => $fields,
+ 'field_callback' => $field_callback,
+ 'labels' => {
+ 'gatewaynum' => 'Gateway #',
+ 'gateway_module' => 'Gateway',
+ 'gateway_username' => 'Username',
+ 'gateway_password' => 'Password',
+ 'gateway_action' => 'Action',
+ 'gateway_options' => 'Options: (Name/Value pairs, one element per line)',
+ 'gateway_callback_url' => 'Callback URL',
+ },
+ )
+%>
+
+
+<SCRIPT TYPE="text/javascript">
+ var gatewayNamespace = new Array;
+
+% foreach my $module ( sort { lc($a) cmp lc ($b) } keys %modules ) {
+ gatewayNamespace.push('<% $modules{$module} %>')
% }
+ // document.getElementById('gateway_namespace').value = gatewayNamespace[0];
+ function setNamespace(what) {
+ document.getElementById('gateway_namespace').value =
+ gatewayNamespace[what.selectedIndex];
+ }
- </TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Username: </TH>
- <TD><INPUT TYPE="text" NAME="gateway_username" VALUE="<% $payment_gateway->gateway_username %>"></TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Password: </TH>
- <TD><INPUT TYPE="text" NAME="gateway_password" VALUE="<% $payment_gateway->gateway_password %>"></TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Action: </TH>
- <TD>
- <SELECT NAME="gateway_action" SIZE=1>
-% foreach my $action (
-% 'Normal Authorization',
-% 'Authorization Only',
-% 'Authorization Only, Post Authorization',
-% ) {
-%
-
- <OPTION VALUE="<% $action %>"<% $action eq $payment_gateway->gateway_action ? ' SELECTED' : '' %>><% $action %>
-% }
-
- </SELECT>
- </TD>
-</TR>
-
-<TR>
- <TH ALIGN="right">Options: (Name/Value pairs, one element per line)</TH>
- <TD>
- <TEXTAREA ROWS="5" NAME="gateway_options"><% join("\r", $payment_gateway->options ) %></TEXTAREA>
- </TD>
-</TR>
-
-</TABLE>
-
-<BR><INPUT TYPE="submit" VALUE="<% $payment_gateway->gatewaynum ? "Apply changes" : "Add gateway" %>">
- </FORM>
-
-<% include('/elements/footer.html') %>
+</SCRIPT>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $payment_gateway;
-if ( $cgi->param('error') ) {
- $payment_gateway = new FS::payment_gateway ( {
- map { $_, scalar($cgi->param($_)) } fields('payment_gateway')
- } );
-} elsif ( $cgi->keywords ) {
- my($query) = $cgi->keywords;
- $query =~ /^(\d+)$/;
- $payment_gateway = qsearchs( 'payment_gateway', { 'gatewaynum' => $1 } );
-} else { #adding
- $payment_gateway = new FS::payment_gateway {};
-}
-my $action = $payment_gateway->gatewaynum ? 'Edit' : 'Add';
-#my $hashref = $payment_gateway->hashref;
+my %modules = (
+ '2CheckOut' => 'Business::OnlinePayment',
+ 'AuthorizeNet' => 'Business::OnlinePayment',
+ 'BankOfAmerica' => 'Business::OnlinePayment',
+ 'Beanstream' => 'Business::OnlinePayment',
+ 'Capstone' => 'Business::OnlinePayment',
+ 'Cardstream' => 'Business::OnlinePayment',
+ 'CashCow' => 'Business::OnlinePayment',
+ 'CyberSource' => 'Business::OnlinePayment',
+ 'eSec' => 'Business::OnlinePayment',
+ 'eSelectPlus' => 'Business::OnlinePayment',
+ 'Exact' => 'Business::OnlinePayment',
+ 'iAuthorizer' => 'Business::OnlinePayment',
+ 'Interswitchng' => 'Business::OnlineThirdPartyPayment',
+ 'IPaymentTPG' => 'Business::OnlinePayment',
+ 'Jettis' => 'Business::OnlinePayment',
+ 'LinkPoint' => 'Business::OnlinePayment',
+ 'MerchantCommerce' => 'Business::OnlinePayment',
+ 'Network1Financial' => 'Business::OnlinePayment',
+ 'OCV' => 'Business::OnlinePayment',
+ 'OpenECHO' => 'Business::OnlinePayment',
+ 'PayConnect' => 'Business::OnlinePayment',
+ 'PayflowPro' => 'Business::OnlinePayment',
+ 'PaymentsGateway' => 'Business::OnlinePayment',
+ 'PXPost' => 'Business::OnlinePayment',
+ 'SecureHostingUPG' => 'Business::OnlinePayment',
+ 'Skipjack' => 'Business::OnlinePayment',
+ 'StGeorge' => 'Business::OnlinePayment',
+ 'SurePay' => 'Business::OnlinePayment',
+ 'TCLink' => 'Business::OnlinePayment',
+ 'TransactionCentral' => 'Business::OnlinePayment',
+ 'TransFirsteLink' => 'Business::OnlinePayment',
+ 'VirtualNet' => 'Business::OnlinePayment',
+);
+
+my @actions = (
+ 'Normal Authorization',
+ 'Authorization Only',
+ 'Authorization Only, Post Authorization',
+ );
+
+my $fields = [
+ {
+ field => 'gateway_namespace',
+ type => 'hidden',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ $modules{$object->gateway_module}
+ || 'Business::OnlinePayment'
+ },
+ },
+ {
+ field => 'gateway_module',
+ type => 'select',
+ options => [ sort { lc($a) cmp lc ($b) } keys %modules ],
+ onchange => 'setNamespace',
+ },
+ 'gateway_username',
+ 'gateway_password',
+ {
+ field => 'gateway_action',
+ type => 'select',
+ options => \@actions,
+ },
+ 'gateway_callback_url',
+ {
+ field => 'gateway_options',
+ type => 'textarea',
+ curr_value_callback => sub { my($cgi, $object, $fref) = @_;
+ join("\r", $object->options );
+ },
+ },
+ ];
+
+my $field_callback = sub {
+ my ($cgi, $object, $field_hashref ) = @_;
+ if ($object->gatewaynum) {
+ if ( $field_hashref->{field} eq 'gateway_module' ) {
+ $field_hashref->{type} = 'fixed';
+ }
+ }
+};
</%init>
-%if ( $error ) {
-% $cgi->param('error', $error);
-<% $cgi->redirect(popurl(2). "payment_gateway.html?". $cgi->query_string ) %>
-%} else {
-<% $cgi->redirect(popurl(3). "browse/payment_gateway.html") %>
-%}
+<% include( 'elements/process.html',
+ 'table' => 'payment_gateway',
+ 'viewall_dir' => 'browse',
+ 'args_callback' => $args_callback,
+ )
+%>
<%init>
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-my $gatewaynum = $cgi->param('gatewaynum');
+my $args_callback = sub {
+ my ( $cgi, $new ) = @_;
-my $old = qsearchs('payment_gateway',{'gatewaynum'=>$gatewaynum}) if $gatewaynum;
+ my @options = split(/\r?\n/, $cgi->param('gateway_options') );
+ pop @options
+ if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
+ (@options)
+};
-my $new = new FS::payment_gateway ( {
- map {
- $_, scalar($cgi->param($_));
- } fields('payment_gateway')
-} );
-
-my @options = split(/\r?\n/, $cgi->param('gateway_options') );
-pop @options
- if scalar(@options) % 2 && $options[-1] =~ /^\s*$/;
-my %options = @options;
-
-my $error;
-if ( $gatewaynum ) {
- $error=$new->replace($old, \%options);
-} else {
- $error=$new->insert(\%options);
- $gatewaynum=$new->getfield('gatewaynum');
-}
</%init>
--- /dev/null
+<% include('tr-td-label.html', @_ ) %>
+
+ <TD <% $cell_style %>>
+
+ <TEXTAREA NAME = "<% $opt{field} %>"
+ ID = "<% $opt{id} %>"
+ <% $onchange %>
+ ><% $curr_value |h %></TEXTAREA>
+
+ </TD>
+
+</TR>
+
+<%init>
+
+my %opt = @_;
+
+my $onchange = $opt{'onchange'}
+ ? 'onChange="'. $opt{'onchange'}. '(this)"'
+ : '';
+
+my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : '';
+my $curr_value = $opt{'curr_value'};
+
+</%init>