back-office API apply_payments_and_credits
[freeside.git] / FS / FS / API.pm
index 400da14..c595dae 100644 (file)
@@ -23,7 +23,9 @@ This module implements a backend API for advanced back-office integration.
 In contrast to the self-service API, which authenticates an end-user and offers
 functionality to that end user, the backend API performs a simple shared-secret
 authentication and offers full, administrator functionality, enabling
-integration with other back-office systems.
+integration with other back-office systems.  Only access this API from a secure 
+network from other backoffice machines. DON'T use this API to create customer 
+portal functionality.
 
 If accessing this API remotely with XML-RPC or JSON-RPC, be careful to block
 the port by default, only allow access from back-office servers with the same
@@ -40,7 +42,7 @@ in plaintext.
 Adds a new payment to a customers account. Takes a list of keys and values as
 paramters with the following keys:
 
-=over 5
+=over 4
 
 =item secret
 
@@ -88,9 +90,7 @@ Example:
 #enter cash payment
 sub insert_payment {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   #less "raw" than this?  we are the backoffice API, and aren't worried
   # about version migration ala cust_main/cust_location here
@@ -104,19 +104,12 @@ sub insert_payment {
 # pass the phone number ( from svc_phone ) 
 sub insert_payment_phonenum {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
-
   $class->_by_phonenum('insert_payment', %opt);
-
 }
 
 sub _by_phonenum {
   my($class, $method, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   my $phonenum = delete $opt{'phonenum'};
 
@@ -129,7 +122,6 @@ sub _by_phonenum {
   $opt{'custnum'} = $cust_pkg->custnum;
 
   $class->$method(%opt);
-
 }
 
 =item insert_credit OPTION => VALUE, ...
@@ -180,11 +172,9 @@ Example:
 #Enter credit
 sub insert_credit {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
-  $opt{'reasonnum'} ||= $conf->config('api_credit_reason');
+  $opt{'reasonnum'} ||= FS::Conf->new->config('api_credit_reason');
 
   #less "raw" than this?  we are the backoffice API, and aren't worried
   # about version migration ala cust_main/cust_location here
@@ -198,12 +188,38 @@ sub insert_credit {
 # pass the phone number ( from svc_phone ) 
 sub insert_credit_phonenum {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
-
   $class->_by_phonenum('insert_credit', %opt);
+}
+
+=item apply_payments_and_credits
+
+Applies payments and credits for this customer.  Takes a list of keys and
+values as parameter with the following keys:
+
+=over 4
+
+=item secret
+
+API secret
+
+=item custnum
 
+Customer number
+
+=back
+
+=cut
+
+#apply payments and credits
+sub apply_payments_and_credits {
+  my($class, %opt) = @_;
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
+
+  my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
+    or return { 'error' => 'Unknown custnum' };
+
+  my $error = $cust_main->apply_payments_and_credits( 'manual'=>1 );
+  return { 'error'  => $error, };
 }
 
 =item insert_refund OPTION => VALUE, ...
@@ -235,9 +251,7 @@ Example:
 #Enter cash refund.
 sub insert_refund {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   # when github pull request #24 is merged,
   #  will have to change over to default reasonnum like credit
@@ -256,12 +270,7 @@ sub insert_refund {
 # pass the phone number ( from svc_phone ) 
 sub insert_refund_phonenum {
   my($class, %opt) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
-
   $class->_by_phonenum('insert_refund', %opt);
-
 }
 
 #---
@@ -414,18 +423,15 @@ Referring customer number
 
 sub new_customer {
   my( $class, %opt ) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   #default agentnum like signup_server-default_agentnum?
  
   #same for refnum like signup_server-default_refnum
 
   my $cust_main = new FS::cust_main ( {
-      'agentnum' => $agentnum,
       'refnum'   => $opt{refnum}
-                    || $conf->config('signup_server-default_refnum'),
+                    || FS::Conf->new->config('signup_server-default_refnum'),
       'payby'    => 'BILL',
       'tagnum'   => [ FS::part_tag->default_tags ],
 
@@ -478,9 +484,11 @@ sub new_customer {
 }
 
 =item update_customer
-Updates an existing customer. Passing an empty value clears that field, while NOT passing that key/value at all leaves it alone.
-Takes a hash reference as parameter with the following keys:
 
+Updates an existing customer. Passing an empty value clears that field, while
+NOT passing that key/value at all leaves it alone. Takes a list of keys and
+values as parameters with the following keys:
 =over 4
 
 =item secret
@@ -545,9 +553,9 @@ Mobile number
 
 =item invoicing_list
 
-comma-separated list of email addresses for email invoices. The special value '$
-postal_invoicing
-Set to 1 to enable postal invoicing
+Comma-separated list of email addresses for email invoices. The special value 
+'POST' is used to designate postal invoicing (it may be specified alone or in
+addition to email addresses)
 
 =item payby
 
@@ -555,7 +563,8 @@ CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
 
 =item payinfo
 
-Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pi$
+Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid 
++"pin" for PREPAY, purchase order number for BILL
 
 =item paycvv
 
@@ -586,11 +595,8 @@ Agent number
 =cut
 
 sub update_customer {
-
  my( $class, %opt ) = @_;
-
-  my $conf = new FS::Conf;
-
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   my $custnum = $opt{'custnum'}
     or return { 'error' => "no customer record" };
@@ -608,10 +614,13 @@ sub update_customer {
         payby payinfo paydate paycvv payname
       ),
 
-  my @invoicing_list = $opt{'invoicing_list'}
-                         ? split( /\s*\,\s*/, $opt{'invoicing_list'} )
-                         : $cust_main->invoicing_list;
-  push @invoicing_list, 'POST' if $opt{'postal_invoicing'};
+  my @invoicing_list;
+  if ( exists $opt{'invoicing_list'} || exists $opt{'postal_invoicing'} ) {
+    @invoicing_list = split( /\s*\,\s*/, $opt{'invoicing_list'} );
+    push @invoicing_list, 'POST' if $opt{'postal_invoicing'};
+  } else {
+    @invoicing_list = $cust_main->invoicing_list;
+  }
  
   if ( exists( $opt{'address1'} ) ) {
     my $bill_location = FS::cust_location->new({
@@ -625,7 +634,7 @@ sub update_customer {
     $new->set('bill_location' => $bill_location);
   }
 
-  if ( exists($opt{'ship_address1'}) ) {
+  if ( exists($opt{'ship_address1'}) && length($opt{"ship_address1"}) > 0 ) {
     my $ship_location = FS::cust_location->new({
         map { $_ => $opt{"ship_$_"} } @location_editable_fields
     });
@@ -634,16 +643,13 @@ sub update_customer {
     my $error = $ship_location->find_or_insert;
     die $error if $error;
 
-   }
-
-   if ( !grep { length($opt{"ship_$_"}) } @location_editable_fields ) {
-      # Selfservice unfortunately tries to indicate "same as billing
-      # address" by sending all fields empty.  Did this ever work?
+    $new->set('ship_location' => $ship_location);
 
-      my $ship_location = $cust_main->bill_location;
-      $new->set('ship_location' => $ship_location);
+   } elsif (exists($opt{'ship_address1'} ) && !grep { length($opt{"ship_$_"}) } @location_editable_fields ) {
+      my $ship_location = $new->bill_location;
+     $new->set('ship_location' => $ship_location);
+    }
 
-   }
   my $error = $new->replace( $cust_main, \@invoicing_list );
   return { 'error'   => $error } if $error;
 
@@ -674,9 +680,7 @@ use vars qw( @cust_main_editable_fields @location_editable_fields );
 
 sub customer_info {
   my( $class, %opt ) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
     or return { 'error' => 'Unknown custnum' };
@@ -727,9 +731,7 @@ and values as paramters with the following keys: custnum, secret
 
 sub location_info {
   my( $class, %opt ) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
 
@@ -746,16 +748,27 @@ sub location_info {
 Bills a single customer now, in the same fashion as the "Bill now" link in the
 UI.
 
-Returns a hash reference with a single key, 'error'.  If there is an error,
-the value contains the error, otherwise it is empty.
+Returns a hash reference with a single key, 'error'.  If there is an error,   
+the value contains the error, otherwise it is empty. Takes a list of keys and
+values as parameters with the following keys:
+
+=over 4
+
+=item secret
+
+API Secret (required)
+
+=item custnum
+
+Customer number (required)
+
+=back
 
 =cut
 
 sub bill_now {
   my( $class, %opt ) = @_;
-  my $conf = new FS::Conf;
-  return { 'error' => 'Incorrect shared secret' }
-    unless $opt{secret} eq $conf->config('api_shared_secret');
+  return _shared_secret_error() unless _check_shared_secret($opt{secret});
 
   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
     or return { 'error' => 'Unknown custnum' };
@@ -771,7 +784,19 @@ sub bill_now {
 }
 
 
-#Advertising sources?
+#next.. Advertising sources?
+
+
+##
+# helper subroutines
+##
 
+sub _check_shared_secret {
+  shift eq FS::Conf->new->config('api_shared_secret');
+}
+
+sub _shared_secret_error {
+  return { 'error' => 'Incorrect shared secret' };
+}
 
 1;