recur-checkout
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRecur Checkout Integration
Recur结账流程集成
You are helping implement Recur checkout flows. Recur supports multiple checkout modes for different use cases.
你将协助实现Recur结账流程。Recur支持多种结账模式以适配不同使用场景。
Checkout Modes
结账模式
| Mode | Best For | User Experience |
|---|---|---|
| SPA apps | Form renders inline in your page |
| Quick purchases | Form appears in a dialog overlay |
| Simple integration | Full page redirect to Recur |
| 模式 | 适用场景 | 用户体验 |
|---|---|---|
| 单页应用(SPA) | 表单直接在页面内渲染 |
| 快速购买 | 表单以弹窗浮层形式展示 |
| 简单集成 | 整页重定向到Recur |
Basic Implementation
基础实现
Using useRecur Hook
使用useRecur Hook
tsx
import { useRecur } from 'recur-tw'
function CheckoutButton({ productId }: { productId: string }) {
const { checkout, isLoading } = useRecur()
const handleClick = async () => {
await checkout({
productId,
// Or use productSlug: 'pro-plan'
// Optional: Pre-fill customer info
customerEmail: 'user@example.com',
customerName: 'John Doe',
// Optional: Link to your user system
externalCustomerId: 'user_123',
// Callbacks
onPaymentComplete: (result) => {
console.log('Success!', result)
// result.id - Subscription/Order ID
// result.status - 'ACTIVE', 'TRIALING', etc.
},
onPaymentFailed: (error) => {
console.error('Failed:', error)
return { action: 'retry' } // or 'close' or 'custom'
},
onPaymentCancel: () => {
console.log('User cancelled')
},
})
}
return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? 'Processing...' : 'Subscribe'}
</button>
)
}tsx
import { useRecur } from 'recur-tw'
function CheckoutButton({ productId }: { productId: string }) {
const { checkout, isLoading } = useRecur()
const handleClick = async () => {
await checkout({
productId,
// 也可以使用 productSlug: 'pro-plan'
// 可选:预填充客户信息
customerEmail: 'user@example.com',
customerName: 'John Doe',
// 可选:关联到你的用户系统
externalCustomerId: 'user_123',
// 回调函数
onPaymentComplete: (result) => {
console.log('成功!', result)
// result.id - 订阅/订单ID
// result.status - 'ACTIVE'、'TRIALING'等
},
onPaymentFailed: (error) => {
console.error('失败:', error)
return { action: 'retry' } // 或 'close' 或 'custom'
},
onPaymentCancel: () => {
console.log('用户已取消')
},
})
}
return (
<button onClick={handleClick} disabled={isLoading}>
{isLoading ? '处理中...' : '订阅'}
</button>
)
}Using useSubscribe Hook (with state management)
使用useSubscribe Hook(带状态管理)
tsx
import { useSubscribe } from 'recur-tw'
function SubscribeButton({ productId }: { productId: string }) {
const { subscribe, isLoading, error, subscription } = useSubscribe()
const handleClick = () => {
subscribe({
productId,
onPaymentComplete: (sub) => {
// Subscription created successfully
router.push('/dashboard')
},
})
}
if (subscription) {
return <p>Subscribed! ID: {subscription.id}</p>
}
return (
<>
<button onClick={handleClick} disabled={isLoading}>
Subscribe
</button>
{error && <p className="error">{error.message}</p>}
</>
)
}tsx
import { useSubscribe } from 'recur-tw'
function SubscribeButton({ productId }: { productId: string }) {
const { subscribe, isLoading, error, subscription } = useSubscribe()
const handleClick = () => {
subscribe({
productId,
onPaymentComplete: (sub) => {
// 订阅创建成功
router.push('/dashboard')
},
})
}
if (subscription) {
return <p>已订阅!ID: {subscription.id}</p>
}
return (
<>
<button onClick={handleClick} disabled={isLoading}>
订阅
</button>
{error && <p className="error">{error.message}</p>}
</>
)
}Embedded Mode Setup
嵌入式模式设置
For embedded mode, you need a container element:
tsx
// In RecurProvider config
<RecurProvider
config={{
publishableKey: process.env.NEXT_PUBLIC_RECUR_PUBLISHABLE_KEY,
checkoutMode: 'embedded',
containerElementId: 'recur-checkout-container',
}}
>
{children}
</RecurProvider>
// In your checkout page
function CheckoutPage() {
return (
<div>
<h1>Complete Your Purchase</h1>
{/* Recur will render the payment form here */}
<div id="recur-checkout-container" />
</div>
)
}对于嵌入式模式,你需要一个容器元素:
tsx
// 在RecurProvider配置中
<RecurProvider
config={{
publishableKey: process.env.NEXT_PUBLIC_RECUR_PUBLISHABLE_KEY,
checkoutMode: 'embedded',
containerElementId: 'recur-checkout-container',
}}
>
{children}
</RecurProvider>
// 在你的结账页面中
function CheckoutPage() {
return (
<div>
<h1>完成你的购买</h1>
{/* Recur会在此处渲染付款表单 */}
<div id="recur-checkout-container" />
</div>
)
}Handling 3D Verification
处理3D验证
Recur handles 3D Secure automatically. For mobile apps or specific flows:
tsx
await checkout({
productId,
// These URLs are used when 3D verification requires redirect
successUrl: 'https://yourapp.com/checkout/success',
cancelUrl: 'https://yourapp.com/checkout/cancel',
})Recur会自动处理3D安全验证。对于移动应用或特定流程:
tsx
await checkout({
productId,
// 当3D验证需要重定向时会使用这些URL
successUrl: 'https://yourapp.com/checkout/success',
cancelUrl: 'https://yourapp.com/checkout/cancel',
})Product Types
产品类型
Recur supports different product types:
tsx
// Subscription (recurring)
checkout({ productId: 'prod_subscription_xxx' })
// One-time purchase
checkout({ productId: 'prod_onetime_xxx' })
// Credits (prepaid wallet)
checkout({ productId: 'prod_credits_xxx' })
// Donation (variable amount)
checkout({ productId: 'prod_donation_xxx' })Recur支持多种产品类型:
tsx
// 订阅(定期扣费)
checkout({ productId: 'prod_subscription_xxx' })
// 一次性购买
checkout({ productId: 'prod_onetime_xxx' })
// 点数(预付费钱包)
checkout({ productId: 'prod_credits_xxx' })
// 捐赠(金额可变)
checkout({ productId: 'prod_donation_xxx' })Listing Products
产品列表展示
tsx
import { useProducts } from 'recur-tw'
function PricingPage() {
const { products, isLoading } = useProducts({
type: 'SUBSCRIPTION', // Filter by type
})
if (isLoading) return <div>Loading...</div>
return (
<div className="pricing-grid">
{products.map(product => (
<PricingCard key={product.id} product={product} />
))}
</div>
)
}tsx
import { useProducts } from 'recur-tw'
function PricingPage() {
const { products, isLoading } = useProducts({
type: 'SUBSCRIPTION', // 按类型筛选
})
if (isLoading) return <div>加载中...</div>
return (
<div className="pricing-grid">
{products.map(product => (
<PricingCard key={product.id} product={product} />
))}
</div>
)
}Payment Failed Handling
付款失败处理
tsx
onPaymentFailed: (error) => {
// error.code tells you what went wrong
switch (error.code) {
case 'CARD_DECLINED':
return { action: 'retry' }
case 'INSUFFICIENT_FUNDS':
return {
action: 'custom',
customTitle: '餘額不足',
customMessage: '請使用其他付款方式',
}
default:
return { action: 'close' }
}
}tsx
onPaymentFailed: (error) => {
// error.code会告诉你失败原因
switch (error.code) {
case 'CARD_DECLINED':
return { action: 'retry' }
case 'INSUFFICIENT_FUNDS':
return {
action: 'custom',
customTitle: '餘額不足',
customMessage: '請使用其他付款方式',
}
default:
return { action: 'close' }
}
}Server-Side Checkout (API)
服务端结账(API方式)
For server-rendered apps or custom flows:
typescript
// Create checkout session
const response = await fetch('https://api.recur.tw/v1/checkouts', {
method: 'POST',
headers: {
'X-Recur-Secret-Key': process.env.RECUR_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId: 'prod_xxx',
customerEmail: 'user@example.com',
successUrl: 'https://yourapp.com/success',
cancelUrl: 'https://yourapp.com/cancel',
}),
})
const { checkoutUrl } = await response.json()
// Redirect user to checkoutUrl适用于服务端渲染应用或自定义流程:
typescript
// 创建结账会话
const response = await fetch('https://api.recur.tw/v1/checkouts', {
method: 'POST',
headers: {
'X-Recur-Secret-Key': process.env.RECUR_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId: 'prod_xxx',
customerEmail: 'user@example.com',
successUrl: 'https://yourapp.com/success',
cancelUrl: 'https://yourapp.com/cancel',
}),
})
const { checkoutUrl } = await response.json()
// 重定向用户到checkoutUrlCheckout Result Structure
结账结果结构
typescript
interface CheckoutResult {
id: string // Subscription or Order ID
status: string // 'ACTIVE', 'TRIALING', 'PENDING'
productId: string
amount: number // In cents (e.g., 29900 = NT$299)
billingPeriod?: string // 'MONTHLY', 'YEARLY' for subscriptions
currentPeriodEnd?: string // ISO date
trialEndsAt?: string // ISO date if trial
}typescript
interface CheckoutResult {
id: string // 订阅或订单ID
status: string // 'ACTIVE'、'TRIALING'、'PENDING'
productId: string
amount: number // 单位为分(例如:29900 = 新台币299元)
billingPeriod?: string // 订阅的计费周期:'MONTHLY'(月度)、'YEARLY'(年度)
currentPeriodEnd?: string // ISO格式日期
trialEndsAt?: string // 试用结束日期(ISO格式,若有试用)
}Best Practices
最佳实践
- Always handle all callbacks - onPaymentComplete, onPaymentFailed, onPaymentCancel
- Show loading states - Use isLoading to disable buttons during checkout
- Pre-fill customer info - Reduces friction if you already have user data
- Use externalCustomerId - Links Recur customers to your user system
- Test in sandbox first - Use keys during development
pk_test_
- 务必处理所有回调函数 - onPaymentComplete、onPaymentFailed、onPaymentCancel
- 显示加载状态 - 使用isLoading在结账过程中禁用按钮
- 预填充客户信息 - 若已拥有用户数据,可减少操作步骤
- 使用externalCustomerId - 将Recur客户与你的用户系统关联
- 先在沙箱环境测试 - 开发阶段使用开头的密钥
pk_test_
Related Skills
相关技能
- - Initial SDK setup
/recur-quickstart - - Receive payment notifications
/recur-webhooks - - Check subscription access
/recur-entitlements
- - 初始SDK设置
/recur-quickstart - - 接收付款通知
/recur-webhooks - - 检查订阅权限
/recur-entitlements