Don't forget the Chrome Dev Summit, starting Monday at 10:00am (Pacific) and streaming live on YouTube. Schedule.

Payment Request API: una guía de integración

Prueba interna: PaymentRequest está aún en desarrollo. Mientras creemos que es lo suficientemente estable para implementarse, puede seguir cambiando. Mantendremos esta página actualizada para que siempre muestre el estado actual de la API (cambios M56). Mientras tanto, te protegemos de los cambios de la API que puedan ser incompatibles con versiones anteriores, ofrecemos una corrección de compatibilidad que se puede integrar a tu sitio. La corrección de compatibilidad oculta cualquier diferencia de la API de dos versiones principales de Chrome.

Comprar productos en línea es una experiencia conveniente, pero a menudo puede ser frustrante, en especial desde dispositivos móviles. Si bien el tráfico móvil continúa en aumento, las conversiones móviles representan solo un tercio de todas las compras realizadas. En otras palabras, los usuarios abandonan las compras desde dispositivos móviles con el doble de frecuencia en comparación con las compras desde equipos de escritorio. ¿Por qué?

Por qué los usuarios abandonan los formularios de compra desde dispositivos móviles

Los formularios de compra en línea requieren una amplia intervención por parte del usuario, son difíciles de usar, se cargan y actualizan con lentitud, y requieren varios pasos para completar la compra. Esto se debe a que dos componentes principales de los pagos en línea, seguridad y conveniencia, a menudo trabajan con diferentes propósitos; por lo general, más de uno significa menos del otro.

La mayoría de los problemas que llevan al abandono se pueden asociar directamente con los formularios de compra. Cada app o sitio tiene su propio proceso de entrada de datos y validación, y los usuarios a menudo descubren que tienen que ingresar la misma información en cada punto de compra de la app. Asimismo, los desarrolladores de apps se esfuerzan por crear flujos de compra que admitan varios métodos de compra exclusivos; incluso las diferencias más pequeñas en los requisitos de los métodos de pago pueden complicar el proceso de llenado y envío de formularios.

Cualquier sistema que mejore o resuelva uno o más de esos problemas es un cambio bienvenido. Ya hemos comenzado a solucionar el problema con Autocompletar, pero ahora quisiéramos hablar sobre una solución más integral.

Introducción de la Payment Request API

La Payment Request API es un sistema que tiene que eliminar los formularios de salida. Mejora notablemente el flujo de trabajo del usuario durante el proceso de compra, lo cual proporciona una experiencia del usuario más uniforme y permite a los comerciantes web usar con facilidad diferentes métodos de pago. La Payment Request API no es un nuevo método de pago ni se integra directamente con procesadores de pago; es una capa de proceso concebida con los siguientes objetivos:

  • Permitir que el navegador actúe como intermediario entre comerciantes, usuarios y métodos de pago.
  • Estandarizar el flujo de comunicación de pago tanto como sea posible.
  • Admitir a la perfección diferentes métodos de pago seguros.
  • Funcionar en cualquier navegador, dispositivo, móvil de plataforma o de otra manera.

La Payment Request API es un estándar abierto compatible con varios navegadores que reemplaza los flujos tradicionales de finalización de compra al permitir a los comerciantes solicitar y aceptar cualquier método de pago en una sola llamada a la API. La Payment Request API permite que la página web intercambie información con el usuario-agente mientras este último proporciona entradas, antes de aprobar o rechazar una solicitud de pago.

Lo mejor de todo, con el navegador como intermediario, toda la información necesaria para que la finalización de la compra sea rápida se puede guardar en el navegador, de modo que los usuarios solo deban confirmar y pagar con un solo clic.

Proceso de transacción de pago

Con la Payment Request API, el proceso de transacción es lo más fluido posible para usuarios y comerciantes.

Proceso de transacción de pago

El proceso comienza cuando el sitio del comerciante crea una nueva PaymentRequest y transmite al navegador toda la información necesaria para realizar la compra: el monto que se cobrará, la moneda en la que se espera el pago y los métodos de pago que acepta el sitio. El navegador determina la compatibilidad entre los métodos de pago aceptados para el sitio y los métodos que el usuario tiene instalados en el dispositivo de destino.

Interfaz de Payment Request

Luego presenta las IU de pago al usuario, quien selecciona un método de pago y autoriza la transacción. Un método de pago puede ser sencillo como una tarjeta de crédito que el navegador haya guardado, o complicado como una aplicación de terceros creada específicamente para proporcionar pagos al sitio (esta funcionalidad se anunciará próximamente). Una vez de que el usuario autoriza la transacción, todos los detalles de pago necesarios se envían directamente al sitio. Por ejemplo, para un pago con tarjeta de crédito, el sitio recibirá un número de tarjeta, un nombre de titular de la tarjeta, una fecha de vencimiento y un CVC.

