# @funnelfox/billing 中文文档

规范地址：https://npmcn.com/package/%40funnelfox/billing/

版本：0\.14\.0
npm 元数据更新时间：2026-11-12T07:34:45.116Z
中文译文更新时间：2026-11-15T18:51:37.048Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40funnelfox/billing
原文对照：https://npmcn.com/package/%40funnelfox/billing/?lang=original
项目主页：https://github.com/adaptyteam/funnelfox-billing-js#readme
源码仓库：https://github.com/adaptyteam/funnelfox-billing-js

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

- 包体积（解压后）：974 kB，20 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=14\.0\.0
- 运行依赖：2 个
- 首次发布：2025-10-01T15:44:10.414Z
- 最新版本发布：2026-11-12T07:34:44.743Z
- 近 30 天下载量：8428（2026-10-16 至 2026-11-14，npm 官方下载统计）
- 已发布版本数：124（近 12 个月 122 个）

## 中文简介

JavaScript SDK 用于与 Primer 集成的 Funnelfox 计费

## 安装

```sh
npm install @funnelfox/billing
```

也可以使用：`pnpm add @funnelfox/billing`、`yarn add @funnelfox/billing`、`bun add @funnelfox/billing`

## README

# @ funnelfox/ billing (英语)

一个现代的TypeScript SDK,用于使用Primer Heads取出集成的订阅支付.

## 特征

-   🚀 **现代 API**: 与事件驱动架构的基于承诺的清洁界面
-   🔄 **动态定价**: 不重新装入页面而更新价格
-   🛡️ **类型安全**: 完整的类型脚本定义和类型安全
-   🎯 **事件驱动**: 轻松处理成功、错误和状态变化
-   🔧 **强壮**: 内置错误处理、重试和验证
-   📦 **轻巧**: 最小依赖,浏览器优化
-   🎨 **无头检查**: 用 Primer 无头检查器全面控制退出 UI
-   💳 **条纹融合**: 原生花纹元素卡表和 Apple Pay / Google Pay 钱包

## 安装

### 维娅 CDN

```html
<!-- Include Primer Headless Checkout SDK first -->
<script src="https://sdk.primer.io/web/v2.57.3/Primer.min.js"></script>
<link rel="stylesheet" href="https://sdk.primer.io/web/v2.57.3/Checkout.css" />

<!-- Include Funnelfox Billing SDK -->
<script src="https://unpkg.com/@funnelfox/billing@latest/dist/funnelfox-billing.min.js"></script>
```

### 国家预防机制

```bash
npm install @funnelfox/billing @primer-io/checkout-web
```

如果您正在本地开发, 请为 TypeScript 构建/测试安装 dev 工具 :

```bash
npm i -D @rollup/plugin-typescript ts-jest @types/jest
```

然后构建:

```bash
npm run build
```

## 快速启动

```javascript
import { Billing } from '@funnelfox/billing';

await Billing.createCheckout({
  orgId: 'your-org-id',
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com',
  },
  container: '#checkout-container',
});
```

## API 参考

### `configure(config)`

配置全局 SDK 设置 .

```javascript
import { configure } from '@funnelfox/billing';

configure({
  orgId: 'your-org-id', // Required
  baseUrl: 'https://custom.api', // Optional, defaults to https://billing.funnelfox.com
  region: 'us-east-1', // Optional, defaults to 'default'
});
```

**参数:**

-   `config.orgId` (字符串,需要) - 您的组织标识符
-   `config.baseUrl` (字符串,可选) - 自定义 API URL
-   `config.region` (字符串,可选) - 区域,默认为"默认"

* * *

### `createCheckout(options)`

创建新检出实例 .

```javascript
const checkout = await createCheckout({
  // Required
  orgId: 'your-org-id',
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com', // Optional if you collect it in the card form
    countryCode: 'US', // Optional
  },
  container: '#checkout-container',
  clientMetadata: { source: 'web' },
  card: {
    emailAddress: {
      visible: true,
      template: '{{email}}',
    },
  },
  cardSelectors: {
    // Custom card input selectors (optional, defaults to auto-generated)
    cardNumber: '#cardNumberInput',
    expiryDate: '#expiryInput',
    cvv: '#cvvInput',
    cardholderName: '#cardHolderInput',
    emailAddress: '#emailAddressInput',
    button: '#submitButton',
  },
  paypalButtonContainer: '#paypalButton', // Optional
  googlePayButtonContainer: '#googlePayButton', // Optional
  applePayButtonContainer: '#applePayButton', // Optional
  paymentMethodOrder: ['PAYMENT_CARD', 'PAYPAL', 'GOOGLE_PAY', 'APPLE_PAY'], // Optional

  // Callbacks (alternative to events)
  onSuccess: result => {
    /* ... */
  },
  onError: error => {
    /* ... */
  },
  onStatusChange: (state, oldState) => {
    /* ... */
  },
});
```

