实施美团式的支付流程,并增强相关功能
- 将支付流程统一至礼品支付页面,禁止从阅读页面进行支付,以优化用户体验。 - 更新了礼物支付详情页面,为发起人和朋友展示了不同的用户界面元素,包括为发起人提供的分享按钮和为朋友提供的支付按钮。 - 增强了后端逻辑,以确保在支付处理过程中正确将收益归因于发起人。 - 增加了每日章节更新,并改进了章节页面的加载状态,以提升用户交互体验。 - 更新了文档,以反映新的支付流程和相关变更。
This commit is contained in:
28
.cursor/agent/后端工程师/evolution/2026-03-17.md
Normal file
28
.cursor/agent/后端工程师/evolution/2026-03-17.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 后端 - 2026-03-17
|
||||
|
||||
## 代付 PayNotify 权益归属修复
|
||||
|
||||
### 问题
|
||||
|
||||
代付支付回调中,`buyerUserID` 由 openID 解析得到,即**代付人**。权益激活(全书、VIP、章节、余额充值)和分佣均用 `buyerUserID`,导致权益错误给到代付人,而非发起人。
|
||||
|
||||
### 修复
|
||||
|
||||
引入 `beneficiaryUserID`(权益归属人):
|
||||
|
||||
- **代付订单**:`beneficiaryUserID = order.UserID`(发起人)
|
||||
- **普通订单**:`beneficiaryUserID = buyerUserID`(付款人)
|
||||
|
||||
权益激活、分佣、取消未支付订单等逻辑统一改用 `beneficiaryUserID`。
|
||||
|
||||
### 经验
|
||||
|
||||
- 代付场景:`order.user_id` = 发起人,`payer_user_id` = 代付人;权益与分佣必须按 `order.user_id` 处理
|
||||
- PayNotify 中 openID 解析得到的是实际付款人,代付时需以 order 的 user_id 为权益归属
|
||||
|
||||
---
|
||||
|
||||
## gift-pay detail 返回 initiatorUserId
|
||||
|
||||
- 供小程序区分发起人/好友,展示不同 UI
|
||||
- 字段:`initiatorUserId`(发起人 user_id)
|
||||
@@ -8,3 +8,4 @@
|
||||
| 2026-03-12 | persons token 字段与 DB 迁移;CKBLead 用 token 兑换 ckb_api_key | [2026-03-12.md](./2026-03-12.md) |
|
||||
| 2026-03-14 | 内容排名算法修正:排名分公式(阅读/新度/付款前 N 名),支持 hot_score 手动覆盖 | [2026-03-14.md](./2026-03-14.md) |
|
||||
| 2026-03-16 | ParseAutoLinkContent data-label;存客宝 create planType/sceneId/status | [2026-03-16.md](./2026-03-16.md) |
|
||||
| 2026-03-17 | 代付 PayNotify beneficiaryUserID 权益归发起人;gift-pay detail 返回 initiatorUserId | [2026-03-17.md](./2026-03-17.md) |
|
||||
|
||||
21
.cursor/agent/团队/evolution/2026-03-17.md
Normal file
21
.cursor/agent/团队/evolution/2026-03-17.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 团队 - 2026-03-17
|
||||
|
||||
## 代付美团式流程与权益归属约定
|
||||
|
||||
### 流程约定
|
||||
|
||||
1. **入口**:读页「找好友代付」→ 创建请求 → **跳转代付详情页**(不再弹窗)
|
||||
2. **代付页**:发起人看到「分享给好友」,好友看到「帮他付款」
|
||||
3. **后端**:detail 返回 `initiatorUserId`,前端据此区分
|
||||
|
||||
### 权益与分佣约定
|
||||
|
||||
- 代付订单:`order.user_id` = 发起人,`payer_user_id` = 代付人
|
||||
- 权益(全书、VIP、章节、余额充值)归属发起人
|
||||
- 分佣按发起人的推荐关系计算
|
||||
- PayNotify 中 openID 解析得到的是代付人,权益与分佣必须用 `order.user_id`
|
||||
|
||||
### 同时影响
|
||||
|
||||
- 小程序:代付详情页双态 UI、读页跳转
|
||||
- 后端:PayNotify beneficiaryUserID、detail initiatorUserId
|
||||
@@ -9,3 +9,4 @@
|
||||
| 2026-03-12 | 密钥/token 设计:关联小程序 key、@ 人物 token,不暴露真实密钥、服务端兑换 | [2026-03-12.md](./2026-03-12.md) |
|
||||
| 2026-03-14 | 内容排名算法跨端复用:管理端内容排行与小程序精选推荐共用 computeArticleRankingSections | [2026-03-14.md](./2026-03-14.md) |
|
||||
| 2026-03-16 | TipTap Mention 需 data-label,否则显示 token | [2026-03-16.md](./2026-03-16.md) |
|
||||
| 2026-03-17 | 代付美团式流程与权益归属约定:读页→代付页→分享;权益/分佣归发起人 | [2026-03-17.md](./2026-03-17.md) |
|
||||
|
||||
40
.cursor/agent/小程序开发工程师/evolution/2026-03-17.md
Normal file
40
.cursor/agent/小程序开发工程师/evolution/2026-03-17.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 小程序 - 2026-03-17
|
||||
|
||||
## 代付美团式流程改造
|
||||
|
||||
### 场景
|
||||
|
||||
用户希望代付流程类似美团:先进入代付页面,再分享给好友,而非在阅读页弹窗分享。
|
||||
|
||||
### 实现
|
||||
|
||||
1. **读页「找好友代付」**:创建请求后 `wx.navigateTo` 跳转 `/pages/gift-pay/detail?requestSn=xxx`,移除弹窗
|
||||
2. **代付详情页双态**:
|
||||
- 发起人(`detail.initiatorUserId === 当前用户ID`):标题「找朋友代付」,主按钮「分享给好友」(`open-type="share"`)
|
||||
- 好友:标题「帮他付款」,主按钮「帮他付款」
|
||||
3. **后端**:detail 接口返回 `initiatorUserId` 供前端区分
|
||||
|
||||
### 经验
|
||||
|
||||
- 代付分享入口:优先「进入代付页再分享」而非「弹窗分享」,符合用户心智
|
||||
- 同一页面根据 `initiatorUserId` 与当前用户比对,展示不同 UI
|
||||
|
||||
---
|
||||
|
||||
## 代付统一到代付页(禁止阅读页代付)
|
||||
|
||||
### 规则
|
||||
|
||||
代付必须在代付页完成,禁止在阅读页代付。代付页可显示部分文章信息。
|
||||
|
||||
### 实现
|
||||
|
||||
- 读页:`gift=1&ref=requestSn` 打开时,`redirectTo` 到 gift-pay/detail,不展示阅读页
|
||||
- 代付页:已展示 description(章节标题等)、金额、发起人昵称
|
||||
|
||||
---
|
||||
|
||||
## 目录页 loading、首页最新新增
|
||||
|
||||
- 目录页:`book/parts` 加载时 `partsLoading: true`,展示旋转圈 +「加载目录中...」
|
||||
- 首页最新新增:默认 5 条、折叠状态,`>5` 时显示「展开更多」
|
||||
@@ -12,3 +12,4 @@
|
||||
| 2026-03-12 | 链接标签 mpKey、@ 人物 token 兑换:contentParser、onLinkTagTap、onMentionTap | [2026-03-12.md](./2026-03-12.md) |
|
||||
| 2026-03-14 | 我的页设置入口隐藏;资料修改引导场景梳理(登录后、@某人、找伙伴、链接卡若) | [2026-03-14.md](./2026-03-14.md) |
|
||||
| 2026-03-16 | 编辑资料页分享名片:转发/朋友圈特殊处理,Canvas 绘制封面,标题「昵称+为您分享名片」 | [2026-03-16.md](./2026-03-16.md) |
|
||||
| 2026-03-17 | 代付美团式:读页→代付页→分享;详情页双态(发起人/好友);目录 loading、最新新增 5 条折叠 | [2026-03-17.md](./2026-03-17.md) |
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
| 2026-03-16 | 软件测试 | 配置约定 | testing SKILL | pytest 架构、配置从 soul-api/.env* 读取、SOUL_TEST_ENV 必显;运行前报告头部显示测试环境,避免误测正式库 |
|
||||
| 2026-03-16 | 小程序 | 最佳实践 | miniprogram-dev SKILL §10 | 编辑资料页分享名片:转发/朋友圈特殊处理,Canvas 绘制 5:4 封面,标题「昵称+为您分享名片」,路径 member-detail |
|
||||
| 2026-03-16 | 开发助理 | 交互习惯分析 | - | 乘风读取 agent-transcripts 抽样分析:角色触发词、表达方式、工作流程、沟通风格、技术偏好、Agent 响应建议 |
|
||||
| 2026-03-17 | 小程序、后端、团队 | 业务规则/bug 修复 | - | 代付美团式:读页→代付页→分享;PayNotify beneficiaryUserID 权益归发起人;detail 返回 initiatorUserId;目录 loading、最新新增 5 条折叠 |
|
||||
| 2026-03-17 | 小程序 | 业务规则 | - | 代付统一到代付页:gift=1&ref 打开 read 时 redirectTo 代付页,禁止在阅读页代付 |
|
||||
|
||||
---
|
||||
|
||||
@@ -58,4 +60,4 @@
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-16(编辑资料页分享名片)
|
||||
**最后更新**:2026-03-17(代付美团式与 PayNotify 完善)
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
| 2026-03-11 | 会议收尾:开发团队对齐业务逻辑与以界面定需求;纪要生成、各角色经验入库、项目索引与会议索引更新 | 已完成 |
|
||||
| 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 |
|
||||
| 2026-03-16 | 乘风读取 agent-transcripts 分析交互习惯,总结经验并吸收 | 已完成 |
|
||||
| 2026-03-17 | 吸收需求:代付美团式流程、PayNotify 权益归属、目录 loading、最新新增 5 条折叠 → 开发文档与 agent | 已完成 |
|
||||
| 2026-03-17 | 乘风吸收经验与交互:迁移完成度与待办清单、运营与变更第十二部分 | 已完成 |
|
||||
| 2026-03-17 | 吸收新需求:代付统一到代付页(gift=1&ref redirectTo)→ 需求汇总、找朋友代付流程、运营与变更 | 已完成 |
|
||||
|
||||
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-16(交互习惯分析)
|
||||
**最后更新**:2026-03-17(吸收代付与体验优化需求)
|
||||
|
||||
@@ -31,9 +31,10 @@ soul-api(Go + Gin + GORM + MySQL)提供三组路由:`/api/miniprogram/*`
|
||||
| 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 |
|
||||
| 2026-03-16 | ParseAutoLinkContent 添加 data-label;存客宝 create planType=1 sceneId=9 status=1 | 已完成 |
|
||||
| 2026-03-16 | 会议:new-soul 新需求与当前项目差异分析;content_upload.py 与 chapters 一致性待核对 | 待续 |
|
||||
| 2026-03-17 | 代付 PayNotify 权益归属修复:beneficiaryUserID(代付=发起人);gift-pay detail 返回 initiatorUserId | 已完成 |
|
||||
|
||||
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-16
|
||||
**最后更新**:2026-03-17
|
||||
|
||||
@@ -27,9 +27,10 @@ Soul 创业派对全项目架构与约定:路由隔离(miniprogram/admin/db
|
||||
| 2026-03-14 | 内容排名算法跨端复用:管理端内容排行与小程序精选推荐共用 computeArticleRankingSections,排名分公式、权重配置统一 | 已完成 |
|
||||
| 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 |
|
||||
| 2026-03-16 | TipTap Mention 需 data-label 规则;链接人与事与存客宝对接优化会议收尾 | 已完成 |
|
||||
| 2026-03-17 | 代付美团式流程与权益归属约定:读页→代付页→分享;权益/分佣归发起人;PayNotify beneficiaryUserID | 已完成 |
|
||||
|
||||
> **格式说明**:每次架构级讨论后在此追加一行,日期格式 YYYY-MM-DD
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-16
|
||||
**最后更新**:2026-03-17
|
||||
|
||||
@@ -34,9 +34,12 @@
|
||||
| 2026-03-16 | 乘风发起例行开发进度同步 | 已完成 |
|
||||
| 2026-03-16 | 编辑资料页分享名片:转发/朋友圈改为分享名片,Canvas 封面(头像+昵称+四栏信息),路径 member-detail | 已完成 |
|
||||
| 2026-03-16 | 会议:new-soul 新需求与当前项目差异分析;派对AI 小程序站管理与当前一致 | 已完成 |
|
||||
| 2026-03-17 | 代付美团式:读页→创建请求→跳转代付详情页;详情页双态(发起人分享/好友帮他付款);目录 loading、首页最新新增 5 条折叠 | 已完成 |
|
||||
| 2026-03-17 | 代付统一到代付页:gift=1&ref 打开 read 时 redirectTo 代付页,禁止在阅读页代付 | 已完成 |
|
||||
| 2026-03-17 | 代付页营销:章节标题+20%内容预览;我的代付列表点击进详情;页面协调 | 已完成 |
|
||||
|
||||
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD,状态用:已完成 / 进行中 / 待续 / 搁置
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-16
|
||||
**最后更新**:2026-03-17
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
const app = getApp()
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -55,6 +56,7 @@ Page({
|
||||
this.updateUserStatus()
|
||||
this.loadVipStatus()
|
||||
this.loadParts()
|
||||
this.loadDailyChapters()
|
||||
},
|
||||
|
||||
// 懒加载:仅拉取篇章列表 + totalSections + fixedSections
|
||||
@@ -174,7 +176,34 @@ Page({
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadParts().then(() => wx.stopPullDownRefresh()).catch(() => wx.stopPullDownRefresh())
|
||||
Promise.all([this.loadParts(), this.loadDailyChapters()])
|
||||
.then(() => wx.stopPullDownRefresh())
|
||||
.catch(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
// 每日新增:用 latest-chapters 接口,展示最近更新章节
|
||||
async loadDailyChapters() {
|
||||
try {
|
||||
const res = await app.request({ url: '/api/miniprogram/book/latest-chapters', silent: true })
|
||||
const list = (res && res.data) ? res.data : []
|
||||
const pt = (c) => (c.partTitle || c.part_title || '').toLowerCase()
|
||||
const exclude = c => !pt(c).includes('序言') && !pt(c).includes('尾声') && !pt(c).includes('附录')
|
||||
const daily = list
|
||||
.filter(exclude)
|
||||
.slice(0, 10)
|
||||
.map(c => {
|
||||
const d = new Date(c.updatedAt || c.updated_at || Date.now())
|
||||
const title = c.section_title || c.sectionTitle || c.title || c.chapterTitle || ''
|
||||
return {
|
||||
id: c.id,
|
||||
mid: c.mid ?? c.MID ?? 0,
|
||||
title,
|
||||
price: c.price ?? 1,
|
||||
dateStr: `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
})
|
||||
this.setData({ dailyChapters: daily })
|
||||
} catch (e) { console.log('[Chapters] 加载每日新增失败:', e) }
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -219,6 +248,7 @@ Page({
|
||||
|
||||
// 切换展开状态,展开时懒加载该篇章章节
|
||||
async togglePart(e) {
|
||||
trackClick('chapters', 'tab_click', e.currentTarget.dataset.id || '篇章')
|
||||
const partId = e.currentTarget.dataset.id
|
||||
const isExpanding = this.data.expandedPart !== partId
|
||||
this.setData({
|
||||
@@ -231,6 +261,7 @@ Page({
|
||||
goToRead(e) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
const mid = e.currentTarget.dataset.mid || app.getSectionMid(id)
|
||||
trackClick('chapters', 'card_click', id || '章节')
|
||||
const q = mid ? `mid=${mid}` : `id=${id}`
|
||||
wx.navigateTo({ url: `/pages/read/read?${q}` })
|
||||
},
|
||||
@@ -249,6 +280,7 @@ Page({
|
||||
|
||||
// 跳转到搜索页
|
||||
goToSearch() {
|
||||
trackClick('chapters', 'nav_click', '搜索')
|
||||
wx.navigateTo({ url: '/pages/search/search' })
|
||||
},
|
||||
|
||||
|
||||
@@ -40,6 +40,31 @@
|
||||
|
||||
<!-- 目录内容 -->
|
||||
<view class="chapters-content" wx:if="{{!partsLoading}}">
|
||||
<!-- 每日新增(最近更新章节快捷入口) -->
|
||||
<view class="daily-section" wx:if="{{dailyChapters.length > 0}}">
|
||||
<view class="daily-header">
|
||||
<text class="daily-title">每日新增</text>
|
||||
<text class="daily-badge">+{{dailyChapters.length}}</text>
|
||||
</view>
|
||||
<view class="daily-list">
|
||||
<view
|
||||
class="daily-item"
|
||||
wx:for="{{dailyChapters}}"
|
||||
wx:key="id"
|
||||
bindtap="goToRead"
|
||||
data-id="{{item.id}}"
|
||||
data-mid="{{item.mid}}"
|
||||
>
|
||||
<view class="daily-dot"></view>
|
||||
<view class="daily-content">
|
||||
<text class="daily-item-title">{{item.title}}</text>
|
||||
<text class="daily-item-meta">{{item.dateStr}} · ¥{{item.price}}</text>
|
||||
</view>
|
||||
<text class="daily-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 序言(优先传 mid,阅读页用 by-mid 请求) -->
|
||||
<view class="chapter-item" bindtap="goToRead" data-id="preface" data-mid="{{fixedSectionsMap.preface}}">
|
||||
<view class="item-left">
|
||||
@@ -83,6 +108,7 @@
|
||||
<text class="section-lock {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? 'lock-open' : 'lock-closed'}}">{{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '○' : '●'}}</text>
|
||||
<text class="section-title {{section.isFree || isVip || (!section.isPremium && hasFullBook) || purchasedSections.indexOf(section.id) > -1 ? '' : 'text-muted'}}">{{section.id}} {{section.title}}</text>
|
||||
<text wx:if="{{section.isNew}}" class="tag tag-new">NEW</text>
|
||||
<text wx:if="{{section.isPremium}}" class="tag tag-vip">增值</text>
|
||||
</view>
|
||||
<view class="section-right">
|
||||
<text wx:if="{{section.isFree}}" class="tag tag-free">免费</text>
|
||||
|
||||
@@ -174,6 +174,89 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ===== 每日新增 ===== */
|
||||
.daily-section {
|
||||
margin-bottom: 32rpx;
|
||||
padding: 24rpx;
|
||||
background: #1c1c1e;
|
||||
border-radius: 24rpx;
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.daily-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.daily-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.daily-badge {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #F6AD55;
|
||||
color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.daily-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.daily-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.daily-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.daily-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 206, 209, 0.6);
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.daily-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.daily-item-title {
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.daily-item-meta {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.daily-arrow {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
/* ===== 章节项 ===== */
|
||||
.chapter-item {
|
||||
display: flex;
|
||||
|
||||
@@ -10,7 +10,8 @@ Page({
|
||||
requestSn: '',
|
||||
detail: null,
|
||||
loading: true,
|
||||
paying: false
|
||||
paying: false,
|
||||
isInitiator: false // 是否发起人,发起人看到「分享给好友」UI,好友看到「帮他付款」
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -32,7 +33,9 @@ Page({
|
||||
try {
|
||||
const res = await app.request(`/api/miniprogram/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}`)
|
||||
if (res && res.success) {
|
||||
this.setData({ detail: res, loading: false })
|
||||
const myId = app.globalData.userInfo?.id || ''
|
||||
const isInitiator = !!myId && res.initiatorUserId === myId
|
||||
this.setData({ detail: res, loading: false, isInitiator })
|
||||
} else {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: res?.error || '加载失败', icon: 'none' })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Soul创业派对 - 代付详情页 -->
|
||||
<!-- Soul创业派对 - 代付详情页(美团式:发起人看到分享入口,好友看到帮他付款) -->
|
||||
<view class="page">
|
||||
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
|
||||
<view class="nav-content">
|
||||
@@ -6,7 +6,7 @@
|
||||
<text class="back-arrow">←</text>
|
||||
</view>
|
||||
<view class="nav-info">
|
||||
<text class="nav-title">帮他付款</text>
|
||||
<text class="nav-title">{{isInitiator ? '找朋友代付' : '帮他付款'}}</text>
|
||||
</view>
|
||||
<view class="nav-right-placeholder"></view>
|
||||
</view>
|
||||
@@ -20,15 +20,22 @@
|
||||
</view>
|
||||
</block>
|
||||
<block wx:elif="{{detail}}">
|
||||
<!-- 营销:章节标题+内容预览,吸引代付人 -->
|
||||
<view class="article-preview" wx:if="{{detail.sectionTitle || detail.contentPreview}}">
|
||||
<text class="article-title">{{detail.sectionTitle || detail.description || '代付商品'}}</text>
|
||||
<text class="article-content" wx:if="{{detail.contentPreview}}">{{detail.contentPreview}}</text>
|
||||
</view>
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">代付订单</text>
|
||||
<text class="initiator">{{detail.initiatorNickname || '好友'}} 请你帮忙付款</text>
|
||||
<view class="card-badge">代付订单</view>
|
||||
<text class="initiator" wx:if="{{!isInitiator}}">{{detail.initiatorNickname || '好友'}} 请你帮忙付款</text>
|
||||
<text class="initiator" wx:else>分享给好友,好友帮你付款</text>
|
||||
</view>
|
||||
<view class="card-divider"></view>
|
||||
<view class="card-body">
|
||||
<view class="row">
|
||||
<view class="row product-row" wx:if="{{!detail.contentPreview}}">
|
||||
<text class="label">商品</text>
|
||||
<text class="value">{{detail.description || '-'}}</text>
|
||||
<text class="value product-desc">{{detail.sectionTitle || detail.description || '-'}}</text>
|
||||
</view>
|
||||
<view class="row amount-row">
|
||||
<text class="label">金额</text>
|
||||
@@ -36,12 +43,27 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">
|
||||
<text>付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益</text>
|
||||
</view>
|
||||
<button class="pay-btn" bindtap="doPay" disabled="{{paying}}">
|
||||
{{paying ? '支付中...' : '帮他付款'}}
|
||||
</button>
|
||||
<!-- 发起人:分享给好友 -->
|
||||
<block wx:if="{{isInitiator}}">
|
||||
<view class="tips">
|
||||
<text class="tips-icon">💡</text>
|
||||
<text>分享给好友,好友打开后点击「帮他付款」即可为你代付</text>
|
||||
</view>
|
||||
<button class="pay-btn share-btn" open-type="share">
|
||||
<image class="btn-icon-img" src="/assets/icons/share.svg" mode="aspectFit"/>
|
||||
<text>分享给好友</text>
|
||||
</button>
|
||||
</block>
|
||||
<!-- 好友:帮他付款 -->
|
||||
<block wx:else>
|
||||
<view class="tips">
|
||||
<text class="tips-icon">✓</text>
|
||||
<text>付款后,{{detail.initiatorNickname || '好友'}}将获得对应权益</text>
|
||||
</view>
|
||||
<button class="pay-btn" bindtap="doPay" disabled="{{paying}}">
|
||||
{{paying ? '支付中...' : '帮他付款'}}
|
||||
</button>
|
||||
</block>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="empty">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Soul创业派对 - 代付详情页 */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #000;
|
||||
background: linear-gradient(180deg, #0a0a0a 0%, #000 40%, #000 100%);
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
@@ -10,8 +10,10 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(0, 0, 0, 0.92);
|
||||
backdrop-filter: blur(20rpx);
|
||||
-webkit-backdrop-filter: blur(20rpx);
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
@@ -26,25 +28,31 @@
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
background: #1c1c1e;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.nav-back:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
font-size: 36rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.loading-box {
|
||||
@@ -58,7 +66,7 @@
|
||||
.loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 4rpx solid rgba(0, 206, 209, 0.3);
|
||||
border: 4rpx solid rgba(0, 206, 209, 0.2);
|
||||
border-top-color: #00CED1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
@@ -71,43 +79,86 @@
|
||||
.loading-text {
|
||||
margin-top: 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
/* 营销:章节标题+内容预览,与订单卡片统一风格 */
|
||||
.article-preview {
|
||||
background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border: 1rpx solid rgba(0, 206, 209, 0.1);
|
||||
}
|
||||
|
||||
.article-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.article-content {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
line-height: 1.65;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 4;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* 订单卡片:与文章预览统一圆角、边距 */
|
||||
.card {
|
||||
background: #1c1c1e;
|
||||
background: linear-gradient(145deg, #1a1a1c 0%, #141416 100%);
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid rgba(0, 206, 209, 0.1);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.06);
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 8rpx;
|
||||
.card-badge {
|
||||
display: inline-block;
|
||||
font-size: 22rpx;
|
||||
color: rgba(0, 206, 209, 0.9);
|
||||
background: rgba(0, 206, 209, 0.08);
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 12rpx;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
.initiator {
|
||||
font-size: 34rpx;
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.3rpx;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
height: 1rpx;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
|
||||
margin: 0 24rpx;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 32rpx;
|
||||
padding: 20rpx 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
@@ -115,46 +166,99 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
flex-shrink: 0;
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
.product-row .value {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.amount-row {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.amount-row .amount {
|
||||
font-size: 40rpx;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #00CED1;
|
||||
letter-spacing: 1rpx;
|
||||
text-shadow: 0 0 24rpx rgba(0, 206, 209, 0.3);
|
||||
}
|
||||
|
||||
/* 提示文案 */
|
||||
.tips {
|
||||
padding: 0 8rpx 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10rpx;
|
||||
padding: 0 4rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tips-icon {
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 主按钮 */
|
||||
.pay-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
background: linear-gradient(90deg, #00CED1 0%, #20B2AA 100%);
|
||||
background: linear-gradient(135deg, #00CED1 0%, #18a8a8 50%, #20B2AA 100%);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 48rpx;
|
||||
border-radius: 50rpx;
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 206, 209, 0.35);
|
||||
transition: opacity 0.2s, transform 0.1s;
|
||||
}
|
||||
|
||||
.pay-btn:active {
|
||||
opacity: 0.92;
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.pay-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.share-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.btn-icon-img {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 120rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,20 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
goToDetail(e) {
|
||||
const requestSn = e.currentTarget.dataset.sn
|
||||
if (requestSn) {
|
||||
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(requestSn)}` })
|
||||
}
|
||||
},
|
||||
|
||||
shareRequest(e) {
|
||||
e.stopPropagation()
|
||||
wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 })
|
||||
},
|
||||
|
||||
async cancelRequest(e) {
|
||||
e.stopPropagation()
|
||||
const requestSn = e.currentTarget.dataset.sn
|
||||
if (!requestSn) return
|
||||
const ok = await new Promise(r => {
|
||||
@@ -74,11 +87,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
shareRequest(e) {
|
||||
const requestSn = e.currentTarget.dataset.sn
|
||||
wx.showToast({ title: '请点击右上角「...」分享给好友', icon: 'none', duration: 2500 })
|
||||
},
|
||||
|
||||
goBack() {
|
||||
app.goBackOrToHome()
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<view class="empty">暂无发起的代付</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="card" wx:for="{{requests}}" wx:key="requestSn">
|
||||
<view class="card" wx:for="{{requests}}" wx:key="requestSn" bindtap="goToDetail" data-sn="{{item.requestSn}}">
|
||||
<view class="card-row">
|
||||
<text class="desc">{{item.description}}</text>
|
||||
<text class="amount">¥{{item.amount}}</text>
|
||||
@@ -49,7 +49,7 @@
|
||||
<view class="empty">暂无帮付记录</view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<view class="card" wx:for="{{payments}}" wx:key="requestSn">
|
||||
<view class="card" wx:for="{{payments}}" wx:key="requestSn" bindtap="goToDetail" data-sn="{{item.requestSn}}">
|
||||
<view class="card-row">
|
||||
<text class="desc">{{item.description}}</text>
|
||||
<text class="amount">¥{{item.amount}}</text>
|
||||
|
||||
@@ -17,6 +17,7 @@ import accessManager from '../../utils/chapterAccessManager'
|
||||
import readingTracker from '../../utils/readingTracker'
|
||||
const { parseScene } = require('../../utils/scene.js')
|
||||
const contentParser = require('../../utils/contentParser.js')
|
||||
const { trackClick } = require('../../utils/trackClick')
|
||||
|
||||
const app = getApp()
|
||||
|
||||
@@ -65,9 +66,6 @@ Page({
|
||||
|
||||
// 弹窗
|
||||
showShareModal: false,
|
||||
showGiftShareModal: false,
|
||||
shareMode: '', // 'gift' = 代付分享,onShareAppMessage 返回 gift-pay/detail
|
||||
giftRequestSn: '', // 代付请求号,分享时用
|
||||
showLoginModal: false,
|
||||
agreeProtocol: false,
|
||||
showPosterModal: false,
|
||||
@@ -97,13 +95,15 @@ Page({
|
||||
// 支持 scene(扫码)、mid、id、ref、gift(代付)
|
||||
const sceneStr = (options && options.scene) || ''
|
||||
const parsed = parseScene(sceneStr)
|
||||
const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0)
|
||||
let id = options.id || parsed.id || app.globalData.initialSectionId
|
||||
const ref = options.ref || parsed.ref
|
||||
const isGift = options.gift === '1' || options.gift === 'true'
|
||||
// 代付统一到代付页:gift=1&ref=requestSn 时直接跳转,禁止在阅读页代付
|
||||
if (isGift && ref) {
|
||||
wx.setStorageSync('gift_for_ref', ref) // 代付模式:好友打开后,购买时传 giftFor(后端待支持)
|
||||
wx.redirectTo({ url: `/pages/gift-pay/detail?requestSn=${encodeURIComponent(ref)}` })
|
||||
return
|
||||
}
|
||||
const mid = options.mid ? parseInt(options.mid, 10) : (parsed.mid || app.globalData.initialSectionMid || 0)
|
||||
let id = options.id || parsed.id || app.globalData.initialSectionId
|
||||
if (app.globalData.initialSectionMid) delete app.globalData.initialSectionMid
|
||||
if (app.globalData.initialSectionId) delete app.globalData.initialSectionId
|
||||
|
||||
@@ -657,7 +657,7 @@ Page({
|
||||
this.setData({ showShareModal: false })
|
||||
},
|
||||
|
||||
// 代付分享弹窗:创建代付请求后分享到代付页面
|
||||
// 找好友代付:创建代付请求后跳转代付页(美团式,在代付页分享)
|
||||
async showGiftShareModal() {
|
||||
if (!app.globalData.userInfo?.id) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
@@ -682,7 +682,7 @@ Page({
|
||||
})
|
||||
wx.hideLoading()
|
||||
if (res && res.success && res.requestSn) {
|
||||
this.setData({ showGiftShareModal: true, giftRequestSn: res.requestSn })
|
||||
wx.navigateTo({ url: `/pages/gift-pay/detail?requestSn=${res.requestSn}` })
|
||||
} else {
|
||||
wx.showToast({ title: res?.error || '创建失败', icon: 'none' })
|
||||
}
|
||||
@@ -692,20 +692,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
closeGiftShareModal() {
|
||||
this.setData({ showGiftShareModal: false })
|
||||
},
|
||||
|
||||
// 分享给好友(代付):引导用户点右上角,分享到代付详情页
|
||||
shareGiftToFriend() {
|
||||
this.setData({ shareMode: 'gift', showGiftShareModal: false })
|
||||
wx.showToast({
|
||||
title: '请点击右上角「...」→ 发送给好友',
|
||||
icon: 'none',
|
||||
duration: 2500
|
||||
})
|
||||
},
|
||||
|
||||
// 复制链接
|
||||
copyLink() {
|
||||
const userInfo = app.globalData.userInfo
|
||||
@@ -741,29 +727,17 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 分享到微信 - 自动带分享人ID;shareMode=gift 时分享到代付详情页
|
||||
// 分享到微信 - 自动带分享人ID
|
||||
onShareAppMessage() {
|
||||
const { section, sectionId, sectionMid, shareMode, giftRequestSn } = this.data
|
||||
trackClick('read', 'btn_click', '分享_' + this.data.sectionId)
|
||||
const { section, sectionId, sectionMid } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
let path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
let title = section?.title
|
||||
const path = ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||
const title = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
if (shareMode === 'gift' && giftRequestSn) {
|
||||
path = `/pages/gift-pay/detail?requestSn=${giftRequestSn}`
|
||||
title = '好友请你帮忙代付 - Soul创业派对'
|
||||
this.setData({ shareMode: '', giftRequestSn: '' })
|
||||
} else {
|
||||
title = section?.title
|
||||
? `📚 ${section.title.length > 20 ? section.title.slice(0, 20) + '...' : section.title}`
|
||||
: '📚 Soul创业派对 - 真实商业故事'
|
||||
}
|
||||
return {
|
||||
title,
|
||||
path
|
||||
// 不设置 imageUrl,使用当前阅读页截图作为分享卡片中间图片
|
||||
}
|
||||
return { title, path }
|
||||
},
|
||||
|
||||
// 底部「分享到朋友圈」按钮点击:微信不支持 button open-type=shareTimeline,只能通过右上角菜单分享,点击时引导用户
|
||||
@@ -775,16 +749,12 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 分享到朋友圈:带文章标题,过长时截断;shareMode=gift 时 query 带 gift=1
|
||||
// 分享到朋友圈:带文章标题,过长时截断
|
||||
onShareTimeline() {
|
||||
const { section, sectionId, sectionMid, chapterTitle, shareMode } = this.data
|
||||
const { section, sectionId, sectionMid, chapterTitle } = this.data
|
||||
const ref = app.getMyReferralCode()
|
||||
const q = sectionMid ? `mid=${sectionMid}` : `id=${sectionId}`
|
||||
let query = ref ? `${q}&ref=${ref}` : q
|
||||
if (shareMode === 'gift' && ref) {
|
||||
query = `${q}&ref=${ref}&gift=1`
|
||||
this.setData({ shareMode: '' })
|
||||
}
|
||||
const query = ref ? `${q}&ref=${ref}` : q
|
||||
const articleTitle = (section?.title || chapterTitle || '').trim()
|
||||
const title = articleTitle
|
||||
? (articleTitle.length > 28 ? articleTitle.slice(0, 28) + '...' : articleTitle)
|
||||
@@ -918,6 +888,7 @@ Page({
|
||||
|
||||
// 购买章节 - 直接调起支付
|
||||
async handlePurchaseSection() {
|
||||
trackClick('read', 'btn_click', '购买章节_' + this.data.sectionId)
|
||||
console.log('[Pay] 点击购买章节按钮')
|
||||
wx.showLoading({ title: '处理中...', mask: true })
|
||||
|
||||
|
||||
@@ -298,23 +298,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 代付分享弹窗:分享到代付页面,好友打开后帮他付款 -->
|
||||
<view class="modal-overlay" wx:if="{{showGiftShareModal}}" bindtap="closeGiftShareModal">
|
||||
<view class="modal-content share-modal" catchtap="stopPropagation">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">找好友代付</text>
|
||||
<view class="modal-close" bindtap="closeGiftShareModal">✕</view>
|
||||
</view>
|
||||
<text class="share-modal-desc">分享给好友,好友打开后点击「帮他付款」即可为你代付本章</text>
|
||||
<view class="share-modal-actions">
|
||||
<view class="share-modal-btn" bindtap="shareGiftToFriend">
|
||||
<text class="btn-icon">👤</text>
|
||||
<text>分享给好友</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付中提示 -->
|
||||
<view class="modal-overlay" wx:if="{{isPaying}}" catchtap="">
|
||||
<view class="loading-box">
|
||||
|
||||
@@ -23,12 +23,19 @@
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "pages/gift-pay/detail",
|
||||
"pathName": "pages/gift-pay/detail",
|
||||
"query": "requestSn=GPRMP20260317114238341300",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "pages/read/read",
|
||||
"pathName": "pages/read/read",
|
||||
"query": "mid=219",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "唤醒",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"soul-api/internal/database"
|
||||
"soul-api/internal/model"
|
||||
@@ -16,6 +17,26 @@ import (
|
||||
|
||||
const giftPayExpireHours = 24
|
||||
|
||||
// giftPayPreviewContent 取内容前 20%,用于代付页营销展示
|
||||
func giftPayPreviewContent(content string) string {
|
||||
n := utf8.RuneCountInString(content)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
limit := n * 20 / 100
|
||||
if limit < 50 {
|
||||
limit = 50
|
||||
}
|
||||
if limit > n {
|
||||
limit = n
|
||||
}
|
||||
runes := []rune(content)
|
||||
if limit >= n {
|
||||
return string(runes)
|
||||
}
|
||||
return string(runes[:limit]) + "……"
|
||||
}
|
||||
|
||||
// GiftPayCreate POST /api/miniprogram/gift-pay/create 创建代付请求
|
||||
func GiftPayCreate(c *gin.Context) {
|
||||
var req struct {
|
||||
@@ -188,15 +209,31 @@ func GiftPayDetail(c *gin.Context) {
|
||||
nickname = n
|
||||
}
|
||||
|
||||
// 营销:章节类型时返回标题和内容预览,吸引代付人
|
||||
sectionTitle := gpr.Description
|
||||
contentPreview := ""
|
||||
if gpr.ProductType == "section" && gpr.ProductID != "" {
|
||||
var ch model.Chapter
|
||||
if err := db.Select("section_title", "content").Where("id = ?", gpr.ProductID).First(&ch).Error; err == nil {
|
||||
if ch.SectionTitle != "" {
|
||||
sectionTitle = ch.SectionTitle
|
||||
}
|
||||
contentPreview = giftPayPreviewContent(ch.Content)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"requestSn": gpr.RequestSN,
|
||||
"productType": gpr.ProductType,
|
||||
"productId": gpr.ProductID,
|
||||
"amount": gpr.Amount,
|
||||
"description": gpr.Description,
|
||||
"success": true,
|
||||
"requestSn": gpr.RequestSN,
|
||||
"productType": gpr.ProductType,
|
||||
"productId": gpr.ProductID,
|
||||
"amount": gpr.Amount,
|
||||
"description": gpr.Description,
|
||||
"sectionTitle": sectionTitle,
|
||||
"contentPreview": contentPreview,
|
||||
"initiatorNickname": nickname,
|
||||
"expireAt": gpr.ExpireAt.Format(time.RFC3339),
|
||||
"initiatorUserId": gpr.InitiatorUserID,
|
||||
"expireAt": gpr.ExpireAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -597,6 +597,12 @@ func MiniprogramPayNotify(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 代付订单:更新 gift_pay_request、订单 payer_user_id
|
||||
// 权益归属与分佣:代付时归发起人(order.UserID),普通订单归 buyerUserID
|
||||
beneficiaryUserID := buyerUserID
|
||||
if attach.GiftPayRequestSn != "" && order.UserID != "" {
|
||||
beneficiaryUserID = order.UserID
|
||||
fmt.Printf("[PayNotify] 代付订单,权益归属发起人: %s\n", beneficiaryUserID)
|
||||
}
|
||||
if attach.GiftPayRequestSn != "" {
|
||||
var payerUserID string
|
||||
if openID != "" {
|
||||
@@ -615,36 +621,35 @@ func MiniprogramPayNotify(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
if buyerUserID != "" && attach.ProductType != "" {
|
||||
if beneficiaryUserID != "" && attach.ProductType != "" {
|
||||
if attach.ProductType == "fullbook" {
|
||||
db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
|
||||
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
|
||||
db.Model(&model.User{}).Where("id = ?", beneficiaryUserID).Update("has_full_book", true)
|
||||
fmt.Printf("[PayNotify] 用户已购全书: %s\n", beneficiaryUserID)
|
||||
} else if attach.ProductType == "vip" {
|
||||
// V4.2 修复:续费时累加剩余天数(从 max(now, vip_expire_date) 加 365 天)
|
||||
vipActivatedAt := time.Now()
|
||||
if order.PayTime != nil {
|
||||
vipActivatedAt = *order.PayTime
|
||||
}
|
||||
expireDate := activateVIP(db, buyerUserID, 365, vipActivatedAt)
|
||||
fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", buyerUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05"))
|
||||
expireDate := activateVIP(db, beneficiaryUserID, 365, vipActivatedAt)
|
||||
fmt.Printf("[VIP] 设置方式=支付设置, userId=%s, orderSn=%s, 过期日=%s, activatedAt=%s\n", beneficiaryUserID, orderSn, expireDate.Format("2006-01-02"), vipActivatedAt.Format("2006-01-02 15:04:05"))
|
||||
} else if attach.ProductType == "match" {
|
||||
fmt.Printf("[PayNotify] 用户购买匹配次数: %s,订单 %s\n", buyerUserID, orderSn)
|
||||
fmt.Printf("[PayNotify] 用户购买匹配次数: %s,订单 %s\n", beneficiaryUserID, orderSn)
|
||||
} else if attach.ProductType == "balance_recharge" {
|
||||
if err := ConfirmBalanceRechargeByOrder(db, &order); err != nil {
|
||||
fmt.Printf("[PayNotify] 余额充值确认失败: %s, err=%v\n", orderSn, err)
|
||||
} else {
|
||||
fmt.Printf("[PayNotify] 余额充值成功: %s, 金额 %.2f\n", buyerUserID, totalAmount)
|
||||
fmt.Printf("[PayNotify] 余额充值成功: %s, 金额 %.2f\n", beneficiaryUserID, totalAmount)
|
||||
}
|
||||
} else if attach.ProductType == "section" && attach.ProductID != "" {
|
||||
var count int64
|
||||
db.Model(&model.Order{}).Where(
|
||||
"user_id = ? AND product_type = 'section' AND product_id = ? AND status = 'paid' AND order_sn != ?",
|
||||
buyerUserID, attach.ProductID, orderSn,
|
||||
beneficiaryUserID, attach.ProductID, orderSn,
|
||||
).Count(&count)
|
||||
if count == 0 {
|
||||
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", buyerUserID, attach.ProductID)
|
||||
fmt.Printf("[PayNotify] 用户首次购买章节: %s - %s\n", beneficiaryUserID, attach.ProductID)
|
||||
} else {
|
||||
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", buyerUserID, attach.ProductID)
|
||||
fmt.Printf("[PayNotify] 用户已有该章节的其他已支付订单: %s - %s\n", beneficiaryUserID, attach.ProductID)
|
||||
}
|
||||
}
|
||||
productID := attach.ProductID
|
||||
@@ -653,9 +658,9 @@ func MiniprogramPayNotify(c *gin.Context) {
|
||||
}
|
||||
db.Where(
|
||||
"user_id = ? AND product_type = ? AND product_id = ? AND status = 'created' AND order_sn != ?",
|
||||
buyerUserID, attach.ProductType, productID, orderSn,
|
||||
beneficiaryUserID, attach.ProductType, productID, orderSn,
|
||||
).Delete(&model.Order{})
|
||||
processReferralCommission(db, buyerUserID, totalAmount, orderSn, &order)
|
||||
processReferralCommission(db, beneficiaryUserID, totalAmount, orderSn, &order)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -295,3 +295,95 @@ Mycontent-temp/miniprogram 为样式预览分支,miniprogram 为线上主线
|
||||
|
||||
- `开发文档/1、需求/链接人与事-所有同步需求.md`
|
||||
- `开发文档/存客宝对接逻辑图.md`
|
||||
|
||||
---
|
||||
|
||||
# 第十一部分:代付美团式流程与 PayNotify 完善(2026-03-17 橙子同步)
|
||||
|
||||
## 代付流程改造
|
||||
|
||||
| 环节 | 变更 |
|
||||
|------|------|
|
||||
| 读页「找好友代付」 | 创建请求后**跳转代付详情页**,不再弹窗 |
|
||||
| 代付详情页 | 发起人看到「分享给好友」UI,好友看到「帮他付款」;后端 detail 返回 `initiatorUserId` 供前端区分 |
|
||||
| 订单与分佣 | 代付支付后并入 orders、分销;权益归属发起人 |
|
||||
|
||||
## PayNotify 权益归属修复
|
||||
|
||||
- **问题**:代付时 `buyerUserID` 来自 openID(代付人),权益错误给到代付人
|
||||
- **修复**:引入 `beneficiaryUserID`,代付时 = `order.UserID`(发起人),普通订单 = `buyerUserID`
|
||||
- **影响范围**:全书、VIP、章节、余额充值、分佣、取消未支付订单
|
||||
|
||||
## 其他同步
|
||||
|
||||
- 目录页:`book/parts` 加载增加 loading
|
||||
- 首页最新新增:默认 5 条、折叠状态,点击展开
|
||||
- 导师预约支付:暂不处理,保持与 new-soul 一致
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `开发文档/代付功能-美团式方案与场景清单.md` 第九章实现状态
|
||||
- `开发文档/迁移完成度与待办清单.md` 迁移完成度与剩余待办
|
||||
|
||||
---
|
||||
|
||||
# 第十二部分:乘风吸收经验与迁移完成度(2026-03-17)
|
||||
|
||||
## 吸收范围
|
||||
|
||||
- agent 经验清单、各角色 evolution、项目索引
|
||||
- 新版迁移方案、稳定版适配清单、代付美团式方案
|
||||
- 开发文档、需求汇总、运营与变更
|
||||
|
||||
## 迁移完成度结论
|
||||
|
||||
**核心迁移已全部完成**。详见 `开发文档/迁移完成度与待办清单.md`。
|
||||
|
||||
**剩余为可选/搁置**:导师预约支付(暂不处理)、阅读统计埋点、目录每日新增、P4 设计稿对齐、富文本/打包引导/存客宝(搁置)。
|
||||
|
||||
---
|
||||
|
||||
# 第十三部分:代付统一到代付页(2026-03-17 橙子同步)
|
||||
|
||||
## 新需求
|
||||
|
||||
**代付不允许在阅读页完成,统一到代付页。** 代付页可显示部分文章信息。
|
||||
|
||||
## 实现
|
||||
|
||||
- 读页:`gift=1&ref=requestSn` 打开时,`redirectTo` 到 `/pages/gift-pay/detail?requestSn=xxx`,不展示阅读页
|
||||
- 代付页:已展示商品描述(章节标题等)、金额、发起人昵称
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `开发文档/找朋友代付-流程与配置.md` 已更新为美团式流程
|
||||
|
||||
---
|
||||
|
||||
# 第十四部分:乘风同步进度(2026-03-17)
|
||||
|
||||
## 代付页营销与体验
|
||||
|
||||
- **章节内容预览**:代付页展示 20% 章节内容 + 标题,吸引代付人
|
||||
- **我的代付列表**:点击卡片进入代付详情页
|
||||
- **页面协调**:统一圆角、间距、视觉风格
|
||||
|
||||
## 迁移完成度
|
||||
|
||||
详见 `开发文档/迁移完成度与待办清单.md`。**核心迁移已全部完成**,剩余为可选/搁置项。
|
||||
|
||||
---
|
||||
|
||||
# 第十五部分:富文本/打包/存客宝 分析结论(2026-03-17)
|
||||
|
||||
## 分析结论
|
||||
|
||||
| 项 | 结论 |
|
||||
|----|------|
|
||||
| **富文本渲染** | 按稳定版处理,用户已落地 |
|
||||
| **打包购买引导** | 稳定版与 new-soul 一致:purchasedCount≥3 时显示「解锁全书」按钮 |
|
||||
| **存客宝对接** | 稳定版已有且更完善:ckb/lead、index-lead、match、join;稳定版多限频、linkTag ckb |
|
||||
|
||||
## 文档同步
|
||||
|
||||
- `开发文档/迁移完成度与待办清单.md`:搁置项更新为「稳定版已有」,无新增搁置
|
||||
|
||||
@@ -54,4 +54,5 @@ IP 设定、风格、输出规范(见原卡若角色设定)。
|
||||
| 2026-03-16 | 链接人与事列表:table 布局、planId/apiKey 列、apiKey 复制图标、删除 Dialog 二次确认 | 已完成 | ContentPage.tsx |
|
||||
| 2026-03-16 | 存客宝创建计划参数:planType=1、sceneId=9、status=1 | 已完成 | db_person.go |
|
||||
| 2026-03-16 | @mention 存储格式:span 必须含 data-label,否则 TipTap 显示 token | 已完成 | ParseAutoLinkContent、autolink.go |
|
||||
| 2026-03-17 | 代付统一到代付页:gift=1&ref 打开 read 页时 redirectTo 代付页,禁止在阅读页代付;代付页显示部分文章信息 | 已完成 | read.js onLoad;代付页已有 description |
|
||||
| - | 链接人与事所有同步需求汇总 | - | 链接人与事-所有同步需求.md |
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
| [1、需求/需求汇总](1、需求/需求汇总.md) | 需求清单、业务需求 |
|
||||
| [10、项目管理/项目落地推进表](10、项目管理/项目落地推进表.md) | 里程碑、永平落地 |
|
||||
| [10、项目管理/运营与变更](10、项目管理/运营与变更.md) | 近期讨论、变更记录 |
|
||||
| [迁移完成度与待办清单](迁移完成度与待办清单.md) | 迁移完成度、剩余待办、搁置项 |
|
||||
|
||||
### 架构与规范
|
||||
|
||||
|
||||
@@ -155,10 +155,10 @@
|
||||
- **Tab**:我发起的 / 我帮付的
|
||||
- **列表**:请求状态、商品、金额、时间、操作(取消/查看)
|
||||
|
||||
### 3. 读页入口改造
|
||||
### 3. 读页入口改造(已实现 2026-03-17)
|
||||
|
||||
- 当前「找好友代付」→ 弹窗分享
|
||||
- 改造:点击后先调 `gift-pay/create` 创建请求,再弹窗分享带 `requestSn` 的链接
|
||||
- ~~当前「找好友代付」→ 弹窗分享~~
|
||||
- **已落地**:点击后调 `gift-pay/create` 创建请求 → **跳转代付详情页** → 发起人在代付页点击「分享给好友」分享
|
||||
|
||||
---
|
||||
|
||||
@@ -190,3 +190,28 @@
|
||||
- **商品类型**:section(章节)、fullbook(全书)、vip(年度会员)、match(匹配次数)、balance_recharge(余额充值)
|
||||
- **价格**:从 `chapters`、`system_config` 等取,创建请求时锁定
|
||||
- **权益激活**:与现有 `PayNotify` 逻辑一致,仅 `user_id` 改为发起人
|
||||
|
||||
---
|
||||
|
||||
## 九、实现状态(2026-03-17 橙子同步)
|
||||
|
||||
### 1. 美团式流程已落地
|
||||
|
||||
| 环节 | 实现 |
|
||||
|------|------|
|
||||
| 读页入口 | 点击「找好友代付」→ 创建请求 → **跳转代付详情页**(不再弹窗) |
|
||||
| 代付详情页 | 发起人:标题「找朋友代付」+「分享给好友」按钮(open-type=share);好友:标题「帮他付款」+「帮他付款」按钮 |
|
||||
| 后端 detail | 返回 `initiatorUserId`,前端据此区分发起人/好友 UI |
|
||||
| 订单与分佣 | 代付支付后并入 orders、分销;权益归属发起人 |
|
||||
|
||||
### 2. PayNotify 权益归属修复
|
||||
|
||||
- **问题**:代付时 `buyerUserID` 来自 openID(代付人),权益错误给到代付人
|
||||
- **修复**:引入 `beneficiaryUserID`,代付时 = `order.UserID`(发起人),普通订单 = `buyerUserID`
|
||||
- **影响**:全书/VIP/章节/余额充值/分佣/取消未支付订单,均按 `beneficiaryUserID` 处理
|
||||
|
||||
### 3. 其他本次同步
|
||||
|
||||
- 目录页:`book/parts` 加载增加 loading
|
||||
- 首页最新新增:默认 5 条、折叠状态,点击展开
|
||||
- 导师预约支付:暂不处理,保持与 new-soul 一致
|
||||
|
||||
@@ -1,60 +1,70 @@
|
||||
# 找朋友代付 - 流程与配置说明
|
||||
|
||||
## 一、当前实现概览
|
||||
|
||||
### 1. 入口与弹窗
|
||||
|
||||
| 位置 | 入口 | 弹窗 |
|
||||
|------|------|------|
|
||||
| 读页(read) | 付费墙下方「找好友代付」 | 代付分享弹窗 |
|
||||
| 个人中心(my) | 菜单「代付链接」 | 跳转 gift-link 页 |
|
||||
|
||||
### 2. 代付分享弹窗(读页内)
|
||||
|
||||
用户点击「找好友代付」后弹出,包含两个操作:
|
||||
|
||||
| 按钮 | 功能 | 实现 |
|
||||
|------|------|------|
|
||||
| **复制代付链接** | 复制到剪贴板 | `copyGiftLink()` → `wx.setClipboardData` |
|
||||
| **分享给好友** | 引导用户点右上角分享 | `shareGiftToFriend()` → 设置 `shareMode=gift` |
|
||||
> 美团式流程:代付统一在代付页完成,禁止在阅读页代付。
|
||||
> 更新日期:2026-03-17
|
||||
|
||||
---
|
||||
|
||||
## 二、复制链接的本质与限制
|
||||
## 一、当前实现概览
|
||||
|
||||
### 当前复制的内容
|
||||
### 1. 主流程(美团式)
|
||||
|
||||
- **读页弹窗**:`/pages/read/read?mid=xxx&ref=xxx&gift=1`(带本章节信息)
|
||||
- **gift-link 页**:`pages/gift-link/gift-link?ref=xxx&gift=1`(通用入口)
|
||||
| 步骤 | 说明 |
|
||||
|------|------|
|
||||
| 1 | 发起人在 read 页点击「找好友代付」→ 创建代付请求 → **跳转 gift-pay/detail** |
|
||||
| 2 | 发起人在代付页看到订单信息,点击「分享给好友」→ 唤起微信分享 |
|
||||
| 3 | 好友收到小程序卡片,打开 **gift-pay/detail** → 看到「帮他付款」→ 完成支付 |
|
||||
| 4 | 权益归属发起人,订单并入 orders、分佣 |
|
||||
|
||||
这是**小程序内部 path**,不是可点击的 H5 链接。
|
||||
### 2. 入口与页面
|
||||
|
||||
### 微信小程序的限制
|
||||
| 位置 | 入口 | 跳转 |
|
||||
|------|------|------|
|
||||
| 读页(read) | 付费墙下方「找好友代付」 | 创建请求 → gift-pay/detail |
|
||||
| 个人中心(my) | 菜单「我的代付」 | gift-pay/list |
|
||||
|
||||
### 3. 代付页双态
|
||||
|
||||
| 身份 | 标题 | 主按钮 |
|
||||
|------|------|--------|
|
||||
| 发起人 | 找朋友代付 | 分享给好友(open-type=share) |
|
||||
| 好友 | 帮他付款 | 帮他付款(支付) |
|
||||
|
||||
---
|
||||
|
||||
## 二、统一到代付页(禁止阅读页代付)
|
||||
|
||||
### 规则
|
||||
|
||||
**代付必须在代付页完成,禁止在阅读页代付。**
|
||||
|
||||
### 实现
|
||||
|
||||
当用户通过 `gift=1&ref=requestSn` 打开阅读页时:
|
||||
|
||||
- **之前**:留在阅读页,可在阅读页购买(逻辑不完整)
|
||||
- **现**:`redirectTo` 到 `/pages/gift-pay/detail?requestSn=xxx`,统一在代付页代付
|
||||
|
||||
### 示例
|
||||
|
||||
- 好友收到链接:`/pages/read/read?mid=123&ref=GPR_xxx&gift=1`
|
||||
- 打开后立即跳转:`/pages/gift-pay/detail?requestSn=GPR_xxx`
|
||||
- 在代付页完成支付
|
||||
|
||||
### 代付页文章信息
|
||||
|
||||
代付页已展示:商品描述(章节标题/全书/VIP)、金额、发起人昵称(脱敏)。
|
||||
|
||||
---
|
||||
|
||||
## 三、微信分享限制
|
||||
|
||||
| 方式 | 好友能否直接打开 |
|
||||
|------|------------------|
|
||||
| 复制 path 发到聊天 | ❌ 不能,收到的是纯文本 |
|
||||
| 分享小程序卡片 | ✅ 能,点击卡片即可打开 |
|
||||
|
||||
**结论**:复制 path 后,好友**无法**像 H5 链接那样点击跳转,只能通过**分享小程序卡片**直接打开。
|
||||
|
||||
---
|
||||
|
||||
## 三、推荐流程设置
|
||||
|
||||
### 主流程:分享给好友(优先)
|
||||
|
||||
1. 用户点击「找好友代付」→ 弹出代付分享弹窗
|
||||
2. 点击「分享给好友」→ 提示「请点击右上角「...」→ 发送给好友」
|
||||
3. 用户点右上角「...」→ 选择「发送给好友」
|
||||
4. 好友收到**小程序卡片**,点击即可打开,自动带 `ref` 和 `gift=1`
|
||||
|
||||
### 辅助流程:复制链接(备用)
|
||||
|
||||
1. 用户点击「复制代付链接」→ path 已复制到剪贴板
|
||||
2. 用户粘贴到聊天发给好友
|
||||
3. 好友收到文本,需**手动**打开「Soul创业派对」小程序
|
||||
4. 建议在弹窗或复制成功提示中说明:「好友需打开 Soul 创业派对小程序,或请优先使用分享给好友」
|
||||
**结论**:复制 path 后好友无法直接打开,主流程为「分享给好友」发小程序卡片。
|
||||
|
||||
---
|
||||
|
||||
@@ -62,53 +72,15 @@
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `miniprogram/pages/read/read.wxml` | 代付分享弹窗 UI、入口「找好友代付」 |
|
||||
| `miniprogram/pages/read/read.js` | `showGiftShareModal`、`copyGiftLink`、`shareGiftToFriend`、`onShareAppMessage` |
|
||||
| `miniprogram/pages/gift-link/gift-link.js` | 通用代付链接页,调 `/api/miniprogram/gift/link` |
|
||||
| `soul-api/internal/handler/miniprogram.go` | `GiftLinkGet` 返回 path、ref、scene |
|
||||
| `miniprogram/pages/read/read.js` | gift=1&ref 时 redirectTo gift-pay/detail |
|
||||
| `miniprogram/pages/read/read.js` | showGiftShareModal:创建请求 → navigateTo gift-pay/detail |
|
||||
| `miniprogram/pages/gift-pay/detail.js` | 代付详情页,发起人/好友双态 |
|
||||
| `miniprogram/pages/gift-pay/list.js` | 我的代付列表 |
|
||||
| `soul-api/internal/handler/gift_pay.go` | create/detail/pay/cancel/my-requests/my-payments |
|
||||
|
||||
---
|
||||
|
||||
## 五、可选优化方向
|
||||
## 五、相关文档
|
||||
|
||||
### 1. 优化复制成功提示
|
||||
|
||||
当前:`wx.showToast({ title: '代付链接已复制', icon: 'success' })`
|
||||
|
||||
可改为更明确的引导,例如:
|
||||
|
||||
```js
|
||||
wx.showToast({
|
||||
title: '已复制,发给好友后请让对方打开 Soul 创业派对小程序',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 弹窗内增加说明
|
||||
|
||||
在「将链接分享给好友,好友可为你代付购买本章」下方补充:
|
||||
|
||||
> 建议使用「分享给好友」,好友点击卡片即可打开;复制链接需好友手动打开小程序。
|
||||
|
||||
### 3. 若需「可点击链接」(进阶)
|
||||
|
||||
要让好友**点击链接直接打开小程序**,需要:
|
||||
|
||||
- **URL Link**:后端调微信接口生成 `https://wxaurl.cn/xxx` 形式链接
|
||||
- **URL Scheme**:生成 `weixin://dl/business/?t=xxx`,有效期有限
|
||||
- **H5 中转页**:部署 H5 页,用 `wx-open-launch-weapp` 打开小程序
|
||||
|
||||
以上均需在微信公众平台配置域名、并在后端实现对应接口。
|
||||
|
||||
---
|
||||
|
||||
## 六、代付模式参数说明
|
||||
|
||||
| 参数 | 含义 | 来源 |
|
||||
|------|------|------|
|
||||
| `ref` | 推荐码,标识「谁在找代付」 | `app.getMyReferralCode()` |
|
||||
| `gift=1` | 代付模式标记 | path 或 query 中携带 |
|
||||
| `gift_for_ref` | 存到 storage,购买时传 `giftFor` | `wx.setStorageSync('gift_for_ref', ref)` |
|
||||
|
||||
好友通过分享打开后,`onLoad` 会解析 `ref` 和 `gift=1`,写入 `gift_for_ref`,后续支付流程需后端支持 `giftFor` 参数完成代付逻辑。
|
||||
- `开发文档/代付功能-美团式方案与场景清单.md`
|
||||
- `开发文档/迁移完成度与待办清单.md`
|
||||
|
||||
110
开发文档/迁移完成度与待办清单.md
Normal file
110
开发文档/迁移完成度与待办清单.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 迁移完成度与待办清单
|
||||
|
||||
> 乘风吸收当前经验与交互后整理。基于 new-soul 迁移方案、稳定版适配清单、agent 经验库。
|
||||
> 更新日期:2026-03-17(乘风同步进度)
|
||||
|
||||
---
|
||||
|
||||
## 一、已迁移完成(✅)
|
||||
|
||||
### 新版迁移方案(R1~R11)
|
||||
|
||||
| 需求 | 说明 | 状态 |
|
||||
|------|------|:----:|
|
||||
| R1~R4 | 余额体系:充值、消费、交易记录、退款 | ✅ |
|
||||
| R5 | 我的页「我的余额」入口 | ✅ |
|
||||
| R2 | 阅读页余额购买(章节/全书) | ✅ |
|
||||
| R6~R8 | 首页精选/最新展开、按热度、book/hot?limit=50 | ✅ |
|
||||
| R9~R10 | 代付(我的代付 + 阅读页代付分享) | ✅ |
|
||||
| R11 | 埋点 trackClick | ✅ |
|
||||
| VIP 余额 | VIP 页支持余额购买 | ✅ |
|
||||
| 管理端 | 订单支付方式、用户详情余额 | ✅ |
|
||||
|
||||
### 稳定版适配(P1~P3)
|
||||
|
||||
| 项 | 说明 | 状态 |
|
||||
|----|------|:----:|
|
||||
| P1 | 首页「阅读进度」卡 | ✅ |
|
||||
| P2 | 我的页「编辑」入口 → profile-show | ✅ |
|
||||
| P3 | 代付功能闭环(美团式:读页→代付页→分享) | ✅ |
|
||||
|
||||
### 代付美团式(2026-03-17)
|
||||
|
||||
| 项 | 说明 | 状态 |
|
||||
|----|------|:----:|
|
||||
| 读页入口 | 创建请求 → 跳转代付详情页 | ✅ |
|
||||
| 代付详情页 | 发起人「分享给好友」/ 好友「帮他付款」双态 | ✅ |
|
||||
| PayNotify | beneficiaryUserID 权益归发起人 | ✅ |
|
||||
| 订单与分佣 | 并入 orders、分销 | ✅ |
|
||||
|
||||
### 体验优化(2026-03-17)
|
||||
|
||||
| 项 | 说明 | 状态 |
|
||||
|----|------|:----:|
|
||||
| 目录页 | book/parts 加载 loading | ✅ |
|
||||
| 首页最新新增 | 默认 5 条、折叠,点击展开 | ✅ |
|
||||
|
||||
### 代付营销与体验(近期)
|
||||
|
||||
| 项 | 说明 | 状态 |
|
||||
|----|------|:----:|
|
||||
| 代付页营销 | 章节标题+内容预览(20%),吸引代付人 | ✅ |
|
||||
| 我的代付列表 | 点击卡片进入代付详情页 | ✅ |
|
||||
| 代付页布局 | 页面协调、间距统一、视觉统一 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 二、未迁移 / 待办
|
||||
|
||||
### 明确暂不处理
|
||||
|
||||
| 项 | 说明 | 决策 |
|
||||
|----|------|------|
|
||||
| 导师预约支付 | mentor-detail 预约成功后调起支付 | 暂不处理,保持与 new-soul 一致,弹「请联系客服」 |
|
||||
|
||||
### 边缘场景(可选)
|
||||
|
||||
| 项 | 说明 | 优先级 |
|
||||
|----|------|:------:|
|
||||
| ~~阅读页代付直链~~ | **已禁止**:gift=1&ref 打开 read 页时直接 redirectTo 代付页,统一在代付页代付 | ✅ |
|
||||
| ~~阅读统计埋点~~ | chapters/read 页补充 trackClick(篇章展开、章节点击、搜索、分享、购买) | ✅ |
|
||||
| ~~目录页「每日新增」~~ | latest-chapters 拉取 + 每日新增区块展示 | ✅ |
|
||||
| ~~P4 目录页与设计稿~~ | 免费/NEW/¥1/增值标签、每日新增区块 | ✅ |
|
||||
|
||||
### 稳定版已有(2026-03-17 分析)
|
||||
|
||||
| 项 | 说明 | 与 new-soul 对比 |
|
||||
|----|------|-----------------|
|
||||
| ~~富文本渲染~~ | 按稳定版处理,用户已落地 | ✅ |
|
||||
| ~~打包购买引导~~ | 购买 ≥3 章显示「解锁全书」按钮,两端一致 | 稳定版 = new-soul |
|
||||
| ~~存客宝对接~~ | ckb/lead、index-lead、match、join 均已对接 | 稳定版更完善:限频、linkTag ckb |
|
||||
|
||||
### 搁置项(无)
|
||||
|
||||
原搁置项富文本/打包引导/存客宝均已确认稳定版已有,无新增搁置。
|
||||
|
||||
### 规则与埋点(待补充,2026-03-17)
|
||||
|
||||
| 项 | 当前状态 | 待办 |
|
||||
|----|----------|------|
|
||||
| **埋点 trackClick** | 已接入:chapters、read、wallet | 遗漏:index、my、match、vip、search、referral 等页的关键操作 |
|
||||
| **规则引擎 ruleEngine** | 迁移方案「不迁移」,稳定版无 | 若需迁移:ruleEngine.js + GET /api/miniprogram/user-rules(soul-api 需新增 miniprogram 路由) |
|
||||
|
||||
**埋点遗漏页**:match(加好友、加入提交)、index(链接卡若、VIP 等)、my、vip、search、referral、gift-pay 等。
|
||||
|
||||
---
|
||||
|
||||
## 三、文档待更新
|
||||
|
||||
| 文档 | 待更新内容 |
|
||||
|------|------------|
|
||||
| 找朋友代付-流程与配置.md | 当前仍写 gift-link、弹窗分享,需改为 gift-pay 美团式流程 |
|
||||
| 稳定版-小程序与API对比.md | wallet 已迁移,可更新为 ✓ |
|
||||
|
||||
---
|
||||
|
||||
## 四、总结
|
||||
|
||||
**核心迁移已全部完成**:余额体系、代付美团式、埋点、首页/目录/阅读/VIP 相关功能均已落地。
|
||||
|
||||
**剩余**:导师预约支付(暂不处理);规则与埋点待补充(见上表)。富文本、打包购买引导、存客宝对接均已确认稳定版已有。
|
||||
Reference in New Issue
Block a user