File Coverage

blib/lib/Business/US/USPS/WebTools/TrackConfirm.pm
Criterion Covered Total %
statement 26 96 27.0
branch 0 36 0.0
condition 0 6 0.0
subroutine 9 19 47.3
pod 4 4 100.0
total 39 161 24.2


line stmt bran cond sub pod time code
1 1     1   766 use v5.10;
  1         3  
2 1     1   834 use utf8;
  1         10  
  1         4  
3              
4             package Business::US::USPS::WebTools::TrackConfirm;
5 1     1   44 use strict;
  1         2  
  1         24  
6 1     1   5 no warnings 'uninitialized';
  1         1  
  1         45  
7              
8 1     1   721 use parent qw(Business::US::USPS::WebTools);
  1         292  
  1         5  
9              
10 1     1   44 use subs qw();
  1         3  
  1         20  
11 1     1   4 use vars qw($VERSION);
  1         2  
  1         42  
12              
13 1     1   6 use Carp qw(croak carp);
  1         1  
  1         128  
14              
15             $VERSION = '1.12_01';
16              
17             =encoding utf8
18              
19             =head1 NAME
20              
21             Business::US::USPS::WebTools::TrackConfirm - track a shipment using the USPS Web Tools
22              
23             =head1 SYNOPSIS
24              
25             use Business::US::USPS::WebTools::TrackConfirm;
26              
27             my $tracker = Business::US::USPS::WebTools::TrackConfirm->new( {
28             UserID => $ENV{USPS_WEBTOOLS_USERID},
29             Password => $ENV{USPS_WEBTOOLS_PASSWORD},
30             Testing => 1,
31             } );
32              
33             my $hash = $tracker->track(
34             );
35              
36             if( $tracker->is_error )
37             {
38             warn "Oh No! $tracker->{error}{description}\n";
39             }
40             else
41             {
42             print join "\n", map { "$_: $hash->{$_}" }
43             qw(FirmName Address1 Address2 City State Zip5 Zip4);
44             }
45              
46              
47             =head1 DESCRIPTION
48              
49             *** THIS IS ALPHA SOFTWARE ***
50              
51             This module implements the Track & Confirm web service from the
52             US Postal Service. It is a subclass of Business::US::USPS::WebTools.
53              
54             =cut
55              
56             =over 4
57              
58             =cut
59              
60 0     0     sub _fields { qw( TrackID DestinationZipCode MailingDate ClientIp SourceId ) }
61 0     0     sub _required { qw( TrackID ) }
62              
63             =item track( KEY, VALUE, ... )
64              
65             The C method takes the following keys, which come
66             directly from the USPS web service interface:
67              
68             TrackID The tracking number
69              
70             It returns an anonymous hash with the data from the response. Although
71             the USPS API allows to make multiple queries in a single request, this
72             method one queries one.
73              
74             If you want to see if something went wrong, check:
75              
76             $tracker->is_error;
77              
78             See the C documentation in Business::US::USPS::WebTools for more
79             details on error information.
80              
81             =cut
82              
83              
84             sub track {
85 0     0 1   my( $self, %hash ) = @_;
86              
87 1     1   6 say Dumper( \%hash ); use Data::Dumper;
  1         2  
  1         1026  
  0            
88              
89 0           foreach my $field ( $self->_required ) {
90 0 0         next if exists $hash{$field};
91 0           carp "Missing field [$field] for track()";
92 0           return;
93             }
94              
95 0           my $tracking_number = $self->is_valid_tracking_number( $hash{'TrackID'} );
96              
97 0 0         unless( $tracking_number ) {
98 0           carp "String [$hash{'TrackID'}] does not look like a valid USPS tracking number";
99 0           return;
100             }
101              
102 0           $self->_make_url( \%hash );
103              
104 0           $self->_make_request;
105              
106 0           $self->_parse_response;
107             }
108              
109             =item tracking_number_regex
110              
111             Returns the regex that checks a tracking number. I have it in its own
112             method so you can easily override it if I got it wrong.
113              
114             The USPS shows the valid forms at
115              
116             https://tools.usps.com/go/TrackConfirmAction!input.action
117              
118             USPS Tracking® 9400 1000 0000 0000 0000 00
119             Priority Mail® 9205 5000 0000 0000 0000 00
120             Certified Mail® 9407 3000 0000 0000 0000 00
121             Collect on Delivery 9303 3000 0000 0000 0000 00
122             Global Express Guaranteed® 82 000 000 00
123             Priority Mail Express International™ EC 000 000 000 US
124             Priority Mail Express™ 9270 1000 0000 0000 0000 00
125             EA 000 000 000 US
126             Priority Mail International® CP 000 000 000 US
127             Registered Mail™ 9208 8000 0000 0000 0000 00
128             Signature Confirmation™ 9202 1000 0000 0000 0000 00
129              
130             =cut
131              
132             sub tracking_number_regex {
133 0     0 1   state $regex = qr/
134             \A
135             9 [234] [0-9]{20} |
136             82 [0-9]{8} |
137             [A-Z]{2}[0-9]{9}US
138             \z /x;
139              
140 0           return $regex;
141             }
142              
143             =item is_valid_tracking_number( ID )
144              
145             Returns a normalized version of the tracking number if ID looks like a
146             tracking number, based on the regex from C.
147             Returns false otherwise.
148              
149             Normalizing ID merely removes all whitespace. Sometimes the USPS shows
150             the numbers with whitespace.
151              
152             =cut
153              
154             sub is_valid_tracking_number {
155 0     0 1   my( $self, $tracking_number ) = @_;
156              
157 0           $tracking_number =~ s/\s+//g;
158 0 0         return unless $tracking_number =~ $self->tracking_number_regex;
159              
160 0           $tracking_number;
161             }
162              
163             =item service_type( ID )
164              
165             Returns the service type, based on the examples shown by the USPS and
166             shown in C. I know this is wrong because I have
167             tracking numbers that don't have the same leading characters for
168             Priority Mail International.
169              
170             =cut
171              
172             sub service_type {
173 0     0 1   my( $self, $tracking_number );
174              
175 0 0         return unless $tracking_number =~ $self->tracking_number_regex;
176              
177 0           return do {
178 0           local $_ = $tracking_number;
179 0 0         if( / \A 94 /x ) { 'USPS Tracking' }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
180 0           elsif( / \A 9205 /x ) { 'Priority Mail' }
181 0           elsif( / \A 9407 /x ) { 'Certified Mail' }
182 0           elsif( / \A 9303 /x ) { 'Collect on Delivery' }
183 0           elsif( / \A 82 /x ) { 'Global Express Guaranteed' }
184 0           elsif( / \A 9270 /x ) { 'Priority Mail Express' }
185 0           elsif( / \A 9208 /x ) { 'Registered Mail' }
186 0           elsif( / \A 9202 /x ) { 'Signature Confirmation' }
187              
188 0           elsif( / \A RA .* US \z /x ) { 'Registered Mail' }
189 0           elsif( / \A EA .* US \z /x ) { 'Priority Mail Express' }
190 0           elsif( / \A EC .* US \z /x ) { 'Priority Mail Express International' }
191 0           elsif( / \A CP .* US \z /x ) { 'Priority Mail International' }
192              
193 0           else { 'Unknown' }
194             };
195              
196             }
197              
198 0     0     sub _api_name { "TrackV2" }
199              
200             sub _make_query_xml {
201 0     0     my( $self, $hash ) = @_;
202              
203 0           my $user = $self->userid;
204 0           my $pass = $self->password;
205              
206 0           my $id = $hash->{'TrackID'};
207 0   0       my $ip = $hash->{'ClientIp'} // '127.0.0.1';
208 0   0       my $source = $hash->{'SourceId'} // __PACKAGE__;
209              
210 0           my $xml =
211             qq|| .
212             qq|1| .
213             qq|| .
214             qq|| .
215             qq||;
216              
217 0           foreach my $field ( $self->_fields ) {
218 0 0         next if $field eq 'TrackID';
219 0 0         next unless defined $hash->{$field};
220 0           $xml .= "<$field>$$hash{$field}";
221             }
222              
223 0           $xml .= qq||;
224              
225 0           say "XML is\n$xml\n";
226              
227 0           return $xml;
228             }
229              
230             sub _parse_response {
231 0     0     my( $self ) = @_;
232              
233 0           my $response = $self->response;
234              
235 0           my( $summary ) = $response =~ m{(.*?)}s;
236 0           my @details = $response =~ m{(.*?)}s;
237              
238 0           my %hash = ();
239 0           $hash{'TrackSummary'} = $summary;
240 0           $hash{'TrackDetail'} = [ map { $self->_parse_subbits( $_ ) } @details ];
  0            
241              
242 0           bless \%hash, ref $self; # 'Hash::AsObject';
243             }
244              
245             sub _parse_subbits {
246 0     0     state $fields = [ qw(
247             EventTime EventDate Event EventCity EventState EventZIPCode
248             EventCountry FirmName Name AuthorizedAgent
249             ) ];
250              
251 0           my( $self, $subbit ) = @_;
252              
253 0           my %hash;
254 0           foreach my $field ( @$fields ) {
255 0           my( $value ) = $subbit =~ m|<$field>(.*?)|sg;
256 0   0       $hash{$field} = $value || '';
257             }
258              
259 0           return \%hash;
260             }
261              
262             =back
263              
264             =head1 TO DO
265              
266             =head1 SEE ALSO
267              
268             L
269              
270             The WebTools API is documented on the US Postal Service's website:
271              
272             https://www.usps.com/business/web-tools-apis/track-and-confirm-api.htm
273              
274             =head1 SOURCE AVAILABILITY
275              
276             This source is in GitHub:
277              
278             https://github.com/briandfoy/business-us-usps-webtools
279              
280             =head1 AUTHOR
281              
282             brian d foy, C<< >>
283              
284             =head1 COPYRIGHT AND LICENSE
285              
286             Copyright © 2006-2016, brian d foy . All rights reserved.
287              
288             This program is free software; you can redistribute it and/or modify
289             it under the same terms as Perl itself.
290              
291             =cut
292              
293             1;