172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
import CryptoJS from 'crypto-js';
|
||
import { encryptedStorage } from '@/utils/storage';
|
||
|
||
export const ACTIVE_QR_URL_CACHE_KEY = 'cashier:property:active-url';
|
||
export const ACTIVE_QR_HASH_CACHE_KEY = 'cashier:property:active-url-hash';
|
||
export const PENDING_MERCHANT_URL_KEY = 'cashier:pending-merchant-url';
|
||
|
||
const decodeOption = (value?: string) => {
|
||
if (!value) return '';
|
||
try {
|
||
return decodeURIComponent(value);
|
||
} catch (error) {
|
||
return value;
|
||
}
|
||
};
|
||
|
||
export const resolveMerchantUrlFromOptions = (options: Record<string, string>) => {
|
||
return decodeOption(options.url || options.qr_url || '');
|
||
};
|
||
|
||
const readQueryParam = (raw: string, key: string) => {
|
||
const match = raw.match(new RegExp(`[?&]${key}=([^&]+)`));
|
||
return match ? decodeOption(match[1]) : '';
|
||
};
|
||
|
||
export const resolveMerchantUrlFromScanResult = (raw: string) => {
|
||
const text = (raw || '').trim();
|
||
if (!text) return '';
|
||
|
||
if (/^https?:\/\//i.test(text)) {
|
||
return text;
|
||
}
|
||
|
||
const fromUrl = readQueryParam(text, 'url') || readQueryParam(text, 'qr_url');
|
||
if (fromUrl) return fromUrl;
|
||
|
||
const fromQ = readQueryParam(text, 'q');
|
||
if (fromQ) return resolveMerchantUrlFromScanResult(fromQ);
|
||
|
||
return '';
|
||
};
|
||
|
||
export const setPendingMerchantUrl = (merchantUrl: string) => {
|
||
if (!merchantUrl) return;
|
||
encryptedStorage.set(PENDING_MERCHANT_URL_KEY, merchantUrl);
|
||
// @ts-ignore
|
||
if (uni.$globalData) {
|
||
// @ts-ignore
|
||
uni.$globalData.pendingMerchantUrl = merchantUrl;
|
||
}
|
||
};
|
||
|
||
export const getPendingMerchantUrl = () => {
|
||
// @ts-ignore
|
||
const fromMemory = uni.$globalData?.pendingMerchantUrl;
|
||
if (fromMemory) return String(fromMemory);
|
||
|
||
return encryptedStorage.get<string>(PENDING_MERCHANT_URL_KEY) || '';
|
||
};
|
||
|
||
export const clearPendingMerchantUrl = () => {
|
||
encryptedStorage.remove(PENDING_MERCHANT_URL_KEY);
|
||
// @ts-ignore
|
||
if (uni.$globalData) {
|
||
// @ts-ignore
|
||
uni.$globalData.pendingMerchantUrl = '';
|
||
}
|
||
};
|
||
|
||
const resolveMerchantUrlFromRawEntry = (raw?: string) => {
|
||
if (!raw) return '';
|
||
return resolveMerchantUrlFromScanResult(decodeOption(raw));
|
||
};
|
||
|
||
const getLaunchQuery = () => {
|
||
try {
|
||
const launchOptions = uni.getLaunchOptionsSync?.();
|
||
return (launchOptions?.query || {}) as Record<string, string>;
|
||
} catch (error) {
|
||
return {};
|
||
}
|
||
};
|
||
|
||
/** App.onLaunch / App.onShow:捕获支付宝普通二维码 qrCode */
|
||
export const captureMerchantUrlFromLaunchOptions = (options: Record<string, any> = {}) => {
|
||
const query = options.query || {};
|
||
const qrCode = query.qrCode || query.qr_code || '';
|
||
if (!qrCode) return '';
|
||
|
||
const merchantUrl = resolveMerchantUrlFromRawEntry(String(qrCode));
|
||
if (!merchantUrl) return '';
|
||
|
||
setPendingMerchantUrl(merchantUrl);
|
||
return merchantUrl;
|
||
};
|
||
|
||
/**
|
||
* 统一解析进页 merchantUrl:
|
||
* 1. ?url= / ?qr_url=(开发调试、内部跳转)
|
||
* 2. 微信普通二维码 options.q
|
||
* 3. 支付宝 query.qrCode(页面参数 / getLaunchOptionsSync)
|
||
* 4. App 缓存 pendingMerchantUrl
|
||
*/
|
||
export const resolveMerchantUrlFromEntry = (options: Record<string, any> = {}) => {
|
||
const direct = resolveMerchantUrlFromOptions(options as Record<string, string>);
|
||
if (direct) {
|
||
const merchantUrl = resolveMerchantUrlFromScanResult(direct);
|
||
if (merchantUrl) return merchantUrl;
|
||
}
|
||
|
||
if (options.q) {
|
||
const fromQ = resolveMerchantUrlFromRawEntry(String(options.q));
|
||
if (fromQ) return fromQ;
|
||
}
|
||
|
||
const pageQrCode = options.qrCode || options.qr_code;
|
||
if (pageQrCode) {
|
||
const fromPageQr = resolveMerchantUrlFromRawEntry(String(pageQrCode));
|
||
if (fromPageQr) return fromPageQr;
|
||
}
|
||
|
||
const launchQuery = getLaunchQuery();
|
||
const launchQrCode = launchQuery.qrCode || launchQuery.qr_code;
|
||
if (launchQrCode) {
|
||
const fromLaunch = resolveMerchantUrlFromRawEntry(String(launchQrCode));
|
||
if (fromLaunch) return fromLaunch;
|
||
}
|
||
|
||
return getPendingMerchantUrl();
|
||
};
|
||
|
||
export const getMerchantUrlHash = (merchantUrl: string) => {
|
||
return CryptoJS.SHA256(merchantUrl).toString();
|
||
};
|
||
|
||
export const getPropertyBindingCacheKeyByHash = (hash: string) => {
|
||
// Hash key keeps cache key short/stable and avoids raw URL special characters.
|
||
return `cashier:property:binding:${hash}`;
|
||
};
|
||
|
||
export const getSupplierCacheKeyByHash = (hash: string) => {
|
||
return `cashier:supplier:detail:${hash}`;
|
||
};
|
||
|
||
export const getPropertyBindingCacheKey = (merchantUrl: string) => {
|
||
return getPropertyBindingCacheKeyByHash(getMerchantUrlHash(merchantUrl));
|
||
};
|
||
|
||
export const getSupplierCacheKey = (merchantUrl: string) => {
|
||
return getSupplierCacheKeyByHash(getMerchantUrlHash(merchantUrl));
|
||
};
|
||
|
||
export const syncActiveMerchantContext = (merchantUrl: string) => {
|
||
const currentHash = getMerchantUrlHash(merchantUrl);
|
||
const previousHash = encryptedStorage.get<string>(ACTIVE_QR_HASH_CACHE_KEY) || '';
|
||
encryptedStorage.set(ACTIVE_QR_URL_CACHE_KEY, merchantUrl);
|
||
encryptedStorage.set(ACTIVE_QR_HASH_CACHE_KEY, currentHash);
|
||
|
||
return {
|
||
currentHash,
|
||
previousHash,
|
||
switched: Boolean(previousHash && previousHash !== currentHash)
|
||
};
|
||
};
|
||
|
||
export const getActiveMerchantUrl = () => encryptedStorage.get<string>(ACTIVE_QR_URL_CACHE_KEY) || '';
|
||
|
||
export const clearActiveMerchantContext = () => {
|
||
encryptedStorage.remove(ACTIVE_QR_URL_CACHE_KEY);
|
||
encryptedStorage.remove(ACTIVE_QR_HASH_CACHE_KEY);
|
||
};
|