Migrating code to PSR2. Adding optional support for live API calls in unit tests. Adding rounding error bug fix. Adding more tests to get to 85% coverage. Adding phpstorm project. Bumping master for v1.0.x target.

pull/11/head
Jamie Isaacs 10 years ago
parent 01ddc321ce
commit ccf447f755

11
.gitignore vendored

@ -1,4 +1,9 @@
vendor/ vendor
.idea/
composer.lock composer.lock
coverage_report
.idea/*.iws
.idea/*.iml
.idea/workspace.xml
.idea/tasks.xml
.DS_Store
live_phpunit.sh

@ -0,0 +1 @@
shipping

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="PhpCSValidationInspection" enabled="true" level="ERROR" enabled_by_default="true">
<option name="CODING_STANDARD" value="PSR2" />
<option name="WARNING_HIGHLIGHT_LEVEL_NAME" value="ERROR" />
</inspection_tool>
</profile>
</component>

@ -0,0 +1,7 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" />
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/shipping.iml" filepath="$PROJECT_DIR$/.idea/shipping.iml" />
</modules>
</component>
</project>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings load_method="CUSTOM_LOADER" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" phpunit_phar_path="" />
</phpunit_settings>
</component>
</project>

@ -0,0 +1,5 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,10 @@
language: php
php:
- 5.4
before_script:
- composer install --prefer-dist
script:
- vendor/bin/phpcs --standard=PSR2 src/ tests/
- vendor/bin/phpunit

@ -1,18 +1,31 @@
## PHP Shipping API ## PHP Shipping API
[![Total Downloads](https://poser.pugx.org/pdt256/shipping/downloads.svg)](https://packagist.org/packages/pdt256/shipping) [![Latest Stable Version](https://poser.pugx.org/pdt256/shipping/v/stable.svg)](https://packagist.org/packages/pdt256/shipping) [![Latest Unstable Version](https://poser.pugx.org/pdt256/shipping/v/unstable.svg)](https://packagist.org/packages/pdt256/shipping) [![License](https://poser.pugx.org/pdt256/shipping/license.svg)](https://packagist.org/packages/pdt256/shipping) [![Test Coverage](http://img.shields.io/badge/coverage-85%25-green.svg)]
[![Build Status](https://travis-ci.org/pdt256/shipping.svg?branch=master)](https://travis-ci.org/pdt256/shipping)
[![Downloads](https://img.shields.io/packagist/dt/pdt256/shipping.svg)](https://packagist.org/packages/pdt256/shipping)
[![License](https://img.shields.io/packagist/l/pdt256/shipping.svg)](https://github.com/pdt256/shipping/blob/master/LICENSE.txt)
A shipping rate wrapper for USPS, UPS, and Fedex. A shipping rate wrapper for USPS, UPS, and Fedex.
## Introduction
This is a PHP shipping package that wraps API calls to UPS, FedEx, and USPS for shipping rates.
Multiple packages can be added to get additional rates.
All code (including tests) conform to the PSR-2 coding standards.
The namespace and autoloader are using the PSR-4 standard.
All pull requests are processed by Travis CI to conform to PSR-2 and to verify all unit tests pass.
## Installation ## Installation
Add the following lines to your ``composer.json`` file. Add the following lines to your ``composer.json`` file.
```JSON ```JSON
{ {
"require": { "require": {
"pdt256/shipping": "dev-master" "pdt256/shipping": "1.0.*@dev"
} }
} }
``` ```
@ -23,15 +36,20 @@ Create a shipment object:
```php ```php
$shipment = new Shipment; $shipment = new Shipment;
$shipment $shipment
->setFromIsResidential(false)
->setFromStateProvinceCode('IN') ->setFromStateProvinceCode('IN')
->setFromPostalCode('46205') ->setFromPostalCode('46205')
->setFromCountryCode('US') ->setFromCountryCode('US')
->setToIsResidential(true);
->setToPostalCode('20101') ->setToPostalCode('20101')
->setToCountryCode('US') ->setToCountryCode('US')
->setToResidential(true);
$package = new Package; $package = new Package;
$package->setLength(12)->setWidth(4)->setHeight(3)->setWeight(3); $package
->setLength(12)
->setWidth(4)
->setHeight(3)
->setWeight(3);
$shipment->addPackage($package); $shipment->addPackage($package);
``` ```
@ -44,7 +62,7 @@ Notice: The below line uses a stub class to fake a response from the UPS API.
You can immediately use this method in your code until you get an account with UPS. You can immediately use this method in your code until you get an account with UPS.
```php ```php
'request_adapter' => new RateRequest\StubUPS(), 'requestAdapter' => new RateRequest\StubUPS(),
``` ```
```php ```php
@ -52,23 +70,23 @@ use pdt256\Shipping\UPS;
use pdt256\Shipping\RateRequest; use pdt256\Shipping\RateRequest;
$ups = new UPS\Rate([ $ups = new UPS\Rate([
'prod' => FALSE, 'prod' => FALSE,
'access_key' => 'XXXX', 'accessKey' => 'XXXX',
'user_id' => 'XXXX', 'userId' => 'XXXX',
'password' => 'XXXX', 'password' => 'XXXX',
'shipper_number' => 'XXXX', 'shipperNumber' => 'XXXX',
'shipment' => $shipment, 'shipment' => $shipment,
'approved_codes' => [ 'approvedCodes' => [
'03', // 1-5 business days '03', // 1-5 business days
'02', // 2 business days '02', // 2 business days
'01', // next business day 10:30am '01', // next business day 10:30am
'13', // next business day by 3pm '13', // next business day by 3pm
'14', // next business day by 8am '14', // next business day by 8am
], ],
'request_adapter' => new RateRequest\StubUPS(), 'requestAdapter' => new RateRequest\StubUPS(),
]); ]);
$ups_rates = $ups->get_rates(); $upsRates = $ups->getRates();
``` ```
Output array sorted by cost: (in cents) Output array sorted by cost: (in cents)
@ -83,9 +101,9 @@ array(4) {
string(10) "UPS Ground" string(10) "UPS Ground"
protected $cost => protected $cost =>
int(1900) int(1900)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(3) "ups" string(3) "ups"
@ -98,9 +116,9 @@ array(4) {
string(15) "UPS 2nd Day Air" string(15) "UPS 2nd Day Air"
protected $cost => protected $cost =>
int(4900) int(4900)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(3) "ups" string(3) "ups"
@ -113,9 +131,9 @@ array(4) {
string(22) "UPS Next Day Air Saver" string(22) "UPS Next Day Air Saver"
protected $cost => protected $cost =>
int(8900) int(8900)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(3) "ups" string(3) "ups"
@ -128,9 +146,9 @@ array(4) {
string(16) "UPS Next Day Air" string(16) "UPS Next Day Air"
protected $cost => protected $cost =>
int(9300) int(9300)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(3) "ups" string(3) "ups"
@ -149,14 +167,14 @@ $usps = new USPS\Rate([
'username' => 'XXXX', 'username' => 'XXXX',
'password' => 'XXXX', 'password' => 'XXXX',
'shipment' => $shipment, 'shipment' => $shipment,
'approved_codes' => [ 'approvedCodes' => [
'1', // 1-3 business days '1', // 1-3 business days
'4', // 2-8 business days '4', // 2-8 business days
], ],
'request_adapter' => new RateRequest\StubUSPS(), 'requestAdapter' => new RateRequest\StubUSPS(),
]); ]);
$usps_rates = $usps->get_rates(); $uspsRates = $usps->getRates();
``` ```
Output array sorted by cost: (in cents) Output array sorted by cost: (in cents)
@ -171,9 +189,9 @@ array(2) {
string(11) "Parcel Post" string(11) "Parcel Post"
protected $cost => protected $cost =>
int(1001) int(1001)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(4) "usps" string(4) "usps"
@ -186,9 +204,9 @@ array(2) {
string(13) "Priority Mail" string(13) "Priority Mail"
protected $cost => protected $cost =>
int(1220) int(1220)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(4) "usps" string(4) "usps"
@ -206,21 +224,21 @@ $fedex = new Fedex\Rate([
'prod' => FALSE, 'prod' => FALSE,
'key' => 'XXXX', 'key' => 'XXXX',
'password' => 'XXXX', 'password' => 'XXXX',
'account_number' => 'XXXX', 'accountNumber' => 'XXXX',
'meter_number' => 'XXXX', 'meterNumber' => 'XXXX',
'drop_off_type' => 'BUSINESS_SERVICE_CENTER', 'dropOffType' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $shipment, 'shipment' => $shipment,
'approved_codes' => [ 'approvedCodes' => [
'FEDEX_EXPRESS_SAVER', // 1-3 business days 'FEDEX_EXPRESS_SAVER', // 1-3 business days
'FEDEX_GROUND', // 1-5 business days 'FEDEX_GROUND', // 1-5 business days
'GROUND_HOME_DELIVERY', // 1-5 business days 'GROUND_HOME_DELIVERY', // 1-5 business days
'FEDEX_2_DAY', // 2 business days 'FEDEX_2_DAY', // 2 business days
'STANDARD_OVERNIGHT', // overnight 'STANDARD_OVERNIGHT', // overnight
], ],
'request_adapter' => new RateRequest\StubFedex(), 'requestAdapter' => new RateRequest\StubFedex(),
]); ]);
$fedex_rates = $fedex->get_rates(); $fedexRates = $fedex->getRates();
``` ```
Output array sorted by cost: (in cents) Output array sorted by cost: (in cents)
@ -235,9 +253,9 @@ array(4) {
string(20) "Ground Home Delivery" string(20) "Ground Home Delivery"
protected $cost => protected $cost =>
int(1600) int(1600)
protected $transit_time => protected $transitTime =>
string(10) "THREE_DAYS" string(10) "THREE_DAYS"
protected $delivery_ts => protected $deliveryTime =>
NULL NULL
protected $carrier => protected $carrier =>
string(5) "fedex" string(5) "fedex"
@ -250,9 +268,9 @@ array(4) {
string(19) "Fedex Express Saver" string(19) "Fedex Express Saver"
protected $cost => protected $cost =>
int(2900) int(2900)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
class Carbon\Carbon#23 (3) { class Carbon\Carbon#23 (3) {
public $date => public $date =>
string(26) "2014-09-30 20:00:00.000000" string(26) "2014-09-30 20:00:00.000000"
@ -272,9 +290,9 @@ array(4) {
string(11) "Fedex 2 Day" string(11) "Fedex 2 Day"
protected $cost => protected $cost =>
int(4000) int(4000)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
class Carbon\Carbon#26 (3) { class Carbon\Carbon#26 (3) {
public $date => public $date =>
string(26) "2014-09-29 20:00:00.000000" string(26) "2014-09-29 20:00:00.000000"
@ -294,9 +312,9 @@ array(4) {
string(18) "Standard Overnight" string(18) "Standard Overnight"
protected $cost => protected $cost =>
int(7800) int(7800)
protected $transit_time => protected $transitTime =>
NULL NULL
protected $delivery_ts => protected $deliveryTime =>
class Carbon\Carbon#58 (3) { class Carbon\Carbon#58 (3) {
public $date => public $date =>
string(26) "2014-09-26 20:00:00.000000" string(26) "2014-09-26 20:00:00.000000"
@ -311,6 +329,31 @@ array(4) {
} }
``` ```
## Unit Tests:
```bash
vendor/bin/phpunit
```
### With Code Coverage:
```bash
vendor/bin/phpunit --coverage-text --coverage-html coverage_report
```
### With Live API Tests:
```bash
./live_phpunit.sh
```
## Run Coding Standards Test:
```bash
vendor/bin/phpcs --standard=PSR2 src/ tests/
```
### License ### License
The MIT License (MIT) The MIT License (MIT)

@ -9,8 +9,17 @@
"email": "pdt256@gmail.com" "email": "pdt256@gmail.com"
} }
], ],
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"require-dev": { "require-dev": {
"phpunit/phpunit": "4.0.*" "phpunit/phpunit": "4.0.*",
"squizlabs/php_codesniffer": "1.5.5"
},
"require": {
"php": ">=5.4"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

@ -0,0 +1,16 @@
#!/bin/bash
export USPS_USERNAME='XXXXXXXXXXXX'
export USPS_PASSWORD='XXXXXXXXXXXX'
export UPS_ACCESS_KEY='XXXXXXXXXXXXXXXX'
export UPS_USER_ID='XXXXXX'
export UPS_PASSWORD='XXXX'
export UPS_SHIPPER_NUMBER='XXXXXX'
export FEDEX_KEY='XXXXXXXXXXXXXXXX'
export FEDEX_PASSWORD='XXXXXXXXXXXXXXXXXXXXXXXXX'
export FEDEX_ACCOUNT_NUMBER='XXXXXXXXX'
export FEDEX_METER_NUMBER='XXXXXXXXX'
vendor/bin/phpunit

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" <phpunit backupGlobals="false"
backupStaticAttributes="false" backupStaticAttributes="false"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
convertErrorsToExceptions="true" convertErrorsToExceptions="true"
convertNoticesToExceptions="true" convertNoticesToExceptions="true"
convertWarningsToExceptions="true" convertWarningsToExceptions="true"
processIsolation="false" processIsolation="false"
stopOnFailure="false" stopOnFailure="false"
syntaxCheck="false"> syntaxCheck="false">
<testsuites> <testsuites>
<testsuite name="Ship Test Suite"> <testsuite name="Ship Test Suite">
<directory suffix=".php">./tests</directory> <directory suffix=".php">./tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
</phpunit> </phpunit>

@ -3,8 +3,8 @@ namespace pdt256\Shipping;
class Arr class Arr
{ {
public static function get($array, $key, $default = NULL) public static function get($array, $key, $default = null)
{ {
return isset($array[$key]) ? $array[$key] : $default; return isset($array[$key]) ? $array[$key] : $default;
} }
} }

@ -12,230 +12,218 @@ use Exception;
class Rate extends RateAdapter class Rate extends RateAdapter
{ {
private $url_dev = 'https://gatewaybeta.fedex.com/web-services/'; private $urlDev = 'https://gatewaybeta.fedex.com/web-services/';
private $url_prod = 'https://gateway.fedex.com/web-services/'; private $urlProd = 'https://gateway.fedex.com/web-services/';
private $key = 'XXX'; private $key = 'XXX';
private $password = 'XXX'; private $password = 'XXX';
private $account_number = 'XXX'; private $accountNumber = 'XXX';
private $meter_number = 'XXX'; private $meterNumber = 'XXX';
private $drop_off_type = 'BUSINESS_SERVICE_CENTER'; private $dropOffType = 'BUSINESS_SERVICE_CENTER';
public $approved_codes = [ public $approvedCodes = [
'PRIORITY_OVERNIGHT', 'PRIORITY_OVERNIGHT',
'FEDEX_2_DAY', 'FEDEX_2_DAY',
'FEDEX_EXPRESS_SAVER', 'FEDEX_EXPRESS_SAVER',
'FEDEX_GROUND', 'FEDEX_GROUND',
'GROUND_HOME_DELIVERY', 'GROUND_HOME_DELIVERY',
]; ];
private $shipping_codes = [ private $shippingCodes = [
'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => 'Europe First International Priority', 'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => 'Europe First International Priority',
'FEDEX_1_DAY_FREIGHT' => 'Fedex 1 Day Freight', 'FEDEX_1_DAY_FREIGHT' => 'Fedex 1 Day Freight',
'FEDEX_2_DAY' => 'Fedex 2 Day', 'FEDEX_2_DAY' => 'Fedex 2 Day',
'FEDEX_2_DAY_AM' => 'Fedex 2 Day AM', 'FEDEX_2_DAY_AM' => 'Fedex 2 Day AM',
'FEDEX_2_DAY_FREIGHT' => 'Fedex 2 Day Freight', 'FEDEX_2_DAY_FREIGHT' => 'Fedex 2 Day Freight',
'FEDEX_3_DAY_FREIGHT' => 'Fedex 3 Day Freight', 'FEDEX_3_DAY_FREIGHT' => 'Fedex 3 Day Freight',
'FEDEX_EXPRESS_SAVER' => 'Fedex Express Saver', 'FEDEX_EXPRESS_SAVER' => 'Fedex Express Saver',
'FEDEX_FIRST_FREIGHT' => 'Fedex First Freight', 'FEDEX_FIRST_FREIGHT' => 'Fedex First Freight',
'FEDEX_FREIGHT_ECONOMY' => 'Fedex Freight Economy', 'FEDEX_FREIGHT_ECONOMY' => 'Fedex Freight Economy',
'FEDEX_FREIGHT_PRIORITY' => 'Fedex Freight Priority', 'FEDEX_FREIGHT_PRIORITY' => 'Fedex Freight Priority',
'FEDEX_GROUND' => 'Fedex Ground', 'FEDEX_GROUND' => 'Fedex Ground',
'FIRST_OVERNIGHT' => 'First Overnight', 'FIRST_OVERNIGHT' => 'First Overnight',
'GROUND_HOME_DELIVERY' => 'Ground Home Delivery', 'GROUND_HOME_DELIVERY' => 'Ground Home Delivery',
'INTERNATIONAL_ECONOMY' => 'International Economy', 'INTERNATIONAL_ECONOMY' => 'International Economy',
'INTERNATIONAL_ECONOMY_FREIGHT' => 'International Economy Freight', 'INTERNATIONAL_ECONOMY_FREIGHT' => 'International Economy Freight',
'INTERNATIONAL_FIRST' => 'International First', 'INTERNATIONAL_FIRST' => 'International First',
'INTERNATIONAL_PRIORITY' => 'International Priority', 'INTERNATIONAL_PRIORITY' => 'International Priority',
'INTERNATIONAL_PRIORITY_FREIGHT' => 'International Priority Freight', 'INTERNATIONAL_PRIORITY_FREIGHT' => 'International Priority Freight',
'PRIORITY_OVERNIGHT' => 'Priority Overnight', 'PRIORITY_OVERNIGHT' => 'Priority Overnight',
'SMART_POST' => 'Smart Post', 'SMART_POST' => 'Smart Post',
'STANDARD_OVERNIGHT' => 'Standard Overnight', 'STANDARD_OVERNIGHT' => 'Standard Overnight',
]; ];
public function __construct($options = []) public function __construct($options = [])
{ {
parent::__construct($options); parent::__construct($options);
if (isset($options['key'])) { $this->key = Arr::get($options, 'key');
$this->key = $options['key']; $this->password = Arr::get($options, 'password');
} $this->accountNumber = Arr::get($options, 'accountNumber');
$this->meterNumber = Arr::get($options, 'meterNumber');
if (isset($options['password'])) { $this->approvedCodes = Arr::get($options, 'approvedCodes');
$this->password = $options['password']; $this->dropOffType = Arr::get($options, 'dropOffType');
}
$this->setRequestAdapter(Arr::get($options, 'requestAdapter', new RateRequest\Post()));
if (isset($options['account_number'])) { }
$this->account_number = $options['account_number'];
} protected function prepare()
{
if (isset($options['meter_number'])) { $date = time();
$this->meter_number = $options['meter_number']; $day_name = date('l', $date);
}
if ($day_name == 'Saturday') {
if (isset($options['approved_codes'])) { $date += 172800;
$this->approved_codes = $options['approved_codes']; } elseif ($day_name == 'Sunday') {
} $date += 86400;
}
if (isset($options['drop_off_type'])) {
$this->drop_off_type = $options['drop_off_type']; // http://www.fedex.com/templates/components/apps/wpor/secure/downloads/pdf/Aug13/PropDevGuide.pdf
} // http://www.fedex.com/us/developer/product/WebServices/MyWebHelp_August2010/Content/
// Proprietary_Developer_Guide/Rate_Services_conditionalized.htm
if (isset($options['request_adapter'])) {
$this->set_request_adapter($options['request_adapter']); $packages = '';
} else { $sequence_number = 0;
$this->set_request_adapter(new RateRequest\Post()); foreach ($this->shipment->getPackages() as $package) {
} $sequence_number++;
}
$packages .=
protected function prepare() '<RequestedPackageLineItems>' .
{ '<SequenceNumber>' . $sequence_number . '</SequenceNumber>' .
$date = time(); '<GroupPackageCount>1</GroupPackageCount>' .
$day_name = date('l', $date); '<Weight>' .
'<Units>LB</Units>' .
if ($day_name == 'Saturday') { '<Value>' . $package->getWeight() . '</Value>' .
$date += 172800; '</Weight>' .
} elseif ($day_name == 'Sunday') { '<Dimensions>' .
$date += 86400; '<Length>' . $package->getLength() . '</Length>' .
} '<Width>' . $package->getWidth() . '</Width>' .
'<Height>' . $package->getHeight() . '</Height>' .
// http://www.fedex.com/templates/components/apps/wpor/secure/downloads/pdf/Aug13/PropDevGuide.pdf '<Units>IN</Units>' .
// http://www.fedex.com/us/developer/product/WebServices/MyWebHelp_August2010/Content/Proprietary_Developer_Guide/Rate_Services_conditionalized.htm '</Dimensions>' .
'</RequestedPackageLineItems>';
}
$packages = '';
$sequence_number = 0; $this->data = '<?xml version="1.0"?>' .
foreach ($this->shipment->getPackages() as $p) { '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" ' .
$sequence_number++; 'xmlns="http://fedex.com/ws/rate/v13">' .
'<SOAP-ENV:Body>' .
$packages .= '<RequestedPackageLineItems> '<RateRequest>' .
<SequenceNumber>' . $sequence_number . '</SequenceNumber> '<WebAuthenticationDetail>' .
<GroupPackageCount>1</GroupPackageCount> '<UserCredential>' .
<Weight> '<Key>' . $this->key . '</Key>' .
<Units>LB</Units> '<Password>' . $this->password . '</Password>' .
<Value>' . $p->getWeight() . '</Value> '</UserCredential>' .
</Weight> '</WebAuthenticationDetail>' .
<Dimensions> '<ClientDetail>' .
<Length>' . $p->getLength() . '</Length> '<AccountNumber>' . $this->accountNumber . '</AccountNumber>' .
<Width>' . $p->getWidth() . '</Width> '<MeterNumber>' . $this->meterNumber . '</MeterNumber>' .
<Height>' . $p->getHeight() . '</Height> '</ClientDetail>' .
<Units>IN</Units> '<Version>' .
</Dimensions> '<ServiceId>crs</ServiceId>' .
</RequestedPackageLineItems>'; '<Major>13</Major>' .
} '<Intermediate>0</Intermediate>' .
'<Minor>0</Minor>' .
$this->data = '</Version>' .
'<?xml version="1.0"?> '<ReturnTransitAndCommit>true</ReturnTransitAndCommit>' .
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://fedex.com/ws/rate/v13"> '<RequestedShipment>' .
<SOAP-ENV:Body> '<ShipTimestamp>' . date('c') . '</ShipTimestamp>' .
<RateRequest> '<DropoffType>' . $this->dropOffType . '</DropoffType>' .
<WebAuthenticationDetail> '<PackagingType>YOUR_PACKAGING</PackagingType>' .
<UserCredential> '<Shipper>' .
<Key>' . $this->key . '</Key> '<Address>' .
<Password>' . $this->password . '</Password> '<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode>' .
</UserCredential> '<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode>' .
</WebAuthenticationDetail> (
<ClientDetail> $this->shipment->getFromIsResidential() ?
<AccountNumber>' . $this->account_number . '</AccountNumber> '<Residential>1</Residential>' :
<MeterNumber>' . $this->meter_number . '</MeterNumber> ''
</ClientDetail> ) .
<Version> '</Address>' .
<ServiceId>crs</ServiceId> '</Shipper>' .
<Major>13</Major> '<Recipient>' .
<Intermediate>0</Intermediate> '<Address>' .
<Minor>0</Minor> '<PostalCode>' . $this->shipment->getToPostalCode() . '</PostalCode>' .
</Version> '<CountryCode>' . $this->shipment->getToCountryCode() . '</CountryCode>' .
<ReturnTransitAndCommit>true</ReturnTransitAndCommit> (
<RequestedShipment> $this->shipment->getToIsResidential() ?
<ShipTimestamp>' . date('c') . '</ShipTimestamp> '<Residential>1</Residential>' :
<DropoffType>' . $this->drop_off_type . '</DropoffType> ''
<PackagingType>YOUR_PACKAGING</PackagingType> ) .
<Shipper> '</Address>' .
<Address> '</Recipient>' .
<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode> '<RateRequestTypes>LIST</RateRequestTypes>' .
<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode> '<PackageCount>' . $this->shipment->packageCount() . '</PackageCount>' .
' . (($this->shipment->isFromResidential()) ? '<Residential>1</Residential>' : '') . ' $packages .
</Address> '</RequestedShipment>' .
</Shipper> '</RateRequest>' .
<Recipient> '</SOAP-ENV:Body>' .
<Address> '</SOAP-ENV:Envelope>';
<PostalCode>' . $this->shipment->getToPostalCode() . '</PostalCode>
<CountryCode>' . $this->shipment->getToCountryCode() . '</CountryCode> return $this;
' . (($this->shipment->isToResidential()) ? '<Residential>1</Residential>' : '') . ' }
</Address>
</Recipient> protected function execute()
<RateRequestTypes>LIST</RateRequestTypes> {
<PackageCount>' . $this->shipment->packageCount() . '</PackageCount> if ($this->isProduction) {
' . $packages . ' $url = $this->urlProd;
</RequestedShipment> } else {
</RateRequest> $url = $this->urlDev;
</SOAP-ENV:Body> }
</SOAP-ENV:Envelope>';
$this->response = $this->rateRequest->execute($url, $this->data);
return $this;
} return $this;
}
protected function execute()
{ protected function process()
if ($this->is_prod) { {
$url = $this->url_prod; try {
} else { $dom = new DOMDocument('1.0', 'UTF-8');
$url = $this->url_dev; $dom->loadXml($this->response);
} $rate_reply = $dom->getElementsByTagName('RateReplyDetails');
$this->response = $this->rate_request->execute($url, $this->data); if (empty($rate_reply->length)) {
throw new Exception('Unable to get FedEx Rates.');
return $this; }
} } catch (Exception $e) {
// StatsD::increment('error.shipping.get_fedex_rate');
protected function process() // Kohana::$log->add(Log::ERROR, $e)->write();
{ throw $e;
try { }
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadXml($this->response); foreach ($rate_reply as $rate) {
$rate_reply = $dom->getElementsByTagName('RateReplyDetails'); $code = $rate->getElementsByTagName('ServiceType')->item(0)->nodeValue;
if (empty($rate_reply->length)) { if (! empty($this->approvedCodes) && ! in_array($code, $this->approvedCodes)) {
throw new Exception('Unable to get FedEx Rates.'); continue;
} }
} catch (Exception $e) {
// StatsD::increment('error.shipping.get_fedex_rate'); $name = Arr::get($this->shippingCodes, $code);
// Kohana::$log->add(Log::ERROR, $e)->write();
throw $e; $delivery_ts = @$rate->getElementsByTagName('DeliveryTimestamp')->item(0)->nodeValue;
} $transit_time = @$rate->getElementsByTagName('TransitTime')->item(0)->nodeValue;
foreach ($rate_reply as $rate) { $cost = $rate
$code = $rate->getElementsByTagName('ServiceType')->item(0)->nodeValue; ->getElementsByTagName('RatedShipmentDetails')->item(0)
->getElementsByTagName('ShipmentRateDetail')->item(0)
if ( ! empty($this->approved_codes) AND ! in_array($code, $this->approved_codes)) { ->getElementsByTagName('TotalNetCharge')->item(0)
continue; ->getElementsByTagName('Amount')->item(0)->nodeValue;
}
$quote = new Quote;
$name = Arr::get($this->shipping_codes, $code); $quote
->setCarrier('fedex')
$delivery_ts = @$rate->getElementsByTagName('DeliveryTimestamp')->item(0)->nodeValue; ->setCode($code)
$transit_time = @$rate->getElementsByTagName('TransitTime')->item(0)->nodeValue; ->setName($name)
->setCost((int) ($cost * 100))
$cost = $rate ->setTransitTime($transit_time);
->getElementsByTagName('RatedShipmentDetails')->item(0) if ($delivery_ts) {
->getElementsByTagName('ShipmentRateDetail')->item(0) $quote->setDeliveryEstimate(new DateTime($delivery_ts));
->getElementsByTagName('TotalNetCharge')->item(0) }
->getElementsByTagName('Amount')->item(0)->nodeValue;
$this->rates[] = $quote;
$quote = new Quote; }
$quote
->setCarrier('fedex') return $this;
->setCode($code) }
->setName($name)
->setCost((int) $cost * 100)
->setTransitTime($transit_time);
if ($delivery_ts) {
$quote->setDeliveryEstimate(new DateTime($delivery_ts));
}
$this->rates[] = $quote;
}
return $this;
}
} }

@ -7,10 +7,18 @@ class Quote
protected $code; protected $code;
protected $name; protected $name;
protected $cost; protected $cost;
protected $transit_time; protected $transitTime;
protected $delivery_ts; protected $deliveryEstimate;
protected $carrier; protected $carrier;
public function __construct($carrier = null, $code = null, $name = null, $cost = null)
{
$this->setCarrier($carrier);
$this->setCode($code);
$this->setName($name);
$this->setCost($cost);
}
/** /**
* @return mixed * @return mixed
*/ */
@ -25,7 +33,7 @@ class Quote
*/ */
public function setCarrier($carrier) public function setCarrier($carrier)
{ {
$this->carrier = $carrier; $this->carrier = (string) $carrier;
return $this; return $this;
} }
@ -43,7 +51,7 @@ class Quote
*/ */
public function setCode($code) public function setCode($code)
{ {
$this->code = $code; $this->code = (string) $code;
return $this; return $this;
} }
@ -61,7 +69,7 @@ class Quote
*/ */
public function setName($name) public function setName($name)
{ {
$this->name = $name; $this->name = (string) $name;
return $this; return $this;
} }
@ -81,7 +89,7 @@ class Quote
*/ */
public function setCost($cost) public function setCost($cost)
{ {
$this->cost = $cost; $this->cost = (int) $cost;
return $this; return $this;
} }
@ -90,16 +98,16 @@ class Quote
*/ */
public function getTransitTime() public function getTransitTime()
{ {
return $this->transit_time; return $this->transitTime;
} }
/** /**
* @param mixed $transit_time * @param mixed $transitTime
* @return $this * @return $this
*/ */
public function setTransitTime($transit_time) public function setTransitTime($transitTime)
{ {
$this->transit_time = $transit_time; $this->transitTime = $transitTime;
return $this; return $this;
} }
@ -108,16 +116,16 @@ class Quote
*/ */
public function getDeliveryEstimate() public function getDeliveryEstimate()
{ {
return $this->delivery_ts; return $this->deliveryEstimate;
} }
/** /**
* @param DateTime $estimate * @param DateTime $deliveryEstimate
* @return $this * @return $this
*/ */
public function setDeliveryEstimate(DateTime $estimate) public function setDeliveryEstimate(DateTime $deliveryEstimate)
{ {
$this->delivery_ts = $estimate; $this->deliveryEstimate = $deliveryEstimate;
return $this; return $this;
} }
} }

