220 lines
5.7 KiB
JavaScript
220 lines
5.7 KiB
JavaScript
/**
|
|
* 用户订阅管理相关表
|
|
*/
|
|
|
|
export const up = (pgm) => {
|
|
// 1. 订阅计划表
|
|
pgm.createTable('subscription_plans', {
|
|
id: { type: 'serial', primaryKey: true },
|
|
plan_id: {
|
|
type: 'varchar(100)',
|
|
unique: true,
|
|
notNull: true,
|
|
comment: '计划唯一标识符'
|
|
},
|
|
name: {
|
|
type: 'varchar(255)',
|
|
notNull: true,
|
|
comment: '计划名称'
|
|
},
|
|
description: {
|
|
type: 'text',
|
|
comment: '计划描述'
|
|
},
|
|
price: {
|
|
type: 'decimal(10,2)',
|
|
notNull: true,
|
|
comment: '价格'
|
|
},
|
|
currency: {
|
|
type: 'varchar(3)',
|
|
notNull: true,
|
|
default: 'CNY',
|
|
comment: '货币类型'
|
|
},
|
|
interval: {
|
|
type: 'varchar(50)',
|
|
notNull: true,
|
|
comment: 'month, year, week'
|
|
},
|
|
interval_count: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
default: 1,
|
|
comment: '间隔数量'
|
|
},
|
|
stripe_price_id: {
|
|
type: 'varchar(255)',
|
|
comment: 'Stripe价格ID'
|
|
},
|
|
features: {
|
|
type: 'jsonb',
|
|
comment: '功能列表JSON'
|
|
},
|
|
is_popular: {
|
|
type: 'boolean',
|
|
default: false,
|
|
comment: '是否为推荐计划'
|
|
},
|
|
is_active: {
|
|
type: 'boolean',
|
|
notNull: true,
|
|
default: true,
|
|
comment: '是否启用'
|
|
},
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') },
|
|
updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') }
|
|
});
|
|
|
|
// 2. 用户订阅表
|
|
pgm.createTable('user_subscriptions', {
|
|
id: { type: 'serial', primaryKey: true },
|
|
user_profile_id: {
|
|
type: 'uuid',
|
|
notNull: true,
|
|
references: 'user_profiles(id)',
|
|
onDelete: 'CASCADE'
|
|
},
|
|
subscription_plan_id: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
references: 'subscription_plans(id)',
|
|
onDelete: 'RESTRICT'
|
|
},
|
|
stripe_subscription_id: {
|
|
type: 'varchar(255)',
|
|
unique: true,
|
|
comment: 'Stripe订阅ID'
|
|
},
|
|
status: {
|
|
type: 'varchar(50)',
|
|
notNull: true,
|
|
comment: 'active, canceled, past_due, trialing, incomplete'
|
|
},
|
|
current_period_start: {
|
|
type: 'timestamptz',
|
|
notNull: true,
|
|
comment: '当前周期开始时间'
|
|
},
|
|
current_period_end: {
|
|
type: 'timestamptz',
|
|
notNull: true,
|
|
comment: '当前周期结束时间'
|
|
},
|
|
cancel_at_period_end: {
|
|
type: 'boolean',
|
|
default: false,
|
|
comment: '是否在周期结束时取消'
|
|
},
|
|
canceled_at: {
|
|
type: 'timestamptz',
|
|
comment: '取消时间'
|
|
},
|
|
trial_start: {
|
|
type: 'timestamptz',
|
|
comment: '试用开始时间'
|
|
},
|
|
trial_end: {
|
|
type: 'timestamptz',
|
|
comment: '试用结束时间'
|
|
},
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') },
|
|
updated_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') }
|
|
});
|
|
|
|
// 3. 订阅历史记录表
|
|
pgm.createTable('subscription_history', {
|
|
id: { type: 'serial', primaryKey: true },
|
|
user_subscription_id: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
references: 'user_subscriptions(id)',
|
|
onDelete: 'CASCADE'
|
|
},
|
|
action: {
|
|
type: 'varchar(100)',
|
|
notNull: true,
|
|
comment: 'created, updated, canceled, renewed, payment_failed'
|
|
},
|
|
old_status: {
|
|
type: 'varchar(50)',
|
|
comment: '原状态'
|
|
},
|
|
new_status: {
|
|
type: 'varchar(50)',
|
|
comment: '新状态'
|
|
},
|
|
metadata: {
|
|
type: 'jsonb',
|
|
comment: '额外信息JSON'
|
|
},
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') }
|
|
});
|
|
|
|
// 4. 支付记录表
|
|
pgm.createTable('payment_records', {
|
|
id: { type: 'serial', primaryKey: true },
|
|
user_subscription_id: {
|
|
type: 'integer',
|
|
notNull: true,
|
|
references: 'user_subscriptions(id)',
|
|
onDelete: 'CASCADE'
|
|
},
|
|
stripe_payment_intent_id: {
|
|
type: 'varchar(255)',
|
|
unique: true,
|
|
comment: 'Stripe支付意向ID'
|
|
},
|
|
amount: {
|
|
type: 'decimal(10,2)',
|
|
notNull: true,
|
|
comment: '支付金额'
|
|
},
|
|
currency: {
|
|
type: 'varchar(3)',
|
|
notNull: true,
|
|
comment: '货币类型'
|
|
},
|
|
status: {
|
|
type: 'varchar(50)',
|
|
notNull: true,
|
|
comment: 'succeeded, failed, pending, canceled'
|
|
},
|
|
payment_method: {
|
|
type: 'varchar(100)',
|
|
comment: '支付方式'
|
|
},
|
|
failure_reason: {
|
|
type: 'varchar(255)',
|
|
comment: '失败原因'
|
|
},
|
|
paid_at: {
|
|
type: 'timestamptz',
|
|
comment: '支付成功时间'
|
|
},
|
|
created_at: { type: 'timestamptz', notNull: true, default: pgm.func('current_timestamp') }
|
|
});
|
|
|
|
// 添加索引
|
|
pgm.createIndex('subscription_plans', 'plan_id');
|
|
pgm.createIndex('subscription_plans', 'is_active');
|
|
pgm.createIndex('user_subscriptions', 'user_profile_id');
|
|
pgm.createIndex('user_subscriptions', 'status');
|
|
pgm.createIndex('user_subscriptions', 'stripe_subscription_id');
|
|
pgm.createIndex('subscription_history', 'user_subscription_id');
|
|
pgm.createIndex('payment_records', 'user_subscription_id');
|
|
pgm.createIndex('payment_records', 'status');
|
|
|
|
// 添加唯一约束:一个用户同时只能有一个活跃订阅
|
|
pgm.addConstraint('user_subscriptions', 'unique_active_subscription', {
|
|
unique: ['user_profile_id'],
|
|
where: "status IN ('active', 'trialing')"
|
|
});
|
|
};
|
|
|
|
export const down = (pgm) => {
|
|
pgm.dropTable('payment_records');
|
|
pgm.dropTable('subscription_history');
|
|
pgm.dropTable('user_subscriptions');
|
|
pgm.dropTable('subscription_plans');
|
|
}; |