feat(machine): limit bill insertion to available balance
Add billWithinBalance guard to state machine that rejects bills exceeding the ATM's available sats. UI disables denomination buttons and shows informational message when limit is reached. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9569f849e7
commit
16159334a4
2 changed files with 45 additions and 2 deletions
|
|
@ -114,6 +114,23 @@ function skipReceipt() {
|
|||
atmStore.skipReceipt()
|
||||
}
|
||||
|
||||
// Check if a bill denomination can still be accepted without exceeding available balance
|
||||
function canAcceptBill(denomination: number): boolean {
|
||||
const ctx = context.value
|
||||
if (!ctx) return false
|
||||
if (ctx.availableBalance <= 0 || ctx.exchangeRate === 0) return true
|
||||
const newFiatCents = ctx.fiatAmount + denomination * 100
|
||||
const grossSats = Math.floor((newFiatCents / 100) * ctx.exchangeRate)
|
||||
const fee = Math.floor(grossSats * ctx.feePercent)
|
||||
return grossSats - fee <= ctx.availableBalance
|
||||
}
|
||||
|
||||
const balanceLimitReached = computed(() => {
|
||||
if (!context.value || context.value.availableBalance <= 0) return false
|
||||
// Check if even the smallest denomination ($1) would exceed
|
||||
return !canAcceptBill(1)
|
||||
})
|
||||
|
||||
// Simulate bill insertion (for development)
|
||||
function simulateBill(denomination: number) {
|
||||
atmStore.insertBill(denomination)
|
||||
|
|
@ -210,11 +227,27 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Available balance indicator -->
|
||||
<div v-if="context && context.availableBalance > 0" class="flex justify-between text-sm">
|
||||
<span class="text-muted-foreground">Available:</span>
|
||||
<span :class="balanceLimitReached ? 'text-foreground' : 'text-muted-foreground'">
|
||||
{{ formatSats(context.availableBalance) }} sats
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Bill Acceptor Visual -->
|
||||
<div class="rounded-lg border-2 border-dashed border-white/20 p-6 text-center">
|
||||
<div v-if="balanceLimitReached">
|
||||
<div class="mb-2 text-4xl">💰</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Maximum amount reached — press Done to continue
|
||||
</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="mb-2 text-4xl">💵 ➡️ 🏧</div>
|
||||
<p class="text-sm text-muted-foreground">Insert bills into the acceptor</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dev: Simulate bill insertion -->
|
||||
<Alert v-if="atmStore.debugMode" class="border-red-500/50 bg-red-500/10">
|
||||
|
|
@ -227,6 +260,7 @@ const isProcessing = computed(() => atmStore.isPayingInvoice || atmStore.isReque
|
|||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
:disabled="!canAcceptBill(denom)"
|
||||
@click="simulateBill(denom)"
|
||||
>
|
||||
${{ denom }}
|
||||
|
|
|
|||
|
|
@ -331,6 +331,14 @@ export function createATMMachine(services: Partial<ATMServices> = {}) {
|
|||
canRetry: ({ context }) => context.retryCount < 3,
|
||||
hasUserNpub: ({ context }) => context.userNpub !== null,
|
||||
hasOfferRequest: ({ context }) => context.pendingOfferRequest !== null,
|
||||
billWithinBalance: ({ context, event }) => {
|
||||
if (event.type !== 'BILL_INSERTED') return false
|
||||
if (context.availableBalance <= 0 || context.exchangeRate === 0) return true
|
||||
const newFiatCents = context.fiatAmount + event.denomination * 100
|
||||
const grossSats = Math.floor((newFiatCents / 100) * context.exchangeRate)
|
||||
const fee = Math.floor(grossSats * context.feePercent)
|
||||
return grossSats - fee <= context.availableBalance
|
||||
},
|
||||
// Cash-out guards
|
||||
hasSelectedAmount: ({ context }) => context.cashOutSelection.length > 0,
|
||||
canAddDenomination: ({ context, event }) => {
|
||||
|
|
@ -392,6 +400,7 @@ export function createATMMachine(services: Partial<ATMServices> = {}) {
|
|||
insertingBills: {
|
||||
on: {
|
||||
BILL_INSERTED: {
|
||||
guard: 'billWithinBalance',
|
||||
actions: ['addBill', 'calculateSats'],
|
||||
},
|
||||
BILL_REJECTED: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue