feat(shipping): carrier tracking with inbound webhooks and scheduled sync - #657
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
POST /store/webhooks/shipping/{driver}endpoint mirroring the payment webhook pattern. The driver authenticates the request inhandleWebhook(Request): ?TrackingInfoand returns a normalized timeline. Unknown, unconfigured or webhook-less drivers get a uniform 404, invalid payloads a 400.shopper:shipments:sync-trackingcommand scheduled every thirty minutes. It queues one uniqueSyncShipmentTrackingJobper open shipment whose configured carrier supports tracking and does not push webhooks. Configurable throughshopper.shipping.tracking(sync toggle, queue, backoff).ApplyTrackingInfoActionresolves 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
ShipmentStatus::rank(): out of order scans, duplicates and same-rank corrections are absorbed while the strict transition graph stays in place for manual actions.order_shipping_events.external_idcolumn with a unique index per shipment, plus a stable content fingerprint for carriers that do not send per event ids.RecordShipmentEventActionis 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 pastPickedUp.Order lifecycle
OrderPaidlistener runs the same check, so an order paid after delivery (COD, deferred payment) completes as well, and auto-completion now dispatchesOrderCompleted.OrderShipmentCreated,OrderShipmentEventRecorded,OrderShipmentDeliveryFailed,OrderShipmentReturned, all dispatched after commit, plus outgoing webhook eventsshipment.created,shipment.delivered,shipment.delivery_failedandshipment.returned.source(manual or carrier) and the acting user, so the audit trail can answer who marked a shipment delivered.CreateShipmentActioncentralizes shipment creation and refuses to create an empty shipment when the requested items were already attached elsewhere.Manager cleanup
ShippingManager,PaymentManager,ChannelManagerandImportManagernow extendIlluminate\Support\Managerinstead of re-implementing driver caching and registration by hand. Built-in drivers resolve through the standardcreate{Driver}convention.Breaking changes
ShippingDrivergainssupportsWebhooks(): boolandhandleWebhook(Request): ?TrackingInfo. The baseDriverprovides safe defaults (false, not supported).Driver::supportsTracking()now defaults tofalse; drivers that implementtrack()must opt in (UPS and FedEx do).TrackingInfo::statusandTrackingEvent::statusare typedShipmentStatus, andTrackingEventgainsexternalId,latitudeandlongitude.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: callrefresh()if you read the shipment afterwards.shopper/webhooks.phpconfig needs the fourshipment.*entries added to itseventsmap.SHIPPING_TRACKING_SYNC=falseto opt out.