400 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { Client } = require('pg');
require('dotenv').config();
// 使用DATABASE_URL如果可用否则回退到个别配置
const client = new Client(
process.env.DATABASE_URL || {
host: process.env.DATABASE_HOST || 'localhost',
port: process.env.DATABASE_PORT || 5432,
database: process.env.DATABASE_NAME || 'meteor_db',
user: process.env.DATABASE_USER || 'postgres',
password: process.env.DATABASE_PASSWORD || 'password',
}
);
// 订阅计划数据
const subscriptionPlans = [
{
plan_id: 'basic-monthly',
name: '基础版(月付)',
description: '适合个人用户的基础功能',
price: 9.99,
currency: 'CNY',
interval: 'month',
interval_count: 1,
stripe_price_id: 'price_basic_monthly',
features: JSON.stringify([
'基础数据访问',
'10个设备连接',
'基础天气数据',
'标准技术支持',
'7天数据保留'
]),
is_popular: false,
is_active: true
},
{
plan_id: 'pro-monthly',
name: '专业版(月付)',
description: '适合专业用户和小团队',
price: 29.99,
currency: 'CNY',
interval: 'month',
interval_count: 1,
stripe_price_id: 'price_pro_monthly',
features: JSON.stringify([
'高级数据分析',
'无限设备连接',
'实时数据流',
'高级天气集成',
'优先技术支持',
'30天数据保留',
'自定义报告',
'API访问权限'
]),
is_popular: true,
is_active: true
},
{
plan_id: 'pro-yearly',
name: '专业版(年付)',
description: '专业版年付享受20%折扣',
price: 287.99,
currency: 'CNY',
interval: 'year',
interval_count: 1,
stripe_price_id: 'price_pro_yearly',
features: JSON.stringify([
'高级数据分析',
'无限设备连接',
'实时数据流',
'高级天气集成',
'优先技术支持',
'30天数据保留',
'自定义报告',
'API访问权限',
'年付8折优惠'
]),
is_popular: false,
is_active: true
},
{
plan_id: 'enterprise-monthly',
name: '企业版(月付)',
description: '适合大型企业和研究机构',
price: 99.99,
currency: 'CNY',
interval: 'month',
interval_count: 1,
stripe_price_id: 'price_enterprise_monthly',
features: JSON.stringify([
'企业级数据分析',
'无限设备和用户',
'实时数据流',
'完整天气数据',
'24/7专属支持',
'365天数据保留',
'高级自定义报告',
'完整API访问',
'数据导出功能',
'白标定制',
'SSO单点登录',
'专属客户经理'
]),
is_popular: false,
is_active: true
},
{
plan_id: 'free-trial',
name: '免费试用',
description: '14天免费试用专业版功能',
price: 0.00,
currency: 'CNY',
interval: 'month',
interval_count: 1,
stripe_price_id: null,
features: JSON.stringify([
'14天试用期',
'所有专业版功能',
'最多5个设备',
'基础技术支持',
'试用期数据保留'
]),
is_popular: false,
is_active: true
}
];
// 生成用户订阅数据
async function generateUserSubscriptions() {
// 获取所有用户
const usersResult = await client.query('SELECT id FROM user_profiles ORDER BY id');
const users = usersResult.rows;
if (users.length === 0) {
console.log('No users found. Skipping user subscription generation.');
return [];
}
// 获取所有订阅计划
const plansResult = await client.query('SELECT id, plan_id FROM subscription_plans WHERE is_active = true');
const plans = plansResult.rows;
const subscriptions = [];
const subscriptionStatuses = ['active', 'canceled', 'past_due', 'trialing'];
// 为部分用户创建订阅 (约70%的用户有订阅)
const usersWithSubscriptions = users.filter(() => Math.random() < 0.7);
for (const user of usersWithSubscriptions) {
const randomPlan = plans[Math.floor(Math.random() * plans.length)];
const status = subscriptionStatuses[Math.floor(Math.random() * subscriptionStatuses.length)];
const now = new Date();
const currentPeriodStart = new Date(now.getTime() - Math.random() * 30 * 24 * 60 * 60 * 1000); // 过去30天内开始
const currentPeriodEnd = new Date(currentPeriodStart.getTime() + 30 * 24 * 60 * 60 * 1000); // 30天周期
let trialStart = null;
let trialEnd = null;
let canceledAt = null;
if (status === 'trialing') {
trialStart = currentPeriodStart;
trialEnd = new Date(trialStart.getTime() + 14 * 24 * 60 * 60 * 1000); // 14天试用
}
if (status === 'canceled') {
canceledAt = new Date(currentPeriodStart.getTime() + Math.random() * 20 * 24 * 60 * 60 * 1000);
}
subscriptions.push({
user_profile_id: user.id,
subscription_plan_id: randomPlan.id,
stripe_subscription_id: `sub_${Math.random().toString(36).substr(2, 9)}`, // 模拟Stripe ID
status: status,
current_period_start: currentPeriodStart,
current_period_end: currentPeriodEnd,
cancel_at_period_end: status === 'canceled' ? true : Math.random() < 0.1,
canceled_at: canceledAt,
trial_start: trialStart,
trial_end: trialEnd
});
}
return subscriptions;
}
// 生成订阅历史记录
function generateSubscriptionHistory(subscriptionId, subscriptionData) {
const history = [];
const actions = ['created', 'updated', 'renewed', 'payment_failed'];
// 创建记录
history.push({
user_subscription_id: subscriptionId,
action: 'created',
old_status: null,
new_status: 'active',
metadata: JSON.stringify({
created_by: 'system',
payment_method: 'card'
})
});
// 添加一些随机历史记录
const numHistory = Math.floor(Math.random() * 3) + 1; // 1-3条记录
for (let i = 0; i < numHistory; i++) {
const action = actions[Math.floor(Math.random() * actions.length)];
let oldStatus = 'active';
let newStatus = 'active';
if (action === 'payment_failed') {
oldStatus = 'active';
newStatus = 'past_due';
} else if (action === 'renewed') {
oldStatus = 'past_due';
newStatus = 'active';
}
history.push({
user_subscription_id: subscriptionId,
action: action,
old_status: oldStatus,
new_status: newStatus,
metadata: JSON.stringify({
timestamp: new Date().toISOString(),
source: 'stripe_webhook'
})
});
}
return history;
}
// 生成支付记录
function generatePaymentRecords(subscriptionId, subscriptionData) {
const payments = [];
const paymentStatuses = ['succeeded', 'failed', 'pending'];
const paymentMethods = ['card', 'alipay', 'wechat_pay'];
// 生成1-5条支付记录
const numPayments = Math.floor(Math.random() * 5) + 1;
for (let i = 0; i < numPayments; i++) {
const status = paymentStatuses[Math.floor(Math.random() * paymentStatuses.length)];
const amount = 29.99 + Math.random() * 70; // 随机金额
let paidAt = null;
let failureReason = null;
if (status === 'succeeded') {
paidAt = new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000);
} else if (status === 'failed') {
failureReason = ['card_declined', 'insufficient_funds', 'expired_card'][Math.floor(Math.random() * 3)];
}
payments.push({
user_subscription_id: subscriptionId,
stripe_payment_intent_id: `pi_${Math.random().toString(36).substr(2, 9)}`,
amount: amount,
currency: 'CNY',
status: status,
payment_method: paymentMethods[Math.floor(Math.random() * paymentMethods.length)],
failure_reason: failureReason,
paid_at: paidAt
});
}
return payments;
}
async function seedSubscriptionData() {
try {
await client.connect();
console.log('Connected to database');
// 清空现有数据
console.log('Clearing existing subscription data...');
await client.query('DELETE FROM payment_records');
await client.query('DELETE FROM subscription_history');
await client.query('DELETE FROM user_subscriptions');
await client.query('DELETE FROM subscription_plans');
// 插入订阅计划数据
console.log('Inserting subscription plans...');
const planIds = [];
for (const plan of subscriptionPlans) {
const query = `
INSERT INTO subscription_plans (
plan_id, name, description, price, currency, interval, interval_count,
stripe_price_id, features, is_popular, is_active
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id
`;
const values = [
plan.plan_id, plan.name, plan.description, plan.price, plan.currency,
plan.interval, plan.interval_count, plan.stripe_price_id, plan.features,
plan.is_popular, plan.is_active
];
const result = await client.query(query, values);
const planId = result.rows[0].id;
planIds.push(planId);
console.log(`Inserted subscription plan: ${plan.name} (ID: ${planId})`);
}
// 生成用户订阅数据
console.log('Generating user subscriptions...');
const subscriptions = await generateUserSubscriptions();
for (const subscription of subscriptions) {
const query = `
INSERT INTO user_subscriptions (
user_profile_id, subscription_plan_id, stripe_subscription_id, status,
current_period_start, current_period_end, cancel_at_period_end,
canceled_at, trial_start, trial_end
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id
`;
const values = [
subscription.user_profile_id, subscription.subscription_plan_id,
subscription.stripe_subscription_id, subscription.status,
subscription.current_period_start, subscription.current_period_end,
subscription.cancel_at_period_end, subscription.canceled_at,
subscription.trial_start, subscription.trial_end
];
const result = await client.query(query, values);
const subscriptionId = result.rows[0].id;
// 生成订阅历史记录
const history = generateSubscriptionHistory(subscriptionId, subscription);
for (const record of history) {
const historyQuery = `
INSERT INTO subscription_history (
user_subscription_id, action, old_status, new_status, metadata
) VALUES ($1, $2, $3, $4, $5)
`;
const historyValues = [
record.user_subscription_id, record.action, record.old_status,
record.new_status, record.metadata
];
await client.query(historyQuery, historyValues);
}
// 生成支付记录
const payments = generatePaymentRecords(subscriptionId, subscription);
for (const payment of payments) {
const paymentQuery = `
INSERT INTO payment_records (
user_subscription_id, stripe_payment_intent_id, amount, currency,
status, payment_method, failure_reason, paid_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`;
const paymentValues = [
payment.user_subscription_id, payment.stripe_payment_intent_id,
payment.amount, payment.currency, payment.status, payment.payment_method,
payment.failure_reason, payment.paid_at
];
await client.query(paymentQuery, paymentValues);
}
console.log(`Generated subscription for user ${subscription.user_profile_id} with ${history.length} history records and ${payments.length} payment records`);
}
console.log('Subscription data seeding completed successfully!');
// 显示统计信息
const planCount = await client.query('SELECT COUNT(*) FROM subscription_plans');
const subscriptionCount = await client.query('SELECT COUNT(*) FROM user_subscriptions');
const historyCount = await client.query('SELECT COUNT(*) FROM subscription_history');
const paymentCount = await client.query('SELECT COUNT(*) FROM payment_records');
console.log(`Total subscription plans: ${planCount.rows[0].count}`);
console.log(`Total user subscriptions: ${subscriptionCount.rows[0].count}`);
console.log(`Total history records: ${historyCount.rows[0].count}`);
console.log(`Total payment records: ${paymentCount.rows[0].count}`);
} catch (error) {
console.error('Error seeding subscription data:', error);
process.exit(1);
} finally {
await client.end();
console.log('Database connection closed');
}
}
// 如果直接运行此脚本
if (require.main === module) {
seedSubscriptionData();
}
module.exports = { seedSubscriptionData };