Files
chengkong-huakaiwuye/huakai-app/pageSubPack/api/appLaunch.js
2026-09-11 09:30:39 +08:00

257 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* App 跳转参数处理
* 花开物业 App 通过 launchMiniProgram 的 extraData 传入支付参数和登录凭证,
* 小程序通过 referrerInfo.extraData 接收(查询字符串格式)。
*
* extraData 示例:
* payAmount=10&acct=xxx&custId=xxx&token=xxx&userId=xxx&sign=xxx&...
*/
const STORAGE_KEY = 'app_launch_payment_data'
const APP_QUERY_KEY = 'app_query_params'
/**
* 需要转为数组的参数字段(逗号分隔字符串 → 数组)
*/
const ARRAY_PARAMS = ['billIds']
/**
* 解析查询字符串为对象,数组字段自动拆分
*/
function parseQueryString(str) {
if (!str || typeof str !== 'string') return null
const params = {}
str.split('&').forEach(item => {
const idx = item.indexOf('=')
if (idx === -1) return
const key = item.substring(0, idx)
const val = item.substring(idx + 1)
const decoded = decodeURIComponent(val || '')
// 同名参数合并为数组;已知数组字段按逗号拆分
if (key in params) {
if (Array.isArray(params[key])) {
params[key].push(decoded)
} else {
params[key] = [params[key], decoded]
}
} else {
params[key] = ARRAY_PARAMS.includes(key) && decoded.includes(',')
? decoded.split(',').map(v => v.trim()).filter(Boolean)
: decoded
}
})
return params
}
/**
* 归一化 billId/billIds:单个 id 用 billId,多个用 billIds,统一转为 billIds 数组
*/
function normalizeBillParams(obj) {
if (!obj) return
// 先解码再拆分为数组
if (typeof obj.billIds === 'string') {
const decoded = decodeURIComponent(obj.billIds)
obj.billIds = decoded.includes(',')
? decoded.split(',').map(v => v.trim()).filter(Boolean)
: [decoded]
}
// billId 存在且 billIds 不存在时,包装为数组
if (obj.billId && !obj.billIds) {
const decoded = decodeURIComponent(
typeof obj.billId === 'string' ? obj.billId : String(obj.billId)
)
obj.billIds = decoded.includes(',')
? decoded.split(',').map(v => v.trim()).filter(Boolean)
: [decoded]
delete obj.billId
}
}
/**
* 从启动参数中获取 App 传入的全部数据(支付参数 + token + userId)
*/
export function captureAppLaunchData(onShowOptions) {
let data = null
let queryData = null
const tryGetData = (options, label) => {
if (!options) return null
console.log(label, JSON.stringify(options))
// 保存 path query 参数(供接口调用)
if (options.query && Object.keys(options.query).length > 0) {
queryData = options.query
console.log(label + ' path query 参数:', JSON.stringify(queryData))
}
// 优先从 query 获取
if (options.query && Object.keys(options.query).length > 0) {
return options.query
}
// referrerInfo.extraData:可能是对象也可能是查询字符串
const extraData = options?.referrerInfo?.extraData
if (extraData) {
if (typeof extraData === 'string') {
console.log(label + ' extraData 是查询字符串,解析中...')
return parseQueryString(extraData)
}
return extraData
}
return null
}
// 优先使用本次进入(onLaunch/onShow 传入的 options)的最新参数。
// 热启动再次从 App 进入时,getLaunchOptionsSync 仍返回首次冷启动的旧值,
// 若先读它会导致新 token 被旧 token 覆盖,因此这里以本次 options 为准。
data = tryGetData(onShowOptions, '本次进入')
// 兜底:本次 options 未取到数据时,依次尝试当前进入参数、冷启动参数。
// 注意:不能以 !onShowOptions 判断,onLaunch/onShow 始终会传入 options 对象,
// 那样会误判导致兜底永远不执行,token 无法恢复。
if (!data) {
try {
data = tryGetData(wx.getEnterOptionsSync?.() || {}, '当前进入(getEnterOptionsSync)')
} catch (e) { console.error('getEnterOptionsSync 失败:', e) }
}
if (!data) {
try {
data = tryGetData(wx.getLaunchOptionsSync?.() || {}, '冷启动(getLaunchOptionsSync)')
} catch (e) { console.error('getLaunchOptionsSync 失败:', e) }
}
if (data) {
// 恢复登录态:只要 App 传入了 token/userId 就恢复,不依赖支付字段名
if (data.token) {
uni.setStorageSync('token', data.token)
console.log('已恢复 token:', String(data.token).slice(0, 16) + '...')
}
if (data.userId) {
uni.setStorageSync('userId', data.userId)
console.log('已恢复 userId')
}
// 存在支付参数时才作为 App 支付进入处理
if (data.payAmount || data.acct || data.custId || data.billId || data.billIds) {
// 归一化 billId/billIds:单个 id 用 billId,多个用 billIds,统一转为 billIds 数组
normalizeBillParams(data)
uni.setStorageSync(STORAGE_KEY, data)
console.log('已存储 App 支付数据:', JSON.stringify(data))
}
}
// 归一化 queryData 中的 billId/billIds
normalizeBillParams(queryData)
// 单独保存参数供接口使用(优先 queryData,fallback 到 data,即 extraData 来源)
const queryParams = queryData || data
if (queryParams) {
uni.setStorageSync(APP_QUERY_KEY, queryParams)
console.log('已存储 App 接口参数:', JSON.stringify(queryParams))
}
return data
}
/** 检查是否已登录(token 存在) */
export function isLoggedIn() {
return !!uni.getStorageSync('token')
}
/** 获取 App 传入的支付数据 */
export function getAppPaymentData() {
try { return uni.getStorageSync(STORAGE_KEY) || null } catch (e) { return null }
}
/** 获取 App 传入的 path query 参数(供接口调用) */
export function getAppQueryParams() {
try { return uni.getStorageSync(APP_QUERY_KEY) || null } catch (e) { return null }
}
/** 清除 */
export function clearAppPaymentData() {
uni.removeStorageSync(STORAGE_KEY)
}
/** 清除 path query 参数(在支付完成后调用) */
export function clearAppQueryParams() {
uni.removeStorageSync(APP_QUERY_KEY)
}
/** 组装 paymentItem */
export function buildPaymentItemFromApp(appData) {
if (!appData) return null
const amount = Number(appData.payAmount || appData.orderAmount || 0).toFixed(2)
const displayName = appData.goodName || appData.remark || appData.type || '缴费'
return { ...appData, type: displayName, rechargeAmount: amount }
}
/**
* 支付返回结果
* 日照银行支付小程序完成支付后,通过 navigateBackMiniProgram 携带 extraData 返回,
* 本小程序在 App.onShow 中通过 referrerInfo.extraData 接收(场景值 1038)。
* 这里只负责捕获/读取/清除原始返回数据,成功/失败判定见 resolvePaymentResult。
*/
const PAYMENT_RESULT_KEY = 'payment_return_result'
/**
* 捕获支付返回结果(在 App.onShow 中调用)
* 注意:App 冷/热启动进入时 extraData 是支付参数(payAmount/billIds 等),
* 并非支付返回,需跳过,避免覆盖真实结果。
*/
export function capturePaymentResult(onShowOptions) {
if (!onShowOptions) return
const extraData = onShowOptions?.referrerInfo?.extraData
if (!extraData) return
// 含支付参数字段说明是 App 跳转进入(冷/热启动),不是支付返回,跳过
if (typeof extraData === 'object' && (extraData.payAmount || extraData.billId || extraData.billIds || extraData.acct || extraData.custId)) {
return
}
let parsed = extraData
if (typeof extraData === 'string') {
try {
parsed = JSON.parse(extraData)
} catch (e) {
parsed = parseQueryString(extraData)
}
}
console.log('捕获到支付返回结果:', JSON.stringify(parsed))
uni.setStorageSync(PAYMENT_RESULT_KEY, parsed)
}
/** 读取支付返回结果(读取后建议调用 clearPaymentReturnResult 清除) */
export function getPaymentReturnResult() {
try { return uni.getStorageSync(PAYMENT_RESULT_KEY) || null } catch (e) { return null }
}
/** 清除支付返回结果 */
export function clearPaymentReturnResult() {
uni.removeStorageSync(PAYMENT_RESULT_KEY)
}
// 失败/成功信号值(大小写不敏感),字段名与取值以日照银行实际返回为准,如不一致只需调整下方映射
const FAIL_SIGNALS = ['fail', 'failed', 'cancel', 'cancelled', 'error', 'closed', 'notpay', 'payerror', '2', 'false', 'no', '01']
const SUCCESS_SIGNALS = ['success', 'ok', 'paid', 'finish', 'finished', '1', '0000', '0', '200', 'true', 'yes', '00']
const RESULT_FIELDS = ['payResult', 'result', 'status', 'payStatus', 'tradeState', 'returnCode', 'code', 'errCode', 'orderState']
/**
* 判定日照银行返回的支付结果:'success' | 'fail' | null(无法判定)
*/
export function resolvePaymentResult(data) {
if (!data || typeof data !== 'object') return null
// 布尔字段优先
if (typeof data.success === 'boolean') return data.success ? 'success' : 'fail'
if (typeof data.isSuccess === 'boolean') return data.isSuccess ? 'success' : 'fail'
for (const key of RESULT_FIELDS) {
const v = data[key]
if (v === undefined || v === null || v === '') continue
const s = String(v).trim().toLowerCase()
if (FAIL_SIGNALS.includes(s)) return 'fail'
if (SUCCESS_SIGNALS.includes(s)) return 'success'
}
return null
}