From 728386e4b1203b44348c99ea5d734824bb404f2b Mon Sep 17 00:00:00 2001 From: RissRIce Date: Sat, 25 Jul 2026 14:40:11 -0600 Subject: [PATCH] fix(referrals): validate positive cent amounts --- src/referrals/index.ts | 12 ++++++++++-- tests/referrals.test.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/referrals/index.ts b/src/referrals/index.ts index e9c280b..e16d5c6 100644 --- a/src/referrals/index.ts +++ b/src/referrals/index.ts @@ -355,8 +355,16 @@ export function createReferralsRouteHandler(options: ReferralsRouteHandlerOption const userId = await getUserId(req); if (!userId) return jsonResponse({ error: "Unauthorized" }, { status: 401 }); const code = typeof body["code"] === "string" ? body["code"] : null; - const amount = typeof body["amount"] === "number" ? body["amount"] : null; - if (!code || !amount) return jsonResponse({ error: "Missing code or amount" }, { status: 400 }); + const rawAmount = body["amount"]; + const amount = + typeof rawAmount === "number" && + Number.isSafeInteger(rawAmount) && + rawAmount > 0 + ? rawAmount + : null; + if (!code || amount === null) { + return jsonResponse({ error: "Missing code or amount" }, { status: 400 }); + } const usage = await applyReferral({ code, newUserId: userId, diff --git a/tests/referrals.test.ts b/tests/referrals.test.ts index 9a119b0..c7ecd5d 100644 --- a/tests/referrals.test.ts +++ b/tests/referrals.test.ts @@ -567,6 +567,35 @@ describe("createReferralsRouteHandler (authenticated)", () => { expect(await missing.json()).toEqual({ error: "Missing code or amount" }); }); + it.each([-100, 0, 1.5, Number.MAX_SAFE_INTEGER + 1, "100", null])( + "POST apply rejects invalid cent amount %j without saving a usage", + async (amount) => { + const { store, usages } = makeMemoryStore(); + await store.saveCode({ + code: "ABC123", + ownerId: "user-1", + createdAt: new Date(), + expiresAt: null, + }); + const { POST } = createReferralsRouteHandler({ + store, + getUserId: () => "user-2", + }); + + const res = await POST( + makeReq("https://app.test/api/referrals", { + action: "apply", + code: "ABC123", + amount, + }) + ); + + expect(res.status).toBe(400); + expect(await res.json()).toEqual({ error: "Missing code or amount" }); + expect(usages).toHaveLength(0); + } + ); + it("POST apply records the usage with the default 60/20 split", async () => { const { store, usages } = makeMemoryStore(); await store.saveCode({