# @krizaka/payments 中文文档

规范地址：https://npmcn.com/package/%40krizaka/payments/

版本：0\.7\.1
npm 元数据更新时间：2026-11-01T14:49:41.233Z
中文译文更新时间：2026-11-01T16:46:54.901Z
软件包许可：Apache-2\.0

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40krizaka/payments
原文对照：https://npmcn.com/package/%40krizaka/payments/?lang=original
项目主页：https://github.com/krizaka/krizaka-ts/tree/main/packages/payments
源码仓库：https://github.com/krizaka/krizaka-ts

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：225 kB，16 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：4 个
- peer 依赖：zod \>=3\.23，vitest \>=4
- 首次发布：2026-10-22T05:22:08.490Z
- 最新版本发布：2026-11-01T14:49:40.552Z
- 已发布版本数：13（近 12 个月 13 个）

## 中文简介

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

## 安装

```sh
npm install @krizaka/payments
```

也可以使用：`pnpm add @krizaka/payments`、`yarn add @krizaka/payments`、`bun add @krizaka/payments`

## README

# @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命令拒绝活动模式，除非你说你是所有者，并且每个测试都使用假的。

```ts
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。

```ts
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 是什么？

每个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 和提交记录判断。

