// Package shipping is the platform's shared shipping seam: the cross-module // vocabulary for delivery options and the Provider interface. It holds only the // wire value types plus the interface, so the producer (a shipping adapter like // PostNord) and every consumer (checkout, cart, backoffice) agree on the types. // It imports only the stdlib — never an app module, never a transport library. // // Dependency rule: packages under platform/ import only third-party libraries // and other platform/ packages — never an application module. Application // modules depend downward onto platform. This is what keeps it a stable seam. // // See platform/inventory for the sibling pattern (value types + interface in // one stdlib-only package, test adapter in _test.go). package shipping import ( "context" "encoding/json" "errors" ) // ── Provider interface ───────────────────────────────────────────────────── // Provider is the cross-module seam for shipping delivery options. Checkout // calls GetShippingOptions to present available delivery methods to the // customer. Every implementation behind this interface is free to choose its // internal provider (PostNord, DHL, Bring, etc.) — the consumer never needs // to know. // // This is the policy-first seam: shipping selection policy lives here; the // provider-specific HTTP/REST details live behind it. type Provider interface { // GetShippingOptions returns available delivery options for a recipient // address. Returns an empty slice (not an error) when the provider has no // matching options. Returns ErrNoOptions when no provider in a multi-provider // setup could serve the address. GetShippingOptions(ctx context.Context, req GetOptionsRequest) ([]DeliveryOptionGroup, error) } // ── Request types ───────────────────────────────────────────────────────── // GetOptionsRequest is what a caller provides to get shipping options. type GetOptionsRequest struct { // Country is the ISO 3166-1 alpha-2 recipient country (e.g. "SE", "NO"). Country string `json:"country"` // PostCode is the recipient postal code. PostCode string `json:"post_code"` // Locale is the IETF BCP 47 language tag for descriptions (e.g. "sv", "en"). // Empty means the provider chooses a default. Locale string `json:"locale,omitempty"` // Items optionally describes the cart items for dimension/weight-based // filtering. May be nil or empty for providers that don't need item data. Items []OptionItem `json:"items,omitempty"` } // OptionItem describes one line item for shipping calculations (weight, volume, // category-based restrictions). type OptionItem struct { SKU string `json:"sku"` Quantity int `json:"quantity"` // WeightGrams is the item weight in grams, if known. WeightGrams int `json:"weight_grams,omitempty"` } // ── Response types ──────────────────────────────────────────────────────── // DeliveryOptionGroup groups shipping options by fulfilment method (e.g. home // delivery, pickup point, parcel locker). A provider returns one or more // groups; each group has one default option and optional alternatives (e.g. // different pickup point locations). type DeliveryOptionGroup struct { // Type is the fulfilment-method category: "home", "pickup", "locker", // "mailbox", etc. Type string `json:"type"` // Name is the display name for the group (e.g. "Home delivery", // "Pickup point"). Provider-localised to the request Locale. Name string `json:"name"` // DefaultOption is the recommended option within this group. DefaultOption DeliveryOption `json:"default_option"` // AdditionalOptions are alternatives within the same group (e.g. other // pickup point locations, different time slots). AdditionalOptions []DeliveryOption `json:"additional_options,omitempty"` } // DeliveryOption is one selectable shipping method or service point. type DeliveryOption struct { // ID is the provider-scoped unique identifier for this option. Used when // the caller confirms the selection (e.g. SetCartDelivery). ID string `json:"id"` // Name is a short display name (e.g. "Home delivery", "PostNord Storgatan"). Name string `json:"name"` // Description is a longer description for the checkout UI. Description string `json:"description,omitempty"` // DeliveryDays is a human-readable estimate (e.g. "2-3 working days", // "1-2 days"). Provider-localised. DeliveryDays string `json:"delivery_days,omitempty"` // PickupLocation is set when this option is a physical pickup point. PickupLocation *PickupLocation `json:"pickup_location,omitempty"` } // PickupLocation describes a physical pickup point (store, locker, post office). type PickupLocation struct { ID string `json:"id"` Name string `json:"name"` Address Address `json:"address"` } // Address is a simplified postal address for delivery or pickup. type Address struct { Street string `json:"street"` Street2 string `json:"street2,omitempty"` City string `json:"city"` PostCode string `json:"post_code"` Country string `json:"country"` // ISO 3166-1 alpha-2 } // ── Sentinels ───────────────────────────────────────────────────────────── // ErrNoOptions is returned by a multi-provider orchestrator when no registered // provider could serve the given address. A single provider returns an empty // slice (not this error) for an unsupported address. var ErrNoOptions = errors.New("shipping: no delivery options available") // ErrProviderUnavailable is returned when a provider's API is unreachable or // returns a non-retryable error. The caller may fall back to another provider // or degrade gracefully. var ErrProviderUnavailable = errors.New("shipping: provider unavailable") // ── Wire helpers ────────────────────────────────────────────────────────── // Encode serialises a DeliveryOptionGroup slice for caching or event payloads. func Encode(groups []DeliveryOptionGroup) ([]byte, error) { return json.Marshal(groups) } // Decode parses a DeliveryOptionGroup slice from Encode output. func Decode(raw []byte) ([]DeliveryOptionGroup, error) { var out []DeliveryOptionGroup err := json.Unmarshal(raw, &out) return out, err }