Encapsulamiento de pagos con WeChat y Alipay para aplicaciones web

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.

  1. 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;
}

  1. 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>

  1. 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
  });
};

  1. 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.

Etiquetas: WeChat Alipay H5 vue.js OAuth

Publicado el 7-12 09:47