primer-web-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Primer Web Components

Primer Web 组件

Overview

概述

This skill provides comprehensive guidance for building checkout and payment experiences using Primer's web component library (
@primer-io/primer-js
). Primer components are framework-agnostic custom elements that work with React, Next.js, Vue, Svelte, or vanilla JavaScript.
Use this skill when:
  • Implementing checkout pages or payment flows
  • Integrating Primer payment methods (cards, PayPal, BLIK, Apple Pay, Google Pay, etc.)
  • Building custom card forms with validation
  • Working with React and need to handle web component integration properly
  • Customizing payment UI with themes and CSS custom properties
  • Implementing vault for saved payment methods
  • Handling payment lifecycle events and callbacks
本内容为使用Primer的Web组件库(
@primer-io/primer-js
)构建结账与支付体验提供全面指导。Primer组件是与框架无关的自定义元素,可在React、Next.js、Vue、Svelte或原生JavaScript中使用。
在以下场景中可使用本内容:
  • 实现结账页面或支付流程
  • 集成Primer支付方式(银行卡、PayPal、BLIK、Apple Pay、Google Pay等)
  • 构建带验证功能的自定义卡片表单
  • 在React中正确处理Web组件集成
  • 通过主题和CSS自定义属性定制支付UI
  • 实现已保存支付方式的存储(Vault)功能
  • 处理支付生命周期事件与回调

🚨 Breaking Changes in v0.7.0

🚨 v0.7.0 中的重大变更

Critical API Changes:
Starting in v0.7.0, the callback and event APIs have been updated for clearer separation of success and failure handling:
  • Callbacks:
    onPaymentComplete
    replaced with
    onPaymentSuccess
    and
    onPaymentFailure
  • State Fields:
    error
    primerJsError
    ,
    failure
    paymentFailure
  • Event Names:
    primer:payment-methods-updated
    → use
    primer:methods-update
New in v0.7.0:
  • Payment lifecycle events:
    primer:payment-start
    ,
    primer:payment-success
    ,
    primer:payment-failure
  • Vault events:
    primer:vault:methods-update
  • Vault callback:
    onVaultedMethodsUpdate
  • PII-filtered payment data in success payloads
All examples in this skill use the v0.7.0+ API. If using older SDK versions, refer to legacy documentation.
关键API变更:
从v0.7.0开始,回调与事件API已更新,以更清晰地区分成功与失败处理:
  • 回调
    onPaymentComplete
    替换为
    onPaymentSuccess
    onPaymentFailure
  • 状态字段
    error
    primerJsError
    failure
    paymentFailure
  • 事件名称
    primer:payment-methods-updated
    → 改为使用
    primer:methods-update
v0.7.0 新增功能:
  • 支付生命周期事件:
    primer:payment-start
    primer:payment-success
    primer:payment-failure
  • Vault事件:
    primer:vault:methods-update
  • Vault回调:
    onVaultedMethodsUpdate
  • 成功负载中经过PII过滤的支付数据
本内容中的所有示例均使用v0.7.0+版本的API。若使用旧版SDK,请参考遗留文档。

Quick Start Guide

快速入门指南

Installation

安装

bash
npm install @primer-io/primer-js
bash
npm install @primer-io/primer-js

Basic HTML Setup

基础HTML配置

html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Primer Checkout</title>
  </head>
  <body>
    <primer-checkout client-token="your-client-token"></primer-checkout>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Primer Checkout</title>
  </head>
  <body>
    <primer-checkout client-token="your-client-token"></primer-checkout>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Vanilla JavaScript Initialization

原生JavaScript初始化

typescript
import { loadPrimer } from '@primer-io/primer-js';
import { fetchClientToken } from './fetchClientToken';

(async function () {
  await loadPrimer();

  const checkout = document.querySelector('primer-checkout')!;
  const response = await fetchClientToken('order-id');

  if (response.success) {
    checkout.setAttribute('client-token', response.clientToken);
  }

  // Handle payment success and failure
  checkout.addEventListener('primer:ready', (event) => {
    const primer = event.detail;

    primer.onPaymentSuccess = ({ paymentSummary, paymentMethodType }) => {
      console.log('✅ Payment successful!', paymentSummary.id);
      window.location.href = `/confirmation?orderId=${paymentSummary.orderId}`;
    };

    primer.onPaymentFailure = ({ error }) => {
      console.error('❌ Payment failed:', error.message);
      // Show error to user
    };
  });
})();
typescript
import { loadPrimer } from '@primer-io/primer-js';
import { fetchClientToken } from './fetchClientToken';

(async function () {
  await loadPrimer();

  const checkout = document.querySelector('primer-checkout')!;
  const response = await fetchClientToken('order-id');

  if (response.success) {
    checkout.setAttribute('client-token', response.clientToken);
  }

  // 处理支付成功与失败
  checkout.addEventListener('primer:ready', (event) => {
    const primer = event.detail;

    primer.onPaymentSuccess = ({ paymentSummary, paymentMethodType }) => {
      console.log('✅ 支付成功!', paymentSummary.id);
      window.location.href = `/confirmation?orderId=${paymentSummary.orderId}`;
    };

    primer.onPaymentFailure = ({ error }) => {
      console.error('❌ 支付失败:', error.message);
      // 向用户展示错误信息
    };
  });
})();

React 19 Setup (Recommended)

React 19 配置(推荐)

TypeScript Configuration:
typescript
import type { CheckoutElement } from '@primer-io/primer-js';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'primer-checkout': CheckoutElement;
    }
  }
}
Component:
typescript
import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

// ✅ Define options outside component for stable reference
const SDK_OPTIONS = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
};

function CheckoutPage({ clientToken }: { clientToken: string }) {
  useEffect(() => {
    loadPrimer();
  }, []);

  return (
    <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />
  );
}
TypeScript配置:
typescript
import type { CheckoutElement } from '@primer-io/primer-js';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'primer-checkout': CheckoutElement;
    }
  }
}
组件:
typescript
import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

// ✅ 在组件外部定义选项以保持稳定引用
const SDK_OPTIONS = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
};

function CheckoutPage({ clientToken }: { clientToken: string }) {
  useEffect(() => {
    loadPrimer();
  }, []);

  return (
    <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />
  );
}

Component Architecture

组件架构

Core Component Hierarchy

核心组件层级

primer-checkout (root)
├── primer-main (layout container)
│   ├── slot="payments" (payment method selection)
│   ├── slot="checkout-complete" (success state)
│   └── slot="checkout-failure" (error state)
├── primer-payment-method (individual payment type)
├── primer-payment-method-container (declarative filtering)
├── primer-billing-address (billing information, SDK Core only)
├── primer-error-message-container (payment failure display)
└── primer-card-form (card payment inputs)
    ├── primer-input-card-number
    ├── primer-input-card-expiry
    ├── primer-input-cvv
    ├── primer-input-card-holder-name
    ├── primer-card-form-submit
    └── Custom fields using base components:
        ├── primer-input-wrapper
        ├── primer-input-label
        └── primer-input
primer-checkout(根组件)
├── primer-main(布局容器)
│   ├── slot="payments"(支付方式选择)
│   ├── slot="checkout-complete"(成功状态)
│   └── slot="checkout-failure"(错误状态)
├── primer-payment-method(单个支付类型)
├── primer-payment-method-container(声明式过滤)
├── primer-billing-address(账单信息,仅SDK Core支持)
├── primer-error-message-container(支付失败展示)
└── primer-card-form(卡片支付输入框)
    ├── primer-input-card-number
    ├── primer-input-card-expiry
    ├── primer-input-cvv
    ├── primer-input-card-holder-name
    ├── primer-card-form-submit
    └── 使用基础组件的自定义字段:
        ├── primer-input-wrapper
        ├── primer-input-label
        └── primer-input

SDK Modes: Core vs Legacy

SDK模式:Core vs Legacy

SDK Core (Default since v0.4.0)

SDK Core(v0.4.0起默认)

The new payment engine with enhanced features. This is the default and recommended for new integrations.
javascript
// SDK Core is enabled by default
checkout.options = {
  sdkCore: true, // Default, no need to specify
};
Currently Supported Payment Methods:
  • PAYMENT_CARD
    - Full card payment forms
  • PAYPAL
    - PayPal button integration
  • ADYEN_BLIK
    - Polish payment method (OTP verification)
New payment methods are added regularly. Check release notes for updates.
Benefits:
  • Modern payment processing engine
  • Enhanced performance and reliability
  • New payment methods support
  • Better error handling and diagnostics
具备增强功能的新型支付引擎。这是默认且推荐用于新集成的模式。
javascript
// SDK Core默认启用
checkout.options = {
  sdkCore: true, // 默认值,无需指定
};
当前支持的支付方式:
  • PAYMENT_CARD
    - 完整卡片支付表单
  • PAYPAL
    - PayPal按钮集成
  • ADYEN_BLIK
    - 波兰支付方式(OTP验证)
新支付方式会定期添加。请查看发布说明了解更新。
优势:
  • 现代化支付处理引擎
  • 增强的性能与可靠性
  • 支持新支付方式
  • 更优的错误处理与诊断

Legacy SDK

Legacy SDK

Enable with
sdkCore: false
. Provides access to 50+ payment methods via Web Headless API.
javascript
checkout.options = {
  sdkCore: false, // Opt into legacy SDK
};
When to use:
  • Need payment methods not yet in SDK Core
  • Existing integration using legacy patterns
  • Require specific processor-specific methods
Important: Payment method availability depends on:
  1. Primer Dashboard configuration
  2. Payment processor Web Headless support
  3. Regional availability
Not all payment methods support Web Headless. Check the Primer Payment Methods catalog for "Web Headless" column.
通过设置
sdkCore: false
启用。可通过Web Headless API访问50+种支付方式。
javascript
checkout.options = {
  sdkCore: false, // 切换到Legacy SDK
};
适用场景:
  • 需要SDK Core中尚未支持的支付方式
  • 现有集成使用Legacy模式
  • 需要特定处理器专属的支付方式
注意: 支付方式的可用性取决于:
  1. Primer控制台配置
  2. 支付处理器的Web Headless支持
  3. 区域可用性
并非所有支付方式都支持Web Headless。请查看Primer支付方式目录中的"Web Headless"列。

SDK Options Reference

SDK选项参考

Core Options

核心选项

Configure SDK behavior through the
options
property:
javascript
checkout.options = {
  // Core configuration
  sdkCore: true, // Default: true (SDK Core enabled)
  locale: 'en-US', // Force UI locale
  merchantDomain: 'merchant.example.com', // For Apple Pay validation
  disabledPayments: false, // Disable all payment methods
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
};
Core Options:
OptionTypeDefaultDescription
sdkCore
boolean
true
Enable SDK Core engine
locale
string
Browser's localeForce UI locale (e.g., "en-GB")
merchantDomain
string
window.location.hostname
Domain for Apple Pay validation
disabledPayments
boolean
false
Disable all payment methods globally
enabledPaymentMethods
PaymentMethodType[]
[PaymentMethodType.PAYMENT_CARD]
Which payment methods to display
通过
options
属性配置SDK行为:
javascript
checkout.options = {
  // 核心配置
  sdkCore: true, // 默认值:true(启用SDK Core)
  locale: 'en-US', // 强制UI语言
  merchantDomain: 'merchant.example.com', // 用于Apple Pay验证
  disabledPayments: false, // 全局禁用所有支付方式
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
};
核心选项说明:
选项类型默认值描述
sdkCore
boolean
true
启用SDK Core引擎
locale
string
浏览器语言强制UI语言(例如:"en-GB")
merchantDomain
string
window.location.hostname
Apple Pay验证用域名
disabledPayments
boolean
false
全局禁用所有支付方式
enabledPaymentMethods
PaymentMethodType[]
[PaymentMethodType.PAYMENT_CARD]
要显示的支付方式

