more platform
This commit is contained in:
1 parent
8a71e32149
commit
2a66754f9e
27 files changed
+2435
-540
No files matched your search
@@ -0,0 +1,140 @@
|
||||
// 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
|
||||
}
|
||||
Reference in new issue
Block a user