From 15514cb97a54b1f17f4e6ef86f860f52281a3003 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Thu, 5 Nov 2009 12:50:00 -0800 Subject: [PATCH 1/9] Begin international USPS shipping driver. --- .../Shop/ShipDriver/USPSInternational.pm | 358 ++++++++++++++++++ .../English/ShipDriver_USPSInternational.pm | 15 + 2 files changed, 373 insertions(+) create mode 100644 lib/WebGUI/Shop/ShipDriver/USPSInternational.pm create mode 100644 lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm diff --git a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm new file mode 100644 index 000000000..a927e3d44 --- /dev/null +++ b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm @@ -0,0 +1,358 @@ +package WebGUI::Shop::ShipDriver::USPSInternational; + +use strict; +use base qw/WebGUI::Shop::ShipDriver/; +use WebGUI::Exception; +use XML::Simple; +use LWP; +use Tie::IxHash; +use Data::Dumper; + +=head1 NAME + +Package WebGUI::Shop::ShipDriver::USPSInternational + +=head1 DESCRIPTION + +Shipping driver for the United States Postal Service, international shipping services. + +=head1 SYNOPSIS + +=head1 METHODS + +See the master class, WebGUI::Shop::ShipDriver for information about +base methods. These methods are customized in this class: + +=cut + +#------------------------------------------------------------------- + +=head2 buildXML ( $cart, @packages ) + +Returns XML for submitting to the US Postal Service servers + +=head3 $cart + +A WebGUI::Shop::Cart object. This allows us access to the user's +address book + +=head3 @packages + +An array of array references. Each array element is 1 set of items. The +quantity of items will vary in each set. If the quantity of an item +is more than 1, then we will check for shipping 1 item, and multiple the +result by the quantity, rather than doing several identical checks. + +=cut + +sub buildXML { + my ($self, $cart, @packages) = @_; + tie my %xmlHash, 'Tie::IxHash'; + %xmlHash = ( RateV3Request => {}, ); + my $xmlTop = $xmlHash{RateV3Request}; + $xmlTop->{USERID} = $self->get('userId'); + $xmlTop->{Package} = []; + ##Do a request for each package. + my $packageIndex; + my $shipType = $self->get('shipType'); + my $service = $shipType eq 'PRIORITY VARIABLE' ? 'PRIORITY' + : $shipType; + PACKAGE: for(my $packageIndex = 0; $packageIndex < scalar @packages; $packageIndex++) { + my $package = $packages[$packageIndex]; + next PACKAGE unless scalar @{ $package }; + tie my %packageData, 'Tie::IxHash'; + my $weight = 0; + foreach my $item (@{ $package }) { + my $sku = $item->getSku; + my $itemWeight = $sku->getWeight(); + ##Items that ship separately with a quantity > 1 are rate estimated as 1 item and then the + ##shipping cost is multiplied by the quantity. + if (! $sku->shipsSeparately ) { + $itemWeight *= $item->get('quantity'); + } + $weight += $itemWeight; + } + my $pounds = int($weight); + my $ounces = sprintf '%3.1f', (16 * ($weight - $pounds)); + if ($pounds == 0 && $ounces eq '0.0' ) { + $ounces = 0.1; + } + my $destination = $package->[0]->getShippingAddress; + my $destZipCode = $destination->get('code'); + $packageData{ID} = $packageIndex; + $packageData{Service} = [ $service ]; + $packageData{ZipOrigination} = [ $self->get('sourceZip') ]; + $packageData{ZipDestination} = [ $destZipCode ]; + $packageData{Pounds} = [ $pounds ]; + $packageData{Ounces} = [ $ounces ]; + if ($shipType eq 'PRIORITY') { + $packageData{Container} = [ 'FLAT RATE BOX' ]; + } + elsif ($shipType eq 'PRIORITY VARIABLE') { + #$packageData{Container} = [ 'VARIABLE' ]; + } + $packageData{Size} = [ 'REGULAR' ]; + $packageData{Machinable} = [ 'true' ]; + push @{ $xmlTop->{Package} }, \%packageData; + } + my $xml = XMLout(\%xmlHash, + KeepRoot => 1, + NoSort => 1, + NoIndent => 1, + KeyAttr => { + Package => 'ID', + }, + SuppressEmpty => 0, + ); + return $xml; +} + + +#------------------------------------------------------------------- + +=head2 calculate ( $cart ) + +Returns a shipping price. + +=head3 $cart + +A WebGUI::Shop::Cart object. The contents of the cart are analyzed to calculate +the shipping costs. If no items in the cart require shipping, then no shipping +costs are assessed. + +=cut + +sub calculate { + my ($self, $cart) = @_; + if (! $self->get('sourceZip')) { + WebGUI::Error::InvalidParam->throw(error => q{Driver configured without a source zipcode.}); + } + if (! $self->get('userId')) { + WebGUI::Error::InvalidParam->throw(error => q{Driver configured without a USPS userId.}); + } + if ($cart->getShippingAddress->get('country') ne 'United States') { + WebGUI::Error::InvalidParam->throw(error => q{Driver only handles domestic shipping}); + } + my $cost = 0; + ##Sort the items into shippable bundles. + my @shippableUnits = $self->_getShippableUnits($cart); + my $packageCount = scalar @shippableUnits; + if ($packageCount > 25) { + WebGUI::Error::InvalidParam->throw(error => q{Cannot do USPS lookups for more than 25 items.}); + } + my $anyShippable = $packageCount > 0 ? 1 : 0; + return $cost unless $anyShippable; + #$cost = scalar @shippableUnits * $self->get('flatFee'); + ##Build XML ($cart, @shippableUnits) + my $xml = $self->buildXML($cart, @shippableUnits); + ##Do request ($xml) + my $response = $self->_doXmlRequest($xml); + ##Error handling + if (! $response->is_success) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Problem connecting to USPS Web Tools: '. $response->status_line); + } + my $returnedXML = $response->content; + my $xmlData = XMLin($returnedXML, KeepRoot => 1, ForceArray => [qw/Package/]); + if (exists $xmlData->{Error}) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Problem with USPS Web Tools XML: '. $xmlData->{Error}->{Description}); + } + ##Summarize costs from returned data + $cost = $self->_calculateFromXML($xmlData, @shippableUnits); + return $cost; +} + +#------------------------------------------------------------------- + +=head2 _calculateFromXML ( $xmlData, @shippableUnits ) + +Takes data from the USPS and returns the calculated shipping price. + +=head3 $xmlData + +Processed XML data from an XML rate request, processed in perl data structure. The data is expected to +have this structure: + + { + RateV3Response => { + Package => [ + { + ID => 0, + Postage => { + Rate => some_number + } + }, + ] + } + } + +=head3 @shippableUnits + +The set of shippable units, which are required to do quantity lookups. + +=cut + +sub _calculateFromXML { + my ($self, $xmlData, @shippableUnits) = @_; + my $cost = 0; + foreach my $package (@{ $xmlData->{RateV3Response}->{Package} }) { + my $id = $package->{ID}; + my $rate = $package->{Postage}->{Rate}; + ##Error check for invalid index + if ($id < 0 || $id > $#shippableUnits) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => "Illegal package index returned by USPS: $id"); + } + if (exists $package->{Error}) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => $package->{Description}); + } + my $unit = $shippableUnits[$id]; + if ($unit->[0]->getSku->shipsSeparately) { + ##This is a single item due to ships separately. Since in reality there will be + ## N things being shipped, multiply the rate by the quantity. + $cost += $rate * $unit->[0]->get('quantity'); + } + else { + ##This is a loose bundle of items, all shipped together + $cost += $rate; + } + } + return $cost; +} + +#------------------------------------------------------------------- + +=head2 definition ( $session ) + +This subroutine returns an arrayref of hashrefs, used to validate data put into +the object by the user, and to automatically generate the edit form to show +the user. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + WebGUI::Error::InvalidParam->throw(error => q{Must provide a session variable}) + unless ref $session eq 'WebGUI::Session'; + my $definition = shift || []; + my $i18n = WebGUI::International->new($session, 'ShipDriver_USPS'); + my $i18n2 = WebGUI::International->new($session, 'ShipDriver_USPSInternational'); + tie my %shippingTypes, 'Tie::IxHash'; + ##Note, these keys are used by buildXML + $shippingTypes{'PRIORITY VARIABLE'} = $i18n->get('priority variable'); + $shippingTypes{'PRIORITY'} = $i18n->get('priority'); + $shippingTypes{'EXPRESS' } = $i18n->get('express'); + $shippingTypes{'PARCEL' } = $i18n->get('parcel post'); + tie my %fields, 'Tie::IxHash'; + %fields = ( + instructions => { + fieldType => 'readOnly', + label => $i18n->get('instructions'), + defaultValue => $i18n->get('usps instructions'), + noFormProcess => 1, + }, + userId => { + fieldType => 'text', + label => $i18n->get('userid'), + hoverHelp => $i18n->get('userid help'), + defaultValue => '', + }, + shipType => { + fieldType => 'selectBox', + label => $i18n->get('ship type'), + hoverHelp => $i18n->get('ship type help'), + options => \%shippingTypes, + defaultValue => 'PARCEL', + }, +##Note, if a flat fee is added to this driver, then according to the license +##terms the website must display a note to the user (shop customer) that additional +##fees have been added. +# flatFee => { +# fieldType => 'float', +# label => $i18n->get('flatFee'), +# hoverHelp => $i18n->get('flatFee help'), +# defaultValue => 0, +# }, + ); + my %properties = ( + name => $i18n2->get('U.S. Postal Service, International'), + properties => \%fields, + ); + push @{ $definition }, \%properties; + return $class->SUPER::definition($session, $definition); +} + +#------------------------------------------------------------------- + +=head2 _doXmlRequest ( $xml ) + +Contact the USPS website and submit the XML for a shipping rate lookup. +Returns a LWP::UserAgent response object. + +=head3 $xml + +XML to send. It has some very high standards, including XML components in +the right order and sets of allowed tags. + +=cut + +sub _doXmlRequest { + my ($self, $xml) = @_; + my $userAgent = LWP::UserAgent->new; + $userAgent->env_proxy; + $userAgent->agent('WebGUI'); + my $url = 'http://production.shippingapis.com/ShippingAPI.dll?API=IntlRate&XML='; + $url .= $xml; + my $request = HTTP::Request->new(GET => $url); + my $response = $userAgent->request($request); + return $response; +} + +#------------------------------------------------------------------- + +=head2 _getShippableUnits ( $cart ) + +This is a private method. + +Sorts items into the cart by how they must be shipped, together, separate, +etc. Returns an array of array references of cart items grouped by +whether or not they ship separately, and then sorted by destination +zip code. + +If an item in the cart must be shipped separately, but has a quantity greater +than 1, then for the purposes of looking up shipping costs it is returned +as 1 bundle, since the total cost can now be calculated by multiplying the +quantity together with the cost for a single unit. + +For an empty cart (which shouldn't ever happen), it would return an empty array. + +=head3 $cart + +A WebGUI::Shop::Cart object. It provides access to the items in the cart +that must be sorted. + +=cut + +sub _getShippableUnits { + my ($self, $cart) = @_; + my @shippableUnits = (); + ##Loose units are sorted by zip code. + my %looseUnits = (); + ITEM: foreach my $item (@{$cart->getItems}) { + my $sku = $item->getSku; + next ITEM unless $sku->isShippingRequired; + if ($sku->shipsSeparately) { + push @shippableUnits, [ $item ]; + } + else { + my $zip = $item->getShippingAddress->get('code'); + if ($item->getShippingAddress->get('country') ne 'United States') { + WebGUI::Error::InvalidParam->throw(error => q{Driver only handles domestic shipping}); + } + push @{ $looseUnits{$zip} }, $item; + } + } + push @shippableUnits, values %looseUnits; + return @shippableUnits; +} + +1; diff --git a/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm b/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm new file mode 100644 index 000000000..5de6964b1 --- /dev/null +++ b/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm @@ -0,0 +1,15 @@ +package WebGUI::i18n::English::ShipDriver_USPS; + +use strict; + +our $I18N = { + + 'U.S. Postal Service, International' => { + message => q|U.S. Postal Service, International|, + lastUpdated => 1203569535, + context => q|Name of the shipping driver|, + } + +}; + +1; From c1f7788309e6d5fca41855052cd5e4bd2707ea2f Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Thu, 5 Nov 2009 15:33:17 -0800 Subject: [PATCH 2/9] Copy tests from USPS. Add insurance back to the International driver. Reverse the country check. --- .../Shop/ShipDriver/USPSInternational.pm | 14 +- t/Shop/ShipDriver/USPSInternational.t | 861 ++++++++++++++++++ 2 files changed, 871 insertions(+), 4 deletions(-) create mode 100644 t/Shop/ShipDriver/USPSInternational.t diff --git a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm index a927e3d44..d583dedab 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm @@ -130,8 +130,8 @@ sub calculate { if (! $self->get('userId')) { WebGUI::Error::InvalidParam->throw(error => q{Driver configured without a USPS userId.}); } - if ($cart->getShippingAddress->get('country') ne 'United States') { - WebGUI::Error::InvalidParam->throw(error => q{Driver only handles domestic shipping}); + if ($cart->getShippingAddress->get('country') eq 'United States') { + WebGUI::Error::InvalidParam->throw(error => q{Driver only handles international shipping}); } my $cost = 0; ##Sort the items into shippable bundles. @@ -263,6 +263,12 @@ sub definition { options => \%shippingTypes, defaultValue => 'PARCEL', }, + addInsurance => { + fieldType => 'yesNo', + label => $i18n->get('add insurance'), + hoverHelp => $i18n->get('add insurance help'), + defaultValue => 0, + }, ##Note, if a flat fee is added to this driver, then according to the license ##terms the website must display a note to the user (shop customer) that additional ##fees have been added. @@ -345,8 +351,8 @@ sub _getShippableUnits { } else { my $zip = $item->getShippingAddress->get('code'); - if ($item->getShippingAddress->get('country') ne 'United States') { - WebGUI::Error::InvalidParam->throw(error => q{Driver only handles domestic shipping}); + if ($item->getShippingAddress->get('country') eq 'United States') { + WebGUI::Error::InvalidParam->throw(error => q{Driver only handles international shipping}); } push @{ $looseUnits{$zip} }, $item; } diff --git a/t/Shop/ShipDriver/USPSInternational.t b/t/Shop/ShipDriver/USPSInternational.t new file mode 100644 index 000000000..55593d256 --- /dev/null +++ b/t/Shop/ShipDriver/USPSInternational.t @@ -0,0 +1,861 @@ +# vim:syntax=perl +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------ +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------ + +# Write a little about what this script tests. +# +# + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; +use Test::More; +use Test::Deep; +use XML::Simple; +use Data::Dumper; + +use WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Shop::ShipDriver::USPSInternational; + +plan tests => 53; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; +my $user = WebGUI::User->create($session); +WebGUI::Test->usersToDelete($user); +$session->user({user => $user}); + +#---------------------------------------------------------------------------- +# Tests + +#---------------------------------------------------------------------------- +# put your tests here + + +my ($driver2, $cart); + +my $versionTag = WebGUI::VersionTag->getWorking($session); + +my $home = WebGUI::Asset->getDefault($session); + +my $rockHammer = $home->addChild({ + className => 'WebGUI::Asset::Sku::Product', + isShippingRequired => 1, title => 'Rock Hammers', + shipsSeparately => 0, +}); + +my $smallHammer = $rockHammer->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'Small rock hammer', price => 7.50, + varSku => 'small-hammer', weight => 1.5, + quantity => 9999, + } +); + +my $bigHammer = $rockHammer->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'Big rock hammer', price => 19.99, + varSku => 'big-hammer', weight => 12, + quantity => 9999, + } +); + +my $bible = $home->addChild({ + className => 'WebGUI::Asset::Sku::Product', + isShippingRequired => 1, title => 'Bibles, individuall wrapped and shipped', + shipsSeparately => 1, +}); + +my $kjvBible = $bible->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'King James Bible', price => 17.50, + varSku => 'kjv-bible', weight => 2.5, + quantity => 99999, + } +); + +my $nivBible = $bible->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'NIV Bible', price => 22.50, + varSku => 'niv-bible', weight => 2.0, + quantity => 999999, + } +); + +my $gospels = $bible->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'Gospels from the new Testament', + price => 1.50, varSku => 'gospels', + weight => 2.0, quantity => 999999, + } +); + +$versionTag->commit; +addToCleanup($versionTag); + +####################################################################### +# +# definition +# +####################################################################### + +my $definition; +my $e; ##Exception variable, used throughout the file + +eval { $definition = WebGUI::Shop::ShipDriver::USPSInternational->definition(); }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidParam', 'definition takes an exception to not giving it a session variable'); +cmp_deeply( + $e, + methods( + error => 'Must provide a session variable', + ), + '... checking error message', +); + + +isa_ok( + $definition = WebGUI::Shop::ShipDriver::USPS->definition($session), + 'ARRAY' +); + + +####################################################################### +# +# create +# +####################################################################### + +my $options = { + label => 'USPS Driver', + enabled => 1, + }; + +$driver2 = WebGUI::Shop::ShipDriver::USPS->create($session, $options); +addToCleanup($driver2); + +isa_ok($driver2, 'WebGUI::Shop::ShipDriver::USPS'); +isa_ok($driver2, 'WebGUI::Shop::ShipDriver'); + +####################################################################### +# +# getName +# +####################################################################### + +is (WebGUI::Shop::ShipDriver::USPS->getName($session), 'U.S. Postal Service', 'getName returns the human readable name of this driver'); + +####################################################################### +# +# delete +# +####################################################################### + +my $driverId = $driver2->getId; +$driver2->delete; + +my $count = $session->db->quickScalar('select count(*) from shipper where shipperId=?',[$driverId]); +is($count, 0, 'delete deleted the object'); + +undef $driver2; + +####################################################################### +# +# calculate, and private methods. +# +####################################################################### + +my $driver = WebGUI::Shop::ShipDriver::USPS->create($session, { + label => 'Shipping from Shawshank', + enabled => 1, + shipType => 'PARCEL', +}); +addToCleanup($driver); + +eval { $driver->calculate() }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidParam', 'calculate throws an exception when no zipcode has been set'); +cmp_deeply( + $e, + methods( + error => 'Driver configured without a source zipcode.', + ), + '... checking error message', +); + +my $properties = $driver->get(); +$properties->{sourceZip} = '97123'; +$driver->update($properties); + +eval { $driver->calculate() }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidParam', 'calculate throws an exception when no userId'); +cmp_deeply( + $e, + methods( + error => 'Driver configured without a USPS userId.', + ), + '... checking error message', +); + +$cart = WebGUI::Shop::Cart->newBySession($session); +addToCleanup($cart); +my $addressBook = $cart->getAddressBook; +my $workAddress = $addressBook->addAddress({ + label => 'work', + organization => 'Plain Black Corporation', + address1 => '1360 Regent St. #145', + city => 'Madison', state => 'WI', code => '53715', + country => 'United States', +}); +my $wucAddress = $addressBook->addAddress({ + label => 'wuc', + organization => 'Madison Concourse Hotel', + address1 => '1 W Dayton St', + city => 'Madison', state => 'WI', code => '53703', + country => 'United States', +}); +$cart->update({shippingAddressId => $workAddress->getId}); + +cmp_deeply( + [$driver->_getShippableUnits($cart)], + [(), ], + '_getShippableUnits: empty cart' +); + +$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); +cmp_deeply( + [$driver->_getShippableUnits($cart)], + [[ ignore() ], ], + '_getShippableUnits: one loose item in the cart' +); + +$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); +cmp_deeply( + [$driver->_getShippableUnits($cart)], + [[ ignore(), ignore() ], ], + '_getShippableUnits: two loose items in the cart' +); + +$bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $kjvBible)); +cmp_bag( + [$driver->_getShippableUnits($cart)], + [[ ignore(), ignore() ], [ ignore(), ], ], + '_getShippableUnits: two loose items, and 1 ships separately item in the cart' +); + +my $bibleItem = $bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $nivBible)); +$bibleItem->setQuantity(5); +cmp_bag( + [$driver->_getShippableUnits($cart)], + [[ ignore(), ignore() ], [ ignore() ], [ ignore() ], ], + '_getShippableUnits: two loose items, and 2 ships separately item in the cart, regarless of quantity for the new item' +); + +my $rockHammer2 = $bible->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); +$rockHammer2->update({shippingAddressId => $wucAddress->getId}); +cmp_bag( + [$driver->_getShippableUnits($cart)], + [[ ignore(), ignore() ], [ ignore() ], [ ignore() ], [ ignore() ], ], + '_getShippableUnits: two loose items, and 2 ships separately item in the cart, and another loose item sorted by zipcode' +); + +$cart->empty; +$bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $nivBible)); +cmp_deeply( + [$driver->_getShippableUnits($cart)], + [ [ ignore() ], ], + '_getShippableUnits: only 1 ships separately item in the cart' +); +$cart->empty; + +my $userId = $session->config->get('testing/USPS_userId'); +my $hasRealUserId = 1; +##If there isn't a userId, set a fake one for XML testing. +if (! $userId) { + $hasRealUserId = 0; + $userId = "blahBlahBlah"; +} +$properties = $driver->get(); +$properties->{userId} = $userId; +$properties->{sourceZip} = '97123'; +$driver->update($properties); + +$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); +my @shippableUnits = $driver->_getShippableUnits($cart); + +$properties = $driver->get(); +$properties->{addInsurance} = 1; +$driver->update($properties); + +is($driver->_calculateInsurance(@shippableUnits), 2, '_calculateInsurance: one item in cart with quantity=1, calculates insurance'); + +$properties->{addInsurance} = 0; +$driver->update($properties); +is($driver->_calculateInsurance(@shippableUnits), 0, '_calculateInsurance: returns 0 if insurance is not enabled'); + +$properties->{addInsurance} = 1; +$properties->{insuranceRates} = ''; +$driver->update($properties); +is($driver->_calculateInsurance(@shippableUnits), 0, '_calculateInsurance: returns 0 if rates are not set'); + +my $xml = $driver->buildXML($cart, @shippableUnits); +like($xml, qr/ 1, + ForceArray => ['Package'], +); +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + ], + } + }, + 'buildXML: PARCEL service, 1 item in cart' +); + +like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '_doXmlRequest to USPS successful'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Machinable => ignore(), Ounces => ignore(), + Pounds => ignore(), Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + ], + }, + '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' + ); + +} + +my $cost = $driver->_calculateFromXML( + { + RateV3Response => { + Package => [ + { + ID => 0, + Postage => { + Rate => 5.25, + }, + }, + ], + }, + }, + @shippableUnits +); + +is($cost, 5.25, '_calculateFromXML calculates shipping cost correctly for 1 item in the cart'); + +$bibleItem = $bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $nivBible)); +@shippableUnits = $driver->_getShippableUnits($cart); + +is(calculateInsurance($driver), 7, '_calculateInsurance: two items in cart with quantity=1, calculates insurance'); + +$xml = $driver->buildXML($cart, @shippableUnits); +$xmlData = XMLin( $xml, + KeepRoot => 1, + ForceArray => ['Package'], +); + +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '2', Ounces => '0.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + { + ID => 1, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + ], + } + }, + 'Validate XML structure and content for 2 items in the cart' +); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '_doXmlRequest to USPS successful for 2 items in cart'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Machinable => ignore(), Ounces => '0.0', + Pounds => 2, Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + { + ID => 1, + ZipOrigination => ignore(), ZipDestination => ignore(), + Machinable => ignore(), Ounces => '8.0', + Pounds => 1, Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + ], + }, + '... returned data from USPS in correct format for 2 items in cart. If this test fails, the driver may need to be updated' + ); + +} + +$cost = $driver->_calculateFromXML( + { + RateV3Response => { + Package => [ + { + ID => 0, + Postage => { + Rate => 7.00, + }, + }, + { + ID => 1, + Postage => { + Rate => 5.25, + }, + }, + ], + }, + }, + @shippableUnits +); + +is($cost, 12.25, '_calculateFromXML calculates shipping cost correctly for 2 items in the cart'); + +$bibleItem->setQuantity(2); +@shippableUnits = $driver->_getShippableUnits($cart); + +is(calculateInsurance($driver), 8, '_calculateInsurance: two items in cart with quantity=2, calculates insurance'); + +$cost = $driver->_calculateFromXML( + { + RateV3Response => { + Package => [ + { + ID => 0, + Postage => { + Rate => 7.00, + }, + }, + { + ID => 1, + Postage => { + Rate => 5.25, + }, + }, + ], + }, + }, + @shippableUnits +); +is($cost, 19.25, '_calculateFromXML calculates shipping cost correctly for 2 items in the cart, with quantity of 2'); + +$rockHammer2 = $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); +$rockHammer2->update({shippingAddressId => $wucAddress->getId}); +@shippableUnits = $driver->_getShippableUnits($cart); +is(calculateInsurance($driver), 12, '_calculateInsurance: calculates insurance'); +$xml = $driver->buildXML($cart, @shippableUnits); + +$xmlData = XMLin( $xml, + KeepRoot => 1, + ForceArray => ['Package'], +); + +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '2', Ounces => '0.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + { + ID => 1, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + { + ID => 2, + ZipDestination => '53703', ZipOrigination => '97123', + Pounds => '12', Ounces => '0.0', + Size => 'REGULAR', Service => 'PARCEL', + Machinable => 'true', + }, + ], + } + }, + 'Validate XML structure and content for 3 items in the cart, 3 shippable items' +); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '_doXmlRequest to USPS successful for 3 items in cart'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Machinable => ignore(), Ounces => '0.0', + Pounds => 2, Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + { + ID => 1, + ZipOrigination => ignore(), ZipDestination => ignore(), + Machinable => ignore(), Ounces => '8.0', + Pounds => 1, Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + { + ID => 2, + ZipOrigination => ignore(), ZipDestination => 53703, + Machinable => ignore(), Ounces => '0.0', + Pounds => 12, Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(20,20), ##A number around 20... + } + }, + ], + }, + '... returned data from USPS in correct format for 3 items in cart. If this test fails, the driver may need to be updated' + ); + +} + +####################################################################### +# +# Test Priority shipping setup +# +####################################################################### + +$cart->empty; +$properties = $driver->get(); +$properties->{shipType} = 'PRIORITY'; +$driver->update($properties); + +$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); +@shippableUnits = $driver->_getShippableUnits($cart); +$xml = $driver->buildXML($cart, @shippableUnits); +my $xmlData = XMLin($xml, + KeepRoot => 1, + ForceArray => ['Package'], +); +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'PRIORITY', + Machinable => 'true', Container => 'FLAT RATE BOX', + }, + ], + } + }, + 'buildXML: PRIORITY service, 1 item in cart' +); +like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Container.+?Size.+?Machinable/, '... and tag order'); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '_doXmlRequest to USPS successful'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Container => ignore(), Ounces => ignore(), ##Machinable missing, added Container + Pounds => ignore(), Size => ignore(), + Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(10,10), ##A number around 10... + } + }, + ], + }, + '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' + ); + +} + +####################################################################### +# +# Test EXPRESS shipping setup +# +####################################################################### + +$properties = $driver->get(); +$properties->{shipType} = 'EXPRESS'; +$driver->update($properties); + +$xml = $driver->buildXML($cart, @shippableUnits); +my $xmlData = XMLin($xml, + KeepRoot => 1, + ForceArray => ['Package'], +); +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'EXPRESS', + Machinable => 'true', + }, + ], + } + }, + 'buildXML: EXPRESS service, 1 item in cart' +); +like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '... _doXmlRequest to USPS successful'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Ounces => ignore(), Pounds => ignore(), + Size => ignore(), Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(30,30), ##A number around 10... + } + }, + ], + }, + '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' + ); + +} + +####################################################################### +# +# Test PRIORITY VARIABLE shipping setup +# +####################################################################### + +$properties = $driver->get(); +$properties->{shipType} = 'PRIORITY VARIABLE'; +$driver->update($properties); + +$xml = $driver->buildXML($cart, @shippableUnits); +my $xmlData = XMLin($xml, + KeepRoot => 1, + ForceArray => ['Package'], +); +cmp_deeply( + $xmlData, + { + RateV3Request => { + USERID => $userId, + Package => [ + { + ID => 0, + ZipDestination => '53715', ZipOrigination => '97123', + Pounds => '1', Ounces => '8.0', + Size => 'REGULAR', Service => 'PRIORITY', + Machinable => 'true',# Container => 'VARIABLE', + }, + ], + } + }, + 'buildXML: PRIORITY, VARIABLE service, 1 item in cart' +); +like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); + +SKIP: { + + skip 'No userId for testing', 2 unless $hasRealUserId; + + my $response = $driver->_doXmlRequest($xml); + ok($response->is_success, '... _doXmlRequest to USPS successful'); + my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + cmp_deeply( + $xmlData, + { + Package => [ + { + ID => 0, + ZipOrigination => ignore(), ZipDestination => ignore(), + Ounces => ignore(), Pounds => ignore(), + Size => ignore(), Zone => ignore(), + Postage => { + CLASSID => ignore(), + MailService => ignore(), + Rate => num(8,8), ##A number around 10... + } + }, + ], + }, + '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' + ); + +} + +####################################################################### +# +# Check for throwing an exception +# +####################################################################### + +my $userId = $driver->get('userId'); +$properties = $driver->get(); +$properties->{userId} = '__NEVER_GOING_TO_HAPPEN__'; +$driver->update($properties); + +$cost = eval { $driver->calculate($cart); }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::Shop::RemoteShippingRate', 'calculate throws an exception when a bad userId is used'); + +$properties->{userId} = $userId; +$driver->update($properties); + +my $dutchAddress = $addressBook->addAddress({ + label => 'dutch', + address1 => 'Rotterdamseweg 183C', + city => 'Delft', code => '2629HD', + country => 'Netherlands', +}); + +$cart->update({shippingAddressId => $dutchAddress->getId}); +$cost = eval { $driver->calculate($cart); }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidParam', "calculate won't calculate for foreign countries"); + +$cart->update({shippingAddressId => $workAddress->getId}); + +# +#-2147219500 +#DomesticRatesV3;clsRateV3.ValidateWeight;RateEngineV3.ProcessRequest +#Please enter the package weight. +#1000440 + +####################################################################### +# +# _calculateInsurance edge case +# +####################################################################### +$cart->empty; +$bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $gospels)); +@shippableUnits = $driver->_getShippableUnits($cart); +is(calculateInsurance($driver), 1, '_calculateInsurance: calculates insurance using the first bin'); + + From 6543ed502734a0652af22ed27519a11d64d78eaa Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 6 Nov 2009 19:43:12 -0800 Subject: [PATCH 3/9] All tests passing, able to talk to USPS and get rates for multiple package configurations. --- .../Shop/ShipDriver/USPSInternational.pm | 59 +- .../English/ShipDriver_USPSInternational.pm | 52 +- t/Shop/ShipDriver/USPSInternational.t | 547 +++++------------- 3 files changed, 228 insertions(+), 430 deletions(-) diff --git a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm index d583dedab..e614bddb7 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm @@ -48,15 +48,12 @@ result by the quantity, rather than doing several identical checks. sub buildXML { my ($self, $cart, @packages) = @_; tie my %xmlHash, 'Tie::IxHash'; - %xmlHash = ( RateV3Request => {}, ); - my $xmlTop = $xmlHash{RateV3Request}; + %xmlHash = ( IntlRateRequest => {}, ); + my $xmlTop = $xmlHash{IntlRateRequest}; $xmlTop->{USERID} = $self->get('userId'); $xmlTop->{Package} = []; ##Do a request for each package. my $packageIndex; - my $shipType = $self->get('shipType'); - my $service = $shipType eq 'PRIORITY VARIABLE' ? 'PRIORITY' - : $shipType; PACKAGE: for(my $packageIndex = 0; $packageIndex < scalar @packages; $packageIndex++) { my $package = $packages[$packageIndex]; next PACKAGE unless scalar @{ $package }; @@ -78,21 +75,13 @@ sub buildXML { $ounces = 0.1; } my $destination = $package->[0]->getShippingAddress; - my $destZipCode = $destination->get('code'); - $packageData{ID} = $packageIndex; - $packageData{Service} = [ $service ]; - $packageData{ZipOrigination} = [ $self->get('sourceZip') ]; - $packageData{ZipDestination} = [ $destZipCode ]; - $packageData{Pounds} = [ $pounds ]; - $packageData{Ounces} = [ $ounces ]; - if ($shipType eq 'PRIORITY') { - $packageData{Container} = [ 'FLAT RATE BOX' ]; - } - elsif ($shipType eq 'PRIORITY VARIABLE') { - #$packageData{Container} = [ 'VARIABLE' ]; - } - $packageData{Size} = [ 'REGULAR' ]; - $packageData{Machinable} = [ 'true' ]; + my $country = $destination->get('country'); + $packageData{ID} = $packageIndex; + $packageData{Pounds} = [ $pounds ]; + $packageData{Ounces} = [ $ounces ]; + $packageData{Machinable} = [ 'true' ]; + $packageData{MailType} = [ 'Package' ]; + $packageData{Country} = [ $country ]; push @{ $xmlTop->{Package} }, \%packageData; } my $xml = XMLout(\%xmlHash, @@ -124,9 +113,6 @@ costs are assessed. sub calculate { my ($self, $cart) = @_; - if (! $self->get('sourceZip')) { - WebGUI::Error::InvalidParam->throw(error => q{Driver configured without a source zipcode.}); - } if (! $self->get('userId')) { WebGUI::Error::InvalidParam->throw(error => q{Driver configured without a USPS userId.}); } @@ -173,7 +159,7 @@ Processed XML data from an XML rate request, processed in perl data structure. have this structure: { - RateV3Response => { + IntlRateResponse => { Package => [ { ID => 0, @@ -194,17 +180,24 @@ The set of shippable units, which are required to do quantity lookups. sub _calculateFromXML { my ($self, $xmlData, @shippableUnits) = @_; my $cost = 0; - foreach my $package (@{ $xmlData->{RateV3Response}->{Package} }) { + foreach my $package (@{ $xmlData->{IntlRateResponse}->{Package} }) { my $id = $package->{ID}; - my $rate = $package->{Postage}->{Rate}; ##Error check for invalid index if ($id < 0 || $id > $#shippableUnits) { WebGUI::Error::Shop::RemoteShippingRate->throw(error => "Illegal package index returned by USPS: $id"); } if (exists $package->{Error}) { - WebGUI::Error::Shop::RemoteShippingRate->throw(error => $package->{Description}); + WebGUI::Error::Shop::RemoteShippingRate->throw(error => $package->{Error}->{Description}); } my $unit = $shippableUnits[$id]; + my $rate; + SERVICE: foreach my $service (@{ $package->{Service} }) { + next SERVICE unless $service->{ID} eq $self->get('shipType'); + $rate = $service->{Postage}; + } + if (!$rate) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Selected shipping service not available'); + } if ($unit->[0]->getSku->shipsSeparately) { ##This is a single item due to ships separately. Since in reality there will be ## N things being shipped, multiply the rate by the quantity. @@ -238,10 +231,14 @@ sub definition { my $i18n2 = WebGUI::International->new($session, 'ShipDriver_USPSInternational'); tie my %shippingTypes, 'Tie::IxHash'; ##Note, these keys are used by buildXML - $shippingTypes{'PRIORITY VARIABLE'} = $i18n->get('priority variable'); - $shippingTypes{'PRIORITY'} = $i18n->get('priority'); - $shippingTypes{'EXPRESS' } = $i18n->get('express'); - $shippingTypes{'PARCEL' } = $i18n->get('parcel post'); + $shippingTypes{1} = $i18n2->get('express mail international'); + $shippingTypes{2} = $i18n2->get('priority mail international'); + $shippingTypes{6} = $i18n2->get('global express guaranteed rectangular'); + $shippingTypes{7} = $i18n2->get('global express guaranteed non-rectangular'); + $shippingTypes{9} = $i18n2->get('priority mail flat rate box'); + $shippingTypes{11} = $i18n2->get('priority mail large flat rate box'); + $shippingTypes{15} = $i18n2->get('first class mail international parcels'); + $shippingTypes{16} = $i18n2->get('priority mail small flat rate box'); tie my %fields, 'Tie::IxHash'; %fields = ( instructions => { diff --git a/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm b/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm index 5de6964b1..b628981f4 100644 --- a/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm +++ b/lib/WebGUI/i18n/English/ShipDriver_USPSInternational.pm @@ -1,4 +1,4 @@ -package WebGUI::i18n::English::ShipDriver_USPS; +package WebGUI::i18n::English::ShipDriver_USPSInternational; use strict; @@ -8,7 +8,55 @@ our $I18N = { message => q|U.S. Postal Service, International|, lastUpdated => 1203569535, context => q|Name of the shipping driver|, - } + }, + + 'express mail international' => { + message => q|Express Mail International|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'priority mail international' => { + message => q|Priority Mail International|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'global express guaranteed rectangular' => { + message => q|Global Express Guaranteed Non-Document Rectangular|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'global express guaranteed non-rectangular' => { + message => q|Global Express Guaranteed Non-Document Non-Rectangular|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'priority mail flat rate box' => { + message => q|Priority Mail Flat Rate Box|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'priority mail large flat rate box' => { + message => q|Priority Mail Large Flat Rate Box|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'priority mail small flat rate box' => { + message => q|Priority Mail Small Flat Rate Box|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, + + 'first class mail international parcels' => { + message => q|First Class Mail International Parcels|, + lastUpdated => 1203569535, + context => q|Name of a shipping option|, + }, }; diff --git a/t/Shop/ShipDriver/USPSInternational.t b/t/Shop/ShipDriver/USPSInternational.t index 55593d256..163c7aeee 100644 --- a/t/Shop/ShipDriver/USPSInternational.t +++ b/t/Shop/ShipDriver/USPSInternational.t @@ -25,7 +25,7 @@ use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; use WebGUI::Shop::ShipDriver::USPSInternational; -plan tests => 53; +plan tests => 34; #---------------------------------------------------------------------------- # Init @@ -124,7 +124,7 @@ cmp_deeply( isa_ok( - $definition = WebGUI::Shop::ShipDriver::USPS->definition($session), + $definition = WebGUI::Shop::ShipDriver::USPSInternational->definition($session), 'ARRAY' ); @@ -136,14 +136,14 @@ isa_ok( ####################################################################### my $options = { - label => 'USPS Driver', + label => 'Intl USPS Driver', enabled => 1, }; -$driver2 = WebGUI::Shop::ShipDriver::USPS->create($session, $options); +$driver2 = WebGUI::Shop::ShipDriver::USPSInternational->create($session, $options); addToCleanup($driver2); -isa_ok($driver2, 'WebGUI::Shop::ShipDriver::USPS'); +isa_ok($driver2, 'WebGUI::Shop::ShipDriver::USPSInternational'); isa_ok($driver2, 'WebGUI::Shop::ShipDriver'); ####################################################################### @@ -152,7 +152,7 @@ isa_ok($driver2, 'WebGUI::Shop::ShipDriver'); # ####################################################################### -is (WebGUI::Shop::ShipDriver::USPS->getName($session), 'U.S. Postal Service', 'getName returns the human readable name of this driver'); +is (WebGUI::Shop::ShipDriver::USPSInternational->getName($session), 'U.S. Postal Service, International', 'getName returns the human readable name of this driver'); ####################################################################### # @@ -174,28 +174,12 @@ undef $driver2; # ####################################################################### -my $driver = WebGUI::Shop::ShipDriver::USPS->create($session, { +my $driver = WebGUI::Shop::ShipDriver::USPSInternational->create($session, { label => 'Shipping from Shawshank', enabled => 1, - shipType => 'PARCEL', }); addToCleanup($driver); -eval { $driver->calculate() }; -$e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', 'calculate throws an exception when no zipcode has been set'); -cmp_deeply( - $e, - methods( - error => 'Driver configured without a source zipcode.', - ), - '... checking error message', -); - -my $properties = $driver->get(); -$properties->{sourceZip} = '97123'; -$driver->update($properties); - eval { $driver->calculate() }; $e = Exception::Class->caught(); isa_ok($e, 'WebGUI::Error::InvalidParam', 'calculate throws an exception when no userId'); @@ -212,17 +196,15 @@ addToCleanup($cart); my $addressBook = $cart->getAddressBook; my $workAddress = $addressBook->addAddress({ label => 'work', - organization => 'Plain Black Corporation', - address1 => '1360 Regent St. #145', - city => 'Madison', state => 'WI', code => '53715', - country => 'United States', + organization => 'ProcoliX', + address1 => 'Rotterdamseweg 183C', + city => 'Delft', code => '2629HD', + country => 'Netherlands', }); -my $wucAddress = $addressBook->addAddress({ - label => 'wuc', - organization => 'Madison Concourse Hotel', - address1 => '1 W Dayton St', - city => 'Madison', state => 'WI', code => '53703', - country => 'United States', +my $sdhAddress = $addressBook->addAddress({ + label => 'other side of planet', + organization => 'SDH', + country => 'Australia', }); $cart->update({shippingAddressId => $workAddress->getId}); @@ -262,7 +244,7 @@ cmp_bag( ); my $rockHammer2 = $bible->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); -$rockHammer2->update({shippingAddressId => $wucAddress->getId}); +$rockHammer2->update({shippingAddressId => $sdhAddress->getId}); cmp_bag( [$driver->_getShippableUnits($cart)], [[ ignore(), ignore() ], [ ignore() ], [ ignore() ], [ ignore() ], ], @@ -285,31 +267,16 @@ if (! $userId) { $hasRealUserId = 0; $userId = "blahBlahBlah"; } -$properties = $driver->get(); -$properties->{userId} = $userId; -$properties->{sourceZip} = '97123'; +my $properties = $driver->get(); +$properties->{userId} = $userId; +$properties->{shipType} = '9'; $driver->update($properties); $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); my @shippableUnits = $driver->_getShippableUnits($cart); -$properties = $driver->get(); -$properties->{addInsurance} = 1; -$driver->update($properties); - -is($driver->_calculateInsurance(@shippableUnits), 2, '_calculateInsurance: one item in cart with quantity=1, calculates insurance'); - -$properties->{addInsurance} = 0; -$driver->update($properties); -is($driver->_calculateInsurance(@shippableUnits), 0, '_calculateInsurance: returns 0 if insurance is not enabled'); - -$properties->{addInsurance} = 1; -$properties->{insuranceRates} = ''; -$driver->update($properties); -is($driver->_calculateInsurance(@shippableUnits), 0, '_calculateInsurance: returns 0 if rates are not set'); - my $xml = $driver->buildXML($cart, @shippableUnits); -like($xml, qr/ { + IntlRateRequest => { USERID => $userId, Package => [ { ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '1', Ounces => '8.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', }, ], } }, - 'buildXML: PARCEL service, 1 item in cart' + 'buildXML: 1 item in cart' ); -like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); +like($xml, qr/IntlRateRequest USERID.+?Package ID=.+?Pounds.+?Ounces.+?Machinable.+?MailType.+?Country.+?/, '... and tag order'); SKIP: { @@ -350,15 +316,25 @@ SKIP: { Package => [ { ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Machinable => ignore(), Ounces => ignore(), - Pounds => ignore(), Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } + AreasServed => ignore(), Prohibitions => ignore(), + ExpressMail => ignore(), CustomsForms => ignore(), + Observations => ignore(), Restrictions => ignore(), + Service => [ + { + ID => ignore(), + MaxWeight => ignore(), + MaxDimensions => ignore(), + MailType => 'Package', + Ounces => '8', + Pounds => '1', + Country => 'NETHERLANDS', + Machinable => 'true', + Postage => num(100,99), + SvcCommitments => ignore(), + SvcDescription => ignore(), + }, + (ignore())x12, + ], }, ], }, @@ -369,13 +345,22 @@ SKIP: { my $cost = $driver->_calculateFromXML( { - RateV3Response => { + IntlRateResponse => { Package => [ { ID => 0, - Postage => { - Rate => 5.25, - }, + Service => [ + { + ID => '9', + Postage => '5.25', + MaxWeight => '70' + }, + { + ID => '11', + Postage => '7.25', + MaxWeight => '70' + }, + ], }, ], }, @@ -388,8 +373,6 @@ is($cost, 5.25, '_calculateFromXML calculates shipping cost correctly for 1 item $bibleItem = $bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $nivBible)); @shippableUnits = $driver->_getShippableUnits($cart); -is(calculateInsurance($driver), 7, '_calculateInsurance: two items in cart with quantity=1, calculates insurance'); - $xml = $driver->buildXML($cart, @shippableUnits); $xmlData = XMLin( $xml, KeepRoot => 1, @@ -399,22 +382,20 @@ $xmlData = XMLin( $xml, cmp_deeply( $xmlData, { - RateV3Request => { + IntlRateRequest => { USERID => $userId, Package => [ { ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '2', Ounces => '0.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '2', Ounces => '0.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', }, { ID => 1, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '1', Ounces => '8.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', }, ], } @@ -428,57 +409,41 @@ SKIP: { my $response = $driver->_doXmlRequest($xml); ok($response->is_success, '_doXmlRequest to USPS successful for 2 items in cart'); - my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); - cmp_deeply( - $xmlData, - { - Package => [ - { - ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Machinable => ignore(), Ounces => '0.0', - Pounds => 2, Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } - }, - { - ID => 1, - ZipOrigination => ignore(), ZipDestination => ignore(), - Machinable => ignore(), Ounces => '8.0', - Pounds => 1, Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } - }, - ], - }, - '... returned data from USPS in correct format for 2 items in cart. If this test fails, the driver may need to be updated' - ); - } $cost = $driver->_calculateFromXML( { - RateV3Response => { + IntlRateResponse => { Package => [ { ID => 0, - Postage => { - Rate => 7.00, - }, + Service => [ + { + ID => '9', + Postage => '7.00', + MaxWeight => '70' + }, + { + ID => '11', + Postage => '9.00', + MaxWeight => '70' + }, + ], }, { ID => 1, - Postage => { - Rate => 5.25, - }, + Service => [ + { + ID => '9', + Postage => '5.25', + MaxWeight => '70' + }, + { + ID => '11', + Postage => '7.25', + MaxWeight => '70' + }, + ], }, ], }, @@ -491,23 +456,39 @@ is($cost, 12.25, '_calculateFromXML calculates shipping cost correctly for 2 ite $bibleItem->setQuantity(2); @shippableUnits = $driver->_getShippableUnits($cart); -is(calculateInsurance($driver), 8, '_calculateInsurance: two items in cart with quantity=2, calculates insurance'); - $cost = $driver->_calculateFromXML( { - RateV3Response => { + IntlRateResponse => { Package => [ { ID => 0, - Postage => { - Rate => 7.00, - }, + Service => [ + { + ID => '9', + Postage => '7.00', + MaxWeight => '70' + }, + { + ID => '11', + Postage => '9.00', + MaxWeight => '70' + }, + ], }, { ID => 1, - Postage => { - Rate => 5.25, - }, + Service => [ + { + ID => '9', + Postage => '5.25', + MaxWeight => '70' + }, + { + ID => '11', + Postage => '7.25', + MaxWeight => '70' + }, + ], }, ], }, @@ -517,9 +498,8 @@ $cost = $driver->_calculateFromXML( is($cost, 19.25, '_calculateFromXML calculates shipping cost correctly for 2 items in the cart, with quantity of 2'); $rockHammer2 = $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); -$rockHammer2->update({shippingAddressId => $wucAddress->getId}); +$rockHammer2->update({shippingAddressId => $sdhAddress->getId}); @shippableUnits = $driver->_getShippableUnits($cart); -is(calculateInsurance($driver), 12, '_calculateInsurance: calculates insurance'); $xml = $driver->buildXML($cart, @shippableUnits); $xmlData = XMLin( $xml, @@ -530,29 +510,26 @@ $xmlData = XMLin( $xml, cmp_deeply( $xmlData, { - RateV3Request => { - USERID => $userId, + IntlRateRequest => { + USERID => $userId, Package => [ { ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '2', Ounces => '0.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '2', Ounces => '0.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', }, { ID => 1, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '12', Ounces => '0.0', + Machinable => 'true', Country => 'Australia', + MailType => 'Package', }, { ID => 2, - ZipDestination => '53703', ZipOrigination => '97123', - Pounds => '12', Ounces => '0.0', - Size => 'REGULAR', Service => 'PARCEL', - Machinable => 'true', + Pounds => '1', Ounces => '8.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', }, ], } @@ -561,252 +538,44 @@ cmp_deeply( ); SKIP: { - skip 'No userId for testing', 2 unless $hasRealUserId; my $response = $driver->_doXmlRequest($xml); ok($response->is_success, '_doXmlRequest to USPS successful for 3 items in cart'); - my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); - cmp_deeply( - $xmlData, - { - Package => [ - { - ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Machinable => ignore(), Ounces => '0.0', - Pounds => 2, Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } - }, - { - ID => 1, - ZipOrigination => ignore(), ZipDestination => ignore(), - Machinable => ignore(), Ounces => '8.0', - Pounds => 1, Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } - }, - { - ID => 2, - ZipOrigination => ignore(), ZipDestination => 53703, - Machinable => ignore(), Ounces => '0.0', - Pounds => 12, Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(20,20), ##A number around 20... - } - }, - ], - }, - '... returned data from USPS in correct format for 3 items in cart. If this test fails, the driver may need to be updated' - ); - } ####################################################################### # -# Test Priority shipping setup +# Check too heavy for my shipping type # ####################################################################### $cart->empty; $properties = $driver->get(); -$properties->{shipType} = 'PRIORITY'; +$properties->{shipType} = '9'; $driver->update($properties); -$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $smallHammer)); -@shippableUnits = $driver->_getShippableUnits($cart); -$xml = $driver->buildXML($cart, @shippableUnits); -my $xmlData = XMLin($xml, - KeepRoot => 1, - ForceArray => ['Package'], -); -cmp_deeply( - $xmlData, - { - RateV3Request => { - USERID => $userId, - Package => [ - { - ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'PRIORITY', - Machinable => 'true', Container => 'FLAT RATE BOX', - }, - ], - } - }, - 'buildXML: PRIORITY service, 1 item in cart' -); -like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Container.+?Size.+?Machinable/, '... and tag order'); - SKIP: { skip 'No userId for testing', 2 unless $hasRealUserId; - my $response = $driver->_doXmlRequest($xml); - ok($response->is_success, '_doXmlRequest to USPS successful'); - my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); + my $heavyHammer = $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); + $heavyHammer->setQuantity(2); + $cost = eval { $driver->calculate($cart); }; + $e = Exception::Class->caught(); + isa_ok($e, 'WebGUI::Error::Shop::RemoteShippingRate', "USPS returns error when package is too heavy for the selected service"); cmp_deeply( - $xmlData, - { - Package => [ - { - ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Container => ignore(), Ounces => ignore(), ##Machinable missing, added Container - Pounds => ignore(), Size => ignore(), - Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(10,10), ##A number around 10... - } - }, - ], - }, - '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' + $e, + methods( + error => 'Selected shipping service not available', + ), + '... checking error message', ); -} - -####################################################################### -# -# Test EXPRESS shipping setup -# -####################################################################### - -$properties = $driver->get(); -$properties->{shipType} = 'EXPRESS'; -$driver->update($properties); - -$xml = $driver->buildXML($cart, @shippableUnits); -my $xmlData = XMLin($xml, - KeepRoot => 1, - ForceArray => ['Package'], -); -cmp_deeply( - $xmlData, - { - RateV3Request => { - USERID => $userId, - Package => [ - { - ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'EXPRESS', - Machinable => 'true', - }, - ], - } - }, - 'buildXML: EXPRESS service, 1 item in cart' -); -like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); - -SKIP: { - - skip 'No userId for testing', 2 unless $hasRealUserId; - - my $response = $driver->_doXmlRequest($xml); - ok($response->is_success, '... _doXmlRequest to USPS successful'); - my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); - cmp_deeply( - $xmlData, - { - Package => [ - { - ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Ounces => ignore(), Pounds => ignore(), - Size => ignore(), Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(30,30), ##A number around 10... - } - }, - ], - }, - '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' - ); - -} - -####################################################################### -# -# Test PRIORITY VARIABLE shipping setup -# -####################################################################### - -$properties = $driver->get(); -$properties->{shipType} = 'PRIORITY VARIABLE'; -$driver->update($properties); - -$xml = $driver->buildXML($cart, @shippableUnits); -my $xmlData = XMLin($xml, - KeepRoot => 1, - ForceArray => ['Package'], -); -cmp_deeply( - $xmlData, - { - RateV3Request => { - USERID => $userId, - Package => [ - { - ID => 0, - ZipDestination => '53715', ZipOrigination => '97123', - Pounds => '1', Ounces => '8.0', - Size => 'REGULAR', Service => 'PRIORITY', - Machinable => 'true',# Container => 'VARIABLE', - }, - ], - } - }, - 'buildXML: PRIORITY, VARIABLE service, 1 item in cart' -); -like($xml, qr/RateV3Request USERID.+?Package ID=.+?Service.+?ZipOrigination.+?ZipDestination.+?Pounds.+?Ounces.+?Size.+?Machinable/, '... and tag order'); - -SKIP: { - - skip 'No userId for testing', 2 unless $hasRealUserId; - - my $response = $driver->_doXmlRequest($xml); - ok($response->is_success, '... _doXmlRequest to USPS successful'); - my $xmlData = XMLin($response->content, ForceArray => [qw/Package/],); - cmp_deeply( - $xmlData, - { - Package => [ - { - ID => 0, - ZipOrigination => ignore(), ZipDestination => ignore(), - Ounces => ignore(), Pounds => ignore(), - Size => ignore(), Zone => ignore(), - Postage => { - CLASSID => ignore(), - MailService => ignore(), - Rate => num(8,8), ##A number around 10... - } - }, - ], - }, - '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' - ); + $heavyHammer->setQuantity(20); + $cost = eval { $driver->calculate($cart); }; + $e = Exception::Class->caught(); + isa_ok($e, 'WebGUI::Error::Shop::RemoteShippingRate', "USPS returns error when package is too heavy for any service"); } @@ -818,7 +587,7 @@ SKIP: { my $userId = $driver->get('userId'); $properties = $driver->get(); -$properties->{userId} = '__NEVER_GOING_TO_HAPPEN__'; +$properties->{userId} = '_NO_NO_NO_NO'; $driver->update($properties); $cost = eval { $driver->calculate($cart); }; @@ -829,33 +598,17 @@ $properties->{userId} = $userId; $driver->update($properties); my $dutchAddress = $addressBook->addAddress({ - label => 'dutch', - address1 => 'Rotterdamseweg 183C', - city => 'Delft', code => '2629HD', - country => 'Netherlands', + label => 'american', + organization => 'Plain Black Corporation', + address1 => '1360 Regent St. #145', + city => 'Madison', state => 'WI', code => '53715', + country => 'United States', }); $cart->update({shippingAddressId => $dutchAddress->getId}); $cost = eval { $driver->calculate($cart); }; $e = Exception::Class->caught(); -isa_ok($e, 'WebGUI::Error::InvalidParam', "calculate won't calculate for foreign countries"); +isa_ok($e, 'WebGUI::Error::InvalidParam', "calculate won't calculate for domestic countries"); $cart->update({shippingAddressId => $workAddress->getId}); -# -#-2147219500 -#DomesticRatesV3;clsRateV3.ValidateWeight;RateEngineV3.ProcessRequest -#Please enter the package weight. -#1000440 - -####################################################################### -# -# _calculateInsurance edge case -# -####################################################################### -$cart->empty; -$bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $gospels)); -@shippableUnits = $driver->_getShippableUnits($cart); -is(calculateInsurance($driver), 1, '_calculateInsurance: calculates insurance using the first bin'); - - From acda103fed972d4415b7a6995e1fdd6f246ca175 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 6 Nov 2009 19:49:20 -0800 Subject: [PATCH 4/9] Add USPS International driver to configuration files. --- docs/upgrades/upgrade_7.8.4-7.8.5.pl | 11 +++++++++++ etc/WebGUI.conf.original | 1 + 2 files changed, 12 insertions(+) diff --git a/docs/upgrades/upgrade_7.8.4-7.8.5.pl b/docs/upgrades/upgrade_7.8.4-7.8.5.pl index 1a027df9e..0931252e2 100644 --- a/docs/upgrades/upgrade_7.8.4-7.8.5.pl +++ b/docs/upgrades/upgrade_7.8.4-7.8.5.pl @@ -30,6 +30,7 @@ my $quiet; # this line required my $session = start(); # this line required fixPackageFlagOnOlder( $session ); +addUSPSInternationalShippingDriver( $session ); # upgrade functions go here @@ -45,6 +46,16 @@ finish($session); # this line required # print "DONE!\n" unless $quiet; #} +#---------------------------------------------------------------------------- +# Describe what our function does +sub addUSPSInternationalShippingDriver { + my $session = shift; + print "\tAdd the USPS International shipping driver... " unless $quiet; + # and here's our code + $session->config->addToArray('shippingDrivers', 'WebGUI::Shop::ShipDriver::USPSInternational'); + print "DONE!\n" unless $quiet; +} + sub fixPackageFlagOnOlder { my $session = shift; print "\tFixing isPackage flag on folders and isDefault on templates from 7.6.35 to 7.7.17 upgrade. If default templates have been deleted from your site, you may see warnings about not being able to find assets. You may safely ignore those warnings. This entire process may take a while.. " unless $quiet; diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original index 229ea13bb..46fbddd80 100644 --- a/etc/WebGUI.conf.original +++ b/etc/WebGUI.conf.original @@ -191,6 +191,7 @@ "shippingDrivers" : [ "WebGUI::Shop::ShipDriver::FlatRate", "WebGUI::Shop::ShipDriver::USPS", + "WebGUI::Shop::ShipDriver::USPSInternational", "WebGUI::Shop::ShipDriver::UPS" ], From 0b483521e859b04aab7ad9b3720fbbefc6c67e5a Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 6 Nov 2009 20:15:09 -0800 Subject: [PATCH 5/9] Update tests for new driver. --- t/Shop/Ship.t | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/t/Shop/Ship.t b/t/Shop/Ship.t index a75073f33..0efead294 100644 --- a/t/Shop/Ship.t +++ b/t/Shop/Ship.t @@ -93,9 +93,10 @@ cmp_bag( [ 'WebGUI::Shop::ShipDriver::FlatRate', 'WebGUI::Shop::ShipDriver::USPS', + 'WebGUI::Shop::ShipDriver::USPSInternational', 'WebGUI::Shop::ShipDriver::UPS', ], - 'getDrivers: WebGUI ships with 3 default shipping drivers', + 'getDrivers: All default shipping drivers present', ); ####################################################################### From 9206a0b80fbb5b7396a29a2644e04f3c9e6a91d0 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 6 Nov 2009 21:21:20 -0800 Subject: [PATCH 6/9] Add insurance option. --- .../Shop/ShipDriver/USPSInternational.pm | 15 +++++ t/Shop/ShipDriver/USPSInternational.t | 59 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm index e614bddb7..1a37d8b05 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm @@ -59,21 +59,26 @@ sub buildXML { next PACKAGE unless scalar @{ $package }; tie my %packageData, 'Tie::IxHash'; my $weight = 0; + my $value = 0; foreach my $item (@{ $package }) { my $sku = $item->getSku; my $itemWeight = $sku->getWeight(); + my $itemValue = $sku->getPrice(); ##Items that ship separately with a quantity > 1 are rate estimated as 1 item and then the ##shipping cost is multiplied by the quantity. if (! $sku->shipsSeparately ) { $itemWeight *= $item->get('quantity'); + $itemValue *= $item->get('quantity'); } $weight += $itemWeight; + $value += $itemValue; } my $pounds = int($weight); my $ounces = sprintf '%3.1f', (16 * ($weight - $pounds)); if ($pounds == 0 && $ounces eq '0.0' ) { $ounces = 0.1; } + $value = sprintf '%.2f', $value; my $destination = $package->[0]->getShippingAddress; my $country = $destination->get('country'); $packageData{ID} = $packageIndex; @@ -81,6 +86,9 @@ sub buildXML { $packageData{Ounces} = [ $ounces ]; $packageData{Machinable} = [ 'true' ]; $packageData{MailType} = [ 'Package' ]; + if ($self->get('addInsurance')) { + $packageData{ValueOfContents} = [ $value ]; + } $packageData{Country} = [ $country ]; push @{ $xmlTop->{Package} }, \%packageData; } @@ -138,6 +146,7 @@ sub calculate { WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Problem connecting to USPS Web Tools: '. $response->status_line); } my $returnedXML = $response->content; + #warn $returnedXML; my $xmlData = XMLin($returnedXML, KeepRoot => 1, ForceArray => [qw/Package/]); if (exists $xmlData->{Error}) { WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Problem with USPS Web Tools XML: '. $xmlData->{Error}->{Description}); @@ -194,6 +203,12 @@ sub _calculateFromXML { SERVICE: foreach my $service (@{ $package->{Service} }) { next SERVICE unless $service->{ID} eq $self->get('shipType'); $rate = $service->{Postage}; + if ($self->get('addInsurance')) { + if (exists $service->{InsComment}) { + WebGUI::Error::Shop::RemoteShippingRate->throw(error => "No insurance because of: ".$service->{InsComment}); + } + $rate += $service->{Insurance}; + } } if (!$rate) { WebGUI::Error::Shop::RemoteShippingRate->throw(error => 'Selected shipping service not available'); diff --git a/t/Shop/ShipDriver/USPSInternational.t b/t/Shop/ShipDriver/USPSInternational.t index 163c7aeee..8d24e5f17 100644 --- a/t/Shop/ShipDriver/USPSInternational.t +++ b/t/Shop/ShipDriver/USPSInternational.t @@ -25,7 +25,7 @@ use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; use WebGUI::Shop::ShipDriver::USPSInternational; -plan tests => 34; +plan tests => 37; #---------------------------------------------------------------------------- # Init @@ -340,7 +340,6 @@ SKIP: { }, '... returned data from USPS in correct format. If this test fails, the driver may need to be updated' ); - } my $cost = $driver->_calculateFromXML( @@ -579,6 +578,62 @@ SKIP: { } +####################################################################### +# +# Insurance +# +####################################################################### + +SKIP: { + + skip 'No userId for testing', 3 unless $hasRealUserId; + + + $cart->empty; + $properties = $driver->get(); + $properties->{shipType} = '9'; + $driver->update($properties); + $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); + + my $noInsuranceCost = $driver->calculate($cart); + + $properties->{addInsurance} = 1; + $driver->update($properties); + + @shippableUnits = $driver->_getShippableUnits($cart); + my $xml = $driver->buildXML($cart, @shippableUnits); + my $xmlData = XMLin($xml, + KeepRoot => 1, + ForceArray => ['Package'], + ); + + cmp_deeply( + $xmlData, + { + IntlRateRequest => { + USERID => $userId, + Package => [ + { + ID => 0, + Pounds => '12', Ounces => '0.0', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', ValueOfContents => '19.99', + }, + ], + } + }, + 'buildXML: 1 item in cart' + ); + like($xml, qr/IntlRateRequest USERID.+?Package ID=.+?Pounds.+?Ounces.+?Machinable.+?MailType.+?ValueOfContents.+?Country.+?/, '... and tag order'); + + my $insuredCost = $driver->calculate($cart); + cmp_ok $noInsuranceCost, '<', $insuredCost, 'insured cost is higher than uninsured cost'; + + $properties->{addInsurance} = 0; + $driver->update($properties); + +} + ####################################################################### # # Check for throwing an exception From bfc05ddc7a0d401eb1652ed78ed977684bb1f755 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Fri, 6 Nov 2009 21:23:22 -0800 Subject: [PATCH 7/9] Fix a bug in error reporting based on returned XML data. --- lib/WebGUI/Shop/ShipDriver/USPS.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebGUI/Shop/ShipDriver/USPS.pm b/lib/WebGUI/Shop/ShipDriver/USPS.pm index 8ddc2bc01..8c66d8e35 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPS.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPS.pm @@ -203,7 +203,7 @@ sub _calculateFromXML { WebGUI::Error::Shop::RemoteShippingRate->throw(error => "Illegal package index returned by USPS: $id"); } if (exists $package->{Error}) { - WebGUI::Error::Shop::RemoteShippingRate->throw(error => $package->{Description}); + WebGUI::Error::Shop::RemoteShippingRate->throw(error => $package->{Error}->{Description}); } my $unit = $shippableUnits[$id]; if ($unit->[0]->getSku->shipsSeparately) { From e647c013b9981bfbc84a238b014bc386a2f78d73 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Sat, 7 Nov 2009 12:56:43 -0800 Subject: [PATCH 8/9] Add a few more tests to hit coverage. Check for non-numeric package IDs. --- .../Shop/ShipDriver/USPSInternational.pm | 2 +- t/Shop/ShipDriver/USPSInternational.t | 96 ++++++++++++++++++- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm index 1a37d8b05..4d52983c3 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPSInternational.pm @@ -192,7 +192,7 @@ sub _calculateFromXML { foreach my $package (@{ $xmlData->{IntlRateResponse}->{Package} }) { my $id = $package->{ID}; ##Error check for invalid index - if ($id < 0 || $id > $#shippableUnits) { + if ($id < 0 || $id > $#shippableUnits || $id !~ /^\d+$/) { WebGUI::Error::Shop::RemoteShippingRate->throw(error => "Illegal package index returned by USPS: $id"); } if (exists $package->{Error}) { diff --git a/t/Shop/ShipDriver/USPSInternational.t b/t/Shop/ShipDriver/USPSInternational.t index 8d24e5f17..2b9d2b2a8 100644 --- a/t/Shop/ShipDriver/USPSInternational.t +++ b/t/Shop/ShipDriver/USPSInternational.t @@ -25,7 +25,7 @@ use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; use WebGUI::Shop::ShipDriver::USPSInternational; -plan tests => 37; +plan tests => 40; #---------------------------------------------------------------------------- # Init @@ -99,6 +99,14 @@ my $gospels = $bible->setCollateral('variantsJSON', 'variantId', 'new', } ); +my $singlePage = $bible->setCollateral('variantsJSON', 'variantId', 'new', + { + shortdesc => 'Single page from bible', + price => 0.01, varSku => 'page', + weight => 0.0001, quantity => 999999, + } +); + $versionTag->commit; addToCleanup($versionTag); @@ -545,19 +553,55 @@ SKIP: { ####################################################################### # -# Check too heavy for my shipping type +# Check for minimum weight allowed # ####################################################################### $cart->empty; $properties = $driver->get(); -$properties->{shipType} = '9'; +$properties->{shipType} = '9'; +$properties->{addInsurance} = 0; $driver->update($properties); +my $page1 = $bible->addToCart($bible->getCollateral('variantsJSON', 'variantId', $singlePage)); +@shippableUnits = $driver->_getShippableUnits($cart); +$xml = $driver->buildXML($cart, @shippableUnits); +$xmlData = XMLin($xml, + KeepRoot => 1, + ForceArray => ['Package'], +); +cmp_deeply( + $xmlData, + { + IntlRateRequest => { + USERID => $userId, + Package => [ + { + ID => 0, + Pounds => '0', Ounces => '0.1', + Machinable => 'true', Country => 'Netherlands', + MailType => 'Package', + }, + ], + } + }, + 'buildXML: minimum weight' +); + +####################################################################### +# +# Check too heavy for my shipping type +# +####################################################################### SKIP: { skip 'No userId for testing', 2 unless $hasRealUserId; + $cart->empty; + $properties = $driver->get(); + $properties->{shipType} = '9'; + $driver->update($properties); + my $heavyHammer = $rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); $heavyHammer->setQuantity(2); $cost = eval { $driver->calculate($cart); }; @@ -634,13 +678,57 @@ SKIP: { } +####################################################################### +# +# _calculateFromXML +# +####################################################################### + +$cart->empty; +$properties = $driver->get(); +$properties->{shipType} = '9'; +$properties->{addInsurance} = 1; +$driver->update($properties); +$rockHammer->addToCart($rockHammer->getCollateral('variantsJSON', 'variantId', $bigHammer)); +@shippableUnits = $driver->_getShippableUnits($cart); + +$cost = eval { $driver->_calculateFromXML( + { + IntlRateResponse => { + Package => [ + { + ID => 11, + Service => [ + { + ID => '9', + Postage => '5.25', + MaxWeight => '70' + }, + ], + }, + ], + }, + }, + @shippableUnits +); }; + +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::Shop::RemoteShippingRate', '_calculateFromXML throws an exception for illegal package ids'); +cmp_deeply( + $e, + methods( + error => 'Illegal package index returned by USPS: 11', + ), + '... checking error message', +); + ####################################################################### # # Check for throwing an exception # ####################################################################### -my $userId = $driver->get('userId'); +$userId = $driver->get('userId'); $properties = $driver->get(); $properties->{userId} = '_NO_NO_NO_NO'; $driver->update($properties); From 0a3836bde391e05e1e265da4652630569f481082 Mon Sep 17 00:00:00 2001 From: Colin Kuskie Date: Sat, 7 Nov 2009 12:58:49 -0800 Subject: [PATCH 9/9] Add non-numeric package check to USPS driver. --- lib/WebGUI/Shop/ShipDriver/USPS.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/WebGUI/Shop/ShipDriver/USPS.pm b/lib/WebGUI/Shop/ShipDriver/USPS.pm index 8c66d8e35..d4c32b91f 100644 --- a/lib/WebGUI/Shop/ShipDriver/USPS.pm +++ b/lib/WebGUI/Shop/ShipDriver/USPS.pm @@ -199,7 +199,7 @@ sub _calculateFromXML { my $id = $package->{ID}; my $rate = $package->{Postage}->{Rate}; ##Error check for invalid index - if ($id < 0 || $id > $#shippableUnits) { + if ($id < 0 || $id > $#shippableUnits || $id !~ /^\d+$/) { WebGUI::Error::Shop::RemoteShippingRate->throw(error => "Illegal package index returned by USPS: $id"); } if (exists $package->{Error}) {