@ coderize/ stripe (编译)
每个CoderizeDEV应用程序如何与Frede交谈:一个被绑定的API版本,客户端用它构建,一个CI守护者
失败 stripe 升级会移动它, 网络hook基础( raw- body signition check, 仅限一次)
处理、按类型排列路由、字段的阅读器在API版本之间移动,以及浏览器安全
卡片费总价 所以,一个出票页 显示了准确的收费 API收费.
pnpm add @coderize/stripe
同伴依赖 : stripe 23. 每个应用程序都保留 stripe 所以这个一揽子方案从不带来
sDK的第二份副本。 包含有 TypeScript 类型的 Plain JavaScript (ESM).
规则:更改API版本是主要发布
STRIPE_API_VERSION 实值 2026-10-28.endive 2.x. 1.x被钉住 2026-08-26.dahlia从1.x移动的应用程序
首先更改其代码:参见 从1.x升级。该版本仅以 主要
释放此软件包。 翻新会将小的和补丁的发布单独合并,并保留主要内容供审查,所以没有
app更改了API版本,没有读取更改日志的人,首先测试出站.
没有了针头,条纹节点不使用账户的 Dashboard 版本:它发送该版本自己的发布
被建造反对。 条形 23.0 将默认移动到 2026-09-30.endive 第23.1段至第23.1段 2026-10-28.endive,所以一个
未绑定的客户端已经处于绝境状态.
从1.x升级
2.0.0 从 2026-08-26.dahlia 改为 2026-10-28.endive。 。 。 . 2026-10-28.endive 没有GA突破
改变它自己的,所以移动意味着承担断裂的改变 2026-09-30.endive。应用程序停留在 1.x 上
直到它自己通过PR(革新持有主要),并且PR做出这些改变:
-
丢弃
payment_method_types从每一个写 不再需要它. 退出会话创建, 支付意向 创建、更新和确认,并设置 Intent 创建和现在更新答案 400个 当它通过时 (单位:千美元)payment_method_types_no_longer_supported以图谋。 要么:- 在Dashboard中删除并管理支付方法(动态支付方法,一种支付方法)
配置),不包括任何你不想与
excluded_payment_method_types;或 ;或 - 改为:
allowed_payment_method_types: [...]。 。 。 .
allowed_payment_method_types静默过滤器: 无法使用的账户或金额类型被忽略 而不是失败请求。 如果一种方法很重要(价格取决于卡或电子检查,例如),则检查 页:1payment_method_types创建对象返回,当您要求的对象缺失时失败. 阅读payment_method_types在返回对象上仍然工作,并且 Paying Links 创建保存 参数:它不是变化的一部分。 更新验证旧创建参数的测试 . - 在Dashboard中删除并管理支付方法(动态支付方法,一种支付方法)
配置),不包括任何你不想与
-
过
billing_cycle_anchor作为订阅更新和恢复的对象.'unchanged'变成{ type: 'unchanged' }财务报告和审定财务报表'now'变成{ type: 'now' }。 。 。 .invoices.createPreview将同一对象带入subscription_details.billing_cycle_anchor时标会变成{ type: 'timestamp', timestamp }。 。 。 . 订阅 创建 不变. -
请检查access-date=中的日期值 (帮助) 2026-09-30.endive对接app. 这些是其他的破碎物品 一个应用程序可以在这里 合理打击:
- 花纹税 : 新设
failed_tax_calculation税收计算错误( 创建和预览) , 发票 最终确定、支付和检索,并用自动税创建和更新订阅。 当作失败 计算,绝不作为零税. - 连接 :
requirements.disabled_reason拒绝的平台rejected.platform_fraud, (中文(简体) ),rejected.platform_terms_of_service财务报告和审定财务报表rejected.platform_other· ;requirements.errors可列出eventually_due要求;以及accounts.reject不再需要钱reason: 'fraud'(使用时fraud_other或一个 具体fraud_*说明理由。 只读的应用程序charges_enabled, (中文(简体) ),payouts_enabled财务报告和审定财务报表details_submitted不受影响. - 脚印.js on Endive (单位:千美元)
@stripe/stripe-js10):付款请求按钮和元素paymentMethodTypes选项已丢失, 退出会话保存的元素canConfirm虚度时 有待更新billingDetails: 'never'需要的账单细节 在确认. - 其余(账户v2,财务连接,财务和资金管理,PayTo,BLIK,印度授权, 雷达付款评价、储备、争议证据页数限制)只涉及使用这些软件的应用程序 产品。 Frede 列表 : https://docs.stripe.com/changelog/endive (中文(简体) )。 。 。 .
- 花纹税 : 新设
-
丢弃任何明确
apiVersion。 。 。 .createStripe拒绝apiVersion: '2026-08-26.dahlia'现在,和类型 拒绝 创建自己客户端的脚本移动到createStripe以及证明dahlia的测试 字符串比较STRIPE_API_VERSION换句话说. -
移动每个 Webhook 端点到
2026-10-28.endive。 。 。 . 一个有效载荷在其端点的API版本中被渲染,而不是 所以Dahlia上留下的终点 总是发送Dahlia的有效载荷 下面的读者选择 形状,所以两个相重叠时没有中断 :- 在
2026-10-28.endive用于同一路径和事件(Dashboard,或webhookEndpoints.create与api_version: STRIPE_API_VERSION) (中文(简体) ). - 把签名的秘密都交给应用公司
verifyWebhook({ secret: [oldSecret, newSecret] })。 。 。 . 两个端点在两场比赛同时进行 都是一样的event.id这样processEventOnce跳过第二次送货. - 部署,确认事件到达新的终点, 然后关闭旧的,并放弃它的秘密.
- 在
-
运行测试模式检出 (和订阅变更,其中应用有一个)合并前.
中央情报局警卫coderize-stripe-check保持原样,并穿行23.0和23.1条.
在应用程序中采纳
1. 客户
替换应用程序 new Stripe(key, { apiVersion }) 与 createStripe,并删除其自己的版本常数.
// src/plugins/stripe.js
import fp from 'fastify-plugin'
import { createStripe } from '@coderize/stripe'
export default fp(async function stripePlugin(app) {
app.decorate('stripe', createStripe(process.env.STRIPE_SECRET_KEY))
})
createStripe(secretKey, options) 返回指针上的条形节点客户端 maxNetworkRetries: 2其他项目
条形节点选项通过( c)timeout, (中文(简体) ), stripeAccount, (中文(简体) ), httpClient(......) (中文(简体) ). 它扔在空钥匙上,然后
a 一个 apiVersion 除了指针: 需要另一个版本的应用程序需要在这里发布另一个主要版本,而不是一个
本地覆盖 。 构建自己客户端的脚本( S)scripts/*.mjs, (中文(简体) ), prisma/*.js使用它,或者他们说话
sDK的默认版本,而应用则会说话取出接针.
仅构建客户端的测试不需要网络:构建一个不需要请求.
2. 中情局警卫
安装后, 在应用程序的 CI 工作流程中添加一个步骤 :
- name: Check the Stripe API version
run: pnpm --filter <scope>/api exec coderize-stripe-check
它不需要网络、密钥或数据库,并且在下列情况下失败(退出 1):
- 已安装
stripe默认为 API 版本, 此版本尚未审查(REVIEWED_SDK_DEFAULTS),或 - 来自
createStripe不发送针.
所以,当翻新提供 stripe 释放移动默认值的 PR 红色直到发布此值
包检出. 别在应用程序里工作 同样的支票是 checkStripeVersion() 测试.
一旦这个程序运行,就删除它自己的守护脚本和打针测试.
3. 网络用户
花纹标记它发送的确切字节. 解析 JSON 并按顺序再次更改白空间和密钥
命令,因此签名不再匹配,对重新平分对象的检查也证明没有。 所以说
webhook 路由读取 活体: 在 Fastify 中,在 webhook 中注册的缓冲内容类型解析器
路由本身的插件( 绝不是全局); 在 Next.js, await request.text()。 。 。 .
使用 handleStripeWebhook。 。 。 . 整个网络都用一个电话 检查签名,回答事件
类型中没有处理器,将事件id记录在与处理器相同的交易中,所以第二个副本是
跳过(甚至一个在同一时刻到达),并在交易完成后产生副作用。 这个
应用程序只写它的处理器和一个表格:
model StripeEvent {
id String @id
type String
receivedAt DateTime @default(now())
}
// src/routes/stripeWebhook.js (Fastify)
import { handleStripeWebhook } from '@coderize/stripe'
export default async function stripeWebhookRoutes(app) {
app.addContentTypeParser('application/json', { parseAs: 'buffer' }, (request, body, done) => done(null, body))
app.post('/', async (request, reply) => {
const { status, body } = await handleStripeWebhook({
stripe: app.stripe,
rawBody: request.body,
signature: request.headers['stripe-signature'],
// Both secrets while an endpoint is being replaced; the old one comes out after the switch.
secret: [process.env.STRIPE_WEBHOOK_SECRET, process.env.STRIPE_WEBHOOK_SECRET_NEXT],
prisma: app.prisma,
handlers,
log: request.log
})
return reply.code(status).send(body)
})
}
const handlers = {
// Every database write goes through `tx`; anything outside the database goes through `after`.
'invoice.paid': async (event, { tx, after }) => {
const payment = await tx.payment.create({ data: { stripeInvoiceId: event.data.object.id /* ... */ } })
after(() => sendReceipt(payment))
}
}
// src/app/api/stripe/webhook/route.ts (Next.js)
export async function POST(request: Request) {
const { status, body } = await handleStripeWebhook({
stripe,
rawBody: await request.text(),
signature: request.headers.get('stripe-signature'),
secret: [process.env.STRIPE_WEBHOOK_SECRET!, process.env.STRIPE_WEBHOOK_SECRET_NEXT!],
prisma,
handlers
})
return Response.json(body, { status })
}
handleStripeWebhook({ stripe, rawBody, signature, secret, handlers, prisma, log }) 决定
{ status, body },答案是:
| 大小写 | 状态 | 体质 |
|---|---|---|
| 已处理 | 200个 | { received: true } |
| 已处理的事件副本 | 200个 | { received: true, duplicate: true } |
| 该类型没有处理器(已登录) | 200个 | { received: true, handled: false } |
被拒绝 verifyWebhook (下关 | 400/500 (中文(简体) ) | { error, code } |
当一个处理者扔出,它 拒绝 而不是以 StripeWebhookHandlerError
(单位:千美元)statusCode 五百块, code HANDLER_FAILED, (中文(简体) ), eventId, (中文(简体) ), eventType,处理器错误为 cause:
交易随事件 ID回滚, 框架回答 500 并报告错误, Frede 重试
再次运行处理器。 别接. 包装器是它保存500的:一个应用程序的错误处理器的答案
Prisma的P2025和P2002,配有404和409 (中文(简体) )@coderize/fastify-kit 在普通路线上进行)和
AppError 和它的4xx, 和斯德雷克以4xx作为最终, 所以事件会被放弃, 没有报告.
这是故意的 StripeWebhookError,所以一个路径 捕捉那些发送状态
吞下它 吞下来 A 类
函数队列 after 失败将被登录, 网络浏览仍然回答 200, 因为支付它
接着发生了. log 取出皮诺 (request.log)或控制台,默认值。 指定或形状的表格
不同的经历 storeOptions (单位:千美元){ model: 'webhookEvent', idField: 'stripeEventId' }),以及
store 全部替换 Prisma 商店 .
以下各部分为从何而来. 只当一个电话不合适时直接使用.
verifyWebhook({ stripe, rawBody, signature, secret, tolerance }) 返回事件或丢出一个
StripeWebhookError 谁 statusCode 答案是发送:
code | 状态 | 含义 |
|---|---|---|
MISSING_SIGNATURE | 400个 | 没有 Stripe-Signature 标题 |
INVALID_SIGNATURE | 400个 | 错误的秘密, 改变的身体, 或签名比 tolerance |
RAW_BODY_REQUIRED | 500个 | 尸体已解析: 路径已丢失其生体解析器 |
WEBHOOK_NOT_CONFIGURED | 500个 | 没有客户端 没有签名秘密 |
这两起500起事件是我们的过错,所以斯德莱克不断重试,直到被修复,而不是被抛出的事件.
secret 可以在替换终点时成为列表:新旧终点以不同的符号表示
秘密,一个事件通过 当它匹配。 任何来自条形节点的其他错误都会被传递.
processEventOnce(event, handler, store, { mode }) 处理每个事件一次。 条纹再换到它
得到一个2xx,并且可以同时提供两次活动. 应用程序保存存储器,一个具有独特事件ID的表格,
和回话 record(id, event, tx), (中文(简体) ), seen(id), (中文(简体) ), transaction(work),可选择
isDuplicate(err) (Prisma's (英语) P2002 (默认情况下). 它决定 { duplicate }; 回答 200 左右。 失败
拒绝: 让路线回答 500 个, 所以 Strape 重试 。 永远不要抓住它和回答2xx, 这停止了重复.
有两种模式,但失败方式不同:
claim(默认值):事件id被记录 首先,在与工作相同的交易中A级 重新交付,即使是同时交付,都会遇到独特的制约,被跳过。 如果工作失败 倒滚取出ID与之并重试再运行工作. 交易中的一切都会发生 一次就是这样 这是舰队需要的图案; 编码-dev-mono'sroutes/webhooks.js这是 参考文献.record返回时已报告身份false(单位:千美元)ON CONFLICT DO NOTHING插入 )或者通过抛出唯一约束错误.seen如果给定的话, 是便宜的预选 它跳过打开一个交易 一个明显的重递.record-after数字 :seen首先,然后工作,然后record它永远不会失去一个事件,因为ID是 只在作品成功后写出. 但是两个送货的赛车 既可以运行工作, 和碰撞之间 作品和唱片在重试中再次运行。 只当工作无法共享交易时使用 (它称为"花纹"或"另一种服务"),并让这种功能发挥作用:每个花纹上的一等钥匙 创建、 更新而不是插入.
它从未提供的东西是在工作前将ID记录在自己的声明中. 如果工作失败了,
重试时看到ID并跳过它,一个付酬事件会永远丢失;这发生在一个舰队应用中. 删除
编号再次在一个 catch 是一个较弱的后退, 因为一个坠机中手仍然失去这个事件.
prismaEventStore(prisma, { model, idField, data, transactionOptions }) 是仓库
handleStripeWebhook 使用 : record, (中文(简体) ), seen 财务报告和审定财务报表 transaction 超过 Prisma 模型, 带有独特的事件 ID
(单位:千美元)prisma.stripeEvent 财务报告和审定财务报表 id 在默认情况下),由于Neon来回路程导致交易超时20秒
普里斯玛5秒的默认紧凑. 传过来 processEventOnce 当你给台阶打电话的时候.
routeEvent(event, handlers, context) 电话 handlers[event.type](event, context) 并决心 true,或
决定 false 当没有处理器匹配时。 登录 false 其中一个: 没人处理的事件类型是最常见的
webhook 代码中的漏洞. 在 TypeScript 中,每个处理器都会将事件缩小到其类型.
4. 移动字段的读者
API版本 2025-03-31. Basil移动了多个字段,一个webhook有效载荷在 它的终点 API 密码
版本,不是客户端的指针,所以两个形状都可以到达. 这些田地都没有再搬进去了
2026-09-30.endive 或者说 2026-10-28.endive。每个读者首先采用新形状,然后采用旧形状
一秒钟,当史黛丽不说时,返回无效。 只读出旧字段的代码悄悄失败: a
续约被跳过作为"未跟踪", 收入份额从未记录过, 船到地址被账单的取而代之.
| 阅读器 | 读取(新形状,后为旧) |
|---|---|
subscriptionPeriod(subscription) | { start, end } 日期从 items.data[].current_period_* (最晚获胜),那么 current_period_* |
invoiceSubscriptionId(invoice) | parent.subscription_details.subscription,则 subscription |
invoicePeriodStart(invoice) | lines.data[0].period.start日期 |
invoicePaymentIntentId(stripe, invoice) | payment_intent,则 payments.data[].payment.payment_intent; async,见下文 |
invoiceChargeId(invoice) | charge,则 payments.data[].payment.charge 或扩大的意图 latest_charge |
checkoutShipping(session) | collected_information.shipping_details,则 shipping_details |
网路的发票没有 payments 名单从basil. invoicePaymentIntentId 当有效载荷时取取
缺少(一个) invoices.retrieve 与 expand: ['payments']; 通过 null 作为 stripe 读取有效载荷
只),并拒绝如果史黛丽无法被接通的话,所以被网友重复. invoiceChargeId 仅读出有效载荷:
通过一张与 expand: ['payments.data.payment.payment_intent'] 以达到后面的电荷
意图。 两人都喜欢用 status: 'paid' 对失败的尝试。 从
subscriptionPeriod 意思是"保存所储存的值",从不"写作虚无".
checkoutShipping 返回为无效。 一个自带的软件 {} 写入
checkoutShipping(session) ?? {} 以保持这种行为.
5. 支付卡费(@coderize/stripe/fees(中文(简体) )
当付款人支付处理费时,显示收费的页面和收费的API必须计算 同一个号码 电离层把它们分开了, 以浮动美元表示的页面和以整数美分表示的API, 其中387 500 000美元从0.01美元到5 000.00美元,显示收费比所收卡高1美元。 双方导入此选项 一个函数代替:
// the checkout page (browser) and the checkout route (API) alike
import { cardFeeCents, grossUpForCardFee } from '@coderize/stripe/fees'
const { feeCents, totalCents } = grossUpForCardFee(subtotalCents) // 5000 -> 181 and 5181
@coderize/stripe/fees 不导入, 因此一个 Vite 或 Astro 页面可以装入它, 没有花纹 SDK 或任何节点
内置。 主条目不再出口: 从 ./fees 到处都是.
| 导出 | 这是什么 |
|---|---|
grossUpForCardFee(netCents, { rate, fixedCents }) | { netCents, feeCents, totalCents }: 蚊帐总数 netCents 收费之后 |
cardFeeCents(netCents, fee?) | 页:1 feeCents 单是"处理费"一行 |
US_CARD_FEE | { rate: 0.029, fixedCents: 30 },花旗的标准美国卡定价和默认 |
总共是 (net + fixed) / (1 - rate) 四舍五入到百分点,所以雄性永远不短一次花纹
轮回自己的费用。 这就是独行侠的API和Implay的 feeCalc 已收费, 每笔金额; 运行中
在整数算术中,没有浮起一分高。 数量为整数分(一浮动或一串抛出),以及
rate 是一个小数,最多为小数点后六位数的分数(0.022 2.2%的非营利性费率.
将应用程序移动到它上会更改这些, 所以将 API 及其页面放入相同的 PR:
- icrotary的页面 (单位:千美元)
PayInvoice, (中文(简体) ),RaffleTicketForm, (中文(简体) ),LunchCredits上面387个数额减去1美元, 现在匹配电荷。 内容DonationForm且API不变. - 庇护者和受孕者 四舍五入到最接近的百分数(
Math.round) (中文(简体) ). 在这500 000人中的249 742人 计算共享函数对付款人1++的收费,以及证明完全需要更新的测试. - 伊姆莱 不变.
审查新的 SDK 默认值
当守卫在新东西上失败时 stripe 释放 :
- 查找新默认值 :
node -p "require('stripe/package.json').version"和警卫的sdkDefault。 。 。 . - 读取 Frede 的变换日志 , 在 Pin 和 新默认值之间 , 并 grep 机队 打破更改:写出参数,读出字段,webhook有效载荷形状.
- 添加条目到
REVIEWED_SDK_DEFAULTS内version.js加上日期、决定和发现 小声 变换器,然后释放。 Apps把它捡起来 他们的后卫是绿色的 针不变. - 把它自己移开就是个 主要 释放: 更改
STRIPE_API_VERSION,标记条目adopted,然后说 在更改列表中,每个应用程序必须改变什么。 每一个应用 也移动它的 Webhook 端点到新版本, 因为有效载荷遵循终点的版本.
计划
第一阶段是一分一分 被钉在了Dahlia身上 所以采用它不会改变任何行为 第二阶段将每个应用程序移动到它上
(客户,警卫,网络浏览基本内容,读者),每个应用程序一个PR,在路上修补被移动的场阅读. 第3阶段是
2.0.0 有关 2026-10-28.endive,一次通过一个应用 步骤
从1.x升级; AllSteel先行. ) (中文(简体) ).
版本数
从一个消耗性应用的感受看: 补丁 改变什么,它看不到,a 小声 添加导出, a 选项或经审查的 SDK 默认值,以及 a 主要 更改 API 版本或其他应用程序可以查看的任何东西 电线或返回值。 改造后将未成年人合并并自行补丁,并设有大修科供审查.
每一次发布都是在npm上进行,并且只有在维护者批准2FA后才能进行直播,因为翻新 把它装入每个舰队的应用程序.