The pending-retry amount guard compared pence via int64(pounds*100),
which truncates instead of rounding. For non-exact pound values (e.g.
£1.14 stored as the float64 1.1399999999999999) the truncation yields
113 != 114, falsely rejecting a legitimate same-amount retry with 400.
Since the frontend reuses the idempotency key on same-amount retries,
every retry was rejected, permanently stranding the pending record
(and any orphaned Square charge) with no recovery path.
Fix: compare in pence via math.Round — the existing pattern already
used in refunds.go — so non-exact values round to the true pence.
Also applied the same correction to the sibling lossy conversions:
- CreateTipPayment / CreateBookingPayment completed-dedup responses
(would have reported 113p for a 114p payment)
- BuyGiftCard retry amount guard (latent: £10/£20/£50 are float-exact
so it never bit, but the identical trap is now closed)
Tests:
- TestTipPayment_RetryPending_NonExactAmountSucceeds: 114p pending
record + same-amount retry completes and charges 114p (failed on the
old truncation with 400)
- TestTipPayment_RetryPending_AmountMismatchRejected: a same-key retry
at a different amount is still rejected with 400 and the pending
record is left untouched
Verified: full backend suite green (25/25 packages, 0 failures),
-race clean on handlers/payments, go build + go vet clean.