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
434 Updates an existing customer. Passing an empty value clears that field, while
435 NOT passing that key/value at all leaves it alone. Takes a list of keys and
436 values as parameters with the following keys:
437
438 =over 4
439
440 =item secret
441
442 API Secret (required)
443
444 =item custnum
445
446 Customer number (required)
447
448 =item first
449
450 first name 
451
452 =item last
453
454 last name 
455
456 =item company
457
458 Company name
459
460 =item address1 
461
462 Address line one
463
464 =item city 
465
466 City
467
468 =item county
469
470 County
471
472 =item state 
473
474 State
475
476 =item zip 
477
478 Zip or postal code
479
480 =item country
481
482 2 Digit Country Code
483
484 =item daytime
485
486 Daytime phone number
487
488 =item night
489
490 Evening phone number
491
492 =item fax
493
494 Fax number
495
496 =item mobile
497
498 Mobile number
499
500 =item invoicing_list
501
502 Comma-separated list of email addresses for email invoices. The special value 
503 'POST' is used to designate postal invoicing (it may be specified alone or in
504 addition to email addresses),
505 postal_invoicing
506 Set to 1 to enable postal invoicing
507
508 =item payby
509
510 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
511
512 =item payinfo
513
514 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid 
515 "pin" for PREPAY, purchase order number for BILL
516
517 =item paycvv
518
519 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
520
521 =item paydate
522
523 Expiration date for CARD/DCRD
524
525 =item payname
526
527 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
528
529 =item referral_custnum
530
531 Referring customer number
532
533 =item salesnum
534
535 Sales person number
536
537 =item agentnum
538
539 Agent number
540
541 =back
542
543 =cut
544
545 sub update_customer {
546   my( $class, %opt ) = @_;
547
548   my $conf = new FS::Conf;
549   return { 'error' => 'Incorrect shared secret' }
550     unless $opt{secret} eq $conf->config('api_shared_secret');
551
552   FS::cust_main->API_update( %opt );
553 }
554
555 =item customer_info OPTION => VALUE, ...
556
557 Returns general customer information. Takes a list of keys and values as
558 parameters with the following keys: custnum, secret 
559
560 =cut
561
562 sub customer_info {
563   my( $class, %opt ) = @_;
564   my $conf = new FS::Conf;
565   return { 'error' => 'Incorrect shared secret' }
566     unless $opt{secret} eq $conf->config('api_shared_secret');
567
568   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
569     or return { 'error' => 'Unknown custnum' };
570
571   $cust_main->API_getinfo;
572 }
573
574 =item location_info
575
576 Returns location specific information for the customer. Takes a list of keys
577 and values as paramters with the following keys: custnum, secret
578
579 =cut
580
581 #I also monitor for changes to the additional locations that are applied to
582 # packages, and would like for those to be exportable as well.  basically the
583 # location data passed with the custnum.
584
585 sub location_info {
586   my( $class, %opt ) = @_;
587   my $conf = new FS::Conf;
588   return { 'error' => 'Incorrect shared secret' }
589     unless $opt{secret} eq $conf->config('api_shared_secret');
590
591   my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
592
593   my %return = (
594     'error'           => '',
595     'locations'       => [ map $_->hashref, @cust_location ],
596   );
597
598   return \%return;
599 }
600
601 =item bill_now OPTION => VALUE, ...
602
603 Bills a single customer now, in the same fashion as the "Bill now" link in the
604 UI.
605
606 Returns a hash reference with a single key, 'error'.  If there is an error,
607 the value contains the error, otherwise it is empty.
608
609 =cut
610
611 sub bill_now {
612   my( $class, %opt ) = @_;
613   my $conf = new FS::Conf;
614   return { 'error' => 'Incorrect shared secret' }
615     unless $opt{secret} eq $conf->config('api_shared_secret');
616
617   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
618     or return { 'error' => 'Unknown custnum' };
619
620   my $error = $cust_main->bill_and_collect( 'fatal'      => 'return',
621                                             'retry'      => 1,
622                                             'check_freq' =>'1d',
623                                           );
624
625    return { 'error' => $error,
626           };
627
628 }
629
630
631 #Advertising sources?
632
633
634 1;