**参数:**

-   `options.priceId` (字符串,要求) - 价格标识符
-   `options.customer` (目标,要求)
    -   `customer.externalId` (字符串, 需要) - 您的用户标识符
    -   `customer.email` (字符串,可选) - 客户电子邮件
    -   `customer.countryCode` (字符串,可选) - ISO国家代码
-   `options.container` (字符串,需要) - CSS选取器,用于取出容器
-   `options.card.emailAddress.visible` (boolean,可选) - 在卡片格式中显示电子邮件字段 。 默认禁用 .
-   `options.card.emailAddress.template` (字符串,可选) - 将输入的电子邮件在以支付方式发送之前包入, 例如 `{{email}}`。 。 。 .

**集装箱造型要求(破损皮肤):**

在使用默认外观时,容器元素必须具有下列CSS属性才能正确显示装载指标:

```css
#checkout-container {
  position: relative;
  min-height: 200px; /* Adjust based on your layout */
}
```

-   `position: relative` - 要求是因为加载覆盖用途 `position: absolute` 覆盖容器
-   `min-height` - 必须确保装载器在初始化期间可见。 建议的最低限度 `200px`

**补充参数:**

-   `options.orgId` (字符串,可选) - Org ID(如果不是全局配置)
-   `options.clientMetadata` (对象,可选) - 自定义元数据
-   `options.cardSelectors` (对象,可选) - 自定义卡片输入选择器(默认自动生成)
-   `options.paypalButtonContainer` (字符串,可选) - PayPal 按钮的容器选择器
-   `options.googlePayButtonContainer` (字符串,可选) - Google 付费按钮的容器选择器
-   `options.applePayButtonContainer` (字符串,可选) - Apple Pay按钮的容器选择器
-   `options.paymentMethodOrder` (阵列,可选) - 支付方法的自定义订单. 可用值 : `'PAYMENT_CARD'`, (中文(简体) ), `'PAYPAL'`, (中文(简体) ), `'GOOGLE_PAY'`, (中文(简体) ), `'APPLE_PAY'`默认为 `['PAYMENT_CARD', 'PAYPAL', 'GOOGLE_PAY', 'APPLE_PAY']`
-   `options.onInitialized` (功能,可选) - 初始化回调
-   `options.onSuccess` (函数,可选) - 成功召回
-   `options.onError` (功能, 可选) - 回调错误
-   `options.onStatusChange` (功能,可选) - 国家更改召回

**回返情况:** `Promise<CheckoutInstance>`

* * *

### `createClientSession(params)`

手动创建客户端会话(用于高级集成).

```javascript
import { createClientSession } from '@funnelfox/billing';

const session = await createClientSession({
  priceId: 'price_123',
  externalId: 'user_456',
  email: 'user@example.com', // Optional
  orgId: 'your-org-id', // Optional if configured
});

console.log(session.clientToken); // Use with Primer Headless Checkout
console.log(session.orderId);
```

**回返情况:** `Promise<{ clientToken: string, orderId: string, type: string }>`

* * *

### 离职偿金

#### 属性

-   `id` (字符串) - 唯一的出票标识符
-   `state` (字符串) - 当前状态 : `initializing`, (中文(简体) ), `ready`, (中文(简体) ), `processing`, (中文(简体) ), `completed`, (中文(简体) ), `error`
-   `orderId` (字符串) - 顺序标识符(初始化后可用)
-   `isDestroyed` (boolean) - 退出是否已被销毁

#### 活动

##### `'success'`

付款完成后立即退出.

```javascript
checkout.on('success', result => {
  console.log('Order ID:', result.orderId);
  console.log('Status:', result.status); // 'succeeded'
  console.log('Transaction:', result.transactionId);
});
```

##### `'error'`

当付款失败或遇到错误时, 即予以释放 .

