File Coverage

blib/lib/Business/FedEx/DirectConnect.pm
Criterion Covered Total %
statement 15 190 7.8
branch 0 90 0.0
condition 0 29 0.0
subroutine 5 22 22.7
pod 13 14 92.8
total 33 345 9.5


line stmt bran cond sub pod time code
1             # FedEx::DirectConnect
2             # $Id: DirectConnect.pm,v 1.27 2004/08/09 16:07:20 jay.powers Exp $
3             # Copyright (c) 2004 Jay Powers
4             # All rights reserved.
5             #
6             # This program is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl itself
8              
9             package Business::FedEx::DirectConnect; #must be in Business/FedEx
10              
11             require 5.006_000;
12              
13 1         195 use Business::FedEx::Constants qw(
14             %FE_RE %FE_SE %FE_TT %FE_RQ
15             field_name_to_tag
16             field_tag_to_name
17 1     1   5823 );
  1         3  
18              
19 1     1   8 use Carp qw(confess croak);
  1         1  
  1         42  
20 1     1   1131 use LWP::UserAgent;
  1         64217  
  1         51  
21              
22             our $VERSION = '1.01';
23              
24 1     1   11 use strict;
  1         2  
  1         36  
25              
26              
27             #Please register with FedEx to receive the addresses to their DirectConnect API.
28 1     1   6 use constant FEDEX_API_URI => '';
  1         2  
  1         2281  
