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 9 years ago
parent 01ddc321ce
commit ccf447f755

11
.gitignore vendored

@ -1,4 +1,9 @@
vendor/
.idea/
vendor
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
[![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.
## 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
Add the following lines to your ``composer.json`` file.
```JSON
{
"require": {
"pdt256/shipping": "dev-master"
}
"require": {
"pdt256/shipping": "1.0.*@dev"
}
}
```
@ -23,15 +36,20 @@ Create a shipment object:
```php
$shipment = new Shipment;
$shipment
->setFromIsResidential(false)
->setFromStateProvinceCode('IN')
->setFromPostalCode('46205')
->setFromCountryCode('US')
->setToIsResidential(true);
->setToPostalCode('20101')
->setToCountryCode('US')
->setToResidential(true);
$package = new Package;
$package->setLength(12)->setWidth(4)->setHeight(3)->setWeight(3);
$package
->setLength(12)
->setWidth(4)
->setHeight(3)
->setWeight(3);
$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.
```php
'request_adapter' => new RateRequest\StubUPS(),
'requestAdapter' => new RateRequest\StubUPS(),
```
```php
@ -52,23 +70,23 @@ use pdt256\Shipping\UPS;
use pdt256\Shipping\RateRequest;
$ups = new UPS\Rate([
'prod' => FALSE,
'access_key' => 'XXXX',
'user_id' => 'XXXX',
'password' => 'XXXX',
'shipper_number' => 'XXXX',
'shipment' => $shipment,
'approved_codes' => [
'03', // 1-5 business days
'02', // 2 business days
'01', // next business day 10:30am
'13', // next business day by 3pm
'14', // next business day by 8am
],
'request_adapter' => new RateRequest\StubUPS(),
'prod' => FALSE,
'accessKey' => 'XXXX',
'userId' => 'XXXX',
'password' => 'XXXX',
'shipperNumber' => 'XXXX',
'shipment' => $shipment,
'approvedCodes' => [
'03', // 1-5 business days
'02', // 2 business days
'01', // next business day 10:30am
'13', // next business day by 3pm
'14', // next business day by 8am
],
'requestAdapter' => new RateRequest\StubUPS(),
]);
$ups_rates = $ups->get_rates();
$upsRates = $ups->getRates();
```
Output array sorted by cost: (in cents)
@ -83,9 +101,9 @@ array(4) {
string(10) "UPS Ground"
protected $cost =>
int(1900)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(3) "ups"
@ -98,9 +116,9 @@ array(4) {
string(15) "UPS 2nd Day Air"
protected $cost =>
int(4900)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(3) "ups"
@ -113,9 +131,9 @@ array(4) {
string(22) "UPS Next Day Air Saver"
protected $cost =>
int(8900)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(3) "ups"
@ -128,9 +146,9 @@ array(4) {
string(16) "UPS Next Day Air"
protected $cost =>
int(9300)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(3) "ups"
@ -149,14 +167,14 @@ $usps = new USPS\Rate([
'username' => 'XXXX',
'password' => 'XXXX',
'shipment' => $shipment,
'approved_codes' => [
'approvedCodes' => [
'1', // 1-3 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)
@ -171,9 +189,9 @@ array(2) {
string(11) "Parcel Post"
protected $cost =>
int(1001)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(4) "usps"
@ -186,9 +204,9 @@ array(2) {
string(13) "Priority Mail"
protected $cost =>
int(1220)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(4) "usps"
@ -206,21 +224,21 @@ $fedex = new Fedex\Rate([
'prod' => FALSE,
'key' => 'XXXX',
'password' => 'XXXX',
'account_number' => 'XXXX',
'meter_number' => 'XXXX',
'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
'accountNumber' => 'XXXX',
'meterNumber' => 'XXXX',
'dropOffType' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $shipment,
'approved_codes' => [
'approvedCodes' => [
'FEDEX_EXPRESS_SAVER', // 1-3 business days
'FEDEX_GROUND', // 1-5 business days
'GROUND_HOME_DELIVERY', // 1-5 business days
'FEDEX_2_DAY', // 2 business days
'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)
@ -235,9 +253,9 @@ array(4) {
string(20) "Ground Home Delivery"
protected $cost =>
int(1600)
protected $transit_time =>
protected $transitTime =>
string(10) "THREE_DAYS"
protected $delivery_ts =>
protected $deliveryTime =>
NULL
protected $carrier =>
string(5) "fedex"
@ -250,9 +268,9 @@ array(4) {
string(19) "Fedex Express Saver"
protected $cost =>
int(2900)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
class Carbon\Carbon#23 (3) {
public $date =>
string(26) "2014-09-30 20:00:00.000000"
@ -272,9 +290,9 @@ array(4) {
string(11) "Fedex 2 Day"
protected $cost =>
int(4000)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
class Carbon\Carbon#26 (3) {
public $date =>
string(26) "2014-09-29 20:00:00.000000"
@ -294,9 +312,9 @@ array(4) {
string(18) "Standard Overnight"
protected $cost =>
int(7800)
protected $transit_time =>
protected $transitTime =>
NULL
protected $delivery_ts =>
protected $deliveryTime =>
class Carbon\Carbon#58 (3) {
public $date =>
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
The MIT License (MIT)

@ -9,8 +9,17 @@
"email": "pdt256@gmail.com"
}
],
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"require-dev": {
"phpunit/phpunit": "4.0.*"
"phpunit/phpunit": "4.0.*",
"squizlabs/php_codesniffer": "1.5.5"
},
"require": {
"php": ">=5.4"
},
"autoload": {
"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"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Ship Test Suite">
<directory suffix=".php">./tests</directory>
</testsuite>
</testsuites>
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false">
<testsuites>
<testsuite name="Ship Test Suite">
<directory suffix=".php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>

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

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

@ -5,49 +5,58 @@ use Exception;
abstract class RateAdapter
{
protected $is_prod = FALSE;
/** @var Shipment */
protected $shipment;
protected $data;
protected $response;
protected $rates = [];
protected $rate_request;
abstract protected function prepare(); // Prepare XML
abstract protected function execute(); // Curl Request
abstract protected function process(); // Convert to shipping rates array
public function __construct($options = [])
{
if (isset($options['prod'])) {
$this->is_prod = (bool) $options['prod'];
}
if (isset($options['shipment'])) {
$this->shipment = $options['shipment'];
}
}
public function set_request_adapter(RateRequest\Adapter $rate_request)
{
$this->rate_request = $rate_request;
}
public function get_rates()
{
$this
->prepare()
->execute()
->process()
->sort_by_cost();
return array_values($this->rates);
}
protected function sort_by_cost()
{
uasort($this->rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());'));
}
protected $isProduction;
/** @var Shipment */
protected $shipment;
protected $data;
protected $response;
protected $rates;
/** @var @var RateRequest\Adapter */
protected $rateRequest;
/**
* Prepare XML
*/
abstract protected function prepare();
/**
* Curl Request
*/
abstract protected function execute();
/**
* Convert to shipping rates array
*/
abstract protected function process();
public function __construct($options = [])
{
$this->rates = [];
$this->isProduction = (bool) Arr::get($options, 'prod', false);
$this->shipment = Arr::get($options, 'shipment');
}
public function setRequestAdapter(RateRequest\Adapter $rateRequest)
{
$this->rateRequest = $rateRequest;
}
public function getRates()
{
$this
->prepare()
->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
{
protected $curl_connect_timeout_ms = 1000; // milliseconds
protected $curl_dl_timeout = 11; // seconds
protected $curlConnectTimeoutInMilliseconds = 1000;
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
{
public function execute($url, $data = NULL)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curl_connect_timeout_ms);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_dl_timeout);
$response = curl_exec($ch);
curl_close($ch);
if ($response === false) {
throw new RequestException(curl_error($ch));
}
return $response;
}
public function execute($url, $data = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1');
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->curlConnectTimeoutInMilliseconds);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->curlDownloadTimeoutInSeconds);
$response = curl_exec($ch);
curl_close($ch);
if ($response === false) {
throw new RequestException(curl_error($ch));
}
return $response;
}
}

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

@ -1,6 +1,6 @@
<?php
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
{
private $artificial_delay = 0;
private $artificialDelay = 0;
public function __construct($artificial_delay = 0)
{
$this->artificial_delay = $artificial_delay;
}
public function __construct($artificial_delay = 0)
{
$this->artificialDelay = $artificial_delay;
}
public function execute($url, $data = NULL)
{
if ($this->artificial_delay > 0) {
sleep($this->artificial_delay);
}
public function execute($url, $data = null)
{
if ($this->artificialDelay > 0) {
sleep($this->artificialDelay);
}
$response = '<?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>';
return $response;
}
return file_get_contents(__DIR__ . '/USPSResponse.xml');
}
}

@ -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
{
protected $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 static function factory($shipping_options = [])
{
$object = new self();
if ( ! empty($shipping_options)) {
$object->shipping_options = $shipping_options;
}
return $object;
}
public function get_approved_codes($carrier = NULL) {
$approved_codes = [];
// Build approved_codes
foreach ($this->shipping_options as $shipping_group => $row) {
foreach ($row as $_carrier => $row2) {
if ( ! isset($approved_codes[$_carrier])) {
$approved_codes[$_carrier] = [];
}
foreach ($row2 as $code => $display) {
$approved_codes[$_carrier][] = $code;
}
}
}
if ($carrier !== NULL AND isset($approved_codes[$carrier])) {
return $approved_codes[$carrier];
}
return $approved_codes;
}
public function get_display_rates($rates)
{
// Build output array with cheapest shipping option for each group
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) {
$display_rates[$shipping_group] = [];
$cheapest_row = NULL;
foreach ($row as $carrier => $row2) {
$group_codes = array_keys($row2);
if ( ! empty($rates[$carrier])) {
foreach ($rates[$carrier] as $row3) {
if (in_array($row3->getCode(), $group_codes)) {
$row3->setCarrier($carrier);
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;
}
}
return $display_rates;
}
public function get_all_display_rates($rates)
{
// Build output array listing all group options
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) {
$display_rates[$shipping_group] = [];
foreach ($row as $carrier => $row2) {
$group_codes = array_keys($row2);
if ( ! empty($rates[$carrier])) {
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;
}
protected function sort_by_cost( & $rates)
{
uasort($rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());'));
}
protected $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 static function factory($shipping_options = [])
{
$object = new self();
if (!empty($shipping_options)) {
$object->shipping_options = $shipping_options;
}
return $object;
}
/**
* @return array
*/
public function getApprovedCodes($carrier = null)
{
$approvedCodes = [];
// Build approvedCodes
foreach ($this->shipping_options as $shipping_group => $row) {
foreach ($row as $_carrier => $row2) {
if (!isset($approvedCodes[$_carrier])) {
$approvedCodes[$_carrier] = [];
}
foreach ($row2 as $code => $display) {
$approvedCodes[$_carrier][] = $code;
}
}
}
if ($carrier !== null && isset($approvedCodes[$carrier])) {
return $approvedCodes[$carrier];
}
return $approvedCodes;
}
public function getDisplayRates($rates)
{
// Build output array with cheapest shipping option for each group
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) {
$display_rates[$shipping_group] = [];
$cheapest_row = null;
foreach ($row as $carrier => $row2) {
$group_codes = array_keys($row2);
if (! empty($rates[$carrier])) {
foreach ($rates[$carrier] as $row3) {
if (in_array($row3->getCode(), $group_codes)) {
$row3->setCarrier($carrier);
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;
}
}
return $display_rates;
}
public function getAllDisplayRates($rates)
{
// Build output array listing all group options
$display_rates = [];
foreach ($this->shipping_options as $shipping_group => $row) {
$display_rates[$shipping_group] = [];
foreach ($row as $carrier => $row2) {
$group_codes = array_keys($row2);
if (!empty($rates[$carrier])) {
foreach ($rates[$carrier] as $row3) {
if (in_array($row3->getCode(), $group_codes)) {
$row3->setCarrier($carrier);
$display_rates[$shipping_group][] = $row3;
}
}
}
}
$this->sortByCost($display_rates[$shipping_group]);
}
return $display_rates;
}
protected function sortByCost(& $rates)
{
uasort($rates, create_function('$a, $b', 'return ($a->getCost() > $b->getCost());'));
}
}

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

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

@ -11,207 +11,188 @@ use Exception;
class Rate extends RateAdapter
{
private $url_dev = 'http://production.shippingapis.com/ShippingAPI.dll';
private $url_prod = 'http://production.shippingapis.com/ShippingAPI.dll';
private $username = 'XXX';
private $password = 'XXX';
public $approved_codes = [
'1',
'4',
];
private $shipping_codes = [
'domestic' => [
'00' => 'First-Class Mail Parcel',
'01' => 'First-Class Mail Large Envelope',
'02' => 'First-Class Mail Letter',
'03' => 'First-Class Mail Postcards',
'1' => 'Priority Mail',
'2' => 'Express Mail Hold for Pickup',
'3' => 'Express Mail',
'4' => 'Parcel Post', // Standard Post
'5' => 'Bound Printed Matter',
'6' => 'Media Mail',
'7' => 'Library',
'12' => 'First-Class Postcard Stamped',
'13' => 'Express Mail Flat-Rate Envelope',
'16' => 'Priority Mail Flat-Rate Envelope',
'17' => 'Priority Mail Regular Flat-Rate Box',
'18' => 'Priority Mail Keys and IDs',
'19' => 'First-Class Keys and IDs',
'22' => 'Priority Mail Flat-Rate Large Box',
'23' => 'Express Mail Sunday/Holiday',
'25' => 'Express Mail Flat-Rate Envelope Sunday/Holiday',
'27' => 'Express Mail Flat-Rate Envelope Hold For Pickup',
'28' => 'Priority Mail Small Flat-Rate Box',
],
'international' => [
'1' => 'Express Mail International',
'2' => 'Priority Mail International',
'4' => 'Global Express Guaranteed (Document and Non-document)',
'5' => 'Global Express Guaranteed Document used',
'6' => 'Global Express Guaranteed Non-Document Rectangular shape',
'7' => 'Global Express Guaranteed Non-Document Non-Rectangular',
'8' => 'Priority Mail Flat Rate Envelope',
'9' => 'Priority Mail Flat Rate Box',
'10' => 'Express Mail International Flat Rate Envelope',
'11' => 'Priority Mail Flat Rate Large Box',
'12' => 'Global Express Guaranteed Envelope',
'13' => 'First Class Mail International Letters',
'14' => 'First Class Mail International Flats',
'15' => 'First Class Mail International Parcels',
'16' => 'Priority Mail Flat Rate Small Box',
'21' => 'Postcards',
],
];
public function __construct($options = [])
{
parent::__construct($options);
if (isset($options['username'])) {
$this->username = $options['username'];
}
if (isset($options['password'])) {
$this->password = $options['password'];
}
if (isset($options['username'])) {
$this->username = $options['username'];
}
if (isset($options['approved_codes'])) {
$this->approved_codes = $options['approved_codes'];
}
if (isset($options['request_adapter'])) {
$this->set_request_adapter($options['request_adapter']);
} else {
$this->set_request_adapter(new RateRequest\Get());
}
}
protected function prepare()
{
$packages = '';
$sequence_number = 0;
foreach ($this->shipment->getPackages() as $p) {
$sequence_number++;
/**
* RateV4Request / Package / Size
required once
Defined as follows:
REGULAR: Package dimensions are 12 or less;
LARGE: Any package dimension is larger than 12.
For example: <Size>REGULAR</Size>
string
whiteSpace=collapse
enumeration=LARGE
enumeration=REGULAR
*/
if ($p->getWidth() > 12 or $p->getLength() > 12 or $p->getHeight() > 12) {
$size = 'LARGE';
$container = 'RECTANGULAR';
} else {
$size = 'REGULAR';
$container = 'VARIABLE';
}
$packages .= '<Package ID="' . $sequence_number .'">
<Service>ALL</Service>
<ZipOrigination>' . $this->shipment->getFromPostalCode() . '</ZipOrigination>
<ZipDestination>' . $this->shipment->getToPostalCode() . '</ZipDestination>
<Pounds>' . $p->getWeight() . '</Pounds>
<Ounces>0</Ounces>
<Container>' . $container . '</Container>
<Size>' . $size . '</Size>
<Width>' . $p->getWidth() . '</Width>
<Length>' . $p->getLength() . '</Length>
<Height>' . $p->getHeight() . '</Height>
<Machinable>' . 'False' . '</Machinable>
</Package>';
}
$this->data =
'<RateV4Request USERID="' . $this->username . '">
<Revision/>
' . $packages . '
</RateV4Request>';
return $this;
}
protected function execute()
{
if ($this->is_prod) {
$url = $this->url_prod;
} else {
$url = $this->url_dev;
}
$url_request = $url . '?API=RateV4&XML=' . rawurlencode($this->data);
$this->response = $this->rate_request->execute($url_request);
return $this;
}
protected function process()
{
try {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadXml($this->response);
$postage_list = @$dom->getElementsByTagName('Postage');
if (empty($postage_list)) {
throw new Exception('Unable to get USPS Rates.');
}
} catch (Exception $e) {
// StatsD::increment('error.shipping.get_usps_rate');
// Kohana::$log->add(Log::ERROR, $e)->write();
throw $e;
}
/** @var Quote[] $rates */
$rates = [];
foreach ($postage_list as $postage) {
$code = @$postage->getAttribute('CLASSID');
$cost = @$postage->getElementsByTagName('Rate')->item(0)->nodeValue;
$name = Arr::get($this->shipping_codes['domestic'], $code);
if ( ! empty($this->approved_codes) AND ! in_array($code, $this->approved_codes)) {
continue;
}
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;
}
private $urlDev = 'http://production.shippingapis.com/ShippingAPI.dll';
private $urlProd = 'http://production.shippingapis.com/ShippingAPI.dll';
private $username = 'XXX';
private $password = 'XXX';
public $approvedCodes = [
'1',
'4',
];
private $shipping_codes = [
'domestic' => [
'00' => 'First-Class Mail Parcel',
'01' => 'First-Class Mail Large Envelope',
'02' => 'First-Class Mail Letter',
'03' => 'First-Class Mail Postcards',
'1' => 'Priority Mail',
'2' => 'Express Mail Hold for Pickup',
'3' => 'Express Mail',
'4' => 'Parcel Post', // Standard Post
'5' => 'Bound Printed Matter',
'6' => 'Media Mail',
'7' => 'Library',
'12' => 'First-Class Postcard Stamped',
'13' => 'Express Mail Flat-Rate Envelope',
'16' => 'Priority Mail Flat-Rate Envelope',
'17' => 'Priority Mail Regular Flat-Rate Box',
'18' => 'Priority Mail Keys and IDs',
'19' => 'First-Class Keys and IDs',
'22' => 'Priority Mail Flat-Rate Large Box',
'23' => 'Express Mail Sunday/Holiday',
'25' => 'Express Mail Flat-Rate Envelope Sunday/Holiday',
'27' => 'Express Mail Flat-Rate Envelope Hold For Pickup',
'28' => 'Priority Mail Small Flat-Rate Box',
],
'international' => [
'1' => 'Express Mail International',
'2' => 'Priority Mail International',
'4' => 'Global Express Guaranteed (Document and Non-document)',
'5' => 'Global Express Guaranteed Document used',
'6' => 'Global Express Guaranteed Non-Document Rectangular shape',
'7' => 'Global Express Guaranteed Non-Document Non-Rectangular',
'8' => 'Priority Mail Flat Rate Envelope',
'9' => 'Priority Mail Flat Rate Box',
'10' => 'Express Mail International Flat Rate Envelope',
'11' => 'Priority Mail Flat Rate Large Box',
'12' => 'Global Express Guaranteed Envelope',
'13' => 'First Class Mail International Letters',
'14' => 'First Class Mail International Flats',
'15' => 'First Class Mail International Parcels',
'16' => 'Priority Mail Flat Rate Small Box',
'21' => 'Postcards',
],
];
public function __construct($options = [])
{
parent::__construct($options);
$this->username = Arr::get($options, 'username');
$this->password = Arr::get($options, 'password');
$this->approvedCodes = Arr::get($options, 'approvedCodes');
$this->setRequestAdapter(Arr::get($options, 'requestAdapter', new RateRequest\Get()));
}
protected function prepare()
{
$packages = '';
$sequence_number = 0;
foreach ($this->shipment->getPackages() as $p) {
$sequence_number++;
/**
* RateV4Request / Package / Size
* required once
* Defined as follows:
*
* REGULAR: Package dimensions are 12 or less;
* LARGE: Any package dimension is larger than 12.
*
* For example: <Size>REGULAR</Size>
* string
* whiteSpace=collapse
* enumeration=LARGE
* enumeration=REGULAR
*/
if ($p->getWidth() > 12 or $p->getLength() > 12 or $p->getHeight() > 12) {
$size = 'LARGE';
$container = 'RECTANGULAR';
} else {
$size = 'REGULAR';
$container = 'VARIABLE';
}
$packages .=
'<Package ID="' . $sequence_number . '">' .
'<Service>ALL</Service>' .
'<ZipOrigination>' . $this->shipment->getFromPostalCode() . '</ZipOrigination>' .
'<ZipDestination>' . $this->shipment->getToPostalCode() . '</ZipDestination>' .
'<Pounds>' . $p->getWeight() . '</Pounds>' .
'<Ounces>0</Ounces>' .
'<Container>' . $container . '</Container>' .
'<Size>' . $size . '</Size>' .
'<Width>' . $p->getWidth() . '</Width>' .
'<Length>' . $p->getLength() . '</Length>' .
'<Height>' . $p->getHeight() . '</Height>' .
'<Machinable>' . 'False' . '</Machinable>' .
'</Package>';
}
$this->data =
'<RateV4Request USERID="' . $this->username . '">' .
'<Revision/>' .
$packages .
'</RateV4Request>';
return $this;
}
protected function execute()
{
if ($this->isProduction) {
$url = $this->urlProd;
} else {
$url = $this->urlDev;
}
$url_request = $url . '?API=RateV4&XML=' . rawurlencode($this->data);
$this->response = $this->rateRequest->execute($url_request);
return $this;
}
protected function process()
{
try {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->loadXml($this->response);
$postage_list = @$dom->getElementsByTagName('Postage');
if (empty($postage_list)) {
throw new Exception('Unable to get USPS Rates.');
}
} catch (Exception $e) {
throw $e;
}
/** @var Quote[] $rates */
$rates = [];
foreach ($postage_list as $postage) {
$code = @$postage->getAttribute('CLASSID');
$cost = @$postage->getElementsByTagName('Rate')->item(0)->nodeValue;
$name = Arr::get($this->shipping_codes['domestic'], $code);
if (!empty($this->approvedCodes) && !in_array($code, $this->approvedCodes)) {
continue;
}
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
use pdt256\Shipping\Package;
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;
namespace pdt256\Shipping;
class ShipTest extends PHPUnit_Framework_TestCase
{
/** @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);
use DateTime;
$nextdaysaver = new Quote;
$nextdaysaver
->setCarrier('ups')
->setCode('13')
->setName('UPS Next Day Air Saver')
->setCost(8900);
$nextdayair = new Quote;
$nextdayair
->setCarrier('ups')
->setCode('01')
->setName('UPS Next Day Air')
->setCost(9300);
$expected_return = [$ground, $twodayair, $nextdaysaver, $nextdayair];
$this->assertEquals($expected_return, $ups_rates);
}
public function testFedexRate()
{
$fedex = new Fedex\Rate($this->getFedexOptions());
$fedex_rates = $fedex->get_rates();
$ground = new Quote;
$ground
->setCarrier('fedex')
->setCode('GROUND_HOME_DELIVERY')
->setName('Ground Home Delivery')
->setCost(1600)
->setTransitTime('THREE_DAYS');
$express = new Quote;
$express
->setCarrier('fedex')
->setCode('FEDEX_EXPRESS_SAVER')
->setName('Fedex Express Saver')
->setCost(2900)
->setDeliveryEstimate(new DateTime('2014-09-30T20:00:00'))
->setTransitTime(null);
$secondday = new Quote;
$secondday
->setCarrier('fedex')
->setCode('FEDEX_2_DAY')
->setName('Fedex 2 Day')
->setCost(4000)
->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00'))
->setTransitTime(null);
$overnight = new Quote;
$overnight
->setCarrier('fedex')
->setCode('STANDARD_OVERNIGHT')
->setName('Standard Overnight')
->setCost(7800)
->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00'))
->setTransitTime(null);
$expected_result = [$ground, $express, $secondday, $overnight];
$this->assertEquals($expected_result, $fedex_rates);
}
public function testDisplayOptions()
{
$rates = [];
$usps = new USPS\Rate($this->getUSPSOptions());
$rates['usps'] = $usps->get_rates();
$ups = new UPS\Rate($this->getUPSOptions());
$rates['ups'] = $ups->get_rates();
$fedex = new Fedex\Rate($this->getFedexOptions());
$rates['fedex'] = $fedex->get_rates();
$ship = Ship::factory($this->shipping_options);
$display_rates = $ship->get_display_rates($rates);
$post = new Quote;
$post->setCode(4)
->setName('Parcel Post')
->setCost(1001)
->setCarrier('usps');
$fedex_two_day = new Quote;
$fedex_two_day->setCode('FEDEX_2_DAY')
->setName('Fedex 2 Day')
->setCost(4000)
->setDeliveryEstimate(new DateTime('2014-09-29T20:00:00'))
->setCarrier('fedex');
$overnight = new Quote;
$overnight->setCode('STANDARD_OVERNIGHT')
->setName('Standard Overnight')
->setCost(7800)
->setDeliveryEstimate(new DateTime('2014-09-26T20:00:00'))
->setCarrier('fedex');
$this->assertEquals([
'Standard Shipping' => [
$post,
],
'Two-Day Shipping' => [
$fedex_two_day,
],
'One-Day Shipping' => [
$overnight,
],
], $display_rates);
}
class ShipTest extends \PHPUnit_Framework_TestCase
{
/** @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')
->setToIsResidential(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);
$approvedCodes = $ship->getApprovedCodes('usps');
return [
'prod' => false,
'username' => 'XXXX',
'password' => 'XXXX',
'shipment' => $this->shipment,
'approvedCodes' => $approvedCodes,
'requestAdapter' => new RateRequest\StubUSPS(),
];
}
private function getUPSOptions()
{
$ship = Ship::factory($this->shipping_options);
$approvedCodes = $ship->getApprovedCodes('ups');
return [
'prod' => false,
'accessKey' => 'XXXX',
'userId' => 'XXXX',
'password' => 'XXXX',
'shipperNumber' => 'XXXX',
'shipment' => $this->shipment,
'approvedCodes' => $approvedCodes,
'requestAdapter' => new RateRequest\StubUPS(),
];
}
private function getFedexOptions()
{
$ship = Ship::factory($this->shipping_options);
$approvedCodes = $ship->getApprovedCodes('fedex');
return [
'prod' => false,
'key' => 'XXXX',
'password' => 'XXXX',
'account_number' => 'XXXX',
'meter_number' => 'XXXX',
'drop_off_type' => 'BUSINESS_SERVICE_CENTER',
'shipment' => $this->shipment,
'approvedCodes' => $approvedCodes,
'requestAdapter' => new RateRequest\StubFedex(),
];
}
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