使用 Payment Request API

Payment Request API” 提供了一种基于浏览器的、将用户及其首选支付系统和平台连接到他们想要为商品和服务付费的商家的方法。本文档将引导你使用“Payment Request API”,并提供示例和建议的最佳实践。

支付的基本流程

本节详细介绍使用 Payment Request API 进行支付的基本流程。

注意: 本节中的代码片段来自我们的功能检测支持演示

创建新的支付请求对象

支付请求始于创建一个新的 PaymentRequest 对象 — 使用 PaymentRequest() 构造函数。它需要两个必需参数和一个可选参数:

  • methodData — 包含有关支付提供商的信息的对象,例如支持的支付方式等。
  • details — 包含有关具体支付的信息的对象,例如总支付金额、税费、运费等。
  • options (可选) — 包含与支付相关的其他选项的对象。

例如,你可以像这样创建一个新的 PaymentRequest 实例:

js
const request = new PaymentRequest(
  buildSupportedPaymentMethodData(),
  buildShoppingCartDetails(),
);

构造函数内部调用的函数会返回所需的参数对象。

js
function buildSupportedPaymentMethodData() {
  // Example supported payment methods:
  return [{ supportedMethods: "https://example.com/pay" }];
}

function buildShoppingCartDetails() {
  // Hardcoded for demo purposes:
  return {
    id: "order-123",
    displayItems: [
      {
        label: "Example item",
        amount: { currency: "USD", value: "1.00" },
      },
    ],
    total: {
      label: "Total",
      amount: { currency: "USD", value: "1.00" },
    },
  };
}

启动支付流程

创建 PaymentRequest 对象后,在其上调用 PaymentRequest.show() 方法以发起支付请求。如果支付成功,它将返回一个解析为 PaymentResponse 对象的 Promise。

js
request.show().then((paymentResponse) => {
  // Here we would process the payment. For this demo, simulate immediate success:
  paymentResponse.complete("success").then(() => {
    // For demo purposes:
    introPanel.style.display = "none";
    successPanel.style.display = "block";
  });
});

此对象为开发者提供了访问详细信息的权限,可用于完成支付完成后的逻辑步骤,例如客户的电子邮件地址、发货地址等。在上面的代码中,你会看到我们调用了 PaymentResponse.complete() 方法来表示交互已完成 — 你可以使用它来执行收尾步骤,例如更新用户界面告知用户交易已完成等。

其他有用的支付请求方法

还有一些其他值得了解的有用支付请求方法。

PaymentRequest.canMakePayment() 可用于在开始支付流程之前检查 PaymentRequest 对象是否能够进行支付。它返回一个解析为布尔值的 Promise,指示是否可以进行支付,例如:

js
// Dummy payment request to check whether payment can be made
new PaymentRequest(buildSupportedPaymentMethodData(), {
  total: { label: "Stub", amount: { currency: "USD", value: "0.01" } },
})
  .canMakePayment()
  .then((result) => {
    if (result) {
      // Real payment request
      const request = new PaymentRequest(
        buildSupportedPaymentMethodData(),
        checkoutObject,
      );
      request.show().then((paymentResponse) => {
        // Here we would process the payment.
        paymentResponse.complete("success").then(() => {
          // Finish handling payment
        });
      });
    }
  });

PaymentRequest.abort() 可用于在需要时中止支付请求。

检测 Payment Request API 的可用性

你可以通过检查用户浏览器是否支持 PaymentRequest 来有效地检测 Payment Request API 的支持情况,即 if (window.PaymentRequest)

在下面的代码片段中,商家页面执行了此检查,如果返回 true,则将结账按钮更新为使用 PaymentRequest 而非传统的 Web 表单。

js
const checkoutButton = document.getElementById("checkout-button");
if (window.PaymentRequest) {
  let request = new PaymentRequest(
    buildSupportedPaymentMethodNames(),
    buildShoppingCartDetails(),
  );
  checkoutButton.addEventListener("click", () => {
    request
      .show()
      .then((paymentResponse) => {
        // Handle successful payment
      })
      .catch((error) => {
        // Handle cancelled or failed payment. For example, redirect to
        // the legacy web form checkout:
        window.location.href = "/legacy-web-form-checkout";
      });

    // Every click on the checkout button should use a new instance of
    // PaymentRequest object, because PaymentRequest.show() can be
    // called only once per instance.
    request = new PaymentRequest(
      buildSupportedPaymentMethodNames(),
      buildShoppingCartDetails(),
    );
  });
}

注意: 有关完整代码,请参阅我们的功能检测支持演示

检查用户是否可以进行支付

检查用户是否可以进行支付始终是有用的。以下是几种相关技术。

自定义支付按钮

一个有用的技术是根据用户是否可以进行支付来定制支付请求按钮。

在下面的代码片段中,我们正是这样做的 — 根据用户是能够进行快速结账还是需要先添加支付凭据,结账按钮的标题会在“使用 W3C 快速结账”和“设置 W3C 结账”之间切换。在这两种情况下,结账按钮都会调用 PaymentRequest.show()

js
const checkoutButton = document.getElementById("checkout-button");
checkoutButton.innerText = "Loading…";
if (window.PaymentRequest) {
  const request = new PaymentRequest(
    buildSupportedPaymentMethodNames(),
    buildShoppingCartDetails(),
  );
  request
    .canMakePayment()
    .then((canMakeAFastPayment) => {
      checkoutButton.textContent = canMakeAFastPayment
        ? "Fast Checkout with W3C"
        : "Setup W3C Checkout";
    })
    .catch((error) => {
      // The user may have turned off the querying functionality in their
      // privacy settings. The website does not know whether they can make
      // a fast payment, so pick a generic title.
      checkoutButton.textContent = "Checkout with W3C";
    });
}

