feat: remove planId from payment and subscription schema
- Created new SQL tables for account, payment, session, user, and verification to support user management and payment processing. - Added foreign key constraints to link account, payment, and session tables to the user table for data integrity. - Updated journal and snapshot metadata to reflect the new schema changes.
This commit is contained in:
parent
f982e1b01a
commit
313625577c
@ -16,7 +16,6 @@ CREATE TABLE "account" (
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "payment" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"plan_id" text NOT NULL,
|
||||
"price_id" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"interval" text,
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "4e932ffc-faf7-4223-b04c-382bf773e626",
|
||||
"id": "7ecbd97a-94eb-4a46-996e-dbff727fc0c7",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@ -119,12 +119,6 @@
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_id": {
|
||||
"name": "plan_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"price_id": {
|
||||
"name": "price_id",
|
||||
"type": "text",
|
||||
|
@ -5,8 +5,8 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1744167230793,
|
||||
"tag": "0000_public_mongoose",
|
||||
"when": 1744304844165,
|
||||
"tag": "0000_fine_sir_ram",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
@ -3,7 +3,7 @@
|
||||
import db from "@/db";
|
||||
import { payment } from "@/db/schema";
|
||||
import { getSession } from "@/lib/server";
|
||||
import { getAllPricePlans } from "@/lib/price-plan";
|
||||
import { getAllPricePlans, findPlanByPriceId } from "@/lib/price-plan";
|
||||
import { PaymentTypes } from "@/payment/types";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
@ -66,7 +66,7 @@ export const getLifetimeStatusAction = actionClient
|
||||
|
||||
// Query the database for one-time payments with lifetime plans
|
||||
const result = await db
|
||||
.select({ id: payment.id, planId: payment.planId, type: payment.type })
|
||||
.select({ id: payment.id, priceId: payment.priceId, type: payment.type })
|
||||
.from(payment)
|
||||
.where(
|
||||
and(
|
||||
@ -77,9 +77,10 @@ export const getLifetimeStatusAction = actionClient
|
||||
);
|
||||
|
||||
// Check if any payment has a lifetime plan
|
||||
const hasLifetimePayment = result.some(paymentRecord =>
|
||||
lifetimePlanIds.includes(paymentRecord.planId)
|
||||
);
|
||||
const hasLifetimePayment = result.some(paymentRecord => {
|
||||
const plan = findPlanByPriceId(paymentRecord.priceId);
|
||||
return plan && lifetimePlanIds.includes(plan.id);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
@ -54,7 +54,6 @@ export const verification = pgTable("verification", {
|
||||
|
||||
export const payment = pgTable("payment", {
|
||||
id: text("id").primaryKey(),
|
||||
planId: text('plan_id').notNull(),
|
||||
priceId: text('price_id').notNull(),
|
||||
type: text('type').notNull(),
|
||||
interval: text('interval'),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import db from '@/db';
|
||||
import { payment, user } from '@/db/schema';
|
||||
import { payment, session, user } from '@/db/schema';
|
||||
import { findPlanByPriceId, findPriceInPlan, findPlanByPlanId } from '@/lib/price-plan';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { desc, eq } from 'drizzle-orm';
|
||||
@ -145,11 +145,6 @@ export class StripeProvider implements PaymentProvider {
|
||||
throw new Error(`Plan with ID ${planId} not found`);
|
||||
}
|
||||
|
||||
// Free plan doesn't need a checkout session
|
||||
if (plan.isFree) {
|
||||
throw new Error('Cannot create checkout session for free plan');
|
||||
}
|
||||
|
||||
// Find price in plan
|
||||
const price = findPriceInPlan(planId, priceId);
|
||||
if (!price) {
|
||||
@ -270,9 +265,8 @@ export class StripeProvider implements PaymentProvider {
|
||||
return subscriptions.map(subscription => ({
|
||||
id: subscription.subscriptionId || '',
|
||||
customerId: subscription.customerId,
|
||||
status: subscription.status as PaymentStatus,
|
||||
planId: subscription.planId,
|
||||
priceId: subscription.priceId,
|
||||
status: subscription.status as PaymentStatus,
|
||||
type: subscription.type as PaymentTypes,
|
||||
interval: subscription.interval as PlanInterval,
|
||||
currentPeriodStart: subscription.periodStart || undefined,
|
||||
@ -355,38 +349,16 @@ export class StripeProvider implements PaymentProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// get userId from metadata or find it by customerId from database
|
||||
let userId = stripeSubscription.metadata.userId;
|
||||
// get userId from metadata, we add it in the createCheckout session
|
||||
const userId = stripeSubscription.metadata.userId;
|
||||
if (!userId) {
|
||||
const foundUserId = await this.findUserIdByCustomerId(customerId);
|
||||
if (!foundUserId) {
|
||||
console.warn(`<< No user found for customer ${customerId}, skipping payment record creation`);
|
||||
return;
|
||||
}
|
||||
userId = foundUserId;
|
||||
console.log(`Found userId ${userId} for customer ${customerId} from database`);
|
||||
} else {
|
||||
console.log(`Using userId ${userId} from subscription metadata`);
|
||||
console.warn(`<< No userId found for subscription ${stripeSubscription.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// get planId from metadata or find it by priceId from payment config
|
||||
let planId = stripeSubscription.metadata.planId;
|
||||
if (!planId) {
|
||||
const foundPlan = findPlanByPriceId(priceId);
|
||||
if (!foundPlan) {
|
||||
console.warn(`<< No plan found for price ${priceId}, skipping payment record creation`);
|
||||
return;
|
||||
}
|
||||
planId = foundPlan.id;
|
||||
console.log(`Found planId ${planId} for price ${priceId} from config`);
|
||||
} else {
|
||||
console.log(`Using planId ${planId} from subscription metadata`);
|
||||
}
|
||||
|
||||
// prepare create fields
|
||||
// create fields
|
||||
const createFields: any = {
|
||||
id: randomUUID(),
|
||||
planId: planId,
|
||||
priceId: priceId,
|
||||
type: PaymentTypes.SUBSCRIPTION,
|
||||
userId: userId,
|
||||
@ -432,21 +404,7 @@ export class StripeProvider implements PaymentProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// we can not trust the planId from metadata when updating subscription, so get it from config
|
||||
// why? because user may update subscription in Stripe Customer Portal, and the planId is not in metadata
|
||||
let planId;
|
||||
let shouldUpdatePlanId = false;
|
||||
const foundPlan = findPlanByPriceId(priceId);
|
||||
if (!foundPlan) {
|
||||
shouldUpdatePlanId = false;
|
||||
console.warn(`No plan found for price ${priceId}, did you update the plans and prices in payment config?`);
|
||||
} else {
|
||||
planId = foundPlan.id;
|
||||
shouldUpdatePlanId = true;
|
||||
console.log(`Found planId ${planId} for price ${priceId} from config`);
|
||||
}
|
||||
|
||||
// prepare update fields
|
||||
// update fields
|
||||
const updateFields: any = {
|
||||
priceId: priceId,
|
||||
interval: this.mapStripeIntervalToPlanInterval(stripeSubscription),
|
||||
@ -463,12 +421,6 @@ export class StripeProvider implements PaymentProvider {
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
// Only include planId if it should be updated
|
||||
if (shouldUpdatePlanId && planId) {
|
||||
updateFields.planId = planId;
|
||||
}
|
||||
console.log('updateFields', updateFields);
|
||||
|
||||
const result = await db
|
||||
.update(payment)
|
||||
.set(updateFields)
|
||||
@ -527,20 +479,12 @@ export class StripeProvider implements PaymentProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// get planId from session metadata, we add it in the createCheckout session
|
||||
const planId = session.metadata?.planId;
|
||||
if (!planId) {
|
||||
console.warn(`<< No planId found for checkout session ${session.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a one-time payment record
|
||||
const now = new Date();
|
||||
const result = await db
|
||||
.insert(payment)
|
||||
.values({
|
||||
id: randomUUID(),
|
||||
planId: planId,
|
||||
priceId: priceId,
|
||||
type: PaymentTypes.ONE_TIME,
|
||||
userId: userId,
|
||||
@ -556,7 +500,7 @@ export class StripeProvider implements PaymentProvider {
|
||||
console.warn(`<< Failed to create one-time payment record for user ${userId}`);
|
||||
return;
|
||||
} else {
|
||||
console.log(`<< Created one-time payment record for user ${userId}, plan: ${planId}`);
|
||||
console.log(`<< Created one-time payment record for user ${userId}, price: ${priceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,6 @@ export interface Subscription {
|
||||
id: string;
|
||||
customerId: string;
|
||||
status: PaymentStatus;
|
||||
planId: string;
|
||||
priceId: string;
|
||||
type: PaymentType;
|
||||
interval?: PlanInterval;
|
||||
|
@ -102,7 +102,8 @@ export const usePaymentStore = create<PaymentState>((set, get) => ({
|
||||
|
||||
// Set subscription state
|
||||
if (activeSubscription) {
|
||||
const plan = plans.find(p => p.id === activeSubscription.planId) || null;
|
||||
const plan = plans.find(p => p.prices.find(price =>
|
||||
price.priceId === activeSubscription.priceId)) || null;
|
||||
console.log('subscription found, setting plan for user', user.id, plan?.id);
|
||||
set({
|
||||
currentPlan: plan,
|
||||
|
Loading…
Reference in New Issue
Block a user