diff --git a/apps/web/lib/coinpay.ts b/apps/web/lib/coinpay.ts index 8dca2fe..c0bef16 100644 --- a/apps/web/lib/coinpay.ts +++ b/apps/web/lib/coinpay.ts @@ -46,6 +46,7 @@ export async function createCoinpayPayment(args: { } return { ok: true, payment: data.payment }; } catch (err) { + console.error(`[coinpay] create payment failed:`, (err as Error).message); return { ok: false, error: (err as Error).message }; } } @@ -57,10 +58,14 @@ export async function getCoinpayPayment(id: string): Promise= Number(row.max_uses)) { return { ok: false, error: "This code has been fully redeemed." }; } - const dup = await sqlClient.execute({ - sql: "SELECT 1 FROM promo_redemptions WHERE code = ? AND user_id = ? LIMIT 1", - args: [code, userId], - }); - if (dup.rows[0]) return { ok: false, error: "You've already used this code." }; - const percentOff = Math.min(100, Math.max(1, Number(row.percent_off ?? 100))); const finalUsd = round2(baseUsd * (1 - percentOff / 100)); return { ok: true, code, percentOff, baseUsd, finalUsd, free: finalUsd <= 0 }; } -/** Record a promo redemption (idempotent). Call once the discount is actually granted. */ -export async function recordPromoRedemption(codeRaw: string, userId: string): Promise { +/** Record a promo redemption (idempotent). Returns true if newly recorded. */ +export async function recordPromoRedemption(codeRaw: string, userId: string): Promise { const code = codeRaw.trim().toUpperCase(); - if (!code) return; + if (!code) return false; try { await sqlClient.execute({ sql: "INSERT INTO promo_redemptions (code, user_id) VALUES (?, ?)", args: [code, userId] }); await sqlClient.execute({ sql: "UPDATE promo_codes SET uses = uses + 1 WHERE code = ?", args: [code] }); + return true; } catch { - /* already recorded — composite PK makes this a no-op */ + return false; } } diff --git a/apps/web/lib/password.ts b/apps/web/lib/password.ts index 685767b..3e105e7 100644 --- a/apps/web/lib/password.ts +++ b/apps/web/lib/password.ts @@ -1,9 +1,9 @@ import { hash, verify } from "@node-rs/argon2"; -// Argon2id parameters — reasonable defaults for interactive auth. +// Argon2id parameters — OWASP recommended minimums for production. const opts = { - memoryCost: 19456, - timeCost: 2, + memoryCost: 47104, + timeCost: 3, parallelism: 1, } as const;