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
6 changes: 5 additions & 1 deletion src/v4/Endpoint/Booking/BookingProduct.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public function __construct(
/** @return array<string, mixed> */
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],
Expand Down
43 changes: 43 additions & 0 deletions src/v4/Enum/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,47 @@ 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.
*
* 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
{
$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,
};
Comment on lines +85 to +98
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Several common products and their return variants appear to be missing from the numeric mapping. Specifically, HOME_DELIVERY_PARCEL (5600), BUSINESS_PALLET (5200), and RETURN_HOME_DELIVERY_PARCEL (9600) are standard Bring products. Without these mappings, booking requests for these products will fall back to their string enum values, which will trigger the same BOOK-INPUT-025 / BOOK_VALIDATION-014 validation errors for domestic Norway shipments.

Adding these mappings ensures complete coverage for all standard products supported by the Booking API.

        $numeric = match ($this) {
            self::PICKUP_PARCEL => 5800,
            self::PICKUP_PARCEL_BULK => 5802,
            self::HOME_DELIVERY_PARCEL => 5600,
            self::BUSINESS_PARCEL => 5000,
            self::BUSINESS_PARCEL_BULK => 5100,
            self::BUSINESS_PALLET => 5200,
            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,
            self::RETURN_HOME_DELIVERY_PARCEL => 9600,
            default => null,
        };

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Holding off on these specific mappings. Cross-referencing against the v3 SDK's Crakter\BringApi\DefaultData\Products catalog (which is the source for the codes I did add), I can't confirm any of the three:

  • 9600 in v3 is RETUREKSPRESS (return-express, i.e. the return variant of EXPRESS_NORDIC_0900) — not RETURN_HOME_DELIVERY_PARCEL. Mapping it as the home-delivery return would silently book the wrong product.
  • 5200 doesn't appear in the v3 numeric catalog at all.
  • 5600 also doesn't appear there. (And a separate developer.bring.com lookup showed 5600 candidate as a "DISTRIBUTION" code — different again.)

A wrong numeric code silently books the wrong product; the current string fallback at worst surfaces a 4xx, which is the safer failure mode. Documented the gap on the method so the next person hitting BOOK-INPUT-025 on an unmapped product knows to look up the code in Mybring rather than guess — see c2dc9bf.

If you have authoritative codes for these from Bring's product catalog, happy to add them.


Generated by Claude Code


return $numeric === null ? $this->value : (string) $numeric;
}
}
6 changes: 4 additions & 2 deletions tests/v4/Endpoint/Booking/BookingApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)],
Expand All @@ -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']);
Expand Down