PAYATHON 2026

如何选择易于集成的 payment gateway?

支付小周

结论

如果希望尽快、安全地接入信用卡支付,应优先考虑具备以下能力的支付网关:

  • 托管收银台或官方支付组件
  • 成熟的服务端 SDK
  • 完整的测试环境
  • Webhook 通知
  • 退款、对账和争议处理接口
  • 支持目标国家、币种和结算账户

只要服务范围符合要求,Stripe 通常比较容易集成。需要处理复杂的企业支付、多个收单机构或全球本地支付方式时,可以评估 Adyen。如果项目已经使用 PayPal 生态,也可以考虑 Braintree。对于中国大陆业务,应结合经营主体、结算地区和支付场景,选择支持银行卡收单的持牌支付机构或聚合支付服务商。

没有一个 payment gateway 适合所有项目。费率当然需要考虑,但接入难度、支付成功率、风控、结算周期和售后支持,往往更直接地影响日常运营。

先确认支付网关是否适合你的业务

选型前,至少需要确认以下问题:

  1. 服务地区
    服务商是否接受你的公司注册地,能否把资金结算到你的银行账户。

  2. 客户所在地区
    是否支持客户常用的信用卡品牌、币种和本地支付方式。

  3. 支付模式
    业务需要一次性付款、订阅扣款、平台分账,还是保存卡片供以后使用。不同模式可能对应不同的产品和资质要求。

  4. 合规要求
    是否支持 3-D Secure、强客户认证,以及业务所在地要求的身份验证、税务和消费者保护流程。

  5. 行业限制
    数字商品、金融服务、成人内容、博彩和虚拟资产等行业通常受到额外限制。接入前,应查看服务商最新的受限业务政策。

  6. 费用和结算
    除交易费之外,还要了解跨境费、换汇费、退款费、拒付费、最低收费、保证金和结算周期。

服务地区、费率和业务限制可能随时间变化,最终应以服务商当前的官方文档和合同为准。

为什么优先使用托管收银台

最省事的做法通常不是自己开发信用卡输入框,而是使用支付服务商提供的 Hosted Checkout。常见流程如下:

Browser
  │
  │ 创建订单
  ▼
Application Server
  │
  │ 创建 Checkout Session
  ▼
Payment Gateway
  │
  │ 返回收银台 URL
  ▼
Browser redirects to Hosted Checkout
  │
  │ 用户完成支付
  ▼
Payment Gateway sends Webhook
  │
  ▼
Application Server marks order as paid

这种方式的好处很直接:

  • 信用卡号通常不会经过你的服务器。
  • PCI DSS 合规范围相对较小,但不代表完全没有合规责任。
  • 3-D Secure、验证码和部分风控流程可以交给服务商处理。
  • 支付页面和 SDK 所需的维护工作更少。
  • 前端实现错误导致敏感卡片数据泄露的风险更低。

除非业务确实需要高度定制的结账体验,否则不要直接接收或保存完整卡号、有效期和 CVV。

推荐的实施步骤

1. 建立自己的订单记录

用户点击付款后,先在数据库中创建订单,并保存:

  • 内部订单号
  • 金额
  • 币种
  • 用户 ID
  • 商品信息
  • 支付状态
  • 网关返回的 session ID 或 payment ID

金额必须由服务端根据商品和优惠规则重新计算,不能直接采用浏览器提交的金额。

const order = await db.orders.create({
  userId: currentUser.id,
  amount: 2999,
  currency: "usd",
  status: "pending"
});

金额通常以最小货币单位表示。例如,2999 美分代表 29.99 USD。不过,并非所有币种都有两位小数,具体规则应查看网关文档。

2. 在服务端创建支付会话

下面用 Stripe Checkout 的 Node.js 写法说明基本结构。这段代码主要展示集成方式,实际使用时,应按照当前 SDK 文档安装依赖并核对参数。

import express from "express";
import Stripe from "stripe";

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

app.use(express.json());

app.post("/api/checkout", async (req, res) => {
  const order = await loadOrderForCurrentUser(req);

  if (!order || order.status !== "pending") {
    return res.status(400).json({ error: "Invalid order" });
  }

  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items: [
      {
        price_data: {
          currency: order.currency,
          unit_amount: order.amount,
          product_data: {
            name: order.productName
          }
        },
        quantity: 1
      }
    ],
    metadata: {
      orderId: String(order.id)
    },
    success_url:
      "https://example.com/payment/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://example.com/payment/cancelled"
  });

  await saveGatewaySessionId(order.id, session.id);

  res.json({ checkoutUrl: session.url });
});

