summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2015-11-17 16:56:23 -0800
committerIvan Kohler <ivan@freeside.biz>2015-11-17 16:56:23 -0800
commit9ced5da7fab076863aa1484ab96d85fbdefe75e2 (patch)
treeaa33f5d62378a77428146d0ab593880541cac27c
parent5366f4b305b16f706e5d89870150298029b57812 (diff)
4.x+ self-service API: list and remove cards on file, RT#38919
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm219
-rw-r--r--FS/FS/ClientAPI_XMLRPC.pm3
-rwxr-xr-xbin/xmlrpc-delete_payby32
-rwxr-xr-xbin/xmlrpc-list_payby30
-rw-r--r--fs_selfservice/FS-SelfService/SelfService.pm117
5 files changed, 298 insertions, 103 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 53deaaa..7e1720d 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -61,8 +61,7 @@ use vars qw( @cust_main_editable_fields @location_editable_fields );
@cust_main_editable_fields = qw(
first last company daytime night fax mobile
locale
- payby payinfo payname paystart_month paystart_year payissue payip
- ss paytype paystate stateid stateid_state
+ ss stateid stateid_state
);
@location_editable_fields = qw(
address1 address2 city county state zip country
@@ -627,8 +626,6 @@ sub customer_info_short {
$return{'last'} = $cust_main->get('last');
$return{name} = $cust_main->first. ' '. $cust_main->get('last');
- $return{payby} = $cust_main->payby;
-
#none of these are terribly expensive if we want 'em...
for (@cust_main_editable_fields) {
$return{$_} = $cust_main->get($_);
@@ -641,11 +638,6 @@ sub customer_info_short {
if $cust_main->ship_locationnum;
}
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{payinfo} = $cust_main->paymask;
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
- }
-
my @invoicing_list = $cust_main->invoicing_list;
$return{'invoicing_list'} =
join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list );
@@ -761,55 +753,8 @@ sub edit_info {
# but if it hasn't been passed in at all, leave ship_location alone--
# DON'T change it to match bill_location.
- my $payby = '';
- if (exists($p->{'payby'})) {
- $p->{'payby'} =~ /^([A-Z]{4})$/
- or return { 'error' => "illegal_payby " . $p->{'payby'} };
- $payby = $1;
- }
-
my $conf = new FS::Conf;
- if ( $payby =~ /^(CARD|DCRD)$/ ) {
-
- $new->paydate($p->{'year'}. '-'. $p->{'month'}. '-01');
-
- if ( $new->payinfo eq $cust_main->paymask ) {
- $new->payinfo($cust_main->payinfo);
- $new->paycvv( $p->{'paycvv'} || $cust_main->paycvv );
- } else {
- $new->payinfo($p->{'payinfo'});
- return { 'error' => 'CVV2 is required' }
- if ! $p->{'paycvv'} && $conf->exists('selfservice-onfile_require_cvv');
- $new->paycvv( $p->{'paycvv'} )
- }
-
- $new->set( 'payby' => $p->{'auto'} ? 'CARD' : 'DCRD' );
-
- } elsif ( $payby =~ /^(CHEK|DCHK)$/ ) {
-
- my $payinfo;
- $p->{'payinfo1'} =~ /^([\dx]+)$/
- or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
- my $payinfo1 = $1;
- $p->{'payinfo2'} =~ /^([\dx\.]+)$/ # . turned on by echeck-country CA ?
- or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
- my $payinfo2 = $1;
- $payinfo = $payinfo1. '@'. $payinfo2;
-
- $new->payinfo( ($payinfo eq $cust_main->paymask)
- ? $cust_main->payinfo
- : $payinfo
- );
-
- $new->set( 'payby' => $p->{'auto'} ? 'CHEK' : 'DCHK' );
-
- } elsif ( $payby =~ /^(BILL)$/ ) {
- #no-op
- } elsif ( $payby ) { #notyet ready
- return { 'error' => "unknown payby $payby" };
- }
-
my @invoicing_list;
if ( exists $p->{'invoicing_list'} || exists $p->{'postal_invoicing'} ) {
#false laziness with httemplate/edit/process/cust_main.cgi
@@ -905,31 +850,30 @@ sub payment_info {
$return{balance} = $cust_main->balance; #XXX pkg-balances?
- $return{payname} = $cust_main->payname
- || ( $cust_main->first. ' '. $cust_main->get('last') );
-
$return{$_} = $cust_main->bill_location->get($_)
for qw(address1 address2 city state zip);
- $return{payby} = $cust_main->payby;
- $return{stateid_state} = $cust_main->stateid_state;
-
- if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
- $return{card_type} = cardtype($cust_main->payinfo);
- $return{payinfo} = $cust_main->paymask;
-
- @return{'month', 'year'} = $cust_main->paydate_monthyear;
-
- }
-
- if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
- my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
- $return{payinfo1} = $payinfo1;
- $return{payinfo2} = $payinfo2;
- $return{paytype} = $cust_main->paytype;
- $return{paystate} = $cust_main->paystate;
- $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
- }
+ #XXX look for stored cust_payby info
+ #
+ # $return{payname} = $cust_main->payname
+ # || ( $cust_main->first. ' '. $cust_main->get('last') );
+ #
+ #if ( $cust_main->payby =~ /^(CARD|DCRD)$/ ) {
+ # $return{card_type} = cardtype($cust_main->payinfo);
+ # $return{payinfo} = $cust_main->paymask;
+ #
+ # @return{'month', 'year'} = $cust_main->paydate_monthyear;
+ #
+ #}
+ #
+ #if ( $cust_main->payby =~ /^(CHEK|DCHK)$/ ) {
+ # my ($payinfo1, $payinfo2) = split '@', $cust_main->paymask;
+ # $return{payinfo1} = $payinfo1;
+ # $return{payinfo2} = $payinfo2;
+ # $return{paytype} = $cust_main->paytype;
+ # $return{paystate} = $cust_main->paystate;
+ # $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
+ #}
if ( $conf->config('prepayment_discounts-credit_type') ) {
#need to eval?
@@ -1550,7 +1494,6 @@ sub invoice_logo {
};
}
-
sub list_invoices {
my $p = shift;
my $session = _cache->get($p->{'session_id'})
@@ -1610,6 +1553,79 @@ sub list_invoices {
};
}
+sub list_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
+ or return { 'error' => "unknown custnum $custnum" };
+
+ return {
+ 'payby' => [ map {
+ my $cust_payby = $_;
+ +{
+ map { $_ => $cust_payby->$_ }
+ qw( custpaybynum weight payby paymask paydate
+ payname paystate paytype
+ )
+ };
+ }
+ $cust_main->cust_payby
+ ],
+ };
+}
+
+sub insert_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ #XXX payinfo1 + payinfo2 for CHEK?
+ #or take the opportunity to use separate, more well- named fields?
+ # my $payinfo;
+ # $p->{'payinfo1'} =~ /^([\dx]+)$/
+ # or return { 'error' => "illegal account number ". $p->{'payinfo1'} };
+ # my $payinfo1 = $1;
+ # $p->{'payinfo2'} =~ /^([\dx\.]+)$/ # . turned on by echeck-country CA ?
+ # or return { 'error' => "illegal ABA/routing number ". $p->{'payinfo2'} };
+ # my $payinfo2 = $1;
+ # $payinfo = $payinfo1. '@'. $payinfo2;
+
+ my $cust_payby = new FS::cust_payby {
+ 'custnum' => $custnum,
+ map { $_ => $p->{$_} } qw( weight payby payinfo paycvv paydate payname
+ paystate paytype payip
+ ),
+ };
+
+ my $error = $cust_payby->insert;
+ if ( $error ) {
+ return { 'error' => $error };
+ } else {
+ return { 'custpaybynum' => $cust_payby->custpaybynum };
+ }
+
+}
+
+sub delete_payby {
+ my $p = shift;
+
+ my($context, $session, $custnum) = _custoragent_session_custnum($p);
+ return { 'error' => $session } if $context eq 'error';
+
+ my $cust_payby = qsearchs('cust_payby', {
+ 'custnum' => $custnum,
+ 'custpaybynum' => $p->{'custpaybynum'},
+ })
+ or return { 'error' => 'unknown custpaybynum '. $p->{'custpaybynum'} };
+
+ return { 'error' => $cust_payby->delete };
+
+}
+
sub cancel {
my $p = shift;
my $session = _cache->get($p->{'session_id'})
@@ -2503,39 +2519,36 @@ sub order_recharge {
sub _do_bop_realtime {
my ($cust_main, $status, %opt) = @_;
- my $old_balance = $cust_main->balance;
-
- my @cust_bill;
- my $bill_error = $cust_main->bill(
- 'return_bill' => \@cust_bill,
- );
+ my $old_balance = $cust_main->balance;
- $bill_error ||= $cust_main->apply_payments_and_credits;
+ my @cust_bill;
+ my $bill_error = $cust_main->bill(
+ 'return_bill' => \@cust_bill,
+ );
- $bill_error ||= $cust_main->realtime_collect('selfservice' => 1)
- if $cust_main->payby =~ /^(CARD|CHEK)$/;
+ $bill_error ||= $cust_main->apply_payments_and_credits;
- if ( $cust_main->balance > $old_balance
- && $cust_main->balance > 0
- && ( $cust_main->payby !~ /^(BILL|DCRD|DCHK)$/
- || $status eq 'suspended'
- )
- )
- {
- unless ( $opt{'no_invoice_void'} ) {
+ $bill_error ||= $cust_main->realtime_collect('selfservice' => 1);
- #this used to apply a credit, but now we can void invoices...
- foreach my $cust_bill (@cust_bill) {
- my $voiderror = $cust_bill->void('automatic payment failed');
- warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
- }
+ if ( $cust_main->balance > $old_balance
+ && $cust_main->balance > 0
+ && ( $cust_main->has_cust_payby_auto || $status eq 'suspended' )
+ )
+ {
+ unless ( $opt{'no_invoice_void'} ) {
+ #this used to apply a credit, but now we can void invoices...
+ foreach my $cust_bill (@cust_bill) {
+ my $voiderror = $cust_bill->void('automatic payment failed');
+ warn "Error voiding cust bill after decline: $voiderror" if $voiderror;
}
- return { 'error' => '_decline', 'bill_error' => $bill_error };
}
- '';
+ return { 'error' => '_decline', 'bill_error' => $bill_error };
+ }
+
+ '';
}
sub renew_info {
diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm
index dbcb565..91f979d 100644
--- a/FS/FS/ClientAPI_XMLRPC.pm
+++ b/FS/FS/ClientAPI_XMLRPC.pm
@@ -127,6 +127,9 @@ sub ss2clientapi {
'legacy_invoice_pdf' => 'MyAccount/legacy_invoice_pdf',
'invoice_logo' => 'MyAccount/invoice_logo',
'list_invoices' => 'MyAccount/list_invoices', #?
+ 'list_payby' => 'MyAccount/list_payby',
+ 'insert_payby' => 'MyAccount/insert_payby',
+ 'delete_payby' => 'MyAccount/delete_payby',
'cancel' => 'MyAccount/cancel', #add to ss cgi!
'payment_info' => 'MyAccount/payment_info',
'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
diff --git a/bin/xmlrpc-delete_payby b/bin/xmlrpc-delete_payby
new file mode 100755
index 0000000..0f489f3
--- /dev/null
+++ b/bin/xmlrpc-delete_payby
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;
+
+my( $email, $password, $custpaybynum ) = @ARGV;
+die "Usage: xmlrpc-delete_payby email password custpaybynum\n"
+ unless $email && length($password) && $custpaybynum;
+
+my $uri = new URI 'http://localhost:8080/';
+
+my $server = new Frontier::Client ( 'url' => $uri );
+
+my $login_result = $server->call(
+ 'FS.ClientAPI_XMLRPC.login',
+ 'email' => $email,
+ 'password' => $password,
+);
+die $login_result->{'error'}."\n" if $login_result->{'error'};
+
+my $list_result = $server->call(
+ 'FS.ClientAPI_XMLRPC.delete_payby',
+ 'session_id' => $login_result->{'session_id'},
+ 'custpaybynum' => $custpaybynum,
+);
+die $list_result->{'error'}."\n" if $list_result->{'error'};
+
+#print Dumper($list_result);
+print "Successfully deleted\n";
+
+1;
diff --git a/bin/xmlrpc-list_payby b/bin/xmlrpc-list_payby
new file mode 100755
index 0000000..60ac67e
--- /dev/null
+++ b/bin/xmlrpc-list_payby
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use Frontier::Client;
+use Data::Dumper;
+
+my( $email, $password ) = @ARGV;
+die "Usage: xmlrpc-list_payby email password\n"
+ unless $email && length($password);
+
+my $uri = new URI 'http://localhost:8080/';
+
+my $server = new Frontier::Client ( 'url' => $uri );
+
+my $login_result = $server->call(
+ 'FS.ClientAPI_XMLRPC.login',
+ 'email' => $email,
+ 'password' => $password,
+);
+die $login_result->{'error'}."\n" if $login_result->{'error'};
+
+my $list_result = $server->call(
+ 'FS.ClientAPI_XMLRPC.list_payby',
+ 'session_id' => $login_result->{'session_id'},
+);
+die $list_result->{'error'}."\n" if $list_result->{'error'};
+
+print Dumper($list_result);
+
+1;
diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm
index e01d17b..f4b47b2 100644
--- a/fs_selfservice/FS-SelfService/SelfService.pm
+++ b/fs_selfservice/FS-SelfService/SelfService.pm
@@ -48,6 +48,9 @@ $socket .= '.'.$tag if defined $tag && length($tag);
'legacy_invoice_pdf' => 'MyAccount/legacy_invoice_pdf',
'invoice_logo' => 'MyAccount/invoice_logo',
'list_invoices' => 'MyAccount/list_invoices', #?
+ 'list_payby' => 'MyAccount/list_payby',
+ 'insert_payby' => 'MyAccount/insert_payby',
+ 'delete_payby' => 'MyAccount/delete_payby',
'cancel' => 'MyAccount/cancel', #add to ss cgi!
'payment_info' => 'MyAccount/payment_info',
'payment_info_renew_info' => 'MyAccount/payment_info_renew_info',
@@ -567,6 +570,120 @@ Invoice date, in UNIX epoch time
=back
+=item list_payby HASHREF
+
+Returns a list of all stored customer payment information (credit cards and
+electronic check accounts). Takes a hash reference with a single key,
+session_id.
+
+Returns a hash reference with the following keys:
+
+=over 4
+
+=item error
+
+Empty on success, or an error message on errors
+
+=item payby
+
+Reference to array of hash references with the following keys:
+
+=over 4
+
+=item custpaybynum
+
+=item weight
+
+Numeric weighting. Stored payment information with a lower weight is attempted
+first.
+
+=item payby
+
+CARD (Automatic credit card), CHEK (Automatic electronic check), DCRD
+(on-demand credit card) or DCHK (on-demand electronic check).
+
+=item paymask
+
+Masked credit card number (or, masked account and routing numbers)
+
+=item paydate
+
+Credit card expiration date
+
+=item payname
+
+Exact name on card (or bank name, for electronic checks)
+
+=item paystate
+
+For electronic checks, bank state
+
+=item paytype
+
+For electronic checks, account type (Personal/Business, Checking/Savings)
+
+=back
+
+=back
+
+=item insert_payby HASHREF
+
+Adds new stored payment information for this customer. Takes a hash reference
+with the following keys:
+
+=over 4
+
+=item session_id
+
+=item weight
+
+Numeric weighting. Stored payment information with a lower weight is attempted
+first.
+
+=item payby
+
+CARD (Automatic credit card), CHEK (Automatic electronic check), DCRD
+(on-demand credit card) or DCHK (on-demand electronic check).
+
+=item payinfo
+
+Credit card number (or electronic check "account@routing")
+
+=item paycvv
+
+CVV2 number / security code
+
+=item paydate
+
+Credit card expiration date
+
+=item payname
+
+Exact name on card (or bank name, for electronic checks)
+
+=item paystate
+
+For electronic checks, bank state
+
+=item paytype
+
+For electronic checks, account type (i.e. "Personal Savings", "Personal Checking", "Business Checking")A
+
+=item payip
+
+Optional IP address from which payment was submitted
+
+=back
+
+If there is an error, returns a hash reference with a single key, B<error>,
+otherwise returns a hash reference with a single key, B<custpaybynum>.
+
+=item delete_payby HASHREF
+
+Removes stored payment information. Takes a hash reference with two keys,
+B<session_id> and B<custpaybynum>. Returns a hash reference with a single key,
+B<error>, which is an error message or empty for successful removal.
+
=item cancel HASHREF
Cancels this customer.