feat(orders): customer-facking tracking labels and webhook response body
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:
2026-05-14 21:17:50 +02:00
parent e29dd6b30d
commit e5ec247a6b
4 changed files with 49 additions and 13 deletions

View File

@@ -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)
}

View File

@@ -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:

View File

@@ -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},

View File

@@ -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")