X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fpayment_gateway.pm;h=3500bf9bcf29215a2cbdb349fe5eef20218d5546;hp=bc8b875c3d0f6c6a238fc9bec762e4a64f5ebcaf;hb=dc83512c36dc6bea2585abada4f88d714c600e55;hpb=40a7b3dc653e099f7bd0bd762b649b04c4432db2 diff --git a/FS/FS/payment_gateway.pm b/FS/FS/payment_gateway.pm index bc8b875c3..3500bf9bc 100644 --- a/FS/FS/payment_gateway.pm +++ b/FS/FS/payment_gateway.pm @@ -1,12 +1,10 @@ package FS::payment_gateway; +use base qw( FS::option_Common ); use strict; -use vars qw( @ISA $me $DEBUG ); -use FS::Record qw( qsearch qsearchs dbh ); -use FS::option_Common; -use FS::agent_payment_gateway; +use vars qw( $me $DEBUG ); +use FS::Record qw( qsearch dbh ); #qw( qsearch qsearchs dbh ); -@ISA = qw( FS::option_Common ); $me = '[ FS::payment_gateway ]'; $DEBUG=0; @@ -39,9 +37,9 @@ currently supported: =item gatewaynum - primary key -=item gateway_namespace - Business::OnlinePayment or Business::OnlineThirdPartyPayment +=item gateway_namespace - Business::OnlinePayment, Business::OnlineThirdPartyPayment, or Business::BatchPayment -=item gateway_module - Business::OnlinePayment:: module name +=item gateway_module - Business::OnlinePayment:: (or other) module name =item gateway_username - payment gateway username @@ -51,6 +49,21 @@ currently supported: =item disabled - Disabled flag, empty or 'Y' +=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 "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. 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 +auto-decline them, or null to do nothing. + +=item auto_resolve_days - For BatchPayment, the number of days to wait before +auto-resolving the batch. + =back =head1 METHODS @@ -116,16 +129,22 @@ sub check { || $self->ut_alpha('gateway_module') || $self->ut_enum('gateway_namespace', ['Business::OnlinePayment', 'Business::OnlineThirdPartyPayment', + 'Business::BatchPayment', ] ) || $self->ut_textn('gateway_username') || $self->ut_anything('gateway_password') || $self->ut_textn('gateway_callback_url') # a bit too permissive + || $self->ut_textn('gateway_cancel_url') || $self->ut_enum('disabled', [ '', 'Y' ] ) + || $self->ut_enum('auto_resolve_status', [ '', 'approve', 'reject' ]) + || $self->ut_numbern('auto_resolve_days') #|| $self->ut_textn('gateway_action') ; return $error if $error; - if ( $self->gateway_action ) { + if ( $self->gateway_namespace eq 'Business::BatchPayment' ) { + $self->gateway_action('Payment'); + } elsif ( $self->gateway_action ) { my @actions = split(/,\s*/, $self->gateway_action); $self->gateway_action( join( ',', map { /^(Normal Authorization|Authorization Only|Credit|Post Authorization)$/ @@ -140,8 +159,8 @@ sub check { } # 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->gateway_callback_url($self->gateway_callback_url. '/') + # if ( $self->gateway_callback_url && $self->gateway_callback_url !~ /\/$/ ); $self->SUPER::check; } @@ -150,13 +169,6 @@ sub check { Returns any agent overrides for this payment gateway. -=cut - -sub agent_payment_gateway { - my $self = shift; - qsearch('agent_payment_gateway', { 'gatewaynum' => $self->gatewaynum } ); -} - =item disable Disables this payment gateway: deletes all associated agent_payment_gateway @@ -198,6 +210,19 @@ sub disable { } +=item label + +Returns a semi-friendly label for the gateway. + +=cut + +sub label { + my $self = shift; + $self->gatewaynum . ': ' . + ($self->gateway_username ? $self->gateway_username . '@' : '') . + $self->gateway_module +} + =item namespace_description returns a friendly name for the namespace @@ -208,12 +233,209 @@ my %namespace2description = ( '' => 'Direct', 'Business::OnlinePayment' => 'Direct', 'Business::OnlineThirdPartyPayment' => 'Hosted', + 'Business::BatchPayment' => 'Batch', ); sub namespace_description { $namespace2description{shift->gateway_namespace} || 'Unknown'; } +=item batch_processor OPTIONS + +For BatchPayment gateways only. Returns a +L object to communicate with the +gateway. + +OPTIONS will be passed to the constructor, along with any gateway +options in the database for this L. Useful things +to include there may include 'input' and 'output' (to direct transport +to files), 'debug', and 'test_mode'. + +If the global 'business-batchpayment-test_transaction' flag is set, +'test_mode' will be forced on, and gateways that don't support test +mode will be disabled. + +=cut + +sub batch_processor { + local $@; + my $self = shift; + my %opt = @_; + my $batch = $opt{batch}; + my $output = $opt{output}; + die 'gateway '.$self->gatewaynum.' is not a Business::BatchPayment gateway' + unless $self->gateway_namespace eq 'Business::BatchPayment'; + eval "use Business::BatchPayment;"; + die "couldn't load Business::BatchPayment: $@" if $@; + + #false laziness with processor + foreach (qw(username password)) { + if (length($self->get("gateway_$_"))) { + $opt{$_} = $self->get("gateway_$_"); + } + } + + my $module = $self->gateway_module; + my $processor = eval { + Business::BatchPayment->create($module, $self->options, %opt) + }; + die "failed to create Business::BatchPayment::$module object: $@" + if $@; + + die "$module does not support test mode" + 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 ); + } +} + +=item default_gateway OPTIONS + +Class method. + +Returns default gateway (from business-onlinepayment conf) as a payment_gateway object. + +Accepts options + +conf - existing conf object + +nofatal - return blank instead of dying if no default gateway is configured + +method - if set to CHEK or ECHECK, returns object for business-onlinepayment-ach if available + +Before using this, be sure you wouldn't rather be using L or, +more likely, L. + +=cut + +# the standard settings from the config could be moved to a null agent +# agent_payment_gateway referenced payment_gateway + +sub default_gateway { + my ($self,%options) = @_; + + $options{'conf'} ||= new FS::Conf; + my $conf = $options{'conf'}; + + unless ( $conf->exists('business-onlinepayment') ) { + if ( $options{'nofatal'} ) { + return ''; + } else { + die "Real-time processing not enabled\n"; + } + } + + #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; + + my $payment_gateway = new FS::payment_gateway; + $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 ]); + return $payment_gateway; +} + +=item by_key_with_namespace GATEWAYNUM + +Like usual by_key, but makes sure namespace is set, +and dies if not found. + +=cut + +sub by_key_with_namespace { + my $self = shift; + my $payment_gateway = $self->by_key(@_); + die "payment_gateway not found" + unless $payment_gateway; + $payment_gateway->gateway_namespace('Business::OnlinePayment') + unless $payment_gateway->gateway_namespace; + return $payment_gateway; +} + +=item by_key_or_default OPTIONS + +Either returns the gateway specified by option gatewaynum, or the default gateway. + +Accepts the same options as L. + +Also ensures that the gateway_namespace has been set. + +=cut + +sub by_key_or_default { + my ($self,%options) = @_; + + if ($options{'gatewaynum'}) { + return $self->by_key_with_namespace($options{'gatewaynum'}); + } else { + return $self->default_gateway(%options); + } +} + +# if it weren't for the way gateway_namespace default is set, this method would not be necessary +# that should really go in check() with an accompanying upgrade, so we could just use qsearch safely, +# but currently short on time to test deeper changes... +# +# if no default gateway is set and nofatal is passed, first value returned is blank string +sub all_gateways { + my ($self,%options) = @_; + my @out; + foreach my $gatewaynum ('',( map {$_->gatewaynum} qsearch('payment_gateway') )) { + push @out, $self->by_key_or_default( %options, gatewaynum => $gatewaynum ); + } + return @out; +} + # _upgrade_data # # Used by FS::Upgrade to migrate to a new database.