Coupon 与促销码控制:核销预留、叠加顺序和退款回补
两个优惠都合法,不代表可以叠加;看到还剩一次,也不代表高并发结账能共同成功。本文版本化资格、互斥组、计算顺序与核销触发,用预留账本和供应商收据控制超发、未知结账和退款回补。
本文要点
- 主题聚焦:DevOps、独立开发
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
营销创建“首年八折”和“开发者大会减 50 美元”,结账页发现两个对象都有效便依次相减;另有一张限量 100 次的码,在最后一秒被 20 个请求同时看到“剩余 1 次”。如果支付响应丢失,客户端换一个 checkout ID 重试,还可能既占两次名额又拿两次折扣。Coupon 定义折扣能力,Promotion Code 提供面向客户的兑换限制;真正的风险在两者与订单、订阅和资金状态的交界。
AI 定价迁移负责基础价格和老客户 cohort,开发者 Referral 控制处理推荐归因与奖励。本篇只治理一个促销如何取得资格、与其他促销组合、绑定一次商业操作并消耗额度;它不允许折扣覆盖未经批准的价格版本。
冻结 promotionPolicy.v1 和组合图
发布不可变 promotion.policy.v1,至少绑定内部 promotion ID、provider coupon/promotion-code ID 与摘要、优惠类型和数值、币种、适用 product/price/cohort、客户资格、最小金额、有效 [start,end)、总量与单主体上限、duration、consume trigger、退款回补规则、互斥 group、可组合集合、计算优先级、分摊与舍入规则。展示字符串和用户输入 code 不是身份;规范化后的 code 只用于查找,授权引用不可变 promotion generation。
schema: promotion.policy.v1
promotion: conf-2026@4
benefit: {type: FIXED_AMOUNT, amountMinor: 5000, currency: USD}
scope: {prices: [pro-annual@7], cohort: new-paid-customer@3}
valid: {start: 2026-08-01T00:00:00Z, end: 2026-09-01T00:00:00Z}
limits: {global: 1000, perCustomer: 1}
exclusiveGroup: acquisition-offer
stackOrder: 20
consumeOn: INVOICE_FINALIZED
组合不是“按用户点击顺序执行”。Evaluator 先固定 cart/price/tax-input digest,再按 policy generation 构建有向顺序并输出 composition root:例如先 item percent、再分摊固定金额,最后应用不可组合的账户 credit。每一步使用整数最小单位或明确十进制定点,固定优惠不能把符合范围的小计降到负数;舍入余数按稳定 line ID 分配。税前还是税后由获批准税务配置决定,不能为了匹配页面总价临时换顺序。
Stripe coupon 可定义 percent-off 或 amount-off,promotion code 还能限制首次交易、最低金额、过期和兑换次数;平台可用不证明本地资格与叠加合法。每个 checkout 只输出一份 promotionDecision.v1,绑定全体候选的 eligible/rejected 原因、规范 benefit lines、policy/composition root、cart digest 和 expiresAt,页面不得逐个重算。
额度先预留,折扣凭发票收据接受
核销状态为 ELIGIBLE -> RESERVED -> PROVIDER_PENDING -> APPLIED -> CONSUMED,可进入 RELEASED/EXPIRED/REVERSED/PROVIDER_UNKNOWN。预留以 checkoutOperation 唯一,绑定 subject、promotionDecision/composition root、cart digest 与整组 promotion generations;一个串行化 CAS 同时复核互斥/stacking 和每项 consumed + reserved 上限并预留整组 quota,任一失败则全组不落账。成员 promotion 不再各自获得可独立接受的 reservation。
唯一业务接受点在取回 provider invoice/checkout receipt 后,以 CAS 匹配 checkout reservation、invoice operation、金额/币种、整组 composition root 与 discount line root,原子推进全组 APPLIED 并签发一个 promotionApplicationReceipt.v1。不得把同 checkout 的互斥优惠拆成两次 reservation 或 acceptance。页面成功、provider 2xx 或名额减一都不算接受;consumeOn 决定何时转 CONSUMED。
外部创建 checkout/invoice 使用稳定 operation ID 和 provider 幂等键。超时后 reservation 进入 PROVIDER_UNKNOWN,禁止换键再建订单;查询相同 provider operation,证明不存在才释放,证明已应用就接受,对方仍未知则继续占用并告知用户状态。批量导入一次性 code 可经运营 CSV 导入进入 staging,但每个 code 的 policy/identity 冲突仍要隔离,不能直接更新 remaining count。HTTP 幂等键正确性封闭结账重试。
退款、撤销和 dispute 不原地删除消费。政策明确全额退款是否创建 REVERSED 并回补可用额度,部分退款按原折扣分摊生成 correction;已经过期或标记不可回补的活动只记收入调整。折扣影响要以原始 price 和 promotion generation 进入AI 毛利版本化归因,不能只拿净收款反推“折扣率”。
攻击最后一个名额和计算顺序
测试 101 请求抢 100 次额度、同客户并发、相似 code、有效期边界、reservation 后换 price、币种不符、互斥优惠被分两次评估/预留、provider 响应丢失、重复 Webhook、reservation 过期、退款和 policy 中途换代。门禁要求超额核销、同 checkout 多个 active composition、同 operation 双 APPLIED、未批准叠加、负小计均为 0,发票 discount root 与 composition root 匹配率 100%,UNKNOWN 盲释放/重试为 0。上线先 shadow,再给内部 code 和 5% 新结账启用;异常时停止新预留并回到上一 policy,已接受折扣只用 credit/补偿发票纠正。
Related