Card Options

卡片选项

Configure card payment form behavior:
javascript
checkout.options = {
  card: {
    cardholderName: {
      required: true, // Whether cardholder name is required
      visible: true, // Whether cardholder name field is visible
    },
  },
};
Card Options:
OptionTypeDefaultDescription
card.cardholderName.required
boolean
false
Require cardholder name
card.cardholderName.visible
boolean
true
Show cardholder name field
配置卡片支付表单行为:
javascript
checkout.options = {
  card: {
    cardholderName: {
      required: true, // 是否必填持卡人姓名
      visible: true, // 是否显示持卡人姓名字段
    },
  },
};
卡片选项说明:
选项类型默认值描述
card.cardholderName.required
boolean
false
要求填写持卡人姓名
card.cardholderName.visible
boolean
true
显示持卡人姓名字段

Apple Pay Options

Apple Pay选项

Configure Apple Pay button appearance and data collection:
javascript
checkout.options = {
  applePay: {
    buttonType: 'buy', // 'plain' | 'buy' | 'set-up' | 'donate' | 'check-out' | 'book' | 'subscribe'
    buttonStyle: 'black', // 'white' | 'white-outline' | 'black'
    billingOptions: {
      requiredBillingContactFields: ['postalAddress', 'emailAddress'],
    },
    shippingOptions: {
      requiredShippingContactFields: ['postalAddress', 'name'],
      requireShippingMethod: false,
    },
  },
};
配置Apple Pay按钮外观与数据收集:
javascript
checkout.options = {
  applePay: {
    buttonType: 'buy', // 'plain' | 'buy' | 'set-up' | 'donate' | 'check-out' | 'book' | 'subscribe'
    buttonStyle: 'black', // 'white' | 'white-outline' | 'black'
    billingOptions: {
      requiredBillingContactFields: ['postalAddress', 'emailAddress'],
    },
    shippingOptions: {
      requiredShippingContactFields: ['postalAddress', 'name'],
      requireShippingMethod: false,
    },
  },
};

Google Pay Options

Google Pay选项

Configure Google Pay button appearance and data collection:
javascript
checkout.options = {
  googlePay: {
    buttonType: 'long', // 'long' | 'short' | 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe'
    buttonColor: 'black', // 'default' | 'black' | 'white'
    buttonSizeMode: 'fill', // 'fill' | 'static'
    captureBillingAddress: true,
    emailRequired: false,
    requireShippingMethod: false,
  },
};
配置Google Pay按钮外观与数据收集:
javascript
checkout.options = {
  googlePay: {
    buttonType: 'long', // 'long' | 'short' | 'book' | 'buy' | 'checkout' | 'donate' | 'order' | 'pay' | 'plain' | 'subscribe'
    buttonColor: 'black', // 'default' | 'black' | 'white'
    buttonSizeMode: 'fill', // 'fill' | 'static'
    captureBillingAddress: true,
    emailRequired: false,
    requireShippingMethod: false,
  },
};

Klarna Options

Klarna选项

Configure Klarna payment behavior:
javascript
checkout.options = {
  klarna: {
    paymentFlow: 'DEFAULT', // 'DEFAULT' | 'PREFER_VAULT'
    allowedPaymentCategories: ['pay_now', 'pay_later', 'pay_over_time'],
    buttonOptions: {
      text: 'Pay with Klarna',
    },
  },
};
配置Klarna支付行为:
javascript
checkout.options = {
  klarna: {
    paymentFlow: 'DEFAULT', // 'DEFAULT' | 'PREFER_VAULT'
    allowedPaymentCategories: ['pay_now', 'pay_later', 'pay_over_time'],
    buttonOptions: {
      text: 'Pay with Klarna',
    },
  },
};

Vault Options

Vault选项

Configure payment method vaulting (saving for future use):
javascript
checkout.options = {
  vault: {
    enabled: true, // Enable vaulting
    showEmptyState: true, // Show message when no vaulted methods exist
  },
};
配置支付方式存储(为未来使用保存):
javascript
checkout.options = {
  vault: {
    enabled: true, // 启用存储功能
    showEmptyState: true, // 当无已存储支付方式时显示提示信息
  },
};

Stripe Options

Stripe选项

Configure Stripe-specific payment options:
javascript
checkout.options = {
  stripe: {
    mandateData: {
      fullMandateText: 'By providing your payment information...',
      merchantName: 'Your Business Name',
    },
    publishableKey: 'pk_test_...',
  },
};
配置Stripe专属支付选项:
javascript
checkout.options = {
  stripe: {
    mandateData: {
      fullMandateText: 'By providing your payment information...',
      merchantName: 'Your Business Name',
    },
    publishableKey: 'pk_test_...',
  },
};

Submit Button Options

提交按钮选项

Configure submit button behavior:
javascript
checkout.options = {
  submitButton: {
    amountVisible: true, // Show amount on button (e.g., "Pay $12.34")
    useBuiltInButton: true, // Default: true, set false for external buttons
  },
};
Using External Submit Buttons:
javascript
// Hide built-in button
checkout.options = {
  submitButton: {
    useBuiltInButton: false,
  },
};

// Dispatch event to submit from external button
document.getElementById('my-button').addEventListener('click', () => {
  document.dispatchEvent(
    new CustomEvent('primer:card-submit', {
      bubbles: true,
      composed: true,
      detail: { source: 'external-button' },
    }),
  );
});
配置提交按钮行为:
javascript
checkout.options = {
  submitButton: {
    amountVisible: true, // 在按钮上显示金额(例如:"Pay $12.34")
    useBuiltInButton: true, // 默认值:true,设为false使用外部按钮
  },
};
使用外部提交按钮:
javascript
// 隐藏内置按钮
checkout.options = {
  submitButton: {
    useBuiltInButton: false,
  },
};

// 触发事件以通过外部按钮提交
document.getElementById('my-button').addEventListener('click', () => {
  document.dispatchEvent(
    new CustomEvent('primer:card-submit', {
      bubbles: true,
      composed: true,
      detail: { source: 'external-button' },
    }),
  );
});

PayPal Integration

PayPal集成

PayPal integration requires SDK Core (
sdkCore: true
, which is the default).
PayPal集成需要SDK Core(
sdkCore: true
,默认已启用)。

Basic Configuration

基础配置

javascript
import { PaymentMethodType } from '@primer-io/primer-js';

checkout.options = {
  sdkCore: true, // Default, required for PayPal
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
  paypal: {
    style: {
      layout: 'vertical',
      color: 'gold',
      shape: 'rect',
      height: 45,
      label: 'checkout',
    },
  },
};
javascript
import { PaymentMethodType } from '@primer-io/primer-js';

checkout.options = {
  sdkCore: true, // 默认值,PayPal必需
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
  paypal: {
    style: {
      layout: 'vertical',
      color: 'gold',
      shape: 'rect',
      height: 45,
      label: 'checkout',
    },
  },
};

Button Styling Options

按钮样式选项

Customize PayPal button appearance:
OptionTypeDefaultDescription
layout
'vertical'
|
'horizontal'
'vertical'
Button layout orientation
color
'gold'
|
'blue'
|
'silver'
|
'white'
|
'black'
'gold'
Button color theme
shape
'rect'
|
'pill'
'rect'
Button border shape
height
number
(25-55)
40
Button height in pixels
label
'paypal'
|
'checkout'
|
'buynow'
|
'pay'
|
'installment'
'paypal'
Button label text
tagline
boolean
false
Show tagline (horizontal layout only)
borderRadius
number
(0-55)
4
Button corner radius in pixels
disableMaxWidth
boolean
false
Disable maximum width constraint
Styling Examples:
javascript
// Horizontal blue pill buttons
paypal: {
  style: {
    layout: 'horizontal',
    color: 'blue',
    shape: 'pill',
    height: 45,
    label: 'checkout',
    tagline: false,
  }
}

// Vertical silver buttons with custom border radius
paypal: {
  style: {
    layout: 'vertical',
    color: 'silver',
    shape: 'rect',
    height: 50,
    borderRadius: 8,
    disableMaxWidth: true,
  }
}
自定义PayPal按钮外观:
选项类型默认值描述
layout
'vertical'
|
'horizontal'
'vertical'
按钮布局方向
color
'gold'
|
'blue'
|
'silver'
|
'white'
|
'black'
'gold'
按钮颜色主题
shape
'rect'
|
'pill'
'rect'
按钮边框形状
height
number
(25-55)
40
按钮高度(像素)
label
'paypal'
|
'checkout'
|
'buynow'
|
'pay'
|
'installment'
'paypal'
按钮标签文本
tagline
boolean
false
显示标语(仅横向布局支持)
borderRadius
number
(0-55)
4
按钮圆角半径(像素)
disableMaxWidth
boolean
false
禁用最大宽度约束
样式示例:
javascript
// 横向蓝色胶囊按钮
paypal: {
  style: {
    layout: 'horizontal',
    color: 'blue',
    shape: 'pill',
    height: 45,
    label: 'checkout',
    tagline: false,
  }
}

// 纵向银色按钮,自定义圆角
paypal: {
  style: {
    layout: 'vertical',
    color: 'silver',
    shape: 'rect',
    height: 50,
    borderRadius: 8,
    disableMaxWidth: true,
  }
}

Funding Source Control

资金来源控制

Control which PayPal funding sources are available:
javascript
paypal: {
  disableFunding: ['credit', 'paylater', 'card'], // Hide these options
  enableFunding: ['venmo'], // Explicitly enable Venmo
}
Available Funding Sources:
  • card
    - Guest card payments (credit/debit without PayPal account)
  • credit
    - PayPal Credit (US, UK)
  • paylater
    - PayPal Pay Later
  • venmo
    - Venmo (US)
Funding Control Examples:
javascript
// Only PayPal balance and bank account
paypal: {
  disableFunding: ['card', 'credit', 'paylater', 'venmo'],
}

// PayPal with Venmo only
paypal: {
  disableFunding: ['card', 'credit', 'paylater'],
  enableFunding: ['venmo'],
}
Important:
disableFunding
takes precedence over
enableFunding
. If a source appears in both arrays, it will be disabled.
控制可用的PayPal资金来源:
javascript
paypal: {
  disableFunding: ['credit', 'paylater', 'card'], // 隐藏这些选项
  enableFunding: ['venmo'], // 显式启用Venmo
}
可用资金来源:
  • card
    - 访客卡片支付(无需PayPal账户的信用卡/借记卡)
  • credit
    - PayPal Credit(美国、英国)
  • paylater
    - PayPal先买后付
  • venmo
    - Venmo(美国)
资金控制示例:
javascript
// 仅支持PayPal余额与银行账户
paypal: {
  disableFunding: ['card', 'credit', 'paylater', 'venmo'],
}

// 仅支持PayPal与Venmo
paypal: {
  disableFunding: ['card', 'credit', 'paylater'],
  enableFunding: ['venmo'],
}
注意:
disableFunding
优先级高于
enableFunding
。若某资金来源同时出现在两个数组中,它将被禁用。

PayPal Vaulting

PayPal存储功能

