Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
authenticated UI via `<img src="…">`.

### Fixed
- `PriceRequest::toQuery()` now gives every package its own indexed query
parameters (`weightInGrams0`, `length0`, …, `weightInGrams1`, …) when a
request carries more than one package. The previous shape repeated the bare
key (`weightInGrams=1200&weightInGrams=800`), which Shipping Guide v2
collapses to a single value — so a 2-, 3- or 20-package request was priced
as one package and every quote came back identical regardless of package
count. A single-package request keeps the unsuffixed names it always used.
- `PriceRequest::toQuery()` now serialises the `product` query parameter
as Bring's numeric service code (via the new `Product::shippingGuideCode()`)
instead of the v2 string name. Shipping Guide v2 prices by numeric code,
Expand Down
19 changes: 14 additions & 5 deletions src/v4/Endpoint/Shipping/PriceRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,25 @@ public function toQuery(): array
'toPostalCode' => $this->toPostalCode,
];

foreach ($this->packages as $i => $pkg) {
$q['weightInGrams'][$i] = (int) $pkg['weightInGrams'];
// Shipping Guide v2 addresses each package by an index *suffix* on the
// parameter name (weightInGrams0, length0, ..., weightInGrams1, ...) —
// not by a repeated bare key. A repeated `weightInGrams` is collapsed to
// a single value server-side, so every multi-package request was priced
// as one package. A lone package keeps the unsuffixed names, which is
// what the API documents for the single-package case.
$packages = array_values($this->packages);
$multiple = \count($packages) > 1;
foreach ($packages as $i => $pkg) {
$suffix = $multiple ? (string) $i : '';
$q['weightInGrams'.$suffix] = (int) $pkg['weightInGrams'];
if (isset($pkg['length'])) {
$q['length'][$i] = (int) $pkg['length'];
$q['length'.$suffix] = (int) $pkg['length'];
}
if (isset($pkg['width'])) {
$q['width'][$i] = (int) $pkg['width'];
$q['width'.$suffix] = (int) $pkg['width'];
}
if (isset($pkg['height'])) {
$q['height'][$i] = (int) $pkg['height'];
$q['height'.$suffix] = (int) $pkg['height'];
}
}

Expand Down
43 changes: 43 additions & 0 deletions tests/v4/Endpoint/Shipping/PriceRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,47 @@ public function testNoProductParamWhenProductsEmpty(): void
{
self::assertArrayNotHasKey('product', $this->request()->toQuery());
}

public function testSinglePackageUsesUnsuffixedParameterNames(): void
{
$q = (new PriceRequest(
fromCountry: Country::NO,
fromPostalCode: '1712',
toCountry: Country::NO,
toPostalCode: '0150',
packages: [['weightInGrams' => 1000, 'length' => 30, 'width' => 20, 'height' => 15]],
))->toQuery();

self::assertSame(1000, $q['weightInGrams']);
self::assertSame(30, $q['length']);
self::assertSame(20, $q['width']);
self::assertSame(15, $q['height']);
}

public function testEachPackageGetsItsOwnIndexedParameters(): void
{
// Bring indexes multi-package requests by suffixing the parameter name.
// Repeating a bare `weightInGrams` loses every package but one, which is
// what made the price identical for 1, 2 and 20 packages.
$q = (new PriceRequest(
fromCountry: Country::NO,
fromPostalCode: '1712',
toCountry: Country::NO,
toPostalCode: '0150',
packages: [
['weightInGrams' => 1000, 'length' => 30, 'width' => 20, 'height' => 15],
['weightInGrams' => 2500],
['weightInGrams' => 400],
],
))->toQuery();

self::assertSame(1000, $q['weightInGrams0']);
self::assertSame(2500, $q['weightInGrams1']);
self::assertSame(400, $q['weightInGrams2']);
self::assertSame(30, $q['length0']);
self::assertSame(20, $q['width0']);
self::assertSame(15, $q['height0']);
self::assertArrayNotHasKey('weightInGrams', $q);
self::assertArrayNotHasKey('length1', $q);
}
}
31 changes: 26 additions & 5 deletions tests/v4/Http/TransportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ public function testGuzzleBadResponseExceptionSurfacesAsBringApiException(): voi
}
}

public function testShippingPriceQueryBuildsBareRepeatedKeys(): void
public function testShippingPriceQueryIndexesEachPackage(): void
{
$client = new RecordingClient([new Response(200, ['Content-Type' => 'application/json'], '{"Product":[]}')]);
$transport = new Transport($client, $this->factory, $this->factory, $this->factory, new NullAuthorization());
Expand All @@ -193,11 +193,32 @@ public function testShippingPriceQueryBuildsBareRepeatedKeys(): void
$transport->send(new PriceEndpoint($request));

$uri = (string) $client->lastRequest()->getUri();
// Repeated weightInGrams without [0]/[1] subscripts (Bring's expected format).
self::assertStringContainsString('weightInGrams=1200', $uri);
self::assertStringContainsString('weightInGrams=800', $uri);
// Shipping Guide v2 needs one suffixed parameter per package; a repeated
// bare `weightInGrams` is collapsed server-side and prices one package.
self::assertStringContainsString('weightInGrams0=1200', $uri);
self::assertStringContainsString('weightInGrams1=800', $uri);
self::assertStringNotContainsString('weightInGrams=', $uri);
self::assertStringNotContainsString('weightInGrams%5B0%5D=', $uri);
self::assertStringNotContainsString('weightInGrams[0]=', $uri);
}

public function testShippingPriceQueryKeepsBareKeysForASinglePackage(): void
{
$client = new RecordingClient([new Response(200, ['Content-Type' => 'application/json'], '{"Product":[]}')]);
$transport = new Transport($client, $this->factory, $this->factory, $this->factory, new NullAuthorization());

$request = new PriceRequest(
fromCountry: Country::NO,
fromPostalCode: '0150',
toCountry: Country::NO,
toPostalCode: '5003',
packages: [['weightInGrams' => 1200, 'length' => 30, 'width' => 20, 'height' => 15]],
);
$transport->send(new PriceEndpoint($request));

$uri = (string) $client->lastRequest()->getUri();
self::assertStringContainsString('weightInGrams=1200', $uri);
self::assertStringContainsString('length=30', $uri);
self::assertStringNotContainsString('weightInGrams0=', $uri);
}

public function testPostalCodeRequestUrl(): void
Expand Down