5 use Net::Ikano::XMLUtil;
11 Net::Ikano - Interface to Ikano wholesale DSL API
19 our $VERSION = '0.01';
21 our $URL = 'https://orders.value.net/OsirisWebService/XmlApi.aspx';
23 our $SCHEMA_ROOT = 'https://orders.value.net/osiriswebservice/schema/v1';
25 our $API_VERSION = "1.0";
33 my $ikano = Net::Ikano->new(
34 'keyid' => $your_ikano_api_keyid,
35 'password' => $your_ikano_admin_user_password,
36 'debug' => 1 # remove this for prod
37 'reqpreviewonly' => 1 # remove this for prod
38 'minimalQualResp' => 1 # on quals, return pairs of ProductCustomId+TermsId only
39 'minimalOrderResp' => 1 # return minimal data on order responses
42 =head1 SUPPORTED API METHODS
46 NOTE: supports orders by ProductCustomId only
51 ProductCustomId => 'abc123',
53 DSLPhoneNumber => '4167800000',
56 CompanyName => 'abc co',
60 ContactMethod => 'PHONE',
61 ContactPhoneNumber => '4167800000',
62 ContactEmail => 'x@x.ca',
64 DateToOrder => '2010-11-29',
65 RequestClientIP => '127.0.0.1',
68 CurrentProvider => '',
83 AddressLine1 => '123 Test Rd',
84 AddressUnitType => '',
85 AddressUnitValue => '',
86 AddressCity => 'Toronto',
88 ZipCode => 'M6C 2J9', # or 12345
89 Country => 'CA', # or US
90 LocationType => 'R', # or B
91 PhoneNumber => '4167800000',
92 RequestClientIP => '127.0.0.1',
93 CheckNetworks => 'ATT,BELLCA,VER', # either one or command-separated like this
106 $ikano->PASSWORDCHANGE( {
107 DSLPhoneNumber => '4167800000',
108 NewPassword => 'xxx',
114 $ikano->CUSTOMERLOOKUP( { PhoneNumber => '4167800000' } );
117 =item ACCOUNTSTATUSCHANGE
119 $ikano->ACCOUNTSTATUSCHANGE(( {
121 DSLPhoneNumber => '4167800000',
128 my ($class,%data) = @_;
129 die "missing keyid and/or password"
130 unless defined $data{'keyid'} && defined $data{'password'};
132 'keyid' => $data{'keyid'},
133 'password' => $data{'password'},
134 'username' => $data{'username'} ? $data{'username'} : 'admin',
135 'debug' => $data{'debug'} ? $data{'debug'} : 0,
136 'reqpreviewonly' => $data{'reqpreviewonly'} ? $data{'reqpreviewonly'} : 0,
144 my ($self, $args) = (shift, shift);
146 my @validOrderTypes = qw( NEW CHANGE CANCEL );
148 return "invalid order data" unless defined $args->{orderType}
149 && defined $args->{ProductCustomId} && defined $args->{DSLPhoneNumber};
150 return "invalid order type ".$args->{orderType}
151 unless grep($_ eq $args->{orderType}, @validOrderTypes);
153 # XXX: rewrite this uglyness?
154 my @ignoreFields = qw( orderType ProductCustomId );
156 while ( my ($k,$v) = each(%$args) ) {
157 $orderArgs{$k} = [ $v ] unless grep($_ eq $k,@ignoreFields);
161 type => $args->{orderType},
163 ProductCustomId => [ split(',',$args->{ProductCustomId}) ],
168 my ($self, $resphash, $reqhash) = (shift, shift);
169 return "invalid order response" unless defined $resphash->{OrderResponse};
170 return $resphash->{OrderResponse};
174 my ($self, $args) = (shift, shift);
176 return "no order id for cancel" unless defined $args->{OrderId};
179 OrderId => [ $args->{OrderId} ],
184 my ($self, $resphash, $reqhash) = (shift, shift);
185 return "invalid cancel response" unless defined $resphash->{OrderResponse};
186 return $resphash->{OrderResponse};
189 sub req_ORDERSTATUS {
190 my ($self, $args) = (shift, shift);
192 return "ORDERSTATUS is supported by OrderId only"
193 if defined $args->{PhoneNumber} || !defined $args->{OrderId};
195 return OrderStatus => {
196 OrderId => [ $args->{OrderId} ],
200 sub resp_ORDERSTATUS {
201 my ($self, $resphash, $reqhash) = (shift, shift);
202 return "invalid order response" unless defined $resphash->{OrderResponse};
203 return $resphash->{OrderResponse};
206 sub req_ACCOUNTSTATUSCHANGE {
207 my ($self, $args) = (shift, shift);
208 return "invalid account status change request" unless defined $args->{type}
209 && defined $args->{DSLServiceId} && defined $args->{DSLPhoneNumber};
211 return AccountStatusChange => {
212 type => $args->{type},
213 DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
214 DSLServiceId => [ $args->{DSLServiceId} ],
218 sub resp_ACCOUNTSTATUSCHANGE {
219 my ($self, $resphash, $reqhash) = (shift, shift);
220 return "invalid account status change response"
221 unless defined $resphash->{AccountStatusChangeResponse}
222 && defined $resphash->{AccountStatusChangeResponse}->{Customer};
223 return $resphash->{AccountStatusChangeResponse}->{Customer};
226 sub req_CUSTOMERLOOKUP {
227 my ($self, $args) = (shift, shift);
228 return "invalid customer lookup request" unless defined $args->{PhoneNumber};
229 return CustomerLookup => {
230 PhoneNumber => [ $args->{PhoneNumber} ],
234 sub resp_CUSTOMERLOOKUP {
235 my ($self, $resphash, $reqhash) = (shift, shift);
236 return "invalid customer lookup response"
237 unless defined $resphash->{CustomerLookupResponse}
238 && defined $resphash->{CustomerLookupResponse}->{Customer};
239 return $resphash->{CustomerLookupResponse}->{Customer};
242 sub req_PASSWORDCHANGE {
243 my ($self, $args) = (shift, shift);
244 return "invalid arguments to PASSWORDCHANGE"
245 unless defined $args->{DSLPhoneNumber} && defined $args->{NewPassword};
247 return PasswordChange => {
248 DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
249 NewPassword => [ $args->{NewPassword} ],
253 sub resp_PASSWORDCHANGE {
254 my ($self, $resphash, $reqhash) = (shift, shift);
255 return "invalid change password response"
256 unless defined $resphash->{ChangePasswordResponse};
257 return $resphash->{ChangePasswordResponse};
261 my ($self, $args) = (shift, shift);
264 map { $_ => [ $args->{$_} ] }
265 qw( AddressLine1 AddressUnitType AddressUnitValue AddressCity
266 AddressState ZipCode LocationType Country )
268 ( map { $_ => [ $args->{$_} ] } qw( PhoneNumber RequestClientIP ) ),
270 Network => [ split(',',$args->{CheckNetworks}) ]
276 my ($self, $resphash, $reqhash) = (shift, shift);
277 return "invalid prequal response" unless defined $resphash->{PreQualResponse};
278 return $resphash->{PreQualResponse};
284 $AUTOLOAD =~ /(^|::)(\w+)$/ or die "invalid AUTOLOAD: $AUTOLOAD";
286 return if $cmd eq 'DESTROY';
288 my $reqsub = "req_$cmd";
289 my $respsub = "resp_$cmd";
290 die "invalid request type $cmd"
291 unless defined &$reqsub && defined &$respsub;
295 my $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => 1 );
299 keyid => $self->{keyid},
300 username => $self->{username},
301 password => $self->{password},
302 version => $API_VERSION,
303 xmlns => "$SCHEMA_ROOT/osirisrequest.xsd",
304 $self->$reqsub($reqargs),
309 my $reqxml = "<?xml version=\"1.0\"?>\n".$xs->XMLout($reqhash, NoSort => 1);
311 # XXX: validate against their schema to ensure we're not sending invalid XML?
313 warn "DEBUG REQUEST\n\tHASH:\n ".Dumper($reqhash)."\n\tXML:\n $reqxml \n\n"
316 my $ua = LWP::UserAgent->new;
318 return "posting disabled for testing" if $self->{reqpreviewonly};
320 my $resp = $ua->post($URL, Content_Type => 'text/xml', Content => $reqxml);
321 return "invalid HTTP response from Ikano: " . $resp->status_line
322 unless $resp->is_success;
323 my $respxml = $resp->decoded_content;
325 $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => '',
326 ForceArray => [ 'Address', 'Product', 'StaticIp', 'OrderNotes' ] );
327 my $resphash = $xs->XMLin($respxml);
329 warn "DEBUG RESPONSE\n\tHASH:\n ".Dumper($resphash)."\n\tXML:\n $respxml"
332 # XXX: validate against their schema to ensure they didn't send us invalid XML?
334 return "invalid response received from Ikano"
335 unless defined $resphash->{responseid} && defined $resphash->{version}
336 && defined $resphash->{type};
338 return "FAILURE response received from Ikano: "
339 . $resphash->{FailureResponse}->{FailureMessage}
340 if $resphash->{type} eq 'FAILURE';
342 my $validRespTypes = {
343 'PREQUAL' => qw( PREQUAL ),
344 'ORDERSTATUS' => qw( ORDERSTATUS ),
345 'ORDER' => qw( NEWORDER CHANGEORDER CANCELORDER ),
346 'CANCEL' => qw( ORDERCANCEL ),
347 'PASSWORDCHANGE' => qw( PASSWORDCHANGE ),
348 'ACCOUNTSTATUSCHANGE' => qw( ACCOUNTSTATUSCHANGE ),
349 'CUSTOMERLOOKUP' => qw( CUSTOMERLOOKUP ),
352 return "invalid response type ".$resphash->{type}." for request type $cmd"
353 unless grep( $_ eq $resphash->{type}, $validRespTypes->{$cmd});
355 return $self->$respsub($resphash,$reqhash);
361 Erik Levinson, C<< <levinse at freeside.biz> >>
365 Please report any bugs or feature requests to C<bug-net-ikano at rt.cpan.org>, or through
366 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-Ikano>. I will be notified, and then you'll
367 automatically be notified of progress on your bug as I make changes.
371 You can find documentation for this module with the perldoc command.
375 You can also look for information at:
379 =item * RT: CPAN's request tracker
381 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Ikano>
383 =item * AnnoCPAN: Annotated CPAN documentation
385 L<http://annocpan.org/dist/Net-Ikano>
389 L<http://cpanratings.perl.org/d/Net-Ikano>
393 L<http://search.cpan.org/dist/Net-Ikano>
397 =head1 ACKNOWLEDGEMENTS
399 This module was developed by Freeside Internet Services, Inc.
400 If you need a complete, open-source web-based application to manage your
401 customers, billing and trouble ticketing, please visit http://freeside.biz/
403 =head1 COPYRIGHT & LICENSE
405 Copyright 2010 Freeside Internet Services, Inc.
408 This program is free software; you can redistribute it and/or modify it
409 under the same terms as Perl itself.