Skip to content

feat(shipping): carrier tracking with inbound webhooks and scheduled sync - #657

Merged
mckenziearts merged 5 commits into
3.xfrom
feat/carrier-tracking
Sep 2, 2026
Merged

feat(shipping): carrier tracking with inbound webhooks and scheduled sync#657
mckenziearts merged 5 commits into
3.xfrom
feat/carrier-tracking

Conversation

@mckenziearts

Copy link
Copy Markdown
Collaborator

What

Shipment tracking now works end to end with real carriers. Until now ShippingDriver::track() had no caller, the shipment state machine only accepted strict manual transitions, and a carrier had no way to push a delivery update into Shopper: every integration had to hand write its own polling command and event ingestion.

Carrier ingestion

  • New POST /store/webhooks/shipping/{driver} endpoint mirroring the payment webhook pattern. The driver authenticates the request in handleWebhook(Request): ?TrackingInfo and returns a normalized timeline. Unknown, unconfigured or webhook-less drivers get a uniform 404, invalid payloads a 400.
  • New shopper:shipments:sync-tracking command scheduled every thirty minutes. It queues one unique SyncShipmentTrackingJob per open shipment whose configured carrier supports tracking and does not push webhooks. Configurable through shopper.shipping.tracking (sync toggle, queue, backoff).
  • ApplyTrackingInfoAction resolves the shipment from the carrier reference (driver + tracking number). Ambiguous matches are refused and logged instead of guessed, whether the candidates are open or already closed.

Lenient state machine for carrier feeds

  • Carrier events use a rank based, forward only mode built on ShipmentStatus::rank(): out of order scans, duplicates and same-rank corrections are absorbed while the strict transition graph stays in place for manual actions.
  • Deduplication relies on a new order_shipping_events.external_id column with a unique index per shipment, plus a stable content fingerprint for carriers that do not send per event ids.
  • RecordShipmentEventAction is the single write path: shipment and order rows are locked, timestamps are stamped once, pickup side effects run even when the first scan is already past PickedUp.

Order lifecycle

  • Delivery completes the order only when every item is delivered and the order is paid. A new OrderPaid listener runs the same check, so an order paid after delivery (COD, deferred payment) completes as well, and auto-completion now dispatches OrderCompleted.
  • New domain events: OrderShipmentCreated, OrderShipmentEventRecorded, OrderShipmentDeliveryFailed, OrderShipmentReturned, all dispatched after commit, plus outgoing webhook events shipment.created, shipment.delivered, shipment.delivery_failed and shipment.returned.
  • Every shipment event now records its source (manual or carrier) and the acting user, so the audit trail can answer who marked a shipment delivered.
  • CreateShipmentAction centralizes shipment creation and refuses to create an empty shipment when the requested items were already attached elsewhere.

Manager cleanup

ShippingManager, PaymentManager, ChannelManager and ImportManager now extend Illuminate\Support\Manager instead of re-implementing driver caching and registration by hand. Built-in drivers resolve through the standard create{Driver} convention.

Breaking changes

  • ShippingDriver gains supportsWebhooks(): bool and handleWebhook(Request): ?TrackingInfo. The base Driver provides safe defaults (false, not supported).
  • Driver::supportsTracking() now defaults to false; drivers that implement track() must opt in (UPS and FedEx do).
  • TrackingInfo::status and TrackingEvent::status are typed ShipmentStatus, and TrackingEvent gains externalId, latitude and longitude.
  • Custom manager creators registered through extend() now receive the container instead of the driver name, and closures are rebound to the manager, matching the framework convention.
  • RecordShipmentEventAction::execute() no longer mutates the passed instance in place: call refresh() if you read the shipment afterwards.
  • A published shopper/webhooks.php config needs the four shipment.* entries added to its events map.
  • The tracking sync scheduler is enabled by default; set SHIPPING_TRACKING_SYNC=false to opt out.

@mckenziearts mckenziearts added this to the v3 milestone Sep 1, 2026
@mckenziearts mckenziearts added enhancement New feature or request php Pull requests that update php code labels Sep 1, 2026
@mckenziearts mckenziearts self-assigned this Sep 2, 2026
@mckenziearts mckenziearts moved this to In Progress in Roadmap Sep 2, 2026
@mckenziearts
mckenziearts merged commit 4f2b5fd into 3.x Sep 2, 2026
50 checks passed
@mckenziearts
mckenziearts deleted the feat/carrier-tracking branch September 2, 2026 11:19
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Roadmap Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request php Pull requests that update php code

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant