141 lines
6.6 KiB
Go
141 lines
6.6 KiB
Go
// 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
|
|
}
|