Node.js中集成PayPal与Stripe支付的最优实现方案是什么
Node.js电商项目同时集成Stripe与PayPal支付方案
前置准备
- 注册两个平台的开发者账号,获取对应环境的密钥:Stripe的
STRIPE_SECRET_KEY、STRIPE_PUBLISHABLE_KEY、STRIPE_WEBHOOK_SECRET,PayPal的PAYPAL_CLIENT_ID、PAYPAL_CLIENT_SECRET,注意区分沙箱和生产环境密钥,不要混用 - 项目中安装官方SDK:
npm install stripe @paypal/checkout-server-sdk
后端核心逻辑实现(以Express框架为例)
初始化客户端
// 初始化Stripe客户端 const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); // 初始化PayPal客户端 const paypal = require('@paypal/checkout-server-sdk'); const environment = process.env.NODE_ENV === 'production' ? new paypal.core.LiveEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET) : new paypal.core.SandboxEnvironment(process.env.PAYPAL_CLIENT_ID, process.env.PAYPAL_CLIENT_SECRET); const paypalClient = new paypal.core.PayPalHttpClient(environment);
通用订单预处理逻辑
不管用户选择哪种支付方式,先在自有系统内创建待支付订单,记录订单ID、对应金额、商品信息、用户ID,订单状态标记为待支付,同时做好订单金额校验,避免前端传参篡改支付金额。
Stripe支付相关接口
// 创建Stripe支付会话接口 app.post('/api/payment/stripe/create', async (req, res) => { const { orderId } = req.body; // 查询自有数据库订单信息,校验订单状态、金额合法性 const order = await Order.findById(orderId); const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price_data: { currency: 'cny', product_data: { name: `订单${orderId}` }, unit_amount: order.amount * 100, // Stripe金额单位为分,需做单位转换 }, quantity: 1, } ], mode: 'payment', success_url: `${process.env.FRONTEND_URL}/payment/success?orderId=${orderId}`, cancel_url: `${process.env.FRONTEND_URL}/payment/cancel?orderId=${orderId}`, metadata: { orderId: orderId } }); res.json({ sessionId: session.id, jumpUrl: session.url }); }); // Stripe webhook回调接口,用于可靠更新订单状态 app.post('/api/payment/stripe/webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); if (event.type === 'checkout.session.completed') { const orderId = event.data.object.metadata.orderId; // 更新订单状态为已支付,触发后续发货、通知逻辑 await Order.findByIdAndUpdate(orderId, { status: 'paid', paymentMethod: 'stripe' }); } res.json({ received: true }); });
PayPal支付相关接口
// 创建PayPal支付订单接口 app.post('/api/payment/paypal/create', async (req, res) => { const { orderId } = req.body; const order = await Order.findById(orderId); const request = new paypal.orders.OrdersCreateRequest(); request.prefer("return=representation"); request.requestBody({ intent: 'CAPTURE', purchase_units: [{ reference_id: orderId, amount: { currency_code: 'CNY', value: order.amount.toString() } }], application_context: { return_url: `${process.env.FRONTEND_URL}/payment/success?orderId=${orderId}`, cancel_url: `${process.env.FRONTEND_URL}/payment/cancel?orderId=${orderId}`, } }); const paypalOrder = await paypalClient.execute(request); const approvalUrl = paypalOrder.result.links.find(link => link.rel === 'approve').href; res.json({ paypalOrderId: paypalOrder.result.id, jumpUrl: approvalUrl }); }); // PayPal支付捕获接口 app.post('/api/payment/paypal/capture', async (req, res) => { const { paypalOrderId, orderId } = req.body; const request = new paypal.orders.OrdersCaptureRequest(paypalOrderId); request.requestBody({}); const captureResult = await paypalClient.execute(request); if (captureResult.result.status === 'COMPLETED') { // 更新订单状态为已支付 await Order.findByIdAndUpdate(orderId, { status: 'paid', paymentMethod: 'paypal' }); res.json({ success: true }); } else { res.json({ success: false, msg: '支付未完成' }); } });
前端对接逻辑
- 支付页提供两个支付选项按钮,用户选择Stripe时调用
/api/payment/stripe/create接口,拿到跳转链接后直接跳转至Stripe支付页即可 - 用户选择PayPal时,调用
/api/payment/paypal/create接口,拿到跳转链接后跳转至PayPal支付页,用户支付完成回跳前端成功页时,再调用/api/payment/paypal/capture接口完成最终的订单状态更新
注意事项
- 所有密钥全部存放在环境变量中,严禁硬编码到代码仓库
- 订单支付状态更新必须以服务端回调/接口校验结果为准,不能信任前端回跳携带的支付成功参数
- 开发阶段统一使用两个平台的沙箱环境测试,流程全量跑通后再切换生产密钥
- 建议增加定时任务轮询超时未支付的订单状态,避免回调丢失导致订单状态异常
内容的提问来源于stack exchange,提问作者NathanWebDev
相关产品推荐
相关产品推荐

