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