收藏
回答

一直遇到questVirtualPayment:fail INVALID_BUY_QUANTITY?

  1. 我在我的小程序里增加充值购买虚拟商品(充值也是一样的),然后这个是虚拟商品,所以选择了虚拟支付,但是支付一直遇到{errMsg: "requestVirtualPayment:fail INVALID_BUY_QUANTITY", errno: -15001, errCode: -15001},这个错误,以下是请求参数:buyQuantity1
  2. currencyType"CNY"
  3. env1
  4. goodsPrice30
  5. mode"short_series_goods"
  6. offerId"1450474855"
  7. orderId"vp_1769924532122_3"
  8. outTradeNo"vp_1769924532122_3"
  9. paySig"f5255ef6304f213a861ab6a405d8046541233455ace3ce1565cd056102e7d3df"
  10. productId"30602"
  11. signData"{"out_trade_no":"vp_1769924532122_3","amount":3000,"product_id":"30602","quantity":1,"env":1,"openid":"oYD-F1w6FMS70RVMbclMoBBGyfNQ"}"
  12. signature"fb78287a34e62222a719920647e7ef7cdfa36c6bb4d09a829548696aed843cad"

商品我在后台定义过了,然后最开始是走的充值,也是一直报这个错误,现在改成了购买虚拟商品也是这个错误。以下是代码:

const api = require('../../utils/api')


const defaultAvatar = '/images/logo.png'


/** Hide system-generated email (e.g. wechat_xxx@generated.com) - not meaningful to user */
function isGeneratedEmail(email) {
  if (!email || typeof email !== 'string') return true
  return /@generated\.com$/i.test(email.trim()) || /^wechat_[^@]*@/i.test(email.trim())
}


