PAYATHON 2026

如何为 headless iOS app 实现 BigCommerce checkout

支付阿杰

结论

可以把用户从 headless iOS app 导向 BigCommerce 托管的 checkout 页面完成结账。项目周期有限时,这通常是更合适的方案。商品、分类和购物车仍由自建后端通过 BigCommerce API 管理。进入支付阶段后,后端生成购物车对应的 checkout redirect URL,iOS 再用 SFSafariViewController 或系统浏览器打开。

只要销售的是实体商品或在 App 外消费的服务,这种结账方式原则上符合 Apple 的支付规则,通常不需要使用 In-App Purchase。不过,审核结果还取决于商品类型、页面设计以及当时适用的 App Review Guidelines,无法保证 Apple 一定批准。

BigCommerce 托管 checkout 能大幅缩小 PCI DSS 范围,但不等于完全不需要 PCI compliance。如果 app 和自建服务器从不接收、记录或转发卡号、CVV 等支付卡数据,商户通常只需承担较轻的合规责任,例如确认自身是否符合 SAQ A 的适用条件。具体范围仍应由收单机构、支付服务商或 PCI 专业人员确认。

推荐的实现方式

可以按以下流程实现:

  1. iOS app 调用自建后端,创建或更新 BigCommerce cart。
  2. 自建后端通过 BigCommerce Carts API 为该 cart 创建 redirect URL。
  3. 后端只向 app 返回 checkout_url。
  4. app 使用 SFSafariViewController 或外部 Safari 打开该 URL。
  5. 用户在 BigCommerce 托管页面填写地址、选择配送方式并付款。
  6. 后端通过 BigCommerce webhook 或订单查询确认最终订单状态。

不要把 BigCommerce store API token 放进 iOS app。创建购物车、生成 checkout URL、查询订单和验证 webhook,都应该在受控的服务端完成。

服务端生成 checkout URL

BigCommerce 提供了为购物车创建 redirect URL 的接口。典型请求如下:

POST https://api.bigcommerce.com/stores/{store_hash}/v3/carts/{cart_id}/redirect_urls
X-Auth-Token: {access_token}
Accept: application/json
Content-Type: application/json

响应通常包含购物车和 checkout 的访问 URL。服务端只需把 checkout 地址返回给客户端:

{
  "checkoutUrl": "https://store.example.com/checkout.php?action=loadInCheckout&id=..."
}

下面是简化后的 Node.js 示例:

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/carts/:cartId/checkout-url", async (req, res) => {
  const cartId = req.params.cartId;

  const response = await fetch(
    `https://api.bigcommerce.com/stores/${process.env.BC_STORE_HASH}` +
      `/v3/carts/${encodeURIComponent(cartId)}/redirect_urls`,
    {
      method: "POST",
      headers: {
        "X-Auth-Token": process.env.BC_ACCESS_TOKEN,
        Accept: "application/json",
        "Content-Type": "application/json"
      }
    }
  );

  if (!response.ok) {
    const details = await response.text();

    return res.status(502).json({
      error: "Unable to create BigCommerce checkout URL",
      details
    });
  }

  const result = await response.json();
  const checkoutUrl = result.data?.checkout_url;

  if (!checkoutUrl) {
    return res.status(502).json({
      error: "BigCommerce did not return a checkout URL"
    });
  }

  res.json({ checkoutUrl });
});

具体的响应字段和 URL 使用限制可能会随 BigCommerce API 版本或店铺配置变化,应以当前 BigCommerce API 文档和实际响应为准。不要长期缓存 redirect URL。用户每次开始结账时,再由后端按需生成会更稳妥。

在 iOS 中打开 checkout

对于普通的托管结账页面,建议优先使用 SFSafariViewController,不要自行实现完整的 WKWebView 支付容器。

import SafariServices
import UIKit

final class CheckoutViewController: UIViewController {
    func openCheckout(urlString: String) {
        guard let url = URL(string: urlString),
              url.scheme == "https" else {
            return
        }

        let safari = SFSafariViewController(url: url)
        safari.dismissButtonStyle = .close
        present(safari, animated: true)
    }
}

也可以直接跳转到外部 Safari:

if let url = URL(string: checkoutUrl) {
    UIApplication.shared.open(url)
}

SFSafariViewController 使用系统提供的浏览器环境。相比自定义 WKWebView,它通常能更可靠地处理 Cookie、密码管理器、3-D Secure 跳转和部分第三方支付认证。

有些支付提供商明确禁止在嵌入式 WebView 中完成认证,或者会阻止用户在 WebView 中登录。上线前应测试实际使用的支付方式和以下场景:

  • 3-D Secure 验证;
  • PayPal 等站外授权;
  • 银行 App 跳转及返回;
  • Apple Pay;
  • checkout 完成页;
  • 用户取消、关闭页面或支付失败。