```javascript
checkout.on('error', error => {
  console.error('Error:', error.message);
  console.error('Code:', error.code);
  console.error('Request ID:', error.requestId); // For support
});
```

##### `'status-change'`

退出状态改变时发送 .

```javascript
checkout.on('status-change', (newState, oldState) => {
  console.log(`${oldState} → ${newState}`);
  // States: initializing, ready, processing, action_required, completed, error
});
```

##### `'destroy'`

退房时通知.

```javascript
checkout.on('destroy', () => {
  console.log('Checkout cleaned up');
});
```

#### 方法

##### `updatePrice(priceId)`

更新退出以使用不同的价格.

```javascript
await checkout.updatePrice('price_yearly');
```

**说明:** 处理中无法更新价格 .

##### `getStatus()`

返回当前退出状态 .

```javascript
const status = checkout.getStatus();
console.log(status.id); // Checkout ID
console.log(status.state); // Current state
console.log(status.orderId); // Order ID
console.log(status.priceId); // Current price ID
console.log(status.isDestroyed); // Cleanup status
```

##### `destroy()`

破坏退出实例并清理资源.

```javascript
await checkout.destroy();
```

##### `isReady()`

检查退出是否准备好支付 .

```javascript
if (checkout.isReady()) {
  console.log('Ready to accept payment');
}
```

##### `isProcessing()`

检查是否正在处理付款 .

```javascript
if (checkout.isProcessing()) {
  console.log('Payment in progress...');
}
```

* * *

## 完整示例

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Funnelfox Checkout</title>
    <script src="https://sdk.primer.io/web/v2.57.3/Primer.min.js"></script>
    <link
      rel="stylesheet"
      href="https://sdk.primer.io/web/v2.57.3/Checkout.css"
    />
    <script src="https://unpkg.com/@funnelfox/billing@latest/dist/funnelfox-billing.min.js"></script>
  </head>
  <body>
    <div id="price-selector">
      <button onclick="selectPrice('price_monthly')">Monthly - $9.99</button>
      <button onclick="selectPrice('price_yearly')">Yearly - $99.99</button>
    </div>

    <div id="checkout-container"></div>

    <script>
      let currentCheckout = null;

      // Configure SDK once
      Billing.configure({
        orgId: 'your-org-id',
      });

      async function selectPrice(priceId) {
        try {
          if (currentCheckout && currentCheckout.isReady()) {
            // Update existing checkout
            await currentCheckout.updatePrice(priceId);
          } else {
            // Destroy old checkout if exists
            if (currentCheckout) {
              await currentCheckout.destroy();
            }

            // Create new checkout
            currentCheckout = await Billing.createCheckout({
              priceId: priceId,
              customer: {
                externalId: generateUserId(),
                email: getUserEmail(),
              },
              container: '#checkout-container',
            });

            // Handle success
            currentCheckout.on('success', result => {
              alert('Payment successful!');
              window.location.href = '/success?order=' + result.orderId;
            });

            // Handle errors
            currentCheckout.on('error', error => {
              alert('Payment failed: ' + error.message);
            });

            // Track state changes
            currentCheckout.on('status-change', state => {
              console.log('Checkout state:', state);
            });
          }
        } catch (error) {
          console.error('Checkout error:', error);
          alert('Failed to initialize checkout');
        }
      }

      function generateUserId() {
        return 'user_' + Math.random().toString(36).substr(2, 9);
      }

      function getUserEmail() {
        return 'user@example.com'; // Get from your auth system
      }
    </script>
  </body>
</html>
```

## 处理错误

SDK为不同的情景提供了特定的出错分类:

```javascript
import {
  ValidationError,
  APIError,
  PrimerError,
  CheckoutError,
  NetworkError,
} from '@funnelfox/billing';

