Terminate a contract
Ends a contract so that it raises no further charges and, once CANCELLED, takes no further payments. The charge records are deliberately left as they are, so the contract can be resumed later with its history intact (Resume a contract); write_off only decides whether the contract is finalised now (CANCELLED, with whatever is still owed left recorded on its charges but no longer collected) or parked as CANCELLING while collection of the balance carries on. With no balance owed the contract is CANCELLED either way. Merchant View sends the cancel call when the contract shows an outstanding amount and the delete call otherwise; both reach the same server operation.
Where you can do this
- Merchant View: Contract (Terminate)
- API: Cancel a contract (
POST /contracts/{contract_id}/cancel) — write_off true or false
Before you start
- The contract exists; it is checked twice on the way through.
- No status precondition is enforced server-side (see Rules); Merchant View only offers the control on a live recurring contract.
Inputs
| Input | Required | Meaning and constraints |
|---|---|---|
| write_off | no | boolean, default false. true finalises the contract (CANCELLED) even while a balance is owed; false leaves it CANCELLING until the balance is settled. Merchant View forces it true for an UNRESOLVED contract and defaults it on for a CANCELLING one |
| cancel_retries | no | boolean, default false. true clears the next payment date, flags the contract so no retry date is ever set again, and removes the collection job. Not offered by Merchant View |
| strict | no | boolean, default false. true refuses to finalise while a PENDING or UNRESOLVED transaction exists on the contract. Not sent by Merchant View |
The delete form carries none of these; whether a body on a DELETE request is forwarded was not traced, so treat it as options-less.
Rules
Checked by the API means Shuttle refuses the request however it is sent; a screen name means only that screen refuses it, and a direct API call would be accepted.
| Rule | Checked by | What you see |
|---|---|---|
| The contract must exist | The API | NOT_FOUND |
Balance owed and write_off not true: the contract becomes CANCELLING; otherwise CANCELLED. Balance owed means the sum of outstanding on in-progress charges less any unresolved amount, floored at zero | The API | status as stated |
No charge is written off, voided or deleted and no transaction is touched by either form. This is by design: a CANCELLED contract keeps its charges' outstanding amounts and keeps reporting them in outstanding_amount, collection of them has stopped, and the untouched records allow the contract to be resumed | The API | (none; see Write off a charge to clear a balance for good) |
With strict true, finalising (write_off true, or nothing owed) while a PENDING or UNRESOLVED transaction exists is refused; parking as CANCELLING is never refused | The API | INVALID: "(BOLT-7115) Cannot cancel contract while a payment is in-flight at the gateway. Wait for the in-flight payment to resolve, then retry." |
Without strict a contract can be finalised while a payment is in flight; if that payment later fails nothing reopens the contract and the balance is stranded | The API | (silent; this is the case strict exists for) |
| Another operation holding the contract lock is refused. The call waits up to 3 seconds for the lock, retrying every 50ms; a lock it does take is held for at most 120 seconds | The API | LOCKED: "This contract is processing, please try again in a few seconds." |
| A one-off (fixed amount due now) contract is cancelled and its payment method scrubbed, but no job is touched and no event is raised | The API | (silent) |
| The payment method's stored card values, CVC and shipping address are cleared if no other in-progress contract on the account uses that method; the gateway token is kept, so collection on a CANCELLING contract still works. This happens for CANCELLING as well as CANCELLED | The API | (silent) |
CANCELLING stops new charges but not collection: the next payment date is kept, the collection job keeps running and the retry schedule is still consulted after each failure unless cancel_retries was sent | The API | (silent) |
| Once CANCELLING or CANCELLED, the contract accepts only a cleared next payment or a resume; new charges cannot be added to it | The API | see Reschedule the next payment and Resume a contract; Amend a charge for the charge-side rejection |
| Merchant View offers Terminate only on a recurring contract in ACTIVE, FAILING, CANCELLING, UNRESOLVED, ERROR, SUSPENDED or PENDING that has not been superseded | Merchant View screen | control hidden |
Merchant View picks the form by outstanding_amount: above zero it sends the cancel call with the write-off choice, otherwise the delete call | Merchant View screen | (choice made for the operator) |
What happens
- The contract raises no further charges. With nothing owed, or with
write_offtrue, it ends CANCELLED; with a balance owed andwrite_offfalse it sits CANCELLING while the balance is still collected. - Charges keep their status and outstanding amounts,
outstanding_amountstill reports anything owed, and scheduled or pending transactions are untouched. - While CANCELLING, collection carries on at the next payment date and retries run after a failure unless
cancel_retrieswas sent; a payment that clears the balance moves the contract to CANCELLED. Once CANCELLED, nothing more is collected. - CONTRACT.COMPLETE is raised when the contract becomes CANCELLED and CONTRACT.UPDATE when it becomes CANCELLING; neither is raised for a one-off contract.
- No email or SMS is sent.
- The cancel call responds 200 with the updated contract; the delete call responds 204.
Who can do this
- Roles: Admin only today; intended for Support as well, and being aligned.
- Permission keys (for clients managing permissions directly):
agreement,payment_contractor*on the client or the contract's division, scoped to the contract's account; checked by Shuttle for both entry points. - No feature option gates the function.
strictandcancel_retriesare per-call inputs available only to API callers.
Related
Updated 33 minutes ago