Keep payment methods synchronized (#1569)

This commit is contained in:
Luke Vella 2025-02-23 16:15:37 +00:00 committed by GitHub
parent 5e356afab6
commit ca46b18f3a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 566 additions and 346 deletions

View file

@ -10,6 +10,7 @@
"normalize-subscription-metadata": "dotenv -e ../../.env -- tsx ./src/scripts/normalize-metadata.ts",
"checkout-expiry": "dotenv -e ../../.env -- tsx ./src/scripts/checkout-expiry.ts",
"subscription-data-sync": "dotenv -e ../../.env -- tsx ./src/scripts/subscription-data-sync.ts",
"sync-payment-methods": "dotenv -e ../../.env -- tsx ./src/scripts/sync-payment-methods.ts",
"type-check": "tsc --pretty --noEmit",
"lint": "eslint ./src"
},

View file

@ -1,44 +0,0 @@
import { prisma } from "@rallly/database";
import { stripe } from "../lib/stripe";
(async function syncCancelAtPeriodEnd() {
let processed = 0;
let failed = 0;
const userSubscriptions = await prisma.subscription.findMany({
select: {
id: true,
},
});
console.info(`🚀 Syncing ${userSubscriptions.length} subscriptions...`);
for (const userSubscription of userSubscriptions) {
try {
const subscription = await stripe.subscriptions.retrieve(
userSubscription.id,
);
await prisma.subscription.update({
where: {
id: subscription.id,
},
data: {
cancelAtPeriodEnd: subscription.cancel_at_period_end,
},
});
console.info(`✅ Subscription ${subscription.id} synced`);
processed++;
} catch (error) {
console.error(
`❌ Failed to sync subscription ${userSubscription.id}:`,
error,
);
failed++;
}
}
console.info(`📊 Sync complete: ${processed} processed, ${failed} failed`);
})();

View file

@ -0,0 +1,53 @@
import type { Prisma } from "@rallly/database";
import { prisma } from "@rallly/database";
import { stripe } from "../lib/stripe";
(async function syncPaymentMethods() {
let processed = 0;
let failed = 0;
const users = await prisma.user.findMany({
select: {
id: true,
customerId: true,
email: true,
},
where: {
customerId: {
not: null,
},
},
});
console.info(`🚀 Syncing ${users.length} users...`);
for (const user of users) {
if (!user.customerId) continue;
try {
const paymentMethods = await stripe.customers.listPaymentMethods(
user.customerId,
);
await prisma.paymentMethod.createMany({
data: paymentMethods.data.map((paymentMethod) => ({
id: paymentMethod.id,
userId: user.id,
type: paymentMethod.type,
data: paymentMethod[paymentMethod.type] as Prisma.JsonObject,
})),
});
console.info(`✅ Payment methods synced for user ${user.email}`);
processed++;
} catch (error) {
console.error(
`❌ Failed to sync payment methods for user ${user.email}:`,
error,
);
failed++;
}
}
console.info(`📊 Sync complete: ${processed} processed, ${failed} failed`);
})();

View file

@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "payment_methods" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"data" JSONB NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "payment_methods_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "payment_methods" ADD CONSTRAINT "payment_methods_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -51,12 +51,13 @@ model User {
customerId String? @map("customer_id")
subscriptionId String? @unique @map("subscription_id")
comments Comment[]
polls Poll[]
watcher Watcher[]
events Event[]
accounts Account[]
participants Participant[]
comments Comment[]
polls Poll[]
watcher Watcher[]
events Event[]
accounts Account[]
participants Participant[]
paymentMethods PaymentMethod[]
subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull)
@ -82,6 +83,19 @@ enum SubscriptionInterval {
@@map("subscription_interval")
}
model PaymentMethod {
id String @id
userId String @map("user_id")
type String
data Json
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("payment_methods")
}
model UserPaymentData {
userId String @id @map("user_id")
subscriptionId String @map("subscription_id")