新手求助:支持Stripe与现金支付的SaaS订阅系统数据库设计
适配Stripe+现金支付的SaaS订阅系统数据库设计
针对你需要支持周期性订阅(月/年付)、同时兼容Stripe和面对面现金支付的需求,以下是一套模块化的数据库表设计方案,兼顾本地数据存储与Stripe关联:
核心表结构
1. 用户表 (users)
存储平台基础用户信息,同时预留Stripe关联字段:
user_id:主键(推荐UUID或自增ID)email:唯一约束,用户登录凭证name:用户姓名phone:联系电话stripe_customer_id:可选字段,仅绑定Stripe支付的用户存储该IDcreated_at:用户注册时间updated_at:信息更新时间
2. 订阅套餐表 (subscription_plans)
定义所有可售卖的订阅产品:
plan_id:主键plan_name:套餐名称(如「专业版-年付」)description:套餐描述文本price:对应计费周期的金额(如999.00元/年)billing_cycle:枚举类型,可选monthly/yearly,标记计费周期features:JSON字段,存储套餐包含的功能列表(如["API调用","数据导出"])is_active:布尔值,标记套餐是否上架可用created_at:套餐创建时间
3. 用户订阅表 (user_subscriptions)
记录用户与套餐的订阅关系,跟踪订阅生命周期:
subscription_id:主键user_id:外键,关联users.user_idplan_id:外键,关联subscription_plans.plan_idstart_date:订阅生效日期end_date:订阅到期日期status:枚举类型,可选active/canceled/expired/paused,标记订阅状态auto_renew:布尔值,标记是否自动续费(仅Stripe订阅生效)stripe_subscription_id:可选字段,存储Stripe侧的订阅ID,用于同步状态created_at:订阅创建时间updated_at:订阅信息更新时间
4. 账单表 (invoices)
每个订阅周期生成对应账单,统一管理应收款项:
invoice_id:主键subscription_id:外键,关联user_subscriptions.subscription_iduser_id:外键,关联users.user_idamount_due:应缴总金额amount_paid:已缴金额currency:货币类型(如CNY/USD)billing_period_start:账单计费周期起始时间billing_period_end:账单计费周期结束时间status:枚举类型,可选draft/issued/paid/overdue/void,标记账单状态due_date:账单到期时间stripe_invoice_id:可选字段,存储Stripe自动生成的账单IDcreated_at:账单创建时间updated_at:账单状态更新时间
5. 交易记录表 (transactions)
记录每一笔实际支付行为,区分支付方式:
transaction_id:主键invoice_id:外键,关联invoices.invoice_iduser_id:外键,关联users.user_idamount:实际交易金额payment_method:枚举类型,可选stripe/cash,标记支付方式status:枚举类型,可选success/failed/pending,标记交易状态stripe_payment_intent_id:可选字段,存储Stripe支付的Intent ID,用于对账cash_payment_note:可选字段,现金支付时记录备注(如支付时间、经办人、收款凭证编号)transaction_date:实际交易发生时间created_at:记录创建时间
关键设计要点
- 数据关联逻辑:用户→订阅→账单→交易,一条订阅对应多个周期账单,一个账单可对应多笔交易(支持部分支付后补款场景)
- Stripe同步机制:通过定时任务同步Stripe的订阅、账单、交易状态到本地数据库,确保两边数据一致;仅在本地存储Stripe的关联ID,核心业务数据保留在自己库中
- 现金支付流程:用户线下支付后,后台操作人员手动创建交易记录,标记对应账单为
paid,并更新订阅状态为active - 索引优化:在
user_id、subscription_id、invoice_id、status等高频查询字段添加索引,提升系统查询效率 - 数据一致性保障:通过业务代码或数据库触发器,确保账单的
amount_paid等于对应交易的amount总和,避免数据混乱
内容的提问来源于stack exchange,提问作者ducanh




