自动刷新Token和微信支付对接

This commit is contained in:
马梓彭 2026-04-07 23:50:41 +08:00
parent c21de31c69
commit b3ce0fe148
2 changed files with 300 additions and 56 deletions

View File

@ -607,10 +607,174 @@ Page({
},
/** 线上支付 */
onPayOnline() {
async onPayOnline() {
const payload = this._validateAndGetPayload(2);
if (!payload) return;
this._submitApply(payload);
try {
// 1. 获取 openid
const openid = await this._getOpenId();
if (!openid) {
wx.showToast({ title: '获取 openid 失败', icon: 'none' });
return;
}
// 2. 提交报名
this.setData({ submitting: true });
const registrationId = await this._submitApplyForPayment(payload);
// 3. 选择支付方式并获取支付参数
await this._selectPaymentMethod(registrationId, openid, 2);
// 4. 获取支付信息
const paymentInfo = await this._getPaymentInfo(registrationId, openid);
// 5. 调起微信支付
await this._invokeWeChatPay(paymentInfo.wechatPayParams, registrationId);
} catch (error) {
console.error('支付流程异常:', error);
wx.showToast({
title: error && error.message ? error.message : '支付失败,请稍后重试',
icon: 'none'
});
} finally {
this.setData({ submitting: false });
}
},
/** 获取 openid */
async _getOpenId() {
const app = getApp();
const cached =
(app && app.globalData && app.globalData.openid) || wx.getStorageSync('openid') || '';
if (cached) {
return cached;
}
return new Promise((resolve, reject) => {
wx.login({
success: (loginRes) => {
const code = loginRes.code;
if (!code) {
reject(new Error('未获取到 code'));
return;
}
request({
path: API.wechatOpenidByJsCode,
method: 'POST',
needAuth: false,
data: { code },
contentType: 'application/json'
})
.then((data) => {
const oid =
data && typeof data === 'object'
? data.openId || data.OpenId || data.openid || ''
: '';
const openid = typeof oid === 'string' ? oid : '';
if (openid && app && typeof app.setAuth === 'function') {
app.setAuth({ openid });
}
if (openid) {
resolve(openid);
} else {
reject(new Error('接口未返回 openid'));
}
})
.catch((err) => {
reject(err);
});
},
fail: () => {
reject(new Error('wx.login 失败'));
}
});
});
},
/** 提交报名(用于支付流程) */
_submitApplyForPayment(payload) {
return request({
path: API.apply,
method: 'POST',
needAuth: true,
data: payload,
contentType: 'application/json'
});
},
/** 选择支付方式 */
_selectPaymentMethod(registrationId, openId, paymentMethod) {
return request({
path: '/api/eventclient/select-payment-method',
method: 'POST',
needAuth: true,
data: { registrationId, openId, paymentMethod },
contentType: 'application/json'
});
},
/** 获取支付信息 */
_getPaymentInfo(registrationId, openId) {
return request({
path: '/api/eventclient/payment-info',
method: 'POST',
needAuth: true,
data: { registrationId, openId },
contentType: 'application/json'
});
},
/** 调起微信支付 */
_invokeWeChatPay(wechatPayParams, registrationId) {
return new Promise((resolve, reject) => {
wx.requestPayment({
...wechatPayParams,
success: async () => {
try {
// 支付成功,同步支付状态
await request({
path: `/api/eventclient/sync-payment/${registrationId}`,
method: 'POST',
needAuth: true,
data: {},
contentType: 'application/json'
});
wx.showToast({ title: '支付成功', icon: 'success' });
setTimeout(() => {
wx.switchTab({ url: '/pages/mine/mine' });
}, 1500);
resolve();
} catch (err) {
console.error('同步支付状态失败:', err);
wx.showModal({
title: '提示',
content: '支付成功,但同步状态失败,请稍后在订单列表中查看',
showCancel: false,
success: () => {
wx.switchTab({ url: '/pages/mine/mine' });
}
});
resolve();
}
},
fail: (err) => {
console.error('微信支付失败:', err);
if (err.errMsg && err.errMsg.includes('cancel')) {
wx.showToast({ title: '支付已取消', icon: 'none' });
} else {
wx.showToast({
title: err.errMsg || '支付失败,请稍后重试',
icon: 'none'
});
}
reject(err);
}
});
});
},
_submitApply(payload) {

View File

@ -1,15 +1,27 @@
const { getBaseUrl } = require('./constants');
function withAuthHeader(needAuth, header) {
// Token刷新状态和请求队列
let isRefreshing = false;
let requestQueue = [];
function withAuthHeader(needAuth, header, useRefreshTokenOnly = false) {
const app = getApp();
if (!needAuth) return header;
const token = app && app.globalData ? app.globalData.accessToken : '';
const accessToken = app && app.globalData ? app.globalData.accessToken : '';
const refreshToken = app && app.globalData ? app.globalData.refreshToken : '';
if (typeof token === 'string' && token.length > 0) {
header = header || {};
header.Authorization = `Bearer ${token}`;
header['x-access-token'] = `Bearer ${token}`;
header['x-refresh-token'] = `Bearer ${refreshToken}`;
header = header || {};
if (useRefreshTokenOnly && refreshToken) {
// 只使用 refresh token 模式(用于刷新)
header['X-Authorization'] = `Bearer ${refreshToken}`;
} else if (accessToken) {
// 正常模式:使用 access token
header.Authorization = `Bearer ${accessToken}`;
// 同时带上 refresh token,供后端在需要时自动刷新
if (refreshToken) {
header['X-Authorization'] = `Bearer ${refreshToken}`;
}
}
return header;
}
@ -37,18 +49,68 @@ function getHeaderValue(headers, headerName) {
function tryUpdateAccessTokenFromResponseHeaders(res) {
// wx.request 的返回头在 res.header(部分环境也可能是 res.headers)
console.log( 'res', res);
const headers = (res && res.header) || (res && res.headers) || null;
const newAccessToken = getHeaderValue(headers, 'x-access-token');
if (!newAccessToken) return;
// 从响应头获取新的 token
const newAccessToken = getHeaderValue(headers, 'access-token') || getHeaderValue(headers, 'x-access-token');
const newRefreshToken = getHeaderValue(headers, 'x-access-token') || getHeaderValue(headers, 'x-refresh-token');
if (!newAccessToken && !newRefreshToken) return false;
const app = getApp();
if (app && typeof app.setAuth === 'function') {
app.setAuth({ accessToken: newAccessToken });
} else {
wx.setStorageSync('accessToken', newAccessToken);
if (app && app.globalData) app.globalData.accessToken = newAccessToken;
const authData = {};
let hasUpdate = false;
if (newAccessToken && newAccessToken !== 'invalid_token') {
authData.accessToken = newAccessToken;
hasUpdate = true;
}
if (newRefreshToken && newRefreshToken !== 'invalid_token') {
authData.refreshToken = newRefreshToken;
hasUpdate = true;
}
if (hasUpdate) {
if (app && typeof app.setAuth === 'function') {
app.setAuth(authData);
} else {
if (authData.accessToken) {
wx.setStorageSync('accessToken', authData.accessToken);
if (app && app.globalData) app.globalData.accessToken = authData.accessToken;
}
if (authData.refreshToken) {
wx.setStorageSync('refreshToken', authData.refreshToken);
if (app && app.globalData) app.globalData.refreshToken = authData.refreshToken;
}
}
}
// 如果正在刷新Token且获取到了新Token,执行队列中的请求
if (isRefreshing && newAccessToken && newAccessToken !== 'invalid_token') {
isRefreshing = false;
requestQueue.forEach((callback) => callback(newAccessToken));
requestQueue = [];
return true;
}
return hasUpdate;
}
function clearAllTokensAndRedirect() {
wx.clearStorageSync();
const app = getApp();
if (app && app.globalData) {
app.globalData.accessToken = '';
app.globalData.refreshToken = '';
}
wx.showModal({
title: '登录失效',
content: '您的登录已失效,请重新登录。',
showCancel: false,
complete() {
wx.navigateTo({ url: '/subpackages/mine/login/login' });
}
});
}
function request({
@ -58,14 +120,18 @@ function request({
needAuth = false,
contentType = 'application/json',
// Internal flag: prevent infinite retry when refresh token也失败
__retry = false
__retry = false,
// Internal flag: 标记这是刷新token的重试请求
__refreshRetry = false
}) {
const baseUrl = getBaseUrl();
const url = `${baseUrl}${path}`;
// 正常请求:同时带上 access token 和 refresh token
// 刷新重试请求:只使用 refresh token
const header = withAuthHeader(needAuth, {
'content-type': contentType
});
}, __refreshRetry);
return new Promise((resolve, reject) => {
wx.request({
@ -75,31 +141,65 @@ function request({
header: header,
success: async (res) => {
// 如果后端在 Response Headers 下发新的 token,就替换本地 token
tryUpdateAccessTokenFromResponseHeaders(res);
const tokenUpdated = tryUpdateAccessTokenFromResponseHeaders(res);
const statusCode = res && res.data && res.data.statusCode ? res.data.statusCode : 0;
const payload = res && res.data !== undefined ? res.data : null;
// ── 401 未授权 ──────────────────────────────────────────────
if (statusCode === 401) {
wx.clearStorageSync();
const app = getApp();
if (app && app.globalData) {
app.globalData.accessToken = '';
app.globalData.refreshToken = '';
// ── 401 未授权:尝试刷新Token ──
if (statusCode === 401 && needAuth && !__retry) {
// 如果已经在刷新Token,将请求加入队列
if (isRefreshing) {
requestQueue.push((newToken) => {
resolve(request({ path, method, data, needAuth, contentType, __retry: true }));
});
return;
}
wx.showModal({
title: '登录失效',
content: '您的登录已失效,请重新登录。',
showCancel: false,
complete() {
wx.navigateTo({ url: '/subpackages/mine/login/login' });
// 开始刷新Token
// 重发原请求
isRefreshing = true;
try {
// 检查是否有 refresh token
const app = getApp();
const refreshToken = app && app.globalData ? app.globalData.refreshToken : '';
if (!refreshToken) {
throw new Error('No refresh token available');
}
});
// 重发请求
await request({
path,
method,
data,
needAuth,
contentType,
__retry: false,
__refreshRetry: true
});
// 刷新成功,现在重试原请求
isRefreshing = false;
resolve(request({ path, method, data, needAuth, contentType, __retry: true }));
return;
} catch (refreshErr) {
// 刷新失败,清除Token并跳转登录
isRefreshing = false;
requestQueue = [];
clearAllTokensAndRedirect();
reject(new Error('Unauthorized'));
return;
}
}
// ── 401 未授权(重试后仍然失败) ──
if (statusCode === 401) {
clearAllTokensAndRedirect();
reject(new Error('Unauthorized'));
return;
}
// ── 403 禁止访问 ─────────────────────────────────────────────
// ── 403 禁止访问 ──
if (statusCode === 403) {
wx.showToast({ title: '无访问权限', icon: 'none' });
reject(new Error('Forbidden'));
@ -141,27 +241,7 @@ function request({
resolve(payload);
},
fail(err) {
if (err && err.statusCode === 401 && needAuth && !__retry) {
// 401: try refresh token, then retry the original request once.
const originalErr = err;
try {
const auth = require('./auth');
if (auth && typeof auth.refreshAccessToken === 'function') {
auth
.refreshAccessToken()
.then(() => {
return request({ path, method, data, needAuth, contentType, __retry: true });
})
.then(resolve)
.catch(() => {
reject(originalErr);
});
return;
}
} catch (e) {
// ignore and fall through to rejecting original error
}
}
// 网络请求失败处理
reject(err);
}
});