使用 Service Worker 编排付款事务

如何使您基于网络的付款应用适应网络付款,并为客户提供更好的用户体验。

注册付款应用后,您就可以接受来自商家的付款请求了。这篇博文解释了如何在运行时编排来自 Service Worker 的付款事务(即,当显示窗口且用户与之交互时)。

使用 Service Worker 编排付款事务
使用 Service Worker 编排付款事务

“运行时付款参数更改”是指一组允许商家和付款处理程序在用户与付款处理程序互动时交换消息的事件。如需了解详情,请参阅使用 Service Worker 处理可选付款信息

接收来自商家的付款请求事件

当客户选择使用您基于网络的付款应用付款并且商家调用 PaymentRequest.show() 时,您的 Service Worker 将收到 paymentrequest 事件。向 Service Worker 添加事件监听器,以捕获事件并为下一项操作做好准备。

[付款处理程序] service-worker.js:

…
let payment_request_event;
let resolver;
let client;

// `self` is the global object in service worker
self.addEventListener('paymentrequest', async e => {
  if (payment_request_event) {
    // If there's an ongoing payment transaction, reject it.
    resolver.reject();
  }
  // Preserve the event for future use
  payment_request_event = e;
…

保留的 PaymentRequestEvent 包含有关此事务的重要信息:

属性名称 说明
topOrigin 表示顶级网页(通常是收款人商家)来源的字符串。使用此名称标识商家来源。
paymentRequestOrigin 指示调用程序来源的字符串。当商家直接调用 Payment Request API 时,此 ID 可能与 topOrigin 相同;但如果第三方(例如支付网关)从 iframe 中调用该 API,则可能会不同。
paymentRequestId 向 Payment Request API 提供的 PaymentDetailsInitid 属性。如果商家省略,浏览器将提供一个自动生成的 ID。
methodData 商家在 PaymentMethodData 中提供的付款方式专属数据。使用此方法可确定付款交易详情。
total 商家提供的 PaymentDetailsInit 总金额。使用此方法构建一个界面,让客户知道需要支付的总金额。
instrumentKey 用户选择的插桩键。这反映的是您之前提供的instrumentKey。空字符串表示用户未指定任何付款方式。

打开付款处理程序窗口以显示基于网络的付款应用前端

收到 paymentrequest 事件后,付款应用可以通过调用 PaymentRequestEvent.openWindow() 打开付款处理程序窗口。付款处理程序窗口将向客户显示您的付款应用界面,他们可以在该界面中进行身份验证、选择送货地址和选项,以及授权付款。我们将在在付款前端处理付款(即将推出)中介绍如何编写前端代码。

基于 Web 付款应用的结账流程。

将保留的 promise 传递给 PaymentRequestEvent.respondWith(),以便日后可以通过付款结果对其进行解析。

[付款处理程序] service-worker.js:

…
self.addEventListener('paymentrequest', async e => {
…
  // Retain a promise for future resolution
  // Polyfill for PromiseResolver is provided below.
  resolver = new PromiseResolver();

  // Pass a promise that resolves when payment is done.
  e.respondWith(resolver.promise);
  // Open the checkout page.
  try {
    // Open the window and preserve the client
    client = await e.openWindow(checkoutURL);
    if (!client) {
      // Reject if the window fails to open
      throw 'Failed to open window';
    }
  } catch (err) {
    // Reject the promise on failure
    resolver.reject(err);
  };
});
…

您可以使用方便的 PromiseResolver polyfill 在任意时间解析 promise。

class PromiseResolver {
  constructor() {
    this.promise_ = new Promise((resolve, reject) => {
      this.resolve_ = resolve;
      this.reject_ = reject;
    })
  }
  get promise() { return this.promise_ }
  get resolve() { return this.resolve_ }
  get reject() { return this.reject_ }
}

与前端交换信息

付款应用的 Service Worker 可以通过 ServiceWorkerController.postMessage() 与付款应用的前端交换消息。如需从前端接收消息,请监听 message 事件。

[付款处理程序] service-worker.js:

// Define a convenient `postMessage()` method
const postMessage = (type, contents = {}) => {
  if (client) client.postMessage({ type, ...contents });
}

从前端接收就绪信号

付款处理程序窗口打开后,Service Worker 应等待来自付款应用前端的就绪状态信号。Service Worker 可以在前端准备就绪时将重要信息传递给前端。

[付款处理程序] 前端:

navigator.serviceWorker.controller.postMessage({
  type: 'WINDOW_IS_READY'
});

[付款处理程序] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      // `WINDOW_IS_READY` is a frontend's ready state signal
      case 'WINDOW_IS_READY':
        const { total } = payment_request_event;
…

将交易详情传递到前端

现在,请将付款明细发回。在本例中,您只发送付款请求总额,但您还可以根据需要传递更多详细信息。

[付款处理程序] service-worker.js:

…
        // Pass the payment details to the frontend
        postMessage('PAYMENT_IS_READY', { total });
        break;
…

[付款处理程序] 前端:

let total;

navigator.serviceWorker.addEventListener('message', async e => {
  switch (e.data.type) {
      case 'PAYMENT_IS_READY':
        ({ total } = e.data);
        // Update the UI
        renderHTML(total);
        break;
…

返回客户的付款凭据

当客户授权付款时,前端可以向 Service Worker 发送一条 POST 消息以继续操作。您可以解析传递给 PaymentRequestEvent.respondWith() 的 promise,以将结果发送回商家。传递 PaymentHandlerResponse 对象。

属性名称 说明
methodName 用于付款的付款方式标识符。
details 付款方式专用数据,为商家提供处理付款所需的必要信息。

[付款处理程序] 前端:

  const paymentMethod = …

  postMessage('PAYMENT_AUTHORIZED', {
    paymentMethod,              // Payment method identifier
  });

[付款处理程序] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      …
      case 'PAYMENT_AUTHORIZED':
        // Resolve the payment request event promise
        // with a payment response object
        const response = {
          methodName: e.data.paymentMethod,
          details: { id: 'put payment credential here' },
        }
        resolver.resolve(response);
        // Don't forget to initialize.
        payment_request_event = null;
        break;
      …

取消付款交易

要允许客户取消交易,前端可以向 Service Worker 发送一条 post 消息来执行此操作。然后,Service Worker 可以使用 null 解析传递给 PaymentRequestEvent.respondWith() 的 promise,以向商家表明交易已取消。

[付款处理程序] 前端:

  postMessage('CANCEL_PAYMENT');

[付款处理程序] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      …
      case 'CANCEL_PAYMENT':
        // Resolve the payment request event promise
        // with null
        resolver.resolve(null);
        // Don't forget to initialize.
        payment_request_event = null;
        break;
      …

示例代码

您在本文档中看到的所有示例代码均摘自以下正常运行的示例应用:

https://paymenthandler-demo.glitch.me

[付款处理程序] Service Worker

[付款处理程序] 前端

如需试用,请执行以下操作:

  1. 前往 https://paymentrequest-demo.glitch.me/
  2. 转至页面底部。
  3. 添加付款按钮
  4. 付款方式标识符字段中输入 https://paymenthandler-demo.glitch.me
  5. 按字段旁边的付款按钮。

后续步骤

在本文中,我们学习了如何编排来自 Service Worker 的付款事务。下一步是了解如何向 Service Worker 添加一些更高级的功能。