注意: 有关完整代码,请参阅我们的自定义支付按钮演示

在所有价格已知之前进行检查

如果结账流程需要在所有行项目及其价格都已知之前就知道 PaymentRequest.canMakePayment() 是否会返回 true,则可以使用虚拟数据实例化 PaymentRequest 并预先查询 .canMakePayment()。如果你多次调用 .canMakePayment(),请记住 PaymentRequest 构造函数的第一个参数应包含相同的支付方式名称和数据。

js
// The page has loaded. Should the page use PaymentRequest?
// If PaymentRequest fails, should the page fallback to manual
// web form checkout?
const supportedPaymentMethods = [
  /* supported methods */
];

let shouldCallPaymentRequest = true;
let fallbackToLegacyOnPaymentRequestFailure = false;
new PaymentRequest(supportedPaymentMethods, {
  total: { label: "Stub", amount: { currency: "USD", value: "0.01" } },
})
  .canMakePayment()
  .then((result) => {
    shouldCallPaymentRequest = result;
  })
  .catch((error) => {
    console.error(error);

    // The user may have turned off query ability in their privacy settings.
    // Let's use PaymentRequest by default and fallback to legacy
    // web form based checkout.
    shouldCallPaymentRequest = true;
    fallbackToLegacyOnPaymentRequestFailure = true;
  });

// User has clicked on the checkout button. We know
// what's in the cart, but we don't have a `Checkout` object.
function onCheckoutButtonClicked(lineItems) {
  callServerToRetrieveCheckoutDetails(lineItems);
}

// The server has constructed the `Checkout` object. Now we know
// all of the prices and shipping options.
function onServerCheckoutDetailsRetrieved(checkoutObject) {
  if (shouldCallPaymentRequest) {
    const request = new PaymentRequest(supportedPaymentMethods, checkoutObject);
    request
      .show()
      .then((paymentResponse) => {
        // Post the results to the server and call `paymentResponse.complete()`.
      })
      .catch((error) => {
        console.error(error);
        if (fallbackToLegacyOnPaymentRequestFailure) {
          window.location.href = "/legacy-web-form-checkout";
        } else {
          showCheckoutErrorToUser();
        }
      });
  } else {
    window.location.href = "/legacy-web-form-checkout";
  }
}

注意: 有关完整代码,请参阅我们的在价格已知前检查用户是否可以进行支付演示

当用户没有支付应用时,推荐支付应用

如果你在此商家页面上选择使用 BobPay 演示支付提供商进行支付,它会尝试调用 PaymentRequest.show(),同时拦截 NotSupportedError DOMException。如果该支付方式不受支持,它将重定向到 BobPay 的注册页面。

代码看起来大致是这样的:

js
checkoutButton.addEventListener("click", () => {
  const request = new PaymentRequest(
    buildSupportedPaymentMethodData(),
    buildShoppingCartDetails(),
  );
  request
    .show()
    .then((paymentResponse) => {
      // Here we would process the payment. For this demo, simulate immediate success:
      paymentResponse.complete("success").then(() => {
        // For demo purposes:
        introPanel.style.display = "none";
        successPanel.style.display = "block";
      });
    })
    .catch((error) => {
      if (error.name === "NotSupportedError") {
        window.location.href = "https://bobpay.xyz/#download";
      } else {
        // Other kinds of errors; cancelled or failed payment. For demo purposes:
        introPanel.style.display = "none";
        legacyPanel.style.display = "block";
      }
    });
});

注意: 有关完整代码,请参阅我们的当用户没有应用时推荐支付应用演示

支付成功后显示额外的用户界面

如果商家希望收集 API 未包含的额外信息(例如,额外的配送说明),商家可以在结账后显示一个包含额外 <input type="text"> 字段的页面。

js
request
  .show()
  .then((paymentResponse) => paymentResponse.complete("success"))
  .then(() => {
    // Process payment here.
    // Close the UI:
    // Request additional shipping address details.
    const additionalDetailsContainer = document.getElementById(
      "additional-details-container",
    );
    additionalDetailsContainer.style.display = "block";
    window.scrollTo(additionalDetailsContainer.getBoundingClientRect().x, 0);
  })
  .catch((error) => {
    // Handle error.
  });

注意: 有关完整代码,请参阅我们的支付成功后显示额外用户界面演示

预授权交易

某些用例(例如,在服务站支付燃油费)涉及预授权支付。一种方法是通过支付处理程序(请参阅Payment Handler API)。在撰写本文时,该规范包含一个 canmakepayment 事件,支付处理程序可以利用该事件返回授权状态。

商家代码应如下所示:

js
const paymentRequest = new PaymentRequest(
  [{ supportedMethods: "https://example.com/preauth" }],
  details,
);

// Send `CanMakePayment` event to the payment handler.
paymentRequest
  .canMakePayment()
  .then((res) => {
    if (res) {
      // The payment handler has pre-authorized a transaction
      // with some static amount, e.g., USD $1.00.
    } else {
      // Pre-authorization failed or payment handler not installed.
    }
  })
  .catch((err) => {
    // Unexpected error occurred.
  });

支付处理程序将包含以下代码:

js
self.addEventListener("canmakepayment", (evt) => {
  // Pre-authorize here.
  const preAuthSuccess = true;
  evt.respondWith(preAuthSuccess);
});

此支付处理程序需要位于作用域为 https://example.com/preauth 的服务工作线程中。

注意: 有关完整代码,请参阅我们的预授权交易演示

另见