4 use FS::Record qw( qsearch qsearchs );
13 FS::API - Freeside backend API
21 This module implements a backend API for advanced back-office integration.
23 In contrast to the self-service API, which authenticates an end-user and offers
24 functionality to that end user, the backend API performs a simple shared-secret
25 authentication and offers full, administrator functionality, enabling
26 integration with other back-office systems.
28 If accessing this API remotely with XML-RPC or JSON-RPC, be careful to block
29 the port by default, only allow access from back-office servers with the same
30 security precations as the Freeside server, and encrypt the communication
31 channel (for example, with an SSH tunnel or VPN) rather than accessing it
40 Adds a new payment to a customers account. Takes a hash reference as parameter with the following keys:
63 Option date for payment
67 my $result = FS::API->insert_payment(
68 'secret' => 'sharingiscaring',
74 '_date' => 1397977200, #UNIX timestamp
77 if ( $result->{'error'} ) {
78 die $result->{'error'};
81 print "paynum ". $result->{'paynum'};
90 my($class, %opt) = @_;
91 my $conf = new FS::Conf;
92 return { 'error' => 'Incorrect shared secret' }
93 unless $opt{secret} eq $conf->config('api_shared_secret');
95 #less "raw" than this? we are the backoffice API, and aren't worried
96 # about version migration ala cust_main/cust_location here
97 my $cust_pay = new FS::cust_pay { %opt };
98 my $error = $cust_pay->insert( 'manual'=>1 );
99 return { 'error' => $error,
100 'paynum' => $cust_pay->paynum,
104 # pass the phone number ( from svc_phone )
105 sub insert_payment_phonenum {
106 my($class, %opt) = @_;
107 my $conf = new FS::Conf;
108 return { 'error' => 'Incorrect shared secret' }
109 unless $opt{secret} eq $conf->config('api_shared_secret');
111 $class->_by_phonenum('insert_payment', %opt);
116 my($class, $method, %opt) = @_;
117 my $conf = new FS::Conf;
118 return { 'error' => 'Incorrect shared secret' }
119 unless $opt{secret} eq $conf->config('api_shared_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);
137 Adds a a credit to a customers account. Takes a hash reference as parameter with the following keys
155 The date the credit will be posted
159 my $result = FS::API->insert_credit(
160 'secret' => 'sharingiscaring',
165 '_date' => 1397977200, #UNIX timestamp
168 if ( $result->{'error'} ) {
169 die $result->{'error'};
172 print "crednum ". $result->{'crednum'};
181 my($class, %opt) = @_;
182 my $conf = new FS::Conf;
183 return { 'error' => 'Incorrect shared secret' }
184 unless $opt{secret} eq $conf->config('api_shared_secret');
186 $opt{'reasonnum'} ||= $conf->config('api_credit_reason');
188 #less "raw" than this? we are the backoffice API, and aren't worried
189 # about version migration ala cust_main/cust_location here
190 my $cust_credit = new FS::cust_credit { %opt };
191 my $error = $cust_credit->insert;
192 return { 'error' => $error,
193 'crednum' => $cust_credit->crednum,
197 # pass the phone number ( from svc_phone )
198 sub insert_credit_phonenum {
199 my($class, %opt) = @_;
200 my $conf = new FS::Conf;
201 return { 'error' => 'Incorrect shared secret' }
202 unless $opt{secret} eq $conf->config('api_shared_secret');
204 $class->_by_phonenum('insert_credit', %opt);
210 Adds a a credit to a customers account. Takes a hash reference as parameter with the following keys: custnum,payby,refund
214 my $result = FS::API->insert_refund(
215 'secret' => 'sharingiscaring',
221 '_date' => 1397977200, #UNIX timestamp
224 if ( $result->{'error'} ) {
225 die $result->{'error'};
228 print "refundnum ". $result->{'crednum'};
235 my($class, %opt) = @_;
236 my $conf = new FS::Conf;
237 return { 'error' => 'Incorrect shared secret' }
238 unless $opt{secret} eq $conf->config('api_shared_secret');
240 # when github pull request #24 is merged,
241 # will have to change over to default reasonnum like credit
242 # but until then, this will do
243 $opt{'reason'} ||= 'API refund';
245 #less "raw" than this? we are the backoffice API, and aren't worried
246 # about version migration ala cust_main/cust_location here
247 my $cust_refund = new FS::cust_refund { %opt };
248 my $error = $cust_refund->insert;
249 return { 'error' => $error,
250 'refundnum' => $cust_refund->refundnum,
254 # pass the phone number ( from svc_phone )
255 sub insert_refund_phonenum {
256 my($class, %opt) = @_;
257 my $conf = new FS::Conf;
258 return { 'error' => 'Incorrect shared secret' }
259 unless $opt{secret} eq $conf->config('api_shared_secret');
261 $class->_by_phonenum('insert_refund', %opt);
267 # "2 way syncing" ? start with non-sync pulling info here, then if necessary
268 # figure out how to trigger something when those things change
270 # long-term: package changes?
274 Creates a new customer. Takes a hash reference as parameter with the following keys:
284 first name (required)
292 (not typically collected; mostly used for ACH transactions)
298 =item address1 (required)
302 =item city (required)
310 =item state (required)
332 Currently used for third party tax vendor lookups
336 Used for determining FCC 477 reporting
340 Used for determining FCC 477 reporting
360 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),
362 Set to 1 to enable postal invoicing
366 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
370 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
374 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
378 Expiration date for CARD/DCRD
382 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
384 =item referral_custnum
386 Referring customer number
398 Agent specific customer number
400 =item referral_custnum
402 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 my $conf = new FS::Conf;
414 return { 'error' => 'Incorrect shared secret' }
415 unless $opt{secret} eq $conf->config('api_shared_secret');
417 #default agentnum like signup_server-default_agentnum?
419 #same for refnum like signup_server-default_refnum
421 my $cust_main = new FS::cust_main ( {
422 'agentnum' => $agentnum,
423 'refnum' => $opt{refnum}
424 || $conf->config('signup_server-default_refnum'),
426 'tagnum' => [ FS::part_tag->default_tags ],
428 map { $_ => $opt{$_} } qw(
429 agentnum salesnum refnum agent_custid referral_custnum
431 daytime night fax mobile
432 payby payinfo paydate paycvv payname
437 my @invoicing_list = $opt{'invoicing_list'}
438 ? split( /\s*\,\s*/, $opt{'invoicing_list'} )
440 push @invoicing_list, 'POST' if $opt{'postal_invoicing'};
442 my ($bill_hash, $ship_hash);
443 foreach my $f (FS::cust_main->location_fields) {
444 # avoid having to change this in front-end code
445 $bill_hash->{$f} = $opt{"bill_$f"} || $opt{$f};
446 $ship_hash->{$f} = $opt{"ship_$f"};
449 my $bill_location = FS::cust_location->new($bill_hash);
451 # we don't have an equivalent of the "same" checkbox in selfservice^Wthis API
452 # so is there a ship address, and if so, is it different from the billing
454 if ( length($ship_hash->{address1}) > 0 and
455 grep { $bill_hash->{$_} ne $ship_hash->{$_} } keys(%$ship_hash)
458 $ship_location = FS::cust_location->new( $ship_hash );
461 $ship_location = $bill_location;
464 $cust_main->set('bill_location' => $bill_location);
465 $cust_main->set('ship_location' => $ship_location);
467 $error = $cust_main->insert( {}, \@invoicing_list );
468 return { 'error' => $error } if $error;
470 return { 'error' => '',
471 'custnum' => $cust_main->custnum,
480 Returns general customer information. Takes a hash reference as parameter with the following keys: custnum and API secret
484 #some false laziness w/ClientAPI::Myaccount customer_info/customer_info_short
486 use vars qw( @cust_main_editable_fields @location_editable_fields );
487 @cust_main_editable_fields = qw(
488 first last company daytime night fax mobile
491 # payby payinfo payname paystart_month paystart_year payissue payip
492 # ss paytype paystate stateid stateid_state
493 @location_editable_fields = qw(
494 address1 address2 city county state zip country
498 my( $class, %opt ) = @_;
499 my $conf = new FS::Conf;
500 return { 'error' => 'Incorrect shared secret' }
501 unless $opt{secret} eq $conf->config('api_shared_secret');
503 my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
504 or return { 'error' => 'Unknown custnum' };
508 'display_custnum' => $cust_main->display_custnum,
509 'name' => $cust_main->first. ' '. $cust_main->get('last'),
510 'balance' => $cust_main->balance,
511 'status' => $cust_main->status,
512 'statuscolor' => $cust_main->statuscolor,
515 $return{$_} = $cust_main->get($_)
516 foreach @cust_main_editable_fields;
518 for (@location_editable_fields) {
519 $return{$_} = $cust_main->bill_location->get($_)
520 if $cust_main->bill_locationnum;
521 $return{'ship_'.$_} = $cust_main->ship_location->get($_)
522 if $cust_main->ship_locationnum;
525 my @invoicing_list = $cust_main->invoicing_list;
526 $return{'invoicing_list'} =
527 join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list );
528 $return{'postal_invoicing'} =
529 0 < ( grep { $_ eq 'POST' } @invoicing_list );
531 #generally, the more useful data from the cust_main record the better.
532 # well, tell me what you want
541 Returns location specific information for the customer. Takes a hash reference as parameter 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 my $conf = new FS::Conf;
554 return { 'error' => 'Incorrect shared secret' }
555 unless $opt{secret} eq $conf->config('api_shared_secret');
557 my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
561 'locations' => [ map $_->hashref, @cust_location ],
567 #Advertising sources?