Back to Practice

Practice · Payments · Card 4

The refunded event arrived first. Now what?

Webhooks aren't guaranteed to arrive in the order things happened. Your handler has to stay correct when the order is wrong.

The handler

# Real order at the provider:
#   t=0   payment_intent.succeeded
#   t=10s payment_intent.refunded  (full refund)
#
# Order your server received the webhooks:
#   1) payment_intent.refunded   (arrived first)
#   2) payment_intent.succeeded  (arrived second)
#
# The handler:
case event.type
when "payment_intent.succeeded"
  Payment.find_by!(intent_id: event.data.object.id)
         .update!(status: :succeeded)
when "payment_intent.refunded"
  Payment.find_by!(intent_id: event.data.object.id)
         .update!(status: :refunded)
end

The question

With the order of arrival above, what's the final state of the Payment, and what's the right way to fix the handler?

Take a moment. Each event arrives, the handler applies the new status, the last one wins. Walk through which last-one-wins ends here. Then think about what change to the handler makes it correct under any arrival order.