pay.js 7.77 KB
/**
 * 民生支付模块
 * 封装民生银行支付流程,包括请求支付凭证、解密数据、调起微信支付等功能
 * @module common/pay
 */

import config from '@/config.js';
import request from './request'
import to from 'await-to-js'

/** 错误消息映射表 */
const ERROR_MESSAGES = {
  USER_CANCEL: '支付已取消',
  NETWORK_ERROR: '网络异常,请检查网络连接',
  INVALID_PARAMS: '参数错误',
  DECRYPT_FAILED: '数据解密失败',
  PARSE_FAILED: '数据解析失败',
  PAY_INFO_MISSING: '支付信息缺失'
}

/**
 * 民生支付主函数
 * @async
 * @param {string} orderId - 订单ID,用于错误处理和取消订单
 * @param {string} encryptedData - 加密数据
 * @returns {Promise<string>}
 * @throws {Error} 支付过程中发生的错误
 */
async function minShengPay(orderId, encryptedData) {
  uni.showLoading({
    title: '生成支付...',
    mask: true
  })
  console.log(1111)
  
  // 参数校验
  if (!orderId) {
    console.error('minShengPay: orderId is required')
    handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), null)
  }
  
  if (!encryptedData) {
    handlePaymentError(new Error(ERROR_MESSAGES.INVALID_PARAMS), orderId)
  }
  
  // 1. 数据准备:将表单数据转换为 URL 编码格式
  const encodedData = buildUrlEncodedData({
    context: encryptedData
  })
  
  // 2. 请求支付凭证
  const [reqErr, res] = await to(requestPaymentCredential(encodedData))
  if (reqErr) {
    handlePaymentError(reqErr, orderId)
  }
  
  // 3. 响应验证
  if (!res?.data?.businessContext) {
    handlePaymentError({
      message: res?.data?.gateReturnMessage
    }, orderId)
  }
  
  // 4. 解密并处理数据
  const [decryptErr, decryptResult] = await to(decrypt({
    bussinessContext: res.data.businessContext
  }))
  
  if (decryptErr || !decryptResult?.data) {
    
    handlePaymentError(new Error(ERROR_MESSAGES.DECRYPT_FAILED), orderId)
  }
  
  // 5. 解析 JSON 数据
  const parsedData = safeJsonParse(decryptResult.data)
  if (!parsedData) {
    handlePaymentError(new Error(ERROR_MESSAGES.PARSE_FAILED), orderId)
  }
  
  // 6. 验证支付信息
  if (!parsedData.payInfo) {
    handlePaymentError(new Error(ERROR_MESSAGES.PAY_INFO_MISSING), orderId)
  }
  
  // 7. 解析支付参数
  const payParams = parsePayInfo(parsedData.payInfo)
  
  uni.hideLoading()
  // 8. 调起微信支付
  const [payErr, paySuccess] = await to(invokeWechatPayment(payParams, orderId))
  if (payErr) {
    handlePaymentError(payErr, orderId)
  }
  if (paySuccess) {
    return 'OK'
  }
}

/**
 * 将对象转换为 URL 编码格式字符串
 * @param {Object} data - 需要转换的数据对象
 * @returns {string} URL 编码格式字符串
 */
function buildUrlEncodedData(data) {
  return Object.entries(data)
    .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
    .join('&')
}

/**
 * 请求支付凭证
 * @async
 * @param {string} encodedData - URL 编码格式的请求数据
 * @returns {Promise<Object>} 支付凭证响应
 */
function requestPaymentCredential(encodedData) {
  return uni.request({
    url: config.payUrl,
    method: 'POST',
    header: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data: encodedData
  })
}

/**
 * 解密支付数据(待实现)
 * @param {Object} params - 解密参数
 * @param {string} params.bussinessContext - 业务上下文数据
 * @returns {Promise<Object>} 解密后的数据
 */
function decrypt(params) {
  return request({
    url: `/api/decrypt`,
    method: 'post',
    params
  })
}

/**
 * 安全解析 JSON 字符串
 * @param {string} jsonString - JSON 字符串
 * @returns {Object|null} 解析后的对象,解析失败返回 null
 */
