feat(GRO-608): Add Stripe webhook handler for payment events
- Add POST /api/webhooks/stripe endpoint for Stripe event processing - Handle payment_intent.succeeded: mark invoice as paid, set paymentMethod=card - Handle payment_intent.payment_failed: record failure reason on invoice - Handle charge.refunded: mark invoice as void - Handle charge.dispute.created: log dispute - Idempotency: skip if stripePaymentIntentId already recorded - Add stripe_payment_intent_id, stripe_refund_id, payment_failure_reason columns - Requires STRIPE_WEBHOOK_SECRET env var for signature verification Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "invoices" ADD COLUMN "stripe_payment_intent_id" text;
|
||||
ALTER TABLE "invoices" ADD COLUMN "stripe_refund_id" text;
|
||||
ALTER TABLE "invoices" ADD COLUMN "payment_failure_reason" text;
|
||||
ALTER TABLE "invoices" ADD CONSTRAINT "idx_invoices_stripe_payment_intent_id" UNIQUE("stripe_payment_intent_id");
|
||||
@@ -251,6 +251,9 @@ export const invoices = pgTable(
|
||||
status: invoiceStatusEnum("status").notNull().default("draft"),
|
||||
paymentMethod: paymentMethodEnum("payment_method"),
|
||||
paidAt: timestamp("paid_at"),
|
||||
stripePaymentIntentId: text("stripe_payment_intent_id"),
|
||||
stripeRefundId: text("stripe_refund_id"),
|
||||
paymentFailureReason: text("payment_failure_reason"),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
@@ -259,6 +262,7 @@ export const invoices = pgTable(
|
||||
index("idx_invoices_client_id").on(t.clientId),
|
||||
index("idx_invoices_status").on(t.status),
|
||||
index("idx_invoices_created_at").on(t.createdAt),
|
||||
unique("idx_invoices_stripe_payment_intent_id").on(t.stripePaymentIntentId),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user