为 PWA 创造收入
发布于
完成本指南后,PWA 里的结账按钮会通过 Payment Request API 唤起浏览器自带的支付面板, 收集买家的卡片或钱包信息,把支付响应交给你的服务器,并在拒绝该请求的浏览器中回退到现有表单。 这个 API 是收集支付、地址与联系方式的标准方式;它本身不转账,扣款仍由你的支付处理商完成。
你需要一个通过 HTTPS 提供的页面(普通 HTTP 下该 API 为 undefined)、支付处理商给出的支付方式标识符,
以及一个接收处理商令牌的服务端接口。哪些商店与支付网络允许网页端结账属于政策问题,带日期的来源整理在
支付。
1. 检测 API 并预检支付方式
Section titled “1. 检测 API 并预检支付方式”没有该 API 的浏览器中 window.PaymentRequest 不存在,所以渲染原生结账按钮之前先检测。存在时,构造一个
一次性请求并调用 canMakePayment():浏览器能处理你列出的至少一种方式时它解析为 true。下面的 URL 形式
标识符沿用 MDN 示例的写法;你的处理商会给出自己的标识符。
const methodData = [{ supportedMethods: 'https://example.com/pay' }];const stubDetails = { total: { label: 'Stub', amount: { currency: 'USD', value: '0.01' } },};
async function nativeCheckoutAvailable() { if (!('PaymentRequest' in window)) return false; try { return await new PaymentRequest(methodData, stubDetails).canMakePayment(); } catch { // 用户可能在隐私设置里关闭了该查询,或者浏览器对频繁调用 // 以 DOMException 拒绝。 return false; }}每个页面只调用一次 canMakePayment(),不要每次渲染都调:MDN 注明调用过于频繁时浏览器可能以 DOMException
拒绝该 Promise。传给预检请求的 methodData 要与真实请求完全一致,否则预检回答的是另一个问题。
2. 在点击处理函数内构造请求
Section titled “2. 在点击处理函数内构造请求”每次点击都新建一个 PaymentRequest:show() 在每个实例上只能调用一次。在处理函数内同步调用 show()。
规范允许浏览器在页面没有瞬时用户激活时以 SecurityError 拒绝 show(),Chrome 正是这样做的,
因此在点击与 show() 之间 await 一次网络请求会让结账失败。金额还没算好时,把一个 Promise 作为
show() 的参数传入,而不是先 await。
function buildDetails(cart) { return { id: cart.orderId, displayItems: cart.lines.map((line) => ({ label: line.name, amount: { currency: 'USD', value: line.price }, })), total: { label: 'Total', amount: { currency: 'USD', value: cart.total } }, };}
document.querySelector('#checkout').addEventListener('click', async () => { const request = new PaymentRequest(methodData, buildDetails(currentCart())); try { const response = await request.show(); const result = await fetch('/api/charge', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ orderId: currentCart().orderId, details: response.details }), }); await response.complete(result.ok ? 'success' : 'fail'); } catch (error) { if (error.name === 'NotSupportedError') { location.assign('/checkout/form'); // 这种支付方式没有可用的支付应用 } else if (error.name !== 'AbortError') { throw error; // 买家关闭面板不算错误 } }});response.details 装的是支付方式返回的内容:对 Google Pay 而言是一个支付令牌,由你的后端转交给处理商,
所以扣款发生在服务端,complete() 传入的是服务端的结论。Google Pay 也提供自己的 JavaScript 库(pay.js)
来驱动同一个面板;代价是页面多加载一个脚本,换来的是在没有 Payment Request API 的浏览器里也能渲染按钮。
3. 保留表单路径并显示正确的按钮
Section titled “3. 保留表单路径并显示正确的按钮”只在第 1 步返回 true 时渲染原生结账按钮;否则渲染指向表单结账的链接。原生面板省去买家在手机上打字,
代价是多出一条需要测试的结账路径。表单是没有该 API 的浏览器(参见
Payment Request API 浏览器支持)以及关闭了面板的买家的回退。
nativeCheckoutAvailable().then((ok) => { document.querySelector('#checkout').hidden = !ok; document.querySelector('#checkout-form-link').hidden = ok;});面板以 success 关闭时,浏览器收起面板、页面继续;以 fail 关闭时,浏览器显示自己的错误状态。
至此,有浏览器面板的地方用面板,其余地方用你的表单。
- Payment Request API:浏览器托管的结账流程
- Digital Goods API(TWA 中的 Play Billing)
- 支付
- Payment Request API: show() method(developer.mozilla.org)
- Payment Request API specification(w3.org)
← 返回指南总览。