try {
  const checkout = await createCheckout(config);
} catch (error) {
  if (error instanceof ValidationError) {
    // Invalid input
    console.log('Field:', error.field);
    console.log('Value:', error.value);
    console.log('Message:', error.message);
  } else if (error instanceof APIError) {
    // API error
    console.log('Status:', error.statusCode);
    console.log('Error Code:', error.errorCode); // e.g., 'double_purchase'
    console.log('Error Type:', error.errorType); // e.g., 'api_exception'
    console.log('Request ID:', error.requestId); // For support
    console.log('Decline Code:', error.declineCode); // e.g., 'INSUFFICIENT_FUNDS', set on failed payments
    console.log('Decline Source:', error.declineCodeSource); // 'primer' | 'stripe' | 'adyen', when the provider reports one
    console.log('Message:', error.message);
  } else if (error instanceof PrimerError) {
    // Primer SDK error
    console.log('Primer error:', error.message);
    console.log('Original:', error.primerError);
  } else if (error instanceof CheckoutError) {
    // Checkout lifecycle error
    console.log('Phase:', error.phase);
    console.log('Message:', error.message);
  } else if (error instanceof NetworkError) {
    // Network/connectivity error
    console.log('Network error:', error.message);
    console.log('Original:', error.originalError);
  }
}
```

### 常见错误代码

-   `double_purchase` - 用户已主动订阅
-   `invalid_price` - 找不到价格标识
-   `invalid_customer` - 客户数据验证失败
-   `payment_failed` - 付款处理失败

## 类型脚本支持

SDK包括全面的类型脚本定义:

```typescript
import {
  configure,
  createCheckout,
  CheckoutInstance,
  PaymentResult,
  CheckoutConfig,
  PaymentMethod,
} from '@funnelfox/billing';

// Configure
configure({
  orgId: 'your-org-id',
});

// Create checkout with type safety
const checkout: CheckoutInstance = await createCheckout({
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com',
    countryCode: 'US',
  },
  container: '#checkout',
  clientMetadata: {
    source: 'web',
    campaign: 'summer-sale',
  },
  paymentMethodOrder: [
    PaymentMethod.PAYPAL,
    PaymentMethod.PAYMENT_CARD,
    PaymentMethod.GOOGLE_PAY,
    PaymentMethod.APPLE_PAY,
  ],
});

// Type-safe event handlers
checkout.on('success', (result: PaymentResult) => {
  console.log('Order:', result.orderId);
  console.log('Status:', result.status);
  console.log('Transaction:', result.transactionId);
});
```

## 高级使用

### 使用回调而不是事件

```javascript
const checkout = await createCheckout({
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com',
  },
  container: '#checkout',

  // Callback style (alternative to .on() events)
  onSuccess: result => {
    console.log('Success!', result.orderId);
  },
  onError: error => {
    console.error('Error!', error.message);
  },
  onStatusChange: (newState, oldState) => {
    console.log(`${oldState} → ${newState}`);
  },
});
```

### 自定义卡输入选择器

默认情况下,SDK会自动生成卡输入元素. 如果您想要使用自己的 HTML 结构, 您可以提供自定义选择器 :

```javascript
const checkout = await createCheckout({
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com',
  },
  container: '#checkout',

  // Custom card input selectors
  cardSelectors: {
    cardNumber: '#my-card-number',
    expiryDate: '#my-expiry',
    cvv: '#my-cvv',
    cardholderName: '#my-cardholder',
    emailAddress: '#my-email',
    button: '#my-submit-button',
  },

  // Custom payment method button containers
  paypalButtonContainer: '#my-paypal-button',
  googlePayButtonContainer: '#my-google-pay-button',
  applePayButtonContainer: '#my-apple-pay-button',
});
```

### 自定义支付方法顺序

您可以自定义向客户显示支付方法的顺序:

```javascript
const checkout = await createCheckout({
  priceId: 'price_123',
  customer: {
    externalId: 'user_456',
    email: 'user@example.com',
  },
  container: '#checkout',

  // Customize payment method order
  paymentMethodOrder: ['PAYPAL', 'GOOGLE_PAY', 'APPLE_PAY', 'PAYMENT_CARD'],
});
```

**现有付款方法:**

-   `'PAYMENT_CARD'` - 信用卡/信用卡付款
-   `'PAYPAL'` - 支付工资
-   `'GOOGLE_PAY'` - 谷歌支付
-   `'APPLE_PAY'` - 支付苹果

默认情况下,支付方法按顺序显示:卡,PayPal,Google Pay,Apple Pay. 您可以重新排序, 以匹配您的商业优先或区域偏好 .

### 使用 `initMethod` 单一支付方法

对于您想要对安置和回调进行完全控制的一种单一支付方法的情景:

```javascript
import { Billing, PaymentMethod } from '@funnelfox/billing';

const container = document.getElementById('payment-container');

