Web SDK Payment
Descripción
Biblioteca JavaScript para mostrar el formulario de pago en la página del comerciante. Este método es adecuado para comerciantes tanto con alto como con bajo nivel de cumplimiento de los requisitos PCI DCC.
Escenario de trabajo:
- El comerciante registra el pedido a través de REST API en la pasarela de pagos
- El
mdOrderobtenido (número de pedido) el comerciante lo transmite a la página donde se utiliza esta biblioteca js
Web SDK proporciona la posibilidad de agregar a su página de pago campos de entrada de datos de pago a través de iframe directamente desde la pasarela de pagos. La seguridad de la transmisión de datos se garantiza mediante el cifrado del protocolo HTTPS.
Ventajas:
- Seguro: La página de pago del comerciante y sus scripts no tienen acceso a los campos de pago transmitidos a través de iframe. Los datos de las tarjetas no pueden ser recopilados por scripts externos.
- Simple: El script se integra fácilmente en la página. Para cumplir con el estilo general del sitio, se requiere una personalización mínima.
- Confiable: Del lado del comerciante no se requiere cumplimiento con altos niveles de PCI DCC.
- Conveniente: Al servidor pueden ser transmitidos campos adicionales, como
email,language,phoneyjsonParams.
Desventajas:
- Por el momento Web SDK no es compatible con la función de múltiples intentos de pago.
La biblioteca ayuda en la recopilación de datos de la tarjeta, su validación y verificación, realización del pago y redirección automática del comprador a la página final a través del returnUrl especificado en la configuración.
Cómo usar
Conectar script
Entorno de prueba
<script src="https://dev.bpcbt.com/payment/modules/multiframe/main.js"></script>Entorno de producción
<script src="https://dev.bpcbt.com/payment/modules/multiframe/main.js"></script>Preparación
En primer lugar, es necesario crear un formulario HTML para recibir pagos. El formulario debe contener bloques #pan, #expiry, #cvc y botón #pay. No es obligatorio usar exactamente estos nombres de campos. Podrás configurar los nombres que necesites durante la inicialización.
Ejemplo de formulario HTML que no soporta enlaces:
<div class="card-body">
<div class="col-12">
<label for="pan" class="form-label">Card number</label>
<!-- Container for card number field -->
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Expiry</label>
<!-- Container for expiry card field -->
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<!-- Container for CVC/CVV field -->
<div id="cvc" class="form-control"></div>
</div>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<!-- Payment loader -->
<span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
<span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>Si usas enlaces, necesitas incluir un bloque HTML adicional: #select-binding.
Aquí tienes un ejemplo de formulario HTML que soporta enlaces:
<div class="card-body">
<div class="col-12" id="select-binding-container" style="display: none">
<!-- Select for bindings -->
<select class="form-select" id="select-binding" aria-label="Default select example">
<option selected value="new_card">Pay with a new card</option>
</select>
</div>
<div class="col-12">
<label for="pan" class="form-label">Card number</label>
<!-- Container for card number field -->
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Expiry</label>
<!-- Container for expiry card field -->
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<!-- Container for cvc/cvv field -->
<div id="cvc" class="form-control"></div>
</div>
<label class="col-12" id="save-card-container">
<!-- Save card checkbox -->
<input class="form-check-input" type="checkbox" value="" id="save-card" />
Save card
</label>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<!-- Payment loader -->
<span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
<span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>Puedes agregar al formulario cualquier campo adicional, como Cardholder name (nombre del titular de la tarjeta), Email (correo electrónico), Phone (número de teléfono), etc. Sin embargo, no olvides pasarlos posteriormente al método doPayment().
Inicialización Web SDK
Descripción del formulario de pago
Necesitas ejecutar la función constructor new window.PaymentForm().
window.PaymentForm() puede recibir las siguientes propiedades:
Propiedades de inicialización PaymentForm
Por defecto, apiContext se toma automáticamente del enlace utilizado para conectar el script
modules/multiframe/main.js.
Valor por defecto:
en.
Valor por defecto -
true
Por defecto
false
Por defecto
false
Por defecto
true
Por defecto
true
Valor por defecto:
field-container
Por ejemplo:
onFormValidate: (isValid) => {
alert(isValid ? 'Congratulations!' : 'Oops! We regret.');
}Ejemplo de inicialización Web SDK
const webSdkPaymentForm = new window.PaymentForm({
// Número de pedido (el registro del pedido ocurre antes de la inicialización del formulario)
mdOrder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
// Nombre de clase que será establecido para los contenedores con campos de entrada
containerClassName: "field-container",
onFormValidate: (isValid) => {
// Manejo de validación del formulario
},
// Contexto para solicitudes API
apiContext: "/payment",
// Idioma - utilizado para localización de errores y nombres de placeholders.
// El idioma debe estar soportado en la configuración del Comerciante
language: "en",
// Cambiar automáticamente el foco al completar los campos
autoFocus: true,
// Mostrar icono del sistema de pago
showPanIcon: true,
// Estilos personalizados para el icono del sistema de pago
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: document.querySelector("#pan"),
onFocus: (containerElement) => {
// Acción al recibir foco el campo
// (containerElement contiene referencia al elemento contenedor del campo)
},
onBlur: (containerElement) => {
// Acción al perder foco el campo
// (containerElement contiene referencia al elemento contenedor del campo)
},
onValidate: (isValid, containerElement) => {
// Acción al validar el campo
// (isValid es igual a true si el campo es válido, sino false)
// (containerElement contiene referencia al elemento contenedor del campo)
},
},
expiry: {
container: document.querySelector("#expiry"),
// ...
},
cvc: {
container: document.querySelector("#cvc"),
// ...
},
},
// Estilos para campos de entrada
styles: {
// Estado base
base: {
color: "black",
padding: '0px 16px',
fontSize: '18px',
fontFamily: 'monospace',
},
// Estado con foco
focus: {
color: "blue",
},
// Estado deshabilitado
disabled: {
color: "gray",
},
// Con valor válido
valid: {
color: "green",
},
// Con valor inválido
invalid: {
color: "red",
},
// Estilo para placeholder
placeholder: {
// Estilo base
base: {
color: "gray",
},
// Estilo con foco
focus: {
color: "transparent",
},
},
},
});Método destroy
El método destroy() en Web SDK se utiliza para eliminar todos los recursos y listeners de eventos asociados con una instancia específica de Web SDK. Cuando invocas el método destroy(), limpia todos los listeners de eventos y contenedores de campos de entrada que fueron creados por Web SDK durante su ciclo de vida. Esto es útil cuando ya no necesitas la instancia de Web SDK.
El método destroy() usualmente realiza las siguientes tareas:
- Elimina todos los listeners de eventos que fueron agregados a la instancia de Web SDK.
- Limpia todos los contenedores de campos de entrada creados.
Ejemplo del método destroy
document.querySelector("#destroy").addEventListener("click", function () {
webSdkPaymentForm.destroy();
});Estilización
La estilización de los contenedores a los que se pasan los campos de entrada se define de forma independiente según el diseño de su página. Los diferentes estados de los contenedores de campos de entrada se pueden estilizar utilizando las siguientes clases CSS:
-
{className}--focus- campo enfocado -
{className}--valid- campo con valor válido -
{className}--invalid- campo con valor inválido
El parámetro className se establece durante la inicialización a través del parámetro containerClassName en las propiedades de window.PaymentForm().
Ejemplo:
<style>
.field-container {
width: 100%;
height: 50px;
padding: 0;
}
.field-container--focus {
border-color: #86b7fe;
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.field-container--valid {
border-color: #198754;
}
.field-container--valid.field-container--focus {
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}
.field-container--invalid {
border-color: #dc3545;
}
.field-container--invalid.field-container--focus {
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
</style>
<script>
//...
const paymentForm = new window.PaymentForm({
//...
containerClassName: 'field-container',
//...
</script>Fuentes
En Web SDK se pueden usar fuentes del sistema/preinstaladas en los dispositivos. La fuente se establece durante la inicialización del Web SDK en la propiedad styles o customStyles. Por ejemplo en styles.base.fontFamily.
Estilización de campos de entrada
Puede personalizar la apariencia de los campos de entrada. Para esto
- Use el objeto
stylespara estilos base de todos los campos - Use
customStylespara sobrescribir estilos de campos individuales (por ejemplo, pan, expiry, cvc)
Ejemplo:
const webSdkPaymentForm = new window.PaymentForm({
// ...
// estilos por defecto para todos los campos de entrada
styles: {
base: {
color: 'black',
padding: '0px 16px',
fontSize: '18px',
fontFamily: 'Arial, sans-serif',
},
// ...
invalid: {
color: 'red',
},
// ...
},
// estilos personalizados para el campo de entrada del número de tarjeta
customStyles: {
pan: {
base: {
color: 'blue',
padding: '0px 24px',
fontSize: '22px',
},
invalid: {
color: 'orange',
},
},
},
});Configuraciones adicionales
Configure parámetros adicionales durante la inicialización, como idioma del placeholder, iconos de sistemas de pago, enmascaramiento de campos de entrada, etc. La lista completa de parámetros está disponible en Propiedades de inicialización PaymentForm.
Validación
WebSDK proporciona verificación, validación y protección solo de los campos de entrada principales requeridos para realizar el pago: Pan, Expiry, CVC.
Todos los demás campos adicionales, como Cardholder name, Phone, Email, campos que aseguran el cumplimiento de los requisitos del mandato Visa Secure Data etc., el comerciante debe validarlos independientemente de su lado.
Ejemplos de expresiones regulares para la validación de campos adicionales:
Cardholder name:
// regex: ^[A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]* [A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]*$
export function validateCardholderName({
errorMessage = 'Invalid cardholder'
} = {}) {
return (value) => {
const regex = /^[A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]* [A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]*$/;
return regex.test(String(value).trim()) ? null : errorMessage;
};
}Email:
// regex: ^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$
export function validateEmail({
errorMessage = 'Invalid email'
} = {}) {
return (value) => {
const regex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
return regex.test(String(value).toLowerCase()) ? null : errorMessage;
};
}Phone:
// regex: ^\+\d{7,15}$
export function validatePhone({
errorMessage = 'Invalid phone number'
} = {}) {
return (value) => {
const regex = /^\+\d{7,15}$/;
return regex.test(String(value).trim()) ? null : errorMessage;
};
}Pago sin tarjeta guardada (vinculación)
Paso 1. Ejecutar método de inicialización
Después de definir los parámetros del Web SDK es necesario llamar init().
Esta función devuelve callback, donde puede, por ejemplo, ocultar el cargador o hacer algo más.
init() devuelve c.
Por ejemplo:
webSdkFormWithoutBindings
.init()
.then((success) => {
console.log('success', success)
// Script inicializado exitosamente. Promise devuelve objeto que contiene información útil sobre la orden registrada
// en la pasarela de pagos. Después de esto se puede quitar el loader o ejecutar otras acciones:
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
})
.catch((error) => {
// Durante la inicialización del script surgieron errores. La ejecución posterior es imposible.
// Promise devuelve mensaje de error que podemos mostrar en la página:
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
})
.finally(() => {
// Acciones ejecutadas después de la inicialización, independientemente de su ejecución exitosa o no exitosa.
});Paso 2. Manejo del clic del botón de pago. Ejecutar método de pago
Para ejecutar el pago, llame la función doPayment().
No es necesario enviar datos de tarjeta, esto lo hará Web SDK. doPayment() devuelve Promise.
El método acepta los siguientes parámetros:
jsonParams: { "t-shirt-color": "negro", "size": "M" }
Actualización del mandato Visa Secure Data Field
Tenga en cuenta los requisitos de IPS Visa con respecto a los campos de datos adicionales necesarios para las solicitudes de autenticación EMV 3DS. Los comerciantes deben proporcionar datos completos y precisos sobre las transacciones en sus solicitudes de autenticación. Los comerciantes también deben garantizar que la URL del método 3DS realice la recopilación de datos del dispositivo para apoyar la autenticación exitosa en caso de que la URL del método 3DS sea proporcionada por el emisor.
Por lo tanto, la recopilación de campos adicionales para VISA es responsabilidad del comerciante. Puede familiarizarse con el texto completo de los requisitos en Visa Secure Data Field Mandate.
Los campos adicionales se pasan como propiedades del objeto, que es argumento de la función doPayment().
Ejemplo de llamada:
webSdkFormWithoutBindings
.doPayment({
// Parámetros adicionales
email: "foo@bar.com",
phone: "4420123456789",
cardholderName: "JOHN DOE",
jsonParams: { foo: "bar" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Manejo de errores. Para ejemplo mostraremos bloque con error
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Se ejecuta en cualquier caso. Por ejemplo, hacer el botón "Pagar" nuevamente activo.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});Demostración sin tarjeta guardada
Para fines de trabajo - registre el pedido a través de API.
Este formulario se utiliza solo con fines de demostración - use el valor Order ID =
xxxxx-xxxxx-xxxxx-xxxxxTodo el código de demostración
<div class="container_demo">
<div class="about">
<form name="formRunTest">
<label for="mdOrder"> Order ID (mdOrder) <br>
<span class="label__desc">(Debe llegar desde el backend. Esta entrada está destinada solo para demostración)</span>
</label>
<div class="run-test">
<input id="mdOrder" type="text" placeholder="Paste the mdOrder registered for sandbox"/>
<button class="btn-mini" id="load" type="submit">Load</button>
</div>
</form>
</div>
<div class="payment-form">
<div class="payment-form-loader payment-form-loader--active">
Web SDK Payment requiere la presencia de mdOrder (pedido previamente registrado en la pasarela).<br>
Se puede registrar el pedido a través de Merchant Portal o a través de API. <br><br>
O intente usar <code>xxxxx-xxxxx-xxxxx-xxxxx</code> si desea obtener solo el formulario de pago.
</div>
<div class="card-body">
<div class="col-12">
<label for="pan" class="form-label">Número de tarjeta</label>
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Fecha de vencimiento</label>
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<div id="cvc" class="form-control"></div>
</div>
<!-- Campos adicionales para Visa Mandatory -->
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Cardholder</label>
<div id="cardholder" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="Surname"value="JOHN DOE">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Mobile phone</label>
<div id="mobile" class="additional-field-container">
<input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Email address</label>
<div id="email" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
</div>
</div>
</div>
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<span
class="spinner-border spinner-border-sm me-2 visually-hidden"
role="status"
aria-hidden="true"
id="pay-spinner">
</span>
<span>Pagar</span>
</button>
<!-- Estos botones son necesarios solo para demostrar el funcionamiento del método destroy -->
<button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
<span>Destruir</span>
</button>
<button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
<span>Inicializar</span>
</button>
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Función para inicialización del formulario de pago con posibilidad de reutilización después de destrucción
function initPaymentForm() {
const mrOrderInput = document.getElementById("mdOrder");
mrOrderInput.classList.remove("invalid");
if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
mrOrderInput.classList.add("invalid");
return;
}
// Inicialización de Web SDK. Se necesita identificador de pedido mdOrder.
initPayment(mrOrderInput.value);
}
document.formRunTest.addEventListener("submit", function (e) {
e.preventDefault();
// Inicialización del formulario de pago
initPaymentForm();
});
let webSdkFormWithoutBindings;
// Array de objetos para campos adicionales, que contienen id del campo, su plantilla de validación y símbolos permitidos para entrada
const mandatoryFieldsWithoutBinding = [
{
id: '#cardholder',
template: /^[a-zA-Z '`.\-]{4,24}$/,
replace: /[^a-zA-Z ' \-`.]/g,
},
{
id: '#mobile',
template: /^\+?[1-9][0-9]{7,14}$/,
replace: /[^0-9\+]/g,
},
{
id: '#email',
template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
replace: /[^a-zA-Z0-9@._-]/g,
}
]
function initPayment(mdOrder) {
webSdkFormWithoutBindings = new window.PaymentForm({
mdOrder: mdOrder,
onFormValidate: () => {},
language: "en", // Idioma (inglés)
containerClassName: "field-container",
autoFocus: true,
showPanIcon: true,
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: document.querySelector("#pan"),
},
expiry: {
container: document.querySelector("#expiry"),
},
cvc: {
container: document.querySelector("#cvc"),
},
},
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
// Acción después de la inicialización
webSdkFormWithoutBindings.init().then(() => {
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
})
.catch((e) => {
// Visualización de error durante la inicialización del webSDK
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
}
.finally(() => {
// Validación y reemplazo automático de caracteres no permitidos
mandatoryFieldsWithBinding.forEach(item => {
const field = document.querySelector(item.id)
field.closest(".additional-field").style.display = '';
field.addEventListener('input', () => {
let inputValue = field.querySelector('input')
inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
if (item.id.includes("#cardholder")) {
inputValue.value = inputValue.value.toUpperCase()
}
if (item.template) {
// Clase CSS ".additional-field-invalid" para visualizar campos no válidos
if (item.template.test(inputValue.value)) {
field.classList.remove("additional-field-invalid")
} else {
field.classList.add("additional-field-invalid")
}
}
})
})
})
}
// Controlador "Pagar"
document.querySelector("#pay").addEventListener("click", () => {
const payButton = document.querySelector("#pay");
// Hacemos el botón "Pagar" inactivo para evitar pagos dobles
payButton.disabled = true;
// Mostramos el cargador al usuario
const spinnerEl = document.querySelector("#pay-spinner");
spinnerEl.classList.remove("visually-hidden");
// Ocultamos el contenedor de error
const errorEl = document.querySelector("#error");
errorEl.classList.add("visually-hidden");
// Validación de campos adicionales Visa Mandatory
if (document.querySelectorAll('.additional-field-invalid').length) {
errorEl.innerHTML = "Form is not valid";
errorEl.classList.remove('visually-hidden');
spinnerEl.classList.add('visually-hidden');
return
}
// Comenzamos el proceso de pago
webSdkFormWithoutBindings
.doPayment({
// Parámetros adicionales
email: document.querySelector('#email input').value,
phone: document.querySelector('#mobile input').value,
cardholderName: document.querySelector('#cardholder input').value,
jsonParams: { size: "L" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Se ejecuta en caso de error
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Se ejecuta en cualquier caso, por ejemplo, hacemos que el botón "Pagar" esté activo nuevamente.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
});
// Manejador "Destruir"
document
.querySelector("#destroyFormWithoutCredentials")
.addEventListener("click", function () {
// Eliminamos el botón "Destruir" y mostramos el botón "Inicializar" para demostración
this.style.display = "none";
document.querySelector("#reinitFormWithoutCredentials").style.display =
"";
// Destruimos el formulario de pago Web SDK
webSdkFormWithoutBindings.destroy();
});
// Manejador "Inicializar formulario"
document
.querySelector("#reinitFormWithoutCredentials")
.addEventListener("click", function () {
// Eliminamos el botón "Inicializar" y mostramos el botón "Destruir" para demostración
this.style.display = "none";
document.querySelector("#destroyFormWithoutCredentials").style.display =
"";
// Inicialización del formulario de pago
initPaymentForm();
});
});
</script>Pago con tarjeta guardada (enlace)
Paso 1. Ejecutar el método de inicialización
Después de determinar los parámetros del Web SDK es necesario llamar a init().
Esta función devuelve un callback, donde puede, por ejemplo, ocultar el cargador o hacer cualquier otra cosa.
init() devuelve una Promise.
Por ejemplo:
// Inicialización
webSdkFormWithBindings
.init()
.then(({ orderSession }) => {
// El objeto `orderSession` contiene toda la información sobre el pedido, incluyendo información sobre las credenciales guardadas (sobre el enlace).
console.info("orderSession", orderSession);
// Mostrar elemento de selección de tarjeta guardada
document.querySelector("#select-binding-container").style.display =
orderSession.bindings.length ? "" : "none";
// Rellenar la selección con credenciales guardadas
orderSession.bindings.forEach((binding) => {
document
.querySelector("#select-binding")
.options.add(new Option(binding.pan, binding.id));
});
// Manejo de selección de credenciales guardadas o nueva tarjeta
document
.querySelector("#select-binding")
.addEventListener("change", function () {
const bindingId = this.value;
if (bindingId !== "new_card") {
webSdkFormWithBindings.selectBinding(bindingId);
// Ocultar casilla "Guardar tarjeta"
document.querySelector("#save-card-container").style.display = "none";
} else {
// Selección de enlace con null significa transición a nueva tarjeta
webSdkFormWithBindings.selectBinding(null);
// Mostrar casilla "Guardar tarjeta"
document.querySelector("#save-card-container").style.display = "";
}
});
// Cuando el formulario está listo, podemos ocultar el cargador
document.querySelector("#pay-form-loader").classList.add("visually-hidden");
})
.catch((error) => {
// Ocurrieron errores durante la inicialización del script. No es posible continuar la ejecución.
// Promise devuelve un mensaje de error, que podemos mostrar en la página:
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
});Paso 2. Manejo del clic del botón de pago. Ejecutar método de pago
Para ejecutar el pago, llame a la función doPayment().
No es necesario enviar los datos de la tarjeta, esto lo hará el Web SDK. doPayment() devuelve una Promise.
El método acepta los siguientes parámetros:
document.querySelector('#save-card').checked
jsonParams: { "t-shirt-color": "negro", "size": "M" }
Aplicación de tarjetas guardadas
Para pagar con credenciales guardadas, necesita pasar el bindingId seleccionado al formulario antes de llamar a doPayment:
webSdkFormWithBindings.selectBinding('bindingId');
Si cambió de opinión y quiere pagar con una nueva tarjeta, no olvide eliminar el bindingId del formulario:
webSdkFormWithBindings.selectBinding(null);
Ejemplo de llamada:
webSdkFormWithBindings
.doPayment({
// Parámetros adicionales
email: "foo@bar.com",
phone: "4420123456789",
saveCard: document.querySelector("#save-card").checked,
cardholderName: "JOHN DOE",
jsonParams: { foo: "bar" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Manejo de errores. Para el ejemplo mostraremos un bloque con error
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Ejecutar en cualquier caso. Por ejemplo, hacer el botón "Pagar" activo nuevamente.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});Demostración con tarjeta guardada
Para fines de trabajo - registre el pedido a través de API.
Este formulario se usa solo para fines de demostración - use el valor Order ID =
xxxxx-xxxxx-xxxxx-xxxxxTodo el código de demostración
<div class="container_demo">
<div class="about">
<form name="formRunTest">
<label for="mdOrder"> Identificador del pedido (mdOrder) <br/>
<span class="label__desc">(Se proporciona solo para fines demostrativos. El identificador del pedido debe provenir del backend.)</span>
</label>
<div class="run-test">
<input id="mdOrder" type="text" placeholder="Pegue mdOrder, registrado en Sandbox"/>
<button class="btn-mini" id="load" type="submit">Cargar</button>
</div>
</form>
</div>
<div class="payment-form">
<div class="payment-form-loader payment-form-loader--active">
Para Web SDK Payment se requiere mdOrder (pedido previamente registrado en la pasarela).<br/>
Puede registrar un pedido a través del panel de control personal o a través de API. <br/><br/>
O intente usar <code>xxxxx-xxxxx-xxxxx-xxxxx</code> si solo quiere verificar el formulario de pago.
</div>
<div id="pay-form-loader" class="spinner-container visually-hidden">
<div class="spinner-border" role="status"></div>
</div>
<div class="card-body">
<div class="col-12" id="select-binding-container" style="display: none">
<select class="form-select" id="select-binding" aria-label="Default select example">
<option selected value="new_card">Pago con tarjeta nueva</option>
</select>
</div>
<div class="col-12">
<label for="pan" class="form-label">Número de tarjeta</label>
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Fecha de vencimiento</label>
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<div id="cvc" class="form-control"></div>
</div>
<label class="col-12" id="save-card-container">
<input class="form-check-input" type="checkbox" value="" id="save-card" />
Guardar tarjeta
</label>
<!--Campos adicionales Visa Mandatory -->
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Titular de la tarjeta</label>
<div id="cardholder" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="NAME SURNAME"value="JOHN DOE">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Teléfono</label>
<div id="mobile" class="additional-field-container">
<input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Correo electrónico</label>
<div id="email" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
</div>
</div>
</div>
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<span
class="spinner-border spinner-border-sm me-2 visually-hidden"
role="status"
aria-hidden="true"
id="pay-spinner">
</span>
<span>Pagar</span>
</button>
<!-- Estos botones son necesarios solo para demostrar el funcionamiento del método destroy -->
<button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
<span>Destruir</span>
</button>
<button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
<span>Inicializar</span>
</button>
<div class="error my-2 visually-hidden" id="error"></div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Función para inicializar el formulario de pago para su reutilización después de la destrucción
function initPaymentForm() {
const mrOrderInput = document.getElementById("mdOrder");
mrOrderInput.classList.remove("invalid");
if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
mrOrderInput.classList.add("invalid");
return;
}
// Eliminar marcador de posición
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
// Agregar cargador de formularios de pago
document
.querySelector("#pay-form-loader")
.classList.remove("visually-hidden");
// Inicialización de Web SDK. Se requiere mdOrder obligatorio (identificador de pedido).
initPayment(mrOrderInput.value);
}
// Inicialización del manejador para datos de prueba
function handleSubmit(e) {
e.preventDefault();
// Inicializar formulario de pago
initPaymentForm();
}
// Registro del evento para ejemplo de entrada
document.formRunTest.addEventListener("submit", handleSubmit);
let webSdkFormWithBindings;
// Array de objetos para campos adicionales que contienen id del campo, su plantilla de validación y caracteres permitidos para entrada
const mandatoryFieldsWithoutBinding = [
{
id: '#cardholder',
template: /^[a-zA-Z '`.\-]{4,24}$/,
replace: /[^a-zA-Z ' \-`.]/g,
},
{
id: '#mobile',
template: /^\+?[1-9][0-9]{7,14}$/,
replace: /[^0-9\+]/g,
},
{
id: '#email',
template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
replace: /[^a-zA-Z0-9@._-]/g,
}
]
function initPayment(mdOrder) {
webSdkFormWithBindings = new window.PaymentForm({
// Número de pedido (registro del pedido ocurre antes de la inicialización del formulario)
mdOrder: mdOrder,
// Manejo de validación del formulario
onFormValidate: (isValid) => {
// Por ejemplo, puedes deshabilitar los botones "Pagar" y "Obtener token" si el formulario no es válido, por ejemplo:
// const payButton = document.querySelector('#pay');
// payButton.disabled = !isValid;
},
// Contexto para llamadas API
apiContext: "/payment",
// El idioma se usa para localización de errores y nombres para marcadores de posición.
// El idioma debe estar soportado en la configuración del comerciante
language: "en",
// Nombre de clase para elementos contenedores que contienen iframe
containerClassName: "field-container",
// Cambio automático de foco al llenar campos
autoFocus: true,
// Mostrar icono del sistema de pago
showPanIcon: true,
// Estilos adicionales para el icono del sistema de pago
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
// Configuración de campo
fields: {
// Elemento contenedor en el que se colocará el iframe con el campo
pan: {
container: document.querySelector("#pan"),
},
// Fecha de vencimiento de la tarjeta
expiry: {
container: document.querySelector("#expiry"),
},
// Código CVC/CVV
cvc: {
container: document.querySelector("#cvc"),
},
},
// Estilos adicionales para personalizar la apariencia de los campos de entrada en iframes
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
disabled: {
backgroundColor: "#e9ecef",
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
// Acción después de la inicialización
webSdkFormWithBindings
.init()
.then(({ orderSession }) => {
// El objeto `orderSession` contiene toda la información sobre el pedido, incluyendo información sobre credenciales guardadas (sobre vinculación).
console.info("orderSession", orderSession);
// Mostrar vinculación seleccionada
document.querySelector("#select-binding-container").style.display =
orderSession.bindings.length ? "" : "none";
// Rellenar selección con credenciales guardadas
orderSession.bindings.forEach((binding) => {
document
.querySelector("#select-binding")
.options.add(new Option(binding.pan, binding.id));
});
// Manejo de selección de credenciales guardadas o nueva tarjeta
document
.querySelector("#select-binding")
.addEventListener("change", function () {
const bindingId = this.value;
if (bindingId !== "new_card") {
// Establecer identificador de vinculación
webSdkFormWithBindings.selectBinding(bindingId);
// Hide the 'Save card' checkbox
document.querySelector("#save-card-container").style.display =
"none";
} else {
// Selección de vinculación con null significa transición a nueva tarjeta
webSdkFormWithBindings.selectBinding(null);
// Mostrar casilla "Guardar tarjeta"
document.querySelector("#save-card-container").style.display = "";
}
});
// Cuando el formulario esté listo, podemos ocultar el cargador
document
.querySelector("#pay-form-loader")
.classList.add("visually-hidden");
// Eliminar evento para ejemplo de entrada
document.formRunTest.removeEventListener("submit", handleSubmit);
})
.catch((error) => {
// Ejecutar en caso de error
const errorEl = document.querySelector("#error");
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Validación y sustitución automática de caracteres no válidos
mandatoryFieldsWithBinding.forEach(item => {
const field = document.querySelector(item.id)
field.closest(".additional-field").style.display = '';
field.addEventListener('input', () => {
let inputValue = field.querySelector('input')
inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
if (item.id.includes("#cardholder")) {
inputValue.value = inputValue.value.toUpperCase()
}
if (item.template) {
// Clase CSS ".additional-field-invalid" para mostrar campos no válidos
if (item.template.test(inputValue.value)) {
field.classList.remove("additional-field-invalid")
} else {
field.classList.add("additional-field-invalid")
}
}
})
})
});
}
// Manejador de pago
document.querySelector("#pay").addEventListener("click", () => {
// Hacer inactivo el botón "Pagar" para evitar pagos dobles
const payButton = document.querySelector("#pay");
payButton.disabled = true;
// Mostrar cargador para el usuario
const spinnerEl = document.querySelector("#pay-spinner");
spinnerEl.classList.remove("visually-hidden");
// Ocultar contenedor de errores
const errorEl = document.querySelector("#error");
errorEl.classList.add("visually-hidden");
// Validación de campos adicionales Visa Mandatory
if (document.querySelectorAll('.additional-field-invalid').length) {
errorEl.innerHTML = "Form is not valid";
errorEl.classList.remove('visually-hidden');
spinnerEl.classList.add('visually-hidden');
return
}
// Iniciar pago
webSdkFormWithBindings
.doPayment({
// Parámetros adicionales
email: document.querySelector('#email input').value,
phone: document.querySelector('#mobile input').value,
cardholderName: document.querySelector('#cardholder input').value,
saveCard: document.querySelector("#save-card").checked,
jsonParams: { foo: "bar" },
})
.then((result) => {
// Aquí se puede hacer algo con el resultado del pago
console.log("result", result);
})
.catch((e) => {
// Ejecutar en caso de error
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Ejecutar en cualquier caso. Por ejemplo, activar de nuevo el botón «Pagar».
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
});
// manejador Destroy
document
.querySelector("#destroyFormWithCredentials")
.addEventListener("click", function () {
// Eliminar el botón Destroy y mostrar el botón de reinicialización para demostración
this.style.display = "none";
document.querySelector("#reinitFormWithCredentials").style.display = "";
// Ejecutar el método destroy en el formulario web SDK
webSdkFormWithBindings.destroy();
});
// Inicializar el manejador del formulario de pago
document
.querySelector("#reinitFormWithCredentials")
.addEventListener("click", function () {
// Eliminar el botón Reinit y mostrar el botón Destroy para demostración
this.style.display = "none";
document.querySelector("#destroyFormWithCredentials").style.display = "";
// Inicialización del formulario de pago
initPaymentForm();
});
});
</script>Procesamiento de datos devueltos por los métodos init( ) y doPayment( )
Método init( )
El método devuelve un Promise, que al ejecutarse exitosamente devuelve un objeto con información sobre el pedido registrado. Por razones de seguridad, este objeto no contiene datos de tarjeta y otra información confidencial.
El método devuelve los siguientes parámetros:
Es posible la presencia de campos adicionales, o la ausencia de algunos campos de la lista anterior.
Ejemplo
{
"mdOrder": "5541f44c-d7ec-7a6c-997d-1d4d0007bc7d",
"orderSession": {
"amount": "100000",
"currencyAlphaCode": "BYN",
"currencyNumericCode": "933",
"sessionTimeOverAt": 1740385187287,
"orderNumber": "27000",
"description": "",
"cvcNotRequired": false,
"bindingEnabled": false,
"bindingDeactivationEnabled": false,
"merchantOptions": [
"MASTERCARD_TDS",
"MASTERCARD",
"VISA",
"VISA_TDS",
"CARD"
],
"customerDetails": {},
"merchantInfo": {
"merchantUrl": "http://google.com",
"merchantFullName": "Coffee to Go",
"merchantLogin": "CoffeToGo",
"captchaMode": "NONE",
"loadedResources": {
"logo": true,
"footer": false
},
"custom": false
},
"bindings": [
{
"cardholderName": "CARDHOLDER NAME",
"createdAt": 1712321609666,
"id": "83ffea5d-061f-7eca-912a-02ff0007bc7d",
"pan": "4111 11** **** 1111",
"expiry": "12/24",
"cardInfo": {
"name": "TEST BANK-A",
"nameEn": "TEST BANK-A",
"backgroundColor": "#fbf0ff",
"backgroundGradient": [
"#fafafa",
"#f3f0ff"
],
"supportedInvertTheme": false,
"backgroundLightness": true,
"country": "hu",
"defaultLanguage": "en",
"textColor": "#040e5d",
"url": null,
"logo": "logo/main/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"logoInvert": "logo/invert/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"logoMini": "logo/mini/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"design": null,
"paymentSystem": "visa",
"cobrand": null,
"productCategory": null,
"productCode": null,
"mnemonic": "TEST BANK-A",
"params": null
}
}
]
}
}Ejecución no exitosa
En caso de ejecución no exitosa, Promise devuelve un mensaje de error, que podemos mostrar en la página. Ejemplo de mensaje: Error: Formulario inválido.
Ejemplos de código de procesamiento de datos devueltos por el método de inicialización init() se presentan en la sección Paso 1. Ejecutar método de inicialización para pago sin tarjeta guardada y con tarjeta guardada.
Método doPayment( )
El método devuelve Promise, que al ejecutarse exitosamente devuelve un objeto con información sobre el pago realizado.
El método devuelve los siguientes parámetros:
Es posible la presencia de campos adicionales, o la ausencia de algunos campos de la lista anterior.
Ejemplo
{
"redirectUrl": "https://bankhost.com/payment/merchants/ecom/finish.html?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"finishedPaymentInfo": {
"paymentSystem": "MASTERCARD",
"merchantShortName": "CoffeToGo",
"merchantLogin": "CoffeToGo",
"merchantFullName": "Coffee to Go",
"approvalCode": "123456",
"orderNumber": "4003",
"formattedTotalAmount": "15.00",
"backUrl": "https://www.coffeetogo.com/congratulation?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"failUrl": "https://www.coffeetogo.com/someproblem?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"terminalId": "12345678",
"orderDescription": "Order 123",
"displayErrorMessage": "",
"loadedResources": {
"footer": false,
"logo": false
},
"currencyAlphaCode": "EUR",
"orderFeatures": [
"ACS_IN_IFRAME",
"BINDING_NOT_NEEDED"
],
"isWebView": false,
"actionCodeDetailedDescription": "Request processed successfully",
"transDate": "29.11.2024 15:19:30",
"currency": "978",
"actionCode": 0,
"expiry": "12/2024",
"formattedAmount": "15.00",
"actionCodeDescription": "",
"formattedFeeAmount": "0.00",
"email": "address@mail.com",
"amount": "1500",
"merchantCode": "12345678",
"ip": "x.x.x.x",
"panMasked": "555555**5599",
"successUrl": "https://www.coffeetogo.com/congratulation?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"paymentWay": "CARD",
"processingErrorType": {
"value": "NO_ERROR",
"messageCode": "payment.errors.no_error",
"apiErrorCodeMessage": "payment.errors.no_error.code"
},
"panMasked4digits": "**** **** **** 5599",
"amountsInfo": {
"currencyDto": {
"alphabeticCode": "EUR",
"numericCode": "978",
"minorUnit": 2
},
"depositedAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"totalAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"refundedAmount": {
"value": 0,
"formattedValue": "0.00"
},
"approvedAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"feeAmount": {
"value": 0,
"formattedValue": "0.00"
},
"paymentAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"amount": {
"value": 1500,
"formattedValue": "15.00"
},
"depositedTotalAmount": {
"value": 1500,
"formattedValue": "15.00"
}
},
"errorTypeName": "SUCCESS",
"feeAmount": "0",
"totalAmount": "1500",
"orderParams": {
"phone": "+4915112345678",
"foo": "bar",
"paymentMethod": "multiframe-sdk"
},
"orderExpired": false,
"refNum": "111111111111",
"finishPageLogin": "ecom",
"sessionExpired": false,
"cardholderName": "JOHN DOE",
"paymentDate": "29.11.2024 15:19:49",
"merchantUrl": "https://www.coffeetogo.com/",
"status": "DEPOSITED"
}
}Ejecución no exitosa
En caso de ejecución no exitosa, Promise devuelve un mensaje de error que podemos mostrar en la página. Ejemplo de mensaje: Error: Operation declined. Please check the data and available balance of the account.
Los ejemplos de código para el procesamiento de datos devueltos por el método doPayment(), se presentan en la sección Paso 2. Procesamiento del clic del botón de pago. Ejecutar método de pago para el pago sin tarjeta guardada y con tarjeta guardada
Redirección automática después de realizar el pago
Después del pago ocurre una redirección automática desde la página con Web SDK. Para procesar el objeto devuelto por el método doPayment() directamente en la página con Web SDK, se requiere desactivar la redirección automática después de realizar el pago. Para esto se requiere un permiso especial en el sistema ‒ en la pasarela de pagos para este comerciante debe estar habilitado el permiso Soporte Acs IFrame habilitado. Para obtener el permiso, contacte al servicio de soporte técnico del banco. También durante la inicialización del Web SDK en el objeto transmitido debe contener la propiedad shouldHandleResultManually: true.
Por ejemplo:
webSdkForm = new window.PaymentForm({
...
shouldHandleResultManually: true,
...
});Web SDK en React SPA
Al usar Web SDK en una single page application (SPA) en React es necesario inicializar Web SDK, ejecutando el método webSdkPaymentForm.init() en cada renderizado inicial de la página con el formulario Web SDK en SPA.
En el evento de reinicio de la página con el formulario Web SDK (es decir, al navegar a otra página), es necesario ejecutar el método webSdkPaymentForm.destroy(). Esto es importante, ya que en la página debe permanecer solo un manejador de formulario webSDK (multiframe-commutator).
Al regresar a la página con el formulario Web SDK es necesario realizar nuevamente la inicialización utilizando el método webSdkPaymentForm.init().
Tenga en cuenta que para utilizar esta biblioteca se requiere cumplimiento del estándar PCI DSS, ya que procesa datos de tarjeta. Más información sobre PCI DSS aquí.
Ejemplo de componente React
import { useEffect, useRef } from "react";
function addScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.setAttribute("src", src);
script.addEventListener("load", resolve);
script.addEventListener("error", reject);
document.body.appendChild(script);
});
}
function App() {
const panRef = useRef(null);
const expiryRef = useRef(null);
const cvcRef = useRef(null);
const selectBindingRef = useRef(null);
const saveCardContainerRef = useRef(null);
const payButtonRef = useRef(null);
let webSdkPaymentForm = null;
useEffect(() => {
const initPaymentForm = async () => {
await addScript(
"https://dev.bpcbt.com/payment/modules/multiframe/main.js",
);
webSdkPaymentForm = new window.PaymentForm({
mdOrder: mdOrder,
containerClassName: "field-container",
onFormValidate: (isValid) => {
// Manejo de validación de formulario
},
apiContext: "/payment",
language: "en",
autoFocus: true,
showPanIcon: true,
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: panRef.current,
onFocus: (containerElement) => {
// Manejo de enfoque
},
onBlur: (containerElement) => {
// Manejo de pérdida de enfoque
},
onValidate: (isValid, containerElement) => {
// Manejo de validación
},
},
expiry: {
container: expiryRef.current,
// Configuración del campo de fecha de expiración
},
cvc: {
container: cvcRef.current,
// Configuración del campo CVC/CVV
},
},
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
focus: {
color: "blue",
},
disabled: {
color: "gray",
},
valid: {
color: "green",
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
webSdkPaymentForm
.init()
.then(({ orderSession }) => {
console.info("orderSession", orderSession);
if (orderSession.bindings.length) {
selectBindingRef.current.style.display = "";
} else {
selectBindingRef.current.style.display = "none";
}
if (orderSession.bindingEnabled) {
saveCardContainerRef.current.style.display = "";
} else {
saveCardContainerRef.current.style.display = "none";
}
orderSession.bindings.forEach((binding) => {
const option = new Option(binding.pan, binding.id);
selectBindingRef.current.options.add(option);
});
})
.catch(() => {
// Manejo de error de inicialización
});
};
initPaymentForm();
return () => {
if (webSdkPaymentForm) {
webSdkPaymentForm.destroy();
}
};
}, []);
const handlePayment = () => {
payButtonRef.current.disabled = true;
webSdkPaymentForm
.doPayment({})
.then((result) => {
// Manejo de pago exitoso
})
.catch((e) => {
alert("Error");
})
.finally(() => {
payButtonRef.current.disabled = false;
});
};
const handleSelectBinding = () => {
const bindingId = selectBindingRef.current.value;
if (bindingId !== "new_card") {
webSdkPaymentForm.selectBinding(bindingId);
saveCardContainerRef.current.style.display = "none";
} else {
webSdkPaymentForm.selectBinding(null);
saveCardContainerRef.current.style.display = "";
}
};
return (
<div className="container">
<div className="websdk-form">
<div className="card-body">
<div
className="col-12"
id="select-binding-container"
onChange={handleSelectBinding}
>
<select
className="form-select"
id="select-binding"
ref={selectBindingRef}
aria-label="Default select example"
>
<option value="new_card">Pagar con nueva tarjeta</option>
</select>
</div>
<div className="col-12 input-form">
<label htmlFor="pan" className="form-label">
Número de tarjeta
</label>
<div id="pan" className="form-control" ref={panRef}></div>
</div>
<div className="col-6 col-expiry input-form">
<label htmlFor="expiry" className="form-label">
Vencimiento
</label>
<div id="expiry" className="form-control" ref={expiryRef}></div>
</div>
<div className="col-6 col-cvc">
<label htmlFor="cvc" className="form-label">
CVC / CVV
</label>
<div id="cvc" className="form-control" ref={cvcRef}></div>
</div>
<label className="col-12" id="save-card-container">
<input
className="form-check-input me-1"
ref={saveCardContainerRef}
type="checkbox"
value=""
id="save-card"
/>
Guardar tarjeta
</label>
</div>
<div className="pay-control">
<button
className="btn btn-primary btn-lg"
type="submit"
id="pay"
ref={payButtonRef}
onClick={handlePayment}
>
Pagar
</button>
</div>
<div
className="error my-2 text-center text-danger visually-hidden"
id="error"
></div>
</div>
</div>
);
}
export default App;