La Payment Request también se puede extender para mostrar información adicional, como direcciones y opciones de envío, correo electrónico y teléfono del ordenante. Esto te permite obtener toda la información que necesitas para finalizar un pago sin siquiera mostrar al usuario un formulario de finalización de compra.

La belleza de este proceso nuevo se debe a tres aspectos: desde la perspectiva del usuario, toda la interacción previamente tediosa (solicitud, autorización, pago y resultado) ahora se produce en un solo paso; desde la perspectiva del sitio web, solo exige una única llamada de JavaScript API, desde la perspectiva del método de pago, no existe cambio de proceso.

Uso de la PaymentRequest API

Cargar la corrección de compatibilidad de la Payment Request API

Para reducir las molestias de actualizar esta API estándar en vivo, te recomendamos agregar esta corrección de compatibilidad en la sección <head> de tu código. Esta corrección de compatibilidad se actualiza como cambios de API y hace lo mejor para que tu código continúe funcionando al menos 2 lanzamientos principales de Chrome.

<script src="https://storage.googleapis.com/prshim/v1/payment-shim.js">

Crear una PaymentRequest

El primer paso es crear un objeto PaymentRequest llamando al constructor de la PaymentRequest. Este paso generalmente (aunque no siempre) está asociado a una acción iniciada por el usuario que indica su intención de realizar una compra. El objeto se construye con parámetros que contienen los datos necesarios.

var request = new PaymentRequest(
  methodData, // required payment method data
  details,    // required information about transaction
  options     // optional parameter for things like shipping, etc.
);

Constructor de PaymentRequest

El parámetro methodData

El parámetro methodData contiene una lista de métodos de pagos y, si fuese relevante, información adicional sobre el método de pago. Esta secuencia contiene diccionarios PaymentMethodData que incluyen identificadores estándares asociados con los métodos de pago que la app pretende aceptar y con los datos específicos del método de pago. Consulta Arquitectura de la PaymentRequest API para obtener más información.

En este momento, PaymentRequest en Chrome solo admite las siguientes tarjetas de crédito estándares: 'amex', 'diners', 'discover', 'jcb', 'maestro', 'mastercard', 'unionpay' y 'visa'.

var methodData = [
  {
    supportedMethods: ["visa", "mastercard"]
  }
]

Métodos y datos de pagos

El parámetro de detalles

El parámetro details contiene información sobre la transacción. Hay dos componentes principales: un total, que refleja el monto total y la moneda que se cobrará, y un conjunto opcional de displayItems que indica la forma en que se calculó el monto final. Este parámetro no está pensado para ser una lista de artículos individuales, sino un resumen de los principales componentes del pedido: subtotal, descuentos, impuestos, costos de envío, etc.

Interfaz de Payment Request

Es importante observar que la PaymentRequest API no realiza operaciones aritméticas. Es decir, no garantiza ni puede garantizar que los componentes en pantalla sumen correctamente el monto total adeudado. Estos cálculos son responsabilidad del desarrollador. Por ello, siempre debes asegurarte de que los artículos sumen el mismo monto en el total. Asimismo, PaymentRequest no admite reembolsos, por lo cual los montos deben ser siempre positivos (aunque los artículos pueden ser negativos, como en el caso de descuentos).

El navegador mostrará las etiquetas a medida que las definas, y presentará automáticamente el formato de moneda correcto según la configuración regional del usuario. Ten en cuenta que las etiquetas deben mostrarse en el mismo idioma que tu contenido.

var details = {
  displayItems: [
    {
      label: "Original donation amount",
      amount: { currency: "USD", value : "65.00" }, // US$65.00
    },
    {
      label: "Friends and family discount",
      amount: { currency: "USD", value : "-10.00" }, // -US$10.00
      pending: true // The price is not determined yet
    }
  ],
  total:  {
    label: "Total",
    amount: { currency: "USD", value : "55.00" }, // US$55.00
  }
}

Detalles de la transacción

pending se usa comúnmente para mostrar los elementos como el envío o los importes del impuesto que dependen de la dirección u opción de envío. Chrome indica los campos pendientes en la IU para la solicitud de pago.

Los valores repetidos o calculados que se usan en details se pueden especificar como literales de string o variables de string individuales.

var currency = "USD";
var amount = "65.00";
var discount = "-10.00";
var total = "55.00";

Variables de PaymentRequest

Muestra PaymentRequest

Interfaz de Payment Request

