primer-web-components
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePrimer Web Components
Primer Web 组件
Overview
概述
This skill provides comprehensive guidance for building checkout and payment experiences using Primer's web component library (). Primer components are framework-agnostic custom elements that work with React, Next.js, Vue, Svelte, or vanilla JavaScript.
@primer-io/primer-jsUse 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组件是与框架无关的自定义元素,可在React、Next.js、Vue、Svelte或原生JavaScript中使用。
@primer-io/primer-js在以下场景中可使用本内容:
- 实现结账页面或支付流程
- 集成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: replaced with
onPaymentCompleteandonPaymentSuccessonPaymentFailure - State Fields: →
error,primerJsError→failurepaymentFailure - Event Names: → use
primer:payment-methods-updatedprimer:methods-update
New in v0.7.0:
- Payment lifecycle events: ,
primer:payment-start,primer:payment-successprimer: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和onPaymentSuccessonPaymentFailure - 状态字段:→
error,primerJsError→failurepaymentFailure - 事件名称:→ 改为使用
primer:payment-methods-updatedprimer:methods-update
v0.7.0 新增功能:
- 支付生命周期事件:、
primer:payment-start、primer:payment-successprimer: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-jsbash
npm install @primer-io/primer-jsBasic 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-inputprimer-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-inputSDK 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:
- - Full card payment forms
PAYMENT_CARD - - PayPal button integration
PAYPAL - - Polish payment method (OTP verification)
ADYEN_BLIK
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 - - 波兰支付方式(OTP验证)
ADYEN_BLIK
新支付方式会定期添加。请查看发布说明了解更新。
优势:
- 现代化支付处理引擎
- 增强的性能与可靠性
- 支持新支付方式
- 更优的错误处理与诊断
Legacy SDK
Legacy SDK
Enable with . Provides access to 50+ payment methods via Web Headless API.
sdkCore: falsejavascript
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:
- Primer Dashboard configuration
- Payment processor Web Headless support
- Regional availability
Not all payment methods support Web Headless. Check the Primer Payment Methods catalog for "Web Headless" column.
通过设置启用。可通过Web Headless API访问50+种支付方式。
sdkCore: falsejavascript
checkout.options = {
sdkCore: false, // 切换到Legacy SDK
};适用场景:
- 需要SDK Core中尚未支持的支付方式
- 现有集成使用Legacy模式
- 需要特定处理器专属的支付方式
注意: 支付方式的可用性取决于:
- Primer控制台配置
- 支付处理器的Web Headless支持
- 区域可用性
并非所有支付方式都支持Web Headless。请查看Primer支付方式目录中的"Web Headless"列。
SDK Options Reference
SDK选项参考
Core Options
核心选项
Configure SDK behavior through the property:
optionsjavascript
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:
| Option | Type | Default | Description |
|---|---|---|---|
| | | Enable SDK Core engine |
| | Browser's locale | Force UI locale (e.g., "en-GB") |
| | | Domain for Apple Pay validation |
| | | Disable all payment methods globally |
| | | Which payment methods to display |
通过属性配置SDK行为:
optionsjavascript
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,
],
};核心选项说明:
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| | | 启用SDK Core引擎 |
| | 浏览器语言 | 强制UI语言(例如:"en-GB") |
| | | Apple Pay验证用域名 |
| | | 全局禁用所有支付方式 |
| | | 要显示的支付方式 |
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:
| Option | Type | Default | Description |
|---|---|---|---|
| | | Require cardholder name |
| | | Show cardholder name field |
配置卡片支付表单行为:
javascript
checkout.options = {
card: {
cardholderName: {
required: true, // 是否必填持卡人姓名
visible: 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 (, which is the default).
sdkCore: truePayPal集成需要SDK Core(,默认已启用)。
sdkCore: trueBasic 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:
| Option | Type | Default | Description |
|---|---|---|---|
| | | Button layout orientation |
| | | Button color theme |
| | | Button border shape |
| | | Button height in pixels |
| | | Button label text |
| | | Show tagline (horizontal layout only) |
| | | Button corner radius in pixels |
| | | 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按钮外观:
| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| | | 按钮布局方向 |
| | | 按钮颜色主题 |
| | | 按钮边框形状 |
| | | 按钮高度(像素) |
| | | 按钮标签文本 |
| | | 显示标语(仅横向布局支持) |
| | | 按钮圆角半径(像素) |
| | | 禁用最大宽度约束 |
样式示例:
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:
- - Guest card payments (credit/debit without PayPal account)
card - - PayPal Credit (US, UK)
credit - - PayPal Pay Later
paylater - - Venmo (US)
venmo
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: takes precedence over . If a source appears in both arrays, it will be disabled.
disableFundingenableFunding控制可用的PayPal资金来源:
javascript
paypal: {
disableFunding: ['credit', 'paylater', 'card'], // 隐藏这些选项
enableFunding: ['venmo'], // 显式启用Venmo
}可用资金来源:
- - 访客卡片支付(无需PayPal账户的信用卡/借记卡)
card - - PayPal Credit(美国、英国)
credit - - PayPal先买后付
paylater - - Venmo(美国)
venmo
资金控制示例:
javascript
// 仅支持PayPal余额与银行账户
paypal: {
disableFunding: ['card', 'credit', 'paylater', 'venmo'],
}
// 仅支持PayPal与Venmo
paypal: {
disableFunding: ['card', 'credit', 'paylater'],
enableFunding: ['venmo'],
}注意: 优先级高于。若某资金来源同时出现在两个数组中,它将被禁用。
disableFundingenableFundingPayPal 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:
- SDK Configuration: Set in PayPal options
vault: true - Client Session: Configure in your client session creation request
vaultOnSuccess: true
Legacy SDK: For , use:
sdkCore: falsejavascript
paypal: {
paymentFlow: 'PREFER_VAULT',
}启用存储功能以允许客户保存其PayPal账户:
javascript
paypal: {
vault: true, // 在SDK中启用存储
}要求:
存储功能需要同时配置SDK与服务端:
- SDK配置:在PayPal选项中设置
vault: true - 客户端会话:在创建客户端会话的请求中配置
vaultOnSuccess: true
Legacy SDK:对于,使用:
sdkCore: falsejavascript
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:readyprimer:ready
primer:readyDispatched 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-changeprimer:state-change
primer:state-changeDispatched whenever the checkout state changes (processing, success, error, etc.).
Event Detail: Contains , , , , .
isProcessingisSuccessfulisLoadingprimerJsErrorpaymentFailureUsage:
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(SDK-level errors)primerJsError - →
failure(payment-level failures)paymentFailure
每当结账状态变化(处理中、成功、错误等)时触发。
事件详情: 包含、、、、。
isProcessingisSuccessfulisLoadingprimerJsErrorpaymentFailure用法:
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(SDK级错误)primerJsError - →
failure(支付级失败)paymentFailure
primer:methods-update
primer:methods-updateprimer:methods-update
primer:methods-updateDispatched when available payment methods are loaded and ready.
Event Detail: Contains instance with and methods.
InitializedPaymentstoArray()size()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 component provides a simpler declarative approach without requiring event listeners.
primer-payment-method-container当可用支付方式加载完成并就绪时触发。
事件详情: 包含带有和方法的实例。
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-containerPayment 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-startprimer:payment-start
primer:payment-startDispatched when payment processing begins, immediately after the user initiates a payment.
Event Detail: (use as trigger signal only)
undefinedUsage:
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-successprimer:payment-success
primer:payment-successDispatched 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):
- : Payment ID
id - : Merchant order ID
orderId - : Type of payment method used
paymentMethodType - : Object containing non-sensitive card data
paymentMethodData- : Last 4 digits of card number (if applicable)
last4Digits - : Card network (Visa, Mastercard, etc.)
network - : Payment method type
paymentMethodType
Filtered fields (not available):
- : Filtered for PII protection
cardholderName
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 object filters sensitive information like cardholder names. Only use the provided non-sensitive fields for display and analytics.
PaymentSummary当支付成功完成时触发。
事件详情:
typescript
{
paymentSummary: PaymentSummary; // 经过PII过滤的支付数据
paymentMethodType: string; // 例如:'PAYMENT_CARD'、'PAYPAL'
timestamp: number; // 支付成功的Unix时间戳
}PaymentSummary结构:
可用字段(经过PII过滤):
- : 支付ID
id - : 商家订单ID
orderId - : 使用的支付方式类型
paymentMethodType - : 包含非敏感卡片数据的对象
paymentMethodData- : 卡片最后4位(若适用)
last4Digits - : 卡片网络(Visa、Mastercard等)
network - : 支付方式类型
paymentMethodType
已过滤字段(不可用):
- : 为保护PII已过滤
cardholderName
用法:
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}`;
});注意: 对象会过滤持卡人姓名等敏感信息。仅使用提供的非敏感字段进行展示与分析。
PaymentSummaryprimer:payment-failure
primer:payment-failureprimer:payment-failure
primer:payment-failureDispatched 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-updateprimer:vault:methods-update
primer:vault:methods-updateDispatched 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:
- : Returns array of
toArray()objectsVaultedPaymentMethodSummary - : Gets a specific vaulted payment method by ID
get(id: string) - : Returns the number of saved payment methods
size()
VaultedPaymentMethodSummary Structure:
- : Unique identifier for the vaulted payment method
id - : Analytics tracking identifier
analyticsId - : Type of payment method (e.g., 'PAYMENT_CARD', 'ADYEN_STRIPE_ACH')
paymentMethodType - : Instrument type
paymentInstrumentType - : Object with PII-filtered payment instrument details
paymentInstrumentData- : Last 4 digits of card (cards only)
last4Digits - : Card network like VISA, MASTERCARD (cards only)
network - : Last 4 of account number (ACH only)
accountNumberLastFourDigits - : Bank name (ACH only)
bankName - : CHECKING or SAVINGS (ACH only)
accountType - : Email address (wallet methods like PayPal)
email
- : Optional user-provided description
userDescription
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 - : 通过ID获取特定的已存储支付方式
get(id: string) - : 返回已保存支付方式的数量
size()
VaultedPaymentMethodSummary结构:
- : 已存储支付方式的唯一标识符
id - : 分析跟踪标识符
analyticsId - : 支付方式类型(例如:'PAYMENT_CARD'、'ADYEN_STRIPE_ACH')
paymentMethodType - : 支付工具类型
paymentInstrumentType - : 包含经过PII过滤的支付工具详情的对象
paymentInstrumentData- : 卡片最后4位(仅卡片)
last4Digits - : 卡片网络如VISA、MASTERCARD(仅卡片)
network - : 账户最后4位(仅ACH)
accountNumberLastFourDigits - : 银行名称(仅ACH)
bankName - : CHECKING或SAVINGS(仅ACH)
accountType - : 邮箱地址(仅PayPal等钱包支付方式)
email
- : 可选的用户提供的描述
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-successprimer:card-success
primer:card-successDispatched when a card form is successfully validated and submitted.
Event Detail: Contains object with payment submission data.
resultUsage:
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-errorprimer:card-error
primer:card-errorDispatched when card validation fails or submission encounters an error.
Event Detail: Contains array with validation error objects.
errorsUsage:
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-changeprimer:card-network-change
primer:card-network-changeDispatched when the card network (Visa, Mastercard, etc.) is detected or changes based on the card number input.
Event Detail: Contains , , and .
detectedCardNetworkselectableCardNetworksisLoadingUsage:
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等)或网络变化时触发。
事件详情: 包含、和。
detectedCardNetworkselectableCardNetworksisLoading用法:
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-submitprimer:card-submit
primer:card-submitTrigger card form submission programmatically from anywhere in your application.
Event Detail: Optional property to identify the trigger source.
sourceUsage:
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 and
bubbles: trueproperties are requiredcomposed: true - Always include a meaningful parameter for debugging
source - 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 event to respond to vault changes:
primer:vault:methods-updatejavascript
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);
}
});使用事件响应Vault变化:
primer:vault:methods-updatejavascript
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 event:
primer:readyjavascript
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
primer.onVaultedMethodsUpdate = ({ vaultedPayments }) => {
console.log('Vault updated:', vaultedPayments.size(), 'methods');
updateVaultUI(vaultedPayments.toArray());
};
});在事件中使用回调直接处理Vault:
primer:readyjavascript
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.
| Aspect | React 18 | React 19 |
|---|---|---|
| How objects passed | ref + useEffect | JSX props |
| Attribute conversion | Converts objects to | Assigns as properties |
| Code pattern | Imperative | Declarative |
| 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 18 | React 19 |
|---|---|---|
| 对象传递方式 | ref + useEffect | JSX props |
| 属性转换 | 将对象转换为 | 作为属性直接赋值 |
| 代码模式 | 命令式 | 声明式 |
| 代码行数 | ~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
- object
window
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 directive marks this component as client-side only.
'use client'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 (modern Nuxt 3) instead of (legacy Nuxt 2).
import.meta.clientprocess.clientvue
<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>注意: 使用(现代Nuxt 3)而非(遗留Nuxt 2)。
import.meta.clientprocess.clientSvelteKit
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
最佳实践
- Always use framework lifecycle methods (useEffect, onMounted, onMount)
- Include environment checks (,
typeof window,import.meta.client)browser - Use dynamic imports to prevent server bundling
- Wrap in try-catch for error handling
- Use stable references for options objects (apply to all frameworks)
- 始终使用框架生命周期方法(useEffect、onMounted、onMount)
- 包含环境检查(、
typeof window、import.meta.client)browser - 使用动态导入以避免服务端打包
- 用try-catch包裹以处理错误
- 对选项对象使用稳定引用(适用于所有框架)
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 or custom handling
<primer-error-message-container> - 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:
- Prominently visible after payment attempt
- Where users naturally look for feedback
- 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>放置指南:
- 在支付尝试后显眼可见
- 用户自然会查看反馈的位置
- 与支付方式处于相同视觉上下文
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 property is the ONLY exception - it's designed to accept direct property assignment.
options组件属性使用Lit的属性系统,该系统会监控DOM属性变化。直接属性赋值会绕过此系统,导致值被忽略。属性是唯一的例外 - 它被设计为接受直接属性赋值。
optionsComponent Properties (use setAttribute()
)
setAttribute()组件属性(使用setAttribute()
)
setAttribute()These are HTML attributes set via :
setAttribute()- - JWT from backend (REQUIRED)
client-token - - JSON string of CSS variables
custom-styles - - Boolean to disable loader
loader-disabled
javascript
checkout.setAttribute('client-token', 'your-token');
checkout.setAttribute('loader-disabled', 'true');
checkout.setAttribute(
'custom-styles',
JSON.stringify({ primerColorBrand: '#4a6cf7' }),
);这些是通过设置的HTML属性:
setAttribute()- - 来自后端的JWT(必填)
client-token - - CSS变量的JSON字符串
custom-styles - - 布尔值,用于禁用加载器
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 object:
options- 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 , SDK options use direct property assignment.
setAttribute()javascript
// 检查是否正确使用
checkout.getAttribute('client-token'); // 应返回token
checkout.options; // 应返回选项对象
// 常见错误
checkout.getAttribute('locale'); // 返回null(locale在options中!)记住: 切勿混淆两者。组件属性使用,SDK选项使用直接属性赋值。
setAttribute()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 (not ) to preserve layout space.
visibility: hiddendisplay: nonecss
primer-checkout:has(:not(:defined)) {
visibility: hidden;
}使用(而非)以保留布局空间。
visibility: hiddendisplay: noneJavaScript 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 attribute:
custom-styleshtml
<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-styleshtml
<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
最佳实践
- Always use stable object references in React (module-level constants or )
useMemo - Set component properties via , SDK options via property assignment
setAttribute() - Clean up event listeners in React cleanup functions
useEffect - Use declarative containers () instead of manual filtering
primer-payment-method-container - Include error handling with or custom callbacks
primer-error-message-container - Load Primer in (or equivalent) for SSR frameworks
useEffect - Use TypeScript declarations for proper JSX support
- Keep SDK options simple - only configure what you need
- Use v0.7.0+ callbacks (,
onPaymentSuccess) for clearer error handlingonPaymentFailure - Track diagnosticsId in payment failures for support inquiries
- 在React中始终使用稳定对象引用(模块级常量或)
useMemo - 通过设置组件属性,通过属性赋值设置SDK选项
setAttribute() - 在React的清理函数中移除事件监听器
useEffect - 使用声明式容器()而非手动过滤
primer-payment-method-container - 包含错误处理,使用或自定义回调
primer-error-message-container - 在SSR框架中,在(或等效方法)中加载Primer
useEffect - 使用TypeScript声明以获得正确的JSX支持
- 简化SDK选项 - 仅配置你需要的内容
- 使用v0.7.0+版本的回调(、
onPaymentSuccess)以获得更清晰的错误处理onPaymentFailure - 在支付失败时记录diagnosticsId以获取支持
Common Troubleshooting
常见故障排除
Component re-initializing on every render?
组件每次渲染都重新初始化?
→ Check object reference stability. Use module-level constants or .
→ In React 19, ensure options object has stable reference.
→ Applies to BOTH React 18 AND React 19.
useMemo→ 检查对象引用稳定性。使用模块级常量或。
→ 在React 19中,确保选项对象具有稳定引用。
→ 适用于React 18和React 19。
useMemoTypeScript errors with JSX?
JSX出现TypeScript错误?
→ Add TypeScript declarations:
→ Declare in global JSX namespace or use type
import type { CheckoutElement } from '@primer-io/primer-js'CustomElements→ 添加TypeScript声明:
→ 在全局JSX命名空间中声明或使用类型
import type { CheckoutElement } from '@primer-io/primer-js'CustomElementsSSR errors ("customElements is not defined", "window is not defined")?
SSR错误("customElements is not defined"、"window is not defined")?
→ Load Primer in client-side lifecycle: , ,
→ Use directive in Next.js App Router
→ Add environment checks:
→ Use dynamic imports:
useEffectonMountedonMount'use client'typeof window !== 'undefined'await import('@primer-io/primer-js')→ 在客户端生命周期中加载Primer:、、
→ 在Next.js App Router中使用指令
→ 添加环境检查:
→ 使用动态导入:
useEffectonMountedonMount'use client'typeof window !== 'undefined'await import('@primer-io/primer-js')Event not firing?
事件未触发?
→ Ensure component is mounted before adding listener
→ Use in React, wait for
→ Check event name (v0.7.0 renamed some events)
useEffectprimer:ready→ 确保在添加监听器前组件已挂载
→ 在React中使用,等待事件
→ 检查事件名称(v0.7.0重命名了部分事件)
useEffectprimer:readyPayment methods not showing?
支付方式未显示?
→ Check client token is valid
→ Check configuration
→ Wait for event before accessing SDK
→ Verify methods are configured in Primer Dashboard
→ Check SDK Core vs Legacy mode compatibility
enabledPaymentMethodsprimer:ready→ 检查client token是否有效
→ 检查配置
→ 等待事件后再访问SDK
→ 验证支付方式已在Primer控制台配置
→ 检查SDK Core与Legacy模式的兼容性
enabledPaymentMethodsprimer:readyOptions not applying?
选项未生效?
→ Check you're using , not
→ Verify object has stable reference in React
→ Check SDK Core vs Legacy mode compatibility
→ Never set in options (it's a component property)
checkout.options = {...}setAttributeclient-token→ 检查是否使用而非
→ 在React中验证对象具有稳定引用
→ 检查SDK Core与Legacy模式的兼容性
→ 切勿在options中设置(它是组件属性)
checkout.options = {...}setAttributeclient-tokenStyling not applying?
样式未生效?
→ CSS custom properties pierce Shadow DOM
→ Use variables
→ Check specificity and scoping
→ Apply to element or
--primer-*primer-checkout:root→ CSS自定义属性可穿透Shadow DOM
→ 使用变量
→ 检查特异性与作用域
→ 作用于元素或
--primer-*primer-checkout:rootInfinite re-renders in React?
React中出现无限重渲染?
→ Inline object in JSX: - use constant or useMemo
→ Object in component body without useMemo
→ Dependencies missing in useMemo array
→ This happens in BOTH React 18 AND React 19
options={{ locale: 'en-GB' }}→ 在JSX中内联对象: - 使用常量或useMemo
→ 在组件内部定义对象但未使用useMemo
→ useMemo数组中缺少依赖项
→ 这在React 18和React 19中都会发生
options={{ locale: 'en-GB' }}"Cannot set property options of HTMLElement"?
"Cannot set property options of HTMLElement"?
→ Component not yet registered, wait for
→ Or ensure was called
→ Use to wait
primer:readyloadPrimer()customElements.whenDefined('primer-checkout')→ 组件尚未注册,等待事件
→ 或确保已调用
→ 使用等待注册完成
primer:readyloadPrimer()customElements.whenDefined('primer-checkout')Payment failures not displaying?
支付失败未显示?
→ Include in your layout
→ Or implement custom error handling with callback
→ Or listen to event
→ Check and in state change events
<primer-error-message-container>onPaymentFailureprimer:payment-failureprimerJsErrorpaymentFailure→ 在布局中包含
→ 或使用回调实现自定义错误处理
→ 或监听事件
→ 在状态变化事件中检查和
<primer-error-message-container>onPaymentFailureprimer:payment-failureprimerJsErrorpaymentFailureVaulted methods not appearing?
已存储的支付方式未显示?
→ Check in options
→ Verify client session has
→ Listen to event
→ Use callback for updates
vault.enabled: truevaultOnSuccess: trueprimer:vault:methods-updateonVaultedMethodsUpdate→ 检查options中
→ 验证客户端会话已设置
→ 监听事件
→ 使用回调处理更新
vault.enabled: truevaultOnSuccess: trueprimer:vault:methods-updateonVaultedMethodsUpdatePayPal button not showing?
PayPal按钮未显示?
→ Check (required for PayPal)
→ Include in
→ Verify PayPal is configured in Primer Dashboard
→ Check browser console for PayPal SDK errors
sdkCore: truePaymentMethodType.PAYPALenabledPaymentMethods→ 检查(PayPal必需)
→ 在中包含
→ 验证PayPal已在Primer控制台配置
→ 检查浏览器控制台中的PayPal SDK错误
sdkCore: trueenabledPaymentMethodsPaymentMethodType.PAYPALResources
资源
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+功能与集成模式。