商品、价格与订单:建立清晰的交易模型
把上一节的报价变成系统能识别的数据。本篇的成品是一个测试商品、一条9美元一次性价格和一份完整订单样例。付款接入时,浏览器只需告诉API“我要10次包”,金额和额度由服务器找到。
需要你已能进入Stripe账户的独立测试环境。若主体申请尚未明确,先完成这里的本地订单示例,控制台步骤等测试权限具备后再做;不要为练习填不真实的申请资料。本篇没有代你创建任何支付账户或商品。
先读懂这份完成的交易
ClearCopy第一次卖的是10次成功报告,使用期按前节练习设为付款后30天。用下面这组教学标识串起来:
{
"orderId": "demo-order-001",
"userId": "demo-user-a",
"offerCode": "clearcopy-10-v1",
"offerTitle": "ClearCopy · 10 checks",
"amountMinor": 900,
"currency": "usd",
"credits": 10,
"validDays": 30,
"status": "pending",
"provider": "stripe",
"providerPriceId": "REPLACE_WITH_SANDBOX_PRICE_ID",
"checkoutSessionId": null,
"fulfilledAt": null
}
amountMinor使用美元的分,因此900表示9美元。offerCode是你自己稳定的套餐代码,providerPriceId是Stripe生成的价格标识,二者用途不同。复制此例时保留套餐代码,用你的测试价格ID替换大写占位值;其他demo-标识仅用于演练。
这份订单把购买当时承诺的价格、次数和有效期保留下来。将来套餐改成12美元,不会改变这笔9美元订单。产品名称可以修正文案,订单里已经成交的金额和权益仍按原快照处理。
在测试环境创建对应商品
打开Stripe产品与价格说明,观察官方将商品解释为卖什么,将价格解释为收多少及多久收一次。配图中的商品、价格关系正好对应本节的两层数据。
- 在Dashboard的账户切换器中进入独立Sandbox,确认页面显示测试环境。
- 打开 Product catalog,选择 Create product,名称输入
ClearCopy · 10 checks。 - 在描述中填写
10 completed English app-description checks.。保存前确认这里说的报告与你实际准备实现的内容一致。 - 添加一次性价格,币种选 USD,金额输入
9.00,不选择月度或年度重复扣款。 - 保存商品,打开价格详情,复制它的
price_标识到本地测试配置。控制台生成值每个账户都不同,不要复制教程中的虚构ID。
完成后,你应看见商品下有一条9美元一次性价格。如果显示/month,回到商品创建一条正确的一次性价格并用于练习;不要为了修展示文字继续沿用错误的周期价格。
让服务端回答“这包多少钱”
把下列配置保存在API侧,而不是Astro的公开脚本里。这是数据结构示例,尚未绑定真实项目环境变量:
const offers = {
'clearcopy-10-v1': {
amountMinor: 900,
currency: 'usd',
credits: 10,
validDays: 30,
priceId: process.env.STRIPE_PRICE_10_CHECKS
}
};
浏览器提交的购买意图只保留套餐代码:
{"offerCode":"clearcopy-10-v1"}
API从已登录会话取得userId,查询这份配置,生成订单。不要接收浏览器自报的userId、credits或amountMinor来决定发给谁和发多少。实际请求创建Checkout时,把本地订单号放入服务端创建的会话关联字段,返回的会话ID再写回本地订单。
这是一次明确的数据转换:收到套餐代码,得到前面的pending订单。它还没有付款,因此额度仍是0。以后查看交易,不必靠邮件地址猜哪个用户买过东西。
处理用户连点两次的那几秒
假设用户点击购买后网络超时,又点了一次。若API每次都新建订单和Checkout,会出现两个可付款页面。给一次购买尝试创建一个随机请求标识,同一次重试继续用它;用户明确重新发起另一笔购买时再换标识。
Stripe幂等请求说明同一个幂等键可用于安全重试。你的数据库也要给(userId, purchaseAttemptId)加唯一约束:重复请求先查回原订单,再复用已有会话;创建Stripe会话时使用由订单号派生的稳定幂等键。这样平台侧和应用侧都能认出同一次尝试。
在纸上连续输入两次同一请求,完成后的记录应是:
| 操作 | 本地订单数 | 对应Checkout会话数 | 可用次数 |
|---|---|---|---|
| 第一次请求 | 1 | 1 | 0 |
| 同次尝试超时后重试 | 1 | 1 | 0 |
| 经服务端确认已付并完成发放 | 1 | 1 | 10 |
发放还需要下一节的付款核对。Stripe履约文档明确要求同一个会话多次、甚至并发调用时只履约一次,因此数据库还应为付款会话建立唯一关联,为发放建立唯一记录。这里不把“已创建会话”改成“已付款”。
带着三项值离开这一页
现在把你的测试price_标识填回订单例,核对金额为900、币种为usd,状态仍为pending;保留尚未付款的0次余额。若你把一条订阅价格填进一次性套餐,或把9当成9美分,这一步就能发现。
保存商品ID、价格ID和已填的订单JSON到独立教学项目中,凭据只放环境配置。到这一步,报价中的“9美元10次”已经有了稳定套餐代码、平台价格和购买快照,付款回调能准确找到要完成的那笔订单。