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