Agent Wranglers

Refunds, disputes and fraud Required

Refund through the provider with a key and a reason

Issue every refund through the provider's API with an idempotency key, a reason and the person who asked. Track it until it succeeds or fails.

How to do it

Refund to the original payment method, unless the customer agrees to something else. Allow partial refunds only up to the amount not yet refunded, checked on the server inside a transaction. Refunds can fail, such as on a closed card, so handle the provider's refund failure event. Write a ledger row as in payment ledger. Issue a credit note for every refund of an invoiced charge.

Example

async function refund(order, amountMinor, reason, staffId) {
  const remaining = order.paidMinor - order.refundedMinor
  if (amountMinor <= 0 || amountMinor > remaining) throw new Error(`Refund must be between 1 and ${remaining}`)
  return stripe.refunds.create(
    { payment_intent: order.paymentId, amount: amountMinor, metadata: { orderId: order.id, reason, staffId } },
    { idempotencyKey: `refund-${order.id}-${order.refundCount + 1}` },
  )
}
def refund(order, amount_minor: int, reason: str, staff_id: str):
    remaining = order.paid_minor - order.refunded_minor
    if not 0 < amount_minor <= remaining:
        raise ValueError(f"Refund must be between 1 and {remaining}")
    return stripe.Refund.create(
        payment_intent=order.payment_id,
        amount=amount_minor,
        metadata={"order_id": order.id, "reason": reason, "staff_id": staff_id},
        idempotency_key=f"refund-{order.id}-{order.refund_count + 1}",
    )

How to check

Send the same refund request twice at once. The provider shows one refund. A refund larger than the remaining amount is rejected before any provider call.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/refunds-disputes-and-fraud/provider-refunds.