import { ref, computed, nextTick } from 'vue' import { api } from '../config' export function useGravityPurchase(onPaymentSuccess?: () => void) { const showQuantityModal = ref(false) const buyQuantity = ref(1) const products = ref([]) const buyProductType = ref('interview') const payLoading = ref(false) const payError = ref('') const showPayModal = ref(false) const payCodeUrl = ref('') const currentOutTradeNo = ref('') const isMp = ref(false) const paySuccess = ref(false) const unitPrice = computed(() => { const p = products.value.find(p => p.type === buyProductType.value) return p?.price || 0 }) const totalPrice = computed(() => unitPrice.value * buyQuantity.value / 100) const buyGravityPerUnit = computed(() => { const p = products.value.find(p => p.type === buyProductType.value) return p?.gravity || 0 }) const changeQty = (delta: number) => { const next = buyQuantity.value + delta if (next >= 1 && next <= 99) buyQuantity.value = next } const clampQty = () => { if (buyQuantity.value < 1) buyQuantity.value = 1 if (buyQuantity.value > 99) buyQuantity.value = 99 } const loadProducts = async () => { try { const res = await uni.request({ url: api('/member/plans'), method: 'GET' }) if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.products) { const prodList: any[] = [] for (const [key, val] of Object.entries(res.data.products as Record)) { if (val?.price > 0) prodList.push({ type: key, ...val }) } products.value = prodList } } catch (e) { /* silent */ } } const openGravityPurchase = async (productType = 'interview') => { buyProductType.value = productType buyQuantity.value = 1 // #ifdef MP-WEIXIN isMp.value = true // #endif // 加载产品信息 await loadProducts() showQuantityModal.value = true } const cancelPay = () => { showPayModal.value = false payCodeUrl.value = '' payLoading.value = false payError.value = '' } const confirmProductBuy = () => { showQuantityModal.value = false startProductPay(buyProductType.value, buyQuantity.value) } const startProductPay = async (type: string, quantity = 1) => { const token = uni.getStorageSync('token') || '' if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }) return } showPayModal.value = true payLoading.value = true payError.value = '' if (isMp.value) { try { let res = await uni.request({ url: api('/payment/jsapi-product'), method: 'POST', data: { type, quantity }, header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, timeout: 30000, }) payLoading.value = false if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.payParams) { const pp = res.data.payParams as any currentOutTradeNo.value = res.data.outTradeNo || '' uni.requestPayment({ provider: 'wxpay', timeStamp: pp.timeStamp, nonceStr: pp.nonceStr, package: pp.package, signType: pp.signType || 'RSA', paySign: pp.paySign, success: () => { const no = currentOutTradeNo.value || res.data.outTradeNo pollPayResult(no) }, fail: () => { payError.value = '支付未完成' uni.showToast({ title: '支付未完成', icon: 'none' }) }, }) } else if (!res.statusCode || res.statusCode === 0) { payError.value = '网络连接失败,请检查网络后重试' uni.showToast({ title: '网络连接失败', icon: 'none' }) } else { const errMsg = res.data?.message || '购买失败' payError.value = errMsg uni.showToast({ title: errMsg, icon: 'none' }) } } catch (e) { payLoading.value = false payError.value = '网络错误,请重试' uni.showToast({ title: '网络错误', icon: 'none' }) } } else { try { const res = await uni.request({ url: api('/payment/create-product'), method: 'POST', data: { type, quantity }, header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, }) payLoading.value = false if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.codeUrl) { payCodeUrl.value = res.data.codeUrl currentOutTradeNo.value = res.data.outTradeNo // 页面需要自己渲染二维码(依赖 uqrcode) pollPayResult(res.data.outTradeNo) } else if (!res.statusCode || res.statusCode === 0) { payError.value = '网络连接失败,请检查网络后重试' uni.showToast({ title: '网络连接失败', icon: 'none' }) } else { payError.value = res.data?.message || '购买失败' uni.showToast({ title: res.data?.message || '购买失败', icon: 'none' }) } } catch (e) { payLoading.value = false payError.value = '网络错误,请重试' uni.showToast({ title: '网络错误', icon: 'none' }) } } } const pollPayResult = async (outTradeNo: string) => { if (!outTradeNo) return const maxAttempts = 30 let attempts = 0 const token = uni.getStorageSync('token') || '' const poll = async () => { attempts++ try { const res = await uni.request({ url: api(`/payment/check/${outTradeNo}`), method: 'GET', header: { 'Authorization': `Bearer ${token}` }, }) if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.status === 'success') { paySuccess.value = true showPayModal.value = false uni.showToast({ title: '充值成功!', icon: 'success' }) onPaymentSuccess?.() return } } catch (e) { /* ignore */ } if (attempts < maxAttempts) { setTimeout(poll, 2000) } else { payError.value = '支付结果查询超时,请联系客服' uni.showToast({ title: '支付查询超时', icon: 'none' }) } } setTimeout(poll, 2000) } return { showQuantityModal, buyQuantity, products, buyProductType, unitPrice, totalPrice, buyGravityPerUnit, payLoading, payError, showPayModal, payCodeUrl, paySuccess, isMp, changeQty, clampQty, loadProducts, openGravityPurchase, confirmProductBuy, cancelPay, } }