全程 AI 写的小程序,刚接上会员虚拟支付,上线第二天就有人真金白银买单了|双签名、定价、iOS 的坑我全踩了一遍
厨菜记是我一个人做的家庭菜谱小程序。10 月 12 号我把它的会员体系正式上线了——两档付费会员、六个 SKU,支付走微信小程序虚拟支付。同一天顺手把 AI 荐菜 / AI 识别 / AI 生图也推了上去。
10 月 13 号,后台蹦出来第一条会员订单。
一个陌生人,真金白银给我开了会员。从 9 月 18 号上架到今天,满打满算 25 天,一个月都不到。上一篇写流量主的时候我还在算"第一个月收入三十多块",那会儿收费这条路我连资质都没有;现在会员真来了一单,金额很小,但性质跟广告那几块钱完全不是一回事——广告是平台按曝光赏我的,会员是有人认可这个产品、主动掏的。
写这篇不是报喜,是想把「一个小程序到底怎么把虚拟支付和会员跑通」这件事从头到尾记一遍:为什么非走虚拟支付不可、六档价格是怎么算出来的、下单到发放那两把签名钥匙卡在哪、iOS 又有哪些后台门槛。数字和坑都是一条条核过的,代码大部分是 AI 写的,但越到钱这一步我越不敢全交给它——原理我得自己看懂才敢上线。口径不一致的地方我直接写出来。
先把数据摆在这
不吹,先给体量,难听的话先说。
| 指标 | 数值 | 备注 |
|---|---|---|
| 上线天数 | 25 天 | 9/18 → 10/13 |
| 累计独立访客(10/07) | 601 | 流量主已开通,见上一篇复盘 |
| 日访问人数 | 47 左右 | 假期后回落,稳在两位数 |
| 会员档位 | 6 个 SKU | 轻享 3 档 + 畅厨 3 档 |
| 到今天会员收入 | 一单 | 个位数金额,截图我看了三遍 |
一单。就一单。搁大厂那是一笔都进不了报表的数,但对我来说,它是第一笔"用户主动掏钱"而不是"平台按曝光分给我"的钱。这两笔钱的来路不一样——广告那几块钱是我把人留在页面上、平台赏我的;这一单是有人觉得这个小程序值这个价,自己点开的。
为什么收费这条路,只能走"虚拟支付"
卖会员这种虚拟商品,2026 年起微信有条硬规矩:
用普通微信支付(商户号那套)卖会员 / 虚拟商品 → 违规,会被判定
必须走 → 小程序虚拟支付(XPay / 虚拟支付 2.0)
得单独开通"虚拟支付"这个能力、签协议、建道具。普通微信支付我熟,unifiedorder 那套签名早烂了;换成虚拟支付是另一条链路,接口叫 wx.requestVirtualPayment,钱也不走我的商户号,走的是微信的虚拟支付账户,按苹果/安卓分账。这一步 AI 帮不上忙——它是后台配置和一整套审核流程,我只能自己一遍遍点。
价格是我算出来的一道除法
六档定价不是拍脑袋,是对着竞品「独家御膳房」一档一档压出来的,也是被 iOS 那条"最低 1 元"的地板卡着定的:
| 档位 | 月付 | 季付 | 年付 |
|---|---|---|---|
| 轻享会员(免广告) | ¥1.9 | ¥4.9 | ¥12.9 |
| 畅厨会员(免广告 + 无限分类 + 无限菜谱) | ¥7.9(首月 ¥2.9) | ¥19.9 | ¥39.9 |
畅厨·月首月 ¥2.9 是转化钩子,阶梯顶只做到年付,三年卡和永久卡我明确不做——理由很实在:服务器我按年续、AI 生图有真实赠额成本、产品随时可能停,"永久"这两个字进了用户账单还属违规口径。竞品能卖 ¥98.9 的永久卡,那是它的风险预算,不是我的。
定价之前我还被 iOS 的费率上了一课。安卓走微信支付,iOS 走 Apple 支付,苹果要抽成,粗略按 88% 到手算:
畅厨年卡 ¥39.9 走 iOS → 到手 ≈ ¥35.1
轻享月卡 ¥1.9 走 iOS → 到手 ≈ ¥1.67 ← 钩子价,基本不赚钱,纯为转化让的
所以 ¥1.9 那档根本不是利润档,是"先让人迈进门"的价格。这笔除法 AI 不会主动替我做——它不知道苹果抽成,也不知道我为什么要贴钱做钩子。
重头戏:虚拟支付那两把签名钥匙,卡在哪
这是我最想聊的一段。整个下单到发放的链路,AI 是这么给我串下来的:
选 SKU
→ POST /member/order/create 后端生成订单号 outTradeNo,落一条待支付单(这一步不碰签名)
→ POST /xpay/sign 按单号回读快照 + 算两把签名
→ wx.requestVirtualPayment({ signData, paySig, signature, mode: 'short_series_goods' })
→ 支付成功
├── 微信发货通知(服务端推送,最多重试 15 次)
└── 万一回调丢了 → 前端主动 POST /member/confirm 查单兜底
→ MemberService.grant() 幂等校验 → 写会员 → 权益当场生效
链路看着顺,AI 也写得顺。真正让我停下来一行行啃的,是那个 sign 里的两把签名:
paySig = hex(hmac_sha256(AppKey, uri + '&' + signData))
signature = hex(hmac_sha256(session_key, signData))
两个签名、两把不同的钥匙,缺一不可。我逼自己搞懂的是这三件事——它们每一个 AI 都能给我写"对",但写错的时候它不会提醒我:
第一,两把钥匙都不能下发到前端。 AppKey 是虚拟支付的现网密钥,session_key 是用户登录时微信给的会话密钥,两个都只能在服务端算签名。这不是我谨慎,是原理决定的:一旦下发,任何人都能伪造任意价格的订单。这种安全边界,AI 默认会写对,但如果你不知道"为什么必须放服务端",将来重构时你很可能亲手把它挪到前端去——它不会拦你。
第二,signData 这个字符串,前端一次都不许多 JSON.stringify。 服务端算签名用的是某一顺序拼出来的 signData 原文,前端必须原封不动透传同一段字符串去调 requestVirtualPayment。哪怕字段一个没变、只是序列化顺序不同,微信那边重算签名对不上,直接 -15006 拒付。这个坑极其隐蔽——本地测试偶尔过、偶尔不过,你根本想不到是 JSON 键顺序在捣鬼。所以我后端把 signData 生成好,原样返回,前端连碰都不能碰。
第三,session_key 我之前根本没存。 这个是整版对既有代码改动最大的一处。登录链路里我早就调了 jscode2session、也拿到了 session_key,但只用了 openid 和 unionid,session_key 用完就丢了。可虚拟支付算 signature 必须要它。AI 能把签名函数写得漂漂亮亮,但它不会主动回头去翻我登录那个类里"拿过却没用"的字段,也不会告诉我"你得把它存进 Redis、还得跟 token 同 TTL"。这个是我自己顺着 signature 需要的入参,倒查到 session_key,再倒查回登录服务的。用户侧的表现是:session 过期了下单会报 -15007,前端得静默重新登录刷一遍再重试一次。
发放那头的原理同样不能含糊。微信的发货通知会被丢,所以我不靠回调直接放行,而是收到通知后先调 query_order 主动查单复核一遍,确认微信侧真的付了钱才发放;发放本身用状态位 0→1 的 CAS 做幂等,同一个人同一单被推两次也只发一次。跨档位续费的折算(低档买高档按剩余天数折、同档顺延)这套规则 AI 一次没写对过,是我拿计算器一档一档试出来的。
一句话:AI 负责把这条链跑通,我负责判断它在钱这件事上跑通的是不是我敢上线的那条。
iOS 支付另有一套后台门槛
Android 上跑通了不代表 iPhone 能付。iOS 走的是苹果 IAP,得满足一堆前置条件,全是后台配置和系统门槛,跟代码没关系,AI 无从知晓:
系统 iOS 15 及以上
微信 客户端 8.0.68 及以上
金额 不低于 1 元(所以我 ¥0.9 的档根本没做)
地区 App Store 账号为中国大陆
后台 ① 设了小程序简称(收银台要展示)
② 开了「虚拟支付 - 苹果 IAP」开关
支付接口本身是 wx.requestVirtualPayment 一个,按设备自动路由:Android 走微信支付、iOS 走 Apple 支付,道具双端互通不用各配一套。这些条件少一条,iPhone 用户点了就是拉不起收银台。我把 IAP 开关实测确认到"已开通"、OfferID 和 AppID 都跟代码对上,才敢发版。这种活没有算法可写,只有一个个后台页面去核对。
会员到底买什么:两档、四件权益
先说清楚这两档分别给什么,因为价格段也好、签名段也好,最后都得落回"用户付了钱到底换到什么":
| 权益 | 免费 | 轻享会员 | 畅厨会员 |
|---|---|---|---|
| 免 Banner / 插屏广告 | ❌ | ✅ | ✅ |
| 分类上限 | 10 个 | 10 个(可看视频 +1) | 不限 |
| 菜谱上限 | 50 道 | 50 道(可看视频 +1) | 不限 |
| 👑 会员标识 | ❌ | ✅ | ✅ |
轻享 = 只去掉展示型广告;畅厨 包含轻享,再拆掉容量天花板。两档互斥且包含,一个人同一时刻只有一个生效档位(member.tier 单值)。而且两个"不限"只作用于你本人创建的厨房,在别人的厨房里添加权限仍由那边厨师长授予,与本服务无关。
会员不是凭空长出来的闸门,它全部挂在既有链路上:免广告收口在 ad-unit 一个渲染闸门(判 member.tier 短路),无限容量收口在 3 处已达上限校验点——不改数据、不动存量。有效性 = expire_at > now() 读时判定,不需要任何定时回收。
(免的那“插屏”一共四处:今天吃什么 / AI 荐菜 / AI 生图 / AI 识别——AI 功能人人能玩,但付了钱不用看这四下插屏。)
会员跟流量主那几块钱,为什么不冲突
上一篇我把流量主讲得很别扭:当时只有广告这一条回本的路,而我为了不伤体验,一个广告位只肯放菜谱列表底部一个 Banner,第一个月几十块。
广告这条腿有个绕不开的死结——收入等于 DAU × eCPM,用户越活跃、看得越多,我赚得越多,可体验也越差。这两件事直接对冲。会员是我给自己找的第二条腿:把"不想看广告"和"不想被 50 道菜 / 10 个分类卡住"这两件已经验证过有痛感的事,打包成付费权益。付费用户把 Banner 和插屏都免了,我不用再靠给他们塞广告挣钱,他们反倒给了我更稳定的订阅。体验和收入,第一次不打架了。
免费用户那边我也留了条白嫖的路:撞到容量上限还能"看视频 +1"。轻享不免激励视频这条我是特意定稿的——看视频换容量是一笔交易,不算打扰,不该被"免广告"顺带走掉;只有畅厨因为直接取消上限,才让那个入口失去意义。付了钱反而被削掉一条免费路径,比权益没生效还严重。
我也给自己划了线:不做自动续费、不做诱导性连续包月话术,到期绝不自动扣款,需要时自己来续。会员到期后之前新增的菜和分类全部保留,只是恢复到原上限提示——能看能用,不能新增。
说点不好听的
别把这篇当成功案例。今天只有一单,金额个位数,连服务器一天的钱都不够。而且有个真实的坑我还留着:会员到期提醒功能后端已经写完了(每天 9 点查 3 天内到期的人、走通用订阅消息发一条),但现网数据库里那张模板表缺了 MEMBER_EXPIRE 那行,导致提醒现在压根发不出去。它不影响下单支付发放,只影响"到期了没人通知你"。这种瑕疵我选择不藏——一个人维护,就是把已知没做完的地方一条条补上,而不是发布时假装它不存在。
我也不打算为了把单量做大去改产品逻辑。不逼氪、不弹"再不买就恢复限制"、不在免费路径上加钉子。付费页上那行"后续调整权益将在本页公示后生效"是我特意写的,就是为了将来真要动权益时,不拿付费用户当既成事实。
这产品本身,欢迎来试
厨菜记干的事很窄:把自己家做过的菜记下来,一家人共用同一份菜单。没有推荐流、没有大 V、没有算法猜你今天想吃什么,你打开看到的每一道都是家里人亲手录的。吃这件事是一条链走完的——按四个时段点菜下单、做饭的人在「下厨」里上菜、吃完每人给菜打 1 到 5 星,分数沉回那道菜本身,"做过 12 次 · 4.8 分"是你们家一口一口吃出来的,不是运营打的标签。
现在多了几个 AI 功能:AI 荐菜(今天吃什么,从没你的菜里帮你挑)、AI 识别(一段文字/截图秒变成结构化菜谱)、AI 生图(给菜配张像样的封面图)。免费每天各有几次额度。昨天上线、今天就有人买单这件事本身也说明,AI 加得对不对,最终是用户用钱投的票,不是我说了算。
微信搜「厨菜记」,首次进来自动创建一个默认厨房,选几个分类就开始,不用注册、不用授权一堆用不上的权限。一个人用也行,当个只装你自己菜谱的笔记本;不过点菜、下厨、打分这条链,得有两人才热闹得起来。会员现在是两档,畅厨能解锁无限分类和无限菜谱。
最后
AI 昨天上线了功能,今天替我挣到了第一笔"被认可"的钱。但我越来越信一件事:它把"做出来"的成本压到几乎为零的同时,也把"看懂它做对没做对"的责任全压回了我身上。写签名函数两分钟,搞懂为什么两把钥匙都不能下发前端、为什么 signData 一次不许重新序列化、为什么那个我"拿过却没用"的 session_key 才是这版的命门——这些 AI 给不了,得你自己顺着调用链往回倒。
钱到账那一刻我挺高兴的,但让我敢点"发布"的,不是那两把 HMAC 钥匙 AI 写得多漂亮,是我终于也看懂它锁的是哪扇门。
有也在趟虚拟支付的,尤其是被 signData 签名不一致和 session_key 卡过的,评论区聊,我看到都会回。