Wasp支付集成:Stripe、PayPal等支付网关配置

Wasp支付集成:Stripe、PayPal等支付网关配置

【免费下载链接】wasp The fastest way to develop full-stack web apps with React & Node.js. 【免费下载链接】wasp 项目地址: https://gitcode.com/GitHub_Trending/wa/wasp

前言:为什么支付集成如此重要?

在现代Web应用开发中,支付功能已成为许多SaaS产品、电商平台和订阅服务的核心需求。然而,支付集成往往涉及复杂的API调用、安全认证和状态管理,让许多开发者望而却步。

Wasp作为全栈Web应用框架,虽然目前没有内置的支付模块,但其灵活的架构设计使得集成第三方支付网关变得异常简单。本文将深入探讨如何在Wasp应用中集成Stripe、PayPal等主流支付网关,为您提供完整的解决方案。

支付网关选择指南

在选择支付网关时,需要考虑以下几个关键因素:

支付网关适用场景手续费开发复杂度全球支持
StripeSaaS订阅、电商2.9% + $0.30中等优秀
PayPal国际支付、B2C2.9% + $0.30简单极佳
支付宝中国市场0.6%-1.2%中等中国为主
微信支付中国市场0.6%-1.0%中等中国为主

Wasp项目结构概述

在开始支付集成前,让我们先了解Wasp的标准项目结构:

mermaid

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. 实现支付状态机

mermaid

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应用中集成主流支付网关的完整流程。以下是关键要点总结:

  1. 选择合适的支付网关:根据目标用户群体和业务需求选择
  2. 安全第一:永远不要在客户端处理敏感支付信息
  3. 完善的错误处理:为用户提供清晰的错误反馈
  4. 测试全覆盖:确保支付流程在各种场景下都能正常工作
  5. 监控与日志:记录所有支付操作以便审计和排查问题

Wasp的声明式架构使得支付集成变得简单而安全,您只需要关注业务逻辑,框架会处理底层的复杂性。现在就开始为您的Wasp应用添加支付功能,让您的产品实现商业化变现!

记住,良好的支付体验是用户信任的基础,投入时间优化支付流程将为您的应用带来长期的商业价值。

【免费下载链接】wasp The fastest way to develop full-stack web apps with React & Node.js. 【免费下载链接】wasp 项目地址: https://gitcode.com/GitHub_Trending/wa/wasp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值