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
40
41 Adds a new payment to a customers account. Takes a hash reference as parameter with the following keys:
42
43 =over 5
44
45 =item secret
46
47 API Secret
48
49 =item custnum
50
51 Customer number
52
53 =item payby
54
55 Payment type
56
57 =item paid
58
59 Amount paid
60
61 =item _date
62
63
64 Option date for payment
65
66 Example:
67
68   my $result = FS::API->insert_payment(
69     'secret'  => 'sharingiscaring',
70     'custnum' => 181318,
71     'payby'   => 'CASH',
72     'paid'    => '54.32',
73
74     #optional
75     '_date'   => 1397977200, #UNIX timestamp
76   );
77
78   if ( $result->{'error'} ) {
79     die $result->{'error'};
80   } else {
81     #payment was inserted
82     print "paynum ". $result->{'paynum'};
83   }
84
85 =back
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
137
138 Adds a a credit to a customers account. Takes a hash reference as parameter with the following keys
139
140 =over 
141
142 =item secret
143
144 API Secret
145
146 =item custnum
147
148 customer number
149
150 =item amount
151
152 Amount of the credit
153
154 =item _date
155
156 The date the credit will be posted
157
158 Example:
159
160   my $result = FS::API->insert_credit(
161     'secret'  => 'sharingiscaring',
162     'custnum' => 181318,
163     'amount'  => '54.32',
164
165     #optional
166     '_date'   => 1397977200, #UNIX timestamp
167   );
168
169   if ( $result->{'error'} ) {
170     die $result->{'error'};
171   } else {
172     #credit was inserted
173     print "crednum ". $result->{'crednum'};
174   }
175
176 =back
177
178 =cut
179
180 #Enter credit
181 sub insert_credit {
182   my($class, %opt) = @_;
183   my $conf = new FS::Conf;
184   return { 'error' => 'Incorrect shared secret' }
185     unless $opt{secret} eq $conf->config('api_shared_secret');
186
187   $opt{'reasonnum'} ||= $conf->config('api_credit_reason');
188
189   #less "raw" than this?  we are the backoffice API, and aren't worried
190   # about version migration ala cust_main/cust_location here
191   my $cust_credit = new FS::cust_credit { %opt };
192   my $error = $cust_credit->insert;
193   return { 'error'  => $error,
194            'crednum' => $cust_credit->crednum,
195          };
196 }
197
198 # pass the phone number ( from svc_phone ) 
199 sub insert_credit_phonenum {
200   my($class, %opt) = @_;
201   my $conf = new FS::Conf;
202   return { 'error' => 'Incorrect shared secret' }
203     unless $opt{secret} eq $conf->config('api_shared_secret');
204
205   $class->_by_phonenum('insert_credit', %opt);
206
207 }
208
209 =item insert_refund
210
211 Adds a a credit to a customers account. Takes a hash reference as parameter with the following keys: custnum,payby,refund
212
213 Example:
214
215   my $result = FS::API->insert_refund(
216     'secret'  => 'sharingiscaring',
217     'custnum' => 181318,
218     'payby'   => 'CASH',
219     'refund'  => '54.32',
220
221     #optional
222     '_date'   => 1397977200, #UNIX timestamp
223   );
224
225   if ( $result->{'error'} ) {
226     die $result->{'error'};
227   } else {
228     #refund was inserted
229     print "refundnum ". $result->{'crednum'};
230   }
231
232 =cut
233
234 #Enter cash refund.
235 sub insert_refund {
236   my($class, %opt) = @_;
237   my $conf = new FS::Conf;
238   return { 'error' => 'Incorrect shared secret' }
239     unless $opt{secret} eq $conf->config('api_shared_secret');
240
241   # when github pull request #24 is merged,
242   #  will have to change over to default reasonnum like credit
243   # but until then, this will do
244   $opt{'reason'} ||= 'API refund';
245
246   #less "raw" than this?  we are the backoffice API, and aren't worried
247   # about version migration ala cust_main/cust_location here
248   my $cust_refund = new FS::cust_refund { %opt };
249   my $error = $cust_refund->insert;
250   return { 'error'     => $error,
251            'refundnum' => $cust_refund->refundnum,
252          };
253 }
254
255 # pass the phone number ( from svc_phone ) 
256 sub insert_refund_phonenum {
257   my($class, %opt) = @_;
258   my $conf = new FS::Conf;
259   return { 'error' => 'Incorrect shared secret' }
260     unless $opt{secret} eq $conf->config('api_shared_secret');
261
262   $class->_by_phonenum('insert_refund', %opt);
263
264 }
265
266 #---
267
268 # "2 way syncing" ?  start with non-sync pulling info here, then if necessary
269 # figure out how to trigger something when those things change
270
271 # long-term: package changes?
272
273 =item new_customer
274
275 Creates a new customer. Takes a hash reference as parameter with the following keys:
276
277 =over 4
278
279 =item secret
280
281 API Secret
282
283 =item first
284
285 first name (required)
286
287 =item last
288
289 last name (required)
290
291 =item ss
292
293 (not typically collected; mostly used for ACH transactions)
294
295 =item company
296
297 Company name
298
299 =item address1 (required)
300
301 Address line one
302
303 =item city (required)
304
305 City
306
307 =item county
308
309 County
310
311 =item state (required)
312
313 State
314
315 =item zip (required)
316
317 Zip or postal code
318
319 =item country
320
321 2 Digit Country Code
322
323 =item latitude
324
325 latitude
326
327 =item Longitude
328
329 longitude
330
331 =item geocode
332
333 Currently used for third party tax vendor lookups
334
335 =item censustract
336
337 Used for determining FCC 477 reporting
338
339 =item censusyear
340
341 Used for determining FCC 477 reporting
342
343 =item daytime
344
345 Daytime phone number
346
347 =item night
348
349 Evening phone number
350
351 =item fax
352
353 Fax number
354
355 =item mobile
356
357 Mobile number
358
359 =item invoicing_list
360
361 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),
362 postal_invoicing
363 Set to 1 to enable postal invoicing
364
365 =item payby
366
367 CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY
368
369 =item payinfo
370
371 Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL
372
373 =item paycvv
374
375 Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch)
376
377 =item paydate
378
379 Expiration date for CARD/DCRD
380
381 =item payname
382
383 Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK
384
385 =item referral_custnum
386
387 referring customer number
388
389 =item agentnum
390
391 Agent number
392
393 =item agent_custid
394
395 Agent specific customer number
396
397 =item referral_custnum
398
399 Referring customer number
400
401
402 =cut
403
404 #certainly false laziness w/ClientAPI::Signup new_customer/new_customer_minimal
405 # but approaching this from a clean start / back-office perspective
406 #  i.e. no package/service, no immediate credit card run, etc.
407
408 sub new_customer {
409   my( $class, %opt ) = @_;
410
411   my $conf = new FS::Conf;
412   return { 'error' => 'Incorrect shared secret' }
413     unless $opt{secret} eq $conf->config('api_shared_secret');
414
415   #default agentnum like signup_server-default_agentnum?
416   #$opt{agentnum} ||= $conf->config('signup_server-default_agentnum');
417  
418   #same for refnum like signup_server-default_refnum
419   $opt{refnum} ||= $conf->config('signup_server-default_refnum');
420
421   $class->API_insert( %opt );
422 }
423
424 =back 
425
426 =item customer_info
427
428 Returns general customer information. Takes a hash reference as parameter with the following keys: custnum and API secret 
429
430 =cut
431
432 sub customer_info {
433   my( $class, %opt ) = @_;
434   my $conf = new FS::Conf;
435   return { 'error' => 'Incorrect shared secret' }
436     unless $opt{secret} eq $conf->config('api_shared_secret');
437
438   my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} })
439     or return { 'error' => 'Unknown custnum' };
440
441   $cust_main->API_getinfo;
442 :
443
444 =item location_info
445
446 Returns location specific information for the customer. Takes a hash reference as parameter with the following keys: custnum,secret
447
448 =back
449
450 =cut
451
452 #I also monitor for changes to the additional locations that are applied to
453 # packages, and would like for those to be exportable as well.  basically the
454 # location data passed with the custnum.
455
456 sub location_info {
457   my( $class, %opt ) = @_;
458   my $conf = new FS::Conf;
459   return { 'error' => 'Incorrect shared secret' }
460     unless $opt{secret} eq $conf->config('api_shared_secret');
461
462   my @cust_location = qsearch('cust_location', { 'custnum' => $opt{custnum} });
463
464   my %return = (
465     'error'           => '',
466     'locations'       => [ map $_->hashref, @cust_location ],
467   );
468
469   return \%return;
470 }
471
472 #Advertising sources?
473
474
475 1;