29              
30             # Use Tie::IxHash so that fields are output in the order they're set,
31             # which makes reading the list of fields easier if you set them in a
32             # useful order.
33             #
34             # XXX only use IxHash when a debug flag is set?
35              
36             { my $have_ix_hash;
37             sub _init_ix_hash {
38 0     0     my ($rhash, $rdata) = @_;
39              
40 0 0         $have_ix_hash = !!eval { local $SIG{__DIE__}; require Tie::IxHash }
  0            
  0            
41             if !defined $have_ix_hash;
42              
43 0 0         if ($have_ix_hash) {
44 0           tie %$rhash, 'Tie::IxHash', @$rdata;
45             }
46             else {
47 0           %$rhash = @$rdata;
48             }
49             } }
50              
51             sub new {
52 0     0 1   my $name = shift;
53 0   0       my $class = ref($name) || $name;
54 0           my $self = {
55             uri=>FEDEX_API_URI
56             ,acc => ''
57             ,meter => ''
58             ,referer => ''
59             ,Debug=>0
60             ,sdata => {}
61             ,@_ };
62 0 0         $self->{UA} = LWP::UserAgent->new(
63             exists $self->{timeout}
64             ? (timeout => $self->{timeout})
65             : ()
66             );
67 0           $self->{REQ} = HTTP::Request->new(POST => $self->{uri}); # Create a request
68 0           bless ($self, $class);
69             }
70              
71             sub get_data {
72 0     0 1   my $self = shift;
73 0           my @key = @_;
74              
75 0 0         if (!@key) {
76 0           return $self->{sdata};
77             }
78              
79 0 0 0       if (@key > 1 && !wantarray) {
80 0           croak "scalar context but asked for ", 0+@key, " items";
81             }
82              
83 0           my $r = $self->{sdata};
84 0           my @ret;
85 0           for my $key (@key) {
86             # This sucks, but the key might be present as either a number
87             # or a name (or both, there's nothing which prevents that
88             # currently).
89             #
90             # Check first as they passed it (in case it's there both ways):
91              
92             #print "key=$key\n";
93 0 0         if (exists $r->{$key}) {
94 0           push @ret, $r->{$key};
95 0           next;
96             }
97              
98             # Then as a number:
99              
100 0           my $tag = field_name_to_tag $key;
101             #print "tag=$tag\n";
102 0 0         if (exists $r->{$tag}) {
103 0           push @ret, $r->{$tag};
104 0           next;
105             }
106              
107             # Then as a name:
108              
109 0           my $name = field_tag_to_name $key;
110             #print "name=$name\n";
111 0 0         if (exists $r->{$name}) {
112 0           push @ret, $r->{$name};
113 0           next;
114             }
115              
116             # It isn't there.
117              
118 0           print "undef\n";
119 0           push @ret, undef;
120             }
121              
122 0 0         return wantarray ? @ret : $ret[0];
123             }
124              
125             sub set_data {
126 0     0 1   my $self = shift;
127 0           $self->{UTI} = shift;
128 0           $self->{sdata} = {};
129 0           _init_ix_hash $self->{sdata}, \@_;
130 0 0         if (!$self->{UTI}) {
131 0           $self->set_err("Error: You must provide a valid UTI.");
132 0           return 0;
133             }
134 0           $self->{sbuf} = '0,"' . $FE_TT{$self->{UTI}}[0] . '"';
135              
136             # Supply default values where appropriate.
137              
138 0 0         for ([3025 => $FE_TT{$self->{UTI}}[1]]
139             , [10 => $self->{acc}]
140             , $self->{UTI} == 3003 ? () : [498 => $self->{meter}]
141             ) {
142 0           my ($key, $val) = @$_;
143 0 0         $self->{sdata}->{$key} = $val
144             unless exists $self->{sdata}->{$key};
145             }
146            
147 0           while (my ($key, $val) = each %{$self->{sdata}}) {
  0            
148 0           $key = field_name_to_tag $key;
149              
150             # Empty value should not be sent (except for 99).
151              
152 0 0         next if !defined $val;
153 0 0         next if $val !~ /\S/;
154              
155             # Alphanumeric fields should not contain leading or
156             # trailing blanks.
157              
158 0           $val =~ s/^\s+//;
159 0           $val =~ s/\s+\z//;
160              
161             # %-escape as necessary
162              
163 0           $val =~ s/([%"\x00])/sprintf '%%%02X', ord $1/eg;
  0            
164              
165             # XXX The TT guide says the data needs to be ASCII only (though
166             # it didn't choke on Latin 1 chars I tried sending it), but
167             # there's no way for me to do the translation here since I don't
168             # know what char set you're using. Croak if there are non-ASCII
169             # data?
170              
171 0           $self->{sbuf} .= qq($key,"$val");
172             }
173 0           $self->{sbuf} .= '99,""';
174 0           return $self->{sbuf};
175             }
176              
177             # Send a call to FedEx
178             sub transaction {
179 0     0 1   my $self = shift;
180 0 0         if (@_) {
181 0           $self->{sbuf} = shift;
182             }
183 0 0         if (!$self->{sbuf}) {
184 0           $self->set_err("Error: You must provide data to send to FedEx.");
185 0           return 0;
186             }
187              
188 0 0         if ($self->_send()) { # send POST to FedEx
189 0           $self->{rbuf} =~ s/[\x00\s]+$//g; # get rid of the extra spaces/nulls
190 0 0         $self->{rstring} = "Total bytes returned ". length($self->{rbuf}) if ($self->{Debug});
191 0 0         print $self->{rstring} . "\n" if ($self->{Debug});
192 0 0         $self->{rHash} = $self->_split_data()
193             or return 0;
194             # Check for Errors from FedEx
195 0 0         if (exists $self->{rHash}->{2}) {
196 0           $self->set_err("FedEx Transaction Error: " . $self->{rHash}->{2} . " " . $self->{rHash}->{3},
197             $self->{rHash}->{2});
198 0           return 0;
199             }
200 0           return 1;
201             } else {
202 0           return 0;
203             }
204             }
205              
206              
207             # Send POST request to FedEx API
208             sub _send {
209 0     0     my $self = shift;
210 0 0         print "Sending ". $self->{sbuf} . "\n" if ($self->{Debug});
211 0           $self->{REQ}->header(()
212             ,'Referer' => $self->{referer}
213             ,'User-Agent' => 'Business-FedEx-DirectConnect-'.$VERSION
214             ,'Accept' => "image/gif,image/jpeg,image/pjpeg,text/plain,text/html,*/*"
215             ,'Content-Type' => "image/gif"
216             ,'Content-Length' => length($self->{sbuf}));
217 0 0         $self->{sbuf} .= '99,""' unless ($self->{sbuf} =~ /99,\"\"$/);
218 0           $self->{REQ}->content($self->{sbuf});
219 0 0         print $self->{REQ}->as_string() if ($self->{Debug});
220             # Pass request to the user agent and get a response back
221 0           my $res = $self->{UA}->request($self->{REQ});
222             # Check the outcome of the response
223 0 0         if ($res->is_success) {
224 0           $self->{rbuf} = $res->content;
225 0           return 1;
226             } else {
227 0           $self->set_err("Request Error: " . $res->status_line);
228 0           return 0;
229             }
230             }
231              
232             # here are some functions to deal with data from FedEx
233             sub _split_data {
234 0     0     my $self = shift;
235 0   0       my $sdata = shift || $self->{rbuf};
236 0           my $count = 0;
237 0           my (%hash, @key);
238              
239 0 0         if (!defined $sdata) {
240 0           $self->set_err("No data received from FedEx server");
241 0           return;
242             }
243              
244             # There ought to be a way to signal less serious errors without
245             # choking completely, or a way for the user to tell you to notice
246             # them.
247              
248 0           my $warn = 0;
249              
250 0           while ($sdata ne '') {
251 0 0         if ($sdata !~ s/^(0|[1-9]\d*(?:-\d*)*),"([^"]*)"//) {
252 0           $self->set_err("Invalid FedEx transaction data at `$sdata'");
253 0           return;
254             }
255 0           my ($key, $val) = ($1, $2);
256              
257 0 0         if (exists $hash{$key}) {
258 0           $self->set_err("Duplicate key $key in FedEx transaction");
259 0           return;
260             }
261              
262 0 0 0       if ($warn && $val eq '' && $key ne '99') {
      0        
263 0           $self->set_err("Empty value for key $key in FedEx transaction");
264 0           return;
265             }
266              
267 0           push @key, $key;
268 0           $hash{$key} = $val;
269             }
270              
271 0 0 0       if ($warn && $key[0] ne '0') {
272 0           $self->set_err("first element of FedEx transaction data isn't 0");
273 0           return;
274             }
275              
276 0 0 0       if ($warn && $key[-1] ne '99') {
277 0           $self->set_err("last element of FedEx transaction data isn't 99");
278 0           return;
279             }
280              
281 0 0 0       if ($warn && $hash{99} ne '') {
282 0           $self->set_err("non-empty value for last element of FedEx transaction data");
283 0           return;
284             }
285              
286             # XXX validate \d+-\d+ type fields
287              
288 0           return \%hash;
289             }
290              
291              
292             # array of all the required fields
293             sub required {
294 0     0 1   my $self = shift;
295 0           my $uti = shift;
296 0           my @req;
297 0           foreach (@{$FE_RQ{$uti}}) {
  0            
298 0           push (@req, $FE_RE{$_});
299             }
300 0           return @req;
301             }
302              
303             #check against required fields
304             # XXX Why are you passing in the UTI instead of using $self->{UTI}?
305             sub has_required {
306 0     0 1   my $self = shift;
307 0           my $uti = shift;
308 0           my @keys = keys %{$self->{sdata}};
  0            
309 0           my (%seen, @diff);
310 0           @seen{@keys} = ();
311             # XXX 0 is in %FE_RQ but not in sdata
312 0           $seen{0} = undef;
313 0           foreach (@{$FE_RQ{$uti}}) {
  0            
314 0 0         push(@diff, $_) unless exists $seen{$_};
315             }
316             return @diff
317 0           }
318             # print or create a label
319             sub label {
320 0     0 1   my $self = shift;
321 0           my $file = shift;
322 0   0       my $image_key = shift || 188;
323 0           $self->{rbinary} = $self->{rHash}->{$image_key};
324 0 0         $self->{rbinary} =~ s/\%([0-9A-F][0-9A-F])/chr(hex("0x$1"))/eg if ($self->{rbinary});
  0            
325 0 0         if ($file) {
326 0 0         open(FILE, ">$file") or die "Could not open $file:\n$!";
327 0           binmode(FILE);
328 0           print FILE $self->{rbinary};
329 0           close(FILE);
330 0           return 1;
331             } else {
332 0           return $self->{rbinary};
333             }
334             }
335             #look up a value
336             sub lookup {
337 0     0 1   my $self = shift;
338 0           my $code = field_name_to_tag shift;
339 0 0         print "Looking for " . $code . "\n" if ($self->{Debug});
340 0           return $self->{rHash}->{$code};
341             }
342             # All the data from FedEx
343             sub rbuf {
344 0     0 1   my $self = shift;
345 0 0         $self->{rbuf} = shift if @_;
346 0           return $self->{rbuf};
347             }
348             # Build a hash from the return data from FedEx
349             sub hash_ret {
350 0     0 1   my $self = shift;
351 0           return $self->{rHash};
352             }
353              
354             sub errstr {
355 0     0 1   my $self = shift;
356 0 0         $self->{errstr} = shift if @_;
357 0           return $self->{errstr};
358             }
359              
360             sub errcode {
361 0     0 1   my $self = shift;
362 0 0         $self->{errcode} = shift if @_;
363 0           return $self->{errcode};
364             }
365              
366             sub set_err {
367 0     0 0   my $self = shift;
368 0 0         my $errstr = @_ ? shift : "Unknown error";
369 0 0         my $errcode = @_ ? shift : -1;
370 0 0         @_ and confess;
371              
372 0           $self->errstr($errstr);
373 0           $self->errcode($errcode);
374             }
375              
376             sub user_data {
377 0 0 0 0 1   @_ == 1 || @_ == 2
378             or croak "->user_data expected 0 or 1 args, got ", 0+@_;
379 0           my $self = shift;
380 0           my $old = $self->{user_data};
381 0 0         $self->{user_data} = shift if @_;
382 0           return $old;
383             }
384              
385             1;
386             __END__