const paymentMethod = await Billing.initMethod(
  PaymentMethod.PAYMENT_CARD, // or PAYPAL, GOOGLE_PAY, APPLE_PAY
  container,
  {
    // Required
    orgId: 'your-org-id',
    priceId: 'price_123',
    externalId: 'user_456',
    email: 'user@example.com',

    // Optional - API configuration
    baseUrl: 'https://custom.api', // Optional, defaults to https://billing.funnelfox.com
    meta: { source: 'web' }, // Optional metadata

    // Optional - Primer configuration (for customizing payment method behavior)
    style: {
      /* Primer style options */
    },
    card: {
      /* Primer card options */
    },
    applePay: {
      /* Primer Apple Pay options */
    },
    paypal: {
      /* Primer PayPal options */
    },
    googlePay: {
      /* Primer Google Pay options */
    },

    // Callbacks
    onRenderSuccess: () => {
      console.log('Payment method rendered successfully');
    },
    onRenderError: method => {
      console.error('Failed to render:', method);
    },
    onLoaderChange: isLoading => {
      console.log('Loading state:', isLoading);
    },
    onPaymentStarted: method => {
      console.log('Payment started with:', method);
    },
    onPaymentSuccess: () => {
      console.log('Payment completed successfully!');
    },
    onPaymentFail: error => {
      console.error('Payment failed:', error.message);
      console.log('Decline code:', error.declineCode); // e.g., 'INSUFFICIENT_FUNDS', when the provider reports one
      console.log('Decline source:', error.declineCodeSource); // 'primer' | 'stripe' | 'adyen'
    },
    onPaymentCancel: () => {
      console.log('Payment was cancelled');
    },
    onErrorMessageChange: message => {
      console.log('Error message:', message);
    },
    onMethodsAvailable: methods => {
      console.log('Available methods:', methods);
    },
  }
);

// Control the payment method
paymentMethod.setDisabled(true); // Disable the payment method
paymentMethod.setDisabled(false); // Enable it

// For card payments, you can trigger submit programmatically
if (paymentMethod.submit) {
  await paymentMethod.submit();
}

// Clean up when done
await paymentMethod.destroy();
```

**参数:**

-   `method` (支付方法,要求) - 初始化的支付方法: `PAYMENT_CARD`, (中文(简体) ), `PAYPAL`, (中文(简体) ), `GOOGLE_PAY`,或 `APPLE_PAY`
-   `element` (HTMlement,要求) - DOM 元素,将使用支付方法
-   `options` (所需方法)
    -   `orgId` (字符串,需要) - 您的组织标识符
    -   `priceId` (字符串,要求) - 价格标识符
    -   `externalId` (字符串, 需要) - 您的用户标识符
    -   `email` (字符串,可选) - 客户电子邮件
    -   `baseUrl` (字符串,可选) - 自定义 API URL
    -   `meta` (对象,可选) - 自定义元数据
    -   `style`, (中文(简体) ), `card`, (中文(简体) ), `applePay`, (中文(简体) ), `paypal`, (中文(简体) ), `googlePay` (可选) - Primer SDK 配置选项
    -   回调( 所有可选) : `onRenderSuccess`, (中文(简体) ), `onRenderError`, (中文(简体) ), `onLoaderChange`, (中文(简体) ), `onPaymentStarted`, (中文(简体) ), `onPaymentSuccess`, (中文(简体) ), `onPaymentFail`, (中文(简体) ), `onPaymentCancel`, (中文(简体) ), `onErrorMessageChange`, (中文(简体) ), `onMethodsAvailable`

**回返情况:** `Promise<PaymentMethodInterface>` 方法:

-   `setDisabled(disabled: boolean)` - 启用/取消支付方法
-   `submit()` - 提交触发表(可用于支付卡片)
-   `destroy()` - 清理并删除支付方法

* * *

### 手动会话创建

对于您想要直接控制Primer Heads的高级集成:

```javascript
import { createClientSession } from '@funnelfox/billing';
import { Primer } from '@primer-io/checkout-web';

// Step 1: Create session
const session = await createClientSession({
  priceId: 'price_123',
  externalId: 'user_456',
  email: 'user@example.com',
  orgId: 'your-org-id',
});

