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";
27 our @orderType = qw( NEW CANCEL CHANGE );
29 our @orderStatus = qw( NEW PENDING CANCELLED COMPLETED ERROR );
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
46 =head1 SUPPORTED API METHODS
52 NOTE: supports orders by ProductCustomId only
57 ProductCustomId => 'abc123',
59 DSLPhoneNumber => '4167800000',
62 CompanyName => 'abc co',
66 ContactMethod => 'PHONE',
67 ContactPhoneNumber => '4167800000',
68 ContactEmail => 'x@x.ca',
70 DateToOrder => '2010-11-29',
71 RequestClientIP => '127.0.0.1',
74 CurrentProvider => '',
89 AddressLine1 => '123 Test Rd',
90 AddressUnitType => '',
91 AddressUnitValue => '',
92 AddressCity => 'Toronto',
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
112 $ikano->PASSWORDCHANGE( {
113 DSLPhoneNumber => '4167800000',
114 NewPassword => 'xxx',
120 $ikano->CUSTOMERLOOKUP( { PhoneNumber => '4167800000' } );
123 =item ACCOUNTSTATUSCHANGE
125 $ikano->ACCOUNTSTATUSCHANGE(( {
127 DSLPhoneNumber => '4167800000',
136 my ($class,%data) = @_;
137 die "missing keyid and/or password"
138 unless defined $data{'keyid'} && defined $data{'password'};
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,
152 my ($self, $args) = (shift, shift);
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);
159 # XXX: rewrite this uglyness?
160 my @ignoreFields = qw( orderType ProductCustomId );
162 while ( my ($k,$v) = each(%$args) ) {
163 $orderArgs{$k} = [ $v ] unless grep($_ eq $k,@ignoreFields);
167 type => $args->{orderType},
169 ProductCustomId => [ split(',',$args->{ProductCustomId}) ],
174 my ($self, $resphash, $reqhash) = (shift, shift);
175 return "invalid order response" unless defined $resphash->{OrderResponse};
176 return $resphash->{OrderResponse};
180 my ($self, $args) = (shift, shift);
182 return "no order id for cancel" unless defined $args->{OrderId};
185 OrderId => [ $args->{OrderId} ],
190 my ($self, $resphash, $reqhash) = (shift, shift);
191 return "invalid cancel response" unless defined $resphash->{OrderResponse};
192 return $resphash->{OrderResponse};
195 sub req_ORDERSTATUS {
196 my ($self, $args) = (shift, shift);
198 return "ORDERSTATUS is supported by OrderId only"
199 if defined $args->{PhoneNumber} || !defined $args->{OrderId};
201 return OrderStatus => {
202 OrderId => [ $args->{OrderId} ],
206 sub resp_ORDERSTATUS {
207 my ($self, $resphash, $reqhash) = (shift, shift);
208 return "invalid order response" unless defined $resphash->{OrderResponse};
209 return $resphash->{OrderResponse};
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};
217 return AccountStatusChange => {
218 type => $args->{type},
219 DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
220 DSLServiceId => [ $args->{DSLServiceId} ],
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};
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} ],
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};
248 sub req_PASSWORDCHANGE {
249 my ($self, $args) = (shift, shift);
250 return "invalid arguments to PASSWORDCHANGE"
251 unless defined $args->{DSLPhoneNumber} && defined $args->{NewPassword};
253 return PasswordChange => {
254 DSLPhoneNumber => [ $args->{DSLPhoneNumber} ],
255 NewPassword => [ $args->{NewPassword} ],
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};
268 my ($self, $args) = (shift, shift);
271 map { $_ => [ $args->{$_} ] }
272 qw( AddressLine1 AddressUnitType AddressUnitValue AddressCity
273 AddressState ZipCode LocationType Country )
275 ( map { $_ => [ $args->{$_} ] } qw( PhoneNumber RequestClientIP ) ),
277 Network => [ split(',',$args->{CheckNetworks}) ]
283 my ($self, $resphash, $reqhash) = (shift, shift);
284 return "invalid prequal response" unless defined $resphash->{PreQualResponse};
285 return $resphash->{PreQualResponse};
295 $AUTOLOAD =~ /(^|::)(\w+)$/ or die "invalid AUTOLOAD: $AUTOLOAD";
297 return if $cmd eq 'DESTROY';
299 my $reqsub = "req_$cmd";
300 my $respsub = "resp_$cmd";
301 die "invalid request type $cmd"
302 unless defined &$reqsub && defined &$respsub;
306 my $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => 1 );
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),
320 my $reqxml = "<?xml version=\"1.0\"?>\n".$xs->XMLout($reqhash, NoSort => 1);
322 # XXX: validate against their schema to ensure we're not sending invalid XML?
324 warn "DEBUG REQUEST\n\tHASH:\n ".Dumper($reqhash)."\n\tXML:\n $reqxml \n\n"
327 my $ua = LWP::UserAgent->new;
329 return "posting disabled for testing" if $self->{reqpreviewonly};
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;
336 $xs = new Net::Ikano::XMLUtil(RootName => undef, SuppressEmpty => '',
337 ForceArray => [ 'Address', 'Network', 'Product', 'StaticIp', 'OrderNotes' ] );
338 my $resphash = $xs->XMLin($respxml);
340 warn "DEBUG RESPONSE\n\tHASH:\n ".Dumper($resphash)."\n\tXML:\n $respxml"
343 # XXX: validate against their schema to ensure they didn't send us invalid XML?
345 return "invalid response received from Ikano"
346 unless defined $resphash->{responseid} && defined $resphash->{version}
347 && defined $resphash->{type};
349 return "FAILURE response received from Ikano: "
350 . $resphash->{FailureResponse}->{FailureMessage}
351 if $resphash->{type} eq 'FAILURE';
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")
359 return $self->$respsub($resphash,$reqhash);
365 Erik Levinson, C<< <levinse at freeside.biz> >>
369 Please report any bugs or feature requests to C<bug-net-ikano at rt.cpan.org>, or through
370 the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-Ikano>. I will be notified, and then you'll
371 automatically be notified of progress on your bug as I make changes.
375 You can find documentation for this module with the perldoc command.
379 You can also look for information at:
383 =item * RT: CPAN's request tracker
385 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Ikano>
387 =item * AnnoCPAN: Annotated CPAN documentation
389 L<http://annocpan.org/dist/Net-Ikano>
393 L<http://cpanratings.perl.org/d/Net-Ikano>
397 L<http://search.cpan.org/dist/Net-Ikano>
401 =head1 COPYRIGHT & LICENSE
403 Copyright 2010 Freeside Internet Services, Inc.
406 This program is free software; you can redistribute it and/or modify it
407 under the same terms as Perl itself.
411 Need a complete, open-source back-office and customer self-service solution?
412 The Freeside software includes support for Ikano integration,
413 invoicing, credit card and electronic check processing, integrated trouble
414 ticketing, and customer signup and self-service web interfaces.
416 http://freeside.biz/freeside/