Enable vaulting to allow customers to save their PayPal account:
javascript
paypal: {
  vault: true, // Enable vaulting in SDK
}
Requirements:
Vaulting requires both SDK configuration and server-side setup:
  1. SDK Configuration: Set
    vault: true
    in PayPal options
  2. Client Session: Configure
    vaultOnSuccess: true
    in your client session creation request
Legacy SDK: For
sdkCore: false
, use:
javascript
paypal: {
  paymentFlow: 'PREFER_VAULT',
}
启用存储功能以允许客户保存其PayPal账户:
javascript
paypal: {
  vault: true, // 在SDK中启用存储
}
要求:
存储功能需要同时配置SDK与服务端
  1. SDK配置:在PayPal选项中设置
    vault: true
  2. 客户端会话:在创建客户端会话的请求中配置
    vaultOnSuccess: true
Legacy SDK:对于
sdkCore: false
,使用:
javascript
paypal: {
  paymentFlow: 'PREFER_VAULT',
}

Complete PayPal Example

完整PayPal示例

javascript
checkout.options = {
  sdkCore: true,
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
  paypal: {
    // Button styling
    style: {
      layout: 'vertical',
      color: 'gold',
      shape: 'pill',
      height: 45,
      label: 'checkout',
      borderRadius: 6,
    },

    // Funding control
    disableFunding: ['credit', 'card'],
    enableFunding: ['venmo'],

    // Vaulting
    vault: true,
  },
};
javascript
checkout.options = {
  sdkCore: true,
  enabledPaymentMethods: [
    PaymentMethodType.PAYMENT_CARD,
    PaymentMethodType.PAYPAL,
  ],
  paypal: {
    // 按钮样式
    style: {
      layout: 'vertical',
      color: 'gold',
      shape: 'pill',
      height: 45,
      label: 'checkout',
      borderRadius: 6,
    },

    // 资金控制
    disableFunding: ['credit', 'card'],
    enableFunding: ['venmo'],

    // 存储功能
    vault: true,
  },
};

Events & Callbacks

事件与回调

Primer Checkout uses an event-driven architecture with custom DOM events and callbacks. Events bubble up through the DOM, and callbacks provide direct handling of payment lifecycle.
Primer Checkout采用事件驱动架构,包含自定义DOM事件与回调。事件会在DOM中冒泡,回调则直接处理支付生命周期。

Core Events

核心事件

primer:ready

primer:ready

Dispatched when the Primer SDK is fully initialized and ready for use.
Event Detail: Contains the PrimerJS instance with methods and callbacks.
Usage:
javascript
const checkout = document.querySelector('primer-checkout');

checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;
  console.log('✅ Primer SDK ready');

  // Configure payment success handler
  primer.onPaymentSuccess = ({ paymentSummary, paymentMethodType }) => {
    console.log('✅ Payment successful', paymentSummary.id);
    console.log('💳 Method:', paymentMethodType);

    // Access available payment data (PII-filtered)
    if (paymentSummary.paymentMethodData?.last4Digits) {
      console.log('Last 4:', paymentSummary.paymentMethodData.last4Digits);
    }

    // Redirect to confirmation page
    window.location.href = `/order/confirmation?id=${paymentSummary.orderId}`;
  };

  // Configure payment failure handler
  primer.onPaymentFailure = ({ error, paymentMethodType }) => {
    console.error('❌ Payment failed', error.message);
    console.error('Error code:', error.code);

    // Log diagnostics ID for support
    if (error.diagnosticsId) {
      console.error('Diagnostics ID:', error.diagnosticsId);
    }

    // Show error message and allow retry
    showErrorMessage(error.message);
  };

  // Configure vaulted methods update handler
  primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
    console.log('Vault updated:', vaultedPayments.size(), 'methods');
    updateVaultUI(vaultedPayments.toArray());
  };
});
当Primer SDK完全初始化并可用时触发。
事件详情: 包含带有方法与回调的PrimerJS实例。
用法:
javascript
const checkout = document.querySelector('primer-checkout');

checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;
  console.log('✅ Primer SDK已就绪');

  // 配置支付成功处理器
  primer.onPaymentSuccess = ({ paymentSummary, paymentMethodType }) => {
    console.log('✅ 支付成功', paymentSummary.id);
    console.log('💳 支付方式:', paymentMethodType);

    // 访问经过PII过滤的可用支付数据
    if (paymentSummary.paymentMethodData?.last4Digits) {
      console.log('最后4位:', paymentSummary.paymentMethodData.last4Digits);
    }

    // 跳转到确认页面
    window.location.href = `/order/confirmation?id=${paymentSummary.orderId}`;
  };

  // 配置支付失败处理器
  primer.onPaymentFailure = ({ error, paymentMethodType }) => {
    console.error('❌ 支付失败', error.message);
    console.error('错误代码:', error.code);

    // 记录诊断ID以获取支持
    if (error.diagnosticsId) {
      console.error('诊断ID:', error.diagnosticsId);
    }

    // 显示错误信息并允许重试
    showErrorMessage(error.message);
  };

  // 配置已存储支付方式更新处理器
  primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
    console.log('存储已更新:', vaultedPayments.size(), '种支付方式');
    updateVaultUI(vaultedPayments.toArray());
  };
});

primer:state-change

primer:state-change

Dispatched whenever the checkout state changes (processing, success, error, etc.).
Event Detail: Contains
isProcessing
,
isSuccessful
,
isLoading
,
primerJsError
,
paymentFailure
.
Usage:
javascript
checkout.addEventListener('primer:state-change', (event) => {
  const { isProcessing, isSuccessful, primerJsError, paymentFailure } =
    event.detail;

  if (isProcessing) {
    console.log('⏳ Processing payment...');
    showLoadingSpinner();
  } else if (isSuccessful) {
    console.log('✅ Payment successful!');
    hideLoadingSpinner();
  } else if (primerJsError || paymentFailure) {
    const errorMessage =
      primerJsError?.message || paymentFailure?.message || 'An error occurred';
    console.error('❌ Payment failed:', errorMessage);

    // Log error code for debugging
    if (paymentFailure?.code) {
      console.error('Error code:', paymentFailure.code);
    }

    hideLoadingSpinner();
    showErrorMessage(errorMessage);
  }
});
State Field Changes in v0.7.0:
  • error
    primerJsError
    (SDK-level errors)
  • failure
    paymentFailure
    (payment-level failures)
每当结账状态变化(处理中、成功、错误等)时触发。
事件详情: 包含
isProcessing
isSuccessful
isLoading
primerJsError
paymentFailure
用法:
javascript
checkout.addEventListener('primer:state-change', (event) => {
  const { isProcessing, isSuccessful, primerJsError, paymentFailure } =
    event.detail;

  if (isProcessing) {
    console.log('⏳ 正在处理支付...');
    showLoadingSpinner();
  } else if (isSuccessful) {
    console.log('✅ 支付成功!');
    hideLoadingSpinner();
  } else if (primerJsError || paymentFailure) {
    const errorMessage =
      primerJsError?.message || paymentFailure?.message || '发生错误';
    console.error('❌ 支付失败:', errorMessage);

    // 记录错误代码用于调试
    if (paymentFailure?.code) {
      console.error('错误代码:', paymentFailure.code);
    }

    hideLoadingSpinner();
    showErrorMessage(errorMessage);
  }
});
v0.7.0中的状态字段变更:
  • error
    primerJsError
    (SDK级错误)
  • failure
    paymentFailure
    (支付级失败)

primer:methods-update

primer:methods-update

Dispatched when available payment methods are loaded and ready.
Event Detail: Contains
InitializedPayments
instance with
toArray()
and
size()
methods.
Usage:
javascript
checkout.addEventListener('primer:methods-update', (event) => {
  const paymentMethods = event.detail.toArray();

  console.log('Available payment methods:', paymentMethods);
  console.log('Total methods:', paymentMethods.length);

  // Access individual method details
  paymentMethods.forEach((method) => {
    console.log('Method type:', method.type);
  });
});
Tip: For most layout and filtering use cases, the
primer-payment-method-container
component provides a simpler declarative approach without requiring event listeners.
当可用支付方式加载完成并就绪时触发。
事件详情: 包含带有
toArray()
size()
方法的
InitializedPayments
实例。
用法:
javascript
checkout.addEventListener('primer:methods-update', (event) => {
  const paymentMethods = event.detail.toArray();

  console.log('可用支付方式:', paymentMethods);
  console.log('总数量:', paymentMethods.length);

  // 访问单个支付方式详情
  paymentMethods.forEach((method) => {
    console.log('支付方式类型:', method.type);
  });
});
提示: 对于大多数布局与过滤场景,
primer-payment-method-container
组件提供了更简单的声明式方式,无需使用事件监听器。

Payment Lifecycle Events (New in v0.7.0)

支付生命周期事件(v0.7.0新增)

Payment lifecycle events provide granular tracking of payment processing stages with detailed data payloads.
支付生命周期事件可对支付处理阶段进行精细化跟踪,并提供详细的数据负载。

primer:payment-start

primer:payment-start

Dispatched when payment processing begins, immediately after the user initiates a payment.
Event Detail:
undefined
(use as trigger signal only)
Usage:
javascript
document.addEventListener('primer:payment-start', () => {
  console.log('💳 Payment processing started');

  // Show loading indicators
  showPaymentLoadingSpinner();

  // Disable form inputs to prevent duplicate submissions
  disableFormInputs();

  // Track payment initiation
  analytics.track('Payment Started');
});
当用户发起支付后,支付处理开始时触发。
事件详情:
undefined
(仅用作触发信号)
用法:
javascript
document.addEventListener('primer:payment-start', () => {
  console.log('💳 支付处理已启动');

  // 显示加载指示器
  showPaymentLoadingSpinner();

  // 禁用表单输入以防止重复提交
  disableFormInputs();

  // 跟踪支付发起事件
  analytics.track('Payment Started');
});

primer:payment-success

primer:payment-success

Dispatched when a payment completes successfully.
Event Detail:
typescript
{
  paymentSummary: PaymentSummary; // PII-filtered payment data
  paymentMethodType: string; // e.g., 'PAYMENT_CARD', 'PAYPAL'
  timestamp: number; // Unix timestamp of success
}
PaymentSummary Structure:
Available fields (PII-filtered):
  • id
    : Payment ID
  • orderId
    : Merchant order ID
  • paymentMethodType
    : Type of payment method used
  • paymentMethodData
    : Object containing non-sensitive card data
    • last4Digits
      : Last 4 digits of card number (if applicable)
    • network
      : Card network (Visa, Mastercard, etc.)
    • paymentMethodType
      : Payment method type
Filtered fields (not available):
  • cardholderName
    : Filtered for PII protection
Usage:
javascript
document.addEventListener('primer:payment-success', (event) => {
  const { paymentSummary, paymentMethodType, timestamp } = event.detail;

  console.log('✅ Payment successful!');
  console.log('Payment ID:', paymentSummary.id);
  console.log('Order ID:', paymentSummary.orderId);
  console.log('Method:', paymentMethodType);
  console.log('Timestamp:', new Date(timestamp));

  // Access available payment method data
  if (paymentSummary.paymentMethodData?.last4Digits) {
    console.log('Last 4 digits:', paymentSummary.paymentMethodData.last4Digits);
    console.log('Network:', paymentSummary.paymentMethodData.network);
  }

  // Track successful payment in analytics
  analytics.track('Payment Successful', {
    paymentId: paymentSummary.id,
    orderId: paymentSummary.orderId,
    method: paymentMethodType,
    last4: paymentSummary.paymentMethodData?.last4Digits,
  });

  // Redirect to confirmation page
  window.location.href = `/order/confirmation?id=${paymentSummary.orderId}`;
});
Important: The
PaymentSummary
object filters sensitive information like cardholder names. Only use the provided non-sensitive fields for display and analytics.
当支付成功完成时触发。
事件详情:
typescript
{
  paymentSummary: PaymentSummary; // 经过PII过滤的支付数据
  paymentMethodType: string; // 例如:'PAYMENT_CARD'、'PAYPAL'
  timestamp: number; // 支付成功的Unix时间戳
}
PaymentSummary结构:
可用字段(经过PII过滤):
  • id
    : 支付ID
  • orderId
    : 商家订单ID
  • paymentMethodType
    : 使用的支付方式类型
  • paymentMethodData
    : 包含非敏感卡片数据的对象
    • last4Digits
      : 卡片最后4位(若适用)
    • network
      : 卡片网络(Visa、Mastercard等)
    • paymentMethodType
      : 支付方式类型
