// pages/buy/buy.js // // 商业化页面:所有购买入口都落到这里。 // 职责是「确认订单 → 拉起微信支付 → 确认发货」,课程详情不在这里展示。 // UI 待设计,wxml 现在只有一个占位块,支付链路已经就位。 const api = require('../../utils/api'); const format = require('../../utils/format'); const store = require('../../utils/store'); const POLL_INTERVAL = 1500; const POLL_MAX = 8; Page({ data: { code: '', loading: true, buying: false, owned: false, title: '', coverUrl: '', priceYuan: '', }, onLoad(options) { const code = options && options.code; // 没带课程号说明是误入(比如直接输路径打开),不该停在这个页面 if (!code) { this.rejectOut('缺少课程参数'); return; } this.setData({ code: code }); this.load(code); }, load(code) { api .getCourse(code) .then((course) => { this.setData({ loading: false, owned: course.owned, title: course.title || '', coverUrl: course.coverUrl || '', priceYuan: format.fenToYuan(course.priceFen), }); // 已经拥有就不用再买了,避免重复下单 if (course.owned) this.rejectOut('你已经拥有这门课程'); }) .catch((err) => { this.setData({ loading: false }); // 课程被下架或课程号拼错,后端会返回错误,这里按同样的方式退出 this.rejectOut((err && err.message) || '课程不存在'); }); }, /** 进错页面(没带课程、课程不存在、已经买过):提示一句然后退出去。 */ rejectOut(message) { wx.showToast({ title: message, icon: 'none' }); setTimeout(() => this.back(), 1500); }, /** 退回上一页;没有上一页(直接打开)就回知识页,避免停在死页面。 */ back() { if (getCurrentPages().length > 1) { wx.navigateBack(); return; } wx.switchTab({ url: '/pages/kecheng/kecheng' }); }, /** 购买:下单 → 拉起支付 → 轮询订单状态确认发货。 */ buyNow() { if (this.data.buying || !this.data.code) return; // 购买的前置条件:用户主动使用过登录功能。静默登录不算。 if (!store.isLoggedIn()) { wx.showModal({ title: '请先登录', content: '使用微信登录后才能购买课程', confirmText: '去登录', success: (res) => { if (res.confirm) { wx.navigateTo({ url: '/pages/LoginOrOutDetails/LoginOrOutDetails' }); } }, }); return; } if (typeof wx.requestVirtualPayment !== 'function') { wx.showToast({ title: '当前微信版本不支持支付,请升级微信', icon: 'none' }); return; } this.setData({ buying: true }); wx.showLoading({ title: '发起支付', mask: true }); api .createOrder(this.data.code) .then((order) => { wx.hideLoading(); return api.requestVirtualPayment(order.payData).then(() => order.outTradeNo); }) .then((outTradeNo) => this.waitForDelivery(outTradeNo)) .catch((err) => { wx.hideLoading(); this.setData({ buying: false }); // 用户主动取消不算错误 if (err && err.errMsg && err.errMsg.indexOf('cancel') >= 0) return; wx.showToast({ title: (err && err.message) || '支付失败', icon: 'none' }); }); }, /** * 支付成功后权益由服务端发货推送发放,存在秒级延迟,这里轮询确认。 * 即便轮询全部超时也不会丢单:服务端有定时查单兜底。 */ waitForDelivery(outTradeNo) { let attempts = 0; const poll = () => { attempts += 1; return api.getOrder(outTradeNo).then((order) => { if (order.status === 2) { this.setData({ buying: false, owned: true }); wx.showToast({ title: '购买成功', icon: 'success', mask: true }); setTimeout(() => this.back(), 1500); return; } if (attempts >= POLL_MAX || order.status === 3 || order.status === 4) { this.setData({ buying: false }); wx.showToast({ title: '支付已提交,开通可能稍有延迟', icon: 'none', duration: 3000 }); return; } return new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)).then(poll); }); }; return poll(); }, onShareAppMessage() { return { title: '叶雷讲数学', path: '/pages/index/index' }; }, });