PAYATHON 2026

Payment Request API 能否添加“货到付款”选项?

支付阿杰

结论

不能只靠 methodData 在 Payment Request API 中添加“货到付款”(Cash on Delivery)选项。

methodData 用于声明网站接受的支付方式,并向相应的支付处理器传递配置。它不会创建新的支付方式,也不能让浏览器把任意字符串显示为付款选项。

普通电商网站通常应在自己的结账页面中提供货到付款。用户选定后,网站后端直接创建 COD 订单,不需要调用 Payment Request API。

为什么 methodData 不能直接实现

Payment Request API 不处理资金,也不定义网站的付款流程。浏览器会根据 supportedMethods 查找对应的支付处理器,例如:

const methodData = [
  {
    supportedMethods: "https://example.com/pay"
  }
];

这里的 URL 不是显示名称,而是支付方式标识符。使用它需要相应的支付处理器、清单和浏览器支持。

因此,下面的代码不能直接添加货到付款:

const methodData = [
  {
    supportedMethods: "cash-on-delivery"
  }
];

cash-on-delivery 不能作为普通展示选项任意注册。实际结果取决于浏览器实现。浏览器可能在创建 PaymentRequest 时因标识符无效而报错,也可能在调用 show() 时因为找不到可用的支付处理器而失败。

推荐实现方式

货到付款应放在网站自己的结账流程中:

  1. 用户填写或选择收货地址。
  2. 后端判断该地址是否支持货到付款。
  3. 前端显示“货到付款”选项。
  4. 用户确认后,将付款方式提交为 cash_on_delivery。
  5. 后端校验商品、库存、配送范围和 COD 资格,然后创建订单。
  6. 订单的初始状态应为“待发货”或“待收款”,不能记为“已支付”。

例如:

<form id="checkout-form">
  <label>
    <input
      type="radio"
      name="payment_method"
      value="cash_on_delivery"
      required
    >
    货到付款
  </label>

  <button type="submit">提交订单</button>
</form>
const form = document.querySelector("#checkout-form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  const formData = new FormData(form);
  const paymentMethod = formData.get("payment_method");

  const response = await fetch("/api/orders", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      paymentMethod
    })
  });

  if (!response.ok) {
    throw new Error("订单创建失败");
  }

  const order = await response.json();
  window.location.href = `/orders/${order.id}`;
});

后端必须再次验证付款方式,不能直接信任前端提交的值。下面是一个简化示例:

app.post("/api/orders", async (req, res) => {
  const { paymentMethod } = req.body;

  if (paymentMethod !== "cash_on_delivery") {
    return res.status(400).json({
      error: "Unsupported payment method"
    });
  }

  const eligible = await checkCashOnDeliveryEligibility(req.user);

  if (!eligible) {
    return res.status(400).json({
      error: "Cash on delivery is not available"
    });
  }

  const order = await createOrder({
    userId: req.user.id,
    paymentMethod: "cash_on_delivery",
    paymentStatus: "pending"
  });

  res.status(201).json(order);
});

能否通过自定义支付处理器实现

理论上,可以使用基于 URL 的支付方式标识符,并实现配套的 Payment Handler。之后再通过 methodData 引用该标识符:

const methodData = [
  {
    supportedMethods: "https://pay.example.com/cash-on-delivery"
  }
];

const details = {
  id: "order-123",
  displayItems: [
    {
      label: "商品金额",
      amount: {
        currency: "CNY",
        value: "99.00"
      }
    }
  ],
  total: {
    label: "应付金额",
    amount: {
      currency: "CNY",
      value: "99.00"
    }
  }
};

const request = new PaymentRequest(methodData, details);

不过,这项方案并非增加一条配置就能完成。它还需要支付方式清单、可安装或已经安装的支付处理器,并依赖目标浏览器对相关机制的支持。不同浏览器和运行环境的支持情况可能不同,所以它不适合作为通用的 COD 实现方案。

如果还要支持在线支付

可以先让用户在结账页选择付款方式:

async function submitCheckout(paymentMethod) {
  if (paymentMethod === "cash_on_delivery") {
    return createCashOnDeliveryOrder();
  }

  if (paymentMethod === "online") {
    return startOnlinePayment();
  }

  throw new Error("Unsupported payment method");
}

在线支付分支可以调用 Payment Request API,货到付款分支则直接创建订单。这样更符合 COD 的实际业务流程,也不会把创建订单错误地建模为浏览器支付处理器。

注意事项

  • 后端应根据配送地址、订单金额、商品类型和用户风险状态判断订单是否支持货到付款。
  • COD 订单创建时不能标记为已支付。收到款项后,再更新支付状态。
  • 不要用虚假的在线支付成功结果来模拟货到付款。
  • 订单金额、运费和 COD 服务费都必须由后端计算并校验。
  • 提交接口需要处理重复请求,避免用户多次点击后生成多个订单。
  • 只有在明确了解目标浏览器的兼容性,并且能够维护完整支付处理器的情况下,才值得考虑自定义 Payment Handler。对于一般网站,把货到付款作为独立的结账选项更简单,也更可靠。

备注:内容仅供参考。