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
73 my $result = FS::API->insert_payment(
74 'secret' => 'sharingiscaring',
80 '_date' => 1397977200, #UNIX timestamp
83 if ( $result->{'error'} ) {
84 die $result->{'error'};
87 print "paynum ". $result->{'paynum'};
94 my($class, %opt) = @_;
95 return _shared_secret_error() unless _check_shared_secret($opt{secret});
97 #less "raw" than this? we are the backoffice API, and aren't worried
98 # about version migration ala cust_main/cust_location here
99 my $cust_pay = new FS::cust_pay { %opt };
100 my $error = $cust_pay->insert( 'manual'=>1 );
101 return { 'error' => $error,
102 'paynum' => $cust_pay->paynum,
106 # pass the phone number ( from svc_phone )
107 sub insert_payment_phonenum {
108 my($class, %opt) = @_;
109 $class->_by_phonenum('insert_payment', %opt);
113 my($class, $method, %opt) = @_;
114 return _shared_secret_error() unless _check_shared_secret($opt{secret});
116 my $phonenum = delete $opt{'phonenum'};
118 my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $phonenum } )
119 or return { 'error' => 'Unknown phonenum' };
121 my $cust_pkg = $svc_phone->cust_svc->cust_pkg
122 or return { 'error' => 'Unlinked phonenum' };
124 $opt{'custnum'} = $cust_pkg->custnum;
126 $class->$method(%opt);
129 =item insert_credit OPTION => VALUE, ...
131 Adds a a credit to a customers account. Takes a list of keys and values as
132 parameters with the following keys
150 The date the credit will be posted
156 my $result = FS::API->insert_credit(
157 'secret' => 'sharingiscaring',
162 '_date' => 1397977200, #UNIX timestamp
165 if ( $result->{'error'} ) {
166 die $result->{'error'};
169 print "crednum ". $result->{'crednum'};
176 my($class, %opt) = @_;
177 return _shared_secret_error() unless _check_shared_secret($opt{secret});
179 $opt{'reasonnum'} ||= FS::Conf->new->config('api_credit_reason');
181 #less "raw" than this? we are the backoffice API, and aren't worried
182 # about version migration ala cust_main/cust_location here
183 my $cust_credit = new FS::cust_credit { %opt };
184 my $error = $cust_credit->insert;
185 return { 'error' => $error,
186 'crednum' => $cust_credit->crednum,
190 # pass the phone number ( from svc_phone )
191 sub insert_credit_phonenum {
192 my($class, %opt) = @_;
193 $class->_by_phonenum('insert_credit', %opt);
196 =item apply_payments_and_credits
198 Applies payments and credits for this customer. Takes a list of keys and
199 values as parameter with the following keys:
215 #apply payments and credits
216 sub apply_payments_and_credits {
217 my($class, %opt) = @_;
218 return _shared_secret_error() unless _check_shared_secret($opt{secret});
220 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
221 or return { 'error' => 'Unknown custnum' };
223 my $error = $cust_main->apply_payments_and_credits( 'manual'=>1 );
224 return { 'error' => $error, };
227 =item insert_refund OPTION => VALUE, ...
229 Adds a a credit to a customers account. Takes a list of keys and values as
230 parmeters with the following keys: custnum, payby, refund
234 my $result = FS::API->insert_refund(
235 'secret' => 'sharingiscaring',
241 '_date' => 1397977200, #UNIX timestamp
244 if ( $result->{'error'} ) {
245 die $result->{'error'};
248 print "refundnum ". $result->{'crednum'};
255 my($class, %opt) = @_;
256 return _shared_secret_error() unless _check_shared_secret($opt{secret});
258 # when github pull request #24 is merged,
259 # will have to change over to default reasonnum like credit
260 # but until then, this will do
261 $opt{'reason'} ||= 'API refund';
263 #less "raw" than this? we are the backoffice API, and aren't worried
264 # about version migration ala cust_main/cust_location here
265 my $cust_refund = new FS::cust_refund { %opt };
266 my $error = $cust_refund->insert;
267 return { 'error' => $error,
268 'refundnum' => $cust_refund->refundnum,
272 # pass the phone number ( from svc_phone )
273 sub insert_refund_phonenum {
274 my($class, %opt) = @_;
275 $class->_by_phonenum('insert_refund', %opt);
280 # "2 way syncing" ? start with non-sync pulling info here, then if necessary
281 # figure out how to trigger something when those things change
283 # long-term: package changes?
285 =item new_customer OPTION => VALUE, ...
287 Creates a new customer. Takes a list of keys and values as parameters with the
298 first name (required)
306 (not typically collected; mostly used for ACH transactions)
312 =item address1 (required)
316 =item city (required)
324 =item state (required)
346 Currently used for third party tax vendor lookups
350 Used for determining FCC 477 reporting
354 Used for determining FCC 477 reporting
374 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),
376 Set to 1 to enable postal invoicing
378 =item referral_custnum
380 Referring customer number
392 Agent specific customer number
394 =item referral_custnum
396 Referring customer number
402 #certainly false laziness w/ClientAPI::Signup new_customer/new_customer_minimal
403 # but approaching this from a clean start / back-office perspective
404 # i.e. no package/service, no immediate credit card run, etc.
407 my( $class, %opt ) = @_;
408 return _shared_secret_error() unless _check_shared_secret($opt{secret});
410 #default agentnum like signup_server-default_agentnum?
411 #$opt{agentnum} ||= $conf->config('signup_server-default_agentnum');
413 #same for refnum like signup_server-default_refnum
414 $opt{refnum} ||= FS::Conf->new->config('signup_server-default_refnum');
416 $class->API_insert( %opt );
419 =item update_customer
421 Updates an existing customer. Passing an empty value clears that field, while
422 NOT passing that key/value at all leaves it alone. Takes a list of keys and
423 values as parameters with the following keys:
429 API Secret (required)
433 Customer number (required)
489 Comma-separated list of email addresses for email invoices. The special value
490 'POST' is used to designate postal invoicing (it may be specified alone or in
491 addition to email addresses),
493 Set to 1 to enable postal invoicing
495 =item referral_custnum
497 Referring customer number
511 sub update_customer {
512 my( $class, %opt ) = @_;
513 return _shared_secret_error() unless _check_shared_secret($opt{secret});
515 FS::cust_main->API_update( %opt );
518 =item customer_info OPTION => VALUE, ...
520 Returns general customer information. Takes a list of keys and values as
521 parameters with the following keys: custnum, secret
526 my( $class, %opt ) = @_;
527 return _shared_secret_error() unless _check_shared_secret($opt{secret});
529 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
530 or return { 'error' => 'Unknown custnum' };
532 $cust_main->API_getinfo;
537 Returns location specific information for the customer. Takes a list of keys
538 and values as paramters with the following keys: custnum, secret
542 #I also monitor for changes to the additional locations that are applied to
543 # packages, and would like for those to be exportable as well. basically the
544 # location data passed with the custnum.
547 my( $class, %opt ) = @_;
548 return _shared_secret_error() unless _check_shared_secret($opt{secret});
550 my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
554 'locations' => [ map $_->hashref, @cust_location ],
560 =item change_package_location
562 Updates package location. Takes a list of keys and values
563 as paramters with the following keys:
569 locationnum - pass this, or the following keys (don't pass both)
601 On error, returns a hashref with an 'error' key.
602 On success, returns a hashref with 'pkgnum' and 'locationnum' keys,
603 containing the new values.
607 sub change_package_location {
610 return _shared_secret_error() unless _check_shared_secret($opt{'secret'});
612 my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
613 or return { 'error' => 'Unknown pkgnum' };
617 foreach my $field ( qw(
635 $changeopt{$field} = $opt{$field} if $opt{$field};
638 $cust_pkg->API_change(%changeopt);
641 =item bill_now OPTION => VALUE, ...
643 Bills a single customer now, in the same fashion as the "Bill now" link in the
646 Returns a hash reference with a single key, 'error'. If there is an error,
647 the value contains the error, otherwise it is empty. Takes a list of keys and
648 values as parameters with the following keys:
654 API Secret (required)
658 Customer number (required)
665 my( $class, %opt ) = @_;
666 return _shared_secret_error() unless _check_shared_secret($opt{secret});
668 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
669 or return { 'error' => 'Unknown custnum' };
671 my $error = $cust_main->bill_and_collect( 'fatal' => 'return',
676 return { 'error' => $error,
682 #next.. Advertising sources?
689 sub _check_shared_secret {
690 shift eq FS::Conf->new->config('api_shared_secret');
693 sub _shared_secret_error {
694 return { 'error' => 'Incorrect shared secret' };