y***@hzwolun.com
y***@hzwolun.com
  • 发布:2025-11-10 17:30
  • 更新:2025-11-10 17:30
  • 阅读:11

【报Bug】ios内购问题

分类:uni-app

产品分类: uniapp/App

PC开发环境操作系统: Windows

PC开发环境操作系统版本号: win11

HBuilderX类型: 正式

HBuilderX版本号: 4.84

手机系统: iOS

手机系统版本号: iOS 26

手机厂商: 苹果

手机机型: iphone13

页面类型: vue

vue版本: vue3

打包方式: 云端

项目创建方式: HBuilderX

操作步骤:

根据uni-pay组件提示配置好支付流程并且在、common/uni-config-center/uni-pay/config.js中设置沙箱模式为true,第一次调用后不触发@success事件,第二次再次调用verifyRes显示值为{},排查云端函数调用,verifyReceiptFromAppleiap方法,结果为:[uni-pay-co/ac1cc31c1762765764968164560/1821ms/ERROR] undefined
undefined请求响应状态: fail。

预期结果:

支付成功

实际结果:

无法触发支付成功事件

bug描述:

根据uni-pay组件提示配置好支付流程并且在、common/uni-config-center/uni-pay/config.js中设置沙箱模式为true,第一次调用后不触发@success事件,第二次再次调用verifyRes显示值为{},排查云端函数调用,verifyReceiptFromAppleiap方法,结果为:[uni-pay-co/ac1cc31c1762765764968164560/1821ms/ERROR] undefined
undefined请求响应状态: fail。
可能是什么问题,该怎么解决,
我在。error.js文件里加了54002错误码进行调试时,就会显示54002订单未支付
引用支付组件代码如下

<template>  
    <view class="rechargePage" @click="onRootClick">  

        <!-- 底部按钮 -->  
        <view class="bottom-bar">  
            <view class="agree-text">充值即代表同意<text style="color: #9e60ff;">《泡泡充值协议》</text></view>  
            <view class="btn-primary" @click="openPaySheet">立即充值</view>  
        </view>  

        <!-- 支付失败弹窗 -->  
        <view class="dialog-mask" v-if="showPayFail" @click.self="closeFailDialog">  
            <view class="dialog-panel box_16">  

                <view class="text-group_7">  
                    <view class="dialog-title text_21">支付失败</view>  
                    <view class="dialog-subtitle text_22">支付遇到问题,请尝试重新支付</view>  
                </view>  
                <view class="dialog-actions box_17">  
                    <view class="btn-cancel button_1" @click="closeFailDialog"><text class="text_23">取消</text></view>  
                    <view class="btn-primary button_2" @click="retryPay"><text class="text_24">继续支付</text></view>  
                </view>  
            </view>  
        </view>  

        <!-- 选择支付方式弹窗(底部弹出) -->  
        <uni-popup ref="paySheetRef" type="bottom" :mask-click="true" @maskClick="closePaySheet">  
            <view class="pay-sheet">  
                <view class="sheet-header">  
                    <view class="sheet-title font-medium">选择支付方式</view>  
                    <uni-icons class="sheet-close" type="closeempty" size="30rpx" color="#363A44"  
                        @click="closePaySheet"></uni-icons>  
                </view>  
                <view class="sheet-amount font-medium">¥ {{ currentPriceDisplay }}</view>  
                <view class="method-list">  
                    <view class="method-item" v-for="m in payMethods" :key="m.id" @click="selectMethod(m.id)">  
                        <view class="method-left">  
                            <image class="method-icon" :src="m.icon" mode="aspectFit"></image>  
                            <text class="method-name">{{ m.name }}</text>  
                        </view>  
                        <view class="radio" v-if="selectedMethod!=m.id"></view>  
                        <image class="radio" src="/static/pay/check.png" mode="" v-else></image>  
                    </view>  
                </view>  
                <view class="sheet-confirm btn-primary" @click="confirmPay">确认</view>  
            </view>  
        </uni-popup>  

        <!-- uni-pay 融合支付组件 -->  
        <uni-pay ref="payRef" @success="onPaySuccess" @fail="onPayFail" @cancel="onPayCancel"></uni-pay>  
        <!-- 苹果内购支付组件 -->  
        <uni-pay v-if="applePayEnabled" ref="applePayRef" :debug="true" :adpid="adpid"  
            return-url="/pages/pay_result/pay_result" @mounted="handleApplePayMounted" @success="onAppleSuccess"  
            @fail="onAppleFail" @cancel="onAppleCancel"></uni-pay>  
    </view>  
</template>  