Activa la interfaz PaymentRequest llamando a su método show(). Este método invoca una IU nativa que permite al usuario examinar los detalles de la compra, agregar o cambiar información y, por último, pagar. Cuando el usuario acepte o rechace la solicitud de pago, se mostrará una Promise (indicada por su método then() y función de callback) que se resuelve.

request.show().then(function(paymentResponse) {
  // Process paymentResponse here
  paymentResponse.complete("success");
}).catch(function(err) {
  console.error("Uh oh, something bad happened", err.message);
});

Método show de PaymentRequest

Anular una Payment Request

Puedes anular de modo intencional una PaymentRequest llamando a su método abort(). Esto es particularmente útil cuando se acaba el tiempo de la sesión de compras o se agota un elemento en el carrito durante la transacción.

Usa este método si la app tiene que cancelar la solicitud de pago después de llamar al método show() pero antes de que se haya resuelto la promesa. Por ejemplo, si un elemento ya no está disponible, o si el usuario falla en la confirmación de la compra dentro de cantidad de tiempo asignado.

So abortas una solicitud, deberás crear una nueva instancia de PaymentRequest antes de volver a llamar a show().

var paymentTimeout = window.setTimeout(function() {
  window.clearTimeout(paymentTimeout);
  request.abort().then(function() {
    console.log('Payment timed out after 20 minutes.');
  }).catch(function() {
    console.log('Unable to abort.');
  });
}, 20 * 60 * 1000);  /* 20 minutes */

Método abort de PaymentRequest

Procesar la PaymentResponse

Una vez que un usuario aprueba una solicitud de pago, la promesa del método show() se resuelve y da como resultado un objeto PaymentResponse.

PaymentResponse tiene los siguientes campos:
methodName Cadena que indica el método de pago seleccionado (p. ej., visa)
details Un diccionario que contiene información para methodName
shippingAddress La dirección de envío del usuario, si así se solicita
shippingOption El ID de la opción de envío seleccionada, si así se lo solicita
payerEmail La dirección de correo electrónico del ordenante, si así se lo solicita
payerPhone El número de teléfono del ordenante, si así se lo solicita
payerName El nombre del ordenante, si así se lo solicita

Para pagos con tarjeta de crédito, la respuesta es estandarizada. Para pagos que no se realicen con tarjeta de crédito (p. ej., Android Pay), el proveedor documentará la respuesta. Una respuesta para tarjeta de crédito contiene el siguiente diccionario:

cardholderName cardNumber expiryMonth expiryYear cardSecurityCode billingAddress

Después de recibir la información de pago, la app debe enviar la información de pago a tu procesador de pagos para que este la procese. La IU mostrará un indicador de carga mientras se realiza la solicitud. Cuando se obtiene una respuesta, la app debe llamar a complete() para cerrar la IU.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string, e.g. “visa”
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details
  };
  return fetch('/pay', {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(paymentData)
  }).then(res => {
    if (res.status === 200) {
      return res.json();
    } else {
      throw 'Payment Error';
    }
  }).then(res => {
    paymentResponse.complete("success");
  }, err => {
    paymentResponse.complete("fail");
  });
}).catch(err => {
  console.error("Uh oh, something bad happened", err.message);
});
Interfaz de Payment Request

El método complete() indica al usuario-agente que ha finalizado la interacción del usuario y permite que la app notifique al usuario el resultado y aborde la disposición de los elementos restantes de la IU.