@ -5,49 +5,58 @@ use Exception;
abstract class RateAdapter abstract class RateAdapter
{ {
protected $is_prod = FALSE; protected $isProduction;
/** @var Shipment */ /** @var Shipment */
protected $shipment; protected $shipment;
protected $data; protected $data;
protected $response; protected $response;
protected $rates = []; protected $rates;
protected $rate_request; /** @var @var RateRequest\Adapter */
protected $rateRequest;
abstract protected function prepare(); // Prepare XML
abstract protected function execute(); // Curl Request /**
abstract protected function process(); // Convert to shipping rates array * Prepare XML
*/
public function __construct($options = []) abstract protected function prepare();
{
if (isset($options['prod'])) { /**
$this->is_prod = (bool) $options['prod']; * Curl Request
} */
abstract protected function execute();
if (isset($options['shipment'])) {
$this->shipment = $options['shipment']; /**
} * Convert to shipping rates array
} */
abstract protected function process();
public function set_request_adapter(RateRequest\Adapter $rate_request)
{ public function __construct($options = [])
$this->rate_request = $rate_request; {
} $this->rates = [];
public function get_rates() $this->isProduction = (bool) Arr::get($options, 'prod', false);
{ $this->shipment = Arr::get($options, 'shipment');
$this }
->prepare()
->execute() public function setRequestAdapter(RateRequest\Adapter $rateRequest)
->process() {
->sort_by_cost(); $this->rateRequest = $rateRequest;
}
return array_values($this->rates);
} public function getRates()
{
protected function sort_by_cost() $this
{ ->prepare()
uasort($this->rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());')); ->execute()
} ->process()
->sortByCost();
return array_values($this->rates);
}
protected function sortByCost()
{
uasort($this->rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());'));
}
} }

@ -3,8 +3,8 @@ namespace pdt256\Shipping\RateRequest;
abstract class Adapter abstract class Adapter
{ {
protected $curl_connect_timeout_ms = 1000; // milliseconds protected $curlConnectTimeoutInMilliseconds = 1000;
protected $curl_dl_timeout = 11; // seconds protected $curlDownloadTimeoutInSeconds = 11;
abstract public function execute($url, $data = NULL); abstract public function execute($url, $data = null);
} }

File diff suppressed because it is too large Load Diff

@ -3,22 +3,23 @@ namespace pdt256\Shipping\RateRequest;
class Get extends Adapter class Get extends Adapter
{ {
public function execute($url, $data = NULL) public function execute($url, $data = null)
{ {
$ch = curl_init(); $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPGET, TRUE); curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curl_connect_timeout_ms); curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_dl_timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curlConnectTimeoutInMilliseconds);
$response = curl_exec($ch); curl_setopt($ch, CURLOPT_TIMEOUT, $this->curlDownloadTimeoutInSeconds);
curl_close($ch); $response = curl_exec($ch);
if ($response === false) { curl_close($ch);
throw new RequestException(curl_error($ch)); if ($response === false) {
} throw new RequestException(curl_error($ch));
return $response; }
} return $response;
}
} }

