diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts
index 1814ae8..92e2c8c 100644
--- a/backend/src/app.module.ts
+++ b/backend/src/app.module.ts
@@ -28,6 +28,7 @@ import { PricingModule } from './modules/schemas/pricing.module'
import { ShareModule } from './modules/share/share.module'
import { InterviewReviewModule } from './modules/interview-review/interview-review.module'
import { CareerAdviceModule } from './modules/career-advice/career-advice.module'
+import { VirtualPaymentModule } from './modules/virtual-payment/virtual-payment.module'
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/zhiyin'
@@ -64,6 +65,7 @@ const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/zhiyin
ShareModule,
InterviewReviewModule,
CareerAdviceModule,
+ VirtualPaymentModule,
],
providers: [
JwtStrategy,
diff --git a/backend/src/modules/admin/admin.controller.ts b/backend/src/modules/admin/admin.controller.ts
index 52c6bdd..f431935 100644
--- a/backend/src/modules/admin/admin.controller.ts
+++ b/backend/src/modules/admin/admin.controller.ts
@@ -428,8 +428,8 @@ const DEFAULT_CONFIG = {
}
const DEFAULT_PRICING = {
- interview: { pricePerSession: 500, creditsPerPurchase: 1 },
- resumeOptimize: { freeLimit: 3, pricePerOptimize: 300, creditsPerPurchase: 1 },
+ interview: { pricePerSession: 600, creditsPerPurchase: 1 },
+ resumeOptimize: { freeLimit: 3, pricePerOptimize: 400, creditsPerPurchase: 1 },
resumeDownload: { pricePerDownload: 200, creditsPerPurchase: 1 },
plans: {
growth: {
diff --git a/backend/src/modules/payment/payment.controller.spec.ts b/backend/src/modules/payment/payment.controller.spec.ts
index ddf5efc..63ae7c7 100644
--- a/backend/src/modules/payment/payment.controller.spec.ts
+++ b/backend/src/modules/payment/payment.controller.spec.ts
@@ -36,12 +36,12 @@ describe('PaymentController', () => {
}
mockPricingService = {
getConfig: jest.fn().mockResolvedValue({
- interview: { pricePerSession: 500, creditsPerPurchase: 1 },
- resumeOptimize: { freeLimit: 3, pricePerOptimize: 300, creditsPerPurchase: 1 },
+ interview: { pricePerSession: 600, creditsPerPurchase: 1 },
+ resumeOptimize: { freeLimit: 3, pricePerOptimize: 400, creditsPerPurchase: 1 },
resumeDownload: { pricePerDownload: 200, creditsPerPurchase: 1 },
plans: {
- growth: { price: 1990, durationDays: 30, gravityPerMonth: 250, credits: { interview: 999, resumeOptimize: 20, resumeDownload: 10 }, features: [] },
- sprint: { price: 4990, durationDays: 30, gravityPerMonth: 600, credits: { interview: 999, resumeOptimize: 50, resumeDownload: 30 }, features: [] },
+ growth: { price: 1990, durationDays: 30, gravityPerMonth: 80, credits: { interview: 999, resumeOptimize: 20, resumeDownload: 10 }, features: [] },
+ sprint: { price: 4990, durationDays: 30, gravityPerMonth: 200, credits: { interview: 999, resumeOptimize: 50, resumeDownload: 30 }, features: [] },
},
}),
}
@@ -158,7 +158,7 @@ describe('PaymentController', () => {
expect(result.plan).toBe('growth')
expect(mockUser.save).toHaveBeenCalled()
expect(mockUser.plan).toBe('growth')
- expect(mockUser.gravity).toBe(250)
+ expect(mockUser.gravity).toBe(80)
expect(mockUser.freeOptimizeUsed).toBe(3)
})
diff --git a/zhiyin-app/src/composables/useGravityPurchase.ts b/zhiyin-app/src/composables/useGravityPurchase.ts
index 3bc5691..240d95b 100644
--- a/zhiyin-app/src/composables/useGravityPurchase.ts
+++ b/zhiyin-app/src/composables/useGravityPurchase.ts
@@ -81,31 +81,41 @@ export function useGravityPurchase(onPaymentSuccess?: () => void) {
if (isMp.value) {
try {
- let res = await uni.request({
- url: api('/payment/jsapi-product'), method: 'POST',
- data: { type, quantity },
+ const loginRes = await new Promise<{ code: string }>((resolve, reject) => {
+ uni.login({ provider: 'weixin', success: r => resolve(r), fail: reject })
+ })
+ if (!loginRes.code) {
+ payLoading.value = false
+ payError.value = '微信登录失败,请重试'
+ uni.showToast({ title: '微信登录失败', icon: 'none' })
+ return
+ }
+
+ const res = await uni.request({
+ url: api('/virtual-payment/create'), method: 'POST',
+ data: { type, quantity, wxCode: loginRes.code },
header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
timeout: 30000,
})
payLoading.value = false
- if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.payParams) {
- const pp = res.data.payParams as any
- currentOutTradeNo.value = res.data.outTradeNo || ''
- uni.requestPayment({
- provider: 'wxpay',
- timeStamp: pp.timeStamp,
- nonceStr: pp.nonceStr,
- package: pp.package,
- signType: pp.signType || 'RSA',
- paySign: pp.paySign,
+ if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.paySig) {
+ const vp = res.data as any
+ currentOutTradeNo.value = vp.outTradeNo
+
+ ;(wx as any).requestVirtualPayment({
+ env: vp.env,
+ offerId: vp.offerId,
+ signData: vp.signData,
+ paySig: vp.paySig,
+ signature: vp.signature,
success: () => {
- const no = currentOutTradeNo.value || res.data.outTradeNo
+ const no = currentOutTradeNo.value || vp.outTradeNo
pollPayResult(no)
},
- fail: () => {
+ fail: (err: any) => {
payError.value = '支付未完成'
- uni.showToast({ title: '支付未完成', icon: 'none' })
+ uni.showToast({ title: err?.errMsg || '支付未完成', icon: 'none' })
},
})
} else if (!res.statusCode || res.statusCode === 0) {
@@ -116,9 +126,9 @@ export function useGravityPurchase(onPaymentSuccess?: () => void) {
payError.value = errMsg
uni.showToast({ title: errMsg, icon: 'none' })
}
- } catch (e) {
+ } catch (e: any) {
payLoading.value = false
- payError.value = '网络错误,请重试'
+ payError.value = e?.errMsg || '网络错误,请重试'
uni.showToast({ title: '网络错误', icon: 'none' })
}
} else {
diff --git a/zhiyin-app/src/config.ts b/zhiyin-app/src/config.ts
index 602b50a..6e0b2d9 100644
--- a/zhiyin-app/src/config.ts
+++ b/zhiyin-app/src/config.ts
@@ -122,6 +122,10 @@ export const API_ENDPOINTS = {
CHAT: '/career-advice/chat',
POSITIONS: '/career-advice/positions',
},
+ VIRTUAL_PAYMENT: {
+ CREATE: '/virtual-payment/create',
+ CHECK: (outTradeNo: string) => `/virtual-payment/check/${outTradeNo}`,
+ },
} as const
const PROD_API_HOST = import.meta.env.VITE_PROD_API_HOST || 'https://zhiyinwx.yzrcloud.cn'
diff --git a/zhiyin-app/src/pages/interview/interview.vue b/zhiyin-app/src/pages/interview/interview.vue
index a4e9152..24e0bcf 100644
--- a/zhiyin-app/src/pages/interview/interview.vue
+++ b/zhiyin-app/src/pages/interview/interview.vue
@@ -90,22 +90,22 @@
-
+
-
-
+
+
引力值不足
您的引力值不足,请补充后继续面试(每次面试消耗 5 引力值)
-
- 官网购买引力值
- 前往网页版充值
- 打开官网 H5 页面,支持多种支付方式
+
+ 购买引力值
+ 前往充值
+ 小程序内直接购买,微信支付安全便捷
- 取消
+ 取消
@@ -136,7 +136,7 @@ const scrollToId = ref('')
const position = ref('')
const avatarMode = ref(true)
const showPositionPicker = ref(false)
-const showH5BuyModal = ref(false)
+const showGravityBuyModal = ref(false)
const positions = ref([])
const positionsLoading = ref(false)
const aiSpeechText = ref('')
@@ -326,25 +326,13 @@ function onAvatarSilent() {
const goResult = () => uni.navigateTo({ url: `/pages/report/report?interviewId=${interviewId.value}` })
-// 官网购买引力值
-const goH5Buy = () => {
- showH5BuyModal.value = true
+// 购买引力值
+const goGravityBuy = () => {
+ showGravityBuyModal.value = true
}
-const goH5BuyAndClose = () => {
- showH5BuyModal.value = false
- const token = uni.getStorageSync('token') || ''
- const url = `https://zhiyin.yzrcloud.cn/?buy=gravity${token ? '&token=' + token : ''}`
- // #ifdef MP-WEIXIN
- uni.setClipboardData({
- data: url,
- success: () => {
- uni.showToast({ title: '链接已复制,请在手机浏览器中打开', icon: 'none', duration: 3000 })
- },
- fail: () => {
- uni.showToast({ title: '复制失败,请手动访问 zhiyin.yzrcloud.cn', icon: 'none', duration: 3000 })
- },
- })
- // #endif
+const goGravityBuyAndClose = () => {
+ showGravityBuyModal.value = false
+ uni.navigateTo({ url: '/pages/member/member' })
}
const scrollToBottom = () => {
nextTick(() => { scrollToId.value = ''; setTimeout(() => { scrollToId.value = 'msg-bottom' }, 100) })
diff --git a/zhiyin-app/src/pages/user/user.vue b/zhiyin-app/src/pages/user/user.vue
index ee8d702..a0b843f 100644
--- a/zhiyin-app/src/pages/user/user.vue
+++ b/zhiyin-app/src/pages/user/user.vue
@@ -53,7 +53,7 @@
分享得引力值
贡献面经
- 官网购买
+ 购买引力值
@@ -77,14 +77,11 @@
-
-
-
+
🛒
- 官网购买
- 通过官网网页端可购买引力值套餐,支持更多支付方式
+ 购买引力值
+ 小程序内直接购买,微信支付安全便捷
›
@@ -278,22 +275,7 @@ const goLogin = () => uni.navigateTo({ url: '/pages/login/login' })
const showGetGravityModal = ref(false)
const openBuyModal = () => { showGetGravityModal.value = true }
const goH5Buy = () => {
- const token = uni.getStorageSync('token') || ''
- const url = `https://zhiyin.yzrcloud.cn/?buy=gravity${token ? '&token=' + token : ''}`
- // #ifdef H5
uni.navigateTo({ url: '/pages/member/member' })
- // #endif
- // #ifdef MP-WEIXIN
- uni.setClipboardData({
- data: url,
- success: () => {
- uni.showToast({ title: '链接已复制,请在手机浏览器中打开', icon: 'none', duration: 3000 })
- },
- fail: () => {
- uni.showToast({ title: '复制失败,请手动访问 zhiyin.yzrcloud.cn', icon: 'none', duration: 3000 })
- },
- })
- // #endif
}
const goCareer = () => uni.navigateTo({ url: '/pages/career/career' })
diff --git a/zhiyin-app/src/services/api.ts b/zhiyin-app/src/services/api.ts
index cc5c821..9bd2d9a 100644
--- a/zhiyin-app/src/services/api.ts
+++ b/zhiyin-app/src/services/api.ts
@@ -105,6 +105,12 @@ const apiService = {
request(API_ENDPOINTS.CAREER.CHAT, 'POST', { message, history }, true),
positions: () => request(API_ENDPOINTS.CAREER.POSITIONS, 'GET', undefined, true),
},
+ virtualPayment: {
+ create: (data: { type: string; quantity: number; wxCode: string }) =>
+ request(API_ENDPOINTS.VIRTUAL_PAYMENT.CREATE, 'POST', data, true),
+ check: (outTradeNo: string) =>
+ request(API_ENDPOINTS.VIRTUAL_PAYMENT.CHECK(outTradeNo), 'GET', undefined, true),
+ },
}
export default apiService