已过滤字段(不可用):
  • cardholderName
    : 为保护PII已过滤
用法:
javascript
document.addEventListener('primer:payment-success', (event) => {
  const { paymentSummary, paymentMethodType, timestamp } = event.detail;

  console.log('✅ 支付成功!');
  console.log('支付ID:', paymentSummary.id);
  console.log('订单ID:', paymentSummary.orderId);
  console.log('支付方式:', paymentMethodType);
  console.log('时间戳:', new Date(timestamp));

  // 访问可用的支付方式数据
  if (paymentSummary.paymentMethodData?.last4Digits) {
    console.log('卡片最后4位:', paymentSummary.paymentMethodData.last4Digits);
    console.log('卡片网络:', paymentSummary.paymentMethodData.network);
  }

  // 在分析工具中跟踪成功支付事件
  analytics.track('Payment Successful', {
    paymentId: paymentSummary.id,
    orderId: paymentSummary.orderId,
    method: paymentMethodType,
    last4: paymentSummary.paymentMethodData?.last4Digits,
  });

  // 跳转到确认页面
  window.location.href = `/order/confirmation?id=${paymentSummary.orderId}`;
});
注意:
PaymentSummary
对象会过滤持卡人姓名等敏感信息。仅使用提供的非敏感字段进行展示与分析。

primer:payment-failure

primer:payment-failure

Dispatched when a payment fails or encounters an error.
Event Detail:
typescript
{
  error: {
    code: string;           // Error code (e.g., 'CARD_DECLINED')
    message: string;        // User-friendly error message
    diagnosticsId?: string; // Optional diagnostics ID for support
    data?: any;            // Optional additional error data
  };
  paymentSummary?: PaymentSummary;  // Optional, may be undefined
  paymentMethodType: string;
  timestamp: number;
}
Usage:
javascript
document.addEventListener('primer:payment-failure', (event) => {
  const { error, paymentSummary, paymentMethodType, timestamp } = event.detail;

  console.error('❌ Payment failed');
  console.error('Error code:', error.code);
  console.error('Error message:', error.message);

  if (error.diagnosticsId) {
    console.error('Diagnostics ID:', error.diagnosticsId);
  }

  // Display error message to user
  showErrorMessage(error.message);

  // Track payment failure in analytics
  analytics.track('Payment Failed', {
    errorCode: error.code,
    errorMessage: error.message,
    diagnosticsId: error.diagnosticsId,
    method: paymentMethodType,
    timestamp: new Date(timestamp),
  });

  // Send to error tracking service
  if (error.diagnosticsId) {
    errorTracker.capturePaymentFailure({
      diagnosticsId: error.diagnosticsId,
      code: error.code,
      paymentMethodType,
    });
  }
});
当支付失败或遇到错误时触发。
事件详情:
typescript
{
  error: {
    code: string;           // 错误代码(例如:'CARD_DECLINED')
    message: string;        // 用户友好的错误提示
    diagnosticsId?: string; // 可选的诊断ID,用于获取支持
    data?: any;            // 可选的额外错误数据
  };
  paymentSummary?: PaymentSummary;  // 可选,可能为undefined
  paymentMethodType: string;
  timestamp: number;
}
用法:
javascript
document.addEventListener('primer:payment-failure', (event) => {
  const { error, paymentSummary, paymentMethodType, timestamp } = event.detail;

  console.error('❌ 支付失败');
  console.error('错误代码:', error.code);
  console.error('错误信息:', error.message);

  if (error.diagnosticsId) {
    console.error('诊断ID:', error.diagnosticsId);
  }

  // 向用户显示错误信息
  showErrorMessage(error.message);

  // 在分析工具中跟踪支付失败事件
  analytics.track('Payment Failed', {
    errorCode: error.code,
    errorMessage: error.message,
    diagnosticsId: error.diagnosticsId,
    method: paymentMethodType,
    timestamp: new Date(timestamp),
  });

  // 将错误发送到错误跟踪服务
  if (error.diagnosticsId) {
    errorTracker.capturePaymentFailure({
      diagnosticsId: error.diagnosticsId,
      code: error.code,
      paymentMethodType,
    });
  }
});

Vault Events (New in v0.7.0)

Vault事件(v0.7.0新增)

primer:vault:methods-update

primer:vault:methods-update

Dispatched when vaulted payment methods are loaded, updated, or when the vault state changes.
Event Detail:
typescript
{
  vaultedPayments: InitializedVaultedPayments; // Vault API instance
  timestamp: number; // Unix timestamp
}
InitializedVaultedPayments API:
  • toArray()
    : Returns array of
    VaultedPaymentMethodSummary
    objects
  • get(id: string)
    : Gets a specific vaulted payment method by ID
  • size()
    : Returns the number of saved payment methods
VaultedPaymentMethodSummary Structure:
  • id
    : Unique identifier for the vaulted payment method
  • analyticsId
    : Analytics tracking identifier
  • paymentMethodType
    : Type of payment method (e.g., 'PAYMENT_CARD', 'ADYEN_STRIPE_ACH')
  • paymentInstrumentType
    : Instrument type
  • paymentInstrumentData
    : Object with PII-filtered payment instrument details
    • last4Digits
      : Last 4 digits of card (cards only)
    • network
      : Card network like VISA, MASTERCARD (cards only)
    • accountNumberLastFourDigits
      : Last 4 of account number (ACH only)
    • bankName
      : Bank name (ACH only)
    • accountType
      : CHECKING or SAVINGS (ACH only)
    • email
      : Email address (wallet methods like PayPal)
  • userDescription
    : Optional user-provided description
Important: Sensitive fields like cardholder names, expiration dates, and full account numbers are filtered out for security.
Usage:
javascript
document.addEventListener('primer:vault:methods-update', (event) => {
  const { vaultedPayments, timestamp } = event.detail;

  console.log('💳 Vault methods updated');
  console.log('Total saved methods:', vaultedPayments.size());

  // Get all saved payment methods
  const methods = vaultedPayments.toArray();

  methods.forEach((method) => {
    console.log('Method ID:', method.id);
    console.log('Type:', method.paymentMethodType);

    if (method.paymentInstrumentData) {
      console.log('Last 4:', method.paymentInstrumentData.last4Digits);
      console.log('Network:', method.paymentInstrumentData.network);
    }
  });

  // Update UI with saved methods
  updateVaultDisplay(methods);

  // Track vault updates in analytics
  analytics.track('Vault Methods Updated', {
    count: methods.length,
    timestamp,
  });
});
当已存储的支付方式被加载、更新或Vault状态变化时触发。
事件详情:
typescript
{
  vaultedPayments: InitializedVaultedPayments; // Vault API实例
  timestamp: number; // Unix时间戳
}
InitializedVaultedPayments API:
  • toArray()
    : 返回
    VaultedPaymentMethodSummary
    对象数组
  • get(id: string)
    : 通过ID获取特定的已存储支付方式
  • size()
    : 返回已保存支付方式的数量
VaultedPaymentMethodSummary结构:
  • id
    : 已存储支付方式的唯一标识符
  • analyticsId
    : 分析跟踪标识符
  • paymentMethodType
    : 支付方式类型(例如:'PAYMENT_CARD'、'ADYEN_STRIPE_ACH')
  • paymentInstrumentType
    : 支付工具类型
  • paymentInstrumentData
    : 包含经过PII过滤的支付工具详情的对象
    • last4Digits
      : 卡片最后4位(仅卡片)
    • network
      : 卡片网络如VISA、MASTERCARD(仅卡片)
    • accountNumberLastFourDigits
      : 账户最后4位(仅ACH)
    • bankName
      : 银行名称(仅ACH)
    • accountType
      : CHECKING或SAVINGS(仅ACH)
    • email
      : 邮箱地址(仅PayPal等钱包支付方式)
  • userDescription
    : 可选的用户提供的描述
注意: 持卡人姓名、有效期和完整账户号等敏感字段已被过滤以保障安全。
用法:
javascript
document.addEventListener('primer:vault:methods-update', (event) => {
  const { vaultedPayments, timestamp } = event.detail;

  console.log('💳 存储的支付方式已更新');
  console.log('已保存支付方式总数:', vaultedPayments.size());

  // 获取所有已保存的支付方式
  const methods = vaultedPayments.toArray();

  methods.forEach((method) => {
    console.log('支付方式ID:', method.id);
    console.log('类型:', method.paymentMethodType);

    if (method.paymentInstrumentData) {
      console.log('最后4位:', method.paymentInstrumentData.last4Digits);
      console.log('网络:', method.paymentInstrumentData.network);
    }
  });

  // 更新UI以显示已保存的支付方式
  updateVaultDisplay(methods);

  // 在分析工具中跟踪存储更新事件
  analytics.track('Vault Methods Updated', {
    count: methods.length,
    timestamp,
  });
});

Card Events

卡片事件

Card events are specific to card payment form interactions and validation.
卡片事件专用于卡片支付表单的交互与验证。

primer:card-success

primer:card-success

Dispatched when a card form is successfully validated and submitted.
Event Detail: Contains
result
object with payment submission data.
Usage:
javascript
checkout.addEventListener('primer:card-success', (event) => {
  const result = event.detail.result;
  console.log('✅ Card form submitted successfully', result);

  // Disable form to prevent duplicate submissions
  disableCardForm();

  // Show intermediate success message
  showMessage('Processing your payment...');
});
当卡片表单验证通过并成功提交时触发。
事件详情: 包含带有支付提交数据的
result
对象。
用法:
javascript
checkout.addEventListener('primer:card-success', (event) => {
  const result = event.detail.result;
  console.log('✅ 卡片表单提交成功', result);

  // 禁用表单以防止重复提交
  disableCardForm();

  // 显示中间成功提示
  showMessage('正在处理你的支付...');
});

primer:card-error

primer:card-error

Dispatched when card validation fails or submission encounters an error.
Event Detail: Contains
errors
array with validation error objects.
Usage:
javascript
checkout.addEventListener('primer:card-error', (event) => {
  const errors = event.detail.errors;
  console.error('❌ Card validation errors:', errors);

  // Log each error
  errors.forEach((error) => {
    console.error(`${error.field}: ${error.error}`);
  });

  // Display custom error UI
  displayValidationErrors(errors);
});
当卡片验证失败或提交遇到错误时触发。
事件详情: 包含带有验证错误对象的
errors
数组。
用法:
javascript
checkout.addEventListener('primer:card-error', (event) => {
  const errors = event.detail.errors;
  console.error('❌ 卡片验证错误:', errors);

  // 记录每个错误
  errors.forEach((error) => {
    console.error(`${error.field}: ${error.error}`);
  });

  // 显示自定义错误UI
  displayValidationErrors(errors);
});

