feat(orders): customer-facking tracking labels and webhook response body
Some checks failed
Deploy / deploy (push) Has been cancelled
Some checks failed
Deploy / deploy (push) Has been cancelled
the big colored status on the order detail page used to just say
"Shipped" the moment i bought the label, which didnt really feel right
since the carrier hadnt actually picked anything up yet. now it walks
through "Label created" -> "In transit" -> "Delivered" as real scans
come in, and keeps the "Delivery Issue" / "Returned to sender" labels
for unhappy paths
also wired up the existing tracking_status_details column to the tui. i
skipped it before but the response actually includes a description of
the delivery, that is now subtext under the colored statu.
and the shippo webhook now returns a small json body instead of an empty
200. this is just for the operator that runs the curls commands to
update the shipment, which makes debugging a lot less guesswork
This commit is contained in:
@@ -279,7 +279,11 @@ func (h *WebhookHandler) HandleShippo(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if payload.Event != "track_updated" {
|
||||
webhookLog().Info("shippo webhook: ignoring no-tracking event", "event", payload.Event)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
writeShippoResponse(w, map[string]any{
|
||||
"received": true,
|
||||
"skipped": "non-tracking event",
|
||||
"event": payload.Event,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -288,7 +292,11 @@ func (h *WebhookHandler) HandleShippo(w http.ResponseWriter, r *http.Request) {
|
||||
webhookLog().Warn("shippo webhook: unrecognized status, skipping",
|
||||
"status", payload.Data.TrackingStatus.Status,
|
||||
"tracking_number", payload.Data.TrackingNumber)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
writeShippoResponse(w, map[string]any{
|
||||
"received": true,
|
||||
"skipped": "unrecognized status",
|
||||
"raw_status": payload.Data.TrackingStatus.Status,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -297,12 +305,21 @@ func (h *WebhookHandler) HandleShippo(w http.ResponseWriter, r *http.Request) {
|
||||
if err := db.Where("tracking_number = ?", payload.Data.TrackingNumber).First(&order).Error; err != nil {
|
||||
webhookLog().Warn("shippo webhook: order not found",
|
||||
"tracking_number", payload.Data.TrackingNumber)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
writeShippoResponse(w, map[string]any{
|
||||
"received": true,
|
||||
"skipped": "order not found",
|
||||
"tracking_number": payload.Data.TrackingNumber,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if order.TrackingStatusUpdatedAt != nil && !payload.Data.TrackingStatus.StatusDate.After(*order.TrackingStatusUpdatedAt) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
writeShippoResponse(w, map[string]any{
|
||||
"received": true,
|
||||
"skipped": "stale status_date",
|
||||
"order_id": order.ID,
|
||||
"current_tracking_status": string(order.TrackingStatus),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -332,7 +349,15 @@ func (h *WebhookHandler) HandleShippo(w http.ResponseWriter, r *http.Request) {
|
||||
if deliveredTransition {
|
||||
audit.OrderDelivered(order.ID, payload.Data.TrackingNumber)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
writeShippoResponse(w, map[string]any{
|
||||
"received": true,
|
||||
"order_id": order.ID,
|
||||
"tracking_status": string(newStatus),
|
||||
"previous_tracking_status": string(previousTrackingStatus),
|
||||
"lifecycle_status": string(order.Status),
|
||||
"delivered_transition": deliveredTransition,
|
||||
"status_details": payload.Data.TrackingStatus.StatusDetails,
|
||||
})
|
||||
}
|
||||
|
||||
func mapShippoStatus(s string) (models.TrackingStatus, bool) {
|
||||
@@ -352,3 +377,9 @@ func mapShippoStatus(s string) (models.TrackingStatus, bool) {
|
||||
}
|
||||
return models.TrackingStatusUnknown, false
|
||||
}
|
||||
|
||||
func writeShippoResponse(w http.ResponseWriter, payload map[string]any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
|
||||
@@ -31,16 +31,16 @@ func (order *Order) DisplayState() string {
|
||||
case OrderStatusShipped:
|
||||
switch order.TrackingStatus {
|
||||
case TrackingStatusTransit:
|
||||
return "Shipped * in transit"
|
||||
return "In Transit"
|
||||
case TrackingStatusFailure:
|
||||
return "Shipped * delivery issue"
|
||||
return "Delivery Issue"
|
||||
case TrackingStatusReturned:
|
||||
return "Shipped * returned to sender"
|
||||
return "Returned to Sender"
|
||||
case TrackingStatusDelivered:
|
||||
// Webhook hasn't promoted Status yet, trust tracking
|
||||
return "Delivered"
|
||||
}
|
||||
return "Shipped"
|
||||
return "Label Created"
|
||||
case OrderStatusDelivered:
|
||||
return "Delivered"
|
||||
case OrderStatusCancelled:
|
||||
|
||||
@@ -13,10 +13,11 @@ func TestOrderDisplayState(t *testing.T) {
|
||||
{"paid no tracking", Order{Status: OrderStatusPaid}, "Paid * awaiting label", DisplayKindAccent},
|
||||
{"paid with tracking, no event", Order{Status: OrderStatusPaid, TrackingNumber: "12999"}, "Paid", DisplayKindAccent},
|
||||
{"Paid pre-transit", Order{Status: OrderStatusPaid, TrackingNumber: "12999", TrackingStatus: TrackingStatusPreTransit}, "Paid * ready to ship", DisplayKindAccent},
|
||||
{"shipped no tracking event", Order{Status: OrderStatusShipped}, "Shipped", DisplayKindBrand},
|
||||
{"shipped in transit", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusTransit}, "Shipped * in transit", DisplayKindBrand},
|
||||
{"shipped failure", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusFailure}, "Shipped * delivery issue", DisplayKindWarning},
|
||||
{"shipped returned", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusReturned}, "Shipped * returned to sender", DisplayKindWarning},
|
||||
{"shipped no tracking event", Order{Status: OrderStatusShipped}, "Label Created", DisplayKindBrand},
|
||||
{"shipped pre-transit", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusPreTransit}, "Label Created", DisplayKindBrand},
|
||||
{"shipped in transit", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusTransit}, "In Transit", DisplayKindBrand},
|
||||
{"shipped failure", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusFailure}, "Delivery Issue", DisplayKindWarning},
|
||||
{"shipped returned", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusReturned}, "Returned to Sender", DisplayKindWarning},
|
||||
{"shipped tracking-delivered (webhook lag)", Order{Status: OrderStatusShipped, TrackingStatus: TrackingStatusDelivered}, "Delivered", DisplayKindSuccess},
|
||||
{"delivered", Order{Status: OrderStatusDelivered}, "Delivered", DisplayKindSuccess},
|
||||
{"cancelled", Order{Status: OrderStatusCancelled}, "Cancelled", DisplayKindError},
|
||||
|
||||
@@ -104,6 +104,10 @@ func (m Model) buildOrderDetailView(order models.Order, _ int) string {
|
||||
b.WriteString(labelStyle.Render("Status: "))
|
||||
b.WriteString(m.displayStyle(order.DisplayKind()).Render(order.DisplayState()))
|
||||
b.WriteString("\n")
|
||||
if order.TrackingStatusDetails != "" {
|
||||
b.WriteString(dimStyle.Render(" " + order.TrackingStatusDetails))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
b.WriteString(labelStyle.Render("Date: "))
|
||||
b.WriteString(valueStyle.Render(order.CreatedAt.Format("Jan 02 2006 3:04 PM")))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
Reference in New Issue
Block a user