Paycose Docs
Features

Refunds & disputes

Giving money back — either because you chose to, or because the customer's bank forced it.

A Refund is you giving money back to a customer, on purpose. A Dispute is a chargeback — the customer's bank pulling the money back, whether you agree or not.

How it works

  • You create a Refund against a captured Charge, the same way as on Stripe. Name the charge with charge_id (or charge, or the payment_intent it belongs to). amount is optional, in the minor unit; leave it out to refund everything that is left. reason is optional too: duplicate, fraudulent or requested_by_customer.
  • There is no approval step. The refund goes to the provider as soon as you create it, and your balance is debited at that moment, so the money can't be paid out while the refund is in flight.
  • The money always goes back to the original payment method. Refunding into a customer wallet balance was removed: a destination other than source is refused with E4515.
  • List Refunds or retrieve one by ID.
  • A Dispute is different: it's created exclusively by a webhook from the provider (charge.dispute.*) when a customer's bank files a chargeback. You can never create one yourself — you can only list them, retrieve one, and respond to it (submit evidence) to track its status.

Refund statuses

StatusWhat it means
pendingSent to the provider, which hasn't confirmed it yet.
requires_actionThe provider needs something more before it can pay the customer. This is the only state you can cancel from.
succeededThe provider accepted the refund; the customer gets the money back.
failedThe provider refused it. failure_reason says why, and the debit returns to your balance.
canceledCanceled while it required action; the debit returns to your balance.

failed and canceled are final. A refund that fails still comes back with 200 and status: "failed", so read the status rather than the HTTP code. Sending the same request again with the same Idempotency-Key returns the same refund; reusing the key for a different charge or amount is refused.

The refund carries balance_transaction, the debit on your balance, and failure_balance_transaction, the entry that gives it back when the refund fails or is canceled.

Refunds created before approval was removed may still read rejected. Refunds that were waiting for approval were canceled automatically, and their rejection_reason says so — create them again if you still want to refund.

When a refund is refused up front

Some payment methods have no refund API at the provider — for example PromptPay and QR payments through SCB or KBank, K PLUS, SCB EASY, KBank SmartPay, Krungsri card and KMA, Apple Pay and Google Pay. PaymentGateway refuses a refund on those charges when you create it (E4526) instead of debiting you for a refund nobody can send; refund the customer outside PaymentGateway. In the dashboard the Refund action stays visible but disabled, with the reason. GET /charges/{id}/refund_methods tells you ahead of time: its single source entry has available: false and an unavailable_reason. See the capability matrix for which methods support refunds.

You cannot POST a dispute — if you see one, it always originated from the provider's webhook. Your only actions are reading it, updating its metadata, and submitting evidence.

Refunding a destination charge

On a charge with transfer_data.destination and an application_fee_amount, the connected account bears the refund by default and you keep your application fee: a full refund of 1,000 with a 200 fee leaves the connected account owing you 200. Send refund_application_fee: true to give back the matching share of the fee too (200 × refund / 1,000); it shows up as an application fee refund once the refund succeeds, and the connected account then owes nothing. The flag does nothing on a charge that uses transfer_data.amount. reverse_transfer is accepted and ignored.

Events

A refund sends refund.created when it is created, refund.updated whenever its status changes, and refund.failed when the provider refuses it. The charge sends charge.refunded once a refund on it succeeds. See Refund events.

Common use cases

  • Refunding a customer who returned an item.
  • Partially refunding an overcharge.
  • Responding to a chargeback with evidence (receipt, delivery proof) before the provider's deadline.

Works with

On this page