File Coverage

blib/lib/Shipment/FedEx.pm
Criterion Covered Total %
statement 22 26 84.6
branch 0 2 0.0
condition n/a
subroutine 8 9 88.8
pod n/a
total 30 37 81.0


line stmt bran cond sub pod time code
1             package Shipment::FedEx;
2             $Shipment::FedEx::VERSION = '2.00';
3 1     1   63342 use strict;
  1         2  
  1         76  
4 1     1   7 use warnings;
  1         2  
  1         56  
5              
6              
7 1     1   927 use Try::Tiny;
  1         3466  
  1         78  
8 1     1   22962 use Shipment::SOAP::WSDL;
  1         23  
  1         47  
9              
10 1     1   17 use Moo;
  1         387  
  1         10  
11 1     1   2767 use MooX::Types::MooseLike::Base qw(:all);
  1         11673  
  1         597  
12 1     1   23 use namespace::clean;
  1         7349  
  1         13  
13              
14             extends 'Shipment::Base';
15              
16              
17             has 'meter' => (
18             is => 'rw',
19             isa => Str,
20             );
21              
22             has 'key' => (
23             is => 'rw',
24             isa => Str,
25             );
26              
27             has 'password' => (
28             is => 'rw',
29             isa => Str,
30             );
31              
32              
33             has 'proxy_domain' => (
34             is => 'rw',
35             isa => Enum [
36             qw(
37             wsbeta.fedex.com:443
38             ws.fedex.com:443
39             )
40             ],
41             default => 'wsbeta.fedex.com:443',
42             );
43              
44              
45             has 'residential_address' => (
46             is => 'rw',
47             isa => Bool,
48             default => 0,
49             );
50              
51              
52             has 'label_stock_type' => (
53             is => 'rw',
54             isa => Enum [
55             qw(
56             STOCK_4X6
57             STOCK_4X6.75_LEADING_DOC_TAB
58             STOCK_4X6.75_TRAILING_DOC_TAB
59             STOCK_4X8
60             STOCK_4X9_LEADING_DOC_TAB
61             STOCK_4X9_TRAILING_DOC_TAB
62             PAPER_4X6
63             PAPER_4X8
64             PAPER_4X9
65             PAPER_7X4.75
66             PAPER_8.5X11_BOTTOM_HALF_LABEL
67             PAPER_8.5X11_TOP_HALF_LABEL
68             PAPER_LETTER
69             )
70             ],
71             lazy => 1,
72             builder => 1,
73             );
74              
75             sub _build_label_stock_type {
76 0 0   0     return 'STOCK_4X6' if shift->printer_type eq 'thermal';
77 0           return 'PAPER_4X6';
78             }
79              
80              
81             has '+bill_type' =>
82             (isa => Enum [qw( sender recipient third_party collect )],);
83              
84             my %bill_type_map = (
85             'sender' => 'SENDER',
86             'recipient' => 'RECIPIENT',
87             'third_party' => 'THIRD_PARTY',
88             'collect' => 'COLLECT',
89             );
90              
91             my %signature_type_map = (
92             'default' => 'SERVICE_DEFAULT',
93             'required' => 'DIRECT',
94             'not_required' => 'NO_SIGNATURE_REQUIRED',
95             'adult' => 'ADULT',
96             );
97              
98             my %pickup_type_map = (
99             'pickup' => 'REGULAR_PICKUP',
100             'dropoff' => 'STATION',
101             );
102              
103             my %package_type_map = (
104             'custom' => 'YOUR_PACKAGING',
105             'envelope' => 'FEDEX_ENVELOPE',
106             'tube' => 'FEDEX_TUBE',
107             'box' => 'FEDEX_BOX',
108             'pack' => 'FEDEX_PAK',
109             );
110              
111             my %units_type_map = (
112             'lb' => 'LB',
113             'kg' => 'KG',
114             'in' => 'IN',
115             'cm' => 'CM',
116             );
117              
118             my %printer_type_map = (
119             'pdf' => 'PDF',
120             'thermal' => 'EPL2',
121             'image' => 'PNG',
122             );
123              
124             my %label_content_type_map = (
125             'pdf' => 'application/pdf',
126             'thermal' => 'text/fedex-epl',
127             'image' => 'image/png',
128             );
129              
130              
131             has '+package_type' => (isa =>
132             Enum [qw( custom envelope tube box pack FEDEX_10KG_BOX FEDEX_25KG_BOX )]
133             );
134              
135              
136             has '+currency' => (default => 'USD',);
137              
138              
139             sub _build_services {
140             my $self = shift;
141              
142 1     1   1654 use Shipment::Package;
  0            
  0            
143             use Shipment::Service;
144             use Shipment::FedEx::WSDL::RateInterfaces::RateService::RateServicePort;
145              
146             my $interface =
147             Shipment::FedEx::WSDL::RateInterfaces::RateService::RateServicePort
148             ->new({proxy_domain => $self->proxy_domain,});
149             my $response;
150              
151             my %services;
152              
153             my @to_streetlines;
154             push @to_streetlines, $self->to_address()->address1;
155             push @to_streetlines, $self->to_address()->address2
156             if $self->to_address()->address2;
157              
158             my @from_streetlines;
159             push @from_streetlines, $self->from_address()->address1;
160             push @from_streetlines, $self->from_address()->address2
161             if $self->from_address()->address2;
162              
163             my $total_weight;
164             $total_weight += $_->weight for @{$self->packages};
165             $total_weight ||= 1;
166              
167             my $options;
168             $options->{SpecialServiceTypes} = 'SIGNATURE_OPTION';
169             $options->{SignatureOptionDetail}->{OptionType} =
170             $signature_type_map{$self->signature_type} || $self->signature_type;
171              
172             my @pieces;
173             if ($self->count_packages) {
174             my $sequence = 1;
175             foreach (@{$self->packages}) {
176             push @pieces,
177             { SequenceNumber => $sequence,
178             InsuredValue => {
179             Currency => $_->insured_value->code || $self->currency,
180             Amount => $_->insured_value->value,
181             },
182             Weight => {
183             Value => $_->weight,
184             Units => $units_type_map{$self->weight_unit}
185             || $self->weight_unit,
186             },
187             Dimensions => {
188             Length => $_->length,
189             Width => $_->width,
190             Height => $_->height,
191             Units => $units_type_map{$self->dim_unit}
192             || $self->dim_unit,
193             },
194             SpecialServicesRequested => $options,
195             };
196             $sequence++;
197             }
198             }
199             else {
200             push @pieces,
201             { Weight => {
202             Value => $total_weight,
203             Units => $units_type_map{$self->weight_unit}
204             || $self->weight_unit,
205             },
206             };
207             }
208              
209             try {
210             $response = $interface->getRates(
211             { WebAuthenticationDetail => {
212             UserCredential => {
213             Key => $self->key,
214             Password => $self->password,
215             },
216             },
217             ClientDetail => {
218             AccountNumber => $self->account,
219             MeterNumber => $self->meter,
220             },
221             Version => {
222             ServiceId => 'crs',
223             Major => 9,
224             Intermediate => 0,
225             Minor => 0,
226             },
227             ReturnTransitAndCommit => 1,
228             RequestedShipment => {
229             DropoffType => $pickup_type_map{$self->pickup_type}
230             || $self->pickup_type,
231             PackagingType => 'YOUR_PACKAGING',
232             Shipper => {
233             Address => {
234             StreetLines => \@from_streetlines,
235             City => $self->from_address()->city,
236             StateOrProvinceCode =>
237             $self->from_address()->province_code,
238             PostalCode => $self->from_address()->postal_code,
239             CountryCode => $self->from_address()->country_code,
240             },
241             },
242             Recipient => {
243             Address => {
244             StreetLines => \@to_streetlines,
245             City => $self->to_address()->city,
246             StateOrProvinceCode =>
247             $self->to_address()->province_code,
248             PostalCode => $self->to_address()->postal_code,
249             CountryCode => $self->to_address()->country_code,
250             Residential => $self->residential_address,
251             },
252             },
253             PackageCount => $self->count_packages || 1,
254             PackageDetail => 'INDIVIDUAL_PACKAGES',
255             RequestedPackageLineItems => \@pieces,
256             },
257             },
258             );
259              
260             #warn $response;
261              
262             foreach my $service (@{$response->get_RateReplyDetails()}) {
263             $services{$service->get_ServiceType()->get_value} =
264             Shipment::Service->new(
265             id => $service->get_ServiceType()->get_value,
266             name => $service->get_ServiceType()->get_value,
267             package => Shipment::Package->new(
268             id => 'YOUR_PACKAGING',
269             name => 'Customer Supplied',
270             ),
271             cost => Data::Currency->new(
272             $service->get_RatedShipmentDetails->[0]
273             ->get_ShipmentRateDetail->get_TotalNetCharge->get_Amount,
274             $service->get_RatedShipmentDetails->[0]
275             ->get_ShipmentRateDetail->get_TotalNetCharge
276             ->get_Currency
277             ),
278             );
279             }
280             $services{ground} =
281             $services{'FEDEX_GROUND'}
282             || $services{'GROUND_HOME_DELIVERY'}
283             || $services{'INTERNATIONAL_GROUND'}
284             || Shipment::Service->new();
285             $services{express} =
286             $services{'FEDEX_2_DAY'}
287             || $services{'INTERNATIONAL_ECONOMY'}
288             || Shipment::Service->new();
289             $services{priority} =
290             $services{'PRIORITY_OVERNIGHT'}
291             || $services{'INTERNATIONAL_PRIORITY'}
292             || Shipment::Service->new();
293              
294             }
295             catch {
296             warn $_;
297             try {
298             warn $response->get_Notifications()->get_Message;
299             $self->error(
300             $response->get_Notifications()->get_Message->get_value);
301             }
302             catch {
303             warn $response->get_faultstring;
304             $self->error($response->get_faultstring->get_value);
305             };
306             };
307              
308             \%services;
309             }
310              
311              
312             sub rate {
313             my ($self, $service_id) = @_;
314              
315             try {
316             $service_id = $self->services->{$service_id}->id;
317             }
318             catch {
319             warn $_;
320             warn "service ($service_id) not available";
321             $self->error("service ($service_id) not available");
322             $service_id = '';
323             };
324             return unless $service_id;
325              
326             my $total_weight;
327             $total_weight += $_->weight for @{$self->packages};
328              
329             my $total_insured_value;
330             $total_insured_value += $_->insured_value->value for @{$self->packages};
331              
332             use Shipment::Package;
333             use Shipment::Service;
334             use Shipment::FedEx::WSDL::RateInterfaces::RateService::RateServicePort;
335              
336             my $interface =
337             Shipment::FedEx::WSDL::RateInterfaces::RateService::RateServicePort
338             ->new({proxy_domain => $self->proxy_domain,});
339             my $response;
340              
341             my $options;
342             $options->{SpecialServiceTypes} = 'SIGNATURE_OPTION';
343             $options->{SignatureOptionDetail}->{OptionType} =
344             $signature_type_map{$self->signature_type} || $self->signature_type;
345              
346             my @pieces;
347             my $sequence = 1;
348             foreach (@{$self->packages}) {
349             push @pieces,
350             { SequenceNumber => $sequence,
351             InsuredValue => {
352             Currency => $_->insured_value->code || $self->currency,
353             Amount => $_->insured_value->value,
354             },
355             Weight => {
356             Value => $_->weight,
357             Units => $units_type_map{$self->weight_unit}
358             || $self->weight_unit,
359             },
360             Dimensions => {
361             Length => $_->length,
362             Width => $_->width,
363             Height => $_->height,
364             Units => $units_type_map{$self->dim_unit} || $self->dim_unit,
365             },
366             SpecialServicesRequested => $options,
367             };
368             $sequence++;
369             }
370              
371             my @to_streetlines;
372             push @to_streetlines, $self->to_address()->address1;
373             push @to_streetlines, $self->to_address()->address2
374             if $self->to_address()->address2;
375              
376             my @from_streetlines;
377             push @from_streetlines, $self->from_address()->address1;
378             push @from_streetlines, $self->from_address()->address2
379             if $self->from_address()->address2;
380              
381             my %services;
382              
383             try {
384             $response = $interface->getRates(
385             { WebAuthenticationDetail => {
386             UserCredential => {
387             Key => $self->key,
388             Password => $self->password,
389             },
390             },
391             ClientDetail => {
392             AccountNumber => $self->account,
393             MeterNumber => $self->meter,
394             },
395             Version => {
396             ServiceId => 'crs',
397             Major => 9,
398             Intermediate => 0,
399             Minor => 0,
400             },
401             ReturnTransitAndCommit => 1,
402             RequestedShipment => {
403             ServiceType => $service_id,
404             DropoffType => 'REGULAR_PICKUP',
405             PackagingType => 'YOUR_PACKAGING',
406             TotalWeight => {
407             Value => $total_weight,
408             Units => $units_type_map{$self->weight_unit}
409             || $self->weight_unit,
410             },
411             TotalInsuredValue => {
412             Currency => $self->currency,
413             Amount => $total_insured_value,
414             },
415             Shipper => {
416             Address => {
417             StreetLines => \@from_streetlines,
418             City => $self->from_address()->city,
419             StateOrProvinceCode =>
420             $self->from_address()->province_code,
421             PostalCode => $self->from_address()->postal_code,
422             CountryCode => $self->from_address()->country_code,
423             },
424             },
425             Recipient => {
426             Address => {
427             StreetLines => \@to_streetlines,
428             City => $self->to_address()->city,
429             StateOrProvinceCode =>
430             $self->to_address()->province_code,
431             PostalCode => $self->to_address()->postal_code,
432             CountryCode => $self->to_address()->country_code,
433             Residential => $self->residential_address,
434             },
435             },
436             PackageCount => $self->count_packages,
437             PackageDetail => 'INDIVIDUAL_PACKAGES',
438             RequestedPackageLineItems => \@pieces,
439             },
440             },
441             );
442              
443             #warn $response;
444              
445             use Data::Currency;
446             use Shipment::Service;
447             $self->service(
448             new Shipment::Service(
449             id => $service_id,
450             name => $self->services->{$service_id}->name,
451             cost => Data::Currency->new(
452             $response->get_RateReplyDetails()
453             ->get_RatedShipmentDetails->[0]
454             ->get_ShipmentRateDetail->get_TotalNetCharge->get_Amount,
455             $response->get_RateReplyDetails()
456             ->get_RatedShipmentDetails->[0]
457             ->get_ShipmentRateDetail->get_TotalNetCharge
458             ->get_Currency,
459             ),
460             )
461             );
462             }
463             catch {
464             warn $_;
465             try {
466             warn $response->get_Notifications()->get_Message;
467             $self->error(
468             $response->get_Notifications()->get_Message->get_value);
469             }
470             catch {
471             warn $response->get_faultstring;
472             $self->error($response->get_faultstring->get_value);
473             };
474             };
475             }
476              
477              
478             sub ship {
479             my ($self, $service_id) = @_;
480              
481             try {
482             $service_id = $self->services->{$service_id}->id;
483             }
484             catch {
485             warn $_;
486             warn "service ($service_id) not available";
487             $self->error("service ($service_id) not available");
488             $service_id = '';
489             };
490             return unless $service_id;
491              
492             my $total_weight;
493             $total_weight += $_->weight for @{$self->packages};
494              
495             my $total_insured_value;
496             $total_insured_value += $_->insured_value->value for @{$self->packages};
497              
498             my $package_options;
499             $package_options->{SpecialServiceTypes} = 'SIGNATURE_OPTION';
500             $package_options->{SignatureOptionDetail}->{OptionType} =
501             $signature_type_map{$self->signature_type} || $self->signature_type;
502              
503             my $shipment_options;
504             my @email_notifications;
505             if ($self->to_address->email) {
506             push @email_notifications,
507             { EMailNotificationRecipientType => 'RECIPIENT',
508             EMailAddress => $self->to_address->email,
509             NotifyOnShipment => 1,
510             Format => 'TEXT',
511             Localization => {LanguageCode => 'EN',},
512             };
513             $shipment_options->{SpecialServiceTypes} = 'EMAIL_NOTIFICATION';
514             $shipment_options->{EMailNotificationDetail}->{Recipients} =
515             \@email_notifications;
516             }
517              
518             my @references;
519             push @references,
520             { CustomerReferenceType => 'CUSTOMER_REFERENCE',
521             Value => $self->get_reference(0),
522             }
523             if $self->get_reference(0);
524             push @references,
525             { CustomerReferenceType => 'INVOICE_NUMBER',
526             Value => $self->get_reference(1),
527             }
528             if $self->get_reference(1);
529             push @references,
530             { CustomerReferenceType => 'P_O_NUMBER',
531             Value => $self->get_reference(2),
532             }
533             if $self->get_reference(2);
534              
535             my @to_streetlines;
536             push @to_streetlines, $self->to_address()->address1;
537             push @to_streetlines, $self->to_address()->address2
538             if $self->to_address()->address2;
539              
540             my @from_streetlines;
541             push @from_streetlines, $self->from_address()->address1;
542             push @from_streetlines, $self->from_address()->address2
543             if $self->from_address()->address2;
544              
545             my $response;
546             my $sequence = 1;
547             my $master_tracking_id = {};
548              
549             use Shipment::Label;
550             use MIME::Base64;
551             use Data::Currency;
552             use Shipment::Service;
553             use DateTime;
554              
555             use Shipment::FedEx::WSDL::ShipInterfaces::ShipService::ShipServicePort;
556              
557             my $interface =
558             Shipment::FedEx::WSDL::ShipInterfaces::ShipService::ShipServicePort
559             ->new({proxy_domain => $self->proxy_domain,});
560              
561             foreach (@{$self->packages}) {
562              
563             try {
564             $response = $interface->processShipment(
565             { WebAuthenticationDetail => {
566             UserCredential => {
567             Key => $self->key,
568             Password => $self->password,
569             },
570             },
571             ClientDetail => {
572             AccountNumber => $self->account,
573             MeterNumber => $self->meter,
574             },
575             Version => {
576             ServiceId => 'ship',
577             Major => 9,
578             Intermediate => 0,
579             Minor => 0,
580             },
581             RequestedShipment => {
582             ShipTimestamp => DateTime->now->datetime,
583             ServiceType => $service_id,
584             DropoffType => $pickup_type_map{$self->pickup_type}
585             || $self->pickup_type,
586             PackagingType => $package_type_map{$self->package_type}
587             || $self->package_type,
588             TotalWeight => {
589             Value => $total_weight,
590             Units => $units_type_map{$self->weight_unit}
591             || $self->weight_unit,
592             },
593             TotalInsuredValue => {
594             Currency => $self->currency,
595             Amount => $total_insured_value,
596             },
597             Shipper => {
598             Contact => {
599             PersonName => $self->from_address()->name,
600             CompanyName => $self->from_address()->company,
601             PhoneNumber => $self->from_address()->phone,
602             },
603             Address => {
604             StreetLines => \@from_streetlines,
605             City => $self->from_address()->city,
606             StateOrProvinceCode =>
607             $self->from_address()->province_code,
608             PostalCode =>
609             $self->from_address()->postal_code,
610             CountryCode =>
611             $self->from_address()->country_code,
612             },
613             },
614             Recipient => {
615             Contact => {
616             PersonName => $self->to_address()->name,
617             CompanyName => $self->to_address()->company,
618             PhoneNumber => $self->to_address()->phone,
619             },
620             Address => {
621             StreetLines => \@to_streetlines,
622             City => $self->to_address()->city,
623             StateOrProvinceCode =>
624             $self->to_address()->province_code,
625             PostalCode => $self->to_address()->postal_code,
626             CountryCode =>
627             $self->to_address()->country_code,
628             Residential => $self->residential_address,
629             },
630             },
631             ShippingChargesPayment => {
632             PaymentType => $bill_type_map{$self->bill_type}
633             || $self->bill_type,
634             Payor => {
635             AccountNumber => $self->bill_account,
636             CountryCode => ($self->bill_address)
637             ? $self->bill_address->country_code
638             : $self->from_address->country_code,
639             },
640             },
641             SpecialServicesRequested => $shipment_options,
642             RateRequestTypes => 'ACCOUNT',
643             PackageCount => $self->count_packages,
644             PackageDetail => 'INDIVIDUAL_PACKAGES',
645             MasterTrackingId => $master_tracking_id,
646             RequestedPackageLineItems => {
647             SequenceNumber => $sequence,
648             InsuredValue => {
649             Currency => $self->currency,
650             Amount => $_->insured_value->value,
651             },
652             Weight => {
653             Value => $_->weight,
654             Units => $units_type_map{$self->weight_unit}
655             || $self->weight_unit,
656             },
657             Dimensions => {
658             Length => $_->length,
659             Width => $_->width,
660             Height => $_->height,
661             Units => $units_type_map{$self->dim_unit}
662             || $self->dim_unit,
663             },
664             SpecialServicesRequested => $package_options,
665             CustomerReferences => \@references,
666             },
667             LabelSpecification => {
668             LabelFormatType => 'COMMON2D',
669             ImageType => $printer_type_map{$self->printer_type}
670             || $self->printer_type,
671             LabelStockType => $self->label_stock_type,
672             },
673             },
674             },
675             );
676              
677             #warn $response;
678             my $package_details = $response->get_CompletedShipmentDetail
679             ->get_CompletedPackageDetails;
680              
681             if ($self->count_packages > 1) {
682             my $master_tracking =
683             $response->get_CompletedShipmentDetail->get_MasterTrackingId;
684             $self->tracking_id(
685             $master_tracking->get_TrackingNumber->get_value);
686             $master_tracking_id = {
687             TrackingIdType =>
688             $master_tracking->get_TrackingIdType->get_value,
689             TrackingNumber =>
690             $master_tracking->get_TrackingNumber->get_value,
691             };
692             }
693             else {
694             $self->tracking_id(
695             $package_details->get_TrackingIds->get_TrackingNumber
696             ->get_value);
697             }
698             $_->tracking_id(
699             $package_details->get_TrackingIds->get_TrackingNumber
700             ->get_value);
701              
702             if ($package_details->get_PackageRating) {
703             $_->cost(
704             Data::Currency->new(
705             $package_details->get_PackageRating
706             ->get_PackageRateDetails->[0]
707             ->get_NetCharge->get_Amount->get_value,
708             $package_details->get_PackageRating
709             ->get_PackageRateDetails->[0]
710             ->get_NetCharge->get_Currency->get_value,
711             )
712             );
713             }
714             elsif ($response->get_CompletedShipmentDetail->get_ShipmentRating)
715             {
716             $_->cost(
717             Data::Currency->new(
718             $response->get_CompletedShipmentDetail
719             ->get_ShipmentRating->get_ShipmentRateDetails->[0]
720             ->get_TotalNetCharge->get_Amount->get_value,
721             $response->get_CompletedShipmentDetail
722             ->get_ShipmentRating->get_ShipmentRateDetails->[0]
723             ->get_TotalNetCharge->get_Currency->get_value,
724             )
725             );
726             }
727             $_->label(
728             Shipment::Label->new(
729             { tracking_id => $package_details->get_TrackingIds
730             ->get_TrackingNumber->get_value,
731             content_type =>
732             $label_content_type_map{$self->printer_type},
733             data => decode_base64(
734             $package_details->get_Label->get_Parts->get_Image
735             ->get_value
736             ),
737             file_name => $package_details->get_TrackingIds
738             ->get_TrackingNumber->get_value . '.'
739             . lc $printer_type_map{$self->printer_type},
740             },
741             )
742             );
743              
744             }
745             catch {
746             warn $_;
747             try {
748             warn $response->get_Notifications()->get_Message;
749             $self->error(
750             $response->get_Notifications()->get_Message->get_value);
751             }
752             catch {
753             warn $response->get_faultstring;
754             $self->error($response->get_faultstring->get_value);
755             };
756             };
757             last if $self->error;
758             $sequence++;
759             }
760              
761             if (!$self->error) {
762             my $total_charge_amount = 0;
763             my $total_charge_currency = $self->currency;
764             try {
765             my $total_charge =
766             $response->get_CompletedShipmentDetail->get_ShipmentRating
767             ->get_ShipmentRateDetails->[0]->get_TotalNetCharge;
768             $total_charge_amount = $total_charge->get_Amount->get_value;
769             $total_charge_currency = $total_charge->get_Currency->get_value;
770             }
771             catch {
772             # for other billing (recipient/third_party/collect), no rate details are returned, so we ignore the caught error
773             #warn $_;
774             };
775             $self->service(
776             new Shipment::Service(
777             id => $service_id,
778             name => $self->services->{$service_id}->name,
779             cost => Data::Currency->new(
780             $total_charge_amount, $total_charge_currency,
781             ),
782             )
783             );
784             }
785              
786             }
787              
788              
789             sub cancel {
790             my $self = shift;
791              
792             if (!$self->tracking_id) {
793             $self->error('no tracking id provided');
794             return;
795             }
796              
797             use Shipment::FedEx::WSDL::ShipInterfaces::ShipService::ShipServicePort;
798              
799             my $interface =
800             Shipment::FedEx::WSDL::ShipInterfaces::ShipService::ShipServicePort
801             ->new({proxy_domain => $self->proxy_domain,});
802             my $response;
803              
804             my $type = (length $self->tracking_id > 12) ? 'GROUND' : 'EXPRESS';
805             my $success;
806              
807             try {
808             $response = $interface->deleteShipment(
809             { WebAuthenticationDetail => {
810             UserCredential => {
811             Key => $self->key,
812             Password => $self->password,
813             },
814             },
815             ClientDetail => {
816             AccountNumber => $self->account,
817             MeterNumber => $self->meter,
818             },
819             Version => {
820             ServiceId => 'ship',
821             Major => 9,
822             Intermediate => 0,
823             Minor => 0,
824             },
825             TrackingId => {
826             TrackingIdType => $type,
827             TrackingNumber => $self->tracking_id,
828             },
829             DeletionControl => 'DELETE_ONE_PACKAGE',
830             },
831             );
832              
833             #warn $response;
834             $success = $response->get_HighestSeverity->get_value;
835             }
836             catch {
837             warn $_;
838             try {
839             warn $response->get_Notifications()->get_Message;
840             $self->error(
841             $response->get_Notifications()->get_Message->get_value);
842             }
843             catch {
844             warn $response->get_faultstring;
845             $self->error($response->get_faultstring->get_value);
846             };
847             };
848              
849             return $success;
850             }
851              
852              
853             sub end_of_day {
854             my $self = shift;
855              
856             use Shipment::FedEx::WSDL::CloseInterfaces::CloseService::CloseServicePort;
857             my $interface =
858             Shipment::FedEx::WSDL::CloseInterfaces::CloseService::CloseServicePort
859             ->new({proxy_domain => $self->proxy_domain,});
860             my $response;
861              
862             try {
863             $response = $interface->groundClose(
864             { WebAuthenticationDetail => {
865             UserCredential => {
866             Key => $self->key,
867             Password => $self->password,
868             },
869             },
870             ClientDetail => {
871             AccountNumber => $self->account,
872             MeterNumber => $self->meter,
873             },
874             Version => {
875             ServiceId => 'clos',
876             Major => 2,
877             Intermediate => 1,
878             Minor => 0,
879             },
880             TimeUpToWhichShipmentsAreToBeClosed => DateTime->now->datetime,
881             },
882             );
883              
884             #warn $response;
885              
886             $self->manifest(
887             Shipment::Label->new(
888             content_type => 'text/plain',
889             data =>
890             decode_base64($response->get_Manifest->get_File->get_value),
891             file_name => 'manifest_' . DateTime->now->ymd('_') . '.txt',
892             )
893             );
894             }
895             catch {
896             warn $_;
897             try {
898             warn $response->get_Notifications()->get_Message;
899             $self->error(
900             $response->get_Notifications()->get_Message->get_value);
901             }
902             catch {
903             warn $response->get_faultstring;
904             $self->error($response->get_faultstring->get_value);
905             };
906             };
907             }
908              
909              
910             1;
911              
912             __END__
913              
914             =pod
915              
916             =encoding UTF-8
917              
918             =head1 NAME
919              
920             Shipment::FedEx
921              
922             =head1 VERSION
923              
924             version 2.00
925              
926             =head1 SYNOPSIS
927              
928             use Shipment::FedEx;
929             use Shipment::Address;
930             use Shipment::Package;
931              
932             my $shipment = Shipment::FedEx->new(
933             from_address => Shipment::Address->new( ... ),
934             to_address => Shipment::Address->new( ... ),
935             packages => [ Shipment::Package->new( ... ), ],
936             );
937              
938             foreach my $service ( $shipment->all_services ) {
939             print $service->id . "\n";
940             }
941              
942             $shipment->rate( 'express' );
943             print $shipment->service->cost . "\n";
944              
945             $shipment->ship( 'ground' );
946             $shipment->get_package(0)->label->save;
947              
948             =head1 NAME
949              
950             Shipment::FedEx - Interface to FedEx Shipping Web Services
951              
952             =head1 ABOUT
953              
954             This class provides an interface to the FedEx Web Services for Shipping. You must sign up for a developer test key in order to make use of this module.
955              
956             https://www.fedex.com/wpor/web/jsp/drclinks.jsp?links=techresources/index.html
957              
958             It is an extension of L<Shipment::Base>.
959              
960             It makes extensive use of SOAP::WSDL in order to create/decode xml requests and responses. The Shipment::FedEx::WSDL interface was created primarily using the wsdl2perl.pl script from SOAP::WSDL.
961              
962             =head1 Class Attributes
963              
964             =head2 meter, key, password
965              
966             Credentials required to access FedEx Web Services
967              
968             =head2 proxy_domain
969              
970             This determines whether you will use the FedEx Web Services Testing Environment or the production (live) environment
971             * wsbeta.fedex.com:443 (testing)
972             * ws.fedex.com:443 (live)
973              
974             =head2 residential_address
975              
976             Flag the ship to address as residential.
977              
978             Default is false.
979              
980             =head2 label_stock_type
981              
982             The label dimensions/type.
983              
984             Default: 4x6
985              
986             =head1 Type Maps
987              
988             =head2 Shipment::Base type maps
989              
990             Shipment::Base provides abstract types which need to be mapped to FedEx codes (i.e. bill_type of "sender" maps to FedEx "SENDER")
991              
992             =head2 Collect billing
993              
994             FedEx offers collect billing (without the need for a billing account #)
995              
996             =head2 custom package types
997              
998             FedEx provides package types in addition to the defaults in Shipment::Base
999             * FEDEX_10KG_BOX
1000             * FEDEX_25KG_BOX
1001              
1002             =head2 default currency
1003              
1004             The default currency is USD
1005              
1006             =head1 Class Methods
1007              
1008             =head2 _build_services
1009              
1010             This calls getRates from the Rate Services API
1011              
1012             Each Service that is returned is added to services
1013              
1014             The following service mapping is used:
1015             * ground => FEDEX_GROUND or GROUND_HOME_DELIVERY or INTERNATIONAL_GROUND
1016             * express => FEDEX_2_DAY or INTERNATIONAL_ECONOMY
1017             * priority => PRIORITY_OVERNIGHT or INTERNATIONAL_PRIORITY
1018              
1019             This method ignores what is in $self->packages and uses a single package weighing 1 pound for rating. The idea is to list what services are available, but for accurate rate comparisons, the rate method should be used.
1020              
1021             =head2 rate
1022              
1023             This calls getRates from the Rate Services API
1024              
1025             =head2 ship
1026              
1027             This method calls processShipment from the Ship Services API
1028              
1029             =head2 cancel
1030              
1031             This method calls deleteShipment from the Ship Services API
1032              
1033             If the tracking id is greater than 12 digits, it assumes that it is a Ground shipment.
1034              
1035             Currently only supports deleting one package (tracking id) at a time - DeletionControl = 'DELETE_ONE_PACKAGE'
1036              
1037             returns "SUCCESS" if successful
1038              
1039             =head2 end_of_day
1040              
1041             This method calls groundClose from the Close Services API
1042              
1043             The manifest is a plain text file intended to be printed off on standard letter paper
1044              
1045             =head1 AUTHOR
1046              
1047             Andrew Baerg @ <andrew at pullingshots dot ca>
1048              
1049             http://pullingshots.ca/
1050              
1051             =head1 BUGS
1052              
1053             Please contact me directly.
1054              
1055             =head1 COPYRIGHT
1056              
1057             Copyright (C) 2010 Andrew J Baerg, All Rights Reserved
1058              
1059             =head1 NO WARRANTY
1060              
1061             Absolutely, positively NO WARRANTY, neither express or implied, is
1062             offered with this software. You use this software at your own risk. In
1063             case of loss, no person or entity owes you anything whatsoever. You
1064             have been warned.
1065              
1066             =head1 LICENSE
1067              
1068             This program is free software; you can redistribute it and/or modify it
1069             under the same terms as Perl itself.
1070              
1071             =head1 AUTHOR
1072              
1073             Andrew Baerg <baergaj@cpan.org>
1074              
1075             =head1 COPYRIGHT AND LICENSE
1076              
1077             This software is copyright (c) 2013 by Andrew Baerg.
1078              
1079             This is free software; you can redistribute it and/or modify it under
1080             the same terms as the Perl 5 programming language system itself.
1081              
1082             =cut