Problem
app/schemas/customerpayment.schema.js declares the amount field as:
cpayAmount: z.coerce.number(),
z.coerce.number() accepts every value Number() would produce — including:
- 0 — a
$0 payment recorded against a customer ledger has no business
meaning. Always operator error.
- Infinity / -Infinity — zod's
.number() rejects NaN by default
but lets the infinities through. The cpayAmount column is a Sequelize
DOUBLE which happily stores inf, and any consumer doing arithmetic
(totals, aging buckets, CSV exports) gets contaminated thereafter.
Negative values are valid — some operators model refunds that way.
Fix
Extract the amount validator to a named cpayAmountField so the create
and update bodies share one definition, and chain:
.finite({ message: 'cpayAmount must be a finite number.' })
.refine((n) => n !== 0, { message: 'cpayAmount must not be zero.' })
Pin the new behavior in tests/api/customerpayment.test.js:
- POST with
cpayAmount: 0 → 400
- POST with
cpayAmount: -50 → not 400 (refund flow stays valid)
- PATCH with
cpayAmount: 0 → 400
Acceptance
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/
Problem
app/schemas/customerpayment.schema.jsdeclares the amount field as:z.coerce.number()accepts every valueNumber()would produce — including:$0 paymentrecorded against a customer ledger has no businessmeaning. Always operator error.
.number()rejectsNaNby defaultbut lets the infinities through. The
cpayAmountcolumn is a SequelizeDOUBLEwhich happily storesinf, and any consumer doing arithmetic(totals, aging buckets, CSV exports) gets contaminated thereafter.
Negative values are valid — some operators model refunds that way.
Fix
Extract the amount validator to a named
cpayAmountFieldso the createand update bodies share one definition, and chain:
Pin the new behavior in
tests/api/customerpayment.test.js:cpayAmount: 0→ 400cpayAmount: -50→ not 400 (refund flow stays valid)cpayAmount: 0→ 400Acceptance
cpayAmount: 0Infinity/-InfinityProudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/