File Coverage

blib/lib/Shipment/FedEx.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


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