Wasp支付集成:Stripe、PayPal等支付网关配置
前言:为什么支付集成如此重要?
在现代Web应用开发中,支付功能已成为许多SaaS产品、电商平台和订阅服务的核心需求。然而,支付集成往往涉及复杂的API调用、安全认证和状态管理,让许多开发者望而却步。
Wasp作为全栈Web应用框架,虽然目前没有内置的支付模块,但其灵活的架构设计使得集成第三方支付网关变得异常简单。本文将深入探讨如何在Wasp应用中集成Stripe、PayPal等主流支付网关,为您提供完整的解决方案。
支付网关选择指南
在选择支付网关时,需要考虑以下几个关键因素:
| 支付网关 | 适用场景 | 手续费 | 开发复杂度 | 全球支持 |
|---|---|---|---|---|
| Stripe | SaaS订阅、电商 | 2.9% + $0.30 | 中等 | 优秀 |
| PayPal | 国际支付、B2C | 2.9% + $0.30 | 简单 | 极佳 |
| 支付宝 | 中国市场 | 0.6%-1.2% | 中等 | 中国为主 |
| 微信支付 | 中国市场 | 0.6%-1.0% | 中等 | 中国为主 |
Wasp项目结构概述
在开始支付集成前,让我们先了解Wasp的标准项目结构:
Stripe支付集成实战
1. 环境配置与依赖安装
首先,在Wasp项目中安装Stripe相关的依赖:
# 在项目根目录执行
npm install stripe @stripe/stripe-js
2. 配置环境变量
在Wasp中配置Stripe密钥,编辑main.wasp文件:
app myApp {
title: "My SaaS App",
wasp: { version: "^0.15.0" },
// 环境变量配置
env: {
STRIPE_PUBLISHABLE_KEY: import.meta.env.STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY: import.meta.env.STRIPE_SECRET_KEY
}
}
在.env文件中添加实际密钥:
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key
STRIPE_SECRET_KEY=sk_test_your_secret_key
3. 创建支付实体模型
在schema.prisma中定义支付相关的数据模型:
model User {
id Int @id @default(autoincrement())
email String @unique
stripeCustomerId String?
subscriptions Subscription[]
payments Payment[]
}
model Payment {
id Int @id @default(autoincrement())
amount Int
currency String @default("usd")
status String // pending, completed, failed
stripePaymentIntentId String?
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
model Subscription {
id Int @id @default(autoincrement())
stripeSubscriptionId String?
status String
plan String
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
}
4. 实现服务器端支付逻辑
创建src/server/payments.js文件:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// 创建支付意图
export const createPaymentIntent = async (amount, currency = 'usd') => {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount * 100, // 转换为分
currency: currency,
automatic_payment_methods: {
enabled: true,
},
});
return {
clientSecret: paymentIntent.client_secret,
paymentIntentId: paymentIntent.id
};
} catch (error) {
console.error('Error creating payment intent:', error);
throw new Error('Failed to create payment intent');
}
};
// 创建Stripe客户
export const createStripeCustomer = async (email, name) => {
try {
const customer = await stripe.customers.create({
email: email,
name: name,
});
return customer.id;
} catch (error) {
console.error('Error creating Stripe customer:', error);
throw new Error('Failed to create customer');
}
};
// 处理支付成功webhook
export const handlePaymentSuccess = async (paymentIntentId) => {
try {
const paymentIntent = await stripe.paymentIntents.retrieve(paymentIntentId);
// 更新数据库中的支付状态
// 这里需要实现具体的业务逻辑
return { success: true, paymentIntent };
} catch (error) {
console.error('Error handling payment success:', error);
throw new Error('Failed to process payment');
}
};
5. 定义Wasp操作
在main.wasp中定义支付相关的操作:
// 支付操作定义
action createPaymentIntent {
fn: import { createPaymentIntent } from "@server/payments.js",
entities: [Payment]
}
action createStripeCustomer {
fn: import { createStripeCustomer } from "@server/payments.js",
entities: [User]
}
action handlePaymentSuccess {
fn: import { handlePaymentSuccess } from "@server/payments.js",
entities: [Payment, User]
}
6. 实现前端支付组件
创建src/client/components/PaymentForm.jsx:
import React, { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { useQuery, useAction } from '@wasp/queries';
import { createPaymentIntent } from '@wasp/actions';
const stripePromise = loadStripe(process.env.STRIPE_PUBLISHABLE_KEY);
const CheckoutForm = ({ amount, onSuccess }) => {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState(null);
const [processing, setProcessing] = useState(false);
const createPaymentIntentFn = useAction(createPaymentIntent);
const handleSubmit = async (event) => {
event.preventDefault();
setProcessing(true);
if (!stripe || !elements) {
return;
}
try {
// 创建支付意图
const { clientSecret } = await createPaymentIntentFn({ amount });
// 确认支付
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
// 可以添加用户账单信息
},
},
});
if (result.error) {
setError(result.error.message);
} else {
// 支付成功
onSuccess(result.paymentIntent);
}
} catch (err) {
setError(err.message);
} finally {
setProcessing(false);
}
};
return (
<form onSubmit={handleSubmit} className="payment-form">
<div className="form-group">
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
},
}}
/>
</div>
{error && <div className="error">{error}</div>}
<button
type="submit"
disabled={!stripe || processing}
className="pay-button"
>
{processing ? '处理中...' : `支付 $${amount}`}
</button>
</form>
);
};
export const PaymentForm = ({ amount, onSuccess }) => {
return (
<Elements stripe={stripePromise}>
<CheckoutForm amount={amount} onSuccess={onSuccess} />
</Elements>
);
};
PayPal支付集成方案
1. 安装PayPal依赖
npm install @paypal/react-paypal-js
2. 实现PayPal支付组件
创建src/client/components/PayPalPayment.jsx:
import React from 'react';
import { PayPalScriptProvider, PayPalButtons } from "@paypal/react-paypal-js";
export const PayPalPayment = ({ amount, onSuccess, onError }) => {
const initialOptions = {
"client-id": process.env.PAYPAL_CLIENT_ID,
currency: "USD",
intent: "capture",
};
const createOrder = (data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
value: amount.toString(),
currency_code: "USD",
},
},
],
});
};
const onApprove = (data, actions) => {
return actions.order.capture().then((details) => {
onSuccess(details);
});
};
return (
<PayPalScriptProvider options={initialOptions}>
<PayPalButtons
createOrder={createOrder}
onApprove={onApprove}
onError={onError}
style={{
layout: "vertical",
shape: "rect",
color: "gold",
label: "paypal",
}}
/>
</PayPalScriptProvider>
);
};
支付状态管理与Webhook处理
1. 实现支付状态机
2. Webhook处理器实现
创建src/server/webhooks.js:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export const handleStripeWebhook = async (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// 处理不同类型的webhook事件
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSuccess(event.data.object);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailure(event.data.object);
break;
case 'customer.subscription.created':
await handleSubscriptionCreated(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object);
break;
default:
console.log(`Unhandled event type ${event.type}`);
}
response.json({ received: true });
};
const handlePaymentSuccess = async (paymentIntent) => {
// 更新数据库支付状态
// 发送确认邮件
// 触发相关业务逻辑
};
const handlePaymentFailure = async (paymentIntent) => {
// 处理支付失败逻辑
// 通知用户
};
安全最佳实践
1. 支付数据安全
// 永远不要在客户端存储敏感信息
const validatePaymentData = (paymentData) => {
const errors = [];
// 验证金额
if (paymentData.amount <= 0) {
errors.push('金额必须大于0');
}
// 验证货币类型
const validCurrencies = ['usd', 'eur', 'gbp', 'jpy', 'cny'];
if (!validCurrencies.includes(paymentData.currency.toLowerCase())) {
errors.push('不支持的货币类型');
}
return errors;
};
2. 防重复支付处理
let processingPayments = new Set();
export const processPayment = async (paymentId, amount) => {
if (processingPayments.has(paymentId)) {
throw new Error('支付正在处理中,请勿重复提交');
}
processingPayments.add(paymentId);
try {
// 处理支付逻辑
const result = await actualPaymentProcessing(paymentId, amount);
return result;
} finally {
processingPayments.delete(paymentId);
}
};
测试与调试策略
1. 支付测试用例
// 测试支付流程
describe('Payment Integration', () => {
it('should create payment intent successfully', async () => {
const result = await createPaymentIntent(1000, 'usd');
expect(result).toHaveProperty('clientSecret');
expect(result).toHaveProperty('paymentIntentId');
});
it('should handle payment failure gracefully', async () => {
await expect(createPaymentIntent(-100, 'usd'))
.rejects
.toThrow('金额必须大于0');
});
});
2. 模拟支付环境
// 开发环境支付模拟
const mockPaymentGateway = {
createPayment: async (amount) => {
if (process.env.NODE_ENV === 'development') {
// 模拟支付成功
return {
id: `mock_${Date.now()}`,
status: 'completed',
amount: amount
};
}
// 生产环境调用真实API
return realPaymentGateway.createPayment(amount);
}
};
部署与生产环境配置
1. 环境变量管理
# 生产环境.env.production
STRIPE_PUBLISHABLE_KEY=pk_live_your_live_key
STRIPE_SECRET_KEY=sk_live_your_live_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
PAYPAL_CLIENT_ID=your_live_paypal_client_id
2. Webhook端点配置
在main.wasp中定义webhook路由:
// Webhook端点定义
api handleStripeWebhook {
fn: import { handleStripeWebhook } from "@server/webhooks.js",
httpRoute: (POST, "/webhooks/stripe")
}
性能优化建议
1. 支付请求优化
// 使用批处理减少API调用
const batchPaymentProcessing = async (payments) => {
const batchSize = 10;
const results = [];
for (let i = 0; i < payments.length; i += batchSize) {
const batch = payments.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(payment => processSinglePayment(payment))
);
results.push(...batchResults);
}
return results;
};
2. 缓存策略
// 缓存支付网关配置
const paymentConfigCache = new Map();
const getPaymentConfig = async (gateway) => {
if (paymentConfigCache.has(gateway)) {
return paymentConfigCache.get(gateway);
}
const config = await fetchPaymentConfig(gateway);
paymentConfigCache.set(gateway, config);
// 设置缓存过期时间
setTimeout(() => {
paymentConfigCache.delete(gateway);
}, 5 * 60 * 1000); // 5分钟
return config;
};
总结与最佳实践
通过本文的详细指导,您已经掌握了在Wasp应用中集成主流支付网关的完整流程。以下是关键要点总结:
- 选择合适的支付网关:根据目标用户群体和业务需求选择
- 安全第一:永远不要在客户端处理敏感支付信息
- 完善的错误处理:为用户提供清晰的错误反馈
- 测试全覆盖:确保支付流程在各种场景下都能正常工作
- 监控与日志:记录所有支付操作以便审计和排查问题
Wasp的声明式架构使得支付集成变得简单而安全,您只需要关注业务逻辑,框架会处理底层的复杂性。现在就开始为您的Wasp应用添加支付功能,让您的产品实现商业化变现!
记住,良好的支付体验是用户信任的基础,投入时间优化支付流程将为您的应用带来长期的商业价值。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



