Cancel Transaction
Overview
Cancel a PENDING transaction before it is settled. This command intelligently reverses all entity changes (holds, balance reserves, state transitions) that were made when the transaction was created.
When to Use
- ✅ Customer changes their mind before approval
- ✅ Operational error detected before settlement
- ✅ Transaction no longer needed
- ✅ Cleanup of stale pending transactions
When NOT to Use
- ❌ Transaction already SETTLED → Use Reverse Transaction instead
- ❌ Part of approval workflow → Use Reject Transaction instead
Endpoint
POST /api/bpm/cmd/CancelTransactionCommand
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
transactionKey | string | Yes | Unique transaction reference (e.g., "TRX-2026-001234") |
reason | string | No | Reason for cancellation (recommended for audit trail) |
Example Request
{
"transactionKey": "TRX-2026-001234",
"reason": "Customer requested cancellation before approval"
}
Response Format
Success Response
{
"success": true,
"message": "Transaction cancelled successfully. Reversed 3 entity changes.",
"code": "SUCCESS",
"data": {
"transactionKey": "TRX-2026-001234",
"oldState": "PENDING",
"newState": "CANCELLED",
"reversedChanges": 3,
"reason": "Customer requested cancellation before approval"
}
}
Error Response - Already Settled
{
"success": false,
"message": "Cannot cancel transaction. Current state: SETTLED. Only PENDING or HOLD transactions can be cancelled.",
"code": "INVALID_STATE_TRANSITION"
}
Error Response - Not Found
{
"success": false,
"message": "Transaction not found: TRX-2026-999999",
"code": "TRANSACTION_NOT_FOUND"
}
What Gets Reversed
When you cancel a transaction, the system automatically reverses:
1. Hold Amounts
BEFORE: Account balance = 50,000, Hold = 10,000
AFTER: Account balance = 50,000, Hold = 0
2. Balance Reserves
BEFORE: Available balance = 40,000 (50,000 - 10,000 hold)
AFTER: Available balance = 50,000
3. Account State Changes
BEFORE: Account state = PENDING_APPROVAL
AFTER: Account state = ACTIVE (restored to original)
4. Loan Account Changes
BEFORE: Principal due = 10,000 (reserved for payment)
AFTER: Principal due = 0 (reservation removed)
State Transition Diagram
Usage Examples
Example 1: Cancel Customer Deposit
const response = await fetch('/api/bpm/cmd/CancelTransactionCommand', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
transactionKey: 'TRX-2026-001234',
reason: 'Customer provided incorrect account number'
})
});
const data = await response.json();
if (data.success) {
console.log(`Cancelled successfully. Reversed ${data.data.reversedChanges} changes.`);
}
Example 2: Bulk Cancellation of Old Pending Transactions
// Find old pending transactions
const oldPending = await getTransactions({
status: [0], // PENDING
endDate: new Date(Date.now() - 30*24*60*60*1000).toISOString() // Older than 30 days
});
// Cancel each one
for (const txn of oldPending.data.items) {
await fetch('/api/bpm/cmd/CancelTransactionCommand', {
method: 'POST',
body: JSON.stringify({
transactionKey: txn.transactionKey,
reason: 'Automatic cleanup of stale pending transaction'
})
});
}
Comparison with Other Commands
| Feature | Cancel Transaction | Reject Transaction | Reverse Transaction |
|---|---|---|---|
| Valid States | PENDING, HOLD | PENDING (in workflow) | SETTLED |
| Use Case | Simple cancellation | Workflow rejection | Undo completed txn |
| Requires Category | No | Yes (rejection category) | Yes (reversal category) |
| Generates Report | No | Yes (if FRAUD/AML) | Yes (reversal journal) |
| Balance Impact | Releases holds | Releases holds | Restores balance |
Security & Permissions
Required Permissions
- Role:
ManageTransactionsorCancelTransactions - Scope: Can cancel own transactions; managers can cancel any
Audit Trail
All cancellations are logged with:
- User who cancelled
- Timestamp
- Cancellation reason
- Reversed entity changes
- Original transaction details
Best Practices
✅ DO
- ✅ Always provide a clear reason for cancellation
- ✅ Verify transaction state before cancelling
- ✅ Notify customer if applicable
- ✅ Document operational errors
❌ DON'T
- ❌ Don't use for settled transactions (use reverse instead)
- ❌ Don't cancel without checking impact
- ❌ Don't skip reason field
- ❌ Don't cancel as part of normal workflow (use reject instead)
Related Commands
- Reject Transaction - Reject with categorization and workflow
- Reverse Transaction - Reverse a settled transaction
- Approve Transaction - Approve a pending transaction
- Get All Transactions - Find transactions to cancel
Testing
describe('Cancel Transaction', () => {
it('should cancel pending transaction', async () => {
// Create pending transaction
const txn = await createDeposit({ amount: 10000, requireApproval: true });
expect(txn.state).toBe('PENDING');
// Cancel it
const cancel = await cancelTransaction({
transactionKey: txn.transactionKey,
reason: 'Test cancellation'
});
expect(cancel.success).toBe(true);
expect(cancel.data.newState).toBe('CANCELLED');
});
it('should not cancel settled transaction', async () => {
const txn = await createDeposit({ amount: 10000, requireApproval: false });
expect(txn.state).toBe('SETTLED');
const cancel = await cancelTransaction({ transactionKey: txn.transactionKey });
expect(cancel.success).toBe(false);
expect(cancel.message).toContain('Cannot cancel');
});
});