Braintree 91530: Cannot refund a transaction unless it is settled (void while unsettled)
Fixes Braintree error 91530, "Cannot refund a transaction unless it is settled." Explains that refunds only work on settling or settled transactions, and that authorized or submitted_for_settlement transactions must be voided for full reversals, with partial refunds after settlement. Use when a Braintree Transaction.refund call fails with 91530 in any SDK. Not for duplicate refunds, disputes, or failures on already-settled transactions.
TL;DR
Braintree's refund API only works on transactions that have settled (or are settling). Error 91530 means the transaction is still authorized or submittedforsettlement. If you need a full reversal right now, call Transaction.void instead. If you need a partial refund, wait until the transaction settles and then refund it.
Verbatim error
Cannot refund a transaction unless it is settled.(Error code 91530, returned as a transaction validation error from Transaction.refund.)
When this applies
- You called Transaction.refund (any SDK: Python, Ruby, Node, PHP, Java, .NET) and got 91530.
- The transaction status is authorized or submittedforsettlement.
- You are trying to refund a test transaction in the sandbox shortly after creating it.
When this does NOT apply
- The transaction status is settling or settled and refunds still fail: something else is wrong (check refund_id for an existing refund).
- You want to reverse a transaction that already settled and was already refunded: this is a duplicate-refund problem, not 91530.
- Disputes or chargebacks: the refund API is not the path for those.
Fix
1. Read the transaction status
result = gateway.transaction.find(transaction_id)
print(result.transaction.status)Expected output: one of authorized, submitted_for_settlement, settling, or settled. The fix depends on it.
2. Unsettled transaction, full reversal needed: void it
result = gateway.transaction.void(transaction_id)
print(result.is_success, result.transaction.status)Expected output: True and status voided. Voiding works on authorized and submittedforsettlement transactions and cancels the whole amount. This is the correct move for full reversals of fresh transactions.
3. Unsettled transaction, partial refund needed: settle first, then refund
Partial refunds are impossible before settlement. In production, wait for the next settlement batch (usually within about 24 hours), then run:
result = gateway.transaction.refund(transaction_id, partial_amount)
print(result.is_success, result.transaction.type)Expected output: True and a new transaction of type credit for the partial amount. In the sandbox, use the SDK's test-helper settle call to move the fixture to settling/settled before refunding.
4. Settling or settled transaction: refund normally
result = gateway.transaction.refund(transaction_id)
print(result.is_success)Expected output: True. Full and partial refunds both work on settling and settled transactions.
Variant phrasings
91530 braintree refund
The same validation error; the fix is the same: void while unsettled, refund once settled.
Braintree refund of an authorized transaction fails
Authorized means funds are held but not captured. Refund cannot run against a hold, so Braintree returns 91530. Void the authorization instead.
Braintree refund of a submittedforsettlement transaction fails
The transaction is queued for the next settlement batch but has not settled yet. Either void it now (full reversal) or wait for the batch and refund after.
Why it happens
A Braintree refund creates a credit transaction against captured funds. Before settlement the money has not moved: the gateway only holds an authorization, so the only allowed reversal is a void, which releases the hold. Partial refunds additionally need the settled record because the final captured amount is what the partial is computed against.
Edge cases
- A transaction that is
settlingcan be refunded, but voids may already be locked out at that point; if void fails, refund instead. - Refunding twice: check
transaction.refunded(or the refund_id field) before retrying. A second refund attempt is a different failure, not 91530. - Settlement timing varies by merchant account and processor; do not hard-code "settles in exactly N hours" into retry logic.
- Sandbox transactions settle on a test schedule and can sit in submittedforsettlement for a long time; this is expected, not a bug.
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.