import {ref, onMounted, onUnmounted} from 'vue' import {createOrder as createOrderApi, getCardPrice, queryOrder} from "@/api/system/user"; import {createEventHook, useIntervalFn, useLocalStorage, useTimeout} from "@vueuse/core"; import mxConst from "@/common/mxConst"; import _ from "lodash"; import {func, number} from "@/uni_modules/uv-ui-tools/libs/function/test"; import {useTransfer} from "@/hooks/useTransfer"; import {toast} from "@/uni_modules/uv-ui-tools/libs/function"; import config from "@/config"; import {useEnvStore} from "@/hooks/useEnvStore"; import {useUserStore} from "@/hooks/useUserStore"; export const paymentState = { NOTPAY: 'NOTPAY', SUCCESS: 'SUCCESS', CLOSED: 'CLOSED' } export const defaultPaymentOptions = { onSucceed: () => { const {transferTo} = useTransfer() uni.hideLoading() uni.showModal({ content: '支付成功, 请查收短信并绑定VIP卡', confirmText: '去绑卡', success: res => { if (res.confirm) { transferTo(mxConst.routes.activate) } } }) }, onTimeout: (msg) => { uni.hideLoading() uni.showModal({ content: msg, showCancel: false }) }, onFailed: () => { uni.hideLoading() // only print error info on console. console.error(...arguments) } } export const usePayment = function (options = defaultPaymentOptions) { const orderSucceed = createEventHook() const orderFailed = createEventHook() const orderTimeout = createEventHook() const price = ref(798) const outTradeNo = useLocalStorage(mxConst.keyOutTradeNo, '') const interval = ref(1500) let queryTimes = 8; let iframe = null let lock = false onMounted(() => loadPrice()) onUnmounted(() => { // clear resources outTradeNo.value = null clearResultFrame() }) const payment = async function () { if (lock) return toast('支付中,请稍候...') uni.showLoading({title:"", mask: true}) try { lock = true // will release while trigger func be called const {h5url, outTradeNo: no} = await createOrder() if (h5url && no) { outTradeNo.value = no clearResultFrame() queryTimes = 8; iframe = await createResultFrame(h5url) } else { triggerFailed('Payment: create order failed', h5url, no) } } catch (e) { triggerFailed('Payment: exception', e) } } const iosPayment = async function () { const {isWap2App} = useEnvStore(); const {token} = useUserStore(); if (isWap2App.value && window.webviewBridge) { uni.showLoading({ title: "", }); const { orderId } = await createOrder('ios'); uni.hideLoading(); if (orderId) { let eventArgs = [] window.webviewBridge.applePay({ orderId, token: token.value, baseUrl: config.serverBaseUrl + '/front/ecard/iosVerifyResult' }).then(res => { if (res === "支付成功") { eventArgs = ['交易成功', orderId] triggerSucceed(...eventArgs) } else { eventArgs = ['支付失败', orderId] triggerFailed(...eventArgs) } }); } } else { toast("请在app中操作"); } } const loadPrice = async function () { const res = await getCardPrice() const val = _.get(res, 'data[0].price') if (number(val)) price.value = val / 100 } const createOrder = async function (type) { const {data: {h5url, outTradeNo, orderId}} = await createOrderApi({totalFee: price.value, type}) return {h5url, outTradeNo, orderId} } const createResultFrame = async function (h5url) { const { isH5, isWap2App } = useEnvStore(); if (isH5.value) { await useTimeout() const iframe = document.createElement('iframe'); const style = `width:0px;height:0px;position: absolute;opacity: 0;z-index: -1;outline:none;border:none;`; iframe.setAttribute('style', style); iframe.setAttribute('sandbox', 'allow-scripts allow-top-navigation allow-same-origin'); iframe.style.position = 'absolute'; iframe.src = h5url; iframe.onload = () => checkOrderStatus() iframe.onerror = () => console.error('Payment: iframe加载失败') document.body.appendChild(iframe); return iframe } else if (isWap2App.value) { const style = { webviewBGTransparent: true, opacity: 0, render: 'always', zindex: -1, width: '1px', height: '1px', top:'0px', left:'0px', additionalHttpHeaders:{ Referer: config.paySiteUrl, } }; const wv = plus.webview.create(h5url, 'wechatPayWebview', style); wv.addEventListener('loaded', (e) => { checkOrderStatus(); }, false); wv.show(); return wv; } } const checkOrderStatus = async function () { if (!outTradeNo.value) return const {pause} = useIntervalFn(() => { const no = outTradeNo.value // double check if (!no) return pause() if (queryTimes-- <= 0) { triggerTimeout('支付超时') return pause(); } // 由于这个请示不是规范返回,request配置为 custom: {toast: false} queryOrder({outTradeNo: no}) .then() .catch(({tradeState}) => { console.log(no, tradeState) let eventArgs = [] switch (tradeState) { case paymentState.NOTPAY: // user payment not complete, do nothing // waiting for next order check break; case paymentState.SUCCESS: // success outTradeNo.value = '' pause() eventArgs = ['交易成功', no] triggerSucceed(...eventArgs) break case paymentState.CLOSED: // timeout outTradeNo.value = '' pause() eventArgs = ['支付超时', no] triggerTimeout(...eventArgs) break default: // unexpected status outTradeNo.value = '' pause() eventArgs = [`未知交易状态:${tradeState}`, no] triggerFailed(...eventArgs) break } }); }, interval) } const triggerSucceed = function () { lock = false if (func(options?.onSucceed)) options.onSucceed(...arguments) else orderSucceed.trigger(...arguments) } const triggerTimeout = function () { lock = false if (func(options?.onTimeout)) options.onTimeout(...arguments) orderTimeout.trigger(...arguments) } const triggerFailed = function () { lock = false if (func(options?.onFailed)) options.onFailed(...arguments) orderFailed.trigger(...arguments) } const clearResultFrame = function () { const { isH5, isWap2App } = useEnvStore(); if (iframe) { if (isH5.value) { document.body.removeChild(iframe) } else if (isWap2App.value) { iframe.close(); } iframe = null; } } return { price, payment, iosPayment, onOrderSucceed: orderSucceed.on, onOrderFailed: orderFailed.on, onOrderTimeout: orderTimeout.on } }