File Coverage

blib/lib/Webservice/Shipment/Carrier/UPS.pm
Criterion Covered Total %
statement 67 74 90.5
branch 12 20 60.0
condition n/a
subroutine 18 21 85.7
pod 6 6 100.0
total 103 121 85.1


line stmt bran cond sub pod time code
1             package Webservice::Shipment::Carrier::UPS;
2              
3 2     2   55538 use Mojo::Base 'Webservice::Shipment::Carrier';
  2         10  
  2         13  
4              
5 2     2   881 use Mojo::Template;
  2         5700  
  2         15  
6 2     2   59 use Mojo::URL;
  2         4  
  2         9  
7 2     2   43 use Mojo::IOLoop;
  2         4  
  2         9  
8 2     2   619 use Time::Piece;
  2         10093  
  2         9  
9 2     2   131 use Carp;
  2         3  
  2         111  
10              
11 2     2   11 use constant DEBUG => $ENV{MOJO_SHIPMENT_DEBUG};
  2         3  
  2         1866  
12              
13             has api_key => sub { croak 'api_key is required' };
14             has api_url => sub { Mojo::URL->new('https://wwwcie.ups.com/ups.app/xml/Track') };
15              
16             has template => <<'TEMPLATE';
17             % my ($self, $id) = @_;
18            
19            
20             <%== $self->api_key %>
21             <%== $self->username %>
22             <%== $self->password %>
23            
24            
25            
26            
27            
28             <%== $id %>
29            
30             Track
31            
32             <%== $id %>
33            
34             TEMPLATE
35              
36             has validation_regex => sub { qr/\b(1Z ?[0-9A-Z]{3} ?[0-9A-Z]{3} ?[0-9A-Z]{2} ?[0-9A-Z]{4} ?[0-9A-Z]{3} ?[0-9A-Z]|[\dT]\d\d\d ?\d\d\d\d ?\d\d\d)\b/i };
37              
38             sub extract_destination {
39 24     24 1 49 my ($self, $id, $dom, $target) = @_;
40              
41 24         81 my %targets = (
42             postal_code => 'PostalCode',
43             state => 'StateProvinceCode',
44             city => 'City',
45             country => 'CountryCode',
46             address1 => 'AddressLine1',
47             address2 => 'AddressLine2',
48             );
49              
50 24 50       52 my $t = $targets{$target} or return;
51 24 100       68 my $addr = $dom->at("Shipment ShipTo Address $t") or return;
52 12         9243 return $addr->text;
53             }
54              
55             sub extract_service {
56 4     4 1 14 my ($self, $id, $dom) = @_;
57 4 50       13 my $service = $dom->at('Shipment Service Description') or return;
58 4         3023 my $text = $service->text;
59 4 100       110 $text = "UPS $text" unless $text =~ /UPS/;
60 4         18 return $text;
61             }
62              
63             sub extract_status {
64 4     4 1 13 my ($self, $id, $dom) = @_;
65              
66 4         16 my $activity = $dom->find('Shipment Package Activity');
67 4         5725 my ($status, $date, $delivered);
68              
69             my $to_date = sub {
70 4     4   10 my $dom = shift;
71 4         12 return Time::Piece->strptime(
72             $dom->at('Date')->text . ' T ' . $dom->at('Time')->text,
73             '%Y%m%d T %H%M%S'
74             );
75 4         19 };
76              
77 4     4   23 my $d = $activity->first(sub{ $_->at('Status StatusType Code')->text eq 'D' });
  4         59  
78 4 50       2121 if ($d) {
79 4         24 $status = $d->at('Status StatusType Description')->text;
80 4         2145 $date = $d->$to_date();
81 4         4169 $delivered = 1;
82             } else {
83             my $current = $activity
84 0     0   0 ->map(sub{ [$_, $_->$to_date()] })
85 0     0   0 ->sort(sub{ $b->[1] <=> $a->[1] })
86 0         0 ->[0];
87              
88 0         0 $status = $current->[0]->at('Status StatusType Description');
89 0 0       0 $status = $status ? $status->text : '';
90 0         0 $date = $current->[1];
91             }
92              
93 4         31 return($status, $date, $delivered);
94             }
95              
96             sub extract_weight {
97 4     4 1 12 my ($self, $id, $dom) = @_;
98              
99 4 50       10 my $weight = $dom->at('Shipment ShipmentWeight') or return '';
100 4         2318 return $weight->at('Weight')->text . ' ' . $weight->at('UnitOfMeasurement Code')->text;
101             }
102              
103             sub human_url {
104 5     5 1 13 my ($self, $id, $dom) = @_;
105 5         26 return Mojo::URL->new('http://wwwapps.ups.com/WebTracking/track')->query(trackNums => $id, 'track.x' => 'Track');
106             }
107              
108             sub request {
109 4     4 1 21 my ($self, $id, $cb) = @_;
110              
111 4         24 my $xml = Mojo::Template->new->render($self->template, $self, $id);
112 4         8354 warn "Request:\n$xml" if DEBUG;
113              
114 4 100       13 unless ($cb) {
115 3         10 my $tx = $self->ua->post($self->api_url, $xml);
116 3         7676 return _handle_response($tx);
117             }
118              
119             Mojo::IOLoop->delay(
120 1     1   154 sub { $self->ua->post($self->api_url, $xml, shift->begin) },
121             sub {
122 1     1   2287 my ($ua, $tx) = @_;
123 1 50       9 die $tx->error->{message} unless $tx->success;
124 1         21 my $dom = _handle_response($tx);
125 1         6 $self->$cb(undef, $dom);
126             },
127 1     0   10 )->tap(on => error => sub{ $self->$cb($_[1], undef) })->wait;
  0         0  
128             }
129              
130             sub _handle_response {
131 4     4   9 my $tx = shift;
132 4         11 my $dom = $tx->res->dom;
133 4         16382 warn "Response:\n$dom\n" if DEBUG;
134 4         13 $dom = $dom->at('TrackResponse');
135              
136 4 50       1030 return undef unless $dom->at('Response ResponseStatusCode')->text; # "1" on success
137 4         1327 return $dom;
138             }
139              
140             1;
141              
142             =head1 NAME
143              
144             Webservice::Shipment::Carrier::UPS - UPS handling for Webservice::Shipment
145              
146             =head1 DESCRIPTION
147              
148             Implements UPS handling for L.
149             It is a subclass of L which implements all the necessary methods.
150              
151             =head1 ATTRIBUTES
152              
153             L implements all of the attributes from L and implements the following new ones
154              
155             =head2 api_key
156              
157             Your api key for the UPS API.
158             The default implementation dies if used without being specified.
159              
160             =head2 template
161              
162             The string template used with L to format the request.
163