返回知识中心
平台

Payment Request API:用户同意与安全回退

围绕明确用户激活、安全上下文、取消处理和可用回退使用 PaymentRequest,不收集支付凭据。

BotBrowser Team

文档中心

想直接进入 平台 文档吗?

这篇文章属于知识中心的指南库。若你要步骤化配置、参考说明和持续更新,请直接进入对应 docs 分区。

Payment Request 可以打开由浏览器控制的支付面板,但 show() 完成并不能证明支付已获授权,也不能证明商户收到资金。BotBrowser 支持重放自有 fixture 并检查可见状态;它不能提供真实支付工具、批准银行交易或处理用户凭据。安全边界应明确:在安全上下文中从明确用户操作发起请求,不把能力检查当成同意,分别处理取消和超时,只把 complete() 用于面板的最终 UI 状态,并在 API 或支付数据不可用时保留普通结账路径。

Payment Request 从用户操作经过支付面板进入脱敏结果或结账回退。

TL;DR

  • new PaymentRequest(methodData, details, options) 描述支持的方法和交易细节;它不会请求凭据,也不保证某个方法存在。
  • canMakePayment() 只是能力提示。结果为 true 不代表同意、授权、有资金,或保证 show() 会成功。
  • 在安全上下文中从可见点击或键盘操作调用 show()。把 AbortError、NotAllowedError、超时和不支持的方法当作普通产品状态处理。
  • 验证 response 后,用 complete('success')、complete('fail') 或 complete('unknown') 关闭面板。complete() 报告 UI 结果,不报告结算。
  • 绝不要记录支付凭据、支付 response、卡片数据或完整异常。保留手动表单、发票请求或其他非 PaymentRequest 路径。

目录

分开能力与同意

Payment Request API 允许页面描述支付方法,并请求浏览器中介用户选择。它不是支付处理器的替代品,也不是读取卡片或钱包账户的方法。浏览器负责支付面板,用户决定是否继续。商户仍需要服务器端授权、幂等性、防欺诈控制,以及支付提供方给出的结算结果。

canMakePayment() 可以帮助决定是否显示快速支付入口。它可能根据浏览器策略、已登记方法、隐私保护和实现返回 false、true 或拒绝。结果可能在不同访问之间变化。不要用它隐藏完整结账、推断用户钱包或创建稳定的能力 profile。只在用户选择支付路径时请求,并让结果短期有效。

功能检测同样应保持边界:

const paymentSupported = window.isSecureContext && 'PaymentRequest' in window;

if (!paymentSupported) showCheckoutFallback('unsupported');

构造函数可用不代表每种方法都支持。文档也可能没有焦点、位于受限策略下的嵌入 frame 中,或已经离开短暂用户激活。浏览器更新可能改变可用方法而不改变页面代码。把每次尝试当作新的能力观察,不要把它当作永久的用户属性。

关于相近的同意边界,Web Share 操作与回退说明已完成的交接不代表接收方接受了数据;Web Share 权限边界还说明上下文和近期用户激活同样会限制有效调用。Payment Request 可能把支付 response 暴露给商户,但结算仍由服务器和处理器负责。安全上下文指南介绍强大浏览器功能共同要求的 HTTPS 基线。

构造最小 PaymentRequest

让 method data 和交易细节保持最小。方法标识符告诉浏览器商户可以处理哪类支付方法。details 对象提供显示总额和可选行项目,但不是可信金额。response 返回后,服务器必须重新计算最终金额,因为价格、税、库存和货币不能信任页面提供的值。

const methodData = [
  {
    supportedMethods: 'https://example.test/pay',
    data: { version: 1 },
  },
];

const details = {
  id: 'order-2026-1042',
  total: { label: 'Example order', amount: { currency: 'USD', value: '19.00' } },
  displayItems: [{ label: 'Item', amount: { currency: 'USD', value: '19.00' } }],
};

const options = { requestPayerName: false, requestPayerEmail: false };
const request = new PaymentRequest(methodData, details, options);

不要把卡号、钱包 token、秘密或个人标识放进 methodData 或页面 URL。requestPayerEmail 等选项只是数据请求,不证明返回值已验证。只请求订单确实需要的字段,说明原因,并在处理器收到已授权 token 后停止保留这些字段。

让 request 对象只属于一次结账尝试。如果购物车改变,应使用新的 details.id 和服务器订单版本创建新 request。超时或导航后不要复用旧对象。短期操作 token 可以让界面拒绝属于旧购物车的迟到 response。

从用户激活开始 show

request.show() 会打开浏览器支付面板,并返回一个包含 PaymentResponse 的 promise。应直接从可见按钮或键盘激活开始。不要在页面加载、定时器、分析回调中调用,也不要先等待可能消耗短暂激活的无关工作。安全上下文、浏览器策略、iframe 边界或平台规则也可能阻止面板。

let activeAttempt = 0;

async function beginPayment() {
  const attempt = ++activeAttempt;
  if (!window.isSecureContext || !('PaymentRequest' in window)) {
    showCheckoutFallback('unsupported');
    return;
  }
  const request = buildRequest();
  showPaymentState('opening');
  try {
    const response = await request.show();
    if (attempt !== activeAttempt) return;
    await handlePaymentResponse(response, request, attempt);
  } catch (error) {
    if (attempt !== activeAttempt) return;
    showPaymentState(error?.name === 'AbortError' ? 'cancelled' : 'rejected');
    showCheckoutFallback(error?.name === 'AbortError' ? 'cancelled' : 'rejected');
  }
}

payButton.addEventListener('click', beginPayment);

面板打开时可以禁用重复激活,但必须保留可见替代路径。取消或拒绝后把焦点还给发起按钮。通过 live region 播报 opening、cancelled 和 fallback,不要暴露 response。用户应在不看到浏览器异常的情况下理解下一步。

嵌入式结账需要明确部署决定。顶层策略或 iframe 属性可能影响浏览器是否允许支付功能,但委托不是绕过权限,也不会替用户授予同意。分别测试同 origin 和嵌入路径。不要假设顶层标签页可用的功能在跨 origin frame 中仍然可用。

验证 response 并完成面板

PaymentResponse 包含方法专属数据、请求的 payer 字段以及 complete() 方法。验证 response 形状后,只通过经过认证的通道把处理器批准的 token 发送到服务器。绝不要把 response 当成收据。处理器可能拒绝、延迟或之后撤销授权。

async function handlePaymentResponse(response, request, attempt) {
  showPaymentState('validating');
  let outcome = 'unknown';
  try {
    const result = await postPaymentToken({
      methodName: response.methodName,
      details: response.details,
      orderId: request.id,
      attempt,
    });
    outcome = result.authorized === true ? 'success' : 'fail';
  } catch {
    outcome = 'unknown';
  }
  try {
    await response.complete(outcome);
  } catch {
    // The payment sheet may already have closed; keep the server outcome.
  }
  showPaymentState(outcome);
}

对每个 response 只调用一次 complete()。success 表示页面请求浏览器显示成功完成状态,fail 表示失败尝试,服务器结果不可用时使用 unknown。这些字符串都不能证明扣款、退款或交付。权威状态来自处理器服务器 response 和订单的幂等记录。

如果 response 已关闭、浏览器结束了面板或文档失去相关状态,response.complete() 可能拒绝。不要把拒绝变成第二次扣款尝试。保留服务器结果,显示会创建新订单尝试的重试路径,并避免发送同一 token 两次。

区分取消、拒绝和超时

取消不是卡片失败。用户可以关闭面板、改变想法或拒绝某种方法。AbortError 通常表示取消,但不会透露原因。把 cancelled 与 rejected、unsupported 和 processor-failed 分开。保留购物车并提供手动表单,不要在诊断消息中索要支付凭据。

超时需要明确负责人。页面可以为服务器调用设置应用截止时间,但不能假设定时器证明支付面板做了什么。如果收到 response 后服务器请求超时,标记为 unknown,停止重复提交,并在重试前与处理器核对订单。迟到 callback 不能覆盖新的尝试。

方法错误与浏览器错误不同。缺少方法可能让 show() 在面板出现前拒绝。策略或激活阻止可能产生 NotAllowedError。格式错误的 response 属于验证阶段,处理器拒绝属于服务器阶段。让 UI 和支持记录保留这些阶段,这样用户不会因 iframe 策略问题被要求重新输入卡片。

使用一条有界状态序列:idle、opening、shown、response-received、authorizing、success、fail、unknown、cancelled、rejected、unsupported 和 fallback。每次尝试只记录第一个失败阶段。面板关闭后不要自动重试 show(),也不要从后台清理任务调用 abort(),除非产品明确拥有这个取消动作。