// Step 2: Use with Primer Headless Checkout directly
const headlessCheckout = await Primer.createHeadless(session.clientToken, {
  paymentHandling: 'MANUAL',
  apiVersion: '2.4',
  onTokenizeSuccess: async (paymentMethodTokenData, handler) => {
    // Your custom payment logic...
    // Call your payment API with paymentMethodTokenData.token
    handler.handleSuccess();
  },
});

await headlessCheckout.start();
```

## 用钱包重新认证的一克Upsell

### `Billing.mountWalletUpsell(container, options)`

保存的 Apple Pay / Google Pay order 不得作为客户主动付款(Primer)收费,因此用钱包付款的买主再次证实了钱包单中的上市,而上市者则以那笔新鲜的钱包付款收费. 打电话 `mountWalletUpsell` 当上售屏幕加载时, 而不是在水龙头上: Apple Pay只从水龙头本身打开工作表, 所以 SDK 提前准备所有文件, 并将 Primer 的本地钱包按钮挂入 `container`。 。 。 .

```javascript
const mount = await Billing.mountWalletUpsell(container, {
  orgId: 'your-org-id',
  priceId: 'upsell_price',
  externalId: 'user_456',
  clientMetadata: { ff_billing_reason: 'upsell' },
  style: {
    buttonType: 'plain',
    buttonColor: 'black',
    height: 54,
    borderRadius: 28,
  },
  onStart: () => {},
  onThreeDSStart: ({ orderId }) => {},
  onThreeDSEnd: ({ result }) => {},
  onResult: result => {
    // result.outcome: 'succeeded' | 'declined' | '3ds_failed' | '3ds_abandoned'
    //   | 'error' | 'processing' | 'already_purchased' | 'cancelled'
  },
});

if (mount.mounted) {
  // The wallet button is shown: hide your own upsell button.
  // mount.setDisabled(true | false), mount.destroy() when the screen goes away.
} else if (mount.reason === 'not_wallet' || mount.reason === 'error') {
  // Keep your own button and use Billing.silentPurchase({ handle3DS: true }).
} else if (mount.reason === 'wallet_unavailable') {
  // The buyer's wallet cannot be shown on this device: open the full checkout on tap.
}
```

挂载结果 :

-   `mounted: true` 与 `wallet` (单位:千美元)`APPLE_PAY` 或者说 `GOOGLE_PAY`: 显示本地按钮 .
-   `not_wallet`:买方保存的方法不是Primer钱包.
-   `wallet_unavailable`:设备无法显示买主的钱包 。 另一个钱包从不提供.
-   `error` 与 `error`: 请求失败(网络, 5xx, `double_purchase`, (中文(简体) ), `invalid_price`, (中文(简体) ), `merchant_config_error`, 速率限制)或按钮无法打开.
-   承诺只拒绝 `ValidationError`与 `mounted: false` 什么都没有了 `container`。 。 。 .

`onResult` 每个尝试都叫一次,与 `silentPurchase({ handle3DS: true })` 加上结果 `cancelled`:买方关闭了钱包单,未收取任何费用。 之后 `cancelled` 按钮保持活性,可以再次被调用;在任何其他结果之后,它永远被禁用。 3DS 重试时使用一能键,每个结果的含义是: `silentPurchase({ handle3DS: true })`· ; `processing` 携带订单 id 的指控被保留.

由SDK负责:

-   尝试运行时的第二个水龙头被忽略,对于两个钱包来说,同样也是 `silentPurchase({ handle3DS: true })` 给同一个买家.
-   钱包会话在到期前再次准备, 当标签变得可见, 剩下的时间不多 。 对即将到期的会话的自拍被忽略并被替换出按钮,所以下个自拍工作.
-   当后端报告钱包会话不再有效(无任何收费)时,SDK会准备一个新的会话,并用相同的钱包付款重试一次,但只有在金额不变的情况下才进行. 否则,当钱包支付本身被拒绝时,结果是 `error`。 。 。 .
-   如果显示按钮时会话无法再次准备, `onResult` 得到一个 `error` 没有水龙头,按钮被禁用.

样式 : `buttonType` `plain` | `buy` | `pay` | `continue` (钱包没有变成 `plain`; 苹果支付 `continue` 用苹果绘制 `<apple-pay-button>`, (中文(简体) ), `buttonColor` `black` | `white` | `white-outline` (谷歌付费显示) `white-outline` 作为 `white`, (中文(简体) ), `height` 财务报告和审定财务报表 `borderRadius` px(默认54和28).

Apple Pay需要该页面域名在Primer中验证Apple Pay,如出账.

## 条纹融合

这个 `Billing.stripe` namespace 提供了一个外形- 本地取出体验—— 不需要Primer依赖 。 它支持通过"花脸元素"支付卡和通过"支付请求API"支付本地钱包(Apple Pay / Google Pay).

> **说明:** `@primer-io/checkout-web` 实值 **没有** 用于条纹融合。 仅 `@funnelfox/billing` 并需要在您的 Funnelfox 账户中有一个花旗价格.

* * *

### `Billing.stripe.createCardForm(element, params)`

将条纹元素支付表挂入 DOM 元素。 回返a `{ submit() }` handle —— 启动付款时控制(例如点击自己的按钮).

```javascript
const element = document.getElementById('card-form');

