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 payby
369
370 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
371
372 =item payinfo
373
374 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
375
376 =item paycvv
377
378 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
379
380 =item paydate
381
382 Expiration date for CARD/DCRD
383
384 =item payname
385
386 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
387
388 =item referral_custnum
389
390 Referring customer number
391
392 =item salesnum
393
394 Sales person number
395
396 =item agentnum
397
398 Agent number
399
400 =item agent_custid
401
402 Agent specific customer number
403
404 =item referral_custnum
405
406 Referring customer number
407
408 =back
409
410 =cut
411
412 #certainly false laziness w/ClientAPI::Signup new_customer/new_customer_minimal
413 # but approaching this from a clean start / back-office perspective
414 #  i.e. no package/service, no immediate credit card run, etc.
415
416 sub new_customer {
417   my( $class, %opt ) = @_;
418
419   my $conf = new FS::Conf;
420   return { 'error' => 'Incorrect shared secret' }
421     unless $opt{secret} eq $conf->config('api_shared_secret');
422
423   #default agentnum like signup_server-default_agentnum?
424   #$opt{agentnum} ||= $conf->config('signup_server-default_agentnum');
425  
426   #same for refnum like signup_server-default_refnum
427   $opt{refnum} ||= $conf->config('signup_server-default_refnum');
428
429   $class->API_insert( %opt );
430 }
431
432 =item update_customer
433 Updates an existing customer. Passing an empty value clears that field, while NOT passing that key/value at all leaves it alone.
434 Takes a hash reference as parameter with the following keys:
435
436 =over 4
437
438 =item secret
439
440 API Secret (required)
441
442 =item custnum
443
444 Customer number (required)
445
446 =item first
447
448 first name 
449
450 =item last
451
452 last name 
453
454 =item company
455
456 Company name
457
458 =item address1 
459
460 Address line one
461
462 =item city 
463
464 City
465
466 =item county
467
468 County
469
470 =item state 
471
472 State
473
474 =item zip 
475
476 Zip or postal code
477
478 =item country
479
480 2 Digit Country Code
481
482 =item daytime
483
484 Daytime phone number
485
486 =item night
487
488 Evening phone number
489
490 =item fax
491
492 Fax number
493
494 =item mobile
495
496 Mobile number
497
498 =item invoicing_list
499
500 comma-separated list of email addresses for email invoices. The special value '$
501 postal_invoicing
502 Set to 1 to enable postal invoicing
503
504 =item payby
505
506 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
507
508 =item payinfo
509
510 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pi$
511
512 =item paycvv
513
514 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
515
516 =item paydate
517
518 Expiration date for CARD/DCRD
519
520 =item payname
521
522 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
523
524 =item referral_custnum
525
526 Referring customer number
527
528 =item salesnum
529
530 Sales person number
531
532 =item agentnum
533
534 Agent number
535
536 =back
537
538 =cut
539
540 sub update_customer {
541   my( $class, %opt ) = @_;
542
543   my $conf = new FS::Conf;
544   return { 'error' => 'Incorrect shared secret' }
545     unless $opt{secret} eq $conf->config('api_shared_secret');
546
547   FS::cust_main->API_update( %opt );
548 }
549
550 =item customer_info OPTION => VALUE, ...
551
552 Returns general customer information. Takes a list of keys and values as
553 parameters with the following keys: custnum, secret 
554
555 =cut
556
557 sub customer_info {
558   my( $class, %opt ) = @_;
559   my $conf = new FS::Conf;
560   return { 'error' => 'Incorrect shared secret' }
561     unless $opt{secret} eq $conf->config('api_shared_secret');
562
563   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
564     or return { 'error' => 'Unknown custnum' };
565
566   $cust_main->API_getinfo;
567 }
568
569 =item location_info
570
571 Returns location specific information for the customer. Takes a list of keys
572 and values as paramters with the following keys: custnum, secret
573
574 =cut
575
576 #I also monitor for changes to the additional locations that are applied to
577 # packages, and would like for those to be exportable as well.  basically the
578 # location data passed with the custnum.
579
580 sub location_info {
581   my( $class, %opt ) = @_;
582   my $conf = new FS::Conf;
583   return { 'error' => 'Incorrect shared secret' }
584     unless $opt{secret} eq $conf->config('api_shared_secret');
585
586   my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
587
588   my %return = (
589     'error'           => '',
590     'locations'       => [ map $_->hashref, @cust_location ],
591   );
592
593   return \%return;
594 }
595
596 =item bill_now OPTION => VALUE, ...
597
598 Bills a single customer now, in the same fashion as the "Bill now" link in the
599 UI.
600
601 Returns a hash reference with a single key, 'error'.  If there is an error,
602 the value contains the error, otherwise it is empty.
603
604 =cut
605
606 sub bill_now {
607   my( $class, %opt ) = @_;
608   my $conf = new FS::Conf;
609   return { 'error' => 'Incorrect shared secret' }
610     unless $opt{secret} eq $conf->config('api_shared_secret');
611
612   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
613     or return { 'error' => 'Unknown custnum' };
614
615   my $error = $cust_main->bill_and_collect( 'fatal'      => 'return',
616                                             'retry'      => 1,
617                                             'check_freq' =>'1d',
618                                           );
619
620    return { 'error' => $error,
621           };
622
623 }
624
625
626 #Advertising sources?
627
628
629 1;