PAYATHON 2026

如何在 BigCommerce 自定义 checkout page 和支付网关?

支付老李

结论

不能只靠 JavaScript 在 BigCommerce Checkout 中加入支付选项。支付方式不只是页面上的按钮,还必须对应一个已在 BigCommerce 后端注册并启用、能够处理订单的支付提供商。强行在前端插入按钮最多只能把用户带到支付页面,Checkout SDK 无法据此创建支付记录、验证交易或推进订单状态。

通常有三种可行方案:

  1. 开发正式的 BigCommerce 支付集成,并完成相应的接入或审核流程。
  2. 使用 BigCommerce 已支持的支付网关。
  3. 完全接管结账流程,由自己的后端创建订单、发起支付跳转、验证回调并处理购物车。这种方案的实现成本和风险都更高。

无论选择哪种方案,都不能根据用户是否回到网站来判断支付成功,也不应在跳转前清空购物车。支付结果必须由服务端通过回调或主动查询确认。

为什么通过 JavaScript 添加支付选项无法工作

BigCommerce Checkout 显示哪些支付方式,取决于后端返回的可用支付方法。正常的支付流程通常包括:

  1. BigCommerce 根据店铺配置、币种、地区和订单信息返回可用支付方式。
  2. Checkout SDK 加载所选支付方式对应的支付策略。
  3. 后端创建订单或支付会话。
  4. 支付提供商处理付款。
  5. BigCommerce 根据支付结果更新订单状态。

通过 DOM 操作添加的单选项或按钮,既没有对应的支付策略,也没有合法的支付方法标识。即使用户可以点击并跳转,BigCommerce 仍然无法:

  • 创建订单并建立关联;
  • 记录交易;
  • 验证付款结果;
  • 处理失败、取消或重复付款;
  • 更新库存和订单状态;
  • 显示正确的订单确认页面。

自定义 Checkout 页面只能改变界面和流程编排,不能让店铺获得注册任意支付网关的能力。

推荐实现:正式支付集成

如果需要让自定义网关和其他支付方式一样出现在 BigCommerce Checkout 中,应将其开发为平台认可的支付提供商,而不是修改 Checkout DOM。

典型流程如下:

Checkout 加载可用支付方式
        ↓
用户选择 Custom Gateway
        ↓
BigCommerce/集成后端创建待支付订单
        ↓
后端向网关创建 payment session
        ↓
浏览器重定向到网关
        ↓
网关通过 webhook 通知支付结果
        ↓
后端验证签名和金额
        ↓
更新 BigCommerce 订单状态
        ↓
用户返回订单确认页

店铺能否自行注册支付提供商、需要哪些合作资质,以及目前开放哪些接口,可能取决于店铺方案、结账架构和 BigCommerce 的合作政策。实施前应向 BigCommerce 支持或支付合作团队确认:

  • 该店铺是否允许接入自定义 payment provider;
  • 是否需要成为支付合作伙伴;
  • 是否提供测试店铺和沙箱认证流程;
  • 是否支持店铺当前使用的 Optimized One-Page Checkout;
  • 是否允许通过 Payments API 处理该支付方式;
  • 对 PCI DSS、3-D Secure、退款和争议处理有哪些要求。

BigCommerce Payments API 通常用于处理平台已经识别并配置的支付方式。这不代表调用方可以传入任意自定义网关名称。

备选方案:接管外部支付流程

如果无法开发正式的支付集成,可以搭建自己的结账服务。但不要先将用户带到支付页面,再在付款后临时拼装订单。更可靠的处理顺序是:

  1. 通过 Checkout API 获取商品、地址、配送、折扣、税费和总金额。
  2. 在服务端创建待支付订单,并保存 order_id。
  3. 向支付网关创建支付会话,将 order_id 写入不可篡改的商户参考字段。
  4. 将用户重定向到支付网关。
  5. 通过 webhook 接收支付结果。
  6. 校验签名、商户号、币种、金额和交易状态。
  7. 以幂等方式更新订单。
  8. 将返回的用户引导到订单结果页。
  9. 确认订单成功后再处理购物车。

下面是简化的服务端伪代码:

app.post('/checkout/start-payment', async (req, res) => {
  const checkout = await loadCheckoutFromBigCommerce(req.body.checkoutId);

  // 金额必须从可信的服务端数据获取,不能采用浏览器提交的金额。
  const order = await createPendingOrder(checkout);

  const paymentSession = await customGateway.createPayment({
    amount: checkout.grandTotal,
    currency: checkout.currency.code,
    merchantReference: String(order.id),
    returnUrl: `${APP_URL}/payment/return?order_id=${order.id}`,
    webhookUrl: `${APP_URL}/webhooks/custom-gateway`
  });

  res.json({
    redirectUrl: paymentSession.redirectUrl
  });
});