const cardForm = await Billing.stripe.createCardForm(element, {
  // Required
  priceId: 'price_123',
  externalId: 'user_456',

  // Optional
  orgId: 'your-org-id',
  email: 'user@example.com',
  countryCode: 'US',
  showWallets: false, // show Apple Pay / Google Pay inside the form
  appearance: {
    // Stripe Elements appearance API
    theme: 'stripe',
  },

  // Callbacks
  onRenderSuccess: () => {
    document.getElementById('pay-button').disabled = false;
  },
  onLoaderChange: loading => {
    document.getElementById('pay-button').disabled = loading;
  },
  onPaymentSuccess: (paymentMethod, orderId) => {
    window.location.href = '/success?order=' + orderId;
  },
  onPaymentFail: error => {
    console.error('Payment failed:', error.message);
  },
});

// Wire up your own submit button
document.getElementById('pay-button').addEventListener('click', async () => {
  await cardForm.submit();
});
```

**关键参数:**

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `priceId` | 字符串 | 价格标识符 |
| `externalId` | 字符串 | 您的用户标识符 |
| `email` | 绳子? | 客户电子邮件 |
| `orgId` | 绳子? | Org ID( 如果没有全局配置) |
| `showWallets` | 布尔? | 在条纹格式内显示 Apple Pay / Google Pay |
| `appearance` | 对象? | [条纹元素外观 API](https://stripe.com/docs/elements/appearance-api) |
| `clientMetadata` | 对象? | 自定义附于命令的元数据 |

**回返情况:** `Promise<{ submit: () => Promise<void> }>`

* * *

### `Billing.stripe.getAvailableWallet(params)`

检查当前设备和浏览器上是否有Apple Pay或Google Pay. 使用此选项在尝试支付前有条件显示钱包按钮 .

```javascript
const wallet = await Billing.stripe.getAvailableWallet({
  priceId: 'price_123',
  externalId: 'user_456',
});

if (wallet === 'APPLE_PAY') {
  document.getElementById('apple-pay-btn').style.display = 'block';
} else if (wallet === 'GOOGLE_PAY') {
  document.getElementById('google-pay-btn').style.display = 'block';
} else {
  // No wallet available — show card form only
}
```

**回返情况:** `Promise<'APPLE_PAY' | 'GOOGLE_PAY' | null>`

打电话 `Billing.stripe.getAvailableWallet(...)` 在渲染页面时( 它决定是否显示钱包按钮 ) 。 除了检测可用性,它预发了付款单,所以 `purchaseWallet(...)` 点击处理器内部可以立即打开——Safari取消在点击处理器中长时间的ASync工作后打开的Apple Pay页. 传出同样的 `priceId`编号 :`externalId`编号 :`email`编号 :`countryCode` 对两个呼叫:它们共享一个检出会话,不匹配的参数创建第二个会话并跳过前置工作表.

* * *

### `Billing.stripe.purchaseWallet(params)`

触发本地苹果支付或谷歌支付表. 在确认钱包可用后点击按钮调用此选项 `getAvailableWallet`。 。 。 .

```javascript
document.getElementById('wallet-btn').addEventListener('click', async () => {
  await Billing.stripe.purchaseWallet({
    priceId: 'price_123',
    externalId: 'user_456',
    totalLabel: 'Premium Plan', // Label shown in the payment sheet

    onPaymentSuccess: (paymentMethod, orderId) => {
      window.location.href = '/success?order=' + orderId;
    },
    onPaymentFail: error => {
      console.error('Wallet payment failed:', error.message);
    },
    onPaymentCancel: () => {
      console.log('User cancelled');
    },
    onLoaderChange: loading => {
      document.getElementById('wallet-btn').disabled = loading;
    },
  });
});
```

**关键参数:**

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `priceId` | 字符串 | 价格标识符 |
| `externalId` | 字符串 | 您的用户标识符 |
| `totalLabel` | 绳子? | 在付款表金额旁边显示标签 |
| `email` | 绳子? | 客户电子邮件 |
| `clientMetadata` | 对象? | 自定义附于命令的元数据 |

* * *

### `Billing.stripe.getAvailablePaymentMethods(params)`

返回当前设备可用的所有花旗支付方法。 总是包含 `PAYMENT_CARD`; 如果检测到钱包, 还包括钱包方法.

```javascript
const methods = await Billing.stripe.getAvailablePaymentMethods({
  priceId: 'price_123',
  externalId: 'user_456',
});

