- 新增支付页面组件,实现JSAPI支付流程和多状态页面展示 - 配置支付页面导航条样式和标题 - app.config.ts中添加支付页面路径 - app.tsx中新增微信隐私协议授权弹窗,拦截敏感API授权流程 - api/system/file中导出ensurePrivacyAuthorized用于隐私授权预检 - 登录页和注册页新增useDidShow钩子调用ensurePrivacyAuthorized预检隐私协议 - 登录和注册接口请求迁移至统一request封装,修正响应解构 - 支付流程中新增获取openid和自动登录逻辑,支持未注册用户跳转登录 - 支付成功后通知后端确认并自动跳转到已购产品页面 - 优化支付状态判断逻辑,正确识别支付和订单状态 - 添加错误处理和用户提示,提升支付体验安全性与稳定性
141 lines
4.3 KiB
TypeScript
141 lines
4.3 KiB
TypeScript
import request from '@/utils/request';
|
||
import Taro from '@tarojs/taro'
|
||
import dayjs from 'dayjs';
|
||
import crypto from 'crypto-js';
|
||
import {Base64} from 'js-base64';
|
||
import {FileRecord} from "@/api/system/file/model";
|
||
import {TenantId} from "@/config/app";
|
||
|
||
export async function uploadOssByPath(filePath: string) {
|
||
return new Promise(async (resolve) => {
|
||
const date = new Date();
|
||
date.setMinutes(date.getMinutes() + 1);
|
||
const policyText = {
|
||
expiration: date.toISOString(), // 设置policy过期时间。
|
||
conditions: [
|
||
// 限制上传大小。
|
||
["content-length-range", 0, 1024 * 1024 * 1024],
|
||
],
|
||
};
|
||
let sts = Taro.getStorageSync('sts');
|
||
const stsExpired = Taro.getStorageSync('stsExpiredAt');
|
||
if (!sts || (stsExpired && dayjs().isBefore(dayjs(stsExpired)))) {
|
||
// @ts-ignore
|
||
const {data: {data: {credentials}}} = await request.get(`https://shop-api.websoft.top/api/oss/getSTSToken`)
|
||
Taro.setStorageSync('sts', credentials)
|
||
Taro.setStorageSync('stsExpiredAt', credentials.expiration)
|
||
sts = credentials
|
||
}
|
||
|
||
const [filename, ext] = filePath.split('.')
|
||
const key = `headers/${filename + (Math.random() * 100000)}.${ext}`
|
||
const policy = Base64.encode(JSON.stringify(policyText))
|
||
const signature = computeSignature(sts.accessKeySecret, policy)
|
||
Taro.uploadFile({
|
||
url: 'https://oss.wsdns.cn',
|
||
name: 'file',
|
||
filePath,
|
||
formData: {
|
||
key,
|
||
policy,
|
||
OSSAccessKeyId: sts.accessKeyId,
|
||
signature,
|
||
'x-oss-security-token': sts.securityToken
|
||
},
|
||
success: () => {
|
||
resolve(`https://oss.wsdns.cn/${key}`)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
const computeSignature = (accessKeySecret: string, canonicalString: string): string => {
|
||
return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret));
|
||
}
|
||
|
||
/**
|
||
* 在调用涉及用户隐私的 API(chooseImage / chooseMedia / getLocation / getPhoneNumber 等)前,
|
||
* 等待用户完成微信隐私协议授权。基础库 3.16.1+ 强制要求,未授权会抛 errno:112。
|
||
* 旧基础库或不支持隐私协议的版本直接放行。
|
||
*/
|
||
export const ensurePrivacyAuthorized = (): Promise<void> => {
|
||
return new Promise((resolve) => {
|
||
const wxAny: any = Taro
|
||
if (typeof wxAny.requirePrivacyAuthorize !== 'function') {
|
||
resolve()
|
||
return
|
||
}
|
||
if (typeof wxAny.getPrivacySetting === 'function') {
|
||
wxAny.getPrivacySetting({
|
||
success: (res: any) => {
|
||
if (res && res.needAuthorization) {
|
||
wxAny.requirePrivacyAuthorize({
|
||
success: () => resolve(),
|
||
fail: () => resolve(),
|
||
})
|
||
} else {
|
||
resolve()
|
||
}
|
||
},
|
||
fail: () => resolve(),
|
||
})
|
||
} else {
|
||
wxAny.requirePrivacyAuthorize({
|
||
success: () => resolve(),
|
||
fail: () => resolve(),
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 上传阿里云OSS
|
||
*/
|
||
export async function uploadFile() {
|
||
return new Promise(async (resolve: (result: FileRecord) => void, reject) => {
|
||
// 修复:基础库 3.16.1+ 强制隐私协议校验,先确保用户已同意
|
||
try {
|
||
await ensurePrivacyAuthorized()
|
||
} catch {
|
||
// 忽略前置授权错误,让 chooseImage 自己抛具体的 fail
|
||
}
|
||
Taro.chooseImage({
|
||
count: 1,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: async (res) => {
|
||
const tempFilePath = res.tempFilePaths[0];
|
||
// 上传图片到OSS
|
||
Taro.uploadFile({
|
||
url: 'https://server.websoft.top/api/oss/upload',
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
header: {
|
||
'content-type': 'application/json',
|
||
TenantId
|
||
},
|
||
success: (res) => {
|
||
try {
|
||
const data = JSON.parse(res.data);
|
||
if (data.code === 0) {
|
||
resolve(data.data)
|
||
} else {
|
||
reject(new Error(data.message || '上传失败'))
|
||
}
|
||
} catch (error) {
|
||
reject(new Error('解析响应数据失败'))
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(new Error('上传请求失败'))
|
||
}
|
||
})
|
||
},
|
||
fail: (err) => {
|
||
reject(new Error('选择图片失败'))
|
||
}
|
||
});
|
||
})
|
||
|
||
}
|