支付
This commit is contained in:
307
src/services/payment/oauth.js
Normal file
307
src/services/payment/oauth.js
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user