.agents/skills/recur-checkout/SKILL.md
Implement Recur checkout flows including embedded, modal, and redirect modes. Use when adding payment buttons, checkout forms, subscription purchase flows, or when user mentions "checkout", "結帳", "付款按鈕", "embedded checkout".
npx skillsauth add flsteven87/three_kingdoms_strategy recur-checkoutInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are helping implement Recur checkout flows. Recur supports multiple checkout modes for different use cases.
This project uses FastAPI (Python) + React (TypeScript). All examples match this stack.
| Mode | Best For | User Experience |
|------|----------|-----------------|
| modal | Quick purchases (default) | Form appears in a dialog overlay |
| embedded | SPA apps | Form renders inline in your page |
| redirect | Simple integration | Full page redirect to Recur hosted page |
import { useRecur } from 'recur-tw'
interface CheckoutButtonProps {
readonly productId: string
readonly userEmail: string
readonly userId: string
}
function CheckoutButton({ productId, userEmail, userId }: CheckoutButtonProps) {
const { checkout, isLoading } = useRecur()
const handleClick = async () => {
await checkout({
productId,
// Or use productSlug: 'pro-plan'
// Pre-fill customer info from auth
customerEmail: userEmail,
externalCustomerId: userId,
// Callbacks
onPaymentComplete: (result) => {
// result.id - Subscription/Order ID
// result.status - 'ACTIVE', 'TRIALING', etc.
console.log('Success!', result)
},
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 ? '處理中...' : '訂閱'}
</button>
)
}
import { useSubscribe } from 'recur-tw'
import { useNavigate } from 'react-router-dom'
function SubscribeButton({ productId }: { readonly productId: string }) {
const { subscribe, isLoading, error, subscription } = useSubscribe()
const navigate = useNavigate()
const handleClick = () => {
subscribe({
productId,
onPaymentComplete: () => {
navigate('/dashboard')
},
})
}
if (subscription) {
return <p>已訂閱!ID: {subscription.id}</p>
}
return (
<>
<button onClick={handleClick} disabled={isLoading}>
訂閱
</button>
{error && <p className="text-destructive">{error.message}</p>}
</>
)
}
For embedded mode, set checkoutMode in RecurProvider:
<RecurProvider
config={{
publishableKey: import.meta.env.VITE_RECUR_PUBLISHABLE_KEY,
checkoutMode: 'embedded',
containerElementId: 'recur-checkout-container',
}}
>
{children}
</RecurProvider>
// In your checkout page
function CheckoutPage() {
return (
<div>
<h1>完成購買</h1>
{/* Recur renders the payment form here */}
<div id="recur-checkout-container" />
</div>
)
}
Recur handles 3D Secure automatically. For redirect flows:
await checkout({
productId,
successUrl: 'https://tktmanager.com/checkout/success',
cancelUrl: 'https://tktmanager.com/checkout/cancel',
})
| Type | Billing | Use Case |
|------|---------|----------|
| SUBSCRIPTION | Recurring (weekly/monthly/yearly) | Ongoing subscriptions |
| ONE_TIME | Single payment | One-time purchases |
| CREDITS | Top-up | In-app credit packages |
| DONATION | Custom amount | Donation/contribution |
// Subscription (recurring)
checkout({ productId: 'prod_subscription_xxx' })
// One-time purchase
checkout({ productId: 'prod_onetime_xxx' })
// Credits
checkout({ productId: 'prod_credits_xxx' })
Products can have family grouping for pricing tiers:
productFamily — group related plans (e.g., "premium")variantType — billing variant: WEEKLY, MONTHLY, YEARLY, CUSTOMisMainVariant — primary variant in familyimport { useProducts } from 'recur-tw'
function PricingPage() {
const { products, isLoading } = useProducts({
type: 'SUBSCRIPTION',
})
if (isLoading) return <div>載入中...</div>
return (
<div className="grid gap-4 md:grid-cols-3">
{products.map(product => (
<PricingCard key={product.id} product={product} />
))}
</div>
)
}
onPaymentFailed: (error) => {
switch (error.code) {
case 'CARD_DECLINED':
case 'PAYUNI_DECLINED':
return { action: 'retry' }
case 'INSUFFICIENT_FUNDS':
return {
action: 'custom',
customTitle: '餘額不足',
customMessage: '請使用其他付款方式',
}
case 'EXPIRED_CARD':
return {
action: 'custom',
customTitle: '卡片已過期',
customMessage: '請更換有效的信用卡',
}
case 'NETWORK_ERROR':
case 'TIMEOUT':
return { action: 'retry' }
default:
return { action: 'close' }
}
}
type PaymentFailureCode =
| 'PAYUNI_DECLINED'
| 'UNAPPROVED'
| 'INSUFFICIENT_FUNDS'
| 'CARD_DECLINED'
| 'EXPIRED_CARD'
| 'NETWORK_ERROR'
| 'TIMEOUT'
| 'INVALID_CARD'
| 'UNKNOWN'
useEffect(() => {
const handleSuccess = (e: CustomEvent) => {
const { subscriptionId, orderId } = e.detail
console.log('Payment success:', subscriptionId || orderId)
}
const handleError = (e: CustomEvent) => {
const { message, code } = e.detail
console.error('Payment error:', code, message)
}
const handleClose = () => {
console.log('Modal closed')
}
window.addEventListener('recur:success', handleSuccess as EventListener)
window.addEventListener('recur:error', handleError as EventListener)
window.addEventListener('recur:close', handleClose)
return () => {
window.removeEventListener('recur:success', handleSuccess as EventListener)
window.removeEventListener('recur:error', handleError as EventListener)
window.removeEventListener('recur:close', handleClose)
}
}, [])
For server-controlled checkout flows:
import httpx
async def create_checkout_session(
product_id: str,
customer_email: str,
external_customer_id: str,
) -> str:
"""Create a Recur checkout session and return the checkout URL."""
async with httpx.AsyncClient() as client:
response = await client.post(
"https://api.recur.tw/v1/checkout/sessions",
headers={
"X-Recur-Secret-Key": settings.recur_secret_key,
"Content-Type": "application/json",
},
json={
"productId": product_id,
"customerEmail": customer_email,
"externalCustomerId": external_customer_id,
"successUrl": f"{settings.frontend_url}/checkout/success",
"cancelUrl": f"{settings.frontend_url}/checkout/cancel",
},
)
response.raise_for_status()
data = response.json()
return data["checkoutUrl"]
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
}
interface CheckoutOptions {
productId?: string // Product ID (required unless productSlug)
productSlug?: string // Alternative to productId
customerName?: string // Pre-fill customer name
customerEmail?: string // Pre-fill & required at checkout
externalCustomerId?: string // Link to your user system
successUrl?: string // Redirect after success
cancelUrl?: string // Redirect after cancellation
onPaymentComplete?: (result: CheckoutResult) => void
onPaymentFailed?: (error: PaymentError) => PaymentFailedAction
onPaymentCancel?: () => void
}
onPaymentComplete, onPaymentFailed, onPaymentCancelisLoading to disable buttons during checkoutcustomerEmail and externalCustomerId from auth contextpk_test_* keys during developmentuseEffect/recur-quickstart - Initial SDK setup/recur-webhooks - Receive payment notifications (FastAPI handler)/recur-entitlements - Check subscription accessdevelopment
Set up and handle Recur webhook events for payment notifications. Use when implementing webhook handlers, verifying signatures, handling subscription events, or when user mentions "webhook", "付款通知", "訂閱事件", "payment notification".
development
Quick setup guide for Recur payment integration. Use when starting a new Recur integration, setting up API keys, installing the SDK, or when user mentions "integrate Recur", "setup Recur", "Recur 串接", "金流設定".
development
Implement Customer Portal for subscription self-service. Use when building account pages, letting customers manage subscriptions, update payment methods, view billing history, or when user mentions "customer portal", "帳戶管理", "訂閱管理", "更新付款方式", "self-service".
data-ai
List all available Recur skills and how to use them. Use when user asks "what can Recur do", "Recur skills", "Recur 有什麼功能", "help with Recur", "如何使用 Recur skills".