共计 1921 个字符,预计需要花费 5 分钟才能阅读完成。
背景与痛点
在运营 ChatGPT Plus 订阅服务时,手动管理订阅会遇到几个主要问题:

- 支付状态延迟 :用户完成支付后,系统无法立即更新订阅状态,导致用户体验下降。
- 配额超限 :缺乏实时配额监控,用户可能超出订阅限制而不自知。
- 续费失败 :手动续费流程复杂,容易出现失败情况,影响服务连续性。
技术选型
在支付网关的选择上,我们对比了 Stripe 和 PayPal 的 Webhook 实现:
- Stripe:
- 提供丰富的事件类型
- 支持签名验证,安全性高
-
文档详细,社区支持好
-
PayPal:
- 集成相对复杂
- 事件类型较少
- 需要额外处理 IPN(即时支付通知)
基于这些比较,我们选择了 Stripe 作为支付网关。
核心架构设计
1. 用户授权
使用 OAuth 2.0 实现用户授权流程:
- 用户点击订阅按钮
- 跳转到授权页面
- 获取 access token
- 存储用户订阅信息
2. 配额计数器
基于 Redis 设计配额计数器:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
def check_quota(user_id):
key = f"quota:{user_id}"
current = r.get(key)
if current and int(current) >= QUOTA_LIMIT:
return False
return True
3. 自动续费处理
确保自动续费的幂等性:
def handle_renewal(event):
idempotency_key = event['idempotency_key']
if is_processed(idempotency_key):
return
# 处理续费逻辑
process_renewal(event)
# 记录处理状态
mark_as_processed(idempotency_key)
代码实现
Webhook 处理器示例
from flask import Flask, request, jsonify
import stripe
import hashlib
import hmac
app = Flask(__name__)
# 验证 Stripe Webhook 签名
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.data
sig_header = request.headers.get('Stripe-Signature')
try:
event = stripe.Webhook.construct_event(payload, sig_header, WEBHOOK_SECRET)
except ValueError as e:
# 无效 payload
return jsonify({'error': str(e)}), 400
except stripe.error.SignatureVerificationError as e:
# 无效签名
return jsonify({'error': str(e)}), 400
# 处理事件
if event['type'] == 'invoice.payment_succeeded':
handle_payment_success(event)
elif event['type'] == 'invoice.payment_failed':
handle_payment_failure(event)
return jsonify({'status': 'success'})
生产环境考量
1. 防重放攻击
使用消息队列确保事件只处理一次:
- 为每个事件生成唯一 ID
- 在处理前检查是否已存在
- 使用 TTL 自动清理旧记录
2. 并发控制
使用 Redis 的原子操作保证配额计算的准确性:
def deduct_quota(user_id):
key = f"quota:{user_id}"
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, 86400) # 24 小时 TTL
pipe.execute()
3. 监控指标
埋点方案示例:
- 成功支付次数
- 失败支付次数
- 配额使用率
- 续费成功率
避坑指南
- 时区问题 :
- 问题:不同地区服务器时区不一致导致续费时间计算错误
-
解决:统一使用 UTC 时间存储和计算
-
事件重复处理 :
- 问题:网络延迟可能导致 Webhook 重复发送
-
解决:使用 Idempotency-Key 确保幂等性
-
配额计算延迟 :
- 问题:高并发下配额计算可能出现偏差
- 解决:使用 Redis 的原子操作或分布式锁
开放式问题
当用户同时发起 API 调用和订阅变更时,如何设计分布式锁策略?可以考虑以下方向:
- 基于用户 ID 的细粒度锁
- 使用 Redis 的 Redlock 算法
- 考虑锁的过期时间以避免死锁
这个问题的解决方案需要根据具体的业务场景和技术栈来选择最合适的实现方式。
正文完
