5 use FS::Record qw( qsearch qsearchs );
15 FS::API - Freeside backend API
23 This module implements a backend API for advanced back-office integration.
25 In contrast to the self-service API, which authenticates an end-user and offers
26 functionality to that end user, the backend API performs a simple shared-secret
27 authentication and offers full, administrator functionality, enabling
28 integration with other back-office systems. Only access this API from a secure
29 network from other backoffice machines. DON'T use this API to create customer
32 If accessing this API remotely with XML-RPC or JSON-RPC, be careful to block
33 the port by default, only allow access from back-office servers with the same
34 security precations as the Freeside server, and encrypt the communication
35 channel (for example, with an SSH tunnel or VPN) rather than accessing it
42 =item insert_payment OPTION => VALUE, ...
44 Adds a new payment to a customers account. Takes a list of keys and values as
45 paramters with the following keys:
67 Option date for payment
77 my $result = FS::API->insert_payment(
78 'secret' => 'sharingiscaring',
84 '_date' => 1397977200, #UNIX timestamp
85 'order_number' => '12345',
88 if ( $result->{'error'} ) {
89 die $result->{'error'};
92 print "paynum ". $result->{'paynum'};
99 my($class, %opt) = @_;
100 return _shared_secret_error() unless _check_shared_secret($opt{secret});
102 #less "raw" than this? we are the backoffice API, and aren't worried
103 # about version migration ala cust_main/cust_location here
104 my $cust_pay = new FS::cust_pay { %opt };
105 my $error = $cust_pay->insert( 'manual'=>1 );
106 return { 'error' => $error,
107 'paynum' => $cust_pay->paynum,
111 # pass the phone number ( from svc_phone )
112 sub insert_payment_phonenum {
113 my($class, %opt) = @_;
114 $class->_by_phonenum('insert_payment', %opt);
118 my($class, $method, %opt) = @_;
119 return _shared_secret_error() unless _check_shared_secret($opt{secret});
121 my $phonenum = delete $opt{'phonenum'};
123 my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $phonenum } )
124 or return { 'error' => 'Unknown phonenum' };
126 my $cust_pkg = $svc_phone->cust_svc->cust_pkg
127 or return { 'error' => 'Unlinked phonenum' };
129 $opt{'custnum'} = $cust_pkg->custnum;
131 $class->$method(%opt);
134 =item insert_credit OPTION => VALUE, ...
136 Adds a a credit to a customers account. Takes a list of keys and values as
137 parameters with the following keys
155 The date the credit will be posted
161 my $result = FS::API->insert_credit(
162 'secret' => 'sharingiscaring',
167 '_date' => 1397977200, #UNIX timestamp
170 if ( $result->{'error'} ) {
171 die $result->{'error'};
174 print "crednum ". $result->{'crednum'};
181 my($class, %opt) = @_;
182 return _shared_secret_error() unless _check_shared_secret($opt{secret});
184 $opt{'reasonnum'} ||= FS::Conf->new->config('api_credit_reason');
186 #less "raw" than this? we are the backoffice API, and aren't worried
187 # about version migration ala cust_main/cust_location here
188 my $cust_credit = new FS::cust_credit { %opt };
189 my $error = $cust_credit->insert;
190 return { 'error' => $error,
191 'crednum' => $cust_credit->crednum,
195 # pass the phone number ( from svc_phone )
196 sub insert_credit_phonenum {
197 my($class, %opt) = @_;
198 $class->_by_phonenum('insert_credit', %opt);
201 =item apply_payments_and_credits
203 Applies payments and credits for this customer. Takes a list of keys and
204 values as parameter with the following keys:
220 #apply payments and credits
221 sub apply_payments_and_credits {
222 my($class, %opt) = @_;
223 return _shared_secret_error() unless _check_shared_secret($opt{secret});
225 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
226 or return { 'error' => 'Unknown custnum' };
228 my $error = $cust_main->apply_payments_and_credits( 'manual'=>1 );
229 return { 'error' => $error, };
232 =item insert_refund OPTION => VALUE, ...
234 Adds a a credit to a customers account. Takes a list of keys and values as
235 parmeters with the following keys: custnum, payby, refund
239 my $result = FS::API->insert_refund(
240 'secret' => 'sharingiscaring',
246 '_date' => 1397977200, #UNIX timestamp
249 if ( $result->{'error'} ) {
250 die $result->{'error'};
253 print "refundnum ". $result->{'crednum'};
260 my($class, %opt) = @_;
261 return _shared_secret_error() unless _check_shared_secret($opt{secret});
263 # when github pull request #24 is merged,
264 # will have to change over to default reasonnum like credit
265 # but until then, this will do
266 $opt{'reason'} ||= 'API refund';
268 #less "raw" than this? we are the backoffice API, and aren't worried
269 # about version migration ala cust_main/cust_location here
270 my $cust_refund = new FS::cust_refund { %opt };
271 my $error = $cust_refund->insert;
272 return { 'error' => $error,
273 'refundnum' => $cust_refund->refundnum,
277 # pass the phone number ( from svc_phone )
278 sub insert_refund_phonenum {
279 my($class, %opt) = @_;
280 $class->_by_phonenum('insert_refund', %opt);
285 # "2 way syncing" ? start with non-sync pulling info here, then if necessary
286 # figure out how to trigger something when those things change
288 # long-term: package changes?
290 =item new_customer OPTION => VALUE, ...
292 Creates a new customer. Takes a list of keys and values as parameters with the
303 first name (required)
311 (not typically collected; mostly used for ACH transactions)
317 =item address1 (required)
321 =item city (required)
329 =item state (required)
351 Currently used for third party tax vendor lookups
355 Used for determining FCC 477 reporting
359 Used for determining FCC 477 reporting
379 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),
381 Set to 1 to enable postal invoicing
383 =item referral_custnum
385 Referring customer number
397 Agent specific customer number
399 =item referral_custnum
401 Referring customer number
407 #certainly false laziness w/ClientAPI::Signup new_customer/new_customer_minimal
408 # but approaching this from a clean start / back-office perspective
409 # i.e. no package/service, no immediate credit card run, etc.
412 my( $class, %opt ) = @_;
413 return _shared_secret_error() unless _check_shared_secret($opt{secret});
415 #default agentnum like signup_server-default_agentnum?
416 #$opt{agentnum} ||= $conf->config('signup_server-default_agentnum');
418 #same for refnum like signup_server-default_refnum
419 $opt{refnum} ||= FS::Conf->new->config('signup_server-default_refnum');
421 $class->API_insert( %opt );
424 =item update_customer
426 Updates an existing customer. Passing an empty value clears that field, while
427 NOT passing that key/value at all leaves it alone. Takes a list of keys and
428 values as parameters with the following keys:
434 API Secret (required)
438 Customer number (required)
494 Comma-separated list of email addresses for email invoices. The special value
495 'POST' is used to designate postal invoicing (it may be specified alone or in
496 addition to email addresses),
498 Set to 1 to enable postal invoicing
500 =item referral_custnum
502 Referring customer number
516 sub update_customer {
517 my( $class, %opt ) = @_;
518 return _shared_secret_error() unless _check_shared_secret($opt{secret});
520 FS::cust_main->API_update( %opt );
523 =item customer_info OPTION => VALUE, ...
525 Returns general customer information. Takes a list of keys and values as
526 parameters with the following keys: custnum, secret
531 my( $class, %opt ) = @_;
532 return _shared_secret_error() unless _check_shared_secret($opt{secret});
534 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
535 or return { 'error' => 'Unknown custnum' };
537 $cust_main->API_getinfo;
542 Returns location specific information for the customer. Takes a list of keys
543 and values as paramters with the following keys: custnum, secret
547 #I also monitor for changes to the additional locations that are applied to
548 # packages, and would like for those to be exportable as well. basically the
549 # location data passed with the custnum.
552 my( $class, %opt ) = @_;
553 return _shared_secret_error() unless _check_shared_secret($opt{secret});
555 my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
559 'locations' => [ map $_->hashref, @cust_location ],
565 =item change_package_location
567 Updates package location. Takes a list of keys and values
568 as paramters with the following keys:
574 locationnum - pass this, or the following keys (don't pass both)
606 On error, returns a hashref with an 'error' key.
607 On success, returns a hashref with 'pkgnum' and 'locationnum' keys,
608 containing the new values.
612 sub change_package_location {
615 return _shared_secret_error() unless _check_shared_secret($opt{'secret'});
617 my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
618 or return { 'error' => 'Unknown pkgnum' };
622 foreach my $field ( qw(
640 $changeopt{$field} = $opt{$field} if $opt{$field};
643 $cust_pkg->API_change(%changeopt);
646 =item bill_now OPTION => VALUE, ...
648 Bills a single customer now, in the same fashion as the "Bill now" link in the
651 Returns a hash reference with a single key, 'error'. If there is an error,
652 the value contains the error, otherwise it is empty. Takes a list of keys and
653 values as parameters with the following keys:
659 API Secret (required)
663 Customer number (required)
670 my( $class, %opt ) = @_;
671 return _shared_secret_error() unless _check_shared_secret($opt{secret});
673 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
674 or return { 'error' => 'Unknown custnum' };
676 my $error = $cust_main->bill_and_collect( 'fatal' => 'return',
681 return { 'error' => $error,
687 #next.. Advertising sources?
694 sub _check_shared_secret {
695 shift eq FS::Conf->new->config('api_shared_secret');
698 sub _shared_secret_error {
699 return { 'error' => 'Incorrect shared secret' };