Class: WebhookProcessors::CurriProcessor

Inherits:
Object
  • Object
show all
Defined in:
app/services/webhook_processors/curri_processor.rb

Overview

Processor for Curri delivery-status webhook callbacks.

Resolves the Delivery by freight_order_number (the Curri delivery id
"del_…" recorded by Shipping::Curri#label), rolls the mapped status onto
each shipment's tracking columns, and projects one ShipmentEvent row per
status transition so the existing Tracking Events tab and status icon
light up with no new UI.

The webhook refires the same status every ~20s with fresher GPS; the
controller's external_id dedup collapses those, and the ShipmentEvent
idempotency key (keyed on tracking_number + event_code, NOT the volatile
GPS) absorbs any that slip through, so replays are no-ops.

Examples:

WebhookProcessors::CurriProcessor.call(webhook_log)

See Also:

Constant Summary collapse

CURRI_STATUS_TO_STATUS_CODE =

Curri delivery statuses (docs.curri.com → Statuses, plus 'scheduled'
observed live on the sandbox 2026-07-24 for future-scheduled bookings)
→ our normalized ShipEngine-style status codes
(ShipmentEvent::STATUS_CODE_LABELS).

{
  'pending' => 'NY',                 # booked, no driver assigned yet
  'scheduled' => 'NY',
  'en_route_to_origin' => 'AC',      # driver engaged, heading to pickup
  'at_origin' => 'AC',
  'en_route_to_destination' => 'IT',
  'at_destination' => 'IT',
  'delivered' => 'DE',
  'canceled' => 'EX'
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(webhook_log) ⇒ Object

Initializes.

Parameters:

  • webhook_log (Object)

    the webhook log



51
52
53
54
# File 'app/services/webhook_processors/curri_processor.rb', line 51

def initialize(webhook_log)
  @webhook_log = webhook_log
  @data = webhook_log.data
end

Class Method Details

.call(webhook_log) ⇒ Object

Handles the self.call.

Parameters:

  • webhook_log (Object)

    the webhook log

Returns:

  • (Object)

    the result



43
44
45
# File 'app/services/webhook_processors/curri_processor.rb', line 43

def self.call(webhook_log)
  new(webhook_log).call
end

Instance Method Details

#callObject

Returns the call.

Returns:

  • (Object)

    the call



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'app/services/webhook_processors/curri_processor.rb', line 59

def call
  curri_delivery_id = @data['id']
  curri_status = extract_status
  status_code = CURRI_STATUS_TO_STATUS_CODE[curri_status]

  Rails.logger.info "[CurriProcessor] Processing #{curri_delivery_id} -> #{curri_status}"

  if status_code.nil?
    # WebhookLog#process! only retries or alerts on a RAISED exception — a
    # returned hash marks the log `processed`, i.e. green. Without this
    # report an unmapped status writes no ShipmentEvent and leaves no
    # signal anywhere. Curri already carries one status we found only by
    # probing the sandbox (`scheduled`); assume there are others.
    report_dropped('Curri webhook: unmapped delivery status',
                   curri_delivery_id: curri_delivery_id, curri_status: curri_status)
    return { status: 'skipped', reason: "Unknown Curri status: #{curri_status.inspect}", curri_delivery_id: curri_delivery_id }
  end

  delivery = Delivery.find_by(freight_order_number: curri_delivery_id)
  unless delivery
    # Same silent-success path, and this one is unrecoverable: the ~20s
    # refire of this status dedups onto the existing WebhookLog
    # (WebhookLog.resolve_ingest) and never reprocesses, so the transition
    # is lost for good rather than retried.
    report_dropped('Curri webhook: no Delivery matches the Curri delivery id',
                   curri_delivery_id: curri_delivery_id, curri_status: curri_status)
    return { status: 'not_found', reason: 'No Delivery with this freight_order_number', curri_delivery_id: curri_delivery_id }
  end

  shipments = tracked_shipments(delivery)
  events_persisted = persist_shipment_events(shipments, curri_status, status_code)
  updated = shipments.count { |shipment| update_shipment_tracking(shipment, curri_status, status_code) }

  {
    status: 'success',
    delivery_id: delivery.id,
    curri_delivery_id: curri_delivery_id,
    curri_status: curri_status,
    status_code: status_code,
    shipments_updated: updated,
    shipment_events_persisted: events_persisted,
    cancellation_reason: @data['cancellationReason']
  }.compact
end