@ -3,24 +3,25 @@ namespace pdt256\Shipping\RateRequest;
class Post extends Adapter class Post extends Adapter
{ {
public function execute($url, $data = NULL) public function execute($url, $data = null)
{ {
$ch = curl_init(); $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, FALSE); curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curl_connect_timeout_ms); curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_dl_timeout); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curlConnectTimeoutInMilliseconds);
$response = curl_exec($ch); curl_setopt($ch, CURLOPT_TIMEOUT, $this->curlDownloadTimeoutInSeconds);
if ($response === false) { $response = curl_exec($ch);
throw new RequestException(curl_error($ch)); if ($response === false) {
} throw new RequestException(curl_error($ch));
curl_close($ch); }
curl_close($ch);
return $response; return $response;
} }
} }

@ -1,6 +1,6 @@
<?php <?php
namespace pdt256\Shipping\RateRequest; namespace pdt256\Shipping\RateRequest;
class RequestException extends \Exception
class RequestException extends \Exception {} {
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -3,22 +3,19 @@ namespace pdt256\Shipping\RateRequest;
class StubUSPS extends Adapter class StubUSPS extends Adapter
{ {
private $artificial_delay = 0; private $artificialDelay = 0;
public function __construct($artificial_delay = 0) public function __construct($artificial_delay = 0)
{ {
$this->artificial_delay = $artificial_delay; $this->artificialDelay = $artificial_delay;
} }
public function execute($url, $data = NULL) public function execute($url, $data = null)
{ {
if ($this->artificial_delay > 0) { if ($this->artificialDelay > 0) {
sleep($this->artificial_delay); sleep($this->artificialDelay);
} }
$response = '<?xml version="1.0" encoding="UTF-8"?> return file_get_contents(__DIR__ . '/USPSResponse.xml');
<RateV4Response><Package ID="1"><ZipOrigination>90401</ZipOrigination><ZipDestination>76667</ZipDestination><Pounds>3</Pounds><Ounces>0</Ounces><Size>LARGE</Size><Machinable>FALSE</Machinable><Zone>6</Zone><Postage CLASSID="3"><MailService>Priority Mail Express 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt;</MailService><Rate>42.25</Rate></Postage><Postage CLASSID="2"><MailService>Priority Mail Express 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt; Hold For Pickup</MailService><Rate>42.25</Rate></Postage><Postage CLASSID="1"><MailService>Priority Mail 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt;</MailService><Rate>12.20</Rate></Postage><Postage CLASSID="4"><MailService>Standard Post&amp;lt;sup&amp;gt;&amp;#174;&amp;lt;/sup&amp;gt;</MailService><Rate>10.01</Rate></Postage><Postage CLASSID="6"><MailService>Media Mail Parcel</MailService><Rate>3.65</Rate></Postage><Postage CLASSID="7"><MailService>Library Mail Parcel</MailService><Rate>3.48</Rate></Postage></Package></RateV4Response>'; }
return $response;
}
} }

@ -0,0 +1,247 @@
<?xml version="1.0"?>
<RatingServiceSelectionResponse>
<Response>
<ResponseStatusCode>1</ResponseStatusCode>
<ResponseStatusDescription>Success</ResponseStatusDescription>
</Response>
<RatedShipment>
<Service>
<Code>03</Code>
</Service>
<RatedShipmentWarning>Your invoice may vary from the displayed reference rates</RatedShipmentWarning>
<RatedShipmentWarning>Missing / Invalid Shipper Number. Returned rates are Retail Rates.</RatedShipmentWarning>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>3.0</Weight>
</BillingWeight>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>19.10</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>19.10</MonetaryValue>
</TotalCharges>
<GuaranteedDaysToDelivery/>
<ScheduledDeliveryTime/>
<RatedPackage>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>19.10</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>19.10</MonetaryValue>
</TotalCharges>
<Weight>3.0</Weight>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>3.0</Weight>
</BillingWeight>
</RatedPackage>
</RatedShipment>
<RatedShipment>
<Service>
<Code>12</Code>
</Service>
<RatedShipmentWarning>Your invoice may vary from the displayed reference rates</RatedShipmentWarning>
<RatedShipmentWarning>Missing / Invalid Shipper Number. Returned rates are Retail Rates.</RatedShipmentWarning>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>37.18</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>37.18</MonetaryValue>
</TotalCharges>
<GuaranteedDaysToDelivery>3</GuaranteedDaysToDelivery>
<ScheduledDeliveryTime/>
<RatedPackage>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>37.18</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>37.18</MonetaryValue>
</TotalCharges>
<Weight>3.0</Weight>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
</RatedPackage>
</RatedShipment>
<RatedShipment>
<Service>
<Code>02</Code>
</Service>
<RatedShipmentWarning>Your invoice may vary from the displayed reference rates</RatedShipmentWarning>
<RatedShipmentWarning>Missing / Invalid Shipper Number. Returned rates are Retail Rates.</RatedShipmentWarning>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>49.23</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>49.23</MonetaryValue>
</TotalCharges>
<GuaranteedDaysToDelivery>2</GuaranteedDaysToDelivery>
<ScheduledDeliveryTime/>
<RatedPackage>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>49.23</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>49.23</MonetaryValue>
</TotalCharges>
<Weight>3.0</Weight>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
</RatedPackage>
</RatedShipment>
<RatedShipment>
<Service>
<Code>13</Code>
</Service>
<RatedShipmentWarning>Your invoice may vary from the displayed reference rates</RatedShipmentWarning>
<RatedShipmentWarning>Missing / Invalid Shipper Number. Returned rates are Retail Rates.</RatedShipmentWarning>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>89.54</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>89.54</MonetaryValue>
</TotalCharges>
<GuaranteedDaysToDelivery>1</GuaranteedDaysToDelivery>
<ScheduledDeliveryTime/>
<RatedPackage>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>89.54</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>89.54</MonetaryValue>
</TotalCharges>
<Weight>3.0</Weight>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
</RatedPackage>
</RatedShipment>
<RatedShipment>
<Service>
<Code>01</Code>
</Service>
<RatedShipmentWarning>Your invoice may vary from the displayed reference rates</RatedShipmentWarning>
<RatedShipmentWarning>Missing / Invalid Shipper Number. Returned rates are Retail Rates.</RatedShipmentWarning>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>93.28</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>93.28</MonetaryValue>
</TotalCharges>
<GuaranteedDaysToDelivery>1</GuaranteedDaysToDelivery>
<ScheduledDeliveryTime>12:00 Noon</ScheduledDeliveryTime>
<RatedPackage>
<TransportationCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>93.28</MonetaryValue>
</TransportationCharges>
<ServiceOptionsCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>0.00</MonetaryValue>
</ServiceOptionsCharges>
<TotalCharges>
<CurrencyCode>USD</CurrencyCode>
<MonetaryValue>93.28</MonetaryValue>
</TotalCharges>
<Weight>3.0</Weight>
<BillingWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>5.0</Weight>
</BillingWeight>
</RatedPackage>
</RatedShipment>
</RatingServiceSelectionResponse>

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<RateV4Response>
<Package ID="1">
<ZipOrigination>90401</ZipOrigination>
<ZipDestination>76667</ZipDestination>
<Pounds>3</Pounds>
<Ounces>0</Ounces>
<Size>LARGE</Size>
<Machinable>FALSE</Machinable>
<Zone>6</Zone>
<Postage CLASSID="3">
<MailService>Priority Mail Express 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt;</MailService>
<Rate>42.25</Rate>
</Postage>
<Postage CLASSID="2">
<MailService>Priority Mail Express 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt; Hold For Pickup
</MailService>
<Rate>42.25</Rate>
</Postage>
<Postage CLASSID="1">
<MailService>Priority Mail 2-Day&amp;lt;sup&amp;gt;&amp;#8482;&amp;lt;/sup&amp;gt;</MailService>
<Rate>12.20</Rate>
</Postage>
<Postage CLASSID="4">
<MailService>Standard Post&amp;lt;sup&amp;gt;&amp;#174;&amp;lt;/sup&amp;gt;</MailService>
<Rate>10.01</Rate>
</Postage>
<Postage CLASSID="6">
<MailService>Media Mail Parcel</MailService>
<Rate>3.65</Rate>
</Postage>
<Postage CLASSID="7">
<MailService>Library Mail Parcel</MailService>
<Rate>3.48</Rate>
</Postage>
</Package>
</RateV4Response>

@ -3,145 +3,149 @@ namespace pdt256\Shipping;
class Ship class Ship
{ {
protected $shipping_options = [ protected $shipping_options = [
'Standard Shipping' => [ 'Standard Shipping' => [
'ups' => [ 'ups' => [
'03' => '1-5 business days', '03' => '1-5 business days',
], ],
'fedex' => [ 'fedex' => [
'FEDEX_EXPRESS_SAVER' => '1-3 business days', 'FEDEX_EXPRESS_SAVER' => '1-3 business days',
'FEDEX_GROUND' => '1-5 business days', 'FEDEX_GROUND' => '1-5 business days',
'GROUND_HOME_DELIVERY' => '1-5 business days', 'GROUND_HOME_DELIVERY' => '1-5 business days',
], ],
'usps' => [ 'usps' => [
'1' => '1-3 business days', '1' => '1-3 business days',
'4' => '2-8 business days', '4' => '2-8 business days',
], ],
], ],
'Two-Day Shipping' => [ 'Two-Day Shipping' => [
'ups' => [ 'ups' => [
'02' => '2 business days', '02' => '2 business days',
], ],
'fedex' => [ 'fedex' => [
'FEDEX_2_DAY' => '2 business days', 'FEDEX_2_DAY' => '2 business days',
], ],
], ],
'One-Day Shipping' => [ 'One-Day Shipping' => [
'ups' => [ 'ups' => [
'01' => 'next business day 10:30am', '01' => 'next business day 10:30am',
'13' => 'next business day by 3pm', '13' => 'next business day by 3pm',
'14' => 'next business day by 8am', '14' => 'next business day by 8am',
], ],
'fedex' => [ 'fedex' => [
'STANDARD_OVERNIGHT' => 'overnight', 'STANDARD_OVERNIGHT' => 'overnight',
], ],
], ],
]; ];
public static function factory($shipping_options = []) public static function factory($shipping_options = [])
{ {
$object = new self(); $object = new self();
if ( ! empty($shipping_options)) { if (!empty($shipping_options)) {
$object->shipping_options = $shipping_options; $object->shipping_options = $shipping_options;
} }
return $object; return $object;
} }
public function get_approved_codes($carrier = NULL) { /**
$approved_codes = []; * @return array
*/
// Build approved_codes public function getApprovedCodes($carrier = null)
foreach ($this->shipping_options as $shipping_group => $row) { {
$approvedCodes = [];
foreach ($row as $_carrier => $row2) {
if ( ! isset($approved_codes[$_carrier])) { // Build approvedCodes
$approved_codes[$_carrier] = []; foreach ($this->shipping_options as $shipping_group => $row) {
}
foreach ($row as $_carrier => $row2) {
foreach ($row2 as $code => $display) { if (!isset($approvedCodes[$_carrier])) {
$approved_codes[$_carrier][] = $code; $approvedCodes[$_carrier] = [];
} }
}
} foreach ($row2 as $code => $display) {
$approvedCodes[$_carrier][] = $code;
if ($carrier !== NULL AND isset($approved_codes[$carrier])) { }
return $approved_codes[$carrier]; }
} }
return $approved_codes; if ($carrier !== null && isset($approvedCodes[$carrier])) {
} return $approvedCodes[$carrier];
}
public function get_display_rates($rates)
{ return $approvedCodes;
// Build output array with cheapest shipping option for each group }
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) { public function getDisplayRates($rates)
$display_rates[$shipping_group] = []; {
$cheapest_row = NULL; // Build output array with cheapest shipping option for each group
$display_rates = [];
foreach ($row as $carrier => $row2) { foreach ($this->shipping_options as $shipping_group => $row) {
$group_codes = array_keys($row2); $display_rates[$shipping_group] = [];
$cheapest_row = null;
if ( ! empty($rates[$carrier])) {
foreach ($row as $carrier => $row2) {
foreach ($rates[$carrier] as $row3) { $group_codes = array_keys($row2);
if (in_array($row3->getCode(), $group_codes)) { if (! empty($rates[$carrier])) {
$row3->setCarrier($carrier);
foreach ($rates[$carrier] as $row3) {
if ($cheapest_row === NULL) {
$cheapest_row = $row3; if (in_array($row3->getCode(), $group_codes)) {
} else { $row3->setCarrier($carrier);
if ($row3->getCost() < $cheapest_row->getCost()) {
$cheapest_row = $row3; if ($cheapest_row === null) {
} $cheapest_row = $row3;
} } else {
} if ($row3->getCost() < $cheapest_row->getCost()) {
} $cheapest_row = $row3;
} }
} }
}
// Add row if it exists }
if ( ! empty($cheapest_row)) { }
$display_rates[$shipping_group][] = $cheapest_row; }
}
} // Add row if it exists
if (! empty($cheapest_row)) {
return $display_rates; $display_rates[$shipping_group][] = $cheapest_row;
} }
}
public function get_all_display_rates($rates)
{ return $display_rates;
// Build output array listing all group options }
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) { public function getAllDisplayRates($rates)
$display_rates[$shipping_group] = []; {
// Build output array listing all group options
foreach ($row as $carrier => $row2) { $display_rates = [];
$group_codes = array_keys($row2); foreach ($this->shipping_options as $shipping_group => $row) {
$display_rates[$shipping_group] = [];
if ( ! empty($rates[$carrier])) {
foreach ($row as $carrier => $row2) {
foreach ($rates[$carrier] as $row3) { $group_codes = array_keys($row2);
if (in_array($row3->getCode(), $group_codes)) { if (!empty($rates[$carrier])) {
$row3->setCarrier($carrier);
$display_rates[$shipping_group][] = $row3; foreach ($rates[$carrier] as $row3) {
}
} if (in_array($row3->getCode(), $group_codes)) {
} $row3->setCarrier($carrier);
} $display_rates[$shipping_group][] = $row3;
}
$this->sort_by_cost($display_rates[$shipping_group]); }
} }
}
return $display_rates;
} $this->sortByCost($display_rates[$shipping_group]);
}
protected function sort_by_cost( & $rates)
{ return $display_rates;
uasort($rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());')); }
}
protected function sortByCost(& $rates)
{
uasort($rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());'));
}
} }

@ -5,15 +5,16 @@ class Shipment
/** @var Package[] */ /** @var Package[] */
protected $packages = []; protected $packages = [];
protected $from_postal_code;
protected $from_country_code;
protected $to_postal_code;
protected $to_country_code;
/** @var bool */ /** @var bool */
protected $to_is_residential; protected $fromIsResidential;
protected $fromPostalCode;
protected $fromCountryCode;
protected $fromStateProvince;
/** @var bool */ /** @var bool */
protected $from_is_residential; protected $toIsResidential;
protected $from_state_province; protected $toPostalCode;
protected $toCountryCode;
/** /**
* @param Package $package * @param Package $package
@ -46,16 +47,16 @@ class Shipment
*/ */
public function getFromPostalCode() public function getFromPostalCode()
{ {
return $this->from_postal_code; return $this->fromPostalCode;
} }
/** /**
* @param mixed $from_postal_code * @param mixed $fromPostalCode
* @return $this * @return $this
*/ */
public function setFromPostalCode($from_postal_code) public function setFromPostalCode($fromPostalCode)
{ {
$this->from_postal_code = $from_postal_code; $this->fromPostalCode = $fromPostalCode;
return $this; return $this;
} }
@ -64,16 +65,16 @@ class Shipment
*/ */
public function getFromCountryCode() public function getFromCountryCode()
{ {
return $this->from_country_code; return $this->fromCountryCode;
} }
/** /**
* @param mixed $from_country_code * @param mixed $fromCountryCode
* @return $this * @return $this
*/ */
public function setFromCountryCode($from_country_code) public function setFromCountryCode($fromCountryCode)
{ {
$this->from_country_code = $from_country_code; $this->fromCountryCode = $fromCountryCode;
return $this; return $this;
} }
@ -82,16 +83,16 @@ class Shipment
*/ */
public function getToPostalCode() public function getToPostalCode()
{ {
return $this->to_postal_code; return $this->toPostalCode;
} }
/** /**
* @param mixed $to_postal_code * @param mixed $toPostalCode
* @return $this * @return $this
*/ */
public function setToPostalCode($to_postal_code) public function setToPostalCode($toPostalCode)
{ {
$this->to_postal_code = $to_postal_code; $this->toPostalCode = $toPostalCode;
return $this; return $this;
} }
@ -100,52 +101,52 @@ class Shipment
*/ */
public function getToCountryCode() public function getToCountryCode()
{ {
return $this->to_country_code; return $this->toCountryCode;
} }
/** /**
* @param mixed $to_country_code * @param mixed $toCountryCode
* @return $this * @return $this
*/ */
public function setToCountryCode($to_country_code) public function setToCountryCode($toCountryCode)
{ {
$this->to_country_code = $to_country_code; $this->toCountryCode = $toCountryCode;
return $this; return $this;
} }
/** /**
* @return boolean * @return boolean
*/ */
public function isToResidential() public function getToIsResidential()
{ {
return $this->to_is_residential; return $this->toIsResidential;
} }
/** /**
* @param boolean $to_is_residential * @param boolean $toIsResidential
* @return $this * @return $this
*/ */
public function setToResidential($to_is_residential) public function setToIsResidential($toIsResidential)
{ {
$this->to_is_residential = $to_is_residential; $this->toIsResidential = $toIsResidential;
return $this; return $this;
} }
/** /**
* @return bool * @return bool
*/ */
public function isFromResidential() public function getFromIsResidential()
{ {
return $this->from_is_residential; return $this->fromIsResidential;
} }
/** /**
* @param $from_is_residential * @param $fromIsResidential
* @return $this * @return $this
*/ */
public function setFromResidential($from_is_residential) public function setFromIsResidential($fromIsResidential)
{ {
$this->from_is_residential = $from_is_residential; $this->fromIsResidential = $fromIsResidential;
return $this; return $this;
} }
@ -154,16 +155,16 @@ class Shipment
*/ */
public function getFromStateProvinceCode() public function getFromStateProvinceCode()
{ {
return $this->from_state_province; return $this->fromStateProvince;
} }
/** /**
* @param $from_state_province * @param $fromStateProvince
* @return $this * @return $this
*/ */
public function setFromStateProvinceCode($from_state_province) public function setFromStateProvinceCode($fromStateProvince)
{ {
$this->from_state_province = $from_state_province; $this->fromStateProvince = $fromStateProvince;
return $this; return $this;
} }
} }

@ -11,238 +11,239 @@ use Exception;
class Rate extends RateAdapter class Rate extends RateAdapter
{ {
private $url_dev = 'https://wwwcie.ups.com/ups.app/xml/Rate'; private $urlDev = 'https://wwwcie.ups.com/ups.app/xml/Rate';
private $url_prod = 'https://www.ups.com/ups.app/xml/Rate'; private $urlProd = 'https://www.ups.com/ups.app/xml/Rate';
private $access_key = 'XXX'; private $accessKey = 'XXX';
private $user_id = 'XXX'; private $userId = 'XXX';
private $password = 'XXX'; private $password = 'XXX';
private $shipper_number = 'XXX'; private $shipperNumber = 'XXX';
public $approved_codes = [ public $approvedCodes = [
'03', '03',
'12', '12',
]; ];
private $shipping_codes = [ private $shippingCodes = [
'US' => [ // United States 'US' => [ // United States
'01' => 'UPS Next Day Air', '01' => 'UPS Next Day Air',
'02' => 'UPS 2nd Day Air', '02' => 'UPS 2nd Day Air',
'03' => 'UPS Ground', '03' => 'UPS Ground',
'07' => 'UPS Worldwide Express', '07' => 'UPS Worldwide Express',
'08' => 'UPS Worldwide Expedited', '08' => 'UPS Worldwide Expedited',
'11' => 'UPS Standard', '11' => 'UPS Standard',
'12' => 'UPS 3 Day Select', '12' => 'UPS 3 Day Select',
'13' => 'UPS Next Day Air Saver', '13' => 'UPS Next Day Air Saver',
'14' => 'UPS Next Day Air Early A.M.', '14' => 'UPS Next Day Air Early A.M.',
'54' => 'UPS Worldwide Express Plus', '54' => 'UPS Worldwide Express Plus',
'59' => 'UPS 2nd Day Air A.M.', '59' => 'UPS 2nd Day Air A.M.',
'65' => 'UPS Saver', '65' => 'UPS Saver',
], ],
'CA' => [ // Canada 'CA' => [ // Canada
'01' => 'UPS Express', '01' => 'UPS Express',
'02' => 'UPS Expedited', '02' => 'UPS Expedited',
'07' => 'UPS Worldwide Express', '07' => 'UPS Worldwide Express',
'08' => 'UPS Worldwide Expedited', '08' => 'UPS Worldwide Expedited',
'11' => 'UPS Standard', '11' => 'UPS Standard',
'12' => 'UPS 3 Day Select', '12' => 'UPS 3 Day Select',
'13' => 'UPS Saver', '13' => 'UPS Saver',
'14' => 'UPS Express Early A.M.', '14' => 'UPS Express Early A.M.',
'54' => 'UPS Worldwide Express Plus', '54' => 'UPS Worldwide Express Plus',
'65' => 'UPS Saver', '65' => 'UPS Saver',
], ],
'EU' => [ // European Union 'EU' => [ // European Union
'07' => 'UPS Express', '07' => 'UPS Express',
'08' => 'UPS Expedited', '08' => 'UPS Expedited',
'11' => 'UPS Standard', '11' => 'UPS Standard',
'54' => 'UPS Worldwide Express Plus', '54' => 'UPS Worldwide Express Plus',
'65' => 'UPS Saver', '65' => 'UPS Saver',
'82' => 'UPS Today Standard', '82' => 'UPS Today Standard',
'83' => 'UPS Today Dedicated Courier', '83' => 'UPS Today Dedicated Courier',
'84' => 'UPS Today Intercity', '84' => 'UPS Today Intercity',
'85' => 'UPS Today Express', '85' => 'UPS Today Express',
'86' => 'UPS Today Express Saver', '86' => 'UPS Today Express Saver',
'01' => 'UPS Next Day Air', '01' => 'UPS Next Day Air',
'02' => 'UPS 2nd Day Air', '02' => 'UPS 2nd Day Air',
'03' => 'UPS Ground', '03' => 'UPS Ground',
'14' => 'UPS Next Day Air Early A.M.', '14' => 'UPS Next Day Air Early A.M.',
], ],
'MX' => [ // Mexico 'MX' => [ // Mexico
'07' => 'UPS Express', '07' => 'UPS Express',
'08' => 'UPS Expedited', '08' => 'UPS Expedited',
'54' => 'UPS Express Plus', '54' => 'UPS Express Plus',
'65' => 'UPS Saver', '65' => 'UPS Saver',
], ],
'other' => [ // Other 'other' => [ // Other
'07' => 'UPS Express', '07' => 'UPS Express',
'08' => 'UPS Worldwide Expedited', '08' => 'UPS Worldwide Expedited',
'11' => 'UPS Standard', '11' => 'UPS Standard',
'54' => 'UPS Worldwide Express Plus', '54' => 'UPS Worldwide Express Plus',
'65' => 'UPS Saver', '65' => 'UPS Saver',
], ],
]; ];
public function __construct($options = []) public function __construct($options = [])
{ {
parent::__construct($options); parent::__construct($options);
if (isset($options['access_key'])) { $this->accessKey = Arr::get($options, 'accessKey');
$this->access_key = $options['access_key']; $this->userId = Arr::get($options, 'userId');
} $this->password = Arr::get($options, 'password');
$this->shipperNumber = Arr::get($options, 'shipperNumber');
if (isset($options['user_id'])) { $this->approvedCodes = Arr::get($options, 'approvedCodes');
$this->user_id = $options['user_id'];
} $this->setRequestAdapter(Arr::get($options, 'requestAdapter', new RateRequest\Post()));
if (isset($options['password'])) { }
$this->password = $options['password'];
} protected function prepare()
{
if (isset($options['shipper_number'])) { $service_code = '03';
$this->shipper_number = $options['shipper_number'];
} $this->data = '<?xml version="1.0"?>' . "\n" .
'<AccessRequest xml:lang="en-US">' .
if (isset($options['approved_codes'])) { '<AccessLicenseNumber>' . $this->accessKey . '</AccessLicenseNumber>'.
$this->approved_codes = $options['approved_codes']; '<UserId>' . $this->userId . '</UserId>' .
} '<Password>' . $this->password . '</Password>' .
'</AccessRequest>' .
if (isset($options['request_adapter'])) { '<RatingServiceSelectionRequest xml:lang="en-US">' .
$this->set_request_adapter($options['request_adapter']); '<Request>' .
} else { '<RequestAction>Rate</RequestAction>' .
$this->set_request_adapter(new RateRequest\Post()); '<RequestOption>shop</RequestOption>' .
} '</Request>' .
} '<Shipment>' .
'<Shipper>' .
protected function prepare() '<Address>' .
{ '<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode>' .
$service_code = '03'; '<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode>' .
(
$packages = ''; $this->shipment->getFromIsResidential() ?
foreach ($this->shipment->getPackages() as $p) { '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' :
$packages .= '<Package> ''
<PackagingType> ) .
<Code>02</Code> '</Address>' .
</PackagingType> '<ShipperNumber>' . $this->shipperNumber . '</ShipperNumber>' .
<Dimensions> '</Shipper>' .
<UnitOfMeasurement> '<ShipTo>' .
<Code>IN</Code> '<Address>' .
</UnitOfMeasurement> '<PostalCode>' . $this->shipment->getToPostalCode() . '</PostalCode>' .
<Length>' . $p->getLength() . '</Length> '<CountryCode>' . $this->shipment->getToCountryCode() . '</CountryCode>' .
<Width>' . $p->getWidth() . '</Width> (
<Height>' . $p->getHeight() . '</Height> $this->shipment->getToIsResidential() ?
</Dimensions> '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' :
<PackageWeight> ''
<UnitOfMeasurement> ) .
<Code>LBS</Code> '</Address>' .
</UnitOfMeasurement> '</ShipTo>' .
<Weight>' . $p->getWeight() . '</Weight> '<ShipFrom>' .
</PackageWeight> '<Address>' .
</Package>'; '<StateProvinceCode>' .
} $this->shipment->getFromStateProvinceCode() .
'</StateProvinceCode>' .
$this->data = '<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode>' .
'<?xml version="1.0"?> '<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode>' .
<AccessRequest xml:lang="en-US"> (
<AccessLicenseNumber>' . $this->access_key . '</AccessLicenseNumber> $this->shipment->getFromIsResidential() ?
<UserId>' . $this->user_id . '</UserId> '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' :
<Password>' . $this->password . '</Password> ''
</AccessRequest> ) .
<RatingServiceSelectionRequest xml:lang="en-US"> '</Address>' .
<Request> '</ShipFrom>' .
<RequestAction>Rate</RequestAction> '<Service>' .
<RequestOption>shop</RequestOption> '<Code>' . $service_code . '</Code>' .
</Request> '</Service>' .
<Shipment> $this->getPackagesXmlString() .
<Shipper> '<RateInformation>' .
<Address> '<NegotiatedRatesIndicator/>' .
<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode> '</RateInformation>' .
<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode> '</Shipment>' .
' . (($this->shipment->isFromResidential()) ? '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' : '') . ' '</RatingServiceSelectionRequest>';
</Address>
<ShipperNumber>' . $this->shipper_number . '</ShipperNumber> return $this;
</Shipper> }
<ShipTo>
<Address> private function getPackagesXmlString()
<PostalCode>' . $this->shipment->getToPostalCode() . '</PostalCode> {
<CountryCode>' . $this->shipment->getToCountryCode() . '</CountryCode> $packages = '';
' . (($this->shipment->isToResidential()) ? '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' : '') . ' foreach ($this->shipment->getPackages() as $p) {
</Address> $packages .=
</ShipTo> '<Package>' .
<ShipFrom> '<PackagingType>' .
<Address> '<Code>02</Code>' .
<StateProvinceCode>' . $this->shipment->getFromStateProvinceCode() . '</StateProvinceCode> '</PackagingType>' .
<PostalCode>' . $this->shipment->getFromPostalCode() . '</PostalCode> '<Dimensions>' .
<CountryCode>' . $this->shipment->getFromCountryCode() . '</CountryCode> '<UnitOfMeasurement>' .
' . (($this->shipment->isFromResidential()) ? '<ResidentialAddressIndicator>1</ResidentialAddressIndicator>' : '') . ' '<Code>IN</Code>' .
</Address> '</UnitOfMeasurement>' .
</ShipFrom> '<Length>' . $p->getLength() . '</Length>' .
<Service> '<Width>' . $p->getWidth() . '</Width>' .
<Code>' . $service_code . '</Code> '<Height>' . $p->getHeight() . '</Height>' .
</Service> '</Dimensions>' .
' . $packages . ' '<PackageWeight>' .
<RateInformation> '<UnitOfMeasurement>' .
<NegotiatedRatesIndicator/> '<Code>LBS</Code>' .
</RateInformation> '</UnitOfMeasurement>' .
</Shipment> '<Weight>' . $p->getWeight() . '</Weight>' .
</RatingServiceSelectionRequest>'; '</PackageWeight>' .
'</Package>';
return $this; }
}
return $packages;
protected function execute() }
{
if ($this->is_prod) { protected function execute()
$url = $this->url_prod; {
} else { if ($this->isProduction) {
$url = $this->url_dev; $url = $this->urlProd;
} } else {
$url = $this->urlDev;
$this->response = $this->rate_request->execute($url, $this->data); }
return $this; $this->response = $this->rateRequest->execute($url, $this->data);
}
return $this;
protected function process() }
{
try { protected function process()
$dom = new DOMDocument('1.0', 'UTF-8'); {
$dom->loadXml($this->response); try {
$dom = new DOMDocument('1.0', 'UTF-8');
$rate_list = @$dom->getElementsByTagName('RatedShipment'); $dom->loadXml($this->response);
if (empty($rate_list->length)) { $rate_list = $dom->getElementsByTagName('RatedShipment');
throw new Exception('Unable to get UPS Rates.');
} if (empty($rate_list->length)) {
} catch (Exception $e) { throw new Exception('Unable to get UPS Rates.');
// StatsD::increment('error.shipping.get_ups_rate'); }
// Kohana::$log->add(Log::ERROR, $e)->write(); } catch (Exception $e) {
throw $e; echo $this->response;
} throw $e;
}
foreach ($rate_list as $rate) {
$code = @$rate foreach ($rate_list as $rate) {
->getElementsByTagName('Service')->item(0) $code = @$rate
->getElementsByTagName('Code')->item(0)->nodeValue; ->getElementsByTagName('Service')->item(0)
->getElementsByTagName('Code')->item(0)->nodeValue;
$name = Arr::get($this->shipping_codes['US'], $code);
$name = Arr::get($this->shippingCodes['US'], $code);
$cost = @$rate
->getElementsByTagName('TotalCharges')->item(0) $cost = @$rate
->getElementsByTagName('MonetaryValue')->item(0)->nodeValue; ->getElementsByTagName('TotalCharges')->item(0)
->getElementsByTagName('MonetaryValue')->item(0)->nodeValue;
if ( ! empty($this->approved_codes) AND ! in_array($code, $this->approved_codes)) {
continue; if (! empty($this->approvedCodes) && ! in_array($code, $this->approvedCodes)) {
} continue;
}
$quote = new Quote;
$quote $quote = new Quote;
->setCarrier('ups') $quote
->setCode($code) ->setCarrier('ups')
->setName($name) ->setCode($code)
->setCost((int) $cost * 100); ->setName($name)
$this->rates[] = $quote; ->setCost($cost * 100);
} $this->rates[] = $quote;
}
return $this;
} return $this;
}
} }