function safeJsonParse(jsonString) {
  try {
    return JSON.parse(jsonString)
  } catch (e) {
    console.error('JSON parse error:', e)
    return null
  }
}

/**
 * 解析支付信息字符串
 * 将 "key1=value1|key2=value2" 格式转换为对象
 * @param {string} payInfoStr - 支付信息字符串,格式为 "key1=value1|key2=value2|..."
 * @returns {Object} 解析后的支付参数对象
 * @example
 * // 返回 { appId: 'wx123', prepayId: 'abc' }
 * parsePayInfo('appId=wx123|prepayId=abc|')
 */
function parsePayInfo(payInfoStr) {
  if (!payInfoStr || typeof payInfoStr !== 'string') {
    return {}
  }
  
  const result = {}
  
  payInfoStr.split('|').forEach(item => {
    if (!item) return
    
    const index = item.indexOf('=')
    if (index === -1) return
    
    const key = item.substring(0, index)
    const value = item.substring(index + 1)
    result[key] = value
  })
  
  return result
}

/**
 * 调起微信支付
 * @param {Object} payParams - 微信支付参数
 * @param {string} payParams.appId - 应用ID
 * @param {string} payParams.nonceStr - 随机字符串
 * @param {string} payParams.prepayId - 预支付ID
 * @param {string} payParams.timeStamp - 时间戳
 * @param {string} payParams.signType - 签名类型
 * @param {string} payParams.paySign - 支付签名
 * @param {string} orderId - 订单ID
 * @returns {Promise<Object>} 支付结果
 */
function invokeWechatPayment(payParams, orderId) {
  return new Promise((resolve, reject) => {
    // 参数校验
    const requiredFields = ['appId', 'nonceStr', 'prepayId', 'timeStamp', 'signType', 'paySign']
    const missingFields = requiredFields.filter(field => !payParams?.[field])
    
    if (missingFields.length > 0) {
      reject(new Error(`缺少必要支付参数: ${missingFields.join(', ')}`))
      return
    }
    
    uni.requestPayment({
      appId: payParams.appId,
      nonceStr: payParams.nonceStr,
      package: `prepay_id=${payParams.prepayId}`,
      timeStamp: payParams.timeStamp,
      signType: payParams.signType,
      paySign: payParams.paySign,
      success: (res) => {
        resolve(res)
        uni.showToast({
          title: '支付成功',
          duration: 2000,
        })
      },
      fail: async (err) => {
        // 用户取消支付
        if (err.errMsg?.includes('cancel')) {
          await handleUserCancel(orderId)
          reject(new Error('USER_CANCEL'))
        } else {
          await handlePaymentFailure(orderId, err)
          reject(err)
        }
      }
    })
  })
}

/**
 * 处理用户取消支付
 * @async
 * @param {string} orderId - 订单ID
 * @returns {Promise<void>}
 */
async function handleUserCancel(orderId) {
  const [err] = await to(request({
    url: 'cancelOrder',
    method: 'get',
    params: {
      orderId
    }
  }))
  
  if (err) {
    console.error('取消订单失败:', err)
    return
  }
  
  uni.showToast({
    title: '支付取消',
    icon: 'none',
    duration: 2000
  })
}

/**
 * 处理支付失败
 * @async
 * @param {string} orderId - 订单ID
 * @param {Error|Object} error - 错误对象
 * @returns {Promise<void>}
 */
async function handlePaymentFailure(orderId, error) {
  console.error(`支付失败 [订单: ${orderId}]:`, error)
  // 可在此处添加支付失败后的业务逻辑,如上报错误等
}

/**
 * 统一错误处理(处理后会抛出错误)
 * @param {Error} error - 错误对象
 * @param {string|null} orderId - 订单ID
 * @throws {Error} 始终抛出传入的错误对象
 */
function handlePaymentError(error, orderId) {
  uni.hideLoading()
  
  const message = ERROR_MESSAGES[error?.message] || error?.message || '支付失败,请重试'
  
  uni.showToast({
    title: message,
    icon: 'none'
  })
  
  // 记录错误日志,便于排查
  console.error(`支付错误 [订单: ${orderId}]:`, error)
  
  throw error
}

export {
  minShengPay
}