/** * 用户订阅管理相关表 */ 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'); };