Merge branch 'patch-1' of https://github.com/gjones2/Freeside
[freeside.git] / FS / FS / ClientAPI / PrepaidPhone.pm
1 package FS::ClientAPI::PrepaidPhone;
2
3 use strict;
4 use vars qw($DEBUG $me);
5 use FS::Record qw(qsearchs);
6 use FS::Conf;
7 use FS::rate;
8 use FS::svc_phone;
9
10 $DEBUG = 0;
11 $me = '[FS::ClientAPI::PrepaidPhone]';
12
13 #TODO:
14 # - shared-secret auth? (set a conf value)
15
16 =item call_time HASHREF
17
18 HASHREF contains the following parameters:
19
20 =over 4
21
22 =item src
23
24 Source number (with countrycode)
25
26 =item dst
27
28 Destination number (with countrycode)
29
30 =back
31
32 Always returns a hashref.  If there is an error, the hashref contains a single
33 "error" key with the error message as a value.  Otherwise, returns a hashref
34 with the following keys:
35
36 =over 4
37
38 =item custnum
39
40 Empty if no customer is found associated with the number, customer number
41 otherwise.
42
43 =item seconds
44
45 Number of seconds remaining for a call to destination number
46
47 =back
48
49 =cut
50
51 sub call_time {
52   my $packet = shift;
53
54   my $src = $packet->{'src'};
55   my $dst = $packet->{'dst'};
56
57   my $chargeto;
58   my $rateby;
59   #my $conf = new FS::Conf;
60   #if ( #XXX toll-free?  collect?
61   #  $phonenum = $dst;
62   #} else { #use the src to find the customer
63     $chargeto = $src;
64     $rateby = $dst;
65   #}
66
67   my( $countrycode, $phonenum );
68   if ( $chargeto #an interesting regex to parse out 1&2 digit countrycodes
69          =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/
70        || $chargeto =~ /^(\d{3})(\d*)$/
71      )
72   {
73     $countrycode = $1;
74     $phonenum = $2;
75   } else { 
76     return { 'error' => "unparsable billing number: $chargeto" };
77   }
78
79
80   my $svc_phone = qsearchs('svc_phone', { 'countrycode' => $countrycode,
81                                           'phonenum'    => $phonenum,
82                                         }
83                           );
84
85   unless ( $svc_phone ) {
86     return { 'error' => "can't find customer for +$countrycode $phonenum" };
87 #    return { 'custnum' => '',
88 #             'seconds' => 0,
89 #             #'balance' => 0,
90 #           };
91   };
92
93   my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
94   my $cust_main = $cust_pkg->cust_main;
95
96   my $part_pkg = $cust_pkg->part_pkg;
97   my @part_pkg = ( $part_pkg, map $_->dst_pkg, $part_pkg->bill_part_pkg_link );
98   #XXX uuh, behavior indeterminate if you have more than one voip_cdr+prefix
99   #add-on, i guess.
100   warn "$me ". scalar(@part_pkg). ': '.
101        join('/', map { $_->plan. $_->option('rating_method') } @part_pkg )
102     if $DEBUG;
103   @part_pkg =
104     grep { $_->plan eq 'voip_cdr' && $_->option('rating_method') eq 'prefix' }
105          @part_pkg;
106
107   my %return = (
108     'custnum' => $cust_pkg->custnum,
109     #'balance' => $cust_pkg->cust_main->balance,
110   );
111
112   warn "$me: ". scalar(@part_pkg). ': '.
113        join('/', map { $_->plan. $_->option('rating_method') } @part_pkg )
114     if $DEBUG;
115   return \%return unless @part_pkg;
116
117   warn "$me searching for rate ". $part_pkg[0]->option('ratenum')
118     if $DEBUG;
119
120   my $rate = qsearchs('rate', { 'ratenum'=>$part_pkg[0]->option('ratenum') } );
121
122   unless ( $rate ) {
123     my $error = 'ratenum '. $part_pkg[0]->option('ratenum'). ' not found';
124     warn "$me $error"
125       if $DEBUG;
126     return { 'error'=>$error };
127   }
128
129   warn "$me found rate ". $rate->ratenum
130     if $DEBUG;
131
132   #rate the call and arrive at a max # of seconds for the customer's balance
133
134   my( $rate_countrycode, $rate_phonenum );
135   if ( $rateby #this is an interesting regex to parse out 1&2 digit countrycodes
136          =~ /^(2[078]|3[0-469]|4[013-9]|5[1-8]|6[0-6]|7|8[1-469]|9[0-58])(\d*)$/
137        || $rateby =~ /^(\d{3})(\d*)$/
138      )
139   {
140     $rate_countrycode = $1;
141     $rate_phonenum = $2;
142   } else { 
143     return { 'error' => "unparsable rating number: $rateby" };
144   }
145
146   my $rate_detail = $rate->dest_detail({ 'countrycode' => $rate_countrycode,
147                                          'phonenum'    => $rate_phonenum,
148                                        });
149   unless ( $rate_detail ) {
150     return { 'error'=>"can't find rate for +$rate_countrycode $rate_phonenum"};
151   }
152
153   unless ( $rate_detail->min_charge > 0 ) {
154     #XXX no charge??  return lots of seconds, a default, 0 or what?
155     #return { 'error' => '0 rate for +$rate_countrycode $rate_phonenum; prepaid service not available" };
156     #customer wants no default for now# $return{'seconds'} = 1800; #half hour?!
157     return \%return;
158   }
159
160   my $conf = new FS::Conf;
161   my $balance = $conf->config_bool('pkg-balances') ? $cust_pkg->balance
162                                                    : $cust_main->balance;
163
164   #XXX granularity?  included minutes?  another day...
165   if ( $balance >= 0 ) {
166     return { 'error'=>'No balance' };
167   } else {
168     $return{'seconds'} = int(60 * abs($balance) / $rate_detail->min_charge);
169   }
170
171   warn "$me returning seconds: ". $return{'seconds'};
172
173   return \%return;
174  
175 }
176
177 =item call_time_nanpa 
178
179 Like I<call_time>, except countrycode 1 is not required, and all other
180 countrycodes must be prefixed with 011.
181
182 =cut
183
184 # - everything is assumed to be countrycode 1 unless it starts with 011(ccode)
185 sub call_time_nanpa {
186   my $packet = shift;
187
188   foreach (qw( src dst )) {
189     if ( $packet->{$_} =~ /^011(\d+)/ ) {
190       $packet->{$_} = $1;
191     } elsif ( $packet->{$_} !~ /^1/ ) {
192       $packet->{$_} = '1'.$packet->{$_};
193     }
194   }
195
196   call_time($packet);
197
198 }
199
200 =item phonenum_balance HASHREF
201
202 HASHREF contains the following parameters:
203
204 =over 4
205
206 =item countrycode
207
208 Optional countrycode.  Defaults to 1.
209
210 =item phonenum
211
212 Phone number.
213
214 =back
215
216 Always returns a hashref.  If there is an error, the hashref contains a single
217 "error" key with the error message as a value.  Otherwise, returns a hashref
218 with the following keys:
219
220 =over 4
221
222 =item custnum
223
224 Empty if no customer is found associated with the number, customer number
225 otherwise.
226
227 =item balance
228
229 Customer balance.
230
231 =back
232
233 =cut
234
235 sub phonenum_balance {
236   my $packet = shift;
237
238   warn "$me phonenum_balance called with countrycode ".$packet->{'countrycode'}.
239        " and phonenum ". $packet->{'phonenum'}. "\n"
240     if $DEBUG;
241
242   my $svc_phone = qsearchs('svc_phone', {
243     'countrycode' => ( $packet->{'countrycode'} || 1 ),
244     'phonenum'    => $packet->{'phonenum'},
245   });
246
247   unless ( $svc_phone ) {
248     warn "$me no phone number found\n" if $DEBUG;
249     return { 'custnum' => '',
250              'balance' => 0,
251            };
252   };
253
254   my $cust_pkg = $svc_phone->cust_svc->cust_pkg;
255
256   my $conf = new FS::Conf;
257   my $balance = $conf->config_bool('pkg-balances')
258     ? $cust_pkg->balance
259     : $cust_pkg->cust_main->balance;
260
261   warn "$me returning $balance balance for pkgnum ".  $cust_pkg->pkgnum.
262                                         ", custnum ". $cust_pkg->custnum
263     if $DEBUG;
264
265   return {
266     'custnum' => $cust_pkg->custnum,
267     'balance' => $balance,
268   };
269
270 }
271
272 1;