// methods: ['PAYMENT_CARD', 'APPLE_PAY'] or ['PAYMENT_CARD'] etc.
console.log('Available:', methods);
```

**回返情况:** `Promise<PaymentMethod[]>` - 总是包含 `PAYMENT_CARD`,可选 `APPLE_PAY` 或者说 `GOOGLE_PAY`

* * *

### 组合示例:钱包检测+卡后退

建议的模式——在可用时显示钱包按钮,始终将卡片表显示为倒置:

```javascript
import { Billing } from '@funnelfox/billing';

Billing.configure({ orgId: 'your-org-id' });

const params = {
  priceId: 'price_123',
  externalId: 'user_456',
  email: 'user@example.com',
};

async function initCheckout() {
  // 1. Check for wallet availability
  const wallet = await Billing.stripe.getAvailableWallet(params);

  if (wallet) {
    const walletBtn = document.getElementById('wallet-btn');
    walletBtn.textContent =
      wallet === 'APPLE_PAY' ? 'Pay with Apple Pay' : 'Pay with Google Pay';
    walletBtn.style.display = 'block';

    walletBtn.addEventListener('click', () => {
      Billing.stripe.purchaseWallet({
        ...params,
        totalLabel: 'Premium Plan',
        onPaymentSuccess: (_, orderId) => {
          window.location.href = '/success?order=' + orderId;
        },
        onPaymentFail: err => alert(err.message),
        onPaymentCancel: () => console.log('Cancelled'),
      });
    });
  }

  // 2. Always mount card form as fallback
  const cardForm = await Billing.stripe.createCardForm(
    document.getElementById('card-form'),
    {
      ...params,
      onPaymentSuccess: (_, orderId) => {
        window.location.href = '/success?order=' + orderId;
      },
      onPaymentFail: err => alert(err.message),
      onLoaderChange: loading => {
        document.getElementById('pay-btn').disabled = loading;
      },
    }
  );

  document.getElementById('pay-btn').addEventListener('click', () => {
    cardForm.submit();
  });
}

initCheckout();
```

* * *

## 浏览器支持

-   颜色60+
-   Firefox 55+ (英语)
-   第12条+
-   边缘79+

## 实例

见 [示例目录](https://npmcn.com/package/%40funnelfox/billing/examples/) 用于比较完整的示例:

-   [基本报到](https://npmcn.com/package/%40funnelfox/billing/examples/basic/) - 简单的离职整合

## 许可证

麻省理工学院

## 常见问题

### @funnelfox/billing 是什么？

JavaScript SDK 用于与 Primer 集成的 Funnelfox 计费

### 如何安装 @funnelfox/billing？

在项目目录运行 npm install @funnelfox/billing，或使用 pnpm add / yarn add / bun add。

### @funnelfox/billing 支持 TypeScript 吗？

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

### @funnelfox/billing 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @funnelfox/billing 需要什么版本的 Node\.js？

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

### @funnelfox/billing 可以商用吗？

@funnelfox/billing 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @funnelfox/billing 的下载量有多少？

根据 npm 官方下载统计，@funnelfox/billing 在 2026/10/16 – 2026/11/14 这 30 天内被下载 8,428 次，日均约 281 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @funnelfox/billing 还在维护吗？

最新版本 0\.14\.0 发布于 2026/11/12（3 天前）；至今共发布 124 个版本，其中过去 12 个月发布了 122 个；首次发布于 2025/10/01。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

