1031 lines
27 KiB
JavaScript
1031 lines
27 KiB
JavaScript
|
|
/**
|
|||
|
|
* 支付服务工具
|
|||
|
|
* 统一管理支付相关功能
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { API_BASE } from '@/config/api.js';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 模拟支付模式配置
|
|||
|
|
* 设置为 true 时,所有支付都会使用模拟数据,不调用真实支付接口
|
|||
|
|
*/
|
|||
|
|
export const MOCK_PAYMENT_MODE = false;
|
|||
|
|
|
|||
|
|
export function mapOfficialVoiceDisplay(list) {
|
|||
|
|
const regionPrefixMap = {
|
|||
|
|
'湾区大叔': '台湾',
|
|||
|
|
'台湾小何': '台湾',
|
|||
|
|
'双节棍小哥': '台湾',
|
|||
|
|
'广州德哥': '广州',
|
|||
|
|
'浩宇小哥': '大陆',
|
|||
|
|
'湾湾小何': '台湾',
|
|||
|
|
'北京小爷': '北京',
|
|||
|
|
'京腔侃爷/Harmony': '北京',
|
|||
|
|
'林北浩儿': '台湾',
|
|||
|
|
'粤语小灿': '广东',
|
|||
|
|
'泉州子轩': '福建',
|
|||
|
|
'豫州子轩': '河南',
|
|||
|
|
'呆萌川妹': '四川',
|
|||
|
|
'广西远舟': '广西',
|
|||
|
|
'妹坨洁儿': '湖南'
|
|||
|
|
};
|
|||
|
|
return (list || []).map(v => {
|
|||
|
|
if (!v) return v;
|
|||
|
|
const vt = v.voice_type || 'CLONE';
|
|||
|
|
if (vt !== 'OFFICIAL') return v;
|
|||
|
|
const rawName = v.voice_name ? String(v.voice_name) : '';
|
|||
|
|
const prefix = rawName && regionPrefixMap[rawName] ? regionPrefixMap[rawName] : '';
|
|||
|
|
if (!prefix || !rawName) return v;
|
|||
|
|
if (rawName.indexOf(prefix + '-') === 0) return v;
|
|||
|
|
return { ...v, voice_name: `${prefix}-${rawName}` };
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 服务类型映射(后端类型 -> 前端配置)
|
|||
|
|
*/
|
|||
|
|
const SERVICE_TYPE_MAPPING = {
|
|||
|
|
'CREATE_VOICE': 'voice_clone',
|
|||
|
|
'PHOTO_REVIVAL': 'photo_revival',
|
|||
|
|
'VOLCENGINE_VIDEO': 'volcengine_video',
|
|||
|
|
'SYNTHESIZE': 'tts_synthesis',
|
|||
|
|
'VIDEO_CALL': 'video_call',
|
|||
|
|
'AI_CALL': 'conversation'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 服务类型配置(默认值,会被后端数据覆盖)
|
|||
|
|
*/
|
|||
|
|
export const SERVICE_TYPES = {
|
|||
|
|
VOICE_CLONE: {
|
|||
|
|
type: 'voice_clone',
|
|||
|
|
backendType: 'CREATE_VOICE',
|
|||
|
|
name: '声音克隆',
|
|||
|
|
desc: '克隆您的声音,让AI学会您的音色',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
},
|
|||
|
|
PHOTO_REVIVAL: {
|
|||
|
|
type: 'photo_revival',
|
|||
|
|
backendType: 'PHOTO_REVIVAL',
|
|||
|
|
name: '照片复活',
|
|||
|
|
desc: '让照片中的人开口说话',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
},
|
|||
|
|
VOLCENGINE_VIDEO: {
|
|||
|
|
type: 'volcengine_video',
|
|||
|
|
backendType: 'VOLCENGINE_VIDEO',
|
|||
|
|
name: '火山视频复活',
|
|||
|
|
desc: '火山视频复活',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
},
|
|||
|
|
TTS_SYNTHESIS: {
|
|||
|
|
type: 'tts_synthesis',
|
|||
|
|
backendType: 'SYNTHESIZE',
|
|||
|
|
name: '语音合成',
|
|||
|
|
desc: '使用已有音色合成语音',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
},
|
|||
|
|
VIDEO_CALL: {
|
|||
|
|
type: 'video_call',
|
|||
|
|
backendType: 'VIDEO_CALL',
|
|||
|
|
name: 'AI视频通话',
|
|||
|
|
desc: 'AI视频通话',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
},
|
|||
|
|
CONVERSATION: {
|
|||
|
|
type: 'conversation',
|
|||
|
|
backendType: 'AI_CALL',
|
|||
|
|
name: '实时对话',
|
|||
|
|
desc: '与AI进行实时语音对话',
|
|||
|
|
price: 0.00,
|
|||
|
|
freeTrialCount: 0
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 缓存价格数据
|
|||
|
|
let priceCache = null;
|
|||
|
|
let priceCacheTime = 0;
|
|||
|
|
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从后端获取服务价格列表
|
|||
|
|
* @returns {Promise<Array>}
|
|||
|
|
*/
|
|||
|
|
export async function fetchServicePrices() {
|
|||
|
|
// 检查缓存
|
|||
|
|
const now = Date.now();
|
|||
|
|
if (priceCache && (now - priceCacheTime) < CACHE_DURATION) {
|
|||
|
|
console.log('[Payment] 使用缓存价格数据');
|
|||
|
|
return priceCache;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('[Payment] 开始获取服务价格,API地址:', `${API_BASE}/api/pay/prices`);
|
|||
|
|
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/prices`,
|
|||
|
|
method: 'GET',
|
|||
|
|
success: (res) => {
|
|||
|
|
console.log('[Payment] 价格API响应:', res);
|
|||
|
|
|
|||
|
|
if (res.statusCode === 200 && res.data) {
|
|||
|
|
priceCache = res.data;
|
|||
|
|
priceCacheTime = now;
|
|||
|
|
|
|||
|
|
console.log('[Payment] 后端返回价格数据:', res.data);
|
|||
|
|
|
|||
|
|
// 更新SERVICE_TYPES中的价格
|
|||
|
|
let updateCount = 0;
|
|||
|
|
res.data.forEach(priceItem => {
|
|||
|
|
console.log('[Payment] 处理价格项:', priceItem);
|
|||
|
|
const frontendType = SERVICE_TYPE_MAPPING[priceItem.serviceType];
|
|||
|
|
console.log('[Payment] 映射类型:', priceItem.serviceType, '->', frontendType);
|
|||
|
|
|
|||
|
|
if (frontendType) {
|
|||
|
|
const serviceKey = Object.keys(SERVICE_TYPES).find(
|
|||
|
|
key => SERVICE_TYPES[key].type === frontendType
|
|||
|
|
);
|
|||
|
|
if (serviceKey && SERVICE_TYPES[serviceKey]) {
|
|||
|
|
console.log('[Payment] 更新服务:', serviceKey, '价格:', priceItem.price, '免费次数:', priceItem.freeTrialCount);
|
|||
|
|
SERVICE_TYPES[serviceKey].price = priceItem.price;
|
|||
|
|
SERVICE_TYPES[serviceKey].freeTrialCount = priceItem.freeTrialCount || 0;
|
|||
|
|
SERVICE_TYPES[serviceKey].name = priceItem.serviceName;
|
|||
|
|
SERVICE_TYPES[serviceKey].desc = priceItem.description || SERVICE_TYPES[serviceKey].desc;
|
|||
|
|
updateCount++;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
console.warn('[Payment] 未找到映射的前端类型:', priceItem.serviceType);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('[Payment] 价格更新完成,共更新', updateCount, '个服务');
|
|||
|
|
console.log('[Payment] 最终SERVICE_TYPES:', JSON.stringify(SERVICE_TYPES, null, 2));
|
|||
|
|
resolve(res.data);
|
|||
|
|
} else {
|
|||
|
|
console.error('[Payment] 获取价格失败,状态码:', res.statusCode);
|
|||
|
|
if (priceCache) {
|
|||
|
|
resolve(priceCache);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
reject(new Error('获取价格失败'));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 获取价格请求失败:', err);
|
|||
|
|
if (priceCache) {
|
|||
|
|
resolve(priceCache);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
reject(err);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成订单号
|
|||
|
|
*/
|
|||
|
|
function generateOrderNo() {
|
|||
|
|
const timestamp = Date.now();
|
|||
|
|
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
|||
|
|
return `ORD${timestamp}${random}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取用户信息
|
|||
|
|
*/
|
|||
|
|
function getUserInfo() {
|
|||
|
|
return {
|
|||
|
|
userId: uni.getStorageSync('userId') || '',
|
|||
|
|
token: uni.getStorageSync('token') || ''
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function fetchUsagePreview(frontendServiceType) {
|
|||
|
|
try {
|
|||
|
|
const { userId, token } = getUserInfo();
|
|||
|
|
if (!userId) return null;
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === frontendServiceType);
|
|||
|
|
const backendServiceType = service ? service.backendType : frontendServiceType;
|
|||
|
|
return await new Promise((resolve) => {
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/usage-preview?serviceType=${backendServiceType}`,
|
|||
|
|
method: 'GET',
|
|||
|
|
header: {
|
|||
|
|
'X-User-Id': userId,
|
|||
|
|
'Authorization': token ? `Bearer ${token}` : ''
|
|||
|
|
},
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.statusCode === 200 && res.data && res.data.success) {
|
|||
|
|
resolve(res.data);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
resolve(null);
|
|||
|
|
},
|
|||
|
|
fail: () => resolve(null)
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 后端资格校验:免费次数/补发次数/已支付 都视为可用。
|
|||
|
|
* @param {string} frontendServiceType - 前端 service.type
|
|||
|
|
* @returns {Promise<boolean>} - true 表示无需支付可直接使用
|
|||
|
|
*/
|
|||
|
|
async function checkBackendEligibility(frontendServiceType) {
|
|||
|
|
try {
|
|||
|
|
const { userId, token } = getUserInfo();
|
|||
|
|
if (!userId) return false;
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === frontendServiceType);
|
|||
|
|
const backendServiceType = service ? service.backendType : frontendServiceType;
|
|||
|
|
return await new Promise((resolve) => {
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/check?userId=${userId}&serviceType=${backendServiceType}`,
|
|||
|
|
method: 'GET',
|
|||
|
|
header: {
|
|||
|
|
'X-User-Id': userId,
|
|||
|
|
'Authorization': token ? `Bearer ${token}` : ''
|
|||
|
|
},
|
|||
|
|
success: (res) => {
|
|||
|
|
// 按次付费场景:只有“明确剩余次数>0”才视为可用。
|
|||
|
|
// 兼容后端可能返回的字段:
|
|||
|
|
// - paid: boolean
|
|||
|
|
// - remainingTotal / remaining / remainingCount / remainingTimes
|
|||
|
|
// - freeTrialRemaining / freeTrialLeft
|
|||
|
|
if (res.statusCode === 200 && res.data && typeof res.data.paid === 'boolean') {
|
|||
|
|
const data = res.data || {};
|
|||
|
|
console.log('[Payment] /api/pay/check 响应:', backendServiceType, data);
|
|||
|
|
|
|||
|
|
if (data.paid === false) {
|
|||
|
|
resolve(false);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const remainingTotal = (typeof data.remainingTotal === 'number') ? data.remainingTotal
|
|||
|
|
: (typeof data.remaining === 'number') ? data.remaining
|
|||
|
|
: (typeof data.remainingCount === 'number') ? data.remainingCount
|
|||
|
|
: (typeof data.remainingTimes === 'number') ? data.remainingTimes
|
|||
|
|
: null;
|
|||
|
|
|
|||
|
|
const freeTrialRemaining = (typeof data.freeTrialRemaining === 'number') ? data.freeTrialRemaining
|
|||
|
|
: (typeof data.freeTrialLeft === 'number') ? data.freeTrialLeft
|
|||
|
|
: null;
|
|||
|
|
|
|||
|
|
// 有任何一种剩余次数>0,则允许跳过支付
|
|||
|
|
if ((typeof remainingTotal === 'number' && remainingTotal > 0) || (typeof freeTrialRemaining === 'number' && freeTrialRemaining > 0)) {
|
|||
|
|
resolve(true);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// paid=true 但没有明确剩余次数(或<=0),视为需要付费
|
|||
|
|
resolve(false);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
resolve(false);
|
|||
|
|
},
|
|||
|
|
fail: () => resolve(false)
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Payment] 后端资格校验异常:', e);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建订单
|
|||
|
|
* @param {string} serviceType - 服务类型
|
|||
|
|
* @param {object} extraData - 额外数据
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
export async function createOrder(serviceType, extraData = {}) {
|
|||
|
|
try {
|
|||
|
|
// 先获取最新价格
|
|||
|
|
await fetchServicePrices();
|
|||
|
|
|
|||
|
|
// 获取服务配置
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === serviceType);
|
|||
|
|
if (!service) {
|
|||
|
|
throw new Error('无效的服务类型');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const orderNo = generateOrderNo();
|
|||
|
|
|
|||
|
|
console.log('[Payment] 创建订单:', {
|
|||
|
|
orderNo,
|
|||
|
|
serviceType,
|
|||
|
|
serviceName: service.name,
|
|||
|
|
amount: service.price
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
orderNo,
|
|||
|
|
serviceType,
|
|||
|
|
serviceName: service.name,
|
|||
|
|
amount: service.price,
|
|||
|
|
service,
|
|||
|
|
...extraData
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Payment] 创建订单失败:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取支付渠道
|
|||
|
|
* @returns {string} 'wechat' | 'alipay'
|
|||
|
|
*/
|
|||
|
|
function getPaymentChannel() {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
return 'wechat';
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef MP-ALIPAY
|
|||
|
|
return 'alipay';
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// App环境:可以根据用户选择或默认使用微信
|
|||
|
|
// 这里默认使用微信,也可以让用户选择
|
|||
|
|
return uni.getStorageSync('preferred_payment') || 'wechat';
|
|||
|
|
// #endif
|
|||
|
|
// 默认返回微信
|
|||
|
|
return 'wechat';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建后端支付订单
|
|||
|
|
* @param {string} serviceType - 服务类型(前端类型)
|
|||
|
|
* @param {number} userId - 用户ID
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
export async function createBackendOrder(serviceType, userId) {
|
|||
|
|
// 模拟支付模式:直接返回模拟订单数据
|
|||
|
|
if (MOCK_PAYMENT_MODE) {
|
|||
|
|
console.log('[Payment] 模拟支付模式:创建模拟订单');
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === serviceType);
|
|||
|
|
const mockOrderNo = 'MOCK_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|||
|
|
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
resolve({
|
|||
|
|
orderNo: mockOrderNo,
|
|||
|
|
userId: userId,
|
|||
|
|
serviceType: service ? service.backendType : serviceType,
|
|||
|
|
serviceName: service ? service.name : '未知服务',
|
|||
|
|
amount: service ? service.price : 0,
|
|||
|
|
status: 'PENDING',
|
|||
|
|
paymentChannel: 'mock',
|
|||
|
|
qrCodeUrl: 'MOCK_QR_CODE_' + mockOrderNo,
|
|||
|
|
createdAt: new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
}, 500); // 模拟网络延迟
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 真实支付模式
|
|||
|
|
const channel = getPaymentChannel();
|
|||
|
|
|
|||
|
|
// 将前端类型转换为后端类型
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === serviceType);
|
|||
|
|
const backendServiceType = service ? service.backendType : serviceType;
|
|||
|
|
|
|||
|
|
console.log('[Payment] 服务类型转换:', serviceType, '->', backendServiceType);
|
|||
|
|
|
|||
|
|
// 获取用户标识用于小程序支付
|
|||
|
|
let bizId = null;
|
|||
|
|
if (channel === 'wechat') {
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
const openid = uni.getStorageSync('wx_openid');
|
|||
|
|
console.log('[Payment] wx_openid:', openid);
|
|||
|
|
if (openid) {
|
|||
|
|
bizId = 'wx_openid_' + openid;
|
|||
|
|
} else {
|
|||
|
|
console.error('[Payment] 缺少wx_openid,无法创建微信小程序支付订单');
|
|||
|
|
return Promise.reject(new Error('缺少openid,请先退出并重新微信登录'));
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// App微信支付不需要openid
|
|||
|
|
bizId = 'wx_app';
|
|||
|
|
// #endif
|
|||
|
|
} else if (channel === 'alipay') {
|
|||
|
|
const alipayUserId = uni.getStorageSync('alipay_user_id');
|
|||
|
|
if (alipayUserId) {
|
|||
|
|
bizId = 'alipay_user_' + alipayUserId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
console.log('[Payment] 下单参数:', { userId, serviceType: backendServiceType, paymentChannel: channel, bizId });
|
|||
|
|
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/orders`,
|
|||
|
|
method: 'POST',
|
|||
|
|
header: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
|
|||
|
|
},
|
|||
|
|
data: {
|
|||
|
|
userId: userId,
|
|||
|
|
serviceType: backendServiceType,
|
|||
|
|
paymentChannel: channel,
|
|||
|
|
bizId: bizId
|
|||
|
|
},
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.statusCode === 200 && res.data) {
|
|||
|
|
console.log('[Payment] 后端订单创建成功:', res.data);
|
|||
|
|
resolve(res.data);
|
|||
|
|
} else {
|
|||
|
|
console.error('[Payment] 后端订单创建失败:', {
|
|||
|
|
statusCode: res.statusCode,
|
|||
|
|
data: res.data,
|
|||
|
|
header: res.header
|
|||
|
|
});
|
|||
|
|
if (res.data && res.data.message) {
|
|||
|
|
console.error('[Payment] 后端错误信息:', res.data.message);
|
|||
|
|
}
|
|||
|
|
reject(new Error(res.data?.message || '创建订单失败'));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 创建订单请求失败:', err);
|
|||
|
|
reject(err);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行微信支付(支持小程序和App)
|
|||
|
|
* @param {object} orderData - 订单数据
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
function executeWechatPayment(orderData) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// 模拟支付模式:直接返回成功
|
|||
|
|
if (MOCK_PAYMENT_MODE) {
|
|||
|
|
console.log('[Payment] 模拟支付模式:模拟微信支付成功');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功(模拟)'
|
|||
|
|
});
|
|||
|
|
}, 1000); // 模拟1秒支付延迟
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// #ifdef MP-WEIXIN
|
|||
|
|
// 微信小程序支付
|
|||
|
|
uni.requestPayment({
|
|||
|
|
timeStamp: orderData.timeStamp || String(Date.now()),
|
|||
|
|
nonceStr: orderData.nonceStr || '',
|
|||
|
|
package: orderData.package || '',
|
|||
|
|
signType: orderData.signType || 'RSA',
|
|||
|
|
paySign: orderData.paySign || '',
|
|||
|
|
success: (res) => {
|
|||
|
|
console.log('[Payment] 微信支付成功:', res);
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/orders/${orderData.orderNo}/sync/wechat`,
|
|||
|
|
method: 'POST',
|
|||
|
|
header: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
|
|||
|
|
},
|
|||
|
|
success: (syncRes) => {
|
|||
|
|
if (syncRes.statusCode === 200) {
|
|||
|
|
console.log('[Payment] 订单同步成功:', syncRes.data);
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功'
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
console.error('[Payment] 订单同步失败:', syncRes);
|
|||
|
|
reject(new Error(syncRes.data?.message || '支付成功但订单同步失败'));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 订单同步请求失败:', err);
|
|||
|
|
reject(new Error('支付成功但订单同步请求失败'));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 微信支付失败:', err);
|
|||
|
|
if (err.errMsg && err.errMsg.includes('cancel')) {
|
|||
|
|
reject(new Error('用户取消支付'));
|
|||
|
|
} else {
|
|||
|
|
reject(new Error(err.errMsg || '支付失败'));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
uni.requestPayment({
|
|||
|
|
provider: 'wxpay',
|
|||
|
|
orderInfo: orderData.orderInfo || '',
|
|||
|
|
success: (res) => {
|
|||
|
|
console.log('[Payment] App微信支付成功:', res);
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功'
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] App微信支付失败:', err);
|
|||
|
|
if (err.errMsg && err.errMsg.includes('cancel')) {
|
|||
|
|
reject(new Error('用户取消支付'));
|
|||
|
|
} else {
|
|||
|
|
reject(new Error(err.errMsg || '支付失败'));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #if !defined(MP-WEIXIN) && !defined(APP-PLUS)
|
|||
|
|
reject(new Error('当前环境不支持微信支付'));
|
|||
|
|
// #endif
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行支付宝支付(支持小程序和App)
|
|||
|
|
* @param {object} orderData - 订单数据
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
function executeAlipayPayment(orderData) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
// 模拟支付模式:直接返回成功
|
|||
|
|
if (MOCK_PAYMENT_MODE) {
|
|||
|
|
console.log('[Payment] 模拟支付模式:模拟支付宝支付成功');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功(模拟)'
|
|||
|
|
});
|
|||
|
|
}, 1000); // 模拟1秒支付延迟
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// #ifdef MP-ALIPAY
|
|||
|
|
// 支付宝小程序支付
|
|||
|
|
my.tradePay({
|
|||
|
|
tradeNO: orderData.tradeNo || '',
|
|||
|
|
success: (res) => {
|
|||
|
|
console.log('[Payment] 支付宝支付成功:', res);
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功'
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 支付宝支付失败:', err);
|
|||
|
|
if (err.resultCode === '6001') {
|
|||
|
|
reject(new Error('用户取消支付'));
|
|||
|
|
} else {
|
|||
|
|
reject(new Error(err.memo || '支付失败'));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// App环境:暂时使用模拟支付(开发测试用)
|
|||
|
|
console.log('[Payment] App环境 - 使用模拟支付');
|
|||
|
|
setTimeout(() => {
|
|||
|
|
console.log('[Payment] 模拟支付宝支付成功');
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '支付成功(模拟)'
|
|||
|
|
});
|
|||
|
|
}, 1000); // 模拟1秒支付延迟
|
|||
|
|
|
|||
|
|
// 真实支付代码(暂时注释)
|
|||
|
|
// uni.requestPayment({
|
|||
|
|
// provider: 'alipay',
|
|||
|
|
// orderInfo: orderData.orderInfo || orderData.tradeNo || '',
|
|||
|
|
// success: (res) => {
|
|||
|
|
// console.log('[Payment] App支付宝支付成功:', res);
|
|||
|
|
// resolve({
|
|||
|
|
// success: true,
|
|||
|
|
// orderNo: orderData.orderNo,
|
|||
|
|
// payTime: new Date().toISOString(),
|
|||
|
|
// message: '支付成功'
|
|||
|
|
// });
|
|||
|
|
// },
|
|||
|
|
// fail: (err) => {
|
|||
|
|
// console.error('[Payment] App支付宝支付失败:', err);
|
|||
|
|
// if (err.errMsg && err.errMsg.includes('cancel')) {
|
|||
|
|
// reject(new Error('用户取消支付'));
|
|||
|
|
// } else {
|
|||
|
|
// reject(new Error(err.errMsg || '支付失败'));
|
|||
|
|
// }
|
|||
|
|
// }
|
|||
|
|
// });
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #if !defined(MP-ALIPAY) && !defined(APP-PLUS)
|
|||
|
|
reject(new Error('当前环境不支持支付宝支付'));
|
|||
|
|
// #endif
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 执行支付(真实版本)
|
|||
|
|
* @param {string} serviceType - 服务类型
|
|||
|
|
* @param {number} userId - 用户ID
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
export async function executePayment(serviceType, userId) {
|
|||
|
|
// 优先校验后端是否已具备使用资格(免费次数/补发/已支付)
|
|||
|
|
try {
|
|||
|
|
const eligible = await checkBackendEligibility(serviceType);
|
|||
|
|
if (eligible) {
|
|||
|
|
console.log('[Payment] 后端校验通过,跳过支付:', serviceType);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: '已具备使用资格,无需支付',
|
|||
|
|
skipPayment: true
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Payment] 后端资格校验失败(忽略继续走支付):', e);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 先获取服务价格
|
|||
|
|
await fetchServicePrices();
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === serviceType);
|
|||
|
|
|
|||
|
|
// 如果价格为0,直接返回成功,跳过支付
|
|||
|
|
if (service && service.price === 0) {
|
|||
|
|
console.log('[Payment] 服务价格为0,跳过支付流程');
|
|||
|
|
const orderNo = generateOrderNo();
|
|||
|
|
const result = {
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderNo,
|
|||
|
|
payTime: new Date().toISOString(),
|
|||
|
|
message: '免费服务,无需支付',
|
|||
|
|
free: true
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '免费使用',
|
|||
|
|
icon: 'success',
|
|||
|
|
duration: 1500
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('[Payment] 免费服务,直接返回成功:', result);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// #ifdef APP-PLUS
|
|||
|
|
// APP环境:使用真实支付流程
|
|||
|
|
try {
|
|||
|
|
uni.showLoading({
|
|||
|
|
title: '创建订单中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const orderData = await createBackendOrder(serviceType, userId);
|
|||
|
|
|
|||
|
|
uni.showLoading({
|
|||
|
|
title: '正在调起支付...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const channel = getPaymentChannel();
|
|||
|
|
let paymentResult;
|
|||
|
|
if (channel === 'wechat') {
|
|||
|
|
paymentResult = await executeWechatPayment(orderData);
|
|||
|
|
} else if (channel === 'alipay') {
|
|||
|
|
paymentResult = await executeAlipayPayment(orderData);
|
|||
|
|
} else {
|
|||
|
|
throw new Error('不支持的支付渠道');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uni.hideLoading();
|
|||
|
|
return paymentResult;
|
|||
|
|
} catch (error) {
|
|||
|
|
uni.hideLoading();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
|
|||
|
|
// #ifndef APP-PLUS
|
|||
|
|
// 小程序环境:使用真实支付流程
|
|||
|
|
try {
|
|||
|
|
uni.showLoading({
|
|||
|
|
title: '创建订单中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 1. 创建后端订单
|
|||
|
|
const orderData = await createBackendOrder(serviceType, userId);
|
|||
|
|
|
|||
|
|
uni.showLoading({
|
|||
|
|
title: '正在调起支付...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 2. 根据渠道执行支付
|
|||
|
|
const channel = getPaymentChannel();
|
|||
|
|
let paymentResult;
|
|||
|
|
|
|||
|
|
if (channel === 'wechat') {
|
|||
|
|
paymentResult = await executeWechatPayment(orderData);
|
|||
|
|
} else if (channel === 'alipay') {
|
|||
|
|
paymentResult = await executeAlipayPayment(orderData);
|
|||
|
|
} else {
|
|||
|
|
throw new Error('不支持的支付渠道');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uni.hideLoading();
|
|||
|
|
return paymentResult;
|
|||
|
|
} catch (error) {
|
|||
|
|
uni.hideLoading();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
// #endif
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查订单支付状态(真实版本)
|
|||
|
|
* @param {string} orderNo - 订单号
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
export function checkPaymentStatus(orderNo) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.request({
|
|||
|
|
url: `${API_BASE}/api/pay/orders/${orderNo}`,
|
|||
|
|
method: 'GET',
|
|||
|
|
header: {
|
|||
|
|
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
|
|||
|
|
},
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.statusCode === 200 && res.data) {
|
|||
|
|
console.log('[Payment] 查询支付状态成功:', res.data);
|
|||
|
|
resolve({
|
|||
|
|
success: true,
|
|||
|
|
orderNo: res.data.orderNo,
|
|||
|
|
status: res.data.status,
|
|||
|
|
payTime: res.data.paidAt
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
reject(new Error('查询订单状态失败'));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('[Payment] 查询订单状态失败:', err);
|
|||
|
|
reject(err);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 完整的支付流程
|
|||
|
|
* @param {string} serviceType - 服务类型
|
|||
|
|
* @param {object} options - 配置选项
|
|||
|
|
* @returns {Promise}
|
|||
|
|
*/
|
|||
|
|
export async function processPayment(serviceType, options = {}) {
|
|||
|
|
const {
|
|||
|
|
extraData = {},
|
|||
|
|
onOrderCreated = null,
|
|||
|
|
onPaymentSuccess = null,
|
|||
|
|
onPaymentFailed = null
|
|||
|
|
} = options;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 创建订单
|
|||
|
|
console.log('[Payment] 创建订单:', serviceType);
|
|||
|
|
const orderData = await createOrder(serviceType, extraData);
|
|||
|
|
|
|||
|
|
if (onOrderCreated) {
|
|||
|
|
onOrderCreated(orderData);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 执行支付
|
|||
|
|
console.log('[Payment] 执行支付:', serviceType);
|
|||
|
|
const userId = getUserInfo().userId;
|
|||
|
|
const paymentResult = await executePayment(serviceType, userId);
|
|||
|
|
|
|||
|
|
// 3. 支付成功
|
|||
|
|
console.log('[Payment] 支付成功:', paymentResult);
|
|||
|
|
|
|||
|
|
if (onPaymentSuccess) {
|
|||
|
|
onPaymentSuccess(paymentResult);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
orderNo: orderData.orderNo,
|
|||
|
|
...paymentResult
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Payment] 支付流程失败:', error);
|
|||
|
|
|
|||
|
|
if (onPaymentFailed) {
|
|||
|
|
onPaymentFailed(error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 显示支付弹窗并处理支付
|
|||
|
|
* 这是一个高级封装,配合PaymentModal组件使用
|
|||
|
|
* @param {object} vm - Vue实例
|
|||
|
|
* @param {string} serviceType - 服务类型
|
|||
|
|
* @param {function} onSuccess - 支付成功回调
|
|||
|
|
* @param {function} onFailed - 支付失败回调
|
|||
|
|
*/
|
|||
|
|
export async function showPaymentModal(vm, serviceType, onSuccess, onFailed) {
|
|||
|
|
// 先获取最新价格
|
|||
|
|
try {
|
|||
|
|
await fetchServicePrices();
|
|||
|
|
} catch (e) {
|
|||
|
|
// ignore
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 优先校验后端是否已具备使用资格(免费次数/补发/已支付),避免弹支付窗
|
|||
|
|
try {
|
|||
|
|
const eligible = await checkBackendEligibility(serviceType);
|
|||
|
|
if (eligible) {
|
|||
|
|
console.log('[Payment] 后端校验通过,跳过支付弹窗:', serviceType);
|
|||
|
|
try {
|
|||
|
|
const preview = await fetchUsagePreview(serviceType);
|
|||
|
|
if (preview && preview.consumeType === 'FREE_TRIAL') {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '本次使用免费次数',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 1500
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
// ignore
|
|||
|
|
}
|
|||
|
|
if (onSuccess) {
|
|||
|
|
onSuccess({ success: true, skipPayment: true, message: '已具备使用资格,无需支付' });
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[Payment] 后端资格校验失败(忽略继续弹窗):', e);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取服务配置
|
|||
|
|
const service = Object.values(SERVICE_TYPES).find(s => s.type === serviceType);
|
|||
|
|
if (!service) {
|
|||
|
|
const error = new Error('无效的服务类型');
|
|||
|
|
console.error('[Payment] 显示支付弹窗失败:', error);
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '无效的服务类型',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
if (onFailed) {
|
|||
|
|
onFailed(error);
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果价格为0,直接执行支付(跳过弹窗)
|
|||
|
|
if (service.price === 0) {
|
|||
|
|
console.log('[Payment] 服务价格为0,跳过支付弹窗,直接执行');
|
|||
|
|
try {
|
|||
|
|
const userId = getUserInfo().userId;
|
|||
|
|
const result = await executePayment(serviceType, userId);
|
|||
|
|
|
|||
|
|
// 调用成功回调
|
|||
|
|
if (onSuccess) {
|
|||
|
|
onSuccess(result);
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Payment] 免费服务执行失败:', error);
|
|||
|
|
if (onFailed) {
|
|||
|
|
onFailed(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 价格不为0,显示支付弹窗
|
|||
|
|
createOrder(serviceType)
|
|||
|
|
.then(orderData => {
|
|||
|
|
// 设置弹窗数据
|
|||
|
|
vm.paymentModalData = {
|
|||
|
|
show: true,
|
|||
|
|
serviceType: service.type,
|
|||
|
|
serviceName: service.name,
|
|||
|
|
serviceDesc: service.desc,
|
|||
|
|
price: service.price,
|
|||
|
|
orderNo: orderData.orderNo
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 保存回调
|
|||
|
|
vm._paymentOnSuccess = onSuccess;
|
|||
|
|
vm._paymentOnFailed = onFailed;
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.error('[Payment] 创建订单失败:', error);
|
|||
|
|
uni.showToast({
|
|||
|
|
title: error.message || '创建订单失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
if (onFailed) {
|
|||
|
|
onFailed(error);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理支付确认(在PaymentModal的confirm事件中调用)
|
|||
|
|
* @param {object} vm - Vue实例
|
|||
|
|
* @param {object} paymentData - 支付数据
|
|||
|
|
*/
|
|||
|
|
export async function handlePaymentConfirm(vm, paymentData) {
|
|||
|
|
try {
|
|||
|
|
// 执行支付
|
|||
|
|
const userId = getUserInfo().userId;
|
|||
|
|
const result = await executePayment(paymentData.serviceType, userId);
|
|||
|
|
|
|||
|
|
// 通知PaymentModal支付成功
|
|||
|
|
if (vm.$refs.paymentModal) {
|
|||
|
|
vm.$refs.paymentModal.paymentSuccess();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 关闭弹窗
|
|||
|
|
vm.paymentModalData.show = false;
|
|||
|
|
|
|||
|
|
// 显示成功提示
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '支付成功',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 调用成功回调
|
|||
|
|
if (vm._paymentOnSuccess) {
|
|||
|
|
vm._paymentOnSuccess(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (vm._paymentResolve) {
|
|||
|
|
vm._paymentResolve(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Payment] 支付失败:', error);
|
|||
|
|
|
|||
|
|
// 通知PaymentModal支付失败
|
|||
|
|
if (vm.$refs.paymentModal) {
|
|||
|
|
vm.$refs.paymentModal.paymentFailed(error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示错误提示
|
|||
|
|
uni.showToast({
|
|||
|
|
title: error.message || '支付失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 调用失败回调
|
|||
|
|
if (vm._paymentOnFailed) {
|
|||
|
|
vm._paymentOnFailed(error);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (vm._paymentReject) {
|
|||
|
|
vm._paymentReject(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
SERVICE_TYPES,
|
|||
|
|
createOrder,
|
|||
|
|
executePayment,
|
|||
|
|
checkPaymentStatus,
|
|||
|
|
processPayment,
|
|||
|
|
showPaymentModal,
|
|||
|
|
handlePaymentConfirm
|
|||
|
|
};
|