ExamplesPayment Dispute

Payment Dispute Workflow


Old fragile flow

POST /api/disputes
  → create dispute row
  → notify risk team
  → pull charge from Stripe
  → request evidence from merchant
  → update status in three tables
  → maybe lose track when Stripe webhook arrives late

Causet version

POST /api/disputes → submit open_payment_dispute
Stripe webhook     → submit record_dispute_update (idempotent)
Causet             → durable dispute timeline
App DB             → updated from events / projections

Sample intents

actions:
  open_payment_dispute:
    state: payment_dispute
    entity_id_expr: intent.dispute_id
    input:
      dispute_id: { type: string, required: true }
      charge_id:  { type: string, required: true }
      reason:     { type: string, required: true }
 
  record_dispute_update:
    state: payment_dispute
    entity_id_expr: intent.dispute_id
    input:
      dispute_id: { type: string, required: true }
      provider_status: { type: string, required: true }
      provider_event_id: { type: string, required: true }

Sample events

payment_dispute_opened
evidence_requested
evidence_submitted
dispute_won
dispute_lost

Sample query

queries:
  dispute_status:
    from: payment_dispute_status
    where:
      dispute_id: { eq: input.dispute_id }

Timeline output

open_payment_dispute (dp_88)
  ✓ payment_dispute_opened
  ✓ evidence_requested
record_dispute_update (Stripe webhook)
  ✓ evidence_submitted
  ✓ dispute_won

What you can inspect

  • Correlation between Stripe event IDs and the dispute entity
  • Whether evidence was requested before the provider closed the case
  • Duplicate webhook deliveries rejected via idempotency key

What you can replay or repair

  • Re-process a missed provider update on a fork
  • Rebuild payment_dispute_status after a projection bug
  • Re-notify risk without double-charging state

Related: Webhooks · Billing Events.