This commit is contained in:
iqudoo
2026-06-17 12:50:02 +08:00
parent d574f08faf
commit c2476b9f85
14 changed files with 1338 additions and 53 deletions

View File

@@ -0,0 +1,307 @@
import { getConfig } from "../config";
import { request } from "../../utils/request";
import { toAny } from "../../utils/toany";
import { getTopWindow } from "./env";
import { getPaymentConfig } from "./config";
const DEFAULT_STORAGE_KEY_PREFIX = "iscan_wechat_openid";
let resolvedOAuthAppId = "";
function getOAuthConfig() {
return getPaymentConfig();
}
function setResolvedOAuthAppId(appId) {
if (appId) {
resolvedOAuthAppId = String(appId);
}
}
function getResolvedOAuthAppId() {
return resolvedOAuthAppId;
}
function buildStorageKey(appId) {
const cfg = getOAuthConfig();
if (cfg.storageKey) {
return cfg.storageKey;
}
const id = appId || getResolvedOAuthAppId();
if (id) {
return `${DEFAULT_STORAGE_KEY_PREFIX}_${id}`;
}
return DEFAULT_STORAGE_KEY_PREFIX;
}
function getSessionStorage() {
const topWin = getTopWindow();
if (!topWin) {
return null;
}
try {
return topWin.sessionStorage;
} catch (e) {
return null;
}
}
function readCachedOpenid(appId) {
const storage = getSessionStorage();
if (!storage) {
return "";
}
return storage.getItem(buildStorageKey(appId)) || "";
}
function writeCachedOpenid(appId, openid) {
const storage = getSessionStorage();
if (!storage || !openid) {
return;
}
storage.setItem(buildStorageKey(appId), openid);
}
function clearCachedOpenid(appId) {
const storage = getSessionStorage();
if (!storage) {
return;
}
const key = buildStorageKey(appId);
if (key) {
storage.removeItem(key);
}
}
function releaseWechatOAuth(appId) {
clearCachedOpenid(appId || getResolvedOAuthAppId());
if (!appId || appId === getResolvedOAuthAppId()) {
resolvedOAuthAppId = "";
}
}
function getCodeFromLocation(win) {
if (!win || !win.location) {
return "";
}
try {
const params = new URLSearchParams(win.location.search || "");
return params.get("code") || "";
} catch (e) {
return "";
}
}
function stripOAuthQueryFromUrl(win) {
if (!win || !win.history || !win.location) {
return;
}
try {
const url = new URL(win.location.href);
const keys = ["code", "state", "error", "error_description"];
let changed = false;
keys.forEach((key) => {
if (url.searchParams.has(key)) {
url.searchParams.delete(key);
changed = true;
}
});
if (!changed) {
return;
}
const next = url.pathname + url.search + url.hash;
win.history.replaceState(win.history.state, "", next);
} catch (e) {
// ignore
}
}
function getOAuthErrorFromLocation(win) {
if (!win || !win.location) {
return "";
}
try {
const params = new URLSearchParams(win.location.search || "");
const error = params.get("error");
if (!error) {
return "";
}
return params.get("error_description") || error;
} catch (e) {
return "";
}
}
function parseOAuthResponse(res) {
const body = toAny(res && res.data, {});
if (!body) {
throw new Error("wechat oauth response is empty");
}
if (body.code !== undefined && body.code !== 0) {
throw new Error(body.msg || body.message || "wechat oauth failed");
}
const data = body.data != null ? body.data : body;
const appId = data && data.appId;
const openid = data && data.openid;
if (!appId) {
throw new Error("wechat oauth appId is empty");
}
if (!openid) {
throw new Error("wechat oauth openid is empty");
}
setResolvedOAuthAppId(appId);
return {
appId: String(appId),
openid: String(openid),
};
}
function parseAuthorizeUrlResponse(res) {
const body = toAny(res && res.data, {});
if (!body) {
throw new Error("wechat oauth authorize url response is empty");
}
if (body.code !== undefined && body.code !== 0) {
throw new Error(body.msg || body.message || "wechat oauth authorize url failed");
}
const data = body.data != null ? body.data : body;
const appId = data && data.appId;
const authorizeUrl = data && (data.authorizeUrl || data.authorize_url);
if (!appId) {
throw new Error("wechat oauth authorizeUrl appId is empty");
}
if (!authorizeUrl) {
throw new Error("wechat oauth authorizeUrl is empty");
}
setResolvedOAuthAppId(appId);
return {
appId: String(appId),
authorizeUrl: String(authorizeUrl),
};
}
function buildDefaultRedirectUri(win) {
if (!win || !win.location) {
return "";
}
try {
const url = new URL(win.location.href);
url.searchParams.delete("code");
url.searchParams.delete("state");
url.searchParams.delete("error");
url.searchParams.delete("error_description");
url.hash = "";
return url.href.split("#")[0];
} catch (e) {
return win.location.href.split("#")[0];
}
}
function resolveRedirectUri(options, win) {
const opts = toAny(options, {});
const cfg = getOAuthConfig();
if (opts.redirectUri) {
return String(opts.redirectUri).split("#")[0];
}
if (cfg.redirectUri) {
return String(cfg.redirectUri).split("#")[0];
}
return buildDefaultRedirectUri(win);
}
function exchangeCodeForOpenid(code) {
const cfg = getOAuthConfig();
const apiUrl = cfg.oauthApiUrl;
if (!apiUrl) {
return Promise.reject(new Error("initWechatPayment.oauthApiUrl is required"));
}
return request({
url: apiUrl,
method: "POST",
json: true,
data: { code },
}).then(parseOAuthResponse);
}
function fetchAuthorizeInfo(redirectUri) {
const cfg = getOAuthConfig();
const apiUrl = cfg.oauthAuthorizeUrlApiUrl;
if (!apiUrl) {
return Promise.reject(new Error("initWechatPayment.oauthAuthorizeUrlApiUrl is required"));
}
if (!redirectUri) {
return Promise.reject(new Error("redirectUri is required for wechat oauth authorize url"));
}
return request({
url: apiUrl,
method: "POST",
json: true,
data: { redirectUri },
}).then(parseAuthorizeUrlResponse);
}
function redirectToAuthorize(authorizeUrl) {
const topWin = getTopWindow();
if (!topWin) {
return;
}
topWin.location.href = authorizeUrl;
}
function rejectWechatOAuth(topWin, appId, error) {
releaseWechatOAuth(appId);
if (topWin) {
stripOAuthQueryFromUrl(topWin);
}
return Promise.reject(error);
}
/**
* 微信支付 JSAPI先拉取授权信息获取 appId再查缓存 / 换 code / 跳转授权。
* 需要跳转授权时返回 null失败时释放 openid 缓存并 reject。
*/
export function ensureWechatOpenid(options) {
const opts = toAny(options, {});
const topWin = getTopWindow();
if (!topWin) {
return Promise.reject(new Error("window is not available"));
}
const oauthError = getOAuthErrorFromLocation(topWin);
if (oauthError) {
return rejectWechatOAuth(topWin, getResolvedOAuthAppId(), new Error(`wechat oauth failed: ${oauthError}`));
}
const redirectUri = resolveRedirectUri(opts, topWin);
const code = getCodeFromLocation(topWin);
return fetchAuthorizeInfo(redirectUri).then((authInfo) => {
const appId = authInfo.appId;
if (!opts.force) {
const cached = readCachedOpenid(appId);
if (cached) {
return cached;
}
}
if (code) {
return exchangeCodeForOpenid(code).then((result) => {
writeCachedOpenid(result.appId, result.openid);
stripOAuthQueryFromUrl(topWin);
return result.openid;
}).catch((err) => rejectWechatOAuth(topWin, appId, err));
}
redirectToAuthorize(authInfo.authorizeUrl);
return null;
}).catch((err) => rejectWechatOAuth(topWin, getResolvedOAuthAppId(), err));
}
export function clearWechatOpenidCache(appId) {
clearCachedOpenid(appId || getResolvedOAuthAppId());
}
export function getCachedWechatOpenid(appId) {
return readCachedOpenid(appId || getResolvedOAuthAppId());
}