The method
Written 13 October 2026. A case is built from four Stripe events, each read the same way: from the charge to its Checkout Session, from the session to the print lab's order, and from the lab's record to a decision about the parcel. Every line of the evidence draft names the call it came from.
The four events
| Event | What it means for the parcel |
|---|---|
charge.dispute.created | The bank has opened a dispute and set a date. An order the lab has not shipped is cancelled. |
charge.dispute.closed | The bank has decided. The case records the outcome; nothing changes at the lab. |
charge.refunded | Money went back. A full refund cancels an unshipped order; a partial refund keeps it, because the customer keeps the order. |
charge.refund.updated | A refund changed state, for example it failed. Recorded on the same case. |
How an event arrives
A dispute object carries no shop tag, so the family's two routers cannot place it. Flume, the Painted account's one webhook, and sluice, the Provenance account's, forward what they cannot route to refundline, signed again in Stripe's own format with a secret each shares with refundline alone. The secret that verifies tells refundline which account the event belongs to, and so which Stripe key reads the session. An owner can also point a Stripe endpoint here directly. An event id already seen is answered and not built again, so Stripe's retries make one case.
The lookup
The dispute names its charge; the charge names its payment intent; checkout.sessions.list with that payment intent returns the session the customer paid through, with its line items, the shop tag in metadata.source and, where the shop writes one, the lab order in metadata.lab_order. Printful is read with GET /orders/{id} and Prodigi with GET /v4.0/orders/{id}, the same calls the Pawtrait studios make.
The cancel rule
Shipped means no cancel, and the case says so with the ship date and the tracking number, because a delivered parcel is the strongest evidence a shop has. A Printful order still in draft, pending, failed or on hold is cancelled with DELETE /orders/{id}; a Prodigi order still in progress with no shipment is cancelled with its cancel action. An order already on the press is left alone and the case asks for a hand in the lab's dashboard. An order is cancelled at most once: a later event on the same order finds it cancelled.
The evidence draft
Tracking: 9400111899223197415820 [printful: GET /orders/118204455 tracking] Tracking: [[NO TRACKING]] [prodigi: GET /v4.0/orders/ord_1288410 tracking]
Each fact is one line with its source in brackets. A fact nobody recorded is a blank in double brackets. The desk refuses to send a draft that still holds one, so nothing reaches the bank that the records do not show.
The deadline
The date on each card is Stripe's own evidence_details.due_by for that dispute. Cards are sorted by it, soonest first.
The one write
The desk's button calls disputes.update with the evidence and submit: true. No other line of refundline writes to Stripe. It never issues a refund: the owner does that in Stripe, and refundline reads the refund when it happens.
What a second account needs
The Desk at $49 a month runs the same code on another owner's account. Connecting one takes a restricted Stripe key that can read charges, disputes and Checkout Sessions and write disputes, an endpoint pointed at /api/webhook for the four events above, and the lab keys for the labs that account uses. The till is closed until accounts after the first can be connected without a hand step.
What is not proven yet
The three cases on the front page are recorded fixtures shaped like the objects Stripe, Printful and Prodigi publish. No live dispute has passed through refundline. The Pawtrait studios keep their lab order ids in their own database, not on the Checkout Session, so a Pawtrait case shows the lab as a blank until a studio writes lab_order to the session.