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