summaryrefslogtreecommitdiff
path: root/FS/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2015-02-10 01:38:56 -0800
committerIvan Kohler <ivan@freeside.biz>2015-02-10 01:38:56 -0800
commita4d4d3df88b33a6db30b565921f6d62efb252351 (patch)
tree0ddfa8fe885dfe6776a0c074aed9e1e0735a7cd7 /FS/FS
parent6615733676adb431ae48c78ce24758fe571614c1 (diff)
multiple payment options, RT#23741
Diffstat (limited to 'FS/FS')
-rw-r--r--FS/FS/Conf.pm8
-rw-r--r--FS/FS/Schema.pm5
-rw-r--r--FS/FS/Template_Mixin.pm19
-rw-r--r--FS/FS/Upgrade.pm5
-rw-r--r--FS/FS/cust_main.pm127
-rw-r--r--FS/FS/cust_main/Location.pm22
-rw-r--r--FS/FS/cust_main/Search.pm44
-rw-r--r--FS/FS/cust_payby.pm442
-rw-r--r--FS/FS/o2m_Common.pm2
-rw-r--r--FS/FS/part_event/Action/cust_bill_realtime_lec.pm28
-rw-r--r--FS/FS/part_event/Condition/has_cust_payby_auto.pm43
-rw-r--r--FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm27
-rw-r--r--FS/FS/part_event/Condition/payby.pm44
-rw-r--r--FS/FS/part_event_condition.pm39
-rw-r--r--FS/FS/payby.pm27
15 files changed, 623 insertions, 259 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 2b959e6..4497916 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2249,7 +2249,7 @@ and customer address. Include units.',
'section' => 'self-service',
'description' => 'Acceptable payment types for the signup server',
'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL BILL COMP) ],
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK PREPAY PPAL ) ], # BILL COMP) ],
},
{
@@ -2652,13 +2652,13 @@ and customer address. Include units.',
'section' => 'billing',
'description' => 'Available payment types.',
'type' => 'selectmultiple',
- 'select_enum' => [ qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD MCHK PPAL COMP) ],
+ 'select_enum' => [ qw(CARD DCRD CHEK DCHK CASH WEST MCRD MCHK PPAL) ],
},
{
'key' => 'payby-default',
- 'section' => 'UI',
- 'description' => 'Default payment type. HIDE disables display of billing information and sets customers to BILL.',
+ 'section' => 'deprecated',
+ 'description' => 'Deprecated; in 4.x there is no longer the concept of a single "payment type". Used to indicate the default payment type. HIDE disables display of billing information and sets customers to BILL.',
'type' => 'select',
'select_enum' => [ '', qw(CARD DCRD CHEK DCHK BILL CASH WEST MCRD PPAL COMP HIDE) ],
},
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 133b6d8..54a4680 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1651,6 +1651,9 @@ sub tables_hashref {
'bill_locationnum', 'int', 'NULL', '', '', '',
'ship_locationnum', 'int', 'NULL', '', '', '',
'taxstatusnum', 'char', 'NULL', 32, '', '',
+ 'complimentary', 'char', 'NULL', 1, '', '',
+ 'po_number', 'varchar', 'NULL', $char_d, '', '',
+ 'invoice_attn', 'varchar', 'NULL', $char_d, '', '',
],
'primary_key' => 'custnum',
'unique' => [ [ 'agentnum', 'agent_custid' ] ],
@@ -1699,7 +1702,7 @@ sub tables_hashref {
'columns' => [
'custpaybynum', 'serial', '', '', '', '',
'custnum', 'int', '', '', '', '',
- 'weight', 'int', '', '', '', '',
+ 'weight', 'int', 'NULL', '', '', '',
'payby', 'char', '', 4, '', '',
'payinfo', 'varchar', 'NULL', 512, '', '',
'cardtype', 'varchar', 'NULL', $char_d, '', '',
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 95d001e..fe484a4 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -316,9 +316,6 @@ sub print_generic {
unless $format =~ /^(latex|html|template)$/;
my $cust_main = $self->cust_main || $self->prospect_main;
- $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') )
- unless $cust_main->payname
- && $cust_main->payby !~ /^(CARD|DCRD|CHEK|DCHK)$/;
my $locale = $params{'locale'} || $cust_main->locale;
@@ -565,9 +562,11 @@ sub print_generic {
'custnum' => $cust_main->display_custnum,
'prospectnum' => $cust_main->prospectnum,
'agent_custid' => &$escape_function($cust_main->agent_custid),
- ( map { $_ => &$escape_function($cust_main->$_()) } qw(
- payname company address1 address2 city state zip fax
- )),
+ ( map { $_ => &$escape_function($cust_main->$_()) }
+ qw( company address1 address2 city state zip fax )
+ ),
+ 'payname' => &$escape_function( $cust_main->invoice_attn
+ || $cust_main->contact_firstlast ),
#global config
'ship_enable' => $conf->exists('invoice-ship_address'),
@@ -655,10 +654,10 @@ sub print_generic {
my @address = ();
$invoice_data{'address'} = \@address;
push @address,
- $cust_main->payname.
- ( ( $cust_main->payby eq 'BILL' ) && $cust_main->payinfo
- ? " (P.O. #". $cust_main->payinfo. ")"
- : ''
+ $invoice_data{'payname'}.
+ ( $cust_main->po_number
+ ? " (P.O. #". $cust_main->po_number. ")"
+ : ''
)
;
push @address, $cust_main->company
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index d05b309..263be80 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -309,7 +309,10 @@ sub upgrade_data {
tie my %hash, 'Tie::IxHash',
- #cust_main (remove paycvv from history)
+ #payby conditions to new ones
+ 'part_event_condition' => [],
+
+ #cust_main (remove paycvv from history, locations, cust_payby, etc)
'cust_main' => [],
#contact -> cust_contact / prospect_contact
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index cd675f9..d38f3d0 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -76,6 +76,7 @@ use FS::Locales;
use FS::upgrade_journal;
use FS::sales;
use FS::cust_payby;
+use FS::contact;
# 1 is mostly method/subroutine entry and options
# 2 traces progress of some operations
@@ -95,8 +96,6 @@ our $ucfirst_nowarn = 0;
our @encrypted_fields = ('payinfo', 'paycvv');
sub nohistory_fields { ('payinfo', 'paycvv'); }
-our @paytypes = ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
-
our $conf;
#ask FS::UID to run this stuff for us later
#$FS::UID::callback{'FS::cust_main'} = sub {
@@ -331,7 +330,7 @@ invoicing_list destination to the newly-created svc_acct. Here's an example:
$cust_main->insert( {}, [ $email, 'POST' ] );
Currently available options are: I<depend_jobnum>, I<noexport>,
-I<tax_exemption> and I<prospectnum>.
+I<tax_exemption>, I<prospectnum>, I<contact> and I<contact_params>.
If I<depend_jobnum> is set, all provisioning jobs will have a dependancy
on the supplied jobnum (they will not run until the specific job completes).
@@ -351,6 +350,14 @@ If I<prospectnum> is set, moves contacts and locations from that prospect.
If I<contact> is set to an arrayref of FS::contact objects, inserts those
new contacts with this new customer.
+If I<contact_params> is set to a hashref of CGI parameters (and I<contact> is
+unset), inserts those new contacts with this new customer. Handles CGI
+paramaters for an "m2" multiple entry field as passed by edit/cust_main.cgi
+
+If I<cust_payby_params> is set to a hashref o fCGI parameters, inserts those
+new stored payment records with this new customer. Handles CGI parameters
+for an "m2" multiple entry field as passed by edit/cust_main.cgi
+
=cut
sub insert {
@@ -378,7 +385,7 @@ sub insert {
my $payby = '';
if ( $self->payby eq 'PREPAY' ) {
- $self->payby('BILL');
+ $self->payby(''); #'BILL');
$prepay_identifier = $self->payinfo;
$self->payinfo('');
@@ -403,7 +410,7 @@ sub insert {
} elsif ( $self->payby =~ /^(CASH|WEST|MCRD|MCHK|PPAL)$/ ) {
$payby = $1;
- $self->payby('BILL');
+ $self->payby(''); #'BILL');
$amount = $self->paid;
}
@@ -557,8 +564,10 @@ sub insert {
}
- my $contact = delete $options{'contact'};
- if ( $contact ) {
+ warn " setting contacts\n"
+ if $DEBUG > 1;
+
+ if ( my $contact = delete $options{'contact'} ) {
foreach my $c ( @$contact ) {
$c->custnum($self->custnum);
@@ -570,6 +579,34 @@ sub insert {
}
+ } elsif ( my $contact_params = delete $options{'contact_params'} ) {
+
+ my $error = $self->process_o2m( 'table' => 'contact',
+ 'fields' => FS::contact->cgi_contact_fields,
+ 'params' => $contact_params,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " setting cust_payby\n"
+ if $DEBUG > 1;
+
+ if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
+
+ my $error = $self->process_o2m(
+ 'table' => 'cust_payby',
+ 'fields' => FS::cust_payby->cgi_cust_payby_fields,
+ 'params' => $cust_payby_params,
+ 'hash_callback' => \&FS::cust_payby::cgi_hash_callback,
+ );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
}
warn " setting cust_main_exemption\n"
@@ -1122,10 +1159,9 @@ sub delete {
#cust_tax_adjustment in financials?
#cust_pay_pending? ouch
- #cust_recon?
foreach my $table (qw(
cust_main_invoice cust_main_exemption cust_tag cust_attachment contact
- cust_location cust_main_note cust_tax_adjustment
+ cust_payby cust_location cust_main_note cust_tax_adjustment
cust_pay_void cust_pay_batch queue cust_tax_exempt
)) {
foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) {
@@ -1250,13 +1286,10 @@ sub replace {
if $DEBUG;
my $curuser = $FS::CurrentUser::CurrentUser;
- if ( $self->payby eq 'COMP'
- && $self->payby ne $old->payby
- && ! $curuser->access_right('Complimentary customer')
- )
- {
- return "You are not permitted to create complimentary accounts.";
- }
+ return "You are not permitted to create complimentary accounts."
+ if $self->complimentary eq 'Y'
+ && $self->complimentary ne $old->complimentary
+ && ! $curuser->access_right('Complimentary customer');
local($ignore_expired_card) = 1
if $old->payby =~ /^(CARD|DCRD)$/
@@ -1285,8 +1318,8 @@ sub replace {
my $dbh = dbh;
for my $l (qw(bill_location ship_location)) {
- my $old_loc = $old->$l;
- my $new_loc = $self->$l;
+ #my $old_loc = $old->$l;
+ my $new_loc = $self->$l or next;
# find the existing location if there is one
$new_loc->set('custnum' => $self->custnum);
@@ -1403,21 +1436,19 @@ sub replace {
}
- if ( $self->payby =~ /^(CARD|CHEK|LECB)$/
- && ( ( $self->get('payinfo') ne $old->get('payinfo')
- && $self->get('payinfo') !~ /^99\d{14}$/
- )
- || grep { $self->get($_) ne $old->get($_) } qw(paydate payname)
- )
- )
- {
+ if ( my $cust_payby_params = delete $options{'cust_payby_params'} ) {
- # card/check/lec info has changed, want to retry realtime_ invoice events
- my $error = $self->retry_realtime;
+ my $error = $self->process_o2m(
+ 'table' => 'cust_payby',
+ 'fields' => FS::cust_payby->cgi_cust_payby_fields,
+ 'params' => $cust_payby_params,
+ 'hash_callback' => \&FS::cust_payby::cgi_hash_callback,
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+
}
unless ( $import || $skip_fuzzyfiles ) {
@@ -1555,6 +1586,8 @@ sub check {
|| $self->ut_flag('message_noemail')
|| $self->ut_enum('locale', [ '', FS::Locales->locales ])
|| $self->ut_currencyn('currency')
+ || $self->ut_alphan('po_number')
+ || $self->ut_enum('complimentary', [ '', 'Y' ])
;
foreach (qw(company ship_company)) {
@@ -1809,6 +1842,11 @@ sub check {
}
+ return "You are not permitted to create complimentary accounts."
+ if ! $self->custnum
+ && $self->complimentary eq 'Y'
+ && ! $FS::CurrentUser->CurrentUser->access_right('Complimentary customer');
+
if ( $self->paydate eq '' || $self->paydate eq '-' ) {
return "Expiration date required"
# shouldn't payinfo_check do this?
@@ -1947,7 +1985,7 @@ sub cust_payby {
qsearch({
'table' => 'cust_payby',
'hashref' => { 'custnum' => $self->custnum },
- 'order_by' => 'ORDER BY weight ASC',
+ 'order_by' => "ORDER BY payby IN ('CARD','CHEK') DESC, weight ASC",
});
}
@@ -4816,18 +4854,31 @@ sub _upgrade_data { #class method
while (my $cust_main = $search->fetch) {
- my $cust_payby = new FS::cust_payby {
- 'custnum' => $cust_main->custnum,
- 'weight' => 1,
- map { $_ => $cust_main->$_(); } @payfields
- };
+ unless ( $cust_main->payby =~ /^(BILL|COMP)$/ ) {
- my $error = $cust_payby->insert;
- die $error if $error;
+ my $cust_payby = new FS::cust_payby {
+ 'custnum' => $cust_main->custnum,
+ 'weight' => 1,
+ map { $_ => $cust_main->$_(); } @payfields
+ };
+
+ my $error = $cust_payby->insert;
+ die $error if $error;
+
+ }
+
+ $cust_main->complimentary('Y') if $cust_main->payby eq 'COMP';
+
+ $cust_main->invoice_attn( $cust_main->payname )
+ if $cust_main->payby eq 'BILL' && $cust_main->payname;
+ $cust_main->po_number( $cust_main->payinfo )
+ if $cust_main->payby eq 'BILL' && $cust_main->payinfo;
$cust_main->setfield($_, '') foreach @payfields;
- $error = $cust_main->replace;
- die $error if $error;
+ my $error = $cust_main->replace;
+ die "Error upgradging payment information for custnum ".
+ $cust_main->custnum. ": $error"
+ if $error;
};
diff --git a/FS/FS/cust_main/Location.pm b/FS/FS/cust_main/Location.pm
index be375dd..3cb73ff 100644
--- a/FS/FS/cust_main/Location.pm
+++ b/FS/FS/cust_main/Location.pm
@@ -74,9 +74,11 @@ sub bill_location {
$self->hashref->{bill_location}
||= FS::cust_location->by_key($self->bill_locationnum)
# degraded mode--let the system keep running during upgrades
- || FS::cust_location->new({
- map { $_ => $self->get($_) } @location_fields
- })
+ || ( $self->get('address1')
+ && FS::cust_location->new({
+ map { $_ => $self->get($_) } @location_fields
+ })
+ );
}
=item ship_location
@@ -89,9 +91,17 @@ sub ship_location {
my $self = shift;
$self->hashref->{ship_location}
||= FS::cust_location->by_key($self->ship_locationnum)
- || FS::cust_location->new({
- map { $_ => $self->get('ship_'.$_) || $self->get($_) } @location_fields
- })
+ # degraded mode--let the system keep running during upgrades
+ || ( $self->get('ship_address1')
+ ? FS::cust_location->new({
+ map { $_ => $self->get('ship_'.$_) } @location_fields
+ })
+ : $self->get('address1')
+ ? FS::cust_location->new({
+ map { $_ => $self->get($_) } @location_fields
+ })
+ : ''
+ );
}
diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm
index f0a7d41..097f2fb 100644
--- a/FS/FS/cust_main/Search.pm
+++ b/FS/FS/cust_main/Search.pm
@@ -608,14 +608,6 @@ listref of start date, end date
listref of start date, end date
-=item payby
-
-listref
-
-=item paydate_year
-
-=item paydate_month
-
=item current_balance
listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance'))
@@ -646,7 +638,6 @@ sub search {
'status' => '',
'address' => '',
'zip' => '',
- 'paydate_year' => '',
'invoice_terms' => '',
'custbatch' => '',
%$params
@@ -911,40 +902,6 @@ sub search {
}
###
- # payby
- ###
-
- if ( $params->{'payby'} ) {
-
- my @payby = ref( $params->{'payby'} )
- ? @{ $params->{'payby'} }
- : ( $params->{'payby'} );
-
- @payby = grep /^([A-Z]{4})$/, @payby;
- my $in_payby = 'IN(' . join(',', map {"'$_'"} @payby) . ')';
- push @where, "EXISTS( SELECT 1 FROM cust_payby WHERE payby $in_payby ".
- "AND cust_payby.custnum = cust_main.custnum)"
- if @payby;
- }
-
- ###
- # paydate_year / paydate_month
- ###
-
- if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) {
- my $year = $1;
- $params->{'paydate_month'} =~ /^(\d\d?)$/
- or die "paydate_year without paydate_month?";
- my $month = $1;
-
- push @where,
- 'paydate IS NOT NULL',
- "paydate != ''",
- "CAST(paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )"
-;
- }
-
- ###
# invoice terms
###
@@ -1134,7 +1091,6 @@ sub search {
'extra_headers' => \@extra_headers,
'extra_fields' => \@extra_fields,
};
- #warn Data::Dumper::Dumper($sql_query);
$sql_query;
}
diff --git a/FS/FS/cust_payby.pm b/FS/FS/cust_payby.pm
index ad3d80a..a65a171 100644
--- a/FS/FS/cust_payby.pm
+++ b/FS/FS/cust_payby.pm
@@ -1,26 +1,23 @@
package FS::cust_payby;
+use base qw( FS::payinfo_Mixin FS::cust_main_Mixin FS::Record );
use strict;
-use base qw( FS::payinfo_Mixin FS::Record );
-use FS::UID;
-use FS::Record qw( qsearchs ); #qsearch;
-use FS::payby;
-use FS::cust_main;
use Business::CreditCard qw( validate cardtype );
+use FS::UID qw( dbh );
use FS::Msgcat qw( gettext );
+use FS::Record; #qw( qsearch qsearchs );
+use FS::payby;
+use FS::cust_main;
+use FS::banned_pay;
-use vars qw( $conf @encrypted_fields
- $ignore_expired_card $ignore_banned_card
- $ignore_invalid_card
- );
-
-@encrypted_fields = ('payinfo', 'paycvv');
+our @encrypted_fields = ('payinfo', 'paycvv');
sub nohistory_fields { ('payinfo', 'paycvv'); }
-$ignore_expired_card = 0;
-$ignore_banned_card = 0;
-$ignore_invalid_card = 0;
+our $ignore_expired_card = 0;
+our $ignore_banned_card = 0;
+our $ignore_invalid_card = 0;
+our $conf;
install_callback FS::UID sub {
$conf = new FS::Conf;
#yes, need it for stuff below (prolly should be cached)
@@ -141,15 +138,44 @@ otherwise returns false.
=cut
-# the insert method can be inherited from FS::Record
+sub insert {
+ my $self = shift;
-=item delete
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
-Delete this record from the database.
+ if ( $self->payby =~ /^(CARD|CHEK)$/ ) {
+ # new auto card/check info, want to retry realtime_ invoice events
+ # (new customer? that's okay, they won't have any)
+ my $error = $self->cust_main->retry_realtime;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
-=cut
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
-# the delete method can be inherited from FS::Record
+}
+
+=item delete
+
+Delete this record from the database.
=item replace OLD_RECORD
@@ -158,7 +184,87 @@ returns the error, otherwise returns false.
=cut
-# the replace method can be inherited from FS::Record
+sub replace {
+ my $self = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $self->replace_old;
+
+ if ( length($old->paycvv) && $self->paycvv =~ /^\s*[\*x]*\s*$/ ) {
+ $self->paycvv($old->paycvv);
+ }
+
+ if ( $self->payby =~ /^(CARD|DCRD)$/
+ && ( $self->payinfo =~ /xx/
+ || $self->payinfo =~ /^\s*N\/A\s+\(tokenized\)\s*$/
+ )
+ )
+ {
+warn $self->payinfo;
+warn $old->payinfo;
+ $self->payinfo($old->payinfo);
+
+ } elsif ( $self->payby =~ /^(CHEK|DCHK)$/ && $self->payinfo =~ /xx/ ) {
+ #fix for #3085 "edit of customer's routing code only surprisingly causes
+ #nothing to happen...
+ # this probably won't do the right thing when we don't have the
+ # public key (can't actually get the real $old->payinfo)
+ my($new_account, $new_aba) = split('@', $self->payinfo);
+ my($old_account, $old_aba) = split('@', $old->payinfo);
+ $new_account = $old_account if $new_account =~ /xx/;
+ $new_aba = $old_aba if $new_aba =~ /xx/;
+ $self->payinfo($new_account.'@'.$new_aba);
+ }
+
+ local($ignore_expired_card) = 1
+ if $old->payby =~ /^(CARD|DCRD)$/
+ && $self->payby =~ /^(CARD|DCRD)$/
+ && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+ local($ignore_banned_card) = 1
+ if ( $old->payby =~ /^(CARD|DCRD)$/ && $self->payby =~ /^(CARD|DCRD)$/
+ || $old->payby =~ /^(CHEK|DCHK)$/ && $self->payby =~ /^(CHEK|DCHK)$/ )
+ && ( $old->payinfo eq $self->payinfo || $old->paymask eq $self->paymask );
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error = $self->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $self->payby =~ /^(CARD|CHEK)$/
+ && ( ( $self->get('payinfo') ne $old->get('payinfo')
+ && $self->get('payinfo') !~ /^99\d{14}$/
+ )
+ || grep { $self->get($_) ne $old->get($_) } qw(paydate payname)
+ )
+ )
+ {
+
+ # card/check/lec info has changed, want to retry realtime_ invoice events
+ my $error = $self->cust_main->retry_realtime;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
=item check
@@ -174,7 +280,7 @@ sub check {
my $error =
$self->ut_numbern('custpaybynum')
|| $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
- || $self->ut_number('weight')
+ || $self->ut_numbern('weight')
#encrypted #|| $self->ut_textn('payinfo')
#encrypted #|| $self->ut_textn('paycvv')
# || $self->ut_textn('paymask') #XXX something
@@ -207,7 +313,7 @@ sub check {
my $payinfo = $self->payinfo;
$payinfo =~ s/\D//g;
$payinfo =~ /^(\d{13,16}|\d{8,9})$/
- or return gettext('invalid_card'); # . ": ". $self->payinfo;
+ or return gettext('invalid_card'); #. ": ". $self->payinfo;
$payinfo = $1;
$self->payinfo($payinfo);
validate($payinfo)
@@ -308,52 +414,23 @@ sub check {
}
}
- } elsif ( $self->payby eq 'LECB' ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\D//g;
- $payinfo =~ /^1?(\d{10})$/ or return 'invalid btn billing telephone number';
- $payinfo = $1;
- $self->payinfo($payinfo);
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'BILL' ) {
-
- $error = $self->ut_textn('payinfo');
- return "Illegal P.O. number: ". $self->payinfo if $error;
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'COMP' ) {
-
- my $curuser = $FS::CurrentUser::CurrentUser;
- if ( ! $self->custnum
- && ! $curuser->access_right('Complimentary customer')
- )
- {
- return "You are not permitted to create complimentary accounts."
- }
-
- $error = $self->ut_textn('payinfo');
- return "Illegal comp account issuer: ". $self->payinfo if $error;
- $self->paycvv('');
-
- } elsif ( $self->payby eq 'PREPAY' ) {
-
- my $payinfo = $self->payinfo;
- $payinfo =~ s/\W//g; #anything else would just confuse things
- $self->payinfo($payinfo);
- $error = $self->ut_alpha('payinfo');
- return "Illegal prepayment identifier: ". $self->payinfo if $error;
- return "Unknown prepayment identifier"
- unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
- $self->paycvv('');
+# } elsif ( $self->payby eq 'PREPAY' ) {
+#
+# my $payinfo = $self->payinfo;
+# $payinfo =~ s/\W//g; #anything else would just confuse things
+# $self->payinfo($payinfo);
+# $error = $self->ut_alpha('payinfo');
+# return "Illegal prepayment identifier: ". $self->payinfo if $error;
+# return "Unknown prepayment identifier"
+# unless qsearchs('prepay_credit', { 'identifier' => $self->payinfo } );
+# $self->paycvv('');
}
if ( $self->paydate eq '' || $self->paydate eq '-' ) {
return "Expiration date required"
# shouldn't payinfo_check do this?
- unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD|MCHK|PPAL)$/;
+ unless $self->payby =~ /^(CHEK|DCHK)$/;
$self->paydate('');
} else {
my( $m, $y );
@@ -400,6 +477,247 @@ sub check {
$self->SUPER::check;
}
+sub _banned_pay_hashref {
+ my $self = shift;
+
+ my %payby2ban = (
+ 'CARD' => 'CARD',
+ 'DCRD' => 'CARD',
+ 'CHEK' => 'CHEK',
+ 'DCHK' => 'CHEK'
+ );
+
+ {
+ 'payby' => $payby2ban{$self->payby},
+ 'payinfo' => $self->payinfo,
+ #don't ever *search* on reason! #'reason' =>
+ };
+}
+
+=item paydate_mon_year
+
+Returns a two element list consisting of the paydate month and year.
+
+=cut
+
+sub paydate_mon_year {
+ my $self = shift;
+
+ my $date = $self->paydate; # || '12-2037';
+
+ #false laziness w/elements/select-month_year.html
+ if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format
+ ( $2, $1 );
+ } elsif ( $date =~ /^(\d{1,2})-(\d{1,2}-)?(\d{4}$)/ ) {
+ ( $1, $3 );
+ } else {
+ warn "unrecognized expiration date format: $date";
+ ( '', '' );
+ }
+
+}
+
+=item realtime_bop
+
+=cut
+
+sub realtime_bop {
+ my( $self, %opt ) = @_;
+
+ $opt{$_} = $self->$_() for qw( payinfo payname paydate );
+
+ if ( $self->locationnum ) {
+ my $cust_location = $self->cust_location;
+ $opt{$_} = $cust_location->$_() for qw( address1 address2 city state zip );
+ }
+
+ $self->cust_main->realtime_bop({
+ 'method' => FS::payby->payby2bop( $self->payby ),
+ %opt,
+ });
+
+}
+
+=item paytypes
+
+Returns a list of valid values for the paytype field (bank account type for
+electronic check payment).
+
+=cut
+
+sub paytypes {
+ #my $class = shift;
+
+ ('', 'Personal checking', 'Personal savings', 'Business checking', 'Business savings');
+}
+
+=item cgi_cust_payby_fields
+
+Returns the field names used in the web interface (including some pseudo-fields).
+
+=cut
+
+sub cgi_cust_payby_fields {
+ #my $class = shift;
+ [qw( payby payinfo paydate_month paydate_year paycvv payname weight
+ payinfo1 payinfo2 payinfo3 paytype paystate )];
+}
+
+=item cgi_hash_callback HASHREF
+
+Subroutine (not a class or object method). Processes a hash reference
+of web interface contet (transfers the data from pseudo-fields to real fields).
+
+=cut
+
+sub cgi_hash_callback {
+ my $hashref = shift;
+
+ my %noauto = (
+ 'CARD' => 'DCRD',
+ 'CHEK' => 'DCHK',
+ );
+ $hashref->{payby} = $noauto{$hashref->{payby}}
+ if ! $hashref->{weight} && exists $noauto{$hashref->{payby}};
+
+ if ( $hashref->{payby} =~ /^(CHEK|DCHK)$/ ) {
+
+ unless ( grep $hashref->{$_}, qw( payinfo1 payinfo2 payinfo3 payname ) ) {
+ %$hashref = ();
+ return;
+ }
+
+ $hashref->{payinfo} = $hashref->{payinfo1}. '@';
+ $hashref->{payinfo} .= $hashref->{payinfo3}.'.'
+ if $conf->config('echeck-country') eq 'CA';
+ $hashref->{payinfo} .= $hashref->{'payinfo2'};
+
+ $hashref->{payname} .= $hashref->{'payname_CHEK'};
+
+ } elsif ( $hashref->{payby} =~ /^(CARD|DCRD)$/ ) {
+
+ unless ( grep $hashref->{$_}, qw( payinfo paycvv payname ) ) {
+ %$hashref = ();
+ return;
+ }
+
+ }
+
+ $hashref->{paydate}= $hashref->{paydate_month}. '-'. $hashref->{paydate_year};
+
+}
+
+=item search_sql
+
+Class method.
+
+Returns a qsearch hash expression to search for parameters specified in HASHREF.
+Valid paramters are:
+
+=over 4
+
+=item payby
+
+listref
+
+=item paydate_year
+
+=item paydate_month
+
+
+=back
+
+=cut
+
+sub search_sql {
+ my ($class, $params) = @_;
+
+ my @where = ();
+ my $orderby;
+
+ # initialize these to prevent warnings
+ $params = {
+ 'paydate_year' => '',
+ %$params
+ };
+
+ ###
+ # payby
+ ###
+
+ if ( $params->{'payby'} ) {
+
+ my @payby = ref( $params->{'payby'} )
+ ? @{ $params->{'payby'} }
+ : ( $params->{'payby'} );
+
+ @payby = grep /^([A-Z]{4})$/, @payby;
+ my $in_payby = 'IN(' . join(',', map {"'$_'"} @payby) . ')';
+ push @where, "cust_payby.payby $in_payby"
+ if @payby;
+ }
+
+ ###
+ # paydate_year / paydate_month
+ ###
+
+ if ( $params->{'paydate_year'} =~ /^(\d{4})$/ ) {
+ my $year = $1;
+ $params->{'paydate_month'} =~ /^(\d\d?)$/
+ or die "paydate_year without paydate_month?";
+ my $month = $1;
+
+ push @where,
+ 'cust_payby.paydate IS NOT NULL',
+ "cust_payby.paydate != ''",
+ "CAST(cust_payby.paydate AS timestamp) < CAST('$year-$month-01' AS timestamp )"
+;
+ }
+ ##
+ # setup queries, subs, etc. for the search
+ ##
+
+ $orderby ||= 'ORDER BY custnum';
+
+ # here is the agent virtualization
+ push @where,
+ $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main');
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $addl_from = ' LEFT JOIN cust_main USING ( custnum ) ';
+ # always make address fields available in results
+ for my $pre ('bill_', 'ship_') {
+ $addl_from .=
+ ' LEFT JOIN cust_location AS '.$pre.'location '.
+ 'ON (cust_main.'.$pre.'locationnum = '.$pre.'location.locationnum) ';
+ }
+
+ my $count_query = "SELECT COUNT(*) FROM cust_payby $addl_from $extra_sql";
+
+ my @select = ( 'cust_payby.*',
+ #'cust_main.custnum',
+ # there's a good chance that we'll need these
+ 'cust_main.bill_locationnum',
+ 'cust_main.ship_locationnum',
+ FS::UI::Web::cust_sql_fields($params->{'cust_fields'}),
+ );
+
+ my $select = join(', ', @select);
+
+ my $sql_query = {
+ 'table' => 'cust_payby',
+ 'select' => $select,
+ 'addl_from' => $addl_from,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'count_query' => $count_query,
+ };
+ $sql_query;
+
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/o2m_Common.pm b/FS/FS/o2m_Common.pm
index d237bef..4f6d2e7 100644
--- a/FS/FS/o2m_Common.pm
+++ b/FS/FS/o2m_Common.pm
@@ -103,6 +103,7 @@ sub process_o2m {
map { $_ => $opt{'params'}->{$add_param."_$_"} }
@{ $opt{'fields'} }
);
+ &{ $opt{'hash_callback'} }( \%hash ) if $opt{'hash_callback'};
#next unless grep { $_ =~ /\S/ } values %hash;
my $new_obj = "FS::$table"->new( { %$hashref, %hash } );
@@ -117,6 +118,7 @@ sub process_o2m {
my %hash = map { $_ => $opt{'params'}->{$add_param."_$_"} }
@{ $opt{'fields'} };
+ &{ $opt{'hash_callback'} }( \%hash ) if $opt{'hash_callback'};
next unless grep { $_ =~ /\S/ } values %hash;
my $add_obj = "FS::$table"->new( { %$hashref, %hash } );
diff --git a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
deleted file mode 100644
index cd03ddc..0000000
--- a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm
+++ /dev/null
@@ -1,28 +0,0 @@
-package FS::part_event::Action::cust_bill_realtime_lec;
-
-use strict;
-use base qw( FS::part_event::Action );
-
-sub description {
- #'Run phone bill ("LEC") billing with a <a href="http://420.am/business-onlinepayment/">Business::OnlinePayment</a> realtime gateway';
- 'Run phone bill ("LEC") billing with a Business::OnlinePayment realtime gateway';
-}
-
-sub deprecated { 1; }
-
-sub eventtable_hashref {
- { 'cust_bill' => 1 };
-}
-
-sub default_weight { 30; }
-
-sub do_action {
- my( $self, $cust_bill ) = @_;
-
- #my $cust_main = $self->cust_main($cust_bill);
- my $cust_main = $cust_bill->cust_main;
-
- $cust_bill->realtime_lec;
-}
-
-1;
diff --git a/FS/FS/part_event/Condition/has_cust_payby_auto.pm b/FS/FS/part_event/Condition/has_cust_payby_auto.pm
new file mode 100644
index 0000000..edfb7ec
--- /dev/null
+++ b/FS/FS/part_event/Condition/has_cust_payby_auto.pm
@@ -0,0 +1,43 @@
+package FS::part_event::Condition::has_cust_payby_auto;
+
+use strict;
+use Tie::IxHash;
+use FS::payby;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Customer has automatic payment information';
+}
+
+tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2shortname;
+delete $payby{'DCRD'};
+delete $payby{'DCHK'};
+
+sub option_fields {
+ (
+ 'payby' => {
+ label => 'Has automatic payment info',
+ type => 'select',
+ options => [ keys %payby ],
+ option_labels => \%payby,
+ },
+ );
+}
+
+sub condition {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ scalar( qsearch({
+ 'table' => 'cust_payby',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ 'payby' => $self->option('payby')
+ },
+ 'order_by' => 'LIMIT 1',
+ }) );
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm b/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm
new file mode 100644
index 0000000..6655a63
--- /dev/null
+++ b/FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm
@@ -0,0 +1,27 @@
+package FS::part_event::Condition::hasnt_cust_payby_auto;
+
+use strict;
+use Tie::IxHash;
+
+use base qw( FS::part_event::Condition );
+
+sub description {
+ 'Customer does not have automatic payment information';
+}
+
+sub condition {
+ my( $self, $object ) = @_;
+
+ my $cust_main = $self->cust_main($object);
+
+ ! scalar( qsearch({
+ 'table' => 'cust_payby',
+ 'hashref' => { 'custnum' => $cust_main->custnum,
+ },
+ 'extra_sql' => "AND payby IN ( 'CARD', 'CHEK' )",
+ 'order_by' => 'LIMIT 1',
+ }) );
+
+}
+
+1;
diff --git a/FS/FS/part_event/Condition/payby.pm b/FS/FS/part_event/Condition/payby.pm
deleted file mode 100644
index 16bf480..0000000
--- a/FS/FS/part_event/Condition/payby.pm
+++ /dev/null
@@ -1,44 +0,0 @@
-package FS::part_event::Condition::payby;
-
-use strict;
-use Tie::IxHash;
-use FS::payby;
-
-use base qw( FS::part_event::Condition );
-
-sub description {
- #'customer payment types: ';
- 'Customer payment type';
-}
-
-#something like this
-tie my %payby, 'Tie::IxHash', FS::payby->cust_payby2longname;
-sub option_fields {
- (
- 'payby' => {
- label => 'Customer payment type',
- #type => 'select-multiple',
- type => 'checkbox-multiple',
- options => [ keys %payby ],
- option_labels => \%payby,
- },
- );
-}
-
-sub condition {
- my( $self, $object ) = @_;
-
- my $cust_main = $self->cust_main($object);
-
- my $hashref = $self->option('payby') || {};
- $hashref->{ $cust_main->payby };
-
-}
-
-sub condition_sql {
- my( $self, $table ) = @_;
-
- 'cust_main.payby IN '. $self->condition_sql_option_option('payby');
-}
-
-1;
diff --git a/FS/FS/part_event_condition.pm b/FS/FS/part_event_condition.pm
index ac2ee82..200049d 100644
--- a/FS/FS/part_event_condition.pm
+++ b/FS/FS/part_event_condition.pm
@@ -354,6 +354,45 @@ sub order_conditions_sql {
}
+sub _upgrade_data { #class method
+ my ($class, %opts) = @_;
+
+ foreach my $part_event_condition (
+ qsearch('part_event_condition', { 'conditionname' => 'payby' } )
+ ) {
+
+ my $payby = $part_event_condition->option('payby');
+
+ if ( scalar( keys %$payby ) == 1 ) {
+
+ if ( $payby->{'CARD'} ) {
+
+ $part_event_condition->conditionname('has_cust_payby_auto');
+
+ } elsif ( $payby->{'CHEK'} ) {
+
+ $part_event_condition->conditionname('has_cust_payby_auto');
+
+ }
+
+ } elsif ( $payby->{'BILL'} && ! $payby->{'CARD'} && ! $payby->{'CHEK'} ) {
+
+ $part_event_condition->conditionname('hasnt_cust_payby_auto');
+
+ } else {
+
+ die 'Unable to automatically convert payby condition for event #'.
+ $part_event_condition->eventpart. "\n";
+
+ }
+
+ my $error = $part_event_condition->replace;
+ die $error if $error;
+
+ }
+
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm
index c4aa1b1..13423c4 100644
--- a/FS/FS/payby.pm
+++ b/FS/FS/payby.pm
@@ -5,7 +5,6 @@ use vars qw(%hash %payby2bop);
use Tie::IxHash;
use Business::CreditCard;
-
=head1 NAME
FS::payby - Object methods for payment type records
@@ -39,9 +38,8 @@ Payment types.
=cut
# paybys can be any/all of:
-# - a customer payment type (cust_main.payby)
+# - a customer saved payment type (cust_payby.payby)
# - a payment or refund type (cust_pay.payby, cust_pay_batch.payby, cust_refund.payby)
-# - an event type (part_bill_event.payby)
tie %hash, 'Tie::IxHash',
'CARD' => {
@@ -70,18 +68,6 @@ tie %hash, 'Tie::IxHash',
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',
- shortname => 'Billing',
- payname => 'Check',
- longname => 'Billing',
- },
'PPAL' => {
tinyname => 'PayPal',
shortname => 'PayPal',
@@ -143,12 +129,6 @@ tie %hash, 'Tie::IxHash',
longname => 'Wire transfer',
cust_main => '', #not a customer type
},
- 'COMP' => {
- tinyname => 'comp',
- shortname => 'Complimentary',
- longname => 'Complimentary',
- cust_pay => '', # (free) is depricated as a payment type in cust_pay
- },
'CBAK' => {
tinyname => 'chargeback',
shortname => 'Chargeback',
@@ -234,6 +214,11 @@ sub cust_payby {
grep { ! exists $hash{$_}->{cust_main} } $self->payby;
}
+sub cust_payby2shortname {
+ my $self = shift;
+ map { $_ => $hash{$_}->{shortname} } $self->cust_payby;
+}
+
sub cust_payby2longname {
my $self = shift;
map { $_ => $hash{$_}->{longname} } $self->cust_payby;