Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / API.pm
1 package FS::API;
2
3 use strict;
4 use FS::Conf;
5 use FS::Record qw( qsearch qsearchs );
6 use FS::cust_main;
7 use FS::cust_location;
8 use FS::cust_pay;
9 use FS::cust_credit;
10 use FS::cust_refund;
11
12 =head1 NAME
13
14 FS::API - Freeside backend API
15
16 =head1 SYNOPSIS
17
18   use FS::API;
19
20 =head1 DESCRIPTION
21
22 This module implements a backend API for advanced back-office integration.
23
24 In contrast to the self-service API, which authenticates an end-user and offers
25 functionality to that end user, the backend API performs a simple shared-secret
26 authentication and offers full, administrator functionality, enabling
27 integration with other back-office systems.
28
29 If accessing this API remotely with XML-RPC or JSON-RPC, be careful to block
30 the port by default, only allow access from back-office servers with the same
31 security precations as the Freeside server, and encrypt the communication
32 channel (for example, with an SSH tunnel or VPN) rather than accessing it
33 in plaintext.
34
35 =head1 METHODS
36
37 =over 4
38
39 =item insert_payment OPTION => VALUE, ...
40
41 Adds a new payment to a customers account. Takes a list of keys and values as
42 paramters with the following keys:
43
44 =over 5
45
46 =item secret
47
48 API Secret
49
50 =item custnum
51
52 Customer number
53
54 =item payby
55
56 Payment type
57
58 =item paid
59
60 Amount paid
61
62 =item _date
63
64 Option date for payment
65
66 =back
67
68 Example:
69
70   my $result = FS::API->insert_payment(
71     'secret'  => 'sharingiscaring',
72     'custnum' => 181318,
73     'payby'   => 'CASH',
74     'paid'    => '54.32',
75
76     #optional
77     '_date'   => 1397977200, #UNIX timestamp
78   );
79
80   if ( $result->{'error'} ) {
81     die $result->{'error'};
82   } else {
83     #payment was inserted
84     print "paynum ". $result->{'paynum'};
85   }
86
87 =cut
88
89 #enter cash payment
90 sub insert_payment {
91   my($class, %opt) = @_;
92   my $conf = new FS::Conf;
93   return { 'error' => 'Incorrect shared secret' }
94     unless $opt{secret} eq $conf->config('api_shared_secret');
95
96   #less "raw" than this?  we are the backoffice API, and aren't worried
97   # about version migration ala cust_main/cust_location here
98   my $cust_pay = new FS::cust_pay { %opt };
99   my $error = $cust_pay->insert( 'manual'=>1 );
100   return { 'error'  => $error,
101            'paynum' => $cust_pay->paynum,
102          };
103 }
104
105 # pass the phone number ( from svc_phone ) 
106 sub insert_payment_phonenum {
107   my($class, %opt) = @_;
108   my $conf = new FS::Conf;
109   return { 'error' => 'Incorrect shared secret' }
110     unless $opt{secret} eq $conf->config('api_shared_secret');
111
112   $class->_by_phonenum('insert_payment', %opt);
113
114 }
115
116 sub _by_phonenum {
117   my($class, $method, %opt) = @_;
118   my $conf = new FS::Conf;
119   return { 'error' => 'Incorrect shared secret' }
120     unless $opt{secret} eq $conf->config('api_shared_secret');
121
122   my $phonenum = delete $opt{'phonenum'};
123
124   my $svc_phone = qsearchs('svc_phone', { 'phonenum' => $phonenum } )
125     or return { 'error' => 'Unknown phonenum' };
126
127   my $cust_pkg = $svc_phone->cust_svc->cust_pkg
128     or return { 'error' => 'Unlinked phonenum' };
129
130   $opt{'custnum'} = $cust_pkg->custnum;
131
132   $class->$method(%opt);
133
134 }
135
136 =item insert_credit OPTION => VALUE, ...
137
138 Adds a a credit to a customers account.  Takes a list of keys and values as
139 parameters with the following keys
140
141 =over 
142
143 =item secret
144
145 API Secret
146
147 =item custnum
148
149 customer number
150
151 =item amount
152
153 Amount of the credit
154
155 =item _date
156
157 The date the credit will be posted
158
159 =back
160
161 Example:
162
163   my $result = FS::API->insert_credit(
164     'secret'  => 'sharingiscaring',
165     'custnum' => 181318,
166     'amount'  => '54.32',
167
168     #optional
169     '_date'   => 1397977200, #UNIX timestamp
170   );
171
172   if ( $result->{'error'} ) {
173     die $result->{'error'};
174   } else {
175     #credit was inserted
176     print "crednum ". $result->{'crednum'};
177   }
178
179 =cut
180
181 #Enter credit
182 sub insert_credit {
183   my($class, %opt) = @_;
184   my $conf = new FS::Conf;
185   return { 'error' => 'Incorrect shared secret' }
186     unless $opt{secret} eq $conf->config('api_shared_secret');
187
188   $opt{'reasonnum'} ||= $conf->config('api_credit_reason');
189
190   #less "raw" than this?  we are the backoffice API, and aren't worried
191   # about version migration ala cust_main/cust_location here
192   my $cust_credit = new FS::cust_credit { %opt };
193   my $error = $cust_credit->insert;
194   return { 'error'  => $error,
195            'crednum' => $cust_credit->crednum,
196          };
197 }
198
199 # pass the phone number ( from svc_phone ) 
200 sub insert_credit_phonenum {
201   my($class, %opt) = @_;
202   my $conf = new FS::Conf;
203   return { 'error' => 'Incorrect shared secret' }
204     unless $opt{secret} eq $conf->config('api_shared_secret');
205
206   $class->_by_phonenum('insert_credit', %opt);
207
208 }
209
210 =item insert_refund OPTION => VALUE, ...
211
212 Adds a a credit to a customers account.  Takes a list of keys and values as
213 parmeters with the following keys: custnum, payby, refund
214
215 Example:
216
217   my $result = FS::API->insert_refund(
218     'secret'  => 'sharingiscaring',
219     'custnum' => 181318,
220     'payby'   => 'CASH',
221     'refund'  => '54.32',
222
223     #optional
224     '_date'   => 1397977200, #UNIX timestamp
225   );
226
227   if ( $result->{'error'} ) {
228     die $result->{'error'};
229   } else {
230     #refund was inserted
231     print "refundnum ". $result->{'crednum'};
232   }
233
234 =cut
235
236 #Enter cash refund.
237 sub insert_refund {
238   my($class, %opt) = @_;
239   my $conf = new FS::Conf;
240   return { 'error' => 'Incorrect shared secret' }
241     unless $opt{secret} eq $conf->config('api_shared_secret');
242
243   # when github pull request #24 is merged,
244   #  will have to change over to default reasonnum like credit
245   # but until then, this will do
246   $opt{'reason'} ||= 'API refund';
247
248   #less "raw" than this?  we are the backoffice API, and aren't worried
249   # about version migration ala cust_main/cust_location here
250   my $cust_refund = new FS::cust_refund { %opt };
251   my $error = $cust_refund->insert;
252   return { 'error'     => $error,
253            'refundnum' => $cust_refund->refundnum,
254          };
255 }
256
257 # pass the phone number ( from svc_phone ) 
258 sub insert_refund_phonenum {
259   my($class, %opt) = @_;
260   my $conf = new FS::Conf;
261   return { 'error' => 'Incorrect shared secret' }
262     unless $opt{secret} eq $conf->config('api_shared_secret');
263
264   $class->_by_phonenum('insert_refund', %opt);
265
266 }
267
268 #---
269
270 # "2 way syncing" ?  start with non-sync pulling info here, then if necessary
271 # figure out how to trigger something when those things change
272
273 # long-term: package changes?
274
275 =item new_customer OPTION => VALUE, ...
276
277 Creates a new customer. Takes a list of keys and values as parameters with the
278 following keys:
279
280 =over 4
281
282 =item secret
283
284 API Secret
285
286 =item first
287
288 first name (required)
289
290 =item last
291
292 last name (required)
293
294 =item ss
295
296 (not typically collected; mostly used for ACH transactions)
297
298 =item company
299
300 Company name
301
302 =item address1 (required)
303
304 Address line one
305
306 =item city (required)
307
308 City
309
310 =item county
311
312 County
313
314 =item state (required)
315
316 State
317
318 =item zip (required)
319
320 Zip or postal code
321
322 =item country
323
324 2 Digit Country Code
325
326 =item latitude
327
328 latitude
329
330 =item Longitude
331
332 longitude
333
334 =item geocode
335
336 Currently used for third party tax vendor lookups
337
338 =item censustract
339
340 Used for determining FCC 477 reporting
341
342 =item censusyear
343
344 Used for determining FCC 477 reporting
345
346 =item daytime
347
348 Daytime phone number
349
350 =item night
351
352 Evening phone number
353
354 =item fax
355
356 Fax number
357
358 =item mobile
359
360 Mobile number
361
362 =item invoicing_list
363
364 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),
365 postal_invoicing
366 Set to 1 to enable postal invoicing
367
368 =item referral_custnum
369
370 Referring customer number
371
372 =item salesnum
373
374 Sales person number
375
376 =item agentnum
377
378 Agent number
379
380 =item agent_custid
381
382 Agent specific customer number
383
384 =item referral_custnum
385
386 Referring customer number
387
388 =back
389
390 =cut
391
392 #certainly false laziness w/ClientAPI::Signup new_customer/new_customer_minimal
393 # but approaching this from a clean start / back-office perspective
394 #  i.e. no package/service, no immediate credit card run, etc.
395
396 sub new_customer {
397   my( $class, %opt ) = @_;
398
399   my $conf = new FS::Conf;
400   return { 'error' => 'Incorrect shared secret' }
401     unless $opt{secret} eq $conf->config('api_shared_secret');
402
403   #default agentnum like signup_server-default_agentnum?
404   #$opt{agentnum} ||= $conf->config('signup_server-default_agentnum');
405  
406   #same for refnum like signup_server-default_refnum
407   $opt{refnum} ||= $conf->config('signup_server-default_refnum');
408
409   $class->API_insert( %opt );
410 }
411
412 =item update_customer
413
414 Updates an existing customer. Passing an empty value clears that field, while
415 NOT passing that key/value at all leaves it alone. Takes a list of keys and
416 values as parameters with the following keys:
417
418 =over 4
419
420 =item secret
421
422 API Secret (required)
423
424 =item custnum
425
426 Customer number (required)
427
428 =item first
429
430 first name 
431
432 =item last
433
434 last name 
435
436 =item company
437
438 Company name
439
440 =item address1 
441
442 Address line one
443
444 =item city 
445
446 City
447
448 =item county
449
450 County
451
452 =item state 
453
454 State
455
456 =item zip 
457
458 Zip or postal code
459
460 =item country
461
462 2 Digit Country Code
463
464 =item daytime
465
466 Daytime phone number
467
468 =item night
469
470 Evening phone number
471
472 =item fax
473
474 Fax number
475
476 =item mobile
477
478 Mobile number
479
480 =item invoicing_list
481
482 Comma-separated list of email addresses for email invoices. The special value 
483 'POST' is used to designate postal invoicing (it may be specified alone or in
484 addition to email addresses),
485 postal_invoicing
486 Set to 1 to enable postal invoicing
487
488 =item referral_custnum
489
490 Referring customer number
491
492 =item salesnum
493
494 Sales person number
495
496 =item agentnum
497
498 Agent number
499
500 =back
501
502 =cut
503
504 sub update_customer {
505   my( $class, %opt ) = @_;
506
507   my $conf = new FS::Conf;
508   return { 'error' => 'Incorrect shared secret' }
509     unless $opt{secret} eq $conf->config('api_shared_secret');
510
511   FS::cust_main->API_update( %opt );
512 }
513
514 =item customer_info OPTION => VALUE, ...
515
516 Returns general customer information. Takes a list of keys and values as
517 parameters with the following keys: custnum, secret 
518
519 =cut
520
521 sub customer_info {
522   my( $class, %opt ) = @_;
523   my $conf = new FS::Conf;
524   return { 'error' => 'Incorrect shared secret' }
525     unless $opt{secret} eq $conf->config('api_shared_secret');
526
527   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
528     or return { 'error' => 'Unknown custnum' };
529
530   $cust_main->API_getinfo;
531 }
532
533 =item location_info
534
535 Returns location specific information for the customer. Takes a list of keys
536 and values as paramters with the following keys: custnum, secret
537
538 =cut
539
540 #I also monitor for changes to the additional locations that are applied to
541 # packages, and would like for those to be exportable as well.  basically the
542 # location data passed with the custnum.
543
544 sub location_info {
545   my( $class, %opt ) = @_;
546   my $conf = new FS::Conf;
547   return { 'error' => 'Incorrect shared secret' }
548     unless $opt{secret} eq $conf->config('api_shared_secret');
549
550   my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
551
552   my %return = (
553     'error'           => '',
554     'locations'       => [ map $_->hashref, @cust_location ],
555   );
556
557   return \%return;
558 }
559
560 =item bill_now OPTION => VALUE, ...
561
562 Bills a single customer now, in the same fashion as the "Bill now" link in the
563 UI.
564
565 Returns a hash reference with a single key, 'error'.  If there is an error,   
566 the value contains the error, otherwise it is empty. Takes a list of keys and
567 values as parameters with the following keys:
568
569 =over 4
570
571 =item secret
572
573 API Secret (required)
574
575 =item custnum
576
577 Customer number (required)
578
579 =back
580
581 =cut
582
583 sub bill_now {
584   my( $class, %opt ) = @_;
585   my $conf = new FS::Conf;
586   return { 'error' => 'Incorrect shared secret' }
587     unless $opt{secret} eq $conf->config('api_shared_secret');
588
589   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
590     or return { 'error' => 'Unknown custnum' };
591
592   my $error = $cust_main->bill_and_collect( 'fatal'      => 'return',
593                                             'retry'      => 1,
594                                             'check_freq' =>'1d',
595                                           );
596
597    return { 'error' => $error,
598           };
599
600 }
601
602
603 #Advertising sources?
604
605
606 1;