跨境电商支付系统技术选型与集成实战
支付系统是跨境电商的核心环节。本文将深入分析主流支付网关的特点、技术实现和最佳实践,帮助你选择最适合的支付解决方案。
主流支付网关对比
1. Stripe
适用场景: 全球市场,特别是欧美地区
优势:
- ✅ 开发者友好的 API
- ✅ 支持 135+ 货币
- ✅ 丰富的支付方式
- ✅ 完善的文档和工具
- ✅ 强大的反欺诈系统
费用:
标准费率:2.9% + $0.30 每笔
国际卡:+1.5%
货币转换:+1%2. PayPal
适用场景: 全球通用,用户基数大
优势:
- ✅ 用户认知度高
- ✅ 买家保护计划
- ✅ 无需信用卡
- ✅ 多种集成方式
费用:
标准费率:2.9% + $0.30 每笔
跨境交易:+1.5%
货币转换:3-4%3. 其他支付方式
typescript
// 不同地区的首选支付方式
const regionalPayments = {
US: ['Stripe', 'PayPal', 'Apple Pay', 'Google Pay'],
EU: ['Stripe', 'PayPal', 'SEPA', 'Klarna'],
UK: ['Stripe', 'PayPal', 'Klarna'],
CN: ['Alipay', 'WeChat Pay', 'UnionPay'],
JP: ['Stripe', 'PayPay', 'Line Pay'],
BR: ['PagSeguro', 'Mercado Pago'],
IN: ['Razorpay', 'PayTM', 'UPI']
};Stripe 集成实战
1. 基础配置
bash
# 安装 Stripe SDK
npm install stripe @stripe/stripe-js @stripe/react-stripe-jstypescript
// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-11-20',
typescript: true,
});
// 客户端配置
import { loadStripe } from '@stripe/stripe-js';
export const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);2. 创建支付意图
typescript
// app/api/create-payment-intent/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
export async function POST(req: NextRequest) {
try {
const { amount, currency, metadata } = await req.json();
// 创建 Payment Intent
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100), // 转换为最小货币单位
currency: currency.toLowerCase(),
automatic_payment_methods: {
enabled: true,
},
metadata: {
orderId: metadata.orderId,
userId: metadata.userId,
},
});
return NextResponse.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
console.error('Payment Intent Error:', error);
return NextResponse.json(
{ error: 'Failed to create payment intent' },
{ status: 500 }
);
}
}3. 前端结账页面
typescript
// components/CheckoutForm.tsx
'use client';
import { useState } from 'react';
import {
PaymentElement,
useStripe,
useElements,
Elements,
} from '@stripe/react-stripe-js';
import { stripePromise } from '@/lib/stripe';
function CheckoutForm({ amount, currency }: {
amount: number;
currency: string;
}) {
const stripe = useStripe();
const elements = useElements();
const [errorMessage, setErrorMessage] = useState<string>();
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
setLoading(true);
// 创建 Payment Intent
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount,
currency,
metadata: {
orderId: 'ORDER-123',
userId: 'USER-456',
},
}),
});
const { clientSecret, error: backendError } = await response.json();
if (backendError) {
setErrorMessage(backendError);
setLoading(false);
return;
}
// 确认支付
const { error } = await stripe.confirmPayment({
elements,
clientSecret,
confirmParams: {
return_url: `${window.location.origin}/payment/success`,
},
});
if (error) {
setErrorMessage(error.message);
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
{errorMessage && (
<div className="error-message">{errorMessage}</div>
)}
<button
type="submit"
disabled={!stripe || loading}
className="pay-button"
>
{loading ? 'Processing...' : `Pay ${currency} ${amount}`}
</button>
</form>
);
}
// 使用 Elements Provider 包装
export function StripeCheckout({ amount, currency }: {
amount: number;
currency: string;
}) {
const options = {
mode: 'payment' as const,
amount: Math.round(amount * 100),
currency: currency.toLowerCase(),
appearance: {
theme: 'stripe' as const,
},
};
return (
<Elements stripe={stripePromise} options={options}>
<CheckoutForm amount={amount} currency={currency} />
</Elements>
);
}4. Webhook 处理
typescript
// app/api/webhooks/stripe/route.ts
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import { stripe } from '@/lib/stripe';
import Stripe from 'stripe';
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get('stripe-signature')!;
let event: Stripe.Event;
try {
// 验证 webhook 签名
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (error) {
console.error('Webhook signature verification failed:', error);
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 400 }
);
}
// 处理不同的事件类型
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object as Stripe.PaymentIntent;
await handlePaymentSuccess(paymentIntent);
break;
case 'payment_intent.payment_failed':
const failedPayment = event.data.object as Stripe.PaymentIntent;
await handlePaymentFailure(failedPayment);
break;
case 'charge.refunded':
const refund = event.data.object as Stripe.Charge;
await handleRefund(refund);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
return NextResponse.json({ received: true });
}
async function handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) {
const orderId = paymentIntent.metadata.orderId;
// 更新订单状态
await updateOrderStatus(orderId, 'paid');
// 发送确认邮件
await sendOrderConfirmationEmail(orderId);
// 触发发货流程
await initiateShipping(orderId);
}
async function handlePaymentFailure(paymentIntent: Stripe.PaymentIntent) {
const orderId = paymentIntent.metadata.orderId;
// 记录失败原因
await logPaymentFailure(orderId, paymentIntent.last_payment_error);
// 通知客户
await sendPaymentFailureEmail(orderId);
}
async function handleRefund(charge: Stripe.Charge) {
// 处理退款逻辑
console.log('Refund processed:', charge.id);
}PayPal 集成实战
1. 基础配置
bash
npm install @paypal/react-paypal-jstypescript
// components/PayPalButton.tsx
'use client';
import {
PayPalScriptProvider,
PayPalButtons,
} from '@paypal/react-paypal-js';
export function PayPalCheckout({ amount, currency, onSuccess }: {
amount: number;
currency: string;
onSuccess: (orderId: string) => void;
}) {
return (
<PayPalScriptProvider
options={{
clientId: process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID!,
currency: currency,
intent: 'capture',
}}
>
<PayPalButtons
style={{
layout: 'vertical',
color: 'gold',
shape: 'rect',
label: 'paypal',
}}
createOrder={async () => {
// 创建订单
const response = await fetch('/api/paypal/create-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount, currency }),
});
const data = await response.json();
return data.orderId;
}}
onApprove={async (data) => {
// 捕获支付
const response = await fetch('/api/paypal/capture-order', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: data.orderID }),
});
const details = await response.json();
if (details.status === 'COMPLETED') {
onSuccess(data.orderID);
}
}}
onError={(err) => {
console.error('PayPal Error:', err);
}}
/>
</PayPalScriptProvider>
);
}2. 服务端 API
typescript
// app/api/paypal/create-order/route.ts
import { NextRequest, NextResponse } from 'next/server';
const PAYPAL_API = 'https://api-m.paypal.com';
async function getAccessToken() {
const auth = Buffer.from(
`${process.env.PAYPAL_CLIENT_ID}:${process.env.PAYPAL_SECRET}`
).toString('base64');
const response = await fetch(`${PAYPAL_API}/v1/oauth2/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${auth}`,
},
body: 'grant_type=client_credentials',
});
const data = await response.json();
return data.access_token;
}
export async function POST(req: NextRequest) {
try {
const { amount, currency } = await req.json();
const accessToken = await getAccessToken();
const response = await fetch(`${PAYPAL_API}/v2/checkout/orders`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
intent: 'CAPTURE',
purchase_units: [
{
amount: {
currency_code: currency,
value: amount.toFixed(2),
},
},
],
}),
});
const order = await response.json();
return NextResponse.json({ orderId: order.id });
} catch (error) {
console.error('PayPal Create Order Error:', error);
return NextResponse.json(
{ error: 'Failed to create order' },
{ status: 500 }
);
}
}多支付方式集成
统一支付接口
typescript
// lib/payment-gateway.ts
export interface PaymentGateway {
createPayment(params: PaymentParams): Promise<PaymentResult>;
capturePayment(paymentId: string): Promise<CaptureResult>;
refundPayment(paymentId: string, amount: number): Promise<RefundResult>;
}
export interface PaymentParams {
amount: number;
currency: string;
orderId: string;
customerId: string;
metadata?: Record<string, any>;
}
export interface PaymentResult {
paymentId: string;
status: 'pending' | 'succeeded' | 'failed';
clientSecret?: string;
}
// Stripe 实现
class StripeGateway implements PaymentGateway {
async createPayment(params: PaymentParams): Promise<PaymentResult> {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(params.amount * 100),
currency: params.currency,
customer: params.customerId,
metadata: params.metadata,
});
return {
paymentId: paymentIntent.id,
status: paymentIntent.status as any,
clientSecret: paymentIntent.client_secret!,
};
}
async capturePayment(paymentId: string): Promise<CaptureResult> {
const paymentIntent = await stripe.paymentIntents.capture(paymentId);
return {
success: paymentIntent.status === 'succeeded',
amount: paymentIntent.amount / 100,
};
}
async refundPayment(paymentId: string, amount: number): Promise<RefundResult> {
const refund = await stripe.refunds.create({
payment_intent: paymentId,
amount: Math.round(amount * 100),
});
return {
success: refund.status === 'succeeded',
refundId: refund.id,
};
}
}
// PayPal 实现
class PayPalGateway implements PaymentGateway {
// 实现类似的接口...
}
// 支付网关工厂
export class PaymentGatewayFactory {
static create(gateway: 'stripe' | 'paypal'): PaymentGateway {
switch (gateway) {
case 'stripe':
return new StripeGateway();
case 'paypal':
return new PayPalGateway();
default:
throw new Error(`Unsupported gateway: ${gateway}`);
}
}
}智能路由选择
typescript
// 根据用户位置和偏好选择支付方式
export function selectPaymentGateway(
country: string,
preferredMethod?: string
): 'stripe' | 'paypal' | 'alipay' {
// 用户偏好优先
if (preferredMethod) {
return preferredMethod as any;
}
// 根据地区选择
const gatewayPreferences: Record<string, string> = {
US: 'stripe',
UK: 'stripe',
EU: 'stripe',
CN: 'alipay',
JP: 'stripe',
};
return (gatewayPreferences[country] || 'stripe') as any;
}安全性最佳实践
1. PCI DSS 合规
typescript
// 永远不要在服务器端存储信用卡信息
// ❌ 错误做法
interface BadPractice {
cardNumber: string; // 不要存储
cvv: string; // 不要存储
expiryDate: string; // 不要存储
}
// ✅ 正确做法 - 使用 Token
interface GoodPractice {
paymentMethodId: string; // Stripe Payment Method ID
last4: string; // 仅存储后4位用于显示
brand: string; // 卡品牌(Visa, Mastercard)
expiryMonth: number;
expiryYear: number;
}2. 防止欺诈
typescript
// 欺诈检测系统
class FraudDetection {
async analyzeTransaction(transaction: Transaction): Promise<{
riskScore: number;
shouldBlock: boolean;
reasons: string[];
}> {
const checks = await Promise.all([
this.checkVelocity(transaction.userId),
this.checkLocation(transaction.ipAddress, transaction.billingCountry),
this.checkAmount(transaction.amount, transaction.userId),
this.checkDevice(transaction.deviceId),
]);
const riskScore = checks.reduce((sum, check) => sum + check.score, 0);
const reasons = checks.flatMap((check) => check.reasons);
return {
riskScore,
shouldBlock: riskScore > 80,
reasons,
};
}
// 检查交易频率
private async checkVelocity(userId: string) {
const recentTransactions = await getRecentTransactions(userId, 24); // 24小时内
if (recentTransactions.length > 10) {
return {
score: 50,
reasons: ['High transaction velocity'],
};
}
return { score: 0, reasons: [] };
}
// 检查地理位置异常
private async checkLocation(ipAddress: string, billingCountry: string) {
const ipCountry = await getCountryFromIP(ipAddress);
if (ipCountry !== billingCountry) {
return {
score: 30,
reasons: ['IP and billing country mismatch'],
};
}
return { score: 0, reasons: [] };
}
}3. 3D Secure 验证
typescript
// Stripe 3D Secure
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100,
currency: currency,
payment_method_types: ['card'],
// 强制 3D Secure
payment_method_options: {
card: {
request_three_d_secure: 'any', // 'automatic' | 'any'
},
},
});退款与争议处理
退款流程
typescript
// app/api/refund/route.ts
export async function POST(req: NextRequest) {
const { paymentId, amount, reason } = await req.json();
try {
// 创建退款
const refund = await stripe.refunds.create({
payment_intent: paymentId,
amount: amount ? Math.round(amount * 100) : undefined, // 部分退款
reason: reason, // 'duplicate' | 'fraudulent' | 'requested_by_customer'
metadata: {
timestamp: new Date().toISOString(),
processedBy: 'admin',
},
});
// 更新订单状态
await updateOrderStatus(paymentId, 'refunded');
// 发送退款确认邮件
await sendRefundConfirmation(paymentId, refund.amount / 100);
return NextResponse.json({
success: true,
refundId: refund.id,
amount: refund.amount / 100,
});
} catch (error) {
console.error('Refund Error:', error);
return NextResponse.json(
{ error: 'Refund failed' },
{ status: 500 }
);
}
}争议管理
typescript
// 处理 Stripe 争议
async function handleDispute(dispute: Stripe.Dispute) {
// 自动提交证据
const evidence = {
customer_name: await getCustomerName(dispute.payment_intent),
customer_email_address: await getCustomerEmail(dispute.payment_intent),
billing_address: await getBillingAddress(dispute.payment_intent),
shipping_address: await getShippingAddress(dispute.payment_intent),
shipping_tracking_number: await getTrackingNumber(dispute.payment_intent),
product_description: await getProductDescription(dispute.payment_intent),
};
await stripe.disputes.update(dispute.id, {
evidence: evidence,
submit: true,
});
}性能优化
1. 支付页面优化
typescript
// 预加载 Stripe.js
import { loadStripe } from '@stripe/stripe-js';
// 在应用初始化时预加载
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);
// 使用 React Suspense 优化加载
import { Suspense } from 'react';
export function CheckoutPage() {
return (
<Suspense fallback={<CheckoutSkeleton />}>
<StripeCheckout />
</Suspense>
);
}2. 缓存支付方式
typescript
// 缓存用户的支付方式
async function getCachedPaymentMethods(customerId: string) {
const cacheKey = `payment_methods:${customerId}`;
// 尝试从缓存获取
let paymentMethods = await redis.get(cacheKey);
if (!paymentMethods) {
// 从 Stripe 获取
const methods = await stripe.paymentMethods.list({
customer: customerId,
type: 'card',
});
paymentMethods = methods.data;
// 缓存 5 分钟
await redis.setex(cacheKey, 300, JSON.stringify(paymentMethods));
}
return JSON.parse(paymentMethods);
}监控与分析
支付指标追踪
typescript
// 关键支付指标
interface PaymentMetrics {
totalTransactions: number;
successfulTransactions: number;
failedTransactions: number;
successRate: number; // %
averageTransactionValue: number;
totalRevenue: number;
refundRate: number; // %
chargebackRate: number; // %
}
async function calculatePaymentMetrics(
startDate: Date,
endDate: Date
): Promise<PaymentMetrics> {
const transactions = await getTransactions(startDate, endDate);
const successful = transactions.filter((t) => t.status === 'succeeded');
const failed = transactions.filter((t) => t.status === 'failed');
const refunded = transactions.filter((t) => t.refunded);
return {
totalTransactions: transactions.length,
successfulTransactions: successful.length,
failedTransactions: failed.length,
successRate: (successful.length / transactions.length) * 100,
averageTransactionValue:
successful.reduce((sum, t) => sum + t.amount, 0) / successful.length,
totalRevenue: successful.reduce((sum, t) => sum + t.amount, 0),
refundRate: (refunded.length / successful.length) * 100,
chargebackRate: 0, // 从 Stripe Dashboard 获取
};
}总结
选择合适的支付系统需要考虑:
- 目标市场 - 不同地区用户的支付习惯
- 费用成本 - 交易费率和固定费用
- 开发成本 - API 友好度和集成难度
- 安全合规 - PCI DSS 和数据保护
- 用户体验 - 结账流程的顺畅度
推荐方案:
- 主要支付:Stripe(全球)+ PayPal(备选)
- 区域支付:根据目标市场添加本地支付方式
- 安全措施:启用 3D Secure + 欺诈检测
发布于 2025-11-30 | 跨境电商系列