File Coverage

blib/lib/Net/Ikano.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Net::Ikano;
2              
3 1     1   27919 use warnings;
  1         3  
  1         33  
4 1     1   7 use strict;
  1         3  
  1         38  
5 1     1   649 use Net::Ikano::XMLUtil;
  0            
  0            
6             use LWP::UserAgent;
7             use Data::Dumper;
8              
9             =head1 NAME
10              
11             Net::Ikano - Interface to Ikano wholesale DSL API
12              
13             =head1 VERSION
14              
15             Version 0.01
16              
17             =cut
18              
19             our $VERSION = '0.01';
20              
21             our $URL = 'https://orders.value.net/OsirisWebService/XmlApi.aspx';
22              
23             our $SCHEMA_ROOT = 'https://orders.value.net/osiriswebservice/schema/v1';
24              
25             our $API_VERSION = "1.0";
26              
27             our @orderType = qw( NEW CANCEL CHANGE );
28              
29             our @orderStatus = qw( NEW PENDING CANCELLED COMPLETED ERROR );
30              
31             our $AUTOLOAD;
32              
33             =head1 SYNOPSIS
34              
35             use Net::Ikano;
36              
37             my $ikano = Net::Ikano->new(
38             'keyid' => $your_ikano_api_keyid,
39             'password' => $your_ikano_admin_user_password,
40             'debug' => 1 # remove this for prod
41             'reqpreviewonly' => 1 # remove this for prod
42             'minimalQualResp' => 1 # on quals, return pairs of ProductCustomId+TermsId only
43             'minimalOrderResp' => 1 # return minimal data on order responses
44             );
45              
46             =head1 SUPPORTED API METHODS
47              
48             =over 4
49              
50             =item ORDER
51              
52             NOTE: supports orders by ProductCustomId only
53              
54             $ikano->ORDER(
55             {
56             orderType => 'NEW',
57             ProductCustomId => 'abc123',
58             TermsId => '123',
59             DSLPhoneNumber => '4167800000',
60             Password => 'abc123',
61             PrequalId => '12345',
62             CompanyName => 'abc co',
63             FirstName => 'first',
64             LastName => 'last',
65             MiddleName => '',
66             ContactMethod => 'PHONE',
67             ContactPhoneNumber => '4167800000',
68             ContactEmail => 'x@x.ca',
69             ContactFax => '',
70             DateToOrder => '2010-11-29',
71             RequestClientIP => '127.0.0.1',
72             IspChange => 'NO',
73             IspPrevious => '',
74             CurrentProvider => '',
75             }
76             );
77              
78              
79             =item CANCEL
80              
81             $i->CANCEL(
82             { OrderId => 555 }
83             );
84              
85              
86             =item PREQUAL
87              
88             $ikano->PREQUAL( {
89             AddressLine1 => '123 Test Rd',
90             AddressUnitType => '',
91             AddressUnitValue => '',
92             AddressCity => 'Toronto',
93             AddressState => 'ON',
94             ZipCode => 'M6C 2J9', # or 12345
95             Country => 'CA', # or US
96             LocationType => 'R', # or B
97             PhoneNumber => '4167800000',
98             RequestClientIP => '127.0.0.1',
99             CheckNetworks => 'ATT,BELLCA,VER', # either one or command-separated like this
100             } );
101              
102              
103             =item ORDERSTATUS
104              
105             $ikano->ORDERSTATUS(
106             { OrderId => 1234 }
107             );
108              
109              
110             =item PASSWORDCHANGE
111              
112             $ikano->PASSWORDCHANGE( {
113             DSLPhoneNumber => '4167800000',
114             NewPassword => 'xxx',
115             } );
116              
117              
118             =item CUSTOMERLOOKUP
119              
120             $ikano->CUSTOMERLOOKUP( { PhoneNumber => '4167800000' } );
121              
122              
123             =item ACCOUNTSTATUSCHANGE
124              
125             $ikano->ACCOUNTSTATUSCHANGE(( {
126             type => 'SUSPEND',
127             DSLPhoneNumber => '4167800000',
128             DSLServiecId => 123,
129             } );
130              
131             =back
132              
133             =cut
134              
135             sub new {
136             my ($class,%data) = @_;
137             die "missing keyid and/or password"
138             unless defined $data{'keyid'} && defined $data{'password'};
139             my $self = {
140             'keyid' => $data{'keyid'},
141             'password' => $data{'password'},
142             'username' => $data{'username'} ? $data{'username'} : 'admin',
143             'debug' => $data{'debug'} ? $data{'debug'} : 0,
144             'reqpreviewonly' => $data{'reqpreviewonly'} ? $data{'reqpreviewonly'} : 0,
145             };
146             bless $self, $class;
147             return $self;
148             }
149              
150              
151             sub req_ORDER {
152             my ($self, $args) = (shift, shift);
153              
154             return "invalid order data" unless defined $args->{orderType}
155             && defined $args->{ProductCustomId} && defined $args->{DSLPhoneNumber};
156             return "invalid order type ".$args->{orderType}
157             unless grep($_ eq $args->{orderType}, @orderType);
158              
159             # XXX: rewrite this uglyness?
160             my @ignoreFields = qw( orderType ProductCustomId );
161             my %orderArgs = ();
162             while ( my ($k,$v) = each(%$args) ) {
163             $orderArgs{$k} = [ $v ] unless grep($_ eq $k,@ignoreFields);
164             }
165              
166             return Order => {
167             type => $args->{orderType},
168             %orderArgs,
169             ProductCustomId => [ split(',',$args->{ProductCustomId}) ],
170             };
171             }
172              
173             sub resp_ORDER {
174             my ($self, $resphash, $reqhash) = (shift, shift);
175             return "invalid order response" unless defined $resphash->{OrderResponse};
176             return $resphash->{OrderResponse};
177             }
178              
179             sub req_CANCEL {
180             my ($self, $args) = (shift, shift);
181              
182             return "no order id for cancel" unless defined $args->{OrderId};
183              
184             return Cancel => {
185             OrderId => [ $args->{OrderId} ],
186             };
187             }
188              
189             sub resp_CANCEL {
190             my ($self, $resphash, $reqhash) = (shift, shift);
191             return "invalid cancel response" unless defined $resphash->{OrderResponse};
192             return $resphash->{OrderResponse};
193             }
194              
195             sub req_ORDERSTATUS {
196             my ($self, $args) = (shift, shift);
197              
198             return "ORDERSTATUS is supported by OrderId only"
199             if defined $args->{PhoneNumber} || !defined $args->{OrderId};
200              
201             return OrderStatus => {
202             OrderId => [ $args->{OrderId} ],
203             };
204             }
205              
206             sub resp_ORDERSTATUS {
207             my ($self, $resphash, $reqhash) = (shift, shift);
208             return "invalid order response" unless defined $resphash->{OrderResponse};
209             return $resphash->{OrderResponse};
210             }
211              
212             sub req_ACCOUNTSTATUSCHANGE {
213             my ($self, $args) = (shift, shift);
214             return "invalid account status change request" unless defined $args->{type}
215             && defined $args->{DSLServiceId} && defined $args->{DSLPhoneNumber};
216              
217             return AccountStatusChange => {
218             type => $args->{type},
219             DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
220             DSLServiceId => [ $args->{DSLServiceId} ],
221             };
222             }
223              
224             sub resp_ACCOUNTSTATUSCHANGE {
225             my ($self, $resphash, $reqhash) = (shift, shift);
226             return "invalid account status change response"
227             unless defined $resphash->{AccountStatusChangeResponse}
228             && defined $resphash->{AccountStatusChangeResponse}->{Customer};
229             return $resphash->{AccountStatusChangeResponse}->{Customer};
230             }
231              
232             sub req_CUSTOMERLOOKUP {
233             my ($self, $args) = (shift, shift);
234             return "invalid customer lookup request" unless defined $args->{PhoneNumber};
235             return CustomerLookup => {
236             PhoneNumber => [ $args->{PhoneNumber} ],
237             };
238             }
239              
240             sub resp_CUSTOMERLOOKUP {
241             my ($self, $resphash, $reqhash) = (shift, shift);
242             return "invalid customer lookup response"
243             unless defined $resphash->{CustomerLookupResponse}
244             && defined $resphash->{CustomerLookupResponse}->{Customer};
245             return $resphash->{CustomerLookupResponse}->{Customer};
246             }
247              
248             sub req_PASSWORDCHANGE {
249             my ($self, $args) = (shift, shift);
250             return "invalid arguments to PASSWORDCHANGE"
251             unless defined $args->{DSLPhoneNumber} && defined $args->{NewPassword};
252              
253             return PasswordChange => {
254             DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
255             NewPassword => [ $args->{NewPassword} ],
256             };
257             }
258              
259             sub resp_PASSWORDCHANGE {
260             my ($self, $resphash, $reqhash) = (shift, shift);
261             return "invalid change password response"
262             unless defined $resphash->{ChangePasswordResponse}
263             && defined $resphash->{ChangePasswordResponse}->{Customer};
264             $resphash->{ChangePasswordResponse}->{Customer};
265             }
266              
267             sub req_PREQUAL {
268             my ($self, $args) = (shift, shift);
269             return PreQual => {
270             Address => [ { (
271             map { $_ => [ $args->{$_} ] }
272             qw( AddressLine1 AddressUnitType AddressUnitValue AddressCity
273             AddressState ZipCode LocationType Country )
274             ) } ],
275             ( map { $_ => [ $args->{$_} ] } qw( PhoneNumber RequestClientIP ) ),
276             CheckNetworks => [ {
277             Network => [ split(',',$args->{CheckNetworks}) ]
278             } ],
279             };
280             }
281              
282             sub resp_PREQUAL {
283             my ($self, $resphash, $reqhash) = (shift, shift);
284             return "invalid prequal response" unless defined $resphash->{PreQualResponse};
285             return $resphash->{PreQualResponse};
286             }
287              
288             sub orderTypes {
289             @orderType;
290             }
291              
292             sub AUTOLOAD {
293             my $self = shift;
294            
295             $AUTOLOAD =~ /(^|::)(\w+)$/ or die "invalid AUTOLOAD: $AUTOLOAD";
296             my $cmd = $2;
297             return if $cmd eq 'DESTROY';
298              
299             my $reqsub = "req_$cmd";
300             my $respsub = "resp_$cmd";
301             die "invalid request type $cmd"
302             unless defined &$reqsub && defined &$respsub;
303              
304             my $reqargs = shift;
305              
306             my $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => 1 );
307             my $reqhash = {
308             OsirisRequest => {
309             type => $cmd,
310             keyid => $self->{keyid},
311             username => $self->{username},
312             password => $self->{password},
313             version => $API_VERSION,
314             xmlns => "$SCHEMA_ROOT/osirisrequest.xsd",
315             $self->$reqsub($reqargs),
316             }
317             };
318              
319              
320             my $reqxml = "\n".$xs->XMLout($reqhash, NoSort => 1);
321            
322             # XXX: validate against their schema to ensure we're not sending invalid XML?
323              
324             warn "DEBUG REQUEST\n\tHASH:\n ".Dumper($reqhash)."\n\tXML:\n $reqxml \n\n"
325             if $self->{debug};
326            
327             my $ua = LWP::UserAgent->new;
328              
329             return "posting disabled for testing" if $self->{reqpreviewonly};
330              
331             my $resp = $ua->post($URL, Content_Type => 'text/xml', Content => $reqxml);
332             return "invalid HTTP response from Ikano: " . $resp->status_line
333             unless $resp->is_success;
334             my $respxml = $resp->decoded_content;
335              
336             $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => '',
337             ForceArray => [ 'Address', 'Network', 'Product', 'StaticIp', 'OrderNotes' ] );
338             my $resphash = $xs->XMLin($respxml);
339              
340             warn "DEBUG RESPONSE\n\tHASH:\n ".Dumper($resphash)."\n\tXML:\n $respxml"
341             if $self->{debug};
342              
343             # XXX: validate against their schema to ensure they didn't send us invalid XML?
344              
345             return "invalid response received from Ikano"
346             unless defined $resphash->{responseid} && defined $resphash->{version}
347             && defined $resphash->{type};
348              
349             return "FAILURE response received from Ikano: "
350             . $resphash->{FailureResponse}->{FailureMessage}
351             if $resphash->{type} eq 'FAILURE';
352              
353             return "invalid response type ".$resphash->{type}." for request type $cmd"
354             unless ( $cmd eq $resphash->{type}
355             || ($cmd eq 'ORDER' && $resphash->{type} =~ /(NEW|CHANGE|CANCEL)ORDER/ )
356             || ($cmd eq "CANCEL" && $resphash->{type} eq "ORDERCANCEL")
357             );
358              
359             return $self->$respsub($resphash,$reqhash);
360             }
361              
362              
363             =head1 AUTHOR
364              
365             Original Author: Erik Levinson
366              
367             Current Maintainer: Ivan Kohler C<< >>
368              
369             =head1 BUGS
370              
371             Please report any bugs or feature requests to C, or through
372             the web interface at L. I will be notified, and then you'll
373             automatically be notified of progress on your bug as I make changes.
374              
375             =head1 SUPPORT
376              
377             You can find documentation for this module with the perldoc command.
378              
379             perldoc Net::Ikano
380              
381             You can also look for information at:
382              
383             =over 4
384              
385             =item * RT: CPAN's request tracker
386              
387             L
388              
389             =item * AnnoCPAN: Annotated CPAN documentation
390              
391             L
392              
393             =item * CPAN Ratings
394              
395             L
396              
397             =item * Search CPAN
398              
399             L
400              
401             =back
402              
403             =head1 COPYRIGHT & LICENSE
404              
405             Copyright 2010-2011 Freeside Internet Services, Inc.
406             All rights reserved.
407              
408             This program is free software; you can redistribute it and/or modify it
409             under the same terms as Perl itself.
410              
411             =head1 ADVERTISEMENT
412              
413             Need a complete, open-source back-office and customer self-service solution?
414             The Freeside software includes support for Ikano integration,
415             invoicing, credit card and electronic check processing, integrated trouble
416             ticketing, and customer signup and self-service web interfaces.
417              
418             http://freeside.biz/freeside/
419              
420             =cut
421              
422             1;
423