primer:card-network-change

primer:card-network-change

Dispatched when the card network (Visa, Mastercard, etc.) is detected or changes based on the card number input.
Event Detail: Contains
detectedCardNetwork
,
selectableCardNetworks
, and
isLoading
.
Usage:
javascript
checkout.addEventListener('primer:card-network-change', (event) => {
  const { detectedCardNetwork, selectableCardNetworks, isLoading } =
    event.detail;

  if (isLoading) {
    console.log('🔍 Detecting card network...');
    return;
  }

  if (detectedCardNetwork) {
    const network = detectedCardNetwork.network;
    console.log('💳 Card network detected:', network);

    // Show card brand logo
    updateCardBrandLogo(network);

    // Track card network detection
    analytics.track('Card Network Detected', { network });
  }
});
当根据卡片号码输入检测到卡片网络(Visa、Mastercard等)或网络变化时触发。
事件详情: 包含
detectedCardNetwork
selectableCardNetworks
isLoading
用法:
javascript
checkout.addEventListener('primer:card-network-change', (event) => {
  const { detectedCardNetwork, selectableCardNetworks, isLoading } =
    event.detail;

  if (isLoading) {
    console.log('🔍 正在检测卡片网络...');
    return;
  }

  if (detectedCardNetwork) {
    const network = detectedCardNetwork.network;
    console.log('💳 检测到卡片网络:', network);

    // 显示卡片品牌Logo
    updateCardBrandLogo(network);

    // 跟踪卡片网络检测事件
    analytics.track('Card Network Detected', { network });
  }
});

Triggerable Events

可触发事件

Triggerable events are events that YOU dispatch to control SDK behavior.
可触发事件是由你主动触发以控制SDK行为的事件。

primer:card-submit

primer:card-submit

Trigger card form submission programmatically from anywhere in your application.
Event Detail: Optional
source
property to identify the trigger source.
Usage:
The checkout component listens for this event at the document level, so you can dispatch it from anywhere without referencing the card form element directly.
javascript
// Trigger card form submission from anywhere
document.dispatchEvent(
  new CustomEvent('primer:card-submit', {
    bubbles: true,
    composed: true,
    detail: { source: 'custom-button' },
  }),
);
Complete Example: External Submit Button
html
<primer-checkout client-token="your-client-token">
  <primer-main slot="main">
    <div slot="payments">
      <primer-card-form>
        <div slot="card-form-content">
          <primer-input-card-number></primer-input-card-number>
          <primer-input-card-expiry></primer-input-card-expiry>
          <primer-input-cvv></primer-input-cvv>
          <!-- No submit button inside the form -->
        </div>
      </primer-card-form>

      <!-- External submit button outside the card form -->
      <button id="external-submit" class="custom-pay-button">Pay Now</button>
    </div>
  </primer-main>
</primer-checkout>

<script>
  // Set up external button
  document.getElementById('external-submit').addEventListener('click', () => {
    // Dispatch event to document - checkout listens at document level
    document.dispatchEvent(
      new CustomEvent('primer:card-submit', {
        bubbles: true,
        composed: true,
        detail: { source: 'external-button' },
      }),
    );
  });

  // Listen for submission results
  const checkout = document.querySelector('primer-checkout');

  checkout.addEventListener('primer:card-success', (event) => {
    console.log('✅ Card form submitted successfully');
  });

  checkout.addEventListener('primer:card-error', (event) => {
    console.log('❌ Validation errors:', event.detail.errors);
  });
</script>
Important:
  • The
    bubbles: true
    and
    composed: true
    properties are required
  • Always include a meaningful
    source
    parameter for debugging
  • The checkout component handles the event at document level and forwards it internally
从应用中的任意位置以编程方式触发卡片表单提交。
事件详情: 可选的
source
属性,用于标识触发来源。
用法:
结账组件会在文档级别监听此事件,因此你可以从任意位置触发它,无需引用卡片表单元素。
javascript
// 从任意位置触发卡片表单提交
document.dispatchEvent(
  new CustomEvent('primer:card-submit', {
    bubbles: true,
    composed: true,
    detail: { source: 'custom-button' },
  }),
);
完整示例:外部提交按钮
html
<primer-checkout client-token="your-client-token">
  <primer-main slot="main">
    <div slot="payments">
      <primer-card-form>
        <div slot="card-form-content">
          <primer-input-card-number></primer-input-card-number>
          <primer-input-card-expiry></primer-input-card-expiry>
          <primer-input-cvv></primer-input-cvv>
          <!-- 表单内部无提交按钮 -->
        </div>
      </primer-card-form>

      <!-- 卡片表单外部的外部提交按钮 -->
      <button id="external-submit" class="custom-pay-button">立即支付</button>
    </div>
  </primer-main>
</primer-checkout>

<script>
  // 设置外部按钮
  document.getElementById('external-submit').addEventListener('click', () => {
    // 向文档发送事件 - 结账组件在文档级别监听
    document.dispatchEvent(
      new CustomEvent('primer:card-submit', {
        bubbles: true,
        composed: true,
        detail: { source: 'external-button' },
      }),
    );
  });

  // 监听提交结果
  const checkout = document.querySelector('primer-checkout');

  checkout.addEventListener('primer:card-success', (event) => {
    console.log('✅ 卡片表单提交成功');
  });

  checkout.addEventListener('primer:card-error', (event) => {
    console.log('❌ 验证错误:', event.detail.errors);
  });
</script>
注意:
  • 必须设置
    bubbles: true
    composed: true
    属性
  • 始终包含有意义的
    source
    参数以方便调试
  • 结账组件在文档级别处理事件并内部转发

Vault Integration

Vault集成

Vault allows customers to save payment methods for future use.
Vault允许客户为未来使用保存支付方式。

Configuration

配置

javascript
checkout.options = {
  vault: {
    enabled: true, // Enable vaulting
    showEmptyState: true, // Show empty state message when no saved methods
  },
};
javascript
checkout.options = {
  vault: {
    enabled: true, // 启用存储功能
    showEmptyState: true, // 当无已保存支付方式时显示空状态提示
  },
};

Vault Events

Vault事件

Use the
primer:vault:methods-update
event to respond to vault changes:
javascript
document.addEventListener('primer:vault:methods-update', (event) => {
  const { vaultedPayments, timestamp } = event.detail;

  console.log('Total saved methods:', vaultedPayments.size());

  // Get all methods
  const methods = vaultedPayments.toArray();
  methods.forEach((method) => {
    console.log(`${method.network} ending in ${method.last4Digits}`);
  });

  // Get specific method
  const method = vaultedPayments.get('payment-method-id');
  if (method) {
    console.log('Found method:', method);
  }
});
使用
primer:vault:methods-update
事件响应Vault变化:
javascript
document.addEventListener('primer:vault:methods-update', (event) => {
  const { vaultedPayments, timestamp } = event.detail;

  console.log('已保存支付方式总数:', vaultedPayments.size());

  // 获取所有支付方式
  const methods = vaultedPayments.toArray();
  methods.forEach((method) => {
    console.log(`${method.network} 结尾为 ${method.last4Digits}`);
  });

  // 获取特定支付方式
  const method = vaultedPayments.get('payment-method-id');
  if (method) {
    console.log('找到支付方式:', method);
  }
});

Vault Callback

Vault回调

Use the callback for direct vault handling in the
primer:ready
event:
javascript
checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;

  primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
    console.log('Vault updated:', vaultedPayments.size(), 'methods');
    updateVaultUI(vaultedPayments.toArray());
  };
});
primer:ready
事件中使用回调直接处理Vault:
javascript
checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;

  primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
    console.log('存储已更新:', vaultedPayments.size(), '种支付方式');
    updateVaultUI(vaultedPayments.toArray());
  };
});

Complete Vault Example

完整Vault示例

html
<primer-checkout client-token="your-client-token">
  <primer-main slot="main">
    <div slot="payments">
      <!-- Vaulted methods will appear here automatically -->
      <primer-payment-method-container></primer-payment-method-container>
    </div>
  </primer-main>
</primer-checkout>

<script>
  const checkout = document.querySelector('primer-checkout');

  // Configure vault
  checkout.options = {
    vault: {
      enabled: true,
      showEmptyState: true,
    },
  };

  // Handle vault updates
  checkout.addEventListener('primer:ready', (event) => {
    const primer = event.detail;

    primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
      const methods = vaultedPayments.toArray();
      console.log(`Loaded ${methods.length} vaulted payment methods`);

      // Display saved methods
      methods.forEach((method) => {
        if (method.paymentInstrumentData) {
          console.log(
            `${method.paymentInstrumentData.network} ending in ${method.paymentInstrumentData.last4Digits}`,
          );
        }
      });
    };
  });
</script>
html
<primer-checkout client-token="your-client-token">
  <primer-main slot="main">
    <div slot="payments">
      <!-- 已存储的支付方式将自动显示在此处 -->
      <primer-payment-method-container></primer-payment-method-container>
    </div>
  </primer-main>
</primer-checkout>

<script>
  const checkout = document.querySelector('primer-checkout');

  // 配置Vault
  checkout.options = {
    vault: {
      enabled: true,
      showEmptyState: true,
    },
  };

  // 处理Vault更新
  checkout.addEventListener('primer:ready', (event) => {
    const primer = event.detail;

    primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
      const methods = vaultedPayments.toArray();
      console.log(`已加载 ${methods.length} 种已存储支付方式`);

      // 显示已保存的支付方式
      methods.forEach((method) => {
        if (method.paymentInstrumentData) {
          console.log(
            `${method.paymentInstrumentData.network} 结尾为 ${method.paymentInstrumentData.last4Digits}`,
          );
        }
      });
    };
  });
</script>

React Integration Patterns

React集成模式

Critical: Stable Object References

关键:稳定对象引用

THE MOST COMMON MISTAKE with Primer in React is creating new object references on every render, causing component re-initialization and loss of user input.
This applies to BOTH React 18 AND React 19.
在React中使用Primer时最常见的错误是在每次渲染时创建新的对象引用,导致组件重新初始化并丢失用户输入。
这适用于React 18和React 19

React 18 vs React 19 Comparison

React 18 vs React 19对比

