From 2eda5959b91ed67177dfd0266a71f6eafd0a14ed Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Wed, 25 Feb 2026 19:12:24 -0500 Subject: [PATCH] fix(state-machine): prevent cancel after bills accepted in cash-in Once bills are stacked in the cash box they cannot be returned. Cancel in displayingQR now goes to confirmAbandon warning state. Error state retries to generatingNdebit instead of idle when bills are present. CANCEL from error only goes to idle if no bills inserted. Co-Authored-By: Claude Opus 4.6 --- packages/state-machine/src/machine.ts | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/state-machine/src/machine.ts b/packages/state-machine/src/machine.ts index 45b72ce..c2cc434 100644 --- a/packages/state-machine/src/machine.ts +++ b/packages/state-machine/src/machine.ts @@ -431,6 +431,7 @@ export function createATMMachine( }, }, generatingNdebit: { + // No CANCEL here — bills are already in the cash box invoke: { src: 'generateNdebit', input: ({ context }) => context, @@ -457,8 +458,15 @@ export function createATMMachine( target: 'error', actions: ['setError', 'setPaymentFailed'], }, - TIMEOUT: '#atm.idle', - CANCEL: '#atm.idle', + CANCEL: 'confirmAbandon', + TIMEOUT: 'confirmAbandon', + }, + }, + confirmAbandon: { + // Warning: cash is in the machine, abandoning forfeits it + on: { + CANCEL: '#atm.idle', // User confirms they want to leave + RETRY: 'generatingNdebit', // Go back and try again }, }, askForReceipt: { @@ -489,10 +497,20 @@ export function createATMMachine( on: { RETRY: { guard: 'canRetry', - target: 'fetchingRate', + target: 'generatingNdebit', actions: 'incrementRetry', }, - CANCEL: '#atm.idle', + CANCEL: [ + { + // No bills inserted: safe to cancel + guard: ({ context }) => context.billsInserted.length === 0, + target: '#atm.idle', + }, + { + // Bills inserted: show abandon warning first + target: 'confirmAbandon', + }, + ], }, }, },