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