From 24f3835730465ccd1425be278923202fbdf74114 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 20:42:59 +0000 Subject: [PATCH 1/2] Send numeric service code in product.id for Booking API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring's Booking API expects the numeric service code in product.id (e.g. "5000" for BUSINESS_PARCEL, "9000" for BUSINESS_PARCEL_RETURN) — the same codes the v3 SDK used. The v4 SDK was sending the string enum value (BUSINESS_PARCEL), which Bring's gateway looks up in the international catalog and rejects Norway→Norway routes with BOOK-INPUT-025 ("product not available between the given countries") plus BOOK_VALIDATION-014 ("customs declarations required for exporting from Norway"), even though both parties are NO. This was an unnoticed v3→v4 regression — the existing BookingApiTest only checked the incorrect shape against a mock client. The Shipping Guide path is unchanged: PriceRequest::toArray keeps using the string Product enum value, which is correct for the v2 Shipping Guide endpoint. Implementation notes: - New Product::bookingProductId(): returns the numeric code as string for products with a known mapping, falls back to the enum value otherwise. Keeps unknown products at least as functional as today (Bring accepts strings for some newer products). - BookingProduct::toArray() now calls bookingProductId() instead of ->value. - BookingApiTest switched to BUSINESS_PARCEL and pins the serialized id to "5000". Mapped service codes (from the v3 numeric catalog Bring still accepts in v4 Booking): PICKUP_PARCEL 5800, PICKUP_PARCEL_BULK 5802, BUSINESS_PARCEL 5000, BUSINESS_PARCEL_BULK 5100, EXPRESS_NORDIC_0900 4850, MAILBOX_PARCEL 3584, MAILBOX_PARCEL_TRACKED 3570, CARGO_GROUPAGE 5300, RETURN_PICKUP_PARCEL 9300, RETURN_BUSINESS_PARCEL 9000, RETURN_BUSINESS_PALLET 9100. --- src/v4/Endpoint/Booking/BookingProduct.php | 6 +++- src/v4/Enum/Product.php | 36 ++++++++++++++++++++ tests/v4/Endpoint/Booking/BookingApiTest.php | 6 ++-- 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/v4/Endpoint/Booking/BookingProduct.php b/src/v4/Endpoint/Booking/BookingProduct.php index 6e38752..581ac04 100644 --- a/src/v4/Endpoint/Booking/BookingProduct.php +++ b/src/v4/Endpoint/Booking/BookingProduct.php @@ -24,7 +24,11 @@ public function __construct( /** @return array */ public function toArray(): array { - $a = ['id' => $this->id->value]; + // Booking API wants the numeric service code in product.id, not the + // string enum value the Shipping Guide uses (see Product::bookingProductId + // for the why). Falls back to the string value for products without a + // known numeric mapping. + $a = ['id' => $this->id->bookingProductId()]; if ($this->additionalServices !== []) { $a['additionalServices'] = array_map( static fn (AdditionalService $s): array => ['id' => $s->value], diff --git a/src/v4/Enum/Product.php b/src/v4/Enum/Product.php index 2459b2d..73ca9f8 100644 --- a/src/v4/Enum/Product.php +++ b/src/v4/Enum/Product.php @@ -56,4 +56,40 @@ public function legacyNumericCode(): ?int default => null, }; } + + /** + * Product identifier as Bring's Booking API expects it in product.id. + * + * The Booking API takes numeric service codes (e.g. "5000" for + * BUSINESS_PARCEL, "9000" for BUSINESS_PARCEL_RETURN) — the same + * codes the v3 SDK used. Sending the v2 Shipping-Guide string name + * (BUSINESS_PARCEL, …) makes Bring's gateway look it up in the + * international catalog and reject Norway→Norway routes with + * BOOK-INPUT-025 / BOOK_VALIDATION-014 ("product not available + * between the given countries" / "customs declarations required for + * exporting from Norway"), even though the parties are both NO. + * + * For products with no known numeric mapping we fall back to the + * enum value — Bring accepts strings for some newer products and + * this keeps unknown cases at least as functional as today. + */ + public function bookingProductId(): string + { + $numeric = match ($this) { + self::PICKUP_PARCEL => 5800, + self::PICKUP_PARCEL_BULK => 5802, + self::BUSINESS_PARCEL => 5000, + self::BUSINESS_PARCEL_BULK => 5100, + self::EXPRESS_NORDIC_0900 => 4850, + self::MAILBOX_PARCEL => 3584, + self::MAILBOX_PARCEL_TRACKED => 3570, + self::CARGO_GROUPAGE => 5300, + self::RETURN_PICKUP_PARCEL => 9300, + self::RETURN_BUSINESS_PARCEL => 9000, + self::RETURN_BUSINESS_PALLET => 9100, + default => null, + }; + + return $numeric === null ? $this->value : (string) $numeric; + } } diff --git a/tests/v4/Endpoint/Booking/BookingApiTest.php b/tests/v4/Endpoint/Booking/BookingApiTest.php index 5c74c72..935c21e 100644 --- a/tests/v4/Endpoint/Booking/BookingApiTest.php +++ b/tests/v4/Endpoint/Booking/BookingApiTest.php @@ -47,7 +47,7 @@ public function testBookSerialisesAndPostsToCorrectUrl(): void $request = BookingRequest::single( schemaVersion: '1', customerNumber: 'PARCELS_NORWAY-10001234567', - product: Product::HOME_DELIVERY_PARCEL, + product: Product::BUSINESS_PARCEL, sender: $sender, recipient: $recipient, packages: [new Package(weightInKg: 2)], @@ -67,7 +67,9 @@ public function testBookSerialisesAndPostsToCorrectUrl(): void 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']); + // Booking API expects the numeric service code, not the string enum + // value (which the Shipping Guide uses). BUSINESS_PARCEL → "5000". + self::assertSame('5000', $body['consignments'][0]['product']['id']); self::assertSame('EVARSLING', $body['consignments'][0]['product']['additionalServices'][0]['id']); self::assertSame('Sender Co', $body['consignments'][0]['parties']['sender']['name']); self::assertSame('NO', $body['consignments'][0]['parties']['recipient']['countryCode']); From c2dc9bf588d348cc9d0791e942e0bb407c17f6c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 1 Jun 2026 20:45:44 +0000 Subject: [PATCH 2/2] Document gap for unmapped Booking product codes (per review) --- src/v4/Enum/Product.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/v4/Enum/Product.php b/src/v4/Enum/Product.php index 73ca9f8..d89990a 100644 --- a/src/v4/Enum/Product.php +++ b/src/v4/Enum/Product.php @@ -69,9 +69,16 @@ public function legacyNumericCode(): ?int * between the given countries" / "customs declarations required for * exporting from Norway"), even though the parties are both NO. * - * For products with no known numeric mapping we fall back to the - * enum value — Bring accepts strings for some newer products and - * this keeps unknown cases at least as functional as today. + * The mapping below covers the products with codes confirmed against + * the v3 SDK catalog Bring still accepts in v4. Products NOT in this + * match (e.g. HOME_DELIVERY_PARCEL, BUSINESS_PALLET, the home/express + * return variants) fall back to the enum string value — Bring + * accepts strings for some products and the fallback keeps unknown + * cases at least as functional as today. If you hit BOOK-INPUT-025 + * on a product handled by the fallback, look up its numeric code in + * Mybring (Customer numbers → service product line) and add it + * here. Do not guess: a wrong numeric code silently books the wrong + * product, while the string fallback at worst surfaces a 4xx. */ public function bookingProductId(): string {