paymentResponse.complete('success').then(() => {
  // Success UI
}

paymentResponse.complete('fail').then(() => {
  // Error UI
};

Método complete de PaymentRequest

Recolectar una dirección de envío

Interfaz de Payment Request

Si eres un comerciante que vende bienes físicos, tal vez quieras obtener la dirección de envío del usuario usando la Payment Request API. Esto se logra agregando requestShipping: true al parámetro options. Con este parámetro configurado, se agregará “Shipping” a la IU y los usuarios podrán seleccionar entre una lista de direcciones guardadas o agregar una nueva dirección de envío.

Como alternativa, puedes usar "entrega" o "recolección" en lugar de "envío" en la IU especificando shippingType. Esto es exclusivo para propósitos de visualización.

var options = {
  requestShipping: true,
  shippingType: "shipping" // "shipping"(default), "delivery" or "pickup"
};

var request = new PaymentRequest(methodData, details, options);

Opciones de transacción

Interfaz de Payment Request

Las opciones de envío se pueden calcular de forma dinámica cada vez que un usuario seleccione o agregue una nueva dirección de envío. Puedes agregar un receptor de eventos para el evento shippingaddresschange, que activa la selección de una dirección de envío por parte del usuario. Luego puedes validar la capacidad de realizar envíos a esa dirección, calcular las opciones de envío y actualizar tu details.shippingOptions con las opciones de envío y los datos de pago nuevos. Puedes ofrecer una opción de envío predeterminada fijando selected en true para una opción.

Para rechazar una dirección por encontrarse, por ejemplo, en una zona no admitida, pasa una matriz vacía a details.shippingOptions. La IU indicará al usuario que no se realizan envíos a la dirección seleccionada.

request.addEventListener('shippingaddresschange', e => {
  e.updateWith(((details, addr) => {
    if (addr.country === 'US') {
      var shippingOption = {
        id: '',
        label: '',
        amount: {currency: 'USD', value: '0.00'},
        selected: true
      };
      if (addr.region === 'US') {
        shippingOption.id = 'us';
        shippingOption.label = 'Standard shipping in US';
        shippingOption.amount.value = '0.00';
        details.total.amount.value = '55.00';
      } else {
        shippingOption.id = 'others';
        shippingOption.label = 'International shipping';
        shippingOption.amount.value = '10.00';
        details.total.amount.value = '65.00';
      }
      if (details.displayItems.length === 2) {
        details.displayItems.splice(1, 0, shippingOption);
      } else {
        details.displayItems.splice(1, 1, shippingOption);
      }
      details.shippingOptions = [shippingOption];
    } else {
      details.shippingOptions = [];
    }
    return Promise.resolve(details);
  })(details, request.shippingAddress));
});
Interfaz de Payment Request

Una vez que el usuario apruebe una solicitud de pago, se resolverá la promesa del método show(). La app puede usar la propiedad .shippingAddress del objeto PaymentResponse para informar al procesador de pagos la dirección de envío, junto con otras propiedades.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON()
  };
  // Send information to the server
});

Adición de opciones de envío

Si tu servicio permite a los usuarios seleccionar opciones de envío, como “free”, “standard” o “express”, puedes hacer lo mismo a través de la IU de PaymentRequest. Para ofrecer esas opciones, agrega la propiedad shippingOptions y sus opciones al objeto details. Al configurar una opción para selected: true, la IU la representará como preseleccionada (lo cual significa que el monto total debe reflejar el precio para la opción de envío en cuestión).

var details = {
  total: {label: 'Donation', amount: {currency: 'USD', value: '55.00'}},
  displayItems: [
    {
      label: 'Original donation amount',
      amount: {currency: 'USD', value: '65.00'}
    },
    {
      label: 'Friends and family discount',
      amount: {currency: 'USD', value: '-10.00'}
    }
  ],
  shippingOptions: [
    {
      id: 'standard',
      label: 'Standard shipping',
      amount: {currency: 'USD', value: '0.00'},
      selected: true
    },
    {
      id: 'express',
      label: 'Express shipping',
      amount: {currency: 'USD', value: '12.00'}
    }
  ]
};
var request = new PaymentRequest(methodData, details, options);

La modificación de las opciones de envío puede implicar precios diferentes. Para agregar el costo de envío y modificar el precio total, puedes agregar un receptor de eventos para el evento shippingoptionchange, que activa la selección de una opción por parte del usuario, de modo que puedas ejecutar un examen programático de los datos de la opción. También puedes cambiar el costo de envío según la dirección de envío.

request.addEventListener('shippingoptionchange', e => {
  e.updateWith(((details, shippingOption) => {
    var selectedShippingOption;
    var otherShippingOption;
    if (shippingOption === 'standard') {
      selectedShippingOption = details.shippingOptions[0];
      otherShippingOption = details.shippingOptions[1];
      details.total.amount.value = '55.00';
    } else {
      selectedShippingOption = details.shippingOptions[1];
      otherShippingOption = details.shippingOptions[0];
      details.total.amount.value = '67.00';
    }
    if (details.displayItems.length === 2) {
      details.displayItems.splice(1, 0, selectedShippingOption);
    } else {
      details.displayItems.splice(1, 1, selectedShippingOption);
    }
    selectedShippingOption.selected = true;
    otherShippingOption.selected = false;
    return Promise.resolve(details);
  })(details, request.shippingOption));
});
Interfaz de Payment Request

Una vez que el usuario apruebe una solicitud de pago, se resolverá la promesa del método show(). La app puede usar la propiedad .shippingOption del objeto PaymentResponse para informar al procesador de pagos la opción de envío, junto con otras propiedades.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON(),
    // shipping option
    shippingOption: paymentResponse.shippingOption
  };
  // Send information to the server
});

Agregar información opcional de contacto

