Skip to main content

Refund scenarios

A refund returns funds to the customer for a transaction that has already been captured. This page covers common implementation scenarios beyond basic requests: handling multiple partial refunds, processing refunds on active subscriptions, navigating disputes, and managing cases where a customer's card or bank account has changed.

For the raw API request specification, see Refund a transaction. For information on underlying ledger rules and balance constraints, see Refunds.

Check isRefundable before calling

Every Transaction response includes the permissionBundle.isRefundable boolean field. This value indicates whether the transaction can currently be refunded, taking into account its status, network time limits, gateway configurations, and any active refund requests. Verify this property before making an API call to prevent unexpected 409 Conflict errors.

A transaction is eligible for a refund when:

  • Its status is completed or partially-refunded.
  • Its refundableAmount balance is greater than 0.
  • The permissionBundle.isRefundable value returns true.

Multiple partial refunds

The refundableAmount parameter acts as a running ledger balance. It matches the original transaction amount at creation and decreases with each successful refund action. You can issue multiple partial refunds against a transaction until the cumulative total equals the original amount.

Here is how a $100 sale modifies transaction states across three partial refunds:

CallAmountNew refundAmountNew refundableAmountNew status
POST /transactions/{id}/refund303070partially-refunded
POST /transactions/{id}/refund407030partially-refunded
POST /transactions/{id}/refund301000refunded

After the third execution, the transaction reaches a terminal state. Any subsequent refund requests will fail with a 409 Conflict error.

Always look up the current refundableAmount using a GET /transactions/{id} request before attempting a refund. Avoid calculating this balance on the client side, as concurrent dashboard modifications or competing API actions can alter the true system value.

Refunding a subscription renewal

A transaction generated by an automated subscription renewal has a paymentType of subscription-payment and includes a subscriptionId. You can refund these records using the standard refund endpoint:

curl -X POST \
"https://staging-api.payments.ai/v1/public-api/organizations/${ORGANIZATION_ID}/transactions/{transactionId}/refund" \
-H 'Content-Type: application/json' \
-H "Authorization: ApiKey ${API_KEY}" \
-d '{ "amount": 9.99 }'

Issuing a refund does not cancel the underlying subscription or alter its future billing schedule. The next renewal will still trigger on its original planned date. If you want to return funds and stop future billing, you must explicitly call the cancellation endpoint. See Cancel a subscription.

To list all transactions associated with a single subscription, use this query:

GET /transactions?subscriptionId=<subscriptionId>&sortBy=createdAt&sortDirection=desc

Refund on a disputed transaction

When a cardholder opens a chargeback against a transaction, its status updates to disputed. When this occurs, the formal banking dispute process takes precedence over manual API requests. Do not attempt to refund a disputed transaction without evaluating the active dispute response. Doing so can result in returning funds twice: once via the chargeback reversal debit and once via the manual refund. See Dispute process to review how PaymentsAI manages active challenges.

Refund declined by the gateway

Because a refund executes as its own unique transaction record, it has the same result field format as other payment types. The refund API call returns a 200 OK status code if the platform validates and accepts the request, but the downstream gateway can still decline the movement during final settlement. When this happens, the refund transaction updates to result: "declined". Always check this field before confirming to a customer that they have been credited.

When a gateway declines a refund, the original transaction's refundableAmount and status fields remain unchanged. The funds stay on your ledger balance.

Refund after the original card was reissued

If a customer's card number changes but their underlying bank account remains open, the issuing bank will typically accept the refund. The bank handles routing the funds to the customer's active account automatically. Because the customer might not see this credit on their new card statement immediately, you can provide them with the Acquirer Reference Number (ARN) from the PaymentsAI panel so their bank can locate the incoming transfer.

Refund after the original bank account was closed

If a customer changes financial institutions and closes the account associated with the original transaction, the issuing bank usually cannot process the credit. You can confirm the final outcome of the transaction inside the PaymentsAI dashboard, where full gateway response logs are preserved. If the panel indicates that the refund failed to reach the customer, you must settle the balance with them directly, such as via an external manual bank wire.

Time limits

Card networks enforce custom time limits determining how long a captured transaction remains eligible for refunds. These windows typically span 120 to 180 days, varying by network rules and acquirer agreements. Once this window closes, the gateway will reject any refund attempts. After expiration, you must arrange to settle balances with the customer outside the platform.

Transactions settled outside the platform should have their isProcessedOutside flag updated to true. These records are not refundable via the API.

Tracking refunds

A successful refund request returns a new Transaction object that represents the refund event and links back to the original payment record. The system also updates the original transaction record, modifying its refundAmount, refundableAmount, status, and refundedAt timestamp fields.

To reconcile your ledger, review both data points:

  • Read the original transaction via GET /transactions/{id} to verify the updated balances.
  • Review the transaction list filtered by status via GET /transactions?combinedStatuses=refunded,partially-refunded for a clean audit trail.