React 19 introduced improved support for web components, but the need for stable references remains critical.
AspectReact 18React 19
How objects passedref + useEffectJSX props
Attribute conversionConverts objects to
[object Object]
Assigns as properties
Code patternImperativeDeclarative
Lines of code~15 lines~5 lines
Stable references needed?✅ Yes (always)✅ Yes (always)
Can inline objects?❌ No (doesn't work)❌ No (causes issues)
React 19引入了对Web组件的改进支持,但对稳定引用的需求仍然至关重要。
方面React 18React 19
对象传递方式ref + useEffectJSX props
属性转换将对象转换为
[object Object]
作为属性直接赋值
代码模式命令式声明式
代码行数~15行~5行
是否需要稳定引用?✅ 是(始终需要)✅ 是(始终需要)
能否内联对象?❌ 否(无法工作)❌ 否(会导致问题)

ALL Three Stable Reference Patterns

三种稳定引用模式

Pattern 1: Constant Outside Component (For Static Options)

模式1:组件外部常量(适用于静态选项)

typescript
// ✅ Created once at module load, same reference forever
const SDK_OPTIONS = {
  locale: 'en-GB',
  card: {
    cardholderName: {
      required: true,
      visible: true,
    },
  },
};

function CheckoutPage({ clientToken }: { clientToken: string }) {
  // React 19 example
  return <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />;
}
When to use: Options are static and don't depend on props, state, or user input
Benefits:
  • ✅ Zero re-render overhead
  • ✅ Simplest pattern
  • ✅ No React hooks needed
typescript
// ✅ 在模块加载时创建一次,引用永久不变
const SDK_OPTIONS = {
  locale: 'en-GB',
  card: {
    cardholderName: {
      required: true,
      visible: true,
    },
  },
};

function CheckoutPage({ clientToken }: { clientToken: string }) {
  // React 19示例
  return <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />;
}
适用场景: 选项是静态的,不依赖props、state或用户输入
优势:
  • ✅ 无重新渲染开销
  • ✅ 最简单的模式
  • ✅ 无需React钩子

Pattern 2: useMemo for Dynamic Options

模式2:useMemo用于动态选项

typescript
import { useMemo } from 'react';

function CheckoutPage({ clientToken, userLocale, merchantName }: Props) {
  // ✅ Creates new object ONLY when dependencies change
  const sdkOptions = useMemo(
    () => ({
      locale: userLocale,
      applePay: {
        merchantName: merchantName,
        merchantCountryCode: 'GB',
      },
    }),
    [userLocale, merchantName] // Only recreate when these change
  );

  // React 19 example
  return <primer-checkout client-token={clientToken} options={sdkOptions} />;
}
When to use: Options depend on props, state, or context that can change
Benefits:
  • ✅ Stable reference until dependencies change
  • ✅ Only re-initializes when necessary
  • ✅ Prevents unnecessary re-renders
typescript
import { useMemo } from 'react';

function CheckoutPage({ clientToken, userLocale, merchantName }: Props) {
  // ✅ 仅当依赖项变化时创建新对象
  const sdkOptions = useMemo(
    () => ({
      locale: userLocale,
      applePay: {
        merchantName: merchantName,
        merchantCountryCode: 'GB',
      },
    }),
    [userLocale, merchantName] // 仅在这些依赖项变化时重新创建
  );

  // React 19示例
  return <primer-checkout client-token={clientToken} options={sdkOptions} />;
}
适用场景: 选项依赖可能变化的props、state或上下文
优势:
  • ✅ 引用保持稳定直到依赖项变化
  • ✅ 仅在必要时重新初始化
  • ✅ 防止不必要的重新渲染

Pattern 3: Common Mistakes to Avoid

模式3:需避免的常见错误

typescript
// ❌ WRONG: Inline object in JSX
function CheckoutPage() {
  // New object on every render
  return <primer-checkout options={{ locale: 'en-GB' }} />;
}

// ❌ WRONG: Object in component body
function CheckoutPage() {
  // New object on every render
  const options = { locale: 'en-GB' };
  return <primer-checkout options={options} />;
}

// ✅ CORRECT: Use constant or useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage() {
  // Same object reference every render
  return <primer-checkout options={SDK_OPTIONS} />;
}

// ✅ CORRECT: Use useMemo for empty deps
function CheckoutPage() {
  const options = useMemo(() => ({ locale: 'en-GB' }), []);
  return <primer-checkout options={options} />;
}
typescript
// ❌ 错误:在JSX中内联对象
function CheckoutPage() {
  // 每次渲染创建新对象
  return <primer-checkout options={{ locale: 'en-GB' }} />;
}

// ❌ 错误:在组件内部定义对象
function CheckoutPage() {
  // 每次渲染创建新对象
  const options = { locale: 'en-GB' };
  return <primer-checkout options={options} />;
}

// ✅ 正确:使用常量或useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage() {
  // 每次渲染使用相同的对象引用
  return <primer-checkout options={SDK_OPTIONS} />;
}

// ✅ 正确:使用空依赖项的useMemo
function CheckoutPage() {
  const options = useMemo(() => ({ locale: 'en-GB' }), []);
  return <primer-checkout options={options} />;
}

TypeScript Setup

TypeScript配置

Show both patterns:
Pattern 1: CheckoutElement
typescript
import type { CheckoutElement } from '@primer-io/primer-js';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'primer-checkout': CheckoutElement;
    }
  }
}
Pattern 2: SDK Options Type
typescript
import type { PrimerCheckoutOptions } from '@primer-io/primer-js';

const options: PrimerCheckoutOptions = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
};
展示两种模式:
模式1:CheckoutElement
typescript
import type { CheckoutElement } from '@primer-io/primer-js';

declare global {
  namespace JSX {
    interface IntrinsicElements {
      'primer-checkout': CheckoutElement;
    }
  }
}
模式2:SDK选项类型
typescript
import type { PrimerCheckoutOptions } from '@primer-io/primer-js';

const options: PrimerCheckoutOptions = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
};

React 18 Pattern (For Legacy Apps)

React 18模式(适用于遗留应用)

For React 18, you must use refs and useEffect:
typescript
import { useRef, useEffect } from 'react';

// ✅ Define options outside component or use useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage({ clientToken }: { clientToken: string }) {
  const checkoutRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const checkout = checkoutRef.current;
    if (!checkout) return;

    // Imperative property assignment
    checkout.options = SDK_OPTIONS;

    // Set up event listeners
    const handleReady = () => console.log('✅ SDK ready');
    checkout.addEventListener('primer:ready', handleReady);

    return () => {
      checkout.removeEventListener('primer:ready', handleReady);
    };
  }, []); // Empty deps - runs once

  return <primer-checkout ref={checkoutRef} client-token={clientToken} />;
}
对于React 18,你必须使用refs和useEffect:
typescript
import { useRef, useEffect } from 'react';

// ✅ 在组件外部定义选项或使用useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage({ clientToken }: { clientToken: string }) {
  const checkoutRef = useRef<HTMLElement>(null);

  useEffect(() => {
    const checkout = checkoutRef.current;
    if (!checkout) return;

    // 命令式属性赋值
    checkout.options = SDK_OPTIONS;

    // 设置事件监听器
    const handleReady = () => console.log('✅ SDK已就绪');
    checkout.addEventListener('primer:ready', handleReady);

    return () => {
      checkout.removeEventListener('primer:ready', handleReady);
    };
  }, []); // 空依赖项 - 仅运行一次

  return <primer-checkout ref={checkoutRef} client-token={clientToken} />;
}

React 19 Pattern (Recommended)

React 19模式(推荐)

React 19 allows direct JSX property assignment:
typescript
// ✅ Define options outside component or use useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage({ clientToken }: { clientToken: string }) {
  return <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />;
}
Critical: Keep the constant! React 19 doesn't eliminate the need for stable references.
React 19允许直接通过JSX属性赋值:
typescript
// ✅ 在组件外部定义选项或使用useMemo
const SDK_OPTIONS = { locale: 'en-GB' };

function CheckoutPage({ clientToken }: { clientToken: string }) {
  return <primer-checkout client-token={clientToken} options={SDK_OPTIONS} />;
}
关键: 务必使用常量!React 19并未消除对稳定引用的需求。

Server-Side Rendering (SSR)

服务端渲染(SSR)

Primer Checkout requires browser APIs (Web Components, DOM) and must load client-side only.
Primer Checkout依赖浏览器API(Web Components、DOM),必须仅在客户端加载。

Why SSR Requires Special Handling

为何SSR需要特殊处理

The SDK depends on:
  • Web Components API (
    customElements.define()
    )
  • DOM APIs for component rendering
  • Browser context for iframes and payment processing
  • window
    object
These don't exist in Node.js (server) environment.
SDK依赖:
  • Web Components API(
    customElements.define()
  • 用于组件渲染的DOM API
  • 用于iframe和支付处理的浏览器上下文
  • window
    对象
这些在Node.js(服务端)环境中不存在。

Next.js

Next.js

App Router (Next.js 13+)

App Router(Next.js 13+)

typescript
'use client';

import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

export default function CheckoutPage() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      loadPrimer().catch(console.error);
    }
  }, []);

  return <primer-checkout client-token="your-token" />;
}
The
'use client'
directive marks this component as client-side only.
typescript
'use client';

import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

export default function CheckoutPage() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      loadPrimer().catch(console.error);
    }
  }, []);

  return <primer-checkout client-token="your-token" />;
}
'use client'
指令将此组件标记为仅客户端组件。

Pages Router (Legacy)

Pages Router(遗留)

typescript
import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

function CheckoutPage() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const initializePrimer = async () => {
        try {
          await loadPrimer();
          console.log('✅ Primer loaded');
        } catch (error) {
          console.error('❌ Failed to load Primer:', error);
        }
      };

      initializePrimer();
    }
  }, []);

  return <primer-checkout client-token="your-token" />;
}

export default CheckoutPage;
typescript
import { useEffect } from 'react';
import { loadPrimer } from '@primer-io/primer-js';

function CheckoutPage() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const initializePrimer = async () => {
        try {
          await loadPrimer();
          console.log('✅ Primer已加载');
        } catch (error) {
          console.error('❌ 加载Primer失败:', error);
        }
      };

      initializePrimer();
    }
  }, []);

  return <primer-checkout client-token="your-token" />;
}

export default CheckoutPage;

Nuxt.js 3

Nuxt.js 3

vue
<template>
  <primer-checkout client-token="your-token" />
</template>

<script setup>
import { onMounted } from 'vue';

onMounted(async () => {
  if (import.meta.client) {
    try {
      const { loadPrimer } = await import('@primer-io/primer-js');
      loadPrimer();
      console.log('✅ Primer loaded');
    } catch (error) {
      console.error('❌ Failed to load Primer:', error);
    }
  }
});
</script>
Note: Use
import.meta.client
(modern Nuxt 3) instead of
process.client
(legacy Nuxt 2).
vue
<template>
  <primer-checkout client-token="your-token" />
</template>

<script setup>
import { onMounted } from 'vue';

onMounted(async () => {
  if (import.meta.client) {
    try {
      const { loadPrimer } = await import('@primer-io/primer-js');
      loadPrimer();
      console.log('✅ Primer已加载');
    } catch (error) {
      console.error('❌ 加载Primer失败:', error);
    }
  }
});
</script>
注意: 使用
import.meta.client
(现代Nuxt 3)而非
process.client
(遗留Nuxt 2)。

SvelteKit

SvelteKit

svelte
<script>
  import { onMount } from 'svelte';
  import { browser } from '$app/environment';

  onMount(async () => {
    if (browser) {
      try {
        const { loadPrimer } = await import('@primer-io/primer-js');
        loadPrimer();
        console.log('✅ Primer loaded');
      } catch (error) {
        console.error('❌ Failed to load Primer:', error);
      }
    }
  });
</script>

<primer-checkout client-token="your-token" />
svelte
<script>
  import { onMount } from 'svelte';
  import { browser } from '$app/environment';

  onMount(async () => {
    if (browser) {
      try {
        const { loadPrimer } = await import('@primer-io/primer-js');
        loadPrimer();
        console.log('✅ Primer已加载');
      } catch (error) {
        console.error('❌ 加载Primer失败:', error);
      }
    }
  });
</script>

<primer-checkout client-token="your-token" />

Best Practices

最佳实践

  1. Always use framework lifecycle methods (useEffect, onMounted, onMount)
  2. Include environment checks (
    typeof window
    ,
    import.meta.client
    ,
    browser
    )
  3. Use dynamic imports to prevent server bundling
  4. Wrap in try-catch for error handling
  5. Use stable references for options objects (apply to all frameworks)
  1. 始终使用框架生命周期方法(useEffect、onMounted、onMount)
  2. 包含环境检查
    typeof window
    import.meta.client
    browser
  3. 使用动态导入以避免服务端打包
  4. 用try-catch包裹以处理错误
  5. 对选项对象使用稳定引用(适用于所有框架)

