pixelpay-module
Version:
Modulo para integrar pagos con PixelPay
658 lines (571 loc) • 21.8 kB
JavaScript
export default class PixelPay {
constructor () {
this.cancel = '';
this.complete = '';
this.order_id = '';
this.amount = '';
this.firstName = '';
this.lastName = '';
this.email = '';
this.baseURL = '';
this.debug = true;
this.request = {
_key: '',
_callback: '',
_cancel: '',
_complete: '',
_order_id: '',
_order_date: '',
_order_content: [],
_order_extras: [],
_currency: '',
_tax_amount: '',
_shipping_amount: '',
_amount: '',
_first_name: '',
_last_name: '',
_email: '',
_address: '',
_address_alt: '',
_zip: '',
_city: '',
_state: '',
_country: '',
};
}
setup(key, endpoint, debug = true) {
this.debug = debug;
this.key = this.isValidKey(key)
var isValid = /^https?:\/\/[\w\-]+(\.[\w\-]+)+[/#?]?.*$/.test(endpoint);
if (isValid) {
this.baseURL = endpoint;
} else {
this.logger('La URL del endpoint no es una URL Valida.');
}
return true;
}
sendPayment(){
return new Promise((resolve, reject) => {
if (this.key && this.baseURL) {
this.request._key = this.key;
if (this.request._order_content.length) {
this.recalculateAmount();
}
if (this.isValid()) {
this.request.json = true;
this.request.sdk = true;
this.createModal();
document.addEventListener('frameclose', function(){
reject(new Error ('Ventana de pago cerrada.'));
});
if (this.request._order_extras.length) {
this.validExtras()
}
var counter = 0;
var urlRequest = `${this.baseURL}/hosted/payment/sdk`;
for (const [key, value] of Object.entries(this.request)) {
var val = this.checkInstance(value)
if (val) {
urlRequest = (counter === 0) ? `${urlRequest}?${key}=${val}` : `${urlRequest}&${key}=${val}`;
}
counter++;
}
var self = this;
var request = new XMLHttpRequest();
request.open("GET", urlRequest, true);
request.addEventListener('load',function(){
var response = JSON.parse(request.responseText);
if (response.success) {
var url = `${response.url}`
self.addIframe(url);
window.onmessage = function(event) {
if (event && event.data.success) {
var resp = event.data;
if (self.request._complete) {
window.location.replace(resp.data.payment_hash);
} else {
resp.data.payment_hash = resp.data.payment_hash.split('=')[1];
resolve(resp);
}
}else {
console.log(event);
}
}
} else {
if (response.hasOwnProperty('message')) {
reject('PIXELPAY: ' + response.message);
}
if (response.hasOwnProperty('errors')) {
reject(response.errors);
}
self.closeFrame();
reject();
}
})
request.send(null);
} else {
reject(new Error('PixelPay. Uno o más valores requeridos no son validos'));
}
} else {
reject(new Error(`PIXELPAY: para poder enviar los datos de cobro el campo "key", "baseURL" deben estar validados con el método setup().`));
}
})
}
/**
* Metodo publico para validar la llave de un comercio.
* @param {*} key - llave del comercio. Valor alfanumerico sin espacios ni caracteres especiales.
*/
isValidKey(key) {
var long = key.length;
if (long >= 8 && /^[A-Za-z0-9]*$/.test(key)) {
return key
}
this.logger('El valor de la llave no es un valor válido');
}
/**
* Metodo publico para validar el order_id de una transaccion.
* Debe ser un string sin espacios.
* @param {*} string -
*/
setOrderID(string) {
var isValid = /^[A-Za-z0-9]*$/.test(String(string));
if (isValid && string) {
this.order_id = string;
this.request._order_id = string;
return true;
}
this.logger('El campo "order_id" debe ser un string sin espacios en blanco o caracteres especiales');
}
/**
* Formato valido 'dd-mm-aa hh:mm'
* @param {*} date - fecha y hora de la orden generada
*/
setDate(date) {
var isValid = /\d{4}-\d{2}-\d{2}/.test(date);
if (isValid) {
this.request._order_date = date;
return;
}
this.logger('Fecha invalida. El formato del campo "order_date" es: "YYYY-MM-DD"');
}
/**
* Valor de la moneda ej. HNL
* @param {*} currency
*/
setCurrency(currency) {
if (currency.length === 3 && typeof currency === 'string' && /[A-Za-z]/.test(currency)) {
this.request._currency = currency.toUpperCase();
return true;
}
this.logger('Formato de moneda invalido. Debe ser un string de 3 caracteres.')
}
/**
*
* @param {*} value - Valor de tipo float.
* @param {*} key - Llave del objeto @var request
*/
addAmount(value, key) {
if (!isNaN(parseFloat(value))) {
var isValid = false;
switch(key) {
case 'amount':
this.amount = value;
this.request._amount = this.amount;
return true;
case 'tax_amount':
this.request._tax_amount = value;
isValid = true;
break;
case 'shipping_amount':
this.request._shipping_amount = value;
isValid = true;
break;
case 'price':
return value;
case 'tax':
return value;
case 'default':
this.invalidKeyException(key)
}
return isValid;
}
this.logger(`El valor asignado a ${key} no es un valor valido. El método espera recibir un valor de tipo float.`);
}
setAmount(amount) {
return this.addAmount(amount, 'amount');
}
setTaxAmount(amount) {
return this.addAmount(amount, 'tax_amount');
}
setShippingAmount(amount) {
return this.addAmount(amount, 'shipping_amount');
}
/**
* Valores permitidos: (first_name, last_name, city, state, country, zip, address, address_alt)
* @param {*} string - Valor asignado a la llave
* @param {*} key - Key del objeto @param request
* @param min - Minimo de caracteres permitidos
* @param max - Maximo de caracteres permitidos
*/
addStringValue(string, key, min = 3, max = 120){
if (this.validLength(String(string), min, max)) {
switch (key) {
case 'first_name':
this.firstName = string;
this.request._first_name = this.firstName;
return true;
case 'last_name':
this.lastName = string;
this.request._last_name = this.lastName;
return true;
case 'city':
this.request._city = string;
return true;
case 'state':
this.request._state = string;
return true;
case 'country':
this.request._country = string;
return true;
case 'zip':
this.request._zip = string;
return true;
case 'address':
this.request._address = string;
return true;
case 'address_alt':
this.request._address_alt = string;
return true;
default:
this.invalidKeyException(key)
}
} else {
this.invalidLengthException(key, min, max)
}
}
setFirstName(value) {
return this.addStringValue(value, 'first_name');
}
setLastName(value){
return this.addStringValue(value, 'last_name')
}
setCity(value){
return this.addStringValue(value, 'city')
}
setState(value){
return this.addStringValue(value, 'state')
}
setCountry(value){
return this.addStringValue(value, 'country')
}
setZip(value){
return this.addStringValue(value, 'zip')
}
setAddress(value){
return this.addStringValue(value, 'address')
}
setAlternateAddress(value){
return this.addStringValue(value, 'address_alt')
}
/**
*
* @param {*} url - Valor a evaluar, Debe ser una URL valida
* @param {*} key - Llave en @var request
*/
addKeyUrl(url, key){
var isValid = /^https?:\/\/[\w\-]+(\.[\w\-]+)+[/#?]?.*$/.test(url);
if (isValid) {
switch (key) {
case 'callback':
this.request._callback = url
break;
case 'cancel':
this.cancel = url;
this.request._cancel = this.cancel;
break;
case 'complete':
this.complete = url;
this.request._complete = this.complete;
break;
default:
this.invalidKeyException(key)
}
return true;
}
this.invalidUrlException(url);
}
setCallBack(url){
return this.addKeyUrl(url, 'callback');
}
setCancel(url){
return this.addKeyUrl(url, 'cancel');
}
setComplete(url){
return this.addKeyUrl(url, 'complete');
}
/**
*
* @param {*} title - Nombre del producto
* @param {*} price - Valor del producto
* @param qty - Canditadd @default 1
* @param description - Descripcion del producto
* @param code - Codigo del producto
* @param tax - Impuesto del producto
*/
setItemContent(title, price, qty = 1, description = "", code = "", tax = ""){
if (this.validLength(title, 3, 60) && !isNaN(parseFloat(price)) && !isNaN(parseInt(qty))) {
if (tax) {
tax = parseFloat(tax);
if (isNaN(tax)) {
this.logger('valor de "tax" no es un valor valido.')
}
}
var total = (price * qty);
var item = {
'title': title,
'code': code,
'description': description,
'price': parseFloat(price),
'qty': parseInt(qty),
'tax': tax,
'total': total,
}
this.request._order_content.push(item)
return true;
} else {
this.logger('Uno o más valores no son válidos, revisa la documentación');
}
}
clearItemContent() {
this.request._order_content = [];
return true;
}
/**
*
* @param {*} key - Llave del objecto que se creara
* @param {*} value - Valor de la llave
*/
setContentExtra(key, value){
if (key && value) {
var obj = new Object();
obj[key] = value;
this.request._order_extras.push(obj)
return true;
}
this.logger('No se aceptan valores nulos')
}
setJsonContentExtra(json) {
if ( typeof json === 'object') {
this.request._order_extras.push(json);
return true;
}
this.logger('El valor debe ser de tipo JSON');
}
clearConentExtra() {
this.request._order_extras = [];
return true;
}
/**
*
* @param {*} email - correo del cliente.
*/
setEmail(email) {
var valid = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/.test(email);
if (valid) {
this.email = email;
this.request._email = this.email;
return true;
}
this.logger(`El valor para el campo de email no es un correo valido`);
}
/**
*
* @param {*} string - valor alfanumerico a evaluar
* @param {*} min - minimo de catacteres validos
* @param {*} max - Maximo de caracteres
*/
validLength(string, min, max) {
if (string.length >= min && string.length <= max) {
return true;
}
return false;
}
/**
* Metodo para validar los requisitos minimos para el envio.
*/
isValid(){
if (this.setOrderID(this.order_id) && this.setAmount(this.amount, 'amount') &&
this.setFirstName(this.firstName, 'first_name') &&
this.setLastName(this.lastName, 'last_name') &&
this.setEmail(this.email))
{
return true;
}
return false;
}
/**
*
* @param {*} value - Metodo para evaluar si un valor es de tipo objeto
* si es asi el valor se convierte a string. es llamado en el @method sendPayment()
*/
checkInstance(value){
if (typeof value == 'object'){
if (value.length) {
var str = this.convertToString(value)
return str;
}
return false;
}
return value;
}
/**
*
* @param {*} obj - Valor de tipo objeto que sera convertido a string.
* Es llamado en el @method checkInstance() y @method validExtras()
*/
convertToString(obj){
var json = JSON.stringify(obj);
var base64 = window.btoa(json);
return encodeURI(base64);
}
/**
* Metodo utilizado para recalcular el precio el array @var request._order_content
* es mayor que 0
*/
recalculateAmount(){
var totalAmount = 0;
var tax_amount = 0;
for (const variant of Object.values(this.request._order_content)) {
var price = String(variant.price).replace(/[&\/\\#,+()$~%'":*?<>{}]/g, '');
tax_amount = (parseFloat(tax_amount) + parseFloat(variant.tax));
totalAmount = (parseFloat(totalAmount) + (parseFloat(price) * parseInt(variant.qty)))
}
this.request._tax_amount = tax_amount;
this.setAmount(totalAmount);
}
/**
* Metodo para convertir a string los valores extras.
* @return void
*/
validExtras(){
var items = this.request._order_extras.values();
var new_obj = {};
var arr = [];
for (var value of items) {
Object.keys(value).forEach(function (val) {
new_obj[val] = value[val];
})
}
arr.push(new_obj);
return arr;
}
createModal(){
document.getElementsByTagName("html")[0].style.overflow = "hidden";
var element = document.createElement('div');
element.setAttribute('id', 'pixelpay-frame')
element.style.cssText = 'position: fixed;top: 0;bottom: 0;left: 0;right: 0;background-color: rgba(0,0,0,.8);overflow: hidden;height: 100vh;padding: 50px 0 0 0; opacity: 0; visibility: hidden; transition: opacity .3s;';
document.body.appendChild(element);
var loading = document.createElement('div');
loading.style.cssText = 'width:200px; height:4px; background:linear-gradient(to right,#5a86f4,#5a86f4); background-color:#ccc; position:fixed; top:0; bottom:0; left:0; right:0; margin:auto; border-radius:4px; background-size:20%; background-repeat:repeat-y; background-position:-25% 0; animation:scroll 1.2s ease-in-out infinite;';
loading.classList.add('pixelpay--preloader')
loading.animate([
{
backgroundSize: '100%',
},
{
backgoundPosition: '125% 0;'
}
],4000);
element.appendChild(loading);
setTimeout(() => {
element.style.visibility = 'visible';
element.style.opacity = 1;
}, 320);
return true;
}
addIframe(url) {
var element = document.getElementById('pixelpay-frame');
var iframe = document.createElement('iframe')
iframe.classList.add('frame--loading');
iframe.style.cssText = 'width: 100%;height: 100%;'
iframe.setAttribute('frameborder', 0);
iframe.setAttribute('frameborder', 0);
iframe.setAttribute('scrolling', 'auto');
iframe.setAttribute('src', url);
iframe.setAttribute('id', 'pixelpay--iframe');
iframe.classList.add('pixelpay--iframe');
var btnClose = document.createElement('span');
btnClose.setAttribute('id', 'pixelpay--btn--close')
var textnode = document.createTextNode("X");
btnClose.style.cssText = 'position: absolute; top: 0; right: 20px; color:white; font-size: 25px; cursor:pointer; line-height: 50px';
btnClose.appendChild(textnode)
element.append(btnClose);
element.append(iframe);
document.body.appendChild(element)
this.closeEventListener();
window.setTimeout(this.checkIframeLoaded, 100);
}
closeFrame(){
var el = document.getElementById("pixelpay--btn--close");
var fr = document.getElementById("pixelpay-frame");
if (el) {
el.click();
} else {
fr.style.opacity = 0;
setTimeout(function(){
fr.style.visibility = 'hidden';
fr.remove();
}, 300);
document.getElementsByTagName("html")[0].style.overflow = "auto";
}
}
closeEventListener(){
var el = document.getElementById("pixelpay--btn--close");
if (el) {
el.addEventListener('click', function (){
var iframe = document.getElementById('pixelpay-frame');
iframe.style.opacity = 0;
setTimeout(function(){
iframe.style.visibility = 'hidden';
iframe.remove();
}, 300);
document.getElementsByTagName("html")[0].style.overflow = "auto";
document.dispatchEvent(new Event('frameclose'));
}, false)
}
}
checkIframeLoaded() {
var iframe = document.getElementById('pixelpay--iframe');
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if ( iframeDoc.readyState == 'complete' ) {
var loading = document.querySelector('.pixelpay--preloader');
if (loading) {
document.getElementById('pixelpay--iframe').classList.remove('frame--loading')
setTimeout(function(){
loading.style.cssText="animation:scroll 1.2s ease-in-out infinite; display: none";
}, 1500)
}
return;
}
}
logger(msj){
if (this.debug ) {
// console.error(`PixelPay: ${msj}`);
var error = new Error(msj)
error.name = 'Pixelpay';
throw error;
}
}
invalidKeyException(key) {
var msj = `El valor de la llave '${key}' no es un valor valido.`
this.logger(msj)
}
invalidLengthException(key, min, max){
var msj = `El mínimo de caracteres especificado para '${key}' es de mínimo ${min} y el máximo ${max}`
this.logger(msj)
}
invalidUrlException(url){
var msj = `'${url}' NO es una URL valida. Ej. 'https://midominio.com'`;
this.logger(msj);
}
}