fix(invoices): wrap refund flow in transaction for idempotency safety
- Wrap idempotency check + processRefund() + db.insert() in db.transaction() - This prevents duplicate Stripe refunds if the DB insert fails after Stripe processes the refund - Add migration 0027_refunds for the refunds table (was missing) - Removes out-of-scope changes from PR #278 (csrf.ts, appointmentGroups, appointments, book, groomingLogs, services, stripe-webhooks) Fixes GRO-637 per CTO review Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
committed by
groombook-cto[bot]
parent
85af080ba2
commit
8f06f32e7d
@@ -395,26 +395,28 @@ invoicesRouter.post(
|
||||
return c.json({ error: "No Stripe payment intent found for this invoice" }, 422);
|
||||
}
|
||||
|
||||
if (body.idempotencyKey) {
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(refunds)
|
||||
.where(eq(refunds.idempotencyKey, body.idempotencyKey));
|
||||
if (existing) {
|
||||
return c.json({ refundId: existing.stripeRefundId });
|
||||
return await db.transaction(async (tx) => {
|
||||
if (body.idempotencyKey) {
|
||||
const [existing] = await tx
|
||||
.select()
|
||||
.from(refunds)
|
||||
.where(eq(refunds.idempotencyKey, body.idempotencyKey));
|
||||
if (existing) {
|
||||
return c.json({ refundId: existing.stripeRefundId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await processRefund(id, body.amountCents);
|
||||
if (!result) return c.json({ error: "Refund failed" }, 500);
|
||||
const result = await processRefund(id, body.amountCents);
|
||||
if (!result) return c.json({ error: "Refund failed" }, 500);
|
||||
|
||||
await db.insert(refunds).values({
|
||||
invoiceId: id,
|
||||
stripeRefundId: result.refundId,
|
||||
idempotencyKey: body.idempotencyKey ?? null,
|
||||
amountCents: body.amountCents ?? null,
|
||||
await tx.insert(refunds).values({
|
||||
invoiceId: id,
|
||||
stripeRefundId: result.refundId,
|
||||
idempotencyKey: body.idempotencyKey ?? null,
|
||||
amountCents: body.amountCents ?? null,
|
||||
});
|
||||
|
||||
return c.json({ refundId: result.refundId });
|
||||
});
|
||||
|
||||
return c.json({ refundId: result.refundId });
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user