feat: 小程序真实微信支付功能

新增:
1. /api/miniprogram/pay - 创建预支付订单API
2. /api/miniprogram/pay/notify - 支付回调处理
3. /api/miniprogram/login - 小程序登录获取openId

配置:
- 小程序AppID: wxb8bbb2b10dec74aa
- 商户号: 1318592501
- 回调地址: https://soul.cunbao.net/api/miniprogram/pay/notify

更新:
- app.js: 添加getOpenId方法,支付前获取openId
- read.js: processPayment调用真实支付接口
- 支持API不可用时回退到测试模式
This commit is contained in:
卡若
2026-01-23 05:44:21 +08:00
parent b60edb3d47
commit 0a5d470fef
5 changed files with 646 additions and 45 deletions

View File

@@ -6,10 +6,14 @@
App({
globalData: {
// API基础地址
baseUrl: 'https://soul.ckb.fit',
baseUrl: 'https://soul.cunbao.net',
// 小程序配置
appId: 'wxb8bbb2b10dec74aa',
// 用户信息
userInfo: null,
openId: null, // 微信openId支付必需
isLoggedIn: false,
// 书籍数据
@@ -170,7 +174,7 @@ App({
})
},
// 登录方法 - 支持模拟登录回退
// 登录方法 - 获取openId用于支付
async login() {
try {
// 获取微信登录code
@@ -181,37 +185,80 @@ App({
})
})
console.log('[App] 获取登录code成功')
try {
// 尝试发送code到服务器
const res = await this.request('/api/wechat/login', {
// 发送code到服务器获取openId
const res = await this.request('/api/miniprogram/login', {
method: 'POST',
data: { code: loginRes.code }
})
if (res.success && res.data) {
// 保存用户信息
this.globalData.userInfo = res.data.user
this.globalData.isLoggedIn = true
this.globalData.purchasedSections = res.data.user.purchasedSections || []
this.globalData.hasFullBook = res.data.user.hasFullBook || false
// 保存openId
if (res.data.openId) {
this.globalData.openId = res.data.openId
wx.setStorageSync('openId', res.data.openId)
console.log('[App] 获取openId成功')
}
wx.setStorageSync('userInfo', res.data.user)
wx.setStorageSync('token', res.data.token)
// 保存用户信息
if (res.data.user) {
this.globalData.userInfo = res.data.user
this.globalData.isLoggedIn = true
this.globalData.purchasedSections = res.data.user.purchasedSections || []
this.globalData.hasFullBook = res.data.user.hasFullBook || false
wx.setStorageSync('userInfo', res.data.user)
wx.setStorageSync('token', res.data.token || '')
}
return res.data
}
} catch (apiError) {
console.log('API登录失败使用模拟登录:', apiError)
console.log('[App] API登录失败使用模拟登录:', apiError.message)
}
// API不可用时使用模拟登录
return this.mockLogin()
} catch (e) {
console.error('登录失败:', e)
console.error('[App] 登录失败:', e)
// 最后尝试模拟登录
return this.mockLogin()
}
},
// 获取openId (支付必需)
async getOpenId() {
// 先检查缓存
const cachedOpenId = wx.getStorageSync('openId')
if (cachedOpenId) {
this.globalData.openId = cachedOpenId
return cachedOpenId
}
// 没有缓存则登录获取
try {
const loginRes = await new Promise((resolve, reject) => {
wx.login({ success: resolve, fail: reject })
})
const res = await this.request('/api/miniprogram/login', {
method: 'POST',
data: { code: loginRes.code }
})
if (res.success && res.data?.openId) {
this.globalData.openId = res.data.openId
wx.setStorageSync('openId', res.data.openId)
return res.data.openId
}
} catch (e) {
console.error('[App] 获取openId失败:', e)
}
return null
},
// 模拟登录(后端不可用时使用)
mockLogin() {

View File

@@ -361,65 +361,96 @@ ${id === 'preface' || id === 'epilogue' || id.startsWith('appendix') || id === '
await this.processPayment('fullbook', null, this.data.fullBookPrice)
},
// 处理支付
// 处理支付 - 调用真实微信支付接口
async processPayment(type, sectionId, amount) {
this.setData({ isPaying: true })
try {
// 创建订单
// 1. 先获取openId (支付必需)
let openId = app.globalData.openId || wx.getStorageSync('openId')
if (!openId) {
console.log('[Pay] 需要先获取openId')
openId = await app.getOpenId()
if (!openId) {
wx.showModal({
title: '提示',
content: '需要登录后才能支付,请先登录',
showCancel: false
})
this.setData({ showLoginModal: true, isPaying: false })
return
}
}
console.log('[Pay] 开始创建订单:', { type, sectionId, amount, openId: openId.slice(0, 10) + '...' })
// 2. 调用后端创建预支付订单
let paymentData = null
try {
const res = await app.request('/api/payment/create-order', {
const res = await app.request('/api/miniprogram/pay', {
method: 'POST',
data: { type, sectionId, amount }
data: {
openId,
productType: type,
productId: sectionId,
amount,
description: type === 'fullbook' ? '《一场Soul的创业实验》全书' : `章节-${sectionId}`,
userId: app.globalData.userInfo?.id || ''
}
})
if (res.success && res.data) {
paymentData = res.data
console.log('[Pay] 创建订单响应:', res)
if (res.success && res.data?.payParams) {
paymentData = res.data.payParams
console.log('[Pay] 获取支付参数成功')
} else {
throw new Error(res.error || '创建订单失败')
}
} catch (apiError) {
console.log('API创建订单失败,使用模拟支付')
}
// 如果API不可用使用模拟支付
if (!paymentData) {
paymentData = {
isMock: true,
orderId: 'mock_' + Date.now()
}
}
// 调用微信支付或模拟支付
if (paymentData.isMock) {
// 模拟支付确认
const confirmRes = await new Promise((resolve) => {
console.log('[Pay] API创建订单失败:', apiError.message)
// 开发环境API不可用时使用模拟支付
const useMock = await new Promise((resolve) => {
wx.showModal({
title: '确认支付',
content: `支付 ¥${amount} 购买${type === 'section' ? '本章' : '全书'}\n(测试环境模拟支付)`,
title: '支付服务暂不可用',
content: '是否使用测试模式完成购买?',
confirmText: '测试购买',
cancelText: '取消',
success: (res) => resolve(res.confirm)
})
})
if (confirmRes) {
// 模拟支付成功,更新本地数据
if (useMock) {
this.mockPaymentSuccess(type, sectionId)
wx.showToast({ title: '购买成功', icon: 'success' })
wx.showToast({ title: '测试购买成功', icon: 'success' })
this.initSection(this.data.sectionId)
}
} else {
// 真实微信支付
await this.callWechatPay(paymentData)
wx.showToast({ title: '购买成功', icon: 'success' })
this.setData({ isPaying: false })
return
}
// 刷新页面
// 3. 调用微信支付
console.log('[Pay] 调起微信支付')
await this.callWechatPay(paymentData)
// 4. 支付成功,更新本地数据
this.mockPaymentSuccess(type, sectionId)
wx.showToast({ title: '购买成功', icon: 'success' })
// 5. 刷新页面
this.initSection(this.data.sectionId)
} catch (e) {
console.error('[Pay] 支付失败:', e)
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消支付', icon: 'none' })
} else {
wx.showToast({ title: '支付失败', icon: 'none' })
wx.showToast({ title: e.errMsg || '支付失败', icon: 'none' })
}
} finally {
this.setData({ isPaying: false })