<script setup>  
    import {  
        ref,  
        computed,  
        onMounted  
    } from 'vue'  
    import {  
        useStore  
    } from 'vuex'  
    import {  
        onShow  
    } from '@dcloudio/uni-app'  
    import {  
        getTotalAmount,  
        getPayComboList,  
        getHasPassword,  
        prePay,  
        wxQuery  
    } from '@/api/pay.js'  

    const store = useStore()  
    const scrollTop = ref(0)  
    const scrollViewHeight = ref(0)  

    const balance = ref(0)  
    const balanceDisplay = computed(() => balance.value.toLocaleString())  
    const totalAmount = ref(0)  
    const remainingAmount = ref(0)  
    const spentAmount = ref(0)  
    const payPassword = ref('')  
    const isOpen = ref('n')  

    const amountList = ref([])  
    const currentId = ref(undefined)  
    const currentPriceDisplay = computed(() => {  
        const cur = amountList.value.find(i => i.id === currentId.value)  
        return cur ? cur.price.toFixed(2) : '0.00'  
    })  

    const showPayFail = ref(false)  
    const showPaySheet = ref(false)  
    const paySheetRef = ref(null)  
    const selectedMethod = ref('wechat')  
    const payMethods = ref([{  
            id: 'wechat',  
            name: '微信支付',  
            icon: '/static/pay/weixin.png'  
        },  
        // {  
        //  id: 'alipay',  
        //  name: '支付宝支付',  
        //  icon: '/static/pay/alipay.png'  
        // },  
        // {  
        //  id: 'unionpay',  
        //  name: '云闪付',  
        //  icon: '/static/pay/unionpay.png'  
        // }  
    ])  
    const applePayEnabled = ref(false)  
    const appleLoading = ref(false)  
    const applePayRef = ref(null)  
    const adpid = ref('1000000001')  
    const appleOrderNo = ref('')  
    const appleOutTradeNo = ref('')  
    const hasPassword = ref(false)  
    const showMenu = ref(false)  
    const menuTop = computed(() => {  
        // 自定义导航:状态栏高度 + 导航栏内容高度(约 88rpx)  
        return (store.state.barHeight || 0) + 88  
    })  

    // uni-pay 组件引用  
    const payRef = ref(null)  

    function onRootClick() {  
        if (showMenu.value) {  
            showMenu.value = false  
        }  
    }  

    function createAppleOrder() {  
        if (!applePayEnabled.value) {  
            return  
        }  
        if (!currentId.value) {  
            uni.showToast({  
                title: '请选择充值套餐',  
                icon: 'none'  
            })  
            return  
        }  
        const currentCombo = amountList.value.find(item => item.id === currentId.value)  
        if (!currentCombo) {  
            uni.showToast({  
                title: '请选择充值套餐',  
                icon: 'none'  
            })  
            return  
        }  
        if (!applePayRef.value || typeof applePayRef.value.createOrder !== 'function') {  
            uni.showToast({  
                title: '苹果支付未初始化',  
                icon: 'none'  
            })  
            return  
        }  
        const orderNo = `ios${Date.now()}`  
        appleOrderNo.value = orderNo  
        appleOutTradeNo.value = orderNo  
        appleLoading.value = true  
        uni.showLoading({  
            title: '发起支付...'  
        })  
        try {  
            applePayRef.value.createOrder({  
                provider: 'appleiap',  
                order_no: appleOrderNo.value,  
                out_trade_no: appleOutTradeNo.value,  
                type: 'appleiap',  
                productid: 'paopao_coin_120',  
                custom: {  
                    comboId: currentId.value,  
                    coin: currentCombo.coin,  
                    price: currentCombo.price  
                }  
            })  
        } catch (err) {  
            appleLoading.value = false  
            uni.hideLoading()  
            console.error('苹果内购下单失败', err)  
            uni.showToast({  
                title: '下单失败,请重试',  
                icon: 'none'  
            })  
        }  
    }  

    function handleApplePayMounted() {  
        if (applePayRef.value && typeof applePayRef.value.appleiapRestore === 'function') {  
            applePayRef.value.appleiapRestore()  
        }  
    }  

    function onAppleSuccess(res) {  
        appleLoading.value = false  
        uni.hideLoading()  
        console.log('苹果内购支付成功', res)  
        if (res && res.user_order_success) {  
            uni.showToast({  
                title: '支付成功',  
                icon: 'success'  
            })  
            setTimeout(() => {  
                fetchTotalAmount()  
                uni.navigateTo({  
                    url: '/pages/pay_result/pay_result?status=success'  
                })  
            }, 1500)  
        } else {  
            uni.showToast({  
                title: '支付结果待确认',  
                icon: 'none'  
            })  
        }  
    }  

    function onAppleFail(err) {  
        appleLoading.value = false  
        uni.hideLoading()  
        console.error('苹果内购支付失败', err)  
        uni.showToast({  
            title: '支付失败,请重试',  
            icon: 'none'  
        })  
    }  

    function onAppleCancel() {  
        appleLoading.value = false  
        uni.hideLoading()  
        uni.showToast({  
            title: '支付已取消',  
            icon: 'none'  
        })  
    }  

    onMounted(() => {  
        const sysInfo = uni.getSystemInfoSync();  
        const pxToRpx = 750 / sysInfo.windowWidth;  
        const topBarHeight = store.state.barHeight + 88;  
        scrollViewHeight.value = sysInfo.windowHeight * pxToRpx - topBarHeight - 160;  
        applePayEnabled.value = sysInfo.platform === 'ios'  
    })  

    // 拉取钱包总额信息  
    async function fetchTotalAmount() {  
        try {  
            const res = await getTotalAmount()  
            const data = (res && (res.root || res.data)) || res || {}  
            totalAmount.value = Number(data.totalAmount || 0)  
            remainingAmount.value = Number(data.remainingAmount || 0)  
            spentAmount.value = Number(data.spentAmount || 0)  
            payPassword.value = data.payPassword || ''  
            isOpen.value = data.isOpen || 'n'  
            balance.value = remainingAmount.value  
        } catch (e) {  
            console.error('获取总额失败', e)  
        }  
    }  

    onShow(() => {  
        const platform = uni.getSystemInfoSync().platform  
        applePayEnabled.value = platform === 'ios'  
        fetchTotalAmount()  
        fetchHasPwd()  
        fetchPayCombos()  
        if (typeof plus !== 'undefined' && plus.payment && typeof plus.payment.getChannels === 'function') {  
            plus.payment.getChannels(function(channels) {  
                for (let i = 0; i < channels.length; i++) {  
                    const channel = channels[i]  
                    if (channel.id === 'appleiap') {  
                        console.log('已获取苹果内购通道')  
                        break  
                    }  
                }  
            }, function(e) {  
                console.log('获取iap支付通道失败:' + e.message)  
            })  
        } else {  
            console.log('当前环境不支持获取苹果内购通道')  
        }  
        if (applePayEnabled.value && applePayRef.value && typeof applePayRef.value.appleiapRestore ===  
            'function') {  
            applePayRef.value.appleiapRestore()  
        }  
    })  

    function toBack() {  
        uni.navigateBack({  
            delta: 1  
        })  
    }  

    function toSetPayPwd() {  
        uni.navigateTo({  
            url: '/pages/pay_password/pay_password'  
        })  
    }  

    function toModifyPayPwd() {  
        showMenu.value = false  
        uni.navigateTo({  
            url: '/pages/pay_forget_password/pay_forget_password'  
        })  
    }  

    function toDisablePayPwd() {  
        showMenu.value = false  
        uni.navigateTo({  
            url: '/pages/pay_disable_password/pay_disable_password'  
        })  
    }  

    function toggleMenu() {  
        showMenu.value = !showMenu.value  
    }  

    function toDetail() {  
        uni.navigateTo({  
            url: '/pages/pay_details/pay_details'  
        })  
    }  

    function selectAmount(item) {  
        currentId.value = item.id  
    }  

    function openPaySheet() {  
        if (!currentId.value) {  
            uni.showToast({  
                title: '请选择充值套餐',  
                icon: 'none'  
            })  
            return  
        }  
        if (applePayEnabled.value) {  
            if (appleLoading.value) {  
                return  
            }  
            createAppleOrder()  
            return  
        }  
        if (paySheetRef.value && typeof paySheetRef.value.open === 'function') {  
            paySheetRef.value.open('bottom')  
        } else {  
            showPaySheet.value = true  
        }  
    }  

    function closePaySheet() {  
        if (paySheetRef.value && typeof paySheetRef.value.close === 'function') {  
            paySheetRef.value.close()  
        }  
        showPaySheet.value = false  
    }  

    function selectMethod(id) {  
        selectedMethod.value = id  
    }  

    function confirmPay() {  
        closePaySheet()  
        openUniPay(selectedMethod.value)  
    }  

    /**  
     * 使用 uni-pay 融合支付  
     */  
    function openUniPay(payType = 'wechat') {  
        if (!currentId.value) {  
            uni.showToast({  
                title: '请选择充值套餐',  
                icon: 'none'  
            })  
            return  
        }  

        // 获取当前选中的套餐信息  
        const currentCombo = amountList.value.find(i => i.id === currentId.value)  
        if (!currentCombo) {  
            uni.showToast({  
                title: '请选择充值套餐',  
                icon: 'none'  
            })  
            return  
        }  

        // 先调用后端预下单生成订单号  
        uni.showLoading({  
            title: '生成订单...'  
        })  
        prePay({  
                comboId: currentId.value,  
                payType: payType  
            })  
            .then((res) => {  
                uni.hideLoading()  
                const order_no = res.root  
                const out_trade_no = order_no  
                // 金额从套餐列表取,若后端返回金额则优先使用  
                const amountYuan = currentCombo.price  
                const total_fee = amountYuan * 100  
                const description = (typeof root === 'object' && root.description) || `充值${currentCombo.coin}泡泡币`  

                if (payRef.value) {  
                    payRef.value.open({  
                        total_fee: total_fee,  
                        order_no: order_no,  
                        out_trade_no: out_trade_no,  
                        description: description,  
                        type: 'recharge',  
                        custom: {  
                            comboId: currentId.value,  
                            coin: currentCombo.coin,  
                            price: amountYuan,  
                            payType: payType  
                        }  
                    })  
                } else {  
                    uni.showToast({  
                        title: '支付组件未初始化',  
                        icon: 'none'  
                    })  
                }  
            })  
            .catch((err) => {  
                uni.hideLoading()  
                console.error('预下单失败', err)  
                uni.showToast({  
                    title: '下单失败,请重试',  
                    icon: 'none'  
                })  
            })  
    }  

    /**  
     * 支付成功回调  
     */  
    function onPaySuccess(res) {  
        console.log('支付成功', res)  
        uni.showToast({  
            title: '支付成功',  
            icon: 'success'  
        })  
        // 支付成功后查询微信订单号  
        try {  
            const outTradeNo = res && res.pay_order && res.pay_order.out_trade_no  
            console.log(outTradeNo)  
            if (outTradeNo) {  
                wxQuery({  
                    outTradeNo  
                }).then(r => {  
                    console.log('wxQuery 查询结果', r)  
                }).catch(e => {  
                    console.warn('wxQuery 查询失败', e)  
                })  
            }  
        } catch (e) {  
            console.warn('调用 wxQuery 异常', e)  
        }  
        // 刷新余额  
        setTimeout(() => {  
            fetchTotalAmount()  
            // 跳转到支付结果页  
            uni.navigateTo({  
                url: '/pages/pay_result/pay_result?status=success'  
            })  
        }, 1500)  
    }  

    /**  
     * 支付失败回调  
     */  
    function onPayFail(err) {  
        console.error('支付失败', err)  
        showPayFail.value = true  
    }  

    /**  
     * 支付取消回调  
     */  
    function onPayCancel() {  
        console.log('支付取消')  
        uni.showToast({  
            title: '支付已取消',  
            icon: 'none'  
        })  
    }  

    function closeFailDialog() {  
        showPayFail.value = false  
    }  

    function retryPay() {  
        showPayFail.value = false  
        // 重新发起支付  
        openUniPay()  
    }  

    // 拉取可购买套餐列表,amountList为请求结果  
    async function fetchPayCombos() {  
        try {  
            // 根据当前平台传入 comboPlatformType(ios/android)  
            const platformType = uni.getSystemInfoSync().platform // 'ios' | 'android'  
            const res = await getPayComboList({  
                comboPlatformType: platformType  
            })  
            const list = (res && (res.root || res.data)) || []  
            amountList.value = (Array.isArray(list) ? list : []).map(it => ({  
                id: it.id,  
                coin: Number(it.comboCoinNumber || 0),  
                price: Number(it.comboPrice || 0)  
            }))  
            if (amountList.value.length > 0) {  
                // 默认选中第一项  
                currentId.value = amountList.value[0].id  
            } else {  
                currentId.value = undefined  
            }  
        } catch (e) {  
            console.error('获取套餐列表失败', e)  
        }  
    }  

    // 查询是否存在支付密码  
    async function fetchHasPwd() {  
        try {  
            const res = await getHasPassword()  
            // 后端返回:root 为 boolean  
            const value = typeof res === 'boolean' ? res : (res && (res.root ?? res.data))  
            hasPassword.value = Boolean(value)  
        } catch (e) {  
            console.error('查询是否存在支付密码失败', e)  
            hasPassword.value = false  
        }  
    }  
</script>  
2025-11-10 17:30 负责人:无 分享
已邀请:

要回复问题请先登录注册