HTML
Poner a disposición el pago con tarjeta mediante Checkout Form es un proceso sencillo que solo requiere incluir un script en un formulario HTML de estructura básica sin las necesidad de realizar requests directamente a Toku. A continuación, se muestra un ejemplo básico de formulario:
<form id="creditCardForm">
<label for="cardNumber">Card Number:</label>
<div id="pan" style="width: 17em; height: 3em;"></div>
<label for="expirationDate">Expiration Date (MM/YY):</label>
<input type="text" id="expirationDate" name="expirationDate" placeholder="MM/YY" required><br>
<label for="cvv">CVV:</label>
<div id="cvv" style="width: 4em; height: 2em;"></div>
<label for="cardholderName">Cardholder's Name:</label>
<input type="text" id="cardholderName" name="cardholderName" required><br>
<button id="submitButton" type="button">Submit</button>
</form>
Es importante destacar que los IDs de los elementos
En el caso de un pago usando una tarjeta previamente inscrita, no es necesario el elemento para el pan.
Script
<!-- Import the JavaScript file -->
<script src="https://storage.googleapis.com/toku-embedded-portal/checkout-form.min.js"></script>
<script>
let tokuCheckoutForm = null;
document.addEventListener('DOMContentLoaded', function () {
tokuCheckoutForm = new TokuCheckoutForm({
panDivId: 'pan',
cvvDivId: 'cvv',
environment: params.get('environment') || 'PROD',
onSuccess: () => {
console.log('Success');
hideLoadingIndicator();
},
onError: () => {
console.log('Error');
hideLoadingIndicator();
},
onPanFocus: tokuCheckoutForm => {
console.log('panFocus');
},
onPanBlur: tokuCheckoutForm => {
console.log('panBlur');
},
onCvvFocus: tokuCheckoutForm => {
console.log('CvvFocus');
tokuCheckoutForm.setCvvStylesheet('input { color: green }');
},
onCvvBlur: tokuCheckoutForm => {
console.log('CvvBlur');
tokuCheckoutForm.setCvvStylesheet('input { color: red }');
},
onPanChange: data => {
console.log('onPanChange' + JSON.stringify(data));
},
onCvvChange: data => {
console.log('onCvvChange' + JSON.stringify(data));
},
onCardValidationLoading: () => {
console.log('onCardValidationLoading');
},
onCardValidationCompleted: data => {
console.log('onCardValidationCompleted' + JSON.stringify(data));
},
panStyle: {
width: '15em',
height: '1em',
'border-radius': '0.5rem',
border: '2px solid',
padding: '0.5rem',
outline: 'none',
},
cvvStyle: { width: '3em', height: '1em', 'box-shadow': '0 0 5px rgba(0, 0, 0, 0.2)' },
options: {
maskPan: true,
panPlaceholder: 'Card Number',
cvvPlaceholder: 'CVV',
},
panStylesheet: 'input { color: blue; } input:focus { color: green }',
cvvStylesheet: '',
organizationId: <org_id_encrypted>,
accountId: <acc_id_encrypted>,
tokuProduct: <toku_product>,
});
});
document.getElementById("submitButton").addEventListener('click', function () {
const formData = {
expirationDate: document.getElementById('expirationDate')?.value,
cardholderName: document.getElementById('cardholderName')?.value,
};
tokuCheckoutForm.processOperation("PAYMENT", {
organizationId: <org_id_encrypted>,
accountId: <acc_id_encrypted>,
customerId: <cus_id_encrypted>,
tokuProducts: [<toku_product>],
expirationDate: formData.expirationDate,
customerName: formData.cardholderName,
invoiceIds: [<in_id1_encrypted>, <in_id2_encrypted>],
amount: <amount>,
tokuProductsForInscription: [<toku_product_for_inscription>],
subscriptionId: <subscription_id_encrypted>,
paymentMethodId: <payment_method_id_encrypted>,
installments: <number_of_installments>
});
});
document.getElementById('expirationDate').addEventListener('input', function (e) {
let input = e.target.value.replace(/\D/g, '').substring(0, 4); // Allow only digits and limit to 4 chars
if (input.length >= 2) {
input = input.slice(0, 2) + '/' + input.slice(2); // Add slash after the second digit
}
e.target.value = input;
});
</script>
TokuCheckoutForm
<th>
Tipo
</th>
<th>
Descripción
</th>
<th>
Requerido
</th>
</tr>
<td>
`string`
</td>
<td>
Identificador del div asociado al **pan** en el form.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
cvvDivId
</td>
<td>
`string`
</td>
<td>
Identificador del div asociado al **cvv** en el form.
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
environment
</td>
<td>
`string`
</td>
<td>
Ambiente de trabajo. Debe ser `PROD`.
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
onSuccess
</td>
<td>
`function`
</td>
<td>
Función gatillada al éxito
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
onError
</td>
<td>
`function`
</td>
<td>
Función gatillada al error
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
onPanFocus
</td>
<td>
`function`
</td>
<td>
Función gatillada al enfocar el `input` del pan.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onPanBlur
</td>
<td>
`function`
</td>
<td>
Función gatillada al desenfocar el `input` del pan.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onCvvFocus
</td>
<td>
`function`
</td>
<td>
Función gatillada al enfocar el `input` del cvv.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onCvvBlur
</td>
<td>
`function`
</td>
<td>
Función gatillada al desenfocar el `input` del cvv.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onPanChange
</td>
<td>
`function`
</td>
<td>
Función gatillada cada vez que el usuario cambia el número de tarjeta. Recibe como primer argumento `data` un objeto con los siguientes atributos:
* `length`: El largo del número de tarjeta ingresado hasta ahora.
* `cardInstitution`: `visa`, `mastercard` o `americanexpress`
* `luhnCheck`: `true` si el número ingresado hasta ahora pasa el [algoritmo de Luhn](https://es.wikipedia.org/wiki/Algoritmo_de_Luhn).
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onCvvChange
</td>
<td>
`function`
</td>
<td>
Función gatillada cada vez que el usuario cambia el cvv. Recibe como primer argumento `data` un objeto con los siguientes atributos:
* `length`: El largo del número de tarjeta ingresado hasta ahora.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onCardValidationLoading
</td>
<td>
`function`
</td>
<td>
Función gatillada cuando se va a buscar el país y tipo de la tarjeta.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
onCardValidationCompleted
</td>
<td>
`function`
</td>
<td>
Función gatillada cuando se obtiene el país y tipo de la tarjeta. Recibe como primer argumento `data` un objeto con los siguientes atributos:
* `success`: `true` si la validación fue exitosa.
* `country`: El país de emisión de la tarjeta.
* `type`: `credit` o `debit`.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
panStyle
</td>
<td>
`dict`
</td>
<td>
Estilo del input del **pan**
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
cvvStyle
</td>
<td>
`dict`
</td>
<td>
Estilo del input del **cvv**
</td>
<td>
:radio_button: Si \*
</td>
</tr>
<tr>
<td>
panStylesheet
</td>
<td>
`string`
</td>
<td>
Estilos para insertar en un tag `<style>` en el `iframe` del `pan`
</td>
<td>
No
</td>
</tr>
<tr>
<td>
cvvStylesheet
</td>
<td>
`string`
</td>
<td>
Estilos para insertar en un tag `<style>` en el `iframe` del `cvv`
</td>
<td>
No
</td>
</tr>
<tr>
<td>
options
</td>
<td>
`dict`
</td>
<td>
`maskPan`: `true` para ofuscar el `pan` cuando se desenfoca el `input`.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
organizationId
</td>
<td>
`string`
</td>
<td>
id organization encriptado.
</td>
<td>
Si
</td>
</tr>
<tr>
<td>
accountId
</td>
<td>
`string`
</td>
<td>
id account encriptado.
</td>
<td>
Si
</td>
</tr>
<tr>
<td>
tokuProduct
</td>
<td>
`string`
</td>
<td>
Toku product que se usará para procesar el pago. Por ahora, solo soporta `payment_orchestration_onetime`.
</td>
<td>
Si
</td>
</tr>
| Nombre |
|---|
| panDivId |
TokuCheckoutForm.processOperation
<th>
Tipo
</th>
<th>
Descripción
</th>
<th>
Requerido
</th>
</tr>
<td>
`string`
</td>
<td>
Tipo de operación a realizar
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
body
</td>
<td>
`json`
</td>
<td>
Cuerpo para la request
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
| Nombre |
|---|
| TokuOperationType |
TokuCheckoutForm.processOperation - body
<th>
Tipo
</th>
<th>
Descripción
</th>
<th>
Requerido
</th>
</tr>
<td>
`string`
</td>
<td>
id organization encriptado
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
accountId
</td>
<td>
`string`
</td>
<td>
id account encriptado
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
customerId
</td>
<td>
`string`
</td>
<td>
id customer encriptado
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
tokuProducts
</td>
<td>
`list[string]`
</td>
<td>
Lista de toku products que se usarán para procesar el pago. Por ahora, solo soporta
`payment_orchestration_onetime`
.
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
expirationDate
</td>
<td>
`string`
</td>
<td>
Fecha expiración de tarjeta ingresada. Requerido si no se manda
`paymentMethodId`
.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
customerName
</td>
<td>
`string`
</td>
<td>
Nombre del portador de tarjeta ingresada. Requerido si no se manda
`paymentMethodId`
.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
invoiceIds
</td>
<td>
`list`
</td>
<td>
Lista de id invoices encriptados
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
amount
</td>
<td>
`float`
</td>
<td>
monto total a pagar
</td>
<td>
:radio_button:
Si
\*
</td>
</tr>
<tr>
<td>
tokuProductsForInscription
</td>
<td>
`list[string]`
</td>
<td>
Lista de toku products que se usarán para inscribir la tarjeta si el pago es exitoso. Por ahora solo soporta
`cards_batch_mx_recurring_pst`
.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
subscriptionId
</td>
<td>
`string`
</td>
<td>
Id subscription encriptado. Solo es necesario si se va a inscribir la tarjeta. La tarjeta quedará suscrita para este producto.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
paymentMethodId
</td>
<td>
`string`
</td>
<td>
Id payment method encriptado. Para pagar con una tarjeta previamente tokenizada.
</td>
<td>
No
</td>
</tr>
<tr>
<td>
installments
</td>
<td>
`number`
</td>
<td>
Número de Meses sin Intereses, solo es necesario si se desea pagar en Meses sin Intereses.
</td>
<td>
No
</td>
</tr>
| Nombre |
|---|
| organizationId |
TokuCheckoutForm.setPanStylesheet()
Recibe un string con reglas css y lo inserta dentro de un tag <style> en el iframe del pan.
TokuCheckoutForm.setCvvStylesheet()
Recibe un string con reglas css y lo inserta dentro de un tag <style> en el iframe del cvv.
Ejemplo integrado
El siguiente ejemplo muestra un ejemplo integral para incorporar un formulario de inscripción de tarjetas utilizando Checkout Form, junto con un indicador de carga simple para mejorar la experiencia del usuario.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout Form Example</title>
<style>
/* Simple loading spinner styles */
#loadingIndicator {
display: none;
width: 40px;
height: 40px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top-color: #000;
animation: spin 1s infinite linear;
margin: 10px auto;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<h1>Checkout Form</h1>
<form id="creditCardForm">
<div id="cardNumber">
<label for="cardNumber">Card Number:</label>
<div id="pan" style="width: 17em; height: 3em;"></div>
</div>
<div id="expirationDate">
<label for="expirationDate">Expiration Date (MM/YY):</label>
<input type="text" id="expirationDate" name="expirationDate" placeholder="MM/YY" required><br>
</div>
<label for="cvv">CVV:</label>
<div id="cvv" style="width: 4em; height: 2em;"></div>
<div id="cardholderName">
<label for="cardholderName">Cardholder's Name:</label>
<input type="text" id="cardholderName" name="cardholderName" required><br>
</div>
<button id="submitButton" type="button">Submit</button>
</form>
<div id="loadingIndicator"></div>
<!-- Import the JavaScript file -->
<script src="https://storage.googleapis.com/toku-embedded-portal/checkout-form.min.js"></script>
<script>
// Get the current URL
const url = new URL(window.location.href);
// Get the search parameters
const params = new URLSearchParams(url.search);
let tokuCheckoutForm = null;
document.addEventListener('DOMContentLoaded', function () {
tokuCheckoutForm = new TokuCheckoutForm({
panDivId: 'pan',
cvvDivId: 'cvv',
environment: params.get('environment') || 'PROD',
onSuccess: () => {
console.log('Success');
hideLoadingIndicator();
},
onError: () => {
console.log('Error');
hideLoadingIndicator();
},
onPanFocus: tokuCheckoutForm => {
console.log('panFocus');
},
onPanBlur: tokuCheckoutForm => {
console.log('panBlur');
},
onCvvFocus: tokuCheckoutForm => {
console.log('CvvFocus');
tokuCheckoutForm.setCvvStylesheet('input { color: green }');
},
onCvvBlur: tokuCheckoutForm => {
console.log('CvvBlur');
tokuCheckoutForm.setCvvStylesheet('input { color: red }');
},
onPanChange: data => {
console.log('onPanChange' + JSON.stringify(data));
},
onCvvChange: data => {
console.log('onCvvChange' + JSON.stringify(data));
},
onCardValidationLoading: () => {
console.log('onCardValidationLoading');
},
onCardValidationCompleted: data => {
console.log('onCardValidationCompleted' + JSON.stringify(data));
},
panStyle: {
width: '15em',
height: '1em',
'border-radius': '0.5rem',
border: '2px solid',
padding: '0.5rem',
outline: 'none',
},
cvvStyle: { width: '3em', height: '1em', 'box-shadow': '0 0 5px rgba(0, 0, 0, 0.2)' },
options: {
maskPan: true,
panPlaceholder: 'Card Number',
cvvPlaceholder: 'CVV',
},
panStylesheet: 'input { color: blue; } input:focus { color: green }',
cvvStylesheet: '',
organizationId: params.get('organizationId'),
accountId: params.get('accountId'),
tokuProduct: params.get('tokuProduct'),
});
});
document.getElementById("submitButton").addEventListener('click', function (event) {
showLoadingIndicator();
const formData = {
expirationDate: document.getElementById('expirationDate').value,
cardholderName: document.getElementById('cardholderName').value
};
tokuCheckoutForm.processOperation("PAYMENT", {
organizationId: params.get('organizationId'),
accountId: params.get('accountId'),
customerId: params.get('customerId'),
tokuProducts: [params.get('tokuProduct')],
expirationDate: params.get('paymentMethodId') && formData.expirationDate,
customerName: params.get('paymentMethodId') && formData.cardholderName,
invoiceIds: [params.get('invoiceId')],
amount: parseFloat(params.get('amount')),
subscriptionId: params.get('subscriptionId'),
tokuProductsForInscription: [params.get('tokuProductForInscription')],
paymentMethodId: params.get('paymentMethodId'),
installments: Number(params.get('installments'))
});
});
function showLoadingIndicator() {
document.getElementById('loadingIndicator').style.display = 'block';
document.getElementById('submitButton').disabled = true;
}
function hideLoadingIndicator() {
document.getElementById('loadingIndicator').style.display = 'none';
document.getElementById('submitButton').disabled = false;
}
document.getElementById('expirationDate').addEventListener('input', function (e) {
let input = e.target.value.replace(/\D/g, '').substring(0, 4); // Allow only digits and limit to 4 chars
if (input.length >= 2) {
input = input.slice(0, 2) + '/' + input.slice(2); // Add slash after the second digit
}
e.target.value = input;
});
</script>
</body>
</html>
