Organización de transacciones de pago con un service worker

Cómo adaptar tu app de pagos basada en la Web a Web Payments y brindar una mejor experiencia del usuario a los clientes

Una vez que se registre la app de pagos, podrás aceptar solicitudes de pago de los comercios. En esta publicación, se explica cómo organizar una transacción de pago de un service worker durante el tiempo de ejecución (es decir, cuando se muestra una ventana y el usuario interactúa con ella).

Cómo organizar transacciones de pago con un service worker
Organización de las transacciones de pago con un service worker

Los “Cambios en los parámetros de pago en tiempo de ejecución” son un conjunto de eventos que permiten que el comercio y el controlador de pagos intercambien mensajes mientras el usuario interactúa con el controlador de pagos. Para obtener más información, consulta Cómo manejar información de pago opcional con un service worker.

Cómo recibir un evento de solicitud de pago del comercio

Cuando un cliente elija pagar con tu app de pagos basada en la Web y el comercio invoca a PaymentRequest.show(), tu service worker recibirá un evento paymentrequest. Agrega un objeto de escucha de eventos al service worker para capturar el evento y prepararte para la siguiente acción.

[controlador de pagos] 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;
…

El PaymentRequestEvent conservado contiene información importante sobre esta transacción:

Nombre de la propiedad Descripción
topOrigin Es una cadena que indica el origen de la página web de nivel superior (por lo general, el comerciante beneficiario). Úsalo para identificar el origen del comercio.
paymentRequestOrigin Una cadena que indica el origen del invocador. Puede ser igual a topOrigin cuando el comercio invoca directamente la API de Payment Request, pero puede ser diferente si un tercero invoca la API desde un iframe, como una puerta de enlace de pago.
paymentRequestId La propiedad id de PaymentDetailsInit que se proporcionó a la API de Payment Request Si el comercio omite este paso, el navegador proporcionará un ID generado automáticamente.
methodData Son los datos específicos de la forma de pago que proporciona el comercio como parte del PaymentMethodData. Úsalo para determinar los detalles de la transacción de pago.
total Es el importe total que proporciona el comercio como parte de PaymentDetailsInit. Úsalo para construir una IU que le informe al cliente el importe total que pagará.
instrumentKey La etiqueta de marca que seleccionó el usuario. Esto refleja los instrumentKey que proporcionaste con anticipación. Una cadena vacía indica que el usuario no especificó ningún instrumento.

Abre la ventana del controlador de pagos para mostrar el frontend de la app de pagos basada en la Web.

Cuando se recibe un evento paymentrequest, la app de pagos puede llamar a PaymentRequestEvent.openWindow() para abrir una ventana del controlador de pagos. La ventana del controlador de pagos presentará a los clientes la interfaz de tu app de pagos donde pueden autenticarse, elegir la dirección y las opciones de envío, y autorizar el pago. Abordaremos cómo escribir el código de frontend en Cómo manejar pagos en el frontend de pagos (próximamente).

Flujo de confirmación de la compra con una app de pagos basada en la Web.

Pasa una promesa preservada a PaymentRequestEvent.respondWith() para que puedas resolverla con un resultado de pago en el futuro.

[controlador de pagos] 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);
  };
});
…

Puedes usar un polyfill PromiseResolver conveniente para resolver una promesa en momentos arbitrarios.

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_ }
}

Intercambia información con el frontend

El service worker de la app de pagos puede intercambiar mensajes con el frontend de la app de pagos mediante ServiceWorkerController.postMessage(). Para recibir mensajes del frontend, escucha los eventos message.

[controlador de pagos] service-worker.js:

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

Recibe el indicador listo del frontend

Una vez que se abre la ventana del controlador de pagos, el service worker debe esperar hasta que llegue un indicador de estado listo del frontend de la app de pagos. El service worker puede pasar información importante al frontend cuando esté listo.

Frontend de [controlador de pagos]:

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

[controlador de pagos] 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;
…

Pasa los detalles de la transacción al frontend

Ahora, devuelve los detalles del pago. En este caso, solo enviarás el total de la solicitud de pago, pero puedes proporcionar más detalles si lo deseas.

[controlador de pagos] service-worker.js:

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

Frontend de [controlador de pagos]:

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;
…

Devuelve las credenciales de pago del cliente

Cuando el cliente autoriza el pago, el frontend puede enviar un mensaje postal al service worker para continuar. Puedes resolver la promesa que se pasó a PaymentRequestEvent.respondWith() para enviar el resultado al comercio. Pasa un objeto PaymentHandlerResponse.

Nombre de la propiedad Descripción
methodName Es el identificador de la forma de pago que se usa para realizar los pagos.
details Son los datos específicos de la forma de pago que proporcionan la información necesaria para que el comercio procese el pago.

Frontend de [controlador de pagos]:

  const paymentMethod = …

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

[controlador de pagos] 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;
      …

Cancela la transacción de pago

Para permitir que el cliente cancele la transacción, el frontend puede enviar un mensaje posterior al service worker. Luego, el service worker puede resolver la promesa que se pasó a PaymentRequestEvent.respondWith() con null para indicarle al comercio que se canceló la transacción.

Frontend de [controlador de pagos]:

  postMessage('CANCEL_PAYMENT');

[controlador de pagos] 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;
      …

Código de muestra

Todos los códigos de muestra que viste en este documento fueron extractos de la siguiente app de muestra funcional:

https://paymenthandler-demo.glitch.me

[controlador de pagos] service worker

Frontend de [controlador de pagos]

Para probarlo, sigue estos pasos:

  1. Ve a https://paymentrequest-demo.glitch.me/.
  2. Ve a la parte inferior de la página.
  3. Presiona el botón Agregar un pago.
  4. Ingresa https://paymenthandler-demo.glitch.me en el campo Payment Method Identifier.
  5. Presiona el botón Pagar junto al campo.

Próximos pasos

En este artículo, aprendimos a organizar una transacción de pago desde un service worker. El próximo paso es aprender a agregar algunas funciones más avanzadas al service worker.