数据边界与回退

支付 response 可能包含敏感的方法 token、payer 资料、账单引用或重放后会成为凭据的不透明值。不要在日志、URL、截图、会话回放、崩溃报告或分析维度中放入 response.details、payer email、卡片片段或完整异常。脱敏也要覆盖 PaymentRequest 调用点之外的集成。

{
  "api": "PaymentRequest",
  "attempt": "pay-7f31",
  "capability": "available",
  "outcome": "cancelled",
  "methodNamePresent": true,
  "paymentDetailsRetained": false,
  "settlement": "not-asserted"
}

这条记录足以回答“面板是否打开,以及页面随后显示了什么结果”,但不能回答用户是否拥有某个账户,也不能回答处理器是否扣款。token 应随机、短期有效,诊断导出应过期,任何支持捕获都必须明确并可复核。不要把支付 token 哈希后就当作无害,稳定值仍可能关联多个 session。

设计回退并区分结算。

第一屏就应保留回退。当 PaymentRequest 不存在、canMakePayment() 拒绝、show() 被取消、方法不支持或处理器 response 未知时,保留普通结账表单、发票请求、支付链接或联系流程。回退必须保留购物车、键盘访问、标签、校验以及下一步说明。

不要因为 canMakePayment() 返回 true 就禁用回退。没有服务器权威处理器结果,不要在 complete('success') 后显示“支付完成”。不要把浏览器面板与 Web Share、Credential Management 或钱包自己的应用 UI 混淆。Payment Request 是浏览器中介的结账界面,不是凭据存储,也不会绕过处理器。

在受控 fixture 中使用合成方法标识符和虚假订单金额。断言没有凭据字段被记录、取消后焦点返回、未知服务器结果调用 complete('unknown'),以及手动路径仍可用。真实支付凭据和支付提供方 endpoint 不应进入浏览器自动化 fixture。

使用决策表。

观察阶段它能证明什么安全下一步
API 不存在或上下文不安全这里不能启动 Payment Request保留手动结账或发票回退
canMakePayment() 返回 false 或拒绝本次没有有用的能力提示保留主要结账,不推断钱包身份
show() 以 AbortError 拒绝面板被取消或关闭保留购物车,记录 cancelled,等待新的动作
策略或激活阻止浏览器拒绝了本次尝试检查安全上下文、焦点、frame 策略和手势负责人
收到 response浏览器把方法数据返回页面严格验证,只向服务器发送获准 token 数据
服务器授权被拒处理器没有授权本次尝试调用 complete('fail'),说明回退,避免重复重试
服务器结果超时结算状态未知条件允许时调用 complete('unknown'),重试前先核对
complete('success') 完成面板接受了 UI 完成请求只显示服务器权威的订单状态

BotBrowser 的能力和限制。

BotBrowser 受控上下文可以重放同 origin 的 PaymentRequest fixture,使用合成方法,运行能力和用户激活分支,模拟取消或超时,并断言脱敏的可见结果和可用回退。高级浏览器能力文档说明了页面自有边界。BotBrowser 不能提供真实支付工具、批准银行或钱包交易、证明结算,或为测试收集支付凭据。

保留确定性的 fixture 记录:

{
  "api": "PaymentRequest",
  "secureContext": true,
  "userActivation": true,
  "canMakePayment": "hint-only",
  "show": "cancelled",
  "complete": "unknown",
  "paymentDetailsRetained": false,
  "fallback": "manual-checkout"
}

每次只改变一个变量后重复测试:浏览器版本、顶层 origin、iframe 策略、激活时间、合成方法、服务器超时或取消分支。比较第一个可见结果差异。通过 fixture 只能证明页面状态处理,不能证明支付授权或资金移动。

来源

实践结论

只把 PaymentRequest 当作浏览器中介的同意界面。构造最小请求,在安全上下文中从新鲜用户操作调用 show(),把 response 交给服务器验证,并让 complete() 只负责面板状态,不声称已结算。区分取消、拒绝、超时和方法不支持,记录脱敏结果,并保留完整的非 PaymentRequest 结账路径。受控 BotBrowser fixture 可以证明页面可见分支,不能证明真实扣款。

#Payment Request API#用户同意#安全上下文#支付#BotBrowser

本文属于指南: Web 平台能力面:一致性复核地图

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。