Error Handling

错误处理

Payment Failure vs Validation Errors

支付失败 vs 验证错误

Validation Errors:
  • Handled by input components themselves
  • Prevent form submission until fixed
  • Displayed inline by card inputs
  • No action needed from you
Payment Failures:
  • Occur after form submission
  • Displayed via
    <primer-error-message-container>
    or custom handling
  • Require user action (retry, change payment method)
验证错误:
  • 由输入组件自身处理
  • 在修复前阻止表单提交
  • 由卡片输入框内联显示
  • 无需你进行额外操作
支付失败:
  • 在表单提交后发生
  • 通过
    <primer-error-message-container>
    或自定义处理显示
  • 需要用户操作(重试、更换支付方式)

Using Error Message Container

使用错误信息容器

html
<primer-checkout client-token="your-token">
  <primer-main slot="main">
    <div slot="payments">
      <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>

      <!-- Shows payment failures automatically -->
      <primer-error-message-container></primer-error-message-container>
    </div>
  </primer-main>
</primer-checkout>
Placement Guidelines:
  1. Prominently visible after payment attempt
  2. Where users naturally look for feedback
  3. Within same visual context as payment method
html
<primer-checkout client-token="your-token">
  <primer-main slot="main">
    <div slot="payments">
      <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>

      <!-- 自动显示支付失败信息 -->
      <primer-error-message-container></primer-error-message-container>
    </div>
  </primer-main>
</primer-checkout>
放置指南:
  1. 在支付尝试后显眼可见
  2. 用户自然会查看反馈的位置
  3. 与支付方式处于相同视觉上下文

Custom Error Handling

自定义错误处理

Using Callbacks:
javascript
checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;

  primer.onPaymentFailure = ({ error, paymentMethodType }) => {
    // Display custom error UI
    showErrorNotification({
      title: 'Payment Failed',
      message: error.message,
      allowRetry: true,
    });

    // Log for debugging
    console.error('Payment failed:', {
      code: error.code,
      message: error.message,
      diagnosticsId: error.diagnosticsId, // For support
      method: paymentMethodType,
    });

    // Send to error tracking
    errorTracker.capture({
      errorCode: error.code,
      diagnosticsId: error.diagnosticsId,
    });
  };
});
Using State Change Event:
javascript
checkout.addEventListener('primer:state-change', (event) => {
  const { primerJsError, paymentFailure } = event.detail;

  if (primerJsError || paymentFailure) {
    const message = primerJsError?.message || paymentFailure?.message;
    showErrorMessage(message);

    // Log diagnostics ID for support
    if (paymentFailure?.diagnosticsId) {
      console.error('Diagnostics ID:', paymentFailure.diagnosticsId);
    }
  }
});
Using Payment Failure Event:
javascript
document.addEventListener('primer:payment-failure', (event) => {
  const { error, paymentMethodType } = event.detail;

  // Show user-friendly error
  showErrorMessage(error.message);

  // Track in analytics
  analytics.track('Payment Failed', {
    errorCode: error.code,
    method: paymentMethodType,
  });

  // Log for debugging
  if (error.diagnosticsId) {
    console.error('Diagnostics ID for support:', error.diagnosticsId);
  }
});
使用回调:
javascript
checkout.addEventListener('primer:ready', (event) => {
  const primer = event.detail;

  primer.onPaymentFailure = ({ error, paymentMethodType }) => {
    // 显示自定义错误UI
    showErrorNotification({
      title: '支付失败',
      message: error.message,
      allowRetry: true,
    });

    // 记录用于调试
    console.error('支付失败:', {
      code: error.code,
      message: error.message,
      diagnosticsId: error.diagnosticsId, // 用于获取支持
      method: paymentMethodType,
    });

    // 发送到错误跟踪服务
    errorTracker.capture({
      errorCode: error.code,
      diagnosticsId: error.diagnosticsId,
    });
  };
});
使用状态变化事件:
javascript
checkout.addEventListener('primer:state-change', (event) => {
  const { primerJsError, paymentFailure } = event.detail;

  if (primerJsError || paymentFailure) {
    const message = primerJsError?.message || paymentFailure?.message;
    showErrorMessage(message);

    // 记录诊断ID以获取支持
    if (paymentFailure?.diagnosticsId) {
      console.error('诊断ID:', paymentFailure.diagnosticsId);
    }
  }
});
使用支付失败事件:
javascript
document.addEventListener('primer:payment-failure', (event) => {
  const { error, paymentMethodType } = event.detail;

  // 显示用户友好的错误信息
  showErrorMessage(error.message);

  // 在分析工具中跟踪
  analytics.track('Payment Failed', {
    errorCode: error.code,
    method: paymentMethodType,
  });

  // 记录用于调试
  if (error.diagnosticsId) {
    console.error('支持用诊断ID:', error.diagnosticsId);
  }
});

Component Properties vs SDK Options

组件属性 vs SDK选项

Why the Distinction Exists

为何存在此区别

Component properties use Lit's attribute system which monitors DOM attribute changes. Direct property assignment bypasses this system, causing values to be ignored. The
options
property is the ONLY exception - it's designed to accept direct property assignment.
组件属性使用Lit的属性系统,该系统会监控DOM属性变化。直接属性赋值会绕过此系统,导致值被忽略。
options
属性是唯一的例外 - 它被设计为接受直接属性赋值。

Component Properties (use
setAttribute()
)

组件属性(使用
setAttribute()

These are HTML attributes set via
setAttribute()
:
  • client-token
    - JWT from backend (REQUIRED)
  • custom-styles
    - JSON string of CSS variables
  • loader-disabled
    - Boolean to disable loader
javascript
checkout.setAttribute('client-token', 'your-token');
checkout.setAttribute('loader-disabled', 'true');
checkout.setAttribute(
  'custom-styles',
  JSON.stringify({ primerColorBrand: '#4a6cf7' }),
);
这些是通过
setAttribute()
设置的HTML属性:
  • client-token
    - 来自后端的JWT(必填)
  • custom-styles
    - CSS变量的JSON字符串
  • loader-disabled
    - 布尔值,用于禁用加载器
javascript
checkout.setAttribute('client-token', 'your-token');
checkout.setAttribute('loader-disabled', 'true');
checkout.setAttribute(
  'custom-styles',
  JSON.stringify({ primerColorBrand: '#4a6cf7' }),
);

SDK Options (use property assignment)

SDK选项(使用属性赋值)

Everything else goes in the
options
object:
  • Locale, payment methods, vault configuration, etc.
javascript
checkout.options = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
  vault: { enabled: true },
};
其他所有配置都放在
options
对象中:
  • 语言、支付方式、Vault配置等
javascript
checkout.options = {
  locale: 'en-GB',
  enabledPaymentMethods: [PaymentMethodType.PAYMENT_CARD],
  vault: { enabled: true },
};

Debugging Tip

调试技巧

javascript
// Check if using correctly
checkout.getAttribute('client-token'); // Should return token
checkout.options; // Should return options object

// Common mistake
checkout.getAttribute('locale'); // Returns null (locale is in options!)
Remember: Never mix these up. Component properties use
setAttribute()
, SDK options use direct property assignment.
javascript
// 检查是否正确使用
checkout.getAttribute('client-token'); // 应返回token
checkout.options; // 应返回选项对象

// 常见错误
checkout.getAttribute('locale'); // 返回null(locale在options中!)
记住: 切勿混淆两者。组件属性使用
setAttribute()
,SDK选项使用直接属性赋值。

Preventing Flash of Undefined Components (FOUC)

防止未定义组件闪烁(FOUC)

Web components register via JavaScript. Before registration, custom elements may flash as undefined.
Web组件通过JavaScript注册。在注册前,自定义元素可能会闪烁为未定义状态。

CSS Solution (Simple)

CSS解决方案(简单)

css
primer-checkout:has(:not(:defined)) {
  visibility: hidden;
}
Use
visibility: hidden
(not
display: none
) to preserve layout space.
css
primer-checkout:has(:not(:defined)) {
  visibility: hidden;
}
使用
visibility: hidden
(而非
display: none
)以保留布局空间。

JavaScript Solution (More Control)

JavaScript解决方案(更灵活)

javascript
Promise.allSettled([
  customElements.whenDefined('primer-checkout'),
  customElements.whenDefined('primer-payment-method'),
]).then(() => {
  document.querySelector('.checkout-container').classList.add('ready');
});
css
.checkout-container {
  visibility: hidden;
}

.checkout-container.ready {
  visibility: visible;
}
javascript
Promise.allSettled([
  customElements.whenDefined('primer-checkout'),
  customElements.whenDefined('primer-payment-method'),
]).then(() => {
  document.querySelector('.checkout-container').classList.add('ready');
});
css
.checkout-container {
  visibility: hidden;
}

.checkout-container.ready {
  visibility: visible;
}

CSS Theming

CSS主题定制

Custom Properties

自定义属性

Apply via CSS:
css
:root {
  --primer-color-brand: #2f98ff;
  --primer-radius-base: 8px;
  --primer-typography-brand: 'Inter, sans-serif';
  --primer-space-base: 4px;
}

/* Or scope to specific checkout */
primer-checkout {
  --primer-color-brand: #4a6cf7;
}
Or via
custom-styles
attribute:
html
<primer-checkout
  custom-styles='{"primerColorBrand":"#2f98ff","primerRadiusBase":"8px"}'
></primer-checkout>
通过CSS应用:
css
:root {
  --primer-color-brand: #2f98ff;
  --primer-radius-base: 8px;
  --primer-typography-brand: 'Inter, sans-serif';
  --primer-space-base: 4px;
}

/* 或作用于特定结账组件 */
primer-checkout {
  --primer-color-brand: #4a6cf7;
}
或通过
custom-styles
属性:
html
<primer-checkout
  custom-styles='{"primerColorBrand":"#2f98ff","primerRadiusBase":"8px"}'
></primer-checkout>

Dark Theme

深色主题

css
primer-checkout.primer-dark-theme {
  --primer-color-text-primary: var(--primer-color-gray-100);
  --primer-color-background-outlined-default: var(--primer-color-gray-800);
}
javascript
// Apply theme
const checkout = document.querySelector('primer-checkout');
checkout.classList.add('primer-dark-theme');
css
primer-checkout.primer-dark-theme {
  --primer-color-text-primary: var(--primer-color-gray-100);
  --primer-color-background-outlined-default: var(--primer-color-gray-800);
}
javascript
// 应用主题
const checkout = document.querySelector('primer-checkout');
checkout.classList.add('primer-dark-theme');

Common Use Cases

常见用例

1. Default Checkout (Simplest)

1. 默认结账(最简单)

html
<primer-checkout client-token="your-token"></primer-checkout>
This provides a complete checkout experience with all available payment methods.
html
<primer-checkout client-token="your-token"></primer-checkout>
这提供了包含所有可用支付方式的完整结账体验。

2. Custom Payment Method Layout

2. 自定义支付方式布局

html
<primer-checkout client-token="your-token">
  <primer-main slot="main">
    <div slot="payments">
      <h2>Choose Payment Method</h2>

      <!-- Individual methods -->
      <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
      <primer-payment-method type="PAYPAL"></primer-payment-method>

      <!-- Error display -->
      <primer-error-message-container></primer-error-message-container>
    </div>

    <div slot="checkout-complete">
      <h2>Thank you for your order!</h2>
    </div>
  </primer-main>
