@krizaka/payments

中文文档
0.7.1发布于 2026/11/01Apache-2.0TypeScriptES Module构建来源已验证

每个Krizaka产品的付款,端口背后的处理器:Stripe适配器(带有折扣条款的结账,到期和幂等性,客户门户,通过查找键读取的价格,带有已付费,过期和退款事件的验证webhook),事件应用程序

中文使用文档
文档目录4 节
  1. 它给了什么
  2. 连接:商家自己的处理器(Stripe Connect标准)
  3. 从krizaka-ui的0.1/0.2迁移过来
  4. 测试Connect应用

@krizaka /付款

每件Krizaka产品的付款, 端口后面的处理器. 从krizaka-ui移到这里(相同的npm名称,相同的版本行:0.2.2变成了0.3.0)。域与 PaymentsPort 和供应商免费 PaymentEvent年代;条带是一个适配器(processorAdapter,在 fetch(没有SDK)。另一个处理器是另一个适配器。

货币是带有货币的整数小单位,而不是浮点数。 money(1149, "CAD") 是 { amountMinor: 1149, currency: "cad" }. 价格是数据 @krizaka/settings 价格目录,ADR-009;处理器保存由它构建的价格对象)。 税(GST/QST)是加工商的 (条纹税, automatic_tax(按贴现金额计算),此处从未写过。这个包不读取活动键:键是参数,setup命令拒绝活动模式,除非你说你是所有者,并且每个测试都使用假的。

import { announcePayment, applyOnceInTransaction, dbDiscountStore, processorAdapter } from "@krizaka/payments";
import { transaction } from "@krizaka/db";

const payments = processorAdapter({
  secretKey,
  webhookSecret, // from the environment: PAYMENTS_STRIPE_SECRET_KEY, PAYMENTS_STRIPE_WEBHOOK_SECRET
  plans: { pro: { lookupKey: "yomizaka.pro", mode: "subscription" } },
  clock, // needed for expiresAt
  discounts: dbDiscountStore(query), // needed for a discount
  webhookToleranceSeconds, // the admin's payments.webhookToleranceSeconds
});

// checkout, with a discount from the coupons module, a retry-safe key and a deadline
const { url, checkoutRef, expiresAt } = await payments.checkout({
  kind: "pro",
  customerId,
  accountId,
  successUrl,
  cancelUrl,
  locale: "fr",
  discount: terms, // DiscountTerms: one Stripe coupon per (couponId, termsVersion)
  idempotencyKey: redemptionId, // a retry returns the same session
  metadata: { redemption_id: redemptionId },
  expiresAt, // 31 minutes to 24 hours from now, else PAYMENTS_BAD_EXPIRY
});

// the webhook route: verify, translate, then record the event and its effect in ONE transaction
const event = payments.parseWebhook(await req.text(), req.headers, Math.floor(clock.now().valueOf() / 1000)); // throws PaymentsError on a bad signature
await applyOnceInTransaction(
  (work) => transaction(connect, work),
  event,
  {
    payment_paid: async (e, tx) => {
      await coupons.redeem(tx, {
        redemptionId: e.metadata.redemption_id,
        checkoutRef: e.checkoutRef,
        paymentRef: e.paymentRef,
        amountPaid: e.amountTotal,
        amountDiscount: e.amountDiscount,
        currency: e.currency,
      });
      await announcePayment(tx, outbox, e); // evt.payment.paid.v1 on the « payments » topic, same transaction
    },
    checkout_expired: async (e, tx) => void (await coupons.release(tx, e.checkoutRef)),
    payment_refunded: async (e, tx) =>
      void (await coupons.refund(tx, { paymentRef: e.paymentRef, amountRefunded: e.amountRefunded })),
  },
  { clock },
);

它给了什么

  • 港口。 createCustomer (每个帐户幂等), checkout, portal, price, charge, refund, parseWebhook.
  • 每件物品一次性收费。 charge({ key, customerId, amountMinor, currency, description, taxCode }) 分条税(税前金额)一行一发票,以节省的付款方式结算支付;每通电话都有按键 key. 拒绝付款使发票无效并抛出 PAYMENTS_DECLINED; refund(ref, { key, amountMinor }) 获取发票或付款凭证。
  • 折扣。 CheckoutRequest.discount 是 DiscountTerms (couponId, termsVersion, percentOffBp 基点或 amountOffMinor + currency, duration 一次/重复/永远)。Stripe适配器使 每条优惠券一张 (couponId, termsVersion) (幂等性键 coupon-<id>-v<n>),记在心里 payments.discount,并发送 discounts: [{ coupon }] 旁边 automatic_tax. 它从未制作促销代码。接受一次性付款 once 只有;在任何电话(PAYMENTS_BAD_DISCOUNT)。
  • 幂等,元数据,最后期限。 idempotencyKey 是Stripe的头部; metadata 在付费事件(account_id 和 kind 是适配器自己的); expiresAt 成为会话的 expires_at,在处理机窗口外被拒绝(PAYMENTS_BAD_EXPIRY)。
  • 事件。 payment_paid 和 subscription_started 携带 checkoutRef, paymentRef, invoiceRef, amountTotal, amountDiscount, currency, metadata. checkout_expired (取消预订)和 payment_refunded (amountRefunded 是 累积;付款的名称是 paymentRef,和 invoiceRef 为订阅)也被翻译。全额折扣付款(no_payment_required)是一种没有的付费方式 paymentRef.
  • webhook是经过验证的,是幂等的。 Stripe-Signature 在重播窗口(管理员的设置)内对原始正文进行固定时间检查。 applyOnceInTransaction 中写入事件id并运行处理程序 一个事务:抛出的处理程序不记录事件,重试应用该事件;许多请求一次只应用一次(在PostgreSQL上测试)。 applyOnce (id第一, forget (如果失败),则为没有数据库效果的处理程序保留。
  • 通过发件箱。 announcePayment(tx, outbox, event) 附加 evt.payment.<fact>.v1 来 @krizaka/outbox 在处理程序的事务中,使用从处理程序的事件id派生的id:宣布一次,至少交付一次。
  • 表 (paymentsSchema、Postgres模式 payments): event (应用事件的记录)和 discount. 一个产品 public.payment_events (Yomizaka migration 0009, Akinaza 0010)运行 adoptPaymentEventsSql() 一次,这 移动表及其行 成 payments.event,然后发出版本1。在那之前 pgEventStore(query) 读取 public.payment_events.
  • 设置 (@krizaka/payments/settings): payments.webhookToleranceSeconds (300年,30 - 900)。
  • 目录设置。 krizaka-payments-setup --catalogue catalogue.json [--webhook-url …] [--secret-out file] 创建,幂等地,产品,价格(不含税),客户门户和webhook端点(现在听 checkout.session.expired 和 charge.refunded 太;更新旧的端点)。更改的金额是接管查找键的新价格(transfer_lookup_key),所以已经售出的订阅可以保留。 plansFromPrices(entries, names) 从设置价格目录建立计划:什么是在出售,与数字,在 month / year / once.
  • 格式。 formatMoney / formatPrice:英语为“$39”,法语为“39 $”;只在有小数的情况下才使用小数;任何货币的指数。
  • eventStoreContract 和 discountStoreContract (@krizaka/payments/testkit)是每家商店的合同; memoryEventStore() 和 memoryDiscountStore() 用于单元测试。

连接:商家自己的处理器(Stripe Connect标准)

对于一种顾客是商家的产品(接受自己客户付款的商店)。每个商店连接 自己的 通过Stripe- hosting登录Stripe账户;每次充电都是一个 直接记在那个账户上 (Stripe-Account):这家店是有记录的商人,Krizaka从来没有拿过钱。库从不读取env:应用程序传递平台密钥、webhook秘密、时钟和store。

const connect = connectAdapter({ secretKey, fetch }); // ConnectPort
const { accountId } = await connect.createAccount({ key: shopId, email, country: "ca" });
const { url } = await connect.onboardingLink({ accountId, returnUrl, refreshUrl }); // redirect the owner
const status = await connect.accountStatus(accountId); // chargesEnabled, detailsSubmitted, state: pending | restricted | active
const { customerId } = await connect.createCustomer(accountId, { key, email });
const { clientSecret } = await connect.saveCard(accountId, { key, customerId }); // the browser confirms it with the processor's form
const deposit = await connect.pay(accountId, {
  key: bookingId,
  customerId,
  paymentMethodRef,
  amountMinor: 2000,
  currency: "cad",
  mode: "hold",
});
await connect.capture(accountId, deposit.paymentRef, { key: `${bookingId}:cap`, amountMinor: 1500 }); // or connect.release(...)
await connect.refund(accountId, deposit.paymentRef, { key: `${bookingId}:ref`, amountMinor: 500 });
  • 无卡数据。 在端口的任何地方都没有卡号,CVC或过期字段:卡由处理器自己的表单保存(saveCard 返回 clientSecret 仅限),并作为参考使用。
  • 钱 是整数小单位; mode: "capture" 拿着付款, "hold" 授权它(作为担保的存款)直到 capture (全部或部分)或 release. applicationFeeMinor 平台的可选费用(默认为0)是否低于金额,否则 PAYMENTS_BAD_FEE)。
  • 退款永远不会超过所支付的金额: 适配器读取付款并拒绝以上付款减去已退款(PAYMENTS_REFUND_EXCEEDS_PAID),或从未获得的付款(PAYMENTS_NO_PAYMENT). 每次调用都取幂等函数 key.
  • ONE Connect网络钩子 在平台帐户(在配置中创建一次,其秘密在env中;商店没有声明): handleConnectWebhook({ secret, clock, store, handlers }, rawBody, signatureHeader) 根据事件的路由验证签名和路由 account,对每个事件应用一次(store.markSeen,重播是 duplicate(失败的处理程序返回id)。 handlers (account_updated, payment_paid, payment_held, payment_released, payment_refunded,退款总额是累积的) 唯一的作家 关于已付费/退款:一条路线永远不会标记为已付费的预订。通知没有 account 是平台自己的,被忽略。
  • 一个产品在确认之前必须向客户展示什么 存档的存款或信用卡:金额、取消和缺席政策,以及退款规则(《quemail消费者保护法》;见单一律师评论,krizaka/.github#250)。缺席费是产品的决定(策略id和原因在其自己的审核中);图书馆只会转移资金。
  • 预授权借记(PAD,加拿大)是同一端口后面的一个适配器步骤。

从krizaka-ui的0.1/0.2迁移过来

stripeAdapter 和 StripeAdapterConfig 是作为别名保存的 processorAdapter 和 ProcessorConfig. checkout 现在的答案 { url, checkoutRef, expiresAt }. payment_paid 和 subscription_started 获得的领域(从未被移除)。 applyOnceInTransaction 写入 payments.event 默认值:pass { table: { schema: "public", table: "payment_events" } } 直到 adoptPaymentEventsSql 运行。环境: PAYMENTS_PROVIDER, PAYMENTS_STRIPE_SECRET_KEY, PAYMENTS_STRIPE_WEBHOOK_SECRET,就像今天一样(由产品阅读,从未在这里)。

测试Connect应用

memoryConnect() 是内存中的 ConnectPort 对于你的测试:同样的拒绝 connectAdapter (类型 PaymentsError),每个键幂等,并且一个与释放赛跑的存款捕获是序列化的,因此只有一个获胜而另一个被拒绝 PAYMENTS_NOT_HELD. activate(accountId) 播放入职结束。

一个可选的合约测试运行真实的适配器来对抗处理器的测试模式:set PAYMENTS_CONTRACT_KEY 到测试模式键(sk_test_… 只有;任何其他值都会被忽略)并运行 pnpm --filter @krizaka/payments test. 默认CI从不设置它。

版本与发布记录

@krizaka/payments 共发布 13 个版本,首次发布于 2026/10/22;过去 12 个月发布了 13 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.7.1latest2026/11/011 天前
0.7.02026/10/304 天前
0.6.02026/10/304 天前
0.5.02026/10/304 天前
0.4.22026/10/304 天前
0.4.12026/10/295 天前

关于 @krizaka/payments 的常见问题

@krizaka/payments 是什么?

每个Krizaka产品的付款,端口背后的处理器:Stripe适配器(带有折扣条款的结账,到期和幂等性,客户门户,通过查找键读取的价格,带有已付费,过期和退款事件的验证webhook),事件应用程序

如何安装 @krizaka/payments?

在项目目录运行 npm install @krizaka/payments,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 krizaka-payments-setup,可以用 npx krizaka-payments-setup 直接运行。

@krizaka/payments 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@krizaka/payments 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@krizaka/payments 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@krizaka/payments 可以商用吗?

@krizaka/payments 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@krizaka/payments 还在维护吗?

最新版本 0.7.1 发布于 2026/11/01(1 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/10/22。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。