密钥必须保存在服务端环境变量中:

STRIPE_SECRET_KEY=your_secret_key

不要把 secret key 放进前端代码、Git 仓库或任何可以公开下载的配置文件。

3. 从前端跳转到收银台

async function startCheckout(orderId) {
  const response = await fetch("/api/checkout", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ orderId })
  });

  if (!response.ok) {
    throw new Error("Unable to create checkout session");
  }

  const { checkoutUrl } = await response.json();
  window.location.href = checkoutUrl;
}

服务端仍然要验证当前用户是否有权支付这笔订单。不能只根据前端传来的 orderId 查找并操作任意订单。

4. 使用 Webhook 确认支付结果

浏览器跳转到 success_url,不能作为付款成功的依据。用户可能在付款后关闭页面,攻击者也可以直接访问成功页面。

更可靠的方式是接收支付网关发送的 Webhook,并验证签名:

app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.headers["stripe-signature"],
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch {
      return res.status(400).send("Invalid webhook signature");
    }

    if (event.type === "checkout.session.completed") {
      const session = event.data.object;
      const orderId = session.metadata?.orderId;

      await markOrderPaidIdempotently({
        orderId,
        gatewaySessionId: session.id,
        gatewayEventId: event.id
      });
    }

    res.sendStatus(200);
  }
);

在 Express 中,Webhook 路由通常需要读取原始请求体。应把它放在全局 express.json() 之前,或者单独调整该路由的中间件,否则签名验证可能失败。

Webhook 处理必须具备幂等性。同一个事件可能被多次发送,系统不能因此重复发货、重复充值或多次发放权益。

比较服务商时不要只看 API

可以从三到五个候选服务商中做一张评分表:

评估项建议关注内容
地区覆盖公司注册地、客户地区和结算银行是否支持
支付方式Visa、Mastercard、American Express 及所需本地方式
开发体验SDK、测试卡、错误信息、Webhook 调试工具
支付能力一次性付款、订阅、退款、预授权、分账
安全合规PCI DSS、3-D Secure、令牌化和风控能力
成本交易、跨境、换汇、退款、拒付及月费
资金管理结算周期、保证金、提现币种和对账文件
运营支持中文支持、响应时间、争议和拒付协助
稳定性状态页面、故障通知和接口变更政策

如果两个网关的功能比较接近,可以各做一个最小原型,实际测试创建支付、支付失败、退款和 Webhook 重试等流程。文档简单易懂,并不代表异常处理和对账同样容易。

上线前必须检查的事项

  • 全站使用 HTTPS。
  • 不记录卡号、CVV、支付密钥或完整的支付请求。
  • 测试环境和生产环境使用不同的密钥。
  • 限制密钥权限,并建立轮换机制。
  • 由服务端校验金额、币种、订单归属和订单状态。
  • 使用 idempotency key,防止网络重试造成重复支付。
  • 验证所有 Webhook 的签名。
  • 对 Webhook 事件去重,并允许失败后安全重试。
  • 不要只在浏览器中保存订单 ID、支付状态或金额。
  • 为退款、部分退款、拒付和支付超时设计相应状态。
  • 保存网关交易 ID,方便对账和客服查询。
  • 正式上线前,完成服务商要求的身份、企业和银行账户验证。

实际选型建议

如果普通 Web 应用只需要一次性信用卡付款,可以先采用”托管收银台 + 服务端创建会话 + Webhook 确认结果”的方案。只要目标地区和业务类型在服务范围内,Stripe 通常值得优先测试。

如果业务覆盖大量国家,需要接入多种本地支付方式、复杂的收单路由或企业级资金管理,可以把 Adyen 列入候选名单。已经深度使用 PayPal 的项目,可以同时测试 Braintree。业务集中在特定国家或地区时,本地支付机构可能更适合当地的支付成功率、结算和客服需求。

选型仍要回到真实的业务条件。先排除无法服务公司注册地、所属行业或结算账户的提供商,再比较集成成本、支付成功率、综合费率和运营支持。这样的结果通常比单纯比较哪个 API 最简单更可靠。

备注:内容仅供参考。