diff options
author | Ivan Kohler <ivan@freeside.biz> | 2015-02-10 01:38:56 -0800 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2015-02-10 01:38:56 -0800 |
commit | a4d4d3df88b33a6db30b565921f6d62efb252351 (patch) | |
tree | 0ddfa8fe885dfe6776a0c074aed9e1e0735a7cd7 /FS/FS | |
parent | 6615733676adb431ae48c78ce24758fe571614c1 (diff) |
multiple payment options, RT#23741
Diffstat (limited to 'FS/FS')
-rw-r--r-- | FS/FS/Conf.pm | 8 | ||||
-rw-r--r-- | FS/FS/Schema.pm | 5 | ||||
-rw-r--r-- | FS/FS/Template_Mixin.pm | 19 | ||||
-rw-r--r-- | FS/FS/Upgrade.pm | 5 | ||||
-rw-r--r-- | FS/FS/cust_main.pm | 127 | ||||
-rw-r--r-- | FS/FS/cust_main/Location.pm | 22 | ||||
-rw-r--r-- | FS/FS/cust_main/Search.pm | 44 | ||||
-rw-r--r-- | FS/FS/cust_payby.pm | 442 | ||||
-rw-r--r-- | FS/FS/o2m_Common.pm | 2 | ||||
-rw-r--r-- | FS/FS/part_event/Action/cust_bill_realtime_lec.pm | 28 | ||||
-rw-r--r-- | FS/FS/part_event/Condition/has_cust_payby_auto.pm | 43 | ||||
-rw-r--r-- | FS/FS/part_event/Condition/hasnt_cust_payby_auto.pm | 27 | ||||
-rw-r--r-- | FS/FS/part_event/Condition/payby.pm | 44 | ||||
-rw-r--r-- | FS/FS/part_event_condition.pm | 39 | ||||
-rw-r--r-- | FS/FS/payby.pm | 27 |
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; |