@ -11,207 +11,188 @@ use Exception;
class Rate extends RateAdapter class Rate extends RateAdapter
{ {
private $url_dev = 'http://production.shippingapis.com/ShippingAPI.dll'; private $urlDev = 'http://production.shippingapis.com/ShippingAPI.dll';
private $url_prod = 'http://production.shippingapis.com/ShippingAPI.dll'; private $urlProd = 'http://production.shippingapis.com/ShippingAPI.dll';
private $username = 'XXX'; private $username = 'XXX';
private $password = 'XXX'; private $password = 'XXX';
public $approved_codes = [ public $approvedCodes = [
'1', '1',
'4', '4',
]; ];
private $shipping_codes = [ private $shipping_codes = [
'domestic' => [ 'domestic' => [
'00' => 'First-Class Mail Parcel', '00' => 'First-Class Mail Parcel',
'01' => 'First-Class Mail Large Envelope', '01' => 'First-Class Mail Large Envelope',
'02' => 'First-Class Mail Letter', '02' => 'First-Class Mail Letter',
'03' => 'First-Class Mail Postcards', '03' => 'First-Class Mail Postcards',
'1' => 'Priority Mail', '1' => 'Priority Mail',
'2' => 'Express Mail Hold for Pickup', '2' => 'Express Mail Hold for Pickup',
'3' => 'Express Mail', '3' => 'Express Mail',
'4' => 'Parcel Post', // Standard Post '4' => 'Parcel Post', // Standard Post
'5' => 'Bound Printed Matter', '5' => 'Bound Printed Matter',
'6' => 'Media Mail', '6' => 'Media Mail',
'7' => 'Library', '7' => 'Library',
'12' => 'First-Class Postcard Stamped', '12' => 'First-Class Postcard Stamped',
'13' => 'Express Mail Flat-Rate Envelope', '13' => 'Express Mail Flat-Rate Envelope',
'16' => 'Priority Mail Flat-Rate Envelope', '16' => 'Priority Mail Flat-Rate Envelope',
'17' => 'Priority Mail Regular Flat-Rate Box', '17' => 'Priority Mail Regular Flat-Rate Box',
'18' => 'Priority Mail Keys and IDs', '18' => 'Priority Mail Keys and IDs',
'19' => 'First-Class Keys and IDs', '19' => 'First-Class Keys and IDs',
'22' => 'Priority Mail Flat-Rate Large Box', '22' => 'Priority Mail Flat-Rate Large Box',
'23' => 'Express Mail Sunday/Holiday', '23' => 'Express Mail Sunday/Holiday',
'25' => 'Express Mail Flat-Rate Envelope Sunday/Holiday', '25' => 'Express Mail Flat-Rate Envelope Sunday/Holiday',
'27' => 'Express Mail Flat-Rate Envelope Hold For Pickup', '27' => 'Express Mail Flat-Rate Envelope Hold For Pickup',
'28' => 'Priority Mail Small Flat-Rate Box', '28' => 'Priority Mail Small Flat-Rate Box',
], ],
'international' => [ 'international' => [
'1' => 'Express Mail International', '1' => 'Express Mail International',
'2' => 'Priority Mail International', '2' => 'Priority Mail International',
'4' => 'Global Express Guaranteed (Document and Non-document)', '4' => 'Global Express Guaranteed (Document and Non-document)',
'5' => 'Global Express Guaranteed Document used', '5' => 'Global Express Guaranteed Document used',
'6' => 'Global Express Guaranteed Non-Document Rectangular shape', '6' => 'Global Express Guaranteed Non-Document Rectangular shape',
'7' => 'Global Express Guaranteed Non-Document Non-Rectangular', '7' => 'Global Express Guaranteed Non-Document Non-Rectangular',
'8' => 'Priority Mail Flat Rate Envelope', '8' => 'Priority Mail Flat Rate Envelope',
'9' => 'Priority Mail Flat Rate Box', '9' => 'Priority Mail Flat Rate Box',
'10' => 'Express Mail International Flat Rate Envelope', '10' => 'Express Mail International Flat Rate Envelope',
'11' => 'Priority Mail Flat Rate Large Box', '11' => 'Priority Mail Flat Rate Large Box',
'12' => 'Global Express Guaranteed Envelope', '12' => 'Global Express Guaranteed Envelope',
'13' => 'First Class Mail International Letters', '13' => 'First Class Mail International Letters',
'14' => 'First Class Mail International Flats', '14' => 'First Class Mail International Flats',
'15' => 'First Class Mail International Parcels', '15' => 'First Class Mail International Parcels',
'16' => 'Priority Mail Flat Rate Small Box', '16' => 'Priority Mail Flat Rate Small Box',
'21' => 'Postcards', '21' => 'Postcards',
], ],
]; ];
public function __construct($options = []) public function __construct($options = [])
{ {
parent::__construct($options); parent::__construct($options);
if (isset($options['username'])) { $this->username = Arr::get($options, 'username');
$this->username = $options['username']; $this->password = Arr::get($options, 'password');
} $this->approvedCodes = Arr::get($options, 'approvedCodes');
$this->setRequestAdapter(Arr::get($options, 'requestAdapter', new RateRequest\Get()));
if (isset($options['password'])) { }
$this->password = $options['password'];
} protected function prepare()
{
if (isset($options['username'])) { $packages = '';
$this->username = $options['username']; $sequence_number = 0;
} foreach ($this->shipment->getPackages() as $p) {
$sequence_number++;
if (isset($options['approved_codes'])) {
$this->approved_codes = $options['approved_codes']; /**
} * RateV4Request / Package / Size
* required once
if (isset($options['request_adapter'])) { * Defined as follows:
$this->set_request_adapter($options['request_adapter']); *
} else { * REGULAR: Package dimensions are 12 or less;
$this->set_request_adapter(new RateRequest\Get()); * LARGE: Any package dimension is larger than 12.
} *
} * For example: <Size>REGULAR</Size>
* string
protected function prepare() * whiteSpace=collapse
{ * enumeration=LARGE
* enumeration=REGULAR
$packages = '';
$sequence_number = 0; */
foreach ($this->shipment->getPackages() as $p) { if ($p->getWidth() > 12 or $p->getLength() > 12 or $p->getHeight() > 12) {
$sequence_number++; $size = 'LARGE';
$container = 'RECTANGULAR';
/** } else {
* RateV4Request / Package / Size $size = 'REGULAR';
required once $container = 'VARIABLE';
Defined as follows: }
REGULAR: Package dimensions are 12 or less; $packages .=
LARGE: Any package dimension is larger than 12. '<Package ID="' . $sequence_number . '">' .
'<Service>ALL</Service>' .
For example: <Size>REGULAR</Size> '<ZipOrigination>' . $this->shipment->getFromPostalCode() . '</ZipOrigination>' .
string '<ZipDestination>' . $this->shipment->getToPostalCode() . '</ZipDestination>' .
whiteSpace=collapse '<Pounds>' . $p->getWeight() . '</Pounds>' .
enumeration=LARGE '<Ounces>0</Ounces>' .
enumeration=REGULAR '<Container>' . $container . '</Container>' .
'<Size>' . $size . '</Size>' .
*/ '<Width>' . $p->getWidth() . '</Width>' .
if ($p->getWidth() > 12 or $p->getLength() > 12 or $p->getHeight() > 12) { '<Length>' . $p->getLength() . '</Length>' .
$size = 'LARGE'; '<Height>' . $p->getHeight() . '</Height>' .
$container = 'RECTANGULAR'; '<Machinable>' . 'False' . '</Machinable>' .
} else { '</Package>';
$size = 'REGULAR'; }
$container = 'VARIABLE';
} $this->data =
'<RateV4Request USERID="' . $this->username . '">' .
$packages .= '<Package ID="' . $sequence_number .'"> '<Revision/>' .
<Service>ALL</Service> $packages .
<ZipOrigination>' . $this->shipment->getFromPostalCode() . '</ZipOrigination> '</RateV4Request>';
<ZipDestination>' . $this->shipment->getToPostalCode() . '</ZipDestination>
<Pounds>' . $p->getWeight() . '</Pounds> return $this;
<Ounces>0</Ounces> }
<Container>' . $container . '</Container>
<Size>' . $size . '</Size> protected function execute()
<Width>' . $p->getWidth() . '</Width> {
<Length>' . $p->getLength() . '</Length> if ($this->isProduction) {
<Height>' . $p->getHeight() . '</Height> $url = $this->urlProd;
<Machinable>' . 'False' . '</Machinable> } else {
</Package>'; $url = $this->urlDev;
} }
$this->data = $url_request = $url . '?API=RateV4&XML=' . rawurlencode($this->data);
'<RateV4Request USERID="' . $this->username . '">
<Revision/> $this->response = $this->rateRequest->execute($url_request);
' . $packages . '
</RateV4Request>'; return $this;
}
return $this;
} protected function process()
{
protected function execute() try {
{ $dom = new DOMDocument('1.0', 'UTF-8');
if ($this->is_prod) { $dom->loadXml($this->response);
$url = $this->url_prod;
} else { $postage_list = @$dom->getElementsByTagName('Postage');
$url = $this->url_dev;
} if (empty($postage_list)) {
throw new Exception('Unable to get USPS Rates.');
$url_request = $url . '?API=RateV4&XML=' . rawurlencode($this->data); }
} catch (Exception $e) {
$this->response = $this->rate_request->execute($url_request); throw $e;
}
return $this;
} /** @var Quote[] $rates */
$rates = [];
protected function process()
{ foreach ($postage_list as $postage) {
try { $code = @$postage->getAttribute('CLASSID');
$dom = new DOMDocument('1.0', 'UTF-8'); $cost = @$postage->getElementsByTagName('Rate')->item(0)->nodeValue;
$dom->loadXml($this->response);
$name = Arr::get($this->shipping_codes['domestic'], $code);
$postage_list = @$dom->getElementsByTagName('Postage');
if (!empty($this->approvedCodes) && !in_array($code, $this->approvedCodes)) {
if (empty($postage_list)) { continue;
throw new Exception('Unable to get USPS Rates.'); }
}
} catch (Exception $e) { if (array_key_exists($code, $rates)) {
// StatsD::increment('error.shipping.get_usps_rate'); $cost = $rates[$code]->getCost() + ($cost * 100);
// Kohana::$log->add(Log::ERROR, $e)->write(); } else {
throw $e; $cost = $cost * 100;
} }
/** @var Quote[] $rates */ $quote = new Quote;
$rates = []; $quote
->setCarrier('usps')
foreach ($postage_list as $postage) { ->setCode($code)
$code = @$postage->getAttribute('CLASSID'); ->setName($name)
$cost = @$postage->getElementsByTagName('Rate')->item(0)->nodeValue; ->setCost((int) $cost);
$name = Arr::get($this->shipping_codes['domestic'], $code); $rates[$quote->getCode()] = $quote;
}
if ( ! empty($this->approved_codes) AND ! in_array($code, $this->approved_codes)) {
continue; $this->rates = array_values($rates);
}
return $this;
if (array_key_exists($code, $rates)) { }
$cost = $rates[$code]->getCost() + ($cost * 100);
} else {
$cost = $cost * 100;
}
$quote = new Quote;
$quote
->setCarrier('usps')
->setCode($code)
->setName($name)
->setCost((int) $cost);
$rates[$quote->getCode()] = $quote;
}
$this->rates = array_values($rates);
return $this;
}
} }

@ -0,0 +1,28 @@
<?php
namespace pdt256\Shipping;
class ArrTest extends \PHPUnit_Framework_TestCase
{
public function providerGet()
{
return array(
array(array('uno', 'dos', 'tress'), 1, null, 'dos'),
array(array('we' => 'can', 'make' => 'change'), 'we', null, 'can'),
array(array('uno', 'dos', 'tress'), 10, null, null),
array(array('we' => 'can', 'make' => 'change'), 'he', null, null),
array(array('we' => 'can', 'make' => 'change'), 'he', 'who', 'who'),
array(array('we' => 'can', 'make' => 'change'), 'he', array('arrays'), array('arrays')),
);
}
/**
* @dataProvider providerGet()
*/
public function testGet(array $array, $key, $default, $expected)
{
$this->assertSame(
$expected,
Arr::get($array, $key, $default)
);
}
}

@ -0,0 +1,110 @@
<?php
namespace pdt256\Shipping\Fedex;
use pdt256\Shipping\RateRequest\StubFedex;
use pdt256\Shipping\Ship;
use pdt256\Shipping\Package;
use pdt256\Shipping\Shipment;
use pdt256\Shipping\Quote;
use DateTime;
class RateTest extends \PHPUnit_Framework_TestCase
{
/** @var Shipment */
protected $shipment;
protected $approvedCodes = [];
public function setUp()
{
$ship = Ship::factory([
'Standard Shipping' => [
'fedex' => [
'FEDEX_EXPRESS_SAVER' => '1-3 business days',
'FEDEX_GROUND' => '1-5 business days',
'GROUND_HOME_DELIVERY' => '1-5 business days',
],
],
'Two-Day Shipping' => [
'fedex' => [
'FEDEX_2_DAY' => '2 business days',
],
],
'One-Day Shipping' => [
'fedex' => [
'STANDARD_OVERNIGHT' => 'overnight',
],
],
]);
$this->approvedCodes = $ship->getApprovedCodes('fedex');
$package = new Package;
$package->setWeight(3)
->setWidth(9)
->setLength(9)
->setHeight(9);
$this->shipment = new Shipment;
$this->shipment->setFromStateProvinceCode('CA')
->setFromPostalCode('90401')
->setFromCountryCode('US')
->setToPostalCode('78703')
->setToCountryCode('US')
->setToIsResidential(true)
->addPackage($package);
}
public function testMockRates()
{
$rateAdapter = new Rate([
'prod' => false,
'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
'requestAdapter' => new StubFedex,
]);
$rates = $rateAdapter->getRates();
$ground = new Quote('fedex', 'GROUND_HOME_DELIVERY', 'Ground Home Delivery', 1655);
$ground->setTransitTime('THREE_DAYS');
$express = new Quote('fedex', 'FEDEX_EXPRESS_SAVER', 'Fedex Express Saver', 2989);
$express->setDeliveryEstimate(new DateTime('2014-09-30T20:00:00'));
$secondDay = new Quote('fedex', 'FEDEX_2_DAY', 'Fedex 2 Day', 4072);
$secondDay->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00'));
$overnight = new Quote('fedex', 'STANDARD_OVERNIGHT', 'Standard Overnight', 7834);
$overnight->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00'));
$expected = [$ground, $express, $secondDay, $overnight];
$this->assertEquals($expected, $rates);
}
public function testLiveRates()
{
if (getenv('FEDEX_KEY') === false) {
$this->markTestSkipped('Live Fedex credentials missing.');
}
$rateAdapter = new Rate([
'prod' => false,
'key' => getenv('FEDEX_KEY'),
'password' => getenv('FEDEX_PASSWORD'),
'account_number' => getenv('FEDEX_ACCOUNT_NUMBER'),
'meter_number' => getenv('FEDEX_METER_NUMBER'),
'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
'requestAdapter' => new StubFedex,
]);
$rates = $rateAdapter->getRates();
$this->assertTrue(count($rates) > 0);
$this->assertTrue($rates[0] instanceof Quote);
}
}

@ -0,0 +1,19 @@
<?php
namespace pdt256\Shipping;
class PackageTest extends \PHPUnit_Framework_TestCase
{
public function testCreate()
{
$package = new Package;
$package->setWeight(5);
$package->setWidth(6);
$package->setLength(7);
$package->setHeight(8);
$this->assertEquals(5, $package->getWeight());
$this->assertEquals(6, $package->getWidth());
$this->assertEquals(7, $package->getLength());
$this->assertEquals(8, $package->getHeight());
}
}

@ -0,0 +1,25 @@
<?php
namespace pdt256\Shipping;
use DateTime;
class QuoteTest extends \PHPUnit_Framework_TestCase
{
public function testCreate()
{
$quote = new Quote;
$quote->setCode('-code-');
$quote->setName('Test Name');
$quote->setCost(500);
$quote->setTransitTime('-transit-time-');
$quote->setDeliveryEstimate(new DateTime);
$quote->setCarrier('-carrier-');
$this->assertEquals('-code-', $quote->getCode());
$this->assertEquals('Test Name', $quote->getName());
$this->assertEquals(500, $quote->getCost());
$this->assertEquals('-transit-time-', $quote->getTransitTime());
$this->assertTrue($quote->getDeliveryEstimate() instanceof DateTime);
$this->assertEquals('-carrier-', $quote->getCarrier());
}
}

@ -0,0 +1,13 @@
<?php
namespace pdt256\Shipping;
class RateAdapterTest extends \PHPUnit_Framework_TestCase
{
public function testCreate()
{
/* @var RateAdapter $mock */
$mock = $this->getMockForAbstractClass('pdt256\Shipping\RateAdapter');
$mockRequestAdapter = $this->getMockForAbstractClass('pdt256\Shipping\RateRequest\Adapter');
$mock->setRequestAdapter($mockRequestAdapter);
}
}

@ -1,277 +1,155 @@
<?php <?php
use pdt256\Shipping\Package; namespace pdt256\Shipping;
use pdt256\Shipping\Quote;
use pdt256\Shipping\Ship;
use pdt256\Shipping\Shipment;
use pdt256\Shipping\USPS;
use pdt256\Shipping\UPS;
use pdt256\Shipping\Fedex;
use pdt256\Shipping\RateRequest;
class ShipTest extends PHPUnit_Framework_TestCase use DateTime;
{
/** @var Shipment */
public $shipment;
public $shipping_options = [
'Standard Shipping' => [
'ups' => [
'03' => '1-5 business days',
],
'fedex' => [
'FEDEX_EXPRESS_SAVER' => '1-3 business days',
'FEDEX_GROUND' => '1-5 business days',
'GROUND_HOME_DELIVERY' => '1-5 business days',
],
'usps' => [
'1' => '1-3 business days',
'4' => '2-8 business days',
],
],
'Two-Day Shipping' => [
'ups' => [
'02' => '2 business days',
],
'fedex' => [
'FEDEX_2_DAY' => '2 business days',
],
],
'One-Day Shipping' => [
'ups' => [
'01' => 'next business day 10:30am',
'13' => 'next business day by 3pm',
'14' => 'next business day by 8am',
],
'fedex' => [
'STANDARD_OVERNIGHT' => 'overnight',
],
],
];
public function setUp()
{
$s = new Shipment;
$s->setFromStateProvinceCode('CA')
->setFromPostalCode('90401')
->setFromCountryCode('US')
->setToPostalCode('78703')
->setToCountryCode('US')
->setToResidential(true);
$p = new Package;
$p->setWeight(3)
->setWidth(9)
->setLength(9)
->setHeight(9);
$s->addPackage($p);
$this->shipment = $s;
}
private function getUSPSOptions()
{
$ship = Ship::factory($this->shipping_options);
$approved_codes = $ship->get_approved_codes('usps');
return [
'prod' => FALSE,
'username' => 'XXXX',
'password' => 'XXXX',
'shipment' => $this->shipment,
'approved_codes' => $approved_codes,
'request_adapter' => new RateRequest\StubUSPS(),
];
}
private function getUPSOptions()
{
$ship = Ship::factory($this->shipping_options);
$approved_codes = $ship->get_approved_codes('ups');
return [
'prod' => FALSE,
'access_key' => 'XXXX',
'user_id' => 'XXXX',
'password' => 'XXXX',
'shipper_number' => 'XXXX',
'shipment' => $this->shipment,
'approved_codes' => $approved_codes,
'request_adapter' => new RateRequest\StubUPS(),
];
}
private function getFedexOptions()
{
$ship = Ship::factory($this->shipping_options);
$approved_codes = $ship->get_approved_codes('fedex');
return [
'prod' => FALSE,
'key' => 'XXXX',
'password' => 'XXXX',
'account_number' => 'XXXX',
'meter_number' => 'XXXX',
'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $this->shipment,
'approved_codes' => $approved_codes,
'request_adapter' => new RateRequest\StubFedex(),
];
}
public function testUSPSRate()
{
$usps = new USPS\Rate($this->getUSPSOptions());
$usps_rates = $usps->get_rates();
$post = new Quote;
$post
->setCarrier('usps')
->setCode(4)
->setName('Parcel Post')
->setCost(1001);
$priority = new Quote;
$priority
->setCarrier('usps')
->setCode(1)
->setName('Priority Mail')
->setCost(1220);
$expected_return = [$post, $priority];
$this->assertEquals($expected_return, $usps_rates);
}
public function testUPSRate()
{
$ups = new UPS\Rate($this->getUPSOptions());
$ups_rates = $ups->get_rates();
$ground = new Quote;
$ground
->setCarrier('ups')
->setCode('03')
->setName('UPS Ground')
->setCost(1900);
$twodayair = new Quote;
$twodayair
->setCarrier('ups')
->setCode('02')
->setName('UPS 2nd Day Air')
->setCost(4900);
$nextdaysaver = new Quote; class ShipTest extends \PHPUnit_Framework_TestCase
$nextdaysaver {
->setCarrier('ups') /** @var Shipment */
->setCode('13') public $shipment;
->setName('UPS Next Day Air Saver')
->setCost(8900); public $shipping_options = [
'Standard Shipping' => [
$nextdayair = new Quote; 'ups' => [
$nextdayair '03' => '1-5 business days',
->setCarrier('ups') ],
->setCode('01') 'fedex' => [
->setName('UPS Next Day Air') 'FEDEX_EXPRESS_SAVER' => '1-3 business days',
->setCost(9300); 'FEDEX_GROUND' => '1-5 business days',
'GROUND_HOME_DELIVERY' => '1-5 business days',
$expected_return = [$ground, $twodayair, $nextdaysaver, $nextdayair]; ],
'usps' => [
$this->assertEquals($expected_return, $ups_rates); '1' => '1-3 business days',
} '4' => '2-8 business days',
],
public function testFedexRate() ],
{ 'Two-Day Shipping' => [
$fedex = new Fedex\Rate($this->getFedexOptions()); 'ups' => [
$fedex_rates = $fedex->get_rates(); '02' => '2 business days',
],
$ground = new Quote; 'fedex' => [
$ground 'FEDEX_2_DAY' => '2 business days',
->setCarrier('fedex') ],
->setCode('GROUND_HOME_DELIVERY') ],
->setName('Ground Home Delivery') 'One-Day Shipping' => [
->setCost(1600) 'ups' => [
->setTransitTime('THREE_DAYS'); '01' => 'next business day 10:30am',
'13' => 'next business day by 3pm',
$express = new Quote; '14' => 'next business day by 8am',
$express ],
->setCarrier('fedex') 'fedex' => [
->setCode('FEDEX_EXPRESS_SAVER') 'STANDARD_OVERNIGHT' => 'overnight',
->setName('Fedex Express Saver') ],
->setCost(2900) ],
->setDeliveryEstimate(new DateTime('2014-09-30T20:00:00')) ];
->setTransitTime(null);
public function setUp()
$secondday = new Quote; {
$secondday $s = new Shipment;
->setCarrier('fedex') $s->setFromStateProvinceCode('CA')
->setCode('FEDEX_2_DAY') ->setFromPostalCode('90401')
->setName('Fedex 2 Day') ->setFromCountryCode('US')
->setCost(4000) ->setToPostalCode('78703')
->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00')) ->setToCountryCode('US')
->setTransitTime(null); ->setToIsResidential(true);
$overnight = new Quote; $p = new Package;
$overnight $p->setWeight(3)
->setCarrier('fedex') ->setWidth(9)
->setCode('STANDARD_OVERNIGHT') ->setLength(9)
->setName('Standard Overnight') ->setHeight(9);
->setCost(7800)
->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00')) $s->addPackage($p);
->setTransitTime(null);
$this->shipment = $s;
$expected_result = [$ground, $express, $secondday, $overnight]; }
$this->assertEquals($expected_result, $fedex_rates); private function getUSPSOptions()
} {
$ship = Ship::factory($this->shipping_options);
public function testDisplayOptions() $approvedCodes = $ship->getApprovedCodes('usps');
{
$rates = []; return [
'prod' => false,
$usps = new USPS\Rate($this->getUSPSOptions()); 'username' => 'XXXX',
$rates['usps'] = $usps->get_rates(); 'password' => 'XXXX',
'shipment' => $this->shipment,
$ups = new UPS\Rate($this->getUPSOptions()); 'approvedCodes' => $approvedCodes,
$rates['ups'] = $ups->get_rates(); 'requestAdapter' => new RateRequest\StubUSPS(),
];
$fedex = new Fedex\Rate($this->getFedexOptions()); }
$rates['fedex'] = $fedex->get_rates();
private function getUPSOptions()
$ship = Ship::factory($this->shipping_options); {
$display_rates = $ship->get_display_rates($rates); $ship = Ship::factory($this->shipping_options);
$approvedCodes = $ship->getApprovedCodes('ups');
$post = new Quote;
$post->setCode(4) return [
->setName('Parcel Post') 'prod' => false,
->setCost(1001) 'accessKey' => 'XXXX',
->setCarrier('usps'); 'userId' => 'XXXX',
'password' => 'XXXX',
$fedex_two_day = new Quote; 'shipperNumber' => 'XXXX',
$fedex_two_day->setCode('FEDEX_2_DAY') 'shipment' => $this->shipment,
->setName('Fedex 2 Day') 'approvedCodes' => $approvedCodes,
->setCost(4000) 'requestAdapter' => new RateRequest\StubUPS(),
->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00')) ];
->setCarrier('fedex'); }
$overnight = new Quote; private function getFedexOptions()
$overnight->setCode('STANDARD_OVERNIGHT') {
->setName('Standard Overnight') $ship = Ship::factory($this->shipping_options);
->setCost(7800) $approvedCodes = $ship->getApprovedCodes('fedex');
->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00'))
->setCarrier('fedex'); return [
'prod' => false,
$this->assertEquals([ 'key' => 'XXXX',
'Standard Shipping' => [ 'password' => 'XXXX',
$post, 'account_number' => 'XXXX',
], 'meter_number' => 'XXXX',
'Two-Day Shipping' => [ 'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
$fedex_two_day, 'shipment' => $this->shipment,
], 'approvedCodes' => $approvedCodes,
'One-Day Shipping' => [ 'requestAdapter' => new RateRequest\StubFedex(),
$overnight, ];
], }
], $display_rates);
} public function testDisplayOptions()
{
$rates = [];
$usps = new USPS\Rate($this->getUSPSOptions());
$rates['usps'] = $usps->getRates();
$ups = new UPS\Rate($this->getUPSOptions());
$rates['ups'] = $ups->getRates();
$fedex = new Fedex\Rate($this->getFedexOptions());
$rates['fedex'] = $fedex->getRates();
$ship = Ship::factory($this->shipping_options);
$rates = $ship->getDisplayRates($rates);
$post = new Quote('usps', '4', 'Parcel Post', 1001);
$fedexTwoDay = new Quote('fedex', 'FEDEX_2_DAY', 'Fedex 2 Day', 4072);
$fedexTwoDay->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00'));
$overnight = new Quote('fedex', 'STANDARD_OVERNIGHT', 'Standard Overnight', 7834);
$overnight->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00'));
$expected = [
'Standard Shipping' => [
$post,
],
'Two-Day Shipping' => [
$fedexTwoDay,
],
'One-Day Shipping' => [
$overnight,
],
];
$this->assertEquals($expected, $rates);
}
} }

@ -0,0 +1,32 @@
<?php
namespace pdt256\Shipping;
class ShipmentTest extends \PHPUnit_Framework_TestCase
{
public function testCreate()
{
$shipment = new Shipment;
$shipment->addPackage(new Package);
$shipment->setFromIsResidential(false);
$shipment->setFromPostalCode('90401');
$shipment->setFromCountryCode('US');
$shipment->setFromStateProvinceCode('CA');
$shipment->setToIsResidential(true);
$shipment->setToPostalCode('90210');
$shipment->setToCountryCode('US');
$this->assertTrue($shipment->getPackages()[0] instanceof Package);
$this->assertEquals(1, $shipment->packageCount());
$this->assertFalse($shipment->getFromIsResidential());
$this->assertEquals('90401', $shipment->getFromPostalCode());
$this->assertEquals('US', $shipment->getFromCountryCode());
$this->assertEquals('CA', $shipment->getFromStateProvinceCode());
$this->assertTrue($shipment->getToIsResidential());
$this->assertEquals('90210', $shipment->getToPostalCode());
$this->assertEquals('US', $shipment->getToCountryCode());
}
}

@ -0,0 +1,99 @@
<?php
namespace pdt256\Shipping\UPS;
use pdt256\Shipping\RateRequest\StubUPS;
use pdt256\Shipping\Ship;
use pdt256\Shipping\Package;
use pdt256\Shipping\Shipment;
use pdt256\Shipping\Quote;
class RateTest extends \PHPUnit_Framework_TestCase
{
/** @var Shipment */
protected $shipment;
protected $approvedCodes = [];
public function setUp()
{
$ship = Ship::factory([
'Standard Shipping' => [
'ups' => [
'03' => '1-5 business days',
],
],
'Two-Day Shipping' => [
'ups' => [
'02' => '2 business days',
],
],
'One-Day Shipping' => [
'ups' => [
'01' => 'next business day 10:30am',
'13' => 'next business day by 3pm',
'14' => 'next business day by 8am',
],
],
]);
$this->approvedCodes = $ship->getApprovedCodes('ups');
$package = new Package;
$package->setWeight(3)
->setWidth(9)
->setLength(9)
->setHeight(9);
$this->shipment = new Shipment;
$this->shipment->setFromStateProvinceCode('CA')
->setFromPostalCode('90401')
->setFromCountryCode('US')
->setToPostalCode('78703')
->setToCountryCode('US')
->setToIsResidential(true)
->addPackage($package);
}
public function testMockRates()
{
$rateAdapter = new Rate([
'prod' => false,
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
'requestAdapter' => new StubUPS,
]);
$rates = $rateAdapter->getRates();
$expected = [
new Quote('ups', '03', 'UPS Ground', 1910),
new Quote('ups', '02', 'UPS 2nd Day Air', 4923),
new Quote('ups', '13', 'UPS Next Day Air Saver', 8954),
new Quote('ups', '01', 'UPS Next Day Air', 9328),
];
$this->assertEquals($expected, $rates);
}
public function testLiveRates()
{
if (getenv('UPS_ACCESS_KEY') === false) {
$this->markTestSkipped('Live UPS credentials missing.');
}
$rateAdapter = new Rate([
'prod' => false,
'accessKey' => getenv('UPS_ACCESS_KEY'),
'userId' => getenv('UPS_USER_ID'),
'password' => getenv('UPS_PASSWORD'),
'shipperNumber' => getenv('UPS_SHIPPER_NUMBER'),
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
]);
$rates = $rateAdapter->getRates();
$this->assertTrue(count($rates) > 0);
$this->assertTrue($rates[0] instanceof Quote);
}
}

@ -0,0 +1,86 @@
<?php
namespace pdt256\Shipping\USPS;
use pdt256\Shipping\RateRequest\StubUSPS;
use pdt256\Shipping\Ship;
use pdt256\Shipping\Package;
use pdt256\Shipping\Shipment;
use pdt256\Shipping\Quote;
class RateTest extends \PHPUnit_Framework_TestCase
{
/** @var Shipment */
protected $shipment;
protected $approvedCodes = [];
public function setUp()
{
$ship = Ship::factory([
'Standard Shipping' => [
'usps' => [
'1' => '1-3 business days',
'4' => '2-8 business days',
],
],
]);
$this->approvedCodes = $ship->getApprovedCodes('usps');
$package = new Package;
$package->setWeight(3)
->setWidth(9)
->setLength(9)
->setHeight(9);
$this->shipment = new Shipment;
$this->shipment->setFromStateProvinceCode('CA')
->setFromPostalCode('90401')
->setFromCountryCode('US')
->setToPostalCode('78703')
->setToCountryCode('US')
->setToIsResidential(true)
->addPackage($package);
}
public function testMockRates()
{
$rateAdapter = new Rate([
'prod' => false,
'username' => 'XXXX',
'password' => 'XXXX',
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
'requestAdapter' => new StubUSPS,
]);
$rates = $rateAdapter->getRates();
$expected = [
new Quote('usps', '4', 'Parcel Post', 1001),
new Quote('usps', '1', 'Priority Mail', 1220),
];
$this->assertEquals($expected, $rates);
}
public function testLiveRates()
{
if (getenv('USPS_USERNAME') === false) {
$this->markTestSkipped('Live USPS credentials missing.');
}
$rateAdapter = new Rate([
'prod' => false,
'username' => getenv('USPS_USERNAME'),
'password' => getenv('USPS_PASSWORD'),
'shipment' => $this->shipment,
'approvedCodes' => $this->approvedCodes,
]);
$rates = $rateAdapter->getRates();
$this->assertTrue(count($rates) > 0);
$this->assertTrue($rates[0] instanceof Quote);
}
}
Loading…
Cancel
Save