如何确认结账完成

不要只根据浏览器显示的“支付成功”就把订单标记为已付款。用户可能会关闭页面,支付也可能仍处于授权、审核或异步处理状态。

更可靠的处理方式包括:

  • 监听 BigCommerce order webhook;
  • 由自建后端查询订单和支付状态;
  • 以服务端获取的订单状态为最终依据;
  • app 返回前台后,调用后端刷新购物车或订单状态。

如果希望用户付款后自动返回 app,可以研究自定义 checkout success page、Universal Links 或 URL Scheme。不过,BigCommerce 店铺主题、checkout 定制能力和支付网关都可能影响跳转行为。即使实现了回跳,也必须由服务端验证订单,不能信任客户端传入的 orderId 或“支付成功”参数。

Apple 审核方面

审核的重点是所售内容,而不是支付页面是否由 BigCommerce 提供。

如果销售服装、食品等实体商品,或其他需要线下交付的商品,Apple 通常允许使用信用卡、Apple Pay 或第三方网页 checkout,不要求使用 In-App Purchase。通过 SFSafariViewController 打开 BigCommerce checkout,通常不会单独构成拒审理由。

如果销售的是在 App 内解锁或消费的数字内容、订阅、功能或虚拟商品,规则则完全不同。把付款页面放到 BigCommerce 并不能绕过 Apple 的 In-App Purchase 规则,还可能导致审核被拒。不同地区、App 类别和 entitlement 也可能适用额外规则,因此提交前应核对当时有效的 App Review Guidelines。

提交审核时还应注意:

  • 在 App 中清楚说明所购商品和履约方式;
  • 不要把网页 checkout 伪装成 Apple 的购买界面;
  • 为审核人员提供测试账号和完整的操作说明;
  • 确保 checkout 页面适配移动端;
  • 提供隐私政策、退款说明和客服入口;
  • 不要在 app 或日志中收集卡号、CVV 等敏感数据。

PCI DSS 的实际边界

PCI DSS 的适用范围主要取决于支付卡数据会经过哪些系统,而不是是否调用了 Checkout API。

采用以下方式时:

  • app 只接收 BigCommerce checkout URL;
  • 用户直接在 BigCommerce 托管页面输入卡信息;
  • 自建后端不接触卡号和 CVV;
  • app 不注入脚本读取支付表单;
  • 日志、埋点和崩溃报告不采集支付字段;

支付卡数据通常不会进入自有环境,PCI 范围也会明显缩小。

如果改为在原生页面收集卡号,再把数据发送到自建服务器或 BigCommerce Payments API,自建系统就可能进入更严格的 PCI 范围。即使使用 HTTPS 传输且不保存卡号,也不代表已经满足 PCI DSS。

部分支付网关提供官方移动 SDK,可以直接在设备端对卡信息进行 tokenization,再由服务器处理 token。这能减少敏感数据暴露,但究竟适用 SAQ A、SAQ A-EP 还是其他要求,仍取决于 SDK、数据流、商户集成方式和收单机构的认定,不能仅凭“使用 token”得出结论。

能否绕过 BigCommerce 使用其他支付网关

技术上可以,但有两种不同的做法。

第一种是使用 BigCommerce 支持的 payment gateway。订单、税费、配送、优惠和支付状态仍由 BigCommerce checkout 流程协调。这种方案通常更容易维护,应优先考虑。

第二种是完全绕开 BigCommerce 收款。例如,自建后端调用另一家支付服务商,支付成功后再通过 BigCommerce API 创建或更新订单。这种方案也能实现,但需要自行处理更多一致性问题:

  • 防止客户端篡改商品价格和优惠金额;
  • 在服务端重新计算并校验订单总额;
  • 管理库存占用和超卖;
  • 处理税费、运费和优惠券;
  • 避免出现支付成功但订单创建失败的情况;
  • 使用幂等键防止重复扣款或重复下单;
  • 同步授权、捕获、退款、拒付和取消状态;
  • 避免 BigCommerce 再次尝试收款;
  • 确保后台订单的支付状态与实际交易一致。

如果第三方网关通过托管页面或官方 SDK 直接采集支付信息,自建系统的 PCI 范围仍可能较小。如果卡数据会经过 app 自己的表单或服务器,则需要重新评估 PCI 范围。

在时间和合规资源有限的情况下,使用 BigCommerce 托管 checkout,并选择 BigCommerce 已支持的支付网关,是更稳妥的路线。只有托管 checkout 无法满足业务需求时,再考虑原生支付 SDK 或完全独立的支付流程。

备注:内容仅供参考。