能力 · API
Payment Request API
发布于
PaymentRequest 把结账面板交给浏览器:页面列出接受的支付方式、商品明细与总额、需要的联系人字段,浏览器从已存的卡片、Apple Pay、Google Pay 或已安装的支付处理程序收集凭据,以 PaymentResponse 返回。页面不渲染卡片表单,除非所选方式主动返回,页面也接触不到原始卡号。
桌面 Chrome 60 与 Android 上的 Chrome 53 发布了该构造函数,Edge 15 与 Safari 11.1 跟进,Android WebView 在 136 加入(BCD api.PaymentRequest)。Safari 只实现一种支付方式:https://apple.com/apple-pay。Firefox 55 的实现藏在 dom.payments.request.enabled 与 dom.payments.request.supportedRegions 之后,没有任何版本默认开启。
new PaymentRequest(methodData, details)new PaymentRequest(methodData, details, options)
request.canMakePayment()request.show()request.show(detailsPromise)request.abort()
response.complete()response.complete(result)response.retry(errorFields)canMakePayment() 返回 Promise<boolean>,不显示任何 UI。show() 返回 Promise<PaymentResponse>,用户授权付款后兑现;abort() 与 complete() 返回 Promise<undefined>,retry() 返回的 Promise 在用户改正被标记的字段后兑现。构造函数带 [SecureContext],并要求 payment Permissions Policy,其默认允许列表为 'self';跨源 iframe 需要 allow="payment"。请求对象一次性使用:show() 调用过一次后 [[state]] 离开 "created",再次 show() 或之后的 canMakePayment() 都会被拒绝。
构造函数接受两个必填字典和一个可选字典。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
methodData |
sequence<PaymentMethodData> |
是 | 每种接受的支付方式一项。supportedMethods 是支付方式标识符,要么是 https://google.com/pay、https://apple.com/apple-pay 这样的 URL,要么是 secure-payment-confirmation 这样的标准化字符串;data 是该方式自己的规范定义的对象(商户 id、允许的卡组织)。列表不能为空,也不能重复同一标识符。 |
details |
PaymentDetailsInit |
是 | total(PaymentItem:label、amount)必填;amount 是 PaymentCurrencyAmount,含 ISO 4217 currency 代码和 "29.99" 这样的十进制字符串 value。可选成员:id(省略时生成 UUID)、displayItems、shippingOptions(id、label、amount、selected),以及按支付方式改动总额的 modifiers(supportedMethods、total、additionalDisplayItems、data)。 |
options |
PaymentOptions |
否 | requestPayerName、requestPayerEmail、requestPayerPhone、requestShipping(布尔,默认 false)与 shippingType("shipping"、"delivery" 或 "pickup",默认 "shipping",只影响面板上的文案)。 |
show(detailsPromise) 接受可选的 Promise<PaymentDetailsUpdate>,让面板在服务端算完购物车之前就能打开。complete(result) 接受 "success"、"fail" 或 "unknown"(默认值)。retry(errorFields) 接受 PaymentValidationErrors 字典,含 error、payer(name、email、phone)、shippingAddress(按地址字段键入的 AddressErrors)与 paymentMethod。
规范定义了下列失败情形。Chrome 附带固定消息的地方引自 components/payments/core/error_strings.cc。
| 异常 | 条件 |
|---|---|
SecurityError |
构造函数:文档不被允许使用 payment Permissions Policy。show():浏览器要求瞬时激活却没有,或对调用限速。 |
TypeError |
构造函数:methodData 为空;缺少 details.total;某个 amount.value 不是合法的十进制金额字符串,或总额为负;两个配送选项共用一个 id。Chrome 另加限制:最多 1024 个配送选项、1024 个 modifier,details.id 最长 1024 字符。 |
RangeError |
构造函数:支付方式标识符既不是合法 URL 也不是标准化字符串、同一标识符出现两次,或 currency 不是格式正确的 ISO 4217 代码。 |
InvalidStateError |
show():文档不是 fully active,或 [[state]] 不是 "created"(Chrome:Already called show() once)。abort():没有进行中的 show() 或 retry()、已有 abort() 挂起,或浏览器无法中止当前交互。canMakePayment():[[state]] 不是 "created" 或文档不是 fully active。complete() 与 retry():响应已完成,或有 retry() 挂起。 |
AbortError |
show():文档不可见、已有另一张支付面板在显示(Chrome:Another PaymentRequest UI is already showing in a different tab or window.)、用户关闭面板(Chrome:User closed the Payment Request UI.),或页面调用了 abort()(Chrome:The website has aborted the payment)。complete():面板打开期间文档不再 fully active。 |
NotSupportedError |
show():列出的方式没有一个受支持。Chrome 还会在既非 localhost、file:// 也非加密协议的源上拒绝,消息为 Only localhost, file://, and cryptographic scheme origins allowed.。 |
NotAllowedError |
canMakePayment():浏览器对反复变换支付方式列表的查询施加反指纹配额。 |
OperationError |
show():所选支付处理程序报告内部错误,例如被操作系统终止。 |
canMakePayment() 兑现为 false 不是异常:它表示设备上没有任何列出的方式可用,这就是改显示托管结账页的信号。
示例以 Google Pay 作为支付方式标识符;换成你的支付服务商的标识符与 data 对象即可。每个示例都先检测构造函数,缺失或没有可用方式时回退到托管结账页。
仅在 canMakePayment() 兑现为 true 时显示钱包按钮
Section titled “仅在 canMakePayment() 兑现为 true 时显示钱包按钮”页面加载时构造请求,询问是否有任何列出的方式可用,只在 true 时展示原生按钮。结果为 false 或构造函数缺失时,保留普通的「继续结账」链接,它跳转到支付服务商的托管页面。
const methodData = [{ supportedMethods: "https://google.com/pay", data: { environment: "PRODUCTION", apiVersion: 2, apiVersionMinor: 0 /* 商户配置 */ },}];const details = { total: { label: "合计", amount: { currency: "CNY", value: "199.00" } },};
async function prepareCheckout() { const hosted = document.querySelector("#hosted-checkout"); const wallet = document.querySelector("#wallet-button");
if (!("PaymentRequest" in window)) { hosted.hidden = false; return; }
const request = new PaymentRequest(methodData, details); let available = false; try { available = await request.canMakePayment(); } catch (err) { console.warn(`${err.name}: ${err.message}`); // 超出浏览器查询配额时被拒绝 }
wallet.hidden = !available; hosted.hidden = available;}
prepareCheckout();保留用来测试的 request 对象:canMakePayment() 不会消耗它,下一个示例的点击处理函数可以在同一实例上调用 show()。
服务端扣款后用 complete() 关闭面板
Section titled “服务端扣款后用 complete() 关闭面板”show() 必须在点击处理函数内运行,瞬时激活才在。把 response.toJSON() 发给服务端,服务端一回应就调用 complete("success") 或 complete("fail");在此之前面板一直开着,Chrome 超时后会自行关闭,效果等同于无参调用 complete()。
const wallet = document.querySelector("#wallet-button");
wallet.addEventListener("click", async () => { const request = new PaymentRequest(methodData, details); let response; try { response = await request.show(); } catch (err) { if (err.name === "AbortError") return; // 用户关闭了面板 location.assign("/checkout/hosted"); // 其他任何拒绝:回退到托管结账页 return; }
const result = await fetch("/api/charge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(response.toJSON()), });
await response.complete(result.ok ? "success" : "fail"); if (result.ok) location.assign("/checkout/thanks");});complete("fail") 显示浏览器的失败状态并关闭面板;接下来该告诉用户怎么办,是页面的责任。
配送地址变化时重新计价
Section titled “配送地址变化时重新计价”设置 requestShipping: true 后,面板让用户选地址,并在请求对象上触发 shippingaddresschange。在监听器内同步调用 event.updateWith(),传入新明细的 Promise;完结之前面板显示加载状态。规范允许浏览器在用户授权前对地址做脱敏,所以用 country、region 与 postalCode 计算运费,不要依赖街道行。
const request = new PaymentRequest(methodData, { total: { label: "合计", amount: { currency: "CNY", value: "199.00" } }, shippingOptions: [ { id: "ground", label: "普通快递(5 天)", amount: { currency: "CNY", value: "0.00" }, selected: true }, ],}, { requestShipping: true });
request.addEventListener("shippingaddresschange", (event) => { const { country, postalCode } = event.target.shippingAddress; event.updateWith( fetch(`/api/shipping?country=${country}&postal=${postalCode}`) .then((r) => r.json()) .then((quote) => ({ total: { label: "合计", amount: { currency: "CNY", value: quote.total } }, shippingOptions: quote.options, })) );});报价返回 shippingOptions: [] 表示该地址无法配送;在更新里加上 error: "该地区暂不支持配送",用户就能看到原因。
- Digital Goods API,在 Trusted Web Activity 内接入
PaymentRequest的 Play Billing 支付方式 - Trusted Web Activity(TWA)
- 为 PWA 创造收入
- Payment Request API: show() method(w3.org)
- Payment Request API: constructor(w3.org)
- Introducing the Payment Request API for Apple Pay(webkit.org)
- How the Payment Request API works(web.dev)
- Chromium payments error strings(chromium.googlesource.com)
规范
| 规范 | 状态 |
|---|---|
| Payment Request API(支付请求) | W3C |
| Payment Request API: constructor | W3C |
| Payment Request API: show() method | W3C |
| Payment Request API: canMakePayment() method | W3C |
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| Chrome (Android) | 支持 | 61 | 中 | 来源 | — |
| Chrome (Desktop) | 支持 | 61 | 中 | 来源 | — |
| Edge (Desktop) | 支持 | 79 | 中 | 来源 | — |
| Safari (iOS) | 支持 | 11.1 | 中 | 来源 | 1 |
| Safari (macOS) | 支持 | 11.1 | 中 | 来源 | 2 |
| Firefox (Desktop) | 不支持 | — | 中 | 来源 | 3 |
| Samsung Internet | 支持 | 7.0 | 中 | 来源 | — |
- 以 Apple Pay 作为支付方式。
- 由 Apple Pay 支撑。
- 实现曾发布后又被禁用;默认不可用。
生态与商业政策
| 主体 | 类型 | 场景 | 状态 | 赞助 | 备注 |
|---|---|---|---|---|---|
| Apple Pay | payment_sdk | Safari / iOS | 支持 | 否 | 在 Safari 中通过 Payment Request 可用;需要完成商户域名验证。 |
| Stripe | payment_sdk | Cross-browser | 支持 | 否 | Stripe 把 Payment Request 封装为 Payment Request Button / Payment Element。 |
| Google Play billing | store_policy | Google Play TWA | 不支持 | 否 | 依据 Play 政策,分发数字商品的 TWA 必须使用 Play Billing,而不是 Payment Request。 |