También puedes obtener la dirección de correo electrónico, el número de teléfono o el nombre de un usuario al configurar el objeto options.

var options = {
  requestPayerPhone: true,  // Request user's phone number
  requestPayerEmail: true,  // Request user's email address
  requestPayerName:  true   // Request user's name
};

var request = new PaymentRequest(methodData, details, options);
Interfaz de Payment Request

Una vez que el usuario apruebe una solicitud de pago, se resolverá la promesa del método show(). La app puede usar las propiedades .payerPhone, .payerEmail y .payerName del objeto PaymentResponse para informarle al procesador de pagos de la opción del usuario, junto con otras propiedades.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON(),
    // shipping option string
    shippingOption: paymentResponse.shippingOption,
    // payer's phone number string
    phone: paymentResponse.payerPhone,
    // payer's email address string
    email: paymentResponse.payerEmail,
    // payer's name string
    name: paymentResponse.payerName
  };
  // Send information to the server
});

Hacer de Payment Request una mejora progresiva

Como la Payment Request API es una función emergente, muchos navegadores no la admiten. Para determinar si la función está disponible, consulta window.PaymentRequest.

if (window.PaymentRequest) {
  // PaymentRequest supported
  // Continue with PaymentRequest API
} else {
  // PaymentRequest NOT supported
  // Continue with existing form based solution
}

Todo junto

function onBuyClicked(event) {
  if (!window.PaymentRequest) {
    return;
  }
  // Payment Request API is available.
  // Stop the default anchor redirect.
  event.preventDefault();

  var supportedInstruments = [{
    supportedMethods: [
      'visa', 'mastercard', 'amex', 'discover', 'maestro',
      'diners', 'jcb', 'unionpay', 'bitcoin'
    ]
  }];

  var details = {
    displayItems: [{
      label: 'Original donation amount',
      amount: { currency: 'USD', value: '65.00' }
    }, {
      label: 'Friends and family discount',
      amount: { currency: 'USD', value: '-10.00' }
    }],
    total: {
      label: 'Total due',
      amount: { currency: 'USD', value : '55.00' }
    }
  };

  var options = {
    requestShipping: true,
    requestPayerEmail: true,
    requestPayerPhone: true,
    requestPayerName: true
  };

  // Initialization
  var request = new PaymentRequest(supportedInstruments, details, options);

  // When user selects a shipping address
  request.addEventListener('shippingaddresschange', e => {
    e.updateWith(((details, addr) => {
      var shippingOption = {
        id: '',
        label: '',
        amount: { currency: 'USD', value: '0.00' },
        selected: true
      };
      // Shipping to US is supported
      if (addr.country === 'US') {
        shippingOption.id = 'us';
        shippingOption.label = 'Standard shipping in US';
        shippingOption.amount.value = '0.00';
        details.total.amount.value = '55.00';
      // Shipping to JP is supported
      } else if (addr.country === 'JP') {
        shippingOption.id = 'jp';
        shippingOption.label = 'International shipping';
        shippingOption.amount.value = '10.00';
        details.total.amount.value = '65.00';
      // Shipping to elsewhere is unsupported
      } else {
        // Empty array indicates rejection of the address
        details.shippingOptions = [];
        return Promise.resolve(details);
      }
      // Hardcode for simplicity
      if (details.displayItems.length === 2) {
        details.displayItems[2] = shippingOption;
      } else {
        details.displayItems.push(shippingOption);
      }
      details.shippingOptions = [shippingOption];

      return Promise.resolve(details);
    })(details, request.shippingAddress));
  });

  // When user selects a shipping option
  request.addEventListener('shippingoptionchange', e => {
    e.updateWith(((details) => {
      // There should be only one option. Do nothing.
      return Promise.resolve(details);
    })(details));
  });

  // Show UI then continue with user payment info
  request.show().then(result => {
    // POST the result to the server
    return fetch('/pay', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(result.toJSON())
    }).then(res => {
      // Only if successful
      if (res.status === 200) {
        return res.json();
      } else {
        throw 'Failure';
      }
    }).then(response => {
      // You should have received a JSON object
      if (response.success == true) {
        return result.complete('success');
      } else {
        return result.complete('fail');
      }
    }).then(() => {
      console.log('Thank you!',
          result.shippingAddress.toJSON(),
          result.methodName,
          result.details.toJSON());
    }).catch(() => {
      return result.complete('fail');
    });
  }).catch(function(err) {
    console.error('Uh oh, something bad happened: ' + err.message);
  });
}

// Assuming an anchor is the target for the event listener.
document.querySelector('#start').addEventListener('click', onBuyClicked);