Introducción
La integración de pagos con WeChat y Alipay en aplicaciones H5 y mini-programas requiere una abstracción para manejar la autenticación y las transacciones de forma unificada. Este artículo detalla la implementación de módulos para detectar la plataforma, autorizar usuarios e iniciar pagos.
- Detección de la plataforma
Para identificar si el entorno es WeChat o Alipay, se analiza el agente de usuario en H5 o se asume el entorno en mini-programas.
export function detectarPlataforma() {
const tienda = useStore();
let tipoPlataforma;
// Entorno H5
// #ifdef H5
const agente = window.navigator.userAgent;
if (agente.includes('MicroMessenger')) {
tienda.setUA('wxapp');
tipoPlataforma = 'WX';
} else if (agente.includes('AlipayClient')) {
tienda.setUA('aliapp');
tipoPlataforma = 'ALI';
} else {
tienda.setUA('otro');
tipoPlataforma = false;
return false;
}
// #endif
// Mini-programa WeChat
// #ifdef MP-WEIXIN
tipoPlataforma = 'WX_MP';
// #endif
return tipoPlataforma;
}
- Autoirzación y obtención de código de acceso
El proceso de autorización redirige al usuario a las páginas de WeChat o Alipay para obtener un código de acceso, excluyendo parámetros conflictivos para evitar bucles.
export const iniciarAutorizacion = (pagina, appId, parametrosConsulta) => {
const tipoPlataforma = detectarPlataforma();
const parametrosLimpios = [];
// Eliminar código de acceso para prevenir ciclos de autorización
for (const clave in parametrosConsulta) {
if (clave !== 'code' && clave !== 'auth_code') {
parametrosLimpios.push(`${clave}=${parametrosConsulta[clave]}`);
}
}
const cadenaParametros = parametrosLimpios.join('&') + '&href=' + encodeURIComponent(`${window.location.origin}${pagina}`);
redirigirAPlataforma(tipoPlataforma, cadenaParametros, appId);
};
export default async function redirigirAPlataforma(tipo, cadenaParametros, appId) {
let urlAutorizacion = '';
const origenBase = 'https://dominio.com';
if (['produccion', 'pruebas'].includes(import.meta.env.MODE)) {
urlAutorizacion = window.location.origin;
} else {
urlAutorizacion = origenBase;
}
switch (tipo) {
case 'WX':
urlAutorizacion = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=` +
encodeURIComponent(`${urlAutorizacion}/pages/h5/TransitoEnvio?${cadenaParametros}`) +
'&response_type=code&scope=snsapi_base&state=ESTADO#wechat_redirect';
break;
case 'ALI':
urlAutorizacion = `https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=${appId}&scope=auth_base&redirect_uri=` +
encodeURIComponent(`${urlAutorizacion}/pages/h5/TransitoEnvio?${cadenaParametros}`);
break;
}
console.log('Redirigiendo a:', urlAutorizacion);
window.location.href = urlAutorizacion;
}
El componente TransitoEnvio.vue sirve como punto intermedio para redirigir al usuario con los parámetros necesarios.
<template>
<div></div>
</template>
<script lang="ts" setup="">
onLoad((opciones) => {
const parametrosFinales = [];
opciones.href = decodeURIComponent(opciones.href);
for (const clave in opciones) {
if (clave !== 'href') {
parametrosFinales.push(`${clave}=${opciones[clave]}`);
}
}
window.location.replace(`${opciones.href}?${parametrosFinales.join('&')}`);
});
</script>
- Obtención de openid
Para entornos H5, se debe redirigir a un servicio externo para obtener el openid, manejando errores y almacenando el código de sesión. En mini-programas, se utiliza el SDK nativo.
export const obtenerOpenId = (parametros) => {
return new Promise(async (resolver, rechazar) => {
const tipoPlataforma = detectarPlataforma();
const idParqueo = parametros.parkId || parametros.parkid;
// Entorno H5
// #ifdef H5
const paginas = getCurrentPages();
const paginaActual = paginas[paginas.length - 1];
const rutaActual = `/${paginaActual.route}`;
const consulta = paginaActual.options;
let identificadorApp;
if (parametros.appid) {
identificadorApp = parametros.appid;
} else if (parametros.parkId) {
identificadorApp = await obtenerAppIdPorParqueo(idParqueo);
} else {
if (parametros.prompt === 'Modal') {
uni.showModal({ title: 'Aviso', content: 'Faltan parámetros esenciales', showCancel: false });
} else {
uni.showToast({ icon: 'none', title: 'Faltan parámetros esenciales' });
}
rechazar();
return;
}
const openidAlmacenado = uni.getStorageSync(`${identificadorApp}_openid`);
if (openidAlmacenado) {
sessionStorage.setItem('openid', openidAlmacenado);
resolver(openidAlmacenado);
return;
}
const codigo = consulta ? (consulta.code || consulta.auth_code) : '';
if (codigo) {
const codigoPrevio = sessionStorage.getItem('code');
if (codigo !== codigoPrevio) {
sessionStorage.setItem('code', codigo);
const funcionObtener = idParqueo ? obtenerOpenIdPorParqueo : obtenerOpenIdPorAppId;
const argumentos = idParqueo ? [tipoPlataforma, idParqueo, codigo] : [identificadorApp, codigo];
funcionObtener(...argumentos).then((resultado) => {
if (resultado.data?.errCode === 40163 || resultado.data?.errCode === 40029) {
iniciarAutorizacion(rutaActual, identificadorApp, consulta);
return;
}
if (resultado.success && resultado.data) {
usePayStore().almacenarOpenId(identificadorApp, resultado.data);
sessionStorage.setItem('openid', resultado.data);
resolver(resultado.data);
} else {
const mensajeError = resultado.msg || 'Error al recuperar openid';
if (parametros.prompt === 'Modal') {
uni.showModal({ title: 'Aviso', content: mensajeError, showCancel: false });
} else {
uni.showToast({ icon: 'none', title: mensajeError });
}
rechazar();
}
}).catch(() => {
rechazar();
});
} else {
iniciarAutorizacion(rutaActual, identificadorApp, consulta);
}
} else {
iniciarAutorizacion(rutaActual, identificadorApp, consulta);
}
// #endif
// Mini-programa WeChat
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success: (respuesta) => {
obtenerOpenIdPorParqueo(tipoPlataforma, idParqueo, respuesta.code, false).then((resultado) => {
if (resultado.success) {
resolver(resultado.data);
} else {
const mensaje = resultado.msg || 'Operación no completada';
if (parametros.prompt === 'Modal') {
uni.showModal({ title: 'Aviso', content: mensaje, showCancel: false });
} else {
uni.showToast({ icon: 'none', title: mensaje });
}
rechazar();
}
});
},
fail: (error) => {
rechazar(error);
}
});
// #endif
});
};
- Iniciación de pagos
La función para iniciar pagos abstrae las diferencias entre WeChat y Alipay, manejando tanto redirecciones como invocaciones nativas.
export const iniciarPago = (datosPago, callbacks) => {
const tipoPlataforma = detectarPlataforma();
// Entorno H5
// #ifdef H5
if (datosPago?.payUrl) {
window.location.href = datosPago.payUrl;
return;
}
const ejecutarPagoWechat = () => {
if (typeof WeixinJSBridge === 'undefined') {
const eventos = ['WeixinJSBridgeReady', 'onWeixinJSBridgeReady'];
eventos.forEach(evento => {
document.addEventListener(evento, () => invocarPuenteWechat(datosPago), false);
});
} else {
invocarPuenteWechat(datosPago);
}
};
if (tipoPlataforma === 'WX') {
ejecutarPagoWechat();
} else if (tipoPlataforma === 'ALI') {
procesarPagoAlipay(datosPago.alipay_trade_create_response.trade_no);
}
function invocarPuenteWechat(parametros) {
window.WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: parametros.appId,
timeStamp: parametros.timeStamp,
nonceStr: parametros.nonceStr,
package: parametros.package,
signType: parametros.signType,
paySign: parametros.paySign
},
(respuesta) => {
const exito = respuesta.err_msg === 'get_brand_wcpay_request:ok';
if (exito && callbacks.exito) {
callbacks.exito(respuesta);
} else if (!exito && callbacks.fallo) {
callbacks.fallo(respuesta);
}
}
);
}
function procesarPagoAlipay(identificadorTransaccion) {
ap.tradePay(
{ tradeNO: identificadorTransaccion },
(respuesta) => {
if (respuesta.resultCode === 9000 && callbacks.exito) {
callbacks.exito(respuesta);
} else if (callbacks.fallo) {
callbacks.fallo(respuesta);
}
}
);
}
// #endif
// Mini-programa WeChat
// #ifdef MP-WEIXIN
wx.requestPayment({
timeStamp: datosPago.timeStamp,
nonceStr: datosPago.nonceStr,
package: datosPago.package,
signType: datosPago.signType,
paySign: datosPago.paySign,
success: (res) => callbacks.exito && callbacks.exito(res),
fail: (err) => callbacks.fallo && callbacks.fallo(err)
});
// #endif
};
Esta estructura encapsulada permite integrar pagos de WeChat y Alipay de manera coherente, adoptándose a diferentes entornos de ejecución.