</primer-checkout>
html
<primer-checkout client-token="your-token">
  <primer-main slot="main">
    <div slot="payments">
      <h2>选择支付方式</h2>

      <!-- 单个支付方式 -->
      <primer-payment-method type="PAYMENT_CARD"></primer-payment-method>
      <primer-payment-method type="PAYPAL"></primer-payment-method>

      <!-- 错误显示 -->
      <primer-error-message-container></primer-error-message-container>
    </div>

    <div slot="checkout-complete">
      <h2>感谢你的订单!</h2>
    </div>
  </primer-main>
</primer-checkout>

3. Declarative Payment Filtering

3. 声明式支付方式过滤

html
<div slot="payments">
  <!-- Show only digital wallets -->
  <primer-payment-method-container include="APPLE_PAY,GOOGLE_PAY">
  </primer-payment-method-container>

  <!-- Show everything except cards -->
  <primer-payment-method-container exclude="PAYMENT_CARD">
  </primer-payment-method-container>
</div>
html
<div slot="payments">
  <!-- 仅显示数字钱包 -->
  <primer-payment-method-container include="APPLE_PAY,GOOGLE_PAY">
  </primer-payment-method-container>

  <!-- 显示除卡片外的所有支付方式 -->
  <primer-payment-method-container exclude="PAYMENT_CARD">
  </primer-payment-method-container>
</div>

4. Custom Card Form

4. 自定义卡片表单

html
<primer-card-form>
  <div slot="card-form-content">
    <primer-input-card-number></primer-input-card-number>

    <div style="display: flex; gap: 8px;">
      <primer-input-card-expiry></primer-input-card-expiry>
      <primer-input-cvv></primer-input-cvv>
    </div>

    <primer-input-card-holder-name></primer-input-card-holder-name>

    <!-- Custom field using base components -->
    <primer-input-wrapper>
      <primer-input-label slot="label">Billing Zip</primer-input-label>
      <primer-input slot="input" type="text" name="zip"></primer-input>
    </primer-input-wrapper>

    <primer-card-form-submit></primer-card-form-submit>
  </div>
</primer-card-form>
html
<primer-card-form>
  <div slot="card-form-content">
    <primer-input-card-number></primer-input-card-number>

    <div style="display: flex; gap: 8px;">
      <primer-input-card-expiry></primer-input-card-expiry>
      <primer-input-cvv></primer-input-cvv>
    </div>

    <primer-input-card-holder-name></primer-input-card-holder-name>

    <!-- 使用基础组件的自定义字段 -->
    <primer-input-wrapper>
      <primer-input-label slot="label">账单邮编</primer-input-label>
      <primer-input slot="input" type="text" name="zip"></primer-input>
    </primer-input-wrapper>

    <primer-card-form-submit></primer-card-form-submit>
  </div>
</primer-card-form>

Best Practices

最佳实践

  1. Always use stable object references in React (module-level constants or
    useMemo
    )
  2. Set component properties via
    setAttribute()
    , SDK options via property assignment
  3. Clean up event listeners in React
    useEffect
    cleanup functions
  4. Use declarative containers (
    primer-payment-method-container
    ) instead of manual filtering
  5. Include error handling with
    primer-error-message-container
    or custom callbacks
  6. Load Primer in
    useEffect
    (or equivalent) for SSR frameworks
  7. Use TypeScript declarations for proper JSX support
  8. Keep SDK options simple - only configure what you need
  9. Use v0.7.0+ callbacks (
    onPaymentSuccess
    ,
    onPaymentFailure
    ) for clearer error handling
  10. Track diagnosticsId in payment failures for support inquiries
  1. 在React中始终使用稳定对象引用(模块级常量或
    useMemo
  2. 通过
    setAttribute()
    设置组件属性
    ,通过属性赋值设置SDK选项
  3. 在React的
    useEffect
    清理函数中移除事件监听器
  4. 使用声明式容器
    primer-payment-method-container
    )而非手动过滤
  5. 包含错误处理,使用
    primer-error-message-container
    或自定义回调
  6. 在SSR框架中,在
    useEffect
    (或等效方法)中加载Primer
  7. 使用TypeScript声明以获得正确的JSX支持
  8. 简化SDK选项 - 仅配置你需要的内容
  9. 使用v0.7.0+版本的回调
    onPaymentSuccess
    onPaymentFailure
    )以获得更清晰的错误处理
  10. 在支付失败时记录diagnosticsId以获取支持

Common Troubleshooting

常见故障排除

Component re-initializing on every render?

组件每次渲染都重新初始化?

→ Check object reference stability. Use module-level constants or
useMemo
. → In React 19, ensure options object has stable reference. → Applies to BOTH React 18 AND React 19.
→ 检查对象引用稳定性。使用模块级常量或
useMemo
。 → 在React 19中,确保选项对象具有稳定引用。 → 适用于React 18和React 19。

TypeScript errors with JSX?

JSX出现TypeScript错误?

→ Add TypeScript declarations:
import type { CheckoutElement } from '@primer-io/primer-js'
→ Declare in global JSX namespace or use
CustomElements
type
→ 添加TypeScript声明:
import type { CheckoutElement } from '@primer-io/primer-js'
→ 在全局JSX命名空间中声明或使用
CustomElements
类型

SSR errors ("customElements is not defined", "window is not defined")?

SSR错误("customElements is not defined"、"window is not defined")?

→ Load Primer in client-side lifecycle:
useEffect
,
onMounted
,
onMount
→ Use
'use client'
directive in Next.js App Router → Add environment checks:
typeof window !== 'undefined'
→ Use dynamic imports:
await import('@primer-io/primer-js')
→ 在客户端生命周期中加载Primer:
useEffect
onMounted
onMount
→ 在Next.js App Router中使用
'use client'
指令 → 添加环境检查:
typeof window !== 'undefined'
→ 使用动态导入:
await import('@primer-io/primer-js')

Event not firing?

事件未触发?

→ Ensure component is mounted before adding listener → Use
useEffect
in React, wait for
primer:ready
→ Check event name (v0.7.0 renamed some events)
→ 确保在添加监听器前组件已挂载 → 在React中使用
useEffect
,等待
primer:ready
事件 → 检查事件名称(v0.7.0重命名了部分事件)

Payment methods not showing?

支付方式未显示?

→ Check client token is valid → Check
enabledPaymentMethods
configuration → Wait for
primer:ready
event before accessing SDK → Verify methods are configured in Primer Dashboard → Check SDK Core vs Legacy mode compatibility
→ 检查client token是否有效 → 检查
enabledPaymentMethods
配置 → 等待
primer:ready
事件后再访问SDK → 验证支付方式已在Primer控制台配置 → 检查SDK Core与Legacy模式的兼容性

Options not applying?

选项未生效?

→ Check you're using
checkout.options = {...}
, not
setAttribute
→ Verify object has stable reference in React → Check SDK Core vs Legacy mode compatibility → Never set
client-token
in options (it's a component property)
→ 检查是否使用
checkout.options = {...}
而非
setAttribute
→ 在React中验证对象具有稳定引用 → 检查SDK Core与Legacy模式的兼容性 → 切勿在options中设置
client-token
(它是组件属性)

Styling not applying?

样式未生效?

→ CSS custom properties pierce Shadow DOM → Use
--primer-*
variables → Check specificity and scoping → Apply to
primer-checkout
element or
:root
→ CSS自定义属性可穿透Shadow DOM → 使用
--primer-*
变量 → 检查特异性与作用域 → 作用于
primer-checkout
元素或
:root

Infinite re-renders in React?

React中出现无限重渲染?

→ Inline object in JSX:
options={{ locale: 'en-GB' }}
- use constant or useMemo → Object in component body without useMemo → Dependencies missing in useMemo array → This happens in BOTH React 18 AND React 19
→ 在JSX中内联对象:
options={{ locale: 'en-GB' }}
- 使用常量或useMemo → 在组件内部定义对象但未使用useMemo → useMemo数组中缺少依赖项 → 这在React 18和React 19中都会发生

"Cannot set property options of HTMLElement"?

"Cannot set property options of HTMLElement"?

→ Component not yet registered, wait for
primer:ready
→ Or ensure
loadPrimer()
was called → Use
customElements.whenDefined('primer-checkout')
to wait
→ 组件尚未注册,等待
primer:ready
事件 → 或确保已调用
loadPrimer()
→ 使用
customElements.whenDefined('primer-checkout')
等待注册完成

Payment failures not displaying?

支付失败未显示?

→ Include
<primer-error-message-container>
in your layout → Or implement custom error handling with
onPaymentFailure
callback → Or listen to
primer:payment-failure
event → Check
primerJsError
and
paymentFailure
in state change events
→ 在布局中包含
<primer-error-message-container>
→ 或使用
onPaymentFailure
回调实现自定义错误处理 → 或监听
primer:payment-failure
事件 → 在状态变化事件中检查
primerJsError
paymentFailure

Vaulted methods not appearing?

已存储的支付方式未显示?

→ Check
vault.enabled: true
in options → Verify client session has
vaultOnSuccess: true
→ Listen to
primer:vault:methods-update
event → Use
onVaultedMethodsUpdate
callback for updates
→ 检查options中
vault.enabled: true
→ 验证客户端会话已设置
vaultOnSuccess: true
→ 监听
primer:vault:methods-update
事件 → 使用
onVaultedMethodsUpdate
回调处理更新

PayPal button not showing?

PayPal按钮未显示?

→ Check
sdkCore: true
(required for PayPal) → Include
PaymentMethodType.PAYPAL
in
enabledPaymentMethods
→ Verify PayPal is configured in Primer Dashboard → Check browser console for PayPal SDK errors
→ 检查
sdkCore: true
(PayPal必需) → 在
enabledPaymentMethods
中包含
PaymentMethodType.PAYPAL
→ 验证PayPal已在Primer控制台配置 → 检查浏览器控制台中的PayPal SDK错误

Resources

资源

For always up-to-date documentation, this skill references the Primer Checkout documentation covering:
  • Component APIs and properties
  • SDK options and configuration
  • Event payloads and callbacks
  • Payment lifecycle handling
  • Vault integration patterns
  • React integration patterns (React 18 & 19)
  • SSR framework patterns (Next.js, Nuxt, SvelteKit)
  • CSS theming and customization
  • TypeScript type definitions
For the latest component APIs, patterns, and examples, use Context7 MCP server:
typescript
// Resolve library
const library = await resolveLibraryId('primer checkout components');
// Returns: /primer-io/examples

// Fetch documentation
const docs = await getLibraryDocs('/primer-io/examples', {
  topic: 'payment lifecycle events',
  tokens: 10000,
});
This ensures access to the most current component APIs, v0.7.0+ features, and integration patterns.
本内容参考Primer Checkout文档以获取始终最新的信息,涵盖:
  • 组件API与属性
  • SDK选项与配置
  • 事件负载与回调
  • 支付生命周期处理
  • Vault集成模式
  • React集成模式(React 18 & 19)
  • SSR框架模式(Next.js、Nuxt、SvelteKit)
  • CSS主题定制
  • TypeScript类型定义
如需最新的组件API、模式与示例,请使用Context7 MCP服务器:
typescript
// 解析库
const library = await resolveLibraryId('primer checkout components');
// 返回:/primer-io/examples

// 获取文档
const docs = await getLibraryDocs('/primer-io/examples', {
  topic: 'payment lifecycle events',
  tokens: 10000,
});
这可确保你能访问最新的组件API、v0.7.0+功能与集成模式。