d433c902b4
* Fix invoice status transitions, tip-split validation, refund idempotency, and tip-split response format - Add ALLOWED_TRANSITIONS state machine for invoice status changes (GRO-637) - Replace floating-point tip-split validation with integer basis-points math - Add idempotency key support to refund endpoint with new refunds table - Return full invoice shape from POST /:id/tip-splits matching GET response - All existing tests pass Co-Authored-By: Paperclip <noreply@paperclip.ing> * 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> * fix(api): wire up CSRF middleware for protected routes Register csrfMiddleware in the protected API routes after authMiddleware and resolveStaffMiddleware to protect against CSRF attacks on state- changing operations (POST, PUT, PATCH, DELETE). Addresses CTO review feedback on PR #278. * fix(api): remove CSRF middleware that breaks POST/PUT/PATCH/DELETE The CSRF middleware requires x-csrf-token header but the frontend never sends it, which would break all mutating operations with 403 errors. CSRF protection should be implemented in a separate coordinated PR with frontend changes. Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Flea Flicker <flea-flicker@groombook.ai>
12 lines
442 B
SQL
12 lines
442 B
SQL
CREATE TABLE "refunds" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
"invoice_id" uuid NOT NULL REFERENCES "invoices"("id") ON DELETE RESTRICT,
|
|
"stripe_refund_id" text NOT NULL,
|
|
"idempotency_key" text UNIQUE,
|
|
"amount_cents" integer,
|
|
"created_at" timestamp NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX "idx_refunds_invoice_id" ON "refunds"("invoice_id");
|
|
CREATE INDEX "idx_refunds_idempotency_key" ON "refunds"("idempotency_key");
|