Page({
  data: {
    userNameDisplay: '微信用户(点击告诉我您的昵称)',
    hasNickname: false,
    userEmail: '',
    userAvatar: defaultAvatar,
    balance: 0,
    balanceText: '0.00',
    freeRemaining: 0,
    commodities: [],
    selectedId: null,
    selectedCommodity: null,
    agreementChecked: false
  },


  onLoad() {
    this.loadBalance()
    this.loadCommodities()
  },


  onShow() {
    this.loadUser()
    this.loadBalance()
  },



  loadBalance() {
    // GET /user/balance 一次返回 balance + freeTranslationsRemaining
    api.getBalance().then((res) => {
      if (!res.success || res.data == null) return
      const data = res.data
      const balance = typeof data === 'object' && 'balance' in data
        ? Number(data.balance) || 0
        : Number(data) || 0
      const freeRemaining = typeof data === 'object' && 'freeTranslationsRemaining' in data
        ? (data.freeTranslationsRemaining != null ? Number(data.freeTranslationsRemaining) : 0)
        : 0
      this.setData({
        balance,
        balanceText: balance.toFixed(2),
        freeRemaining
      })
    })
  },


  loadCommodities() {
    // GET /api/commodities?platform=trans-web;sale===1 已上架,按价格从高到低排序,标签按价格档:尊享/进阶/入门/尝鲜
    api.getCommodities('trans-web').then((res) => {
      if (res.success && Array.isArray(res.data)) {
        const raw = (res.data || [])
          .filter((c) => c.isAvailable !== false && Number(c.sale) === 1)
        const sorted = [...raw].sort((a, b) => (Number(b.price) || 0) - (Number(a.price) || 0))
        const tierLabels = ['尊享', '豪华']
        const commodities = sorted.map((c, index) => ({
          id: c.id,
          name: c.name,
          description: c.description || c.shortDescription || '',
          price: c.price,
          priceText: ((c.price || 0) / 100).toFixed(0),
          discount: c.discount || 0,
          discountText: ((c.discount || 0) / 100).toFixed(0),
          priceTierLabel: index < tierLabels.length ? tierLabels[index] : ''
        }))
        const selectedId = commodities.length > 0 ? commodities[0].id : null
        const selectedCommodity = commodities.length > 0 ? commodities[0] : null
        this.setData({ commodities, selectedId, selectedCommodity })
      }
    })
  },


  onSelectCommodity(e) {
    const id = e.currentTarget.dataset.id
    const commodities = this.data.commodities
    const selectedCommodity = commodities.find((c) => c.id === id) || null
    this.setData({ selectedId: id, selectedCommodity })
  },


  onToggleAgreement() {
    this.setData({ agreementChecked: !this.data.agreementChecked })
  },


  onAgreementLinkTap() {
    // Prevent agreement row tap when clicking link (navigator still navigates)
  },


  onRecharge() {
    if (!this.data.selectedCommodity) {
      wx.showToast({ title: '请先选择套餐', icon: 'none' })
      return
    }
    if (!this.data.agreementChecked) {
      wx.showModal({
        title: '服务协议与隐私政策',
        content: '请阅读并同意《服务协议》和《隐私政策》后再进行充值。是否同意?',
        confirmText: '同意',
        cancelText: '取消',
        success: (res) => {
          if (res.confirm) {
            this.setData({ agreementChecked: true })
            this.doRecharge()
          }
        }
      })
      return
    }
    this.doRecharge()
  },


  doRecharge() {
    const commodity = this.data.selectedCommodity
    const amountFen = Number(commodity.price) || 0
    if (amountFen <= 0) {
      wx.showToast({ title: '请选择有效套餐', icon: 'none' })
      return
    }
    const commodityId = commodity.id
    wx.showLoading({ title: '准备支付…' })
    api.prepareVirtualPay(commodityId, amountFen).then((res) => {
      wx.hideLoading()
      if (!res.success || !res.data) {
        wx.showToast({
          title: res.errorMessage || '获取支付参数失败',
          icon: 'none'
        })
        return
      }
      const { orderId, signData, pay_sig, signature, offerId, env, productId } = res.data
      if (!orderId || !signData || pay_sig == null || signature == null || offerId == null || env == null || !productId) {
        wx.showToast({ title: '支付参数不完整', icon: 'none' })
        return
      }
      // 商品购买:mode=short_series_goods,按文档传 buyQuantity=1、goodsPrice=价格(元)=commodity.price/100
      const mode = 'short_series_goods'
      const buyQuantity = 1
      const goodsPrice = amountFen / 100
      const requestParams = {
        orderId,
        signData,
        paySig: pay_sig,
        signature,
        offerId,
        env,
        mode,
        productId,
        buyQuantity,
        goodsPrice,
        currencyType: 'CNY',
        outTradeNo: orderId
      }
      console.log('[requestVirtualPayment] 商品 id:', commodityId, '金额(分):', amountFen, '元:', goodsPrice)
      console.log('[requestVirtualPayment] 请求参数:', requestParams)
      wx.requestVirtualPayment({
        orderId,
        signData,
        paySig: pay_sig,
        signature,
        offerId,
        env,
        mode,
        productId,
        buyQuantity,
        goodsPrice,
        currencyType: 'CNY',
        outTradeNo: orderId,
        success: () => {
          wx.showToast({ title: '购买成功', icon: 'success' })
          this.loadBalance()
        },
        fail: (err) => {
          console.error('[requestVirtualPayment] fail', err)
          const msg = (err && err.errMsg) || (err && err.message) || '支付失败'
          wx.showToast({ title: msg.indexOf('cancel') !== -1 ? '已取消' : msg, icon: 'none' })
        }
      })
    }).catch(() => {
      wx.hideLoading()
      wx.showToast({ title: '网络异常', icon: 'none' })
    })
  },


  goProfileComplete() {
    wx.navigateTo({ url: '/pages/profileComplete/profileComplete' })
  }
})


回答关注问题邀请回答
收藏

1 个回答

  • 智能回答 智能回答 本次回答由AI生成
    02-01
    有用
登录 后发表内容