File Coverage

blib/lib/Business/CanadaPost.pm
Criterion Covered Total %
statement 12 192 6.2
branch 0 76 0.0
condition 0 101 0.0
subroutine 4 36 11.1
pod 26 29 89.6
total 42 434 9.6


line stmt bran cond sub pod time code
1             package Business::CanadaPost;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: Fetch shipping costs for Canada Post (DEPRECATED)
4             $Business::CanadaPost::VERSION = '1.07';
5 2     2   461009 use strict;
  2         3  
  2         76  
6 2     2   1268 use LWP;
  2         103424  
  2         97  
7 2     2   23 use vars qw($VERSION @ISA @EXPORT);
  2         3  
  2         140  
8 2     2   11 use Exporter;
  2         4  
  2         7285  
9              
10             @ISA = qw(Exporter);
11             @EXPORT = qw();
12              
13              
14             sub new # {{{
15             {
16 0     0 1   my ($class, %data) = @_;
17              
18 0           my $self = {
19             language => 'en', #canada post supports english (en) and french (fr)
20             frompostalcode => '', #canada post says to send a space if we have no entry...
21             turnaroundtime => '',
22             merchantid => '',
23             totalprice => '0.00',
24             units => 'metric', #allows for metric (cm and kg) or imperial (in and lb) measurements.
25             items => [],
26             testing => 0
27             };
28              
29 0           foreach (keys %data)
30             {
31 0           $self->{$_} = $data{$_};
32             }
33              
34 0           bless $self, $class;
35              
36 0           return $self;
37             } # }}}
38              
39              
40             sub geterror # {{{
41             {
42 0     0 1   my $self = shift;
43 0           my $error = $self->{'error'};
44 0           $self->{'error'} = ''; #clear it once we've sent it.
45 0           return $error;
46             } # }}}
47              
48             sub setlanguage # {{{
49             {
50 0     0 1   my ($self, $lang) = @_;
51              
52 0           $lang = lc($lang);
53 0 0 0       return $self->_error(4) unless $lang eq 'fr' or $lang eq 'en' or $lang eq '';
      0        
54              
55 0   0       $self->{'language'} = $lang || 'en';
56             } # }}}
57              
58              
59             sub settocity # {{{
60              
61             {
62 0     0 1   my ($self, $city) = @_;
63 0           $self->{'city'} = $city;
64             } # }}}
65              
66              
67             sub settesting # {{{
68            
69             {
70 0     0 1   my ($self, $testing) = @_;
71              
72 0           $self->{'testing'} = $testing;
73             } # }}}
74              
75              
76             sub setcountry # {{{
77              
78             {
79 0     0 1   my ($self, $country) = @_;
80 0           $self->{'country'} = $country;
81             } # }}}
82              
83              
84             sub setmerchantid # {{{
85              
86             {
87 0     0 1   my ($self, $id) = @_;
88            
89 0   0       $self->{'merchantid'} = $id || ' ';
90             } # }}}
91              
92              
93             sub setunits # {{{
94              
95             {
96 0     0 1   my ($self, $units) = @_;
97              
98             #FIXME -- make it go through each item and convert to/from metric if they change!
99 0           $units = lc($units);
100 0 0 0       return $self->_error(5) unless $units eq 'metric' or $units eq 'imperial';
101 0           $self->{'units'} = $units;
102             } # }}}
103              
104              
105             sub setfrompostalcode # {{{
106              
107             {
108 0     0 1   my ($self, $code) = @_;
109              
110 0   0       $self->{'frompostalcode'} = $code || ' ';
111             } # }}}
112              
113              
114             sub settopostalzip # {{{
115              
116             {
117 0     0 0   my ($self, $code) = @_;
118              
119 0   0       $self->{'postalcode'} = $code || ' ';
120             } # }}}
121              
122              
123             sub setprovstate # {{{
124              
125             {
126 0     0 1   my ($self, $province) = @_;
127 0   0       $self->{'provstate'} = $province || ' ';
128             } # }}}
129              
130              
131             sub setturnaroundtime # {{{
132              
133             {
134 0     0 1   my ($self, $code) = @_;
135 0   0       $self->{'turnaroundtime'} = $code || ' ';
136             } # }}}
137              
138              
139             sub settotalprice # {{{
140              
141             {
142 0     0 1   my ($self, $price) = @_;
143 0   0       $self->{'totalprice'} = sprintf('%01.2f', $price) || '0.00';
144             } # }}}
145              
146              
147             sub additem # {{{
148             {
149 0     0 1   my ($self, %item) = @_;
150              
151 0 0 0       $item{'length'} and $item{'width'} and $item{'height'} or
      0        
152             return $self->_error(6);
153              
154 0 0         my @currentitems = @{$self->{'items'}} if ref $self->{'items'};
  0            
155              
156             #canadapost specifies that the longest dimension is the length,
157             #second longest is the width and shortest is height.
158 0           my @dimensions = ($item{'length'}, $item{'height'}, $item{'width'});
159 0           ($item{'length'}, $item{'width'}, $item{'height'}) = reverse sort @dimensions;
160              
161 0 0         my $metric = $self->{'units'} eq 'imperial' ? 0 : 1;
162              
163             push (@currentitems, $item{'quantity'} || 1,
164             $metric ? $item{'weight'} : $item{'weight'} * .45359237, # 1lb = .45359237kg
165             $metric ? $item{'length'} : $item{'length'} * 2.54, # 1in = 2.54cm
166             $metric ? $item{'width'} : $item{'width'} * 2.54,
167             $metric ? $item{'height'} : $item{'height'} * 2.54,
168             $item{'description'} || ' ',
169 0 0 0       $item{'readytoship'} ? '' : '');
    0 0        
    0          
    0          
    0          
170              
171 0           $self->{'items'} = \@currentitems;
172             } # }}}
173              
174              
175             sub getrequest # {{{
176             {
177 0     0 1   my $self = shift;
178 0 0         my $xmlfile = $self->buildXML() or return $self->_error($self->{'error'});
179              
180 0           my $lwp = LWP::UserAgent->new();
181 0           my $result = $lwp->post("http://sellonline.canadapost.ca:30000", { 'XMLRequest' => $xmlfile });
182 0 0         return $self->_error(8) unless $result->is_success;
183              
184 0           my $raw_data = $result->content();
185              
186 0           return $self->parseXML($raw_data);
187             } # }}}
188              
189             sub parseXML # {{{
190              
191             {
192 0     0 0   my ($self, $xml) = @_;
193              
194 0           my ($parcel) = $xml =~ /(.+)<\/eparcel>/s;
195 0           my ($resultcode) = $parcel =~ /([^<]+)<\/statusCode>/s;
196 0 0         unless ($resultcode == 1)
197             {
198 0           my ($resultmessage) = $parcel =~ /([^<]+)<\/statusMessage>/s;
199 0           return $self->_error($resultmessage);
200             }
201 0           my ($products) = $parcel =~ //s; #should be greedy and get them all..
202 0           my @options;
203 0           foreach my $product (split /<\/product>\s+
204             {
205 0           my ($name) = $product =~ /([^<]+)<\/name>/s;
206 0           my ($rate) = $product =~ /([^<]+)<\/rate>/s;
207 0           my ($shipdate) = $product =~ /([^<]+)<\/shippingDate>/s;
208 0           my ($delvdate) = $product =~ /([^<]+)<\/deliveryDate>/s;
209 0           my ($dayofweek) = $product =~ /([^<]+)<\/deliveryDayOfWeek>/s;
210 0           my ($nextdayam) = $product =~ /([^<]+)<\/nextDayAM>/s;
211 0           my $estdays = _getdaysbetween($shipdate, $delvdate);
212 0 0         $estdays = 'Unknown' if $estdays == -1;
213 0 0         $nextdayam = $nextdayam eq 'true' ? 1 : 0;
214 0           push (@options, $name, $rate, $shipdate, $delvdate, $dayofweek, $nextdayam, $estdays);
215             }
216              
217 0           $self->{'shippingoptioncount'} = scalar(@options) / 7;
218 0           $self->{'shiprates'} = \@options;
219              
220 0           my ($soptions) = $parcel =~ /(.+)<\/shippingOptions>/s;
221 0 0         if ($soptions =~ /([^<]+)<\/insurance>/)
222             {
223 0 0         $self->{'shipinsurance'} = $1 eq 'No' ? 0 : 1;
224             }
225 0 0         if ($soptions =~ /([^<]+)<\/deliveryConfirmation>/)
226             {
227 0 0         $self->{'shipconfirm'} = $1 eq 'No' ? 0 : 1;
228             }
229 0 0         if ($soptions =~ /([^<]+)<\/signature>/)
230             {
231 0 0         $self->{'signature'} = $1 eq 'No' ? 0 : 1;
232             }
233              
234 0 0         $self->{'shipcomments'} = $1 if $parcel =~ /([^<]+)<\/comment>/s;
235 0           return 1;
236             } # }}}
237              
238              
239             sub getoptioncount # {{{
240             {
241 0     0 1   my $self = shift;
242 0           return $self->{'shippingoptioncount'};
243             } # }}}
244              
245              
246              
247             sub getsignature # {{{
248              
249             {
250 0     0 1   my $self = shift;
251 0           return $self->{'signature'};
252             } # }}}
253              
254              
255             sub getinsurance # {{{
256              
257             {
258 0     0 1   my $self = shift;
259 0           return $self->{'shipinsurance'};
260             } # }}}
261              
262              
263              
264             sub getshipname # {{{
265              
266             {
267 0     0 1   my $self = shift;
268 0   0       my $shipmentnum = shift || 1;
269 0           $shipmentnum--; #we're looking for the offset in the array...
270              
271 0           my @options = @{$self->{'shiprates'}};
  0            
272 0           return $options[$shipmentnum * 7]
273             } # }}}
274              
275              
276             sub getshiprate # {{{
277              
278             {
279 0     0 1   my $self = shift;
280 0   0       my $shipmentnum = shift || 1;
281 0           $shipmentnum--;
282 0           my @options = @{$self->{'shiprates'}};
  0            
283 0           return $options[$shipmentnum * 7 + 1]
284             } # }}}
285              
286              
287              
288             sub getshipdate # {{{
289              
290             {
291 0     0 1   my $self = shift;
292 0   0       my $shipmentnum = shift || 1;
293 0           $shipmentnum--;
294 0           my @options = @{$self->{'shiprates'}};
  0            
295 0           return $options[$shipmentnum * 7 + 2]
296             } # }}}
297              
298              
299             sub getdelvdate # {{{
300              
301             {
302 0     0 1   my $self = shift;
303 0   0       my $shipmentnum = shift || 1;
304 0           $shipmentnum--;
305 0           my @options = @{$self->{'shiprates'}};
  0            
306 0           return $options[$shipmentnum * 7 + 3]
307             } # }}}
308              
309              
310              
311             sub getdayofweek # {{{
312              
313             {
314 0     0 1   my $self = shift;
315 0   0       my $shipmentnum = shift || 1;
316 0           $shipmentnum--;
317 0           my @options = @{$self->{'shiprates'}};
  0            
318 0           return $options[$shipmentnum * 7 + 4]
319             } # }}}
320              
321              
322              
323             sub getnextdayam # {{{
324              
325             {
326 0     0 1   my $self = shift;
327 0   0       my $shipmentnum = shift || 1;
328 0           $shipmentnum--;
329 0           my @options = @{$self->{'shiprates'}};
  0            
330 0           return $options[$shipmentnum * 7 + 5]
331             } # }}}
332              
333              
334              
335             sub getestshipdays # {{{
336              
337             {
338 0     0 1   my $self = shift;
339 0   0       my $shipmentnum = shift || 1;
340 0           $shipmentnum--;
341 0           my @options = @{$self->{'shiprates'}};
  0            
342 0           return $options[$shipmentnum * 7 + 6]
343             } # }}}
344              
345              
346              
347             sub getconfirmation # {{{
348              
349             {
350 0     0 1   my $self = shift;
351 0           return $self->{'shipconfirm'};
352             } # }}}
353              
354              
355             sub getcomments # {{{
356              
357             {
358 0     0 1   my $self = shift;
359 0           return $self->{'shipcomments'};
360             } # }}}
361              
362             sub _error # {{{
363              
364             {
365 0     0     my ($self, $msgnum) = @_;
366 0           my @englishmessages = ('You need to specify some items to ship!',
367             'You must specify a valid postal code for Canadian shipments!',
368             'You must specify a state for American shipments!',
369             'You must specify the country being shipped to!',
370             'Valid languages are English and French',
371             'Valid units are metric (cm and kg) or imperial (in and lb)',
372             'You must specify a height, width, and length for each item.',
373             'You must specify your Canada Post merchant ID!',
374             'Failed sending to Canada Posts servers!');
375 0           my @frenchmessages = ('Vous devez indiquer quelques pour transporter!',
376             'Vous devez indiquer un code postal valide pour les expéditions Canadiannes!',
377             'Vous devez indiquer un état pour les expéditions américaines!',
378             'Vous devez indiquer le pays que vous voulez embarquer à!',
379             'Les langues valides sont Anglaises et Françaises',
380             'Les unités valides sont métriques (cm et kg) ou impériales (po et lv)',
381             'Vous devez indiquer une taille, une largeur, et une longueur pour chaque article.',
382             'Vous devez indiquer votre identification du Postes Canada!',
383             'Envoi échoué aux serveurs du Postes Canada!');
384              
385 0 0         if ($msgnum == 0)
386             {
387 0           push (@englishmessages, $msgnum);
388 0           push (@frenchmessages, $msgnum);
389 0           $msgnum = scalar(@englishmessages) - 1;
390             }
391              
392             $self->{'error'} = sprintf("%s\n",
393 0 0         $self->{'language'} eq 'fr' ? $frenchmessages[$msgnum] :
394             $englishmessages[$msgnum]);
395 0           return 0;
396             } # }}}
397              
398             sub _getdaysbetween # {{{
399              
400             {
401 0     0     my ($fromdate, $todate) = @_;
402 0           my @daysinmonth = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
403              
404 0           my ($fromyear, $frommon, $fromday) = split /-/, $fromdate;
405 0           my ($toyear, $tomon, $today) = split /-/, $todate;
406              
407 0 0 0       return 0 if $fromyear == $toyear and $frommon == $tomon and $fromday == $today;
      0        
408 0 0 0       return -1 unless $fromyear and $frommon and $fromday and $toyear and $tomon and $today;
      0        
      0        
      0        
      0        
409              
410 0           my $days;
411            
412             do
413 0   0       {
      0        
414 0           $days++;
415 0           $fromday++;
416 0 0         $daysinmonth[2] = _isleapyear($fromyear) ? 29 : 28;
417 0 0         $fromday = 1, $frommon++ if $fromday > $daysinmonth[$frommon];
418 0 0         $frommon = 1, $fromyear++ if $frommon == 13;
419             } until $fromyear == $toyear and $frommon == $tomon and $fromday == $today;
420              
421 0           return $days;
422             } # }}}
423              
424             sub _isleapyear # {{{
425              
426             {
427 0     0     my $year = shift;
428              
429 0 0 0       return 1 if $year % 4 == 0 and $year % 400 == 0;
430 0 0         return 0 if $year % 100 == 0;
431 0 0         return 1 if $year % 4 == 0;
432 0           return 0;
433             } # }}}
434              
435             sub buildXML # {{{
436              
437             {
438 0     0 0   my $self = shift;
439            
440 0           my @items = @{$self->{'items'}};
  0            
441 0 0         return $self->_error(0) unless @items;
442              
443             # language can be en or fr (this is Canada!)
444             my $xmlfile = sprintf('
445            
446             %s
447            
448             %s
449             %s
450             %s
451             %.2f%s',
452             $self->{'language'} || 'en',
453             $self->{'merchantid'} || return $self->_error(7),
454             $self->{'frompostalcode'} ? "" . $self->{'frompostalcode'} . "\n" : '',
455             $self->{'turnaroundtime'} ? "" . $self->{'turnaroundtime'} . "\n" : '',
456 0   0       $self->{'totalprice'} || '0.00',
      0        
457             "\n");
458              
459            
460 0           $xmlfile .= " \n";
461 0           for (my $n = 0; $n < @items; $n += 7)
462             {
463 0           $xmlfile .= sprintf("
464             %d
465             %01.2f
466             %01.2f
467             %01.2f
468             %01.2f
469             %s
470             %s
471             \n",
472             @items[$n .. $n+6]);
473             }
474 0           $xmlfile .= " \n";
475              
476 0 0 0       if (!$self->{'country'} or $self->{'country'} =~ /^\s*$/) # no country specified...
    0 0        
    0 0        
      0        
477             {
478 0           return $self->_error(3);
479             }
480             elsif (uc($self->{'country'}) eq 'CANADA' or uc($self->{'country'}) eq 'CA')
481             {
482             #canada post docs state that only postal code must exist for canadian shipments
483 0           $self->{'postalcode'} =~ s/[^\d\w]//g;
484 0 0         $self->{'postalcode'} =~ /^\w\d\w\d\w\d$/
485             or return $self->_error(1);
486              
487             }
488             elsif (uc($self->{'country'}) eq 'UNITED STATES' or uc($self->{'country'} eq 'US')
489             or uc($self->{'country'}) eq 'ÉTATS-UNIS')
490             {
491             #canada post says that all they require for now is country and provorstate; however,
492             #zipcodes will be used in the future...
493 0 0         $self->{'provstate'} or return $self->_error(2);
494 0   0       $self->{'postalcode'} ||= ' ';
495 0           $self->{'postalcode'} =~ s/\D//g;
496             }
497            
498             $xmlfile .= sprintf(" %s
499             %s
500             %s
501             %s
502            
503             ",
504             $self->{'city'} || ' ',
505             $self->{'provstate'} || ' ',
506             $self->{'country'},
507 0   0       $self->{'postalcode'} || ' ');
      0        
      0        
508              
509 0           return $xmlfile;
510             } # }}}
511              
512             1;
513              
514             __END__