Stripe出于PCI DSS合规要求,永久屏蔽对完整信用卡号、CVC等敏感字段的访问;创建Token后仅返回last4、brand、exp_month/year等脱敏信息,开发者无法通过API检索原始卡号。

为什么不能直接在前端调用 Stripe.createToken?
因为 Stripe.createToken 需要前端传入敏感卡信息,但若把 stripe.publishableKey 和卡号一起发到后端再转发给 Stripe,就等于让服务端接触了原始卡数据——这违反 Stripe 的 PCI 合规要求,也触发严格审计。正确路径是:前端用 stripe.js 直接向 Stripe 请求一次性 token,后端只处理这个 token,绝不碰卡号、CVV 或完整卡信息。
常见错误现象:Invalid request: Cannot create token without card,往往是因为前端没正确构造 card 对象,或传了空值;400 Bad Request 从后端发给 Stripe,则大概率是后端误传了卡字段(比如把 number 当作参数发给了 /v1/charges)。
- 确保前端只调用
stripe.createToken({number, exp_month, exp_year, cvc}),且所有字段来自用户输入的表单(不要预填、不要缓存) - 后端接收的只能是前端返回的
token.id(形如tok_1P...xY),不是卡号 - 后端发起
stripe.Charge.create()时,source参数必须设为该token.id,而非任何卡字段
Flask 路由如何安全接收并验证 Stripe token?
不能信任任何前端传来的 token,必须做三件事:校验非空、检查格式(以 tok_ 开头)、绑定业务逻辑(比如确认该 token 对应的订单未支付且金额匹配)。否则攻击者可重放 token 或篡改金额。
示例场景:用户提交 99 元订单,后端收到 token=tok_1Q...,但没核对数据库中该订单状态和金额,直接发起扣款——就可能被重复使用同一 token 扣多次,或恶意替换为更高金额订单。
立即学习“Python免费学习笔记(深入)”;
- 用
request.get_json()或request.form.get('stripeToken')获取 token,立刻判空并拒绝空值 - 用正则
^tok_[a-zA-Z0-9_]+$初筛 token 格式(Stripe 官方不承诺格式不变,但当前稳定以tok_开头) - 查库确认该 token 关联的订单存在、状态为
pending、金额与请求一致——这步必须在调用stripe.Charge.create()前完成 - 建议在数据库订单表加
stripe_token字段并设唯一索引,防止同一 token 被多次消费
如何避免 Flask 中的同步阻塞导致支付超时?
Stripe 的 stripe.Charge.create() 是同步 HTTP 调用,默认超时 80 秒。若 Flask 主线程卡住(比如数据库慢、网络抖动),用户会看到白屏或 504,而 Stripe 实际可能已扣款成功——造成“用户以为失败,实际已扣钱”的严重不一致。
这不是并发问题,而是单次请求的可靠性问题。简单加 try/except 不够,关键在超时控制和幂等性保障。
- 显式设置
stripe.api_timeout = 10(单位秒),避免长等待;同时捕获stripe.error.CardError、stripe.error.RateLimitError等具体异常,分别处理 - 给每次
Charge.create()加idempotency_key参数(如订单 ID + 时间戳哈希),确保相同 key 的重复请求只产生一笔交易 - 不要在视图函数里直接写 “扣款成功 → 发邮件 → 更新库存”,而应先落库标记为
charging,再异步处理后续动作(用celery或至少threading.Thread)
Webhook 验证为何不能只比对 signature header?
仅检查 Stripe-Signature header 是否存在,或只用硬编码密钥解签,极易被伪造。Stripe webhook 的签名含时间戳和 payload,目的是防重放和篡改,跳过验证等于把支付结果开关交给攻击者。
典型翻车点:本地调试时用 ngrok 转发 webhook,但没配好 stripe.Webhook.construct_event() 的 payload 和 sig_header,导致 SignatureVerificationError;或者漏掉对 event.type 的严格白名单过滤(比如允许 charge.refunded 但没限制只处理 charge.succeeded)。
- 必须用
request.get_data()原始 body(不能用request.json,会破坏签名)传给stripe.Webhook.construct_event() -
endpoint_secret必须从 Stripe Dashboard 复制,且存为环境变量(os.environ['STRIPE_WEBHOOK_SECRET']),绝不能硬编码 - 验证通过后,立即检查
event.type in ['charge.succeeded', 'payment_intent.succeeded'],其他类型(如invoice.payment_succeeded)需单独逻辑,不可一概而论 - 更新订单状态前,先用
stripe.Charge.retrieve(event.data.object.id)再次确认金额、订单号、状态,防止 event 数据被中间篡改


















