mysql如何设计一个简单的支付接口系统_mysql电商支付设计

3次阅读

支付接口需幂等性控制,推荐用select for UPDATE加唯一索引拦截重复支付;订单与资金流水须分离存储;库存与余额扣减须事务内原子操作;敏感信息必须Token化处理。

mysql如何设计一个简单的支付接口系统_mysql电商支付设计

支付接口必须有幂等性控制,否则重复请求会扣多次钱

用户点击“支付”后网络卡顿,前端重试、浏览器刷新、后端超时重发——这些都会导致同一笔订单被多次调用 pay_order()mysql 不能只靠应用层判断“订单是否已支付”,必须在数据库层面拦截重复执行。

推荐做法是:在订单表加唯一索引 UNIQUE KEY uk_order_no_status (order_no, status),但只对 status = 'paid' 生效(MySQL 8.0+ 支持函数索引;5.7 可改用生成列或业务层配合 INSERT ... ON DUPLICATE KEY UPDATE)。

更稳妥的实操方式:

  • 支付前先 SELECT ... FOR UPDATE 锁住该 order_no 行(注意:必须走主键或唯一索引,否则会锁表)
  • 查出当前 status,若已是 'paid' 直接返回成功,不更新
  • 若为 'unpaid',再执行 UPDATE orders SET status = 'paid', paid_at = NOW() WHERE order_no = ? AND status = 'unpaid'
  • 检查 ROW_COUNT() 是否为 1,不是则说明已被并发更新,拒绝本次支付

订单表要分离交易流水,别把支付结果直接写进 orders 表

一个订单可能经历「支付宝支付」「微信退款」「人工冲正」多次资金变动,如果所有状态和金额都orders 表里,字段会越来越难维护,审计也无从下手。

正确拆分方式:

  • orders 表只存业务单据信息:order_nouser_idtotal_amountstatus(仅表示订单整体状态,如 created/paid/cancelled)
  • 新建 payment_transactions 表,记录每一笔资金动作:tx_id(支付平台返回的交易号)、order_nochannel(alipay/wechat)、amounttype(pay/refund/adjust)、status(processing/success/fail)、created_at
  • orders.total_amountorders.paid_amount 应由定时任务或触发器基于 payment_transactions 汇总更新,而非手动 set

这样设计后,查某笔订单的所有资金流只需 SELECT * FROM payment_transactions WHERE order_no = ? ORDER BY created_at,清晰可追溯。

防超卖和余额不足必须用 SELECT FOR UPDATE + UPDATE 原子组合

用户支付时,要同时扣减商品库存和用户账户余额。这两个操作跨表、跨行,又必须全部成功或全部失败——MySQL 的事务能保证原子性,但得用对姿势。

常见错误是先 SELECT stock,再判断,再 UPDATE。这中间有竞态窗口,高并发下必然超卖。

正确顺序(在同一个事务内):

  • SELECT stock, locked_stock FROM products WHERE product_id = ? FOR UPDATE
  • 检查 stock - locked_stock >= order_quantity,不满足则 rollback
  • UPDATE products SET locked_stock = locked_stock + ? WHERE product_id = ?
  • SELECT balance FROM user_accounts WHERE user_id = ? FOR UPDATE
  • 检查 balance >= total_amount,不满足则 rollback
  • UPDATE user_accounts SET balance = balance - ? WHERE user_id = ?

注意:FOR UPDATE 必须在 UPDATE 之前,并且所有涉及行都要在同一事务中锁定。否则释放锁后别人就可能修改。

不要用 MySQL 存敏感支付信息,token 化才是合规做法

银行卡号、CVV、微信 openid、支付宝 user_id —— 这些都不能明文落库,哪怕加了 AES 加密也不符合 PCI DSS 和国内《个人信息保护法》要求。

实操方案只有两个可行路径:

  • 支付渠道返回的 pay_tokenprepay_id 可以存,它们是渠道颁发的、有时效、可作废的临时凭证
  • 用户绑定卡信息交由合规的第三方支付网关托管(如支付宝的 bind_card 接口),你只存一个 binding_id,后续扣款用这个 ID 去调用网关

哪怕只是记录“用户选了微信支付”,也要避免在日志或数据库里拼接出完整 openid 字符串。真正容易被忽略的是:开发环境的 SQL dump、慢查询日志、监控埋点,都可能意外泄露这类字段。

text=ZqhQzanResources