[freeside-commits] branch master updated. f32ac83068c6211f829f1688a1a9cdec71bc6ec7

Ivan ivan at 420.am
Tue Nov 17 17:00:15 PST 2015


The branch, master has been updated
       via  f32ac83068c6211f829f1688a1a9cdec71bc6ec7 (commit)
       via  2c112f32561f23f9c538ace00db46659ce16da32 (commit)
      from  c0886229d19cfa798580fbeb342826fd11ceeeb0 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f32ac83068c6211f829f1688a1a9cdec71bc6ec7
Merge: 2c112f3 c088622
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Nov 17 17:00:06 2015 -0800

    Merge branch 'master' of git.freeside.biz:/home/git/freeside


commit 2c112f32561f23f9c538ace00db46659ce16da32
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Nov 17 17:00:01 2015 -0800

    4.x+ self-service API: list and remove cards on file, RT#38919

diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index f272cd4..1a3e57e 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 );
@@ -756,55 +748,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
@@ -900,31 +845,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?
@@ -1545,7 +1489,6 @@ sub invoice_logo {
          };
 }
 
-
 sub list_invoices {
   my $p = shift;
   my $session = _cache->get($p->{'session_id'})
@@ -1605,6 +1548,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'})
@@ -2498,39 +2514,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 at 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.

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/ClientAPI/MyAccount.pm                      |  219 +++++++++++----------
 FS/FS/ClientAPI_XMLRPC.pm                         |    3 +
 bin/{xmlrpc-list_contacts => xmlrpc-delete_payby} |   10 +-
 bin/{xmlrpc-list_contacts => xmlrpc-list_payby}   |    4 +-
 fs_selfservice/FS-SelfService/SelfService.pm      |  117 +++++++++++
 5 files changed, 246 insertions(+), 107 deletions(-)
 copy bin/{xmlrpc-list_contacts => xmlrpc-delete_payby} (62%)
 copy bin/{xmlrpc-list_contacts => xmlrpc-list_payby} (82%)




More information about the freeside-commits mailing list