Files
scan-code-jssdk/src/services/payment/oauth.js
iqudoo 378c7e4ba5 fix
2026-06-17 16:36:12 +08:00

300 lines
7.3 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.

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();
return request({
url: cfg.oauthApiUrl,
method: "POST",
json: true,
data: { code },
}).then(parseOAuthResponse);
}
function fetchAuthorizeInfo(redirectUri) {
const cfg = getOAuthConfig();
if (!redirectUri) {
return Promise.reject(new Error("redirectUri is required for wechat oauth authorize url"));
}
return request({
url: cfg.oauthAuthorizeUrlApiUrl,
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());
}