fix: cumulative fund claims — deposits accumulate until claimed

Instead of expiring old claims, new deposits add to the existing
pending claim's fiat_amount and extend the expiry. The claim email
shows the updated total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-06 23:42:44 -08:00
parent 6c807afeb0
commit f662881170
2 changed files with 31 additions and 6 deletions

View File

@ -1271,6 +1271,18 @@ export async function acceptFundClaim(token: string, userId: string): Promise<St
return rowToFundClaim(rows[0]); return rowToFundClaim(rows[0]);
} }
export async function accumulateFundClaim(claimId: string, additionalAmount: string, expiresAt: number): Promise<StoredFundClaim | null> {
const rows = await sql`
UPDATE fund_claims
SET fiat_amount = (COALESCE(fiat_amount::numeric, 0) + ${additionalAmount}::numeric)::text,
expires_at = ${new Date(expiresAt)}
WHERE id = ${claimId} AND status IN ('pending', 'resent')
RETURNING *
`;
if (rows.length === 0) return null;
return rowToFundClaim(rows[0]);
}
export async function expireFundClaim(claimId: string): Promise<void> { export async function expireFundClaim(claimId: string): Promise<void> {
await sql`UPDATE fund_claims SET status = 'expired', email = NULL WHERE id = ${claimId} AND status IN ('pending', 'resent')`; await sql`UPDATE fund_claims SET status = 'expired', email = NULL WHERE id = ${claimId} AND status IN ('pending', 'resent')`;
} }

View File

@ -77,7 +77,7 @@ import {
getFundClaimByToken, getFundClaimByToken,
getFundClaimsByEmailHash, getFundClaimsByEmailHash,
acceptFundClaim, acceptFundClaim,
expireFundClaim, accumulateFundClaim,
cleanExpiredFundClaims, cleanExpiredFundClaims,
sql, sql,
} from './db.js'; } from './db.js';
@ -2926,17 +2926,30 @@ app.post('/api/internal/fund-claims', async (c) => {
return c.json({ error: 'email and walletAddress are required' }, 400); return c.json({ error: 'email and walletAddress are required' }, 400);
} }
const id = crypto.randomUUID();
const token = generateToken();
const emailHashed = await hashEmail(email); const emailHashed = await hashEmail(email);
const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days const expiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000; // 7 days
// Expire any existing pending claims for this email (one active claim per user) // Check for existing pending claim — accumulate deposits
const existingClaims = await getFundClaimsByEmailHash(emailHashed); const existingClaims = await getFundClaimsByEmailHash(emailHashed);
for (const ec of existingClaims) { if (existingClaims.length > 0 && fiatAmount) {
await expireFundClaim(ec.id); const existing = existingClaims[0];
const updated = await accumulateFundClaim(existing.id, fiatAmount, expiresAt);
let sent = false;
try {
const totalAmount = updated?.fiatAmount || fiatAmount;
sent = await sendClaimEmail(email, existing.token, totalAmount, fiatCurrency || 'USD');
} catch (err) {
console.error('EncryptID: Failed to send claim email:', err);
}
return c.json({ claimId: existing.id, accumulated: true, sent });
} }
// No existing claim — create new
const id = crypto.randomUUID();
const token = generateToken();
const claim = await createFundClaim({ const claim = await createFundClaim({
id, id,
token, token,