支付回调负责最终确认:

app.post('/webhooks/custom-gateway', async (req, res) => {
  const event = verifyGatewayWebhook(req);

  if (!event.valid) {
    return res.sendStatus(401);
  }

  const order = await loadBigCommerceOrder(event.merchantReference);

  if (
    event.currency !== order.currency_code ||
    event.amount !== order.total_inc_tax
  ) {
    return res.sendStatus(400);
  }

  if (await transactionAlreadyProcessed(event.transactionId)) {
    return res.sendStatus(200);
  }

  if (event.status === 'paid') {
    await markOrderAsPaid(order.id, event.transactionId);
  } else if (event.status === 'failed') {
    await markOrderPaymentFailed(order.id, event.transactionId);
  }

  res.sendStatus(200);
});

markOrderAsPaid 和 markOrderPaymentFailed 只是示意名称。实际使用的订单状态 ID、交易记录写入方式以及可调用的 API,需要根据店铺配置和 BigCommerce 当前开放的接口确定,不能假定某个固定状态 ID 始终适用。

返回网站后如何清空购物车

最稳妥的做法是让 BigCommerce 的标准下单流程完成购物车到订单的转换。这样,购物车、订单确认页和结账状态都由平台统一处理,无需手动修改浏览器数据。

如果使用完全自定义的外部流程,应在满足以下两个条件后再清空购物车:

  • 服务端已经验证支付成功;
  • BigCommerce 订单已经成功创建或更新。

在同域 Storefront 环境中,可以通过 Storefront Cart API 删除指定购物车。示意代码如下:

async function deleteCart(cartId) {
  const response = await fetch(
    `/api/storefront/carts/${encodeURIComponent(cartId)}`,
    {
      method: 'DELETE',
      credentials: 'same-origin'
    }
  );

  if (!response.ok && response.status !== 404) {
    throw new Error(`Unable to delete cart: ${response.status}`);
  }
}

不能因为用户访问了返回 URL 就执行删除操作。返回页面可以被伪造,用户也可能在支付完成前关闭页面。建议按以下流程处理:

网关 webhook 确认付款
        ↓
服务端将订单标记为已支付
        ↓
返回页向服务端查询订单结果
        ↓
服务端确认该订单属于当前 checkout/cart
        ↓
删除或重置购物车

还需要处理多标签页的情况。用户可能在支付期间修改了购物车,如果无条件删除当前购物车,后来加入的商品也可能被一并删除。因此,创建支付会话时应保存原始 cart_id 或 checkout_id,支付成功后只处理与该订单关联的购物车。

返回页不能用于验证付款

下面的做法不安全:

const params = new URLSearchParams(location.search);

if (params.get('status') === 'success') {
  await deleteCart(cartId);
}

用户可以修改查询参数。返回页应向自己的后端查询支付状态:

const response = await fetch(
  `/api/payment-status?order_id=${encodeURIComponent(orderId)}`,
  { credentials: 'same-origin' }
);

const result = await response.json();

if (result.status === 'paid') {
  await deleteCart(result.cartId);
  location.assign(result.confirmationUrl);
}

后端应根据 webhook 已验证的交易记录返回结果,不能信任前端提供的 status、金额或交易编号。

实施时还需注意

  • 使用幂等键或网关交易号,避免 webhook 重试导致重复处理。
  • 核对订单金额、币种、商户号和订单引用,不能只检查“支付成功”字段。
  • 明确定义 pending、paid、failed、cancelled 和 refunded 对应的订单状态。
  • 不要在浏览器中保存网关密钥或 BigCommerce 管理 API Token。
  • 信用卡数据应由合规的托管支付页面或 tokenization 组件采集。
  • 用户付款成功后可能不会返回网站,业务结果应以 webhook 为准。
  • 用户返回网站时,webhook 可能尚未到达。结果页可以短暂轮询服务端。
  • 如果自行通过 Orders API 创建订单,需要确认税费、优惠券、运费、库存和订单邮件没有遗漏,也不会被重复处理。
  • Checkout SDK 可以定制结账体验,但不能绕过后端对支付方法的注册和验证。

如果网关必须原生出现在 BigCommerce 的支付选项中,应开发正式的支付提供商集成。如果只是短期接入外部支付页,则应先创建待支付订单,由服务端验证回调,最后再处理购物车。仅修改 Checkout 页面的 JavaScript,无法构成完整可靠的支付集成。

备注:内容仅供参考。