From c024200a1f9ae367fdfe206938e85ac71faa8620 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 20:16:17 +0000 Subject: [PATCH 1/2] Move customerNumber from request root into each consignment's product MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring's Booking API expects customerNumber inside consignments[].product.customerNumber. The SDK was emitting it at the request root, which Bring's gateway silently ignores — it reads from the product slot, finds nothing, and rejects every consignment with BOOK-INPUT-019 "Customer number must be provided". The constructor invariant (non-empty customerNumber) prevented the local failure but couldn't help the wire shape. The public SDK shape is unchanged: customerNumber stays a single request-level field on BookingRequest. At toArray() time we inject it into each consignment's product object, so callers keep modelling it once even though Bring's schema repeats it per consignment. The existing BookingApiTest only checked the (incorrect) root placement against a mock client, so the bug went unnoticed. The assertion is updated to pin the field to consignments[0].product and to assert it is NOT present at the root, locking in the correct shape against the live API. Confirmed against https://developer.bring.com/api/booking/ — schema lists customerNumber as a required property of the product object inside each consignment. --- src/v4/Endpoint/Booking/BookingRequest.php | 27 ++++++++++++++++---- tests/v4/Endpoint/Booking/BookingApiTest.php | 3 ++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/v4/Endpoint/Booking/BookingRequest.php b/src/v4/Endpoint/Booking/BookingRequest.php index 9df8f77..bcdbdd5 100644 --- a/src/v4/Endpoint/Booking/BookingRequest.php +++ b/src/v4/Endpoint/Booking/BookingRequest.php @@ -70,14 +70,31 @@ public static function single( /** @return array */ public function toArray(): array { + // Bring's Booking API expects customerNumber inside each + // consignment's product object — not at the request root. Posting + // it at the root produces a per-consignment BOOK-INPUT-019 + // ("Customer number must be provided") even though the SDK + // constructor enforces a non-empty value, because Bring reads it + // from the product slot and finds nothing there. + // + // We model customerNumber once on the request (it's per-request in + // practice) and inject it into each consignment's product at + // serialization time, so the public SDK shape stays unchanged. + $customerNumber = $this->customerNumber; + $consignments = array_map( + static function (Consignment $c) use ($customerNumber): array { + $arr = $c->toArray(); + $arr['product'] = ['customerNumber' => $customerNumber] + (array) $arr['product']; + + return $arr; + }, + $this->consignments, + ); + return [ 'schemaVersion' => $this->schemaVersion, - 'consignments' => array_map( - static fn (Consignment $c): array => $c->toArray(), - $this->consignments, - ), + 'consignments' => $consignments, 'testIndicator' => $this->testIndicator, - 'customerNumber' => $this->customerNumber, ]; } } diff --git a/tests/v4/Endpoint/Booking/BookingApiTest.php b/tests/v4/Endpoint/Booking/BookingApiTest.php index 500c1ed..5c74c72 100644 --- a/tests/v4/Endpoint/Booking/BookingApiTest.php +++ b/tests/v4/Endpoint/Booking/BookingApiTest.php @@ -64,7 +64,8 @@ public function testBookSerialisesAndPostsToCorrectUrl(): void $body = json_decode((string) $req->getBody(), true); self::assertSame('1', $body['schemaVersion']); - self::assertSame('PARCELS_NORWAY-10001234567', $body['customerNumber']); + self::assertArrayNotHasKey('customerNumber', $body, 'customerNumber must not be at request root — Bring expects it under consignments[].product'); + self::assertSame('PARCELS_NORWAY-10001234567', $body['consignments'][0]['product']['customerNumber']); self::assertTrue($body['testIndicator']); self::assertSame(Product::HOME_DELIVERY_PARCEL->value, $body['consignments'][0]['product']['id']); self::assertSame('EVARSLING', $body['consignments'][0]['product']['additionalServices'][0]['id']); From 268ce2ee7531d372cc80790c932c343249acd4b8 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 20:17:45 +0000 Subject: [PATCH 2/2] Address review: drop array union operator in toArray injection --- src/v4/Endpoint/Booking/BookingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v4/Endpoint/Booking/BookingRequest.php b/src/v4/Endpoint/Booking/BookingRequest.php index bcdbdd5..a022d27 100644 --- a/src/v4/Endpoint/Booking/BookingRequest.php +++ b/src/v4/Endpoint/Booking/BookingRequest.php @@ -84,7 +84,7 @@ public function toArray(): array $consignments = array_map( static function (Consignment $c) use ($customerNumber): array { $arr = $c->toArray(); - $arr['product'] = ['customerNumber' => $customerNumber] + (array) $arr['product']; + $arr['product']['customerNumber'] = $customerNumber; return $arr; },