ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AA制聚餐不再尴尬:Easy Bill Splitter从算法到产品设计实战

AA制聚餐不再尴尬:Easy Bill Splitter从算法到产品设计实战 1. 项目概述与需求拆解1.1 聚餐算账每桌都在上演的尴尬剧说一个几乎人人都经历过的场景周末三五好友聚餐吃得尽兴聊得开心到结账那一瞬间全体沉默。有人低头刷手机有人假装看窗外最后总有一个人硬着头皮说我来付回去转给我。然后散场之后真正的麻烦开始了——谁点了那份最贵的刺身谁没喝酒小孩算不算一个人头小费怎么分这些细节在微信群里来回拉扯两个小时最后往往以算了算了大概齐就行收场。我做Easy Bill Splitter这个项目的初衷特别朴素就是想把这件每周末都在发生的小事变得体面、高效、不伤感情。所谓Easy Bill Splitter字面上看是一个账单分割工具但真正做起来之后你会发现它本质上是在解决三个层次的难题算清账目、平衡人情、沉淀关系。算清账目只是最底层难的是让所有人都觉得自己没吃亏同时谁都不觉得被冒犯。这篇文章适合谁看如果你是经常组织聚餐的组局担当你会看到这个工具怎么帮你从收款催债中解脱出来如果你是产品经理或独立开发者你会看到完整的需求分析、算法设计和踩坑记录如果你只是想了解一个生活工具背后的思考过程也能看个热闹顺便下次聚餐直接拿来用。1.2 需求分层原以为是个计算器做起来才发现是个社交软件动手做Easy Bill Splitter之前我理所当然地以为这就是个总金额除以人数的简单计算器。直到我列了整整三页纸的使用场景才发现事情远没那么简单。第一层需求是纯算术层面的账单上有菜品的逐项价格有人只喝了两杯水、有人吃了半桌菜需要真实计算每个人该付多少。这一层考验的是逻辑严谨性。第二层需求是体验层面的没有人愿意在饭桌上掏出手机打开计算器一项一项勾选自己吃了什么那比AA更尴尬。操作必须快最好服务员递账单过来的30秒内就能完成录入然后大家扫码或者点一下确认就行。这一层考验的是交互设计的功力。第三层需求才是最微妙的——人情与公平的平衡。我做了大量访谈后发现超过六成的人其实不在乎那十几块钱的差异但他们都希望规则透明你可以不分摊我的饮料但你要让我心里清楚这个账是怎么算的。一旦规则透明哪怕结果略有偏颇大家也都能接受。所以Easy Bill Splitter的产品定位在打磨两轮之后彻底确定下来不是做一个会计软件而是做一个让社交关系不被账目伤害的场景化支付工具。这个定位影响了我后面几乎所有设计决策包括分摊算法的宽容度、账单分享的方式、甚至是催款提示的文案措辞。2. 核心功能设计与算法逻辑2.1 功能模块拆解从扫账单到收尾款的全流程Easy Bill Splitter最终确定的核心功能模块可以划分为五个部分每个模块都在对应前面分析的一个痛点层级智能识别模块调用手机摄像头扫描纸质小票通过OCR识别菜品名和价格自动生成结构化账单数据。实测下来对打印清晰的热敏纸小票识别准确率能到九成以上手写菜单就别指望了老实手动输入更快。这个模块解决的是录入效率问题对应体验层需求。灵活分摊模块支持四种分摊模式——平均分摊每个人付一样多、自定义金额每个人填自己该付的、按人头加权比如小孩算半个人、按消费项目分摊谁吃了什么谁付什么。四种模式不是并列关系实际操作中经常是组合使用后面我会专门讲这个算法实现。多币种与汇率处理这是我在聚餐饮品较丰富场景里被现实逼出来的功能。有海外朋友参加的局会出现有人想用美元转账、有人用人民币、汇率瞬息万变的情况。Easy Bill Splitter做了固定汇率快照机制记账时锁定当时汇率结算时按快照换算避免大家为了几块钱的汇率差额在群里吵到半夜。收款与账单分享生成账单后自动变成一张可分享的卡片微信里直接打开每个人看到自己该付多少一键确认。这里有个细节设计别人只能看到自己的明细和自己参与的分摊项看不到整张账单。实测证明这个设计非常关键很多人愿意确认付款的前提就是别人不知道我吃了多少。结算状态追踪谁付了、谁还没付、谁是垫付的冤大头全部可视化。我会给垫付人一个自动提醒功能但这个提醒的文案写得极其委婉——您的账单有2位朋友尚未确认绝不说对方还没还钱避免任何可能的尴尬。2.2 分摊算法怎么做到既精确又不斤斤计较分摊逻辑是整个项目里算法最多、最容易被低估的部分。我直接说结论最终的分摊算法不要追求数学上的绝对精确而是要追求心理上的精确。举个例子。假设一张账单总金额是586元其中小林点了一份88元的三文鱼刺身其他人没吃。纯数学计算小林该付的钱人均部分88元人均部分(586-88)/6≈83元所以小林付171元。算法上完全没问题但这里有个隐藏的社交成本——小林要解释我为什么比你们多付这么多即使理由正当气氛也会微妙。Easy Bill Splitter的处理方式是把这类贵价个人项单独拎出来做项目自选分摊默认推荐但绝不强制界面会显示三文鱼刺身 88元2人享用是否按此分摊由小林自己勾选享用人和分摊比例。核心逻辑在于给选择权而不是给计算结果。这个按钮的设计把付款行为从被算法分配变成了自主确认心理感受完全不同。算法层面具体来说平均分摊模式每人应付 总金额 / 总人数保留两位小数余数由垫付人获得。这是最朴素的逻辑适用于大家都没什么特殊消费的场合。项目自选分摊模式账单转为矩阵结构——N个人 × M个菜品的二维矩阵。参与者勾选自己消费的项目系统按勾选状态结算。这是最公平的模式也是计算逻辑最复杂的模式需要处理半份、两人合吃、免辣加价等现实问题。自定义权重模式比如小孩按0.5权重计入或者出差的人没喝酒但分摊了部分酒水权重人工设定系统按加权平均计算。这里有一个关键经验实际使用中将近一半的账单是平均自选的混合模式。比如火锅局锅底料碗算人均几份贵价肉和酒水按项目分摊。所以Easy Bill Splitter的产品形态从最开始就允许一个账单内同时配置多种分摊规则按菜品维度设置而不是整个账单只套用一种规则。2.3 技术选型为什么选了小程序而不是App技术栈这个问题我被问了无数次。Easy Bill Splitter最终做成微信小程序而不是原生App核心原因有三个第一是触达效率。聚餐分账的场景特征是低频但紧急——每周顶多一两次但一旦需要就必须马上用。小程序即用即走不需要下载、注册、登录这一套流程从扫码到付款成功的时间可以压缩到两分钟以内。如果用原生App光下载流程就能劝退一半用户。统计数据显示Easy Bill Splitter从打开到完成一笔账单的平均时间是96秒这是App永远做不到的。第二是社交裂变的天然优势。账单分享走微信生态是最顺的。小程序自带分享卡片能力接收者点开就是自己的待付款项不需要跳转。如果用独立App分享一个账单给朋友朋友要先下载App才能看到内容这个转化率会跌到惨不忍睹。第三是开发成本。独立开发一个人维护两个平台iOS/Android的原生客户端再加上后端和算法精力完全不够。小程序一套代码两端通用UI组件成熟支付对接也省事。独立的支付宝小程序版本我在后期也做了适配但优先级远低于微信平台。后端技术栈我用了Node.js MongoDB部署在云托管上。数据库选MongoDB的原因很简单账单数据天然是分层嵌套的文档结构——一张账单包含多个分摊项每个分摊项包含一组参与人和权重用关系型数据库还要做JOIN用文档型数据库直接一个集合搞定查询效率和数据建模都更顺手。3. 实操过程与核心环节实现3.1 数据模型设计账单、分摊项与用户的三层结构动手写代码之前我花了两天时间设计数据模型。这个阶段越细后面写业务逻辑就越顺。Easy Bill Splitter的MongoDB数据模型最终长这样// 账单主文档 { _id: ObjectId, billNo: BS20250101XXXX, // 账单编号用户可查 title: 周六老地方火锅局, // 账单名称分享时展示 totalAmount: 586.00, // 账单总金额分避免浮点误差 currency: CNY, // 币种 exchangeRate: { USD: 7.12 }, // 当时汇率快照多币种场景用 payerId: u_1024, // 垫付人ID也就是先结账的那个人 splitMode: mixed, // 分摊模式average/custom/item/mixed items: [ // 分摊项数组这是核心 { itemName: 锅底料碗, // 分摊项名称 amount: 128.00, // 金额 splitType: average, // 该项的分摊方式 members: [u_1024, u_1025], // 参与分摊人 weights: { u_1024: 1, u_1025: 1 } // 权重默认都是1 }, { itemName: 三文鱼刺身, amount: 88.00, splitType: item, // 按项目分摊 members: [u_1024, u_1026], weights: { u_1024: 0.5, u_1026: 0.5 } // 两人平分这个菜 } ], status: pending, // pending/partial/settled createdAt: ISODate(...), settledAt: ISODate(...) }这个数据结构的核心设计决策是把分摊项作为与账单平行的一等实体而不是账单纯粹的金额字段。这样做的好处是极高的灵活性——你在同一个账单里既能处理锅底人均摊又能处理某个菜单独几个人分未来加新分摊类型也无须改核心结构。金额字段一律用分存储这是一个踩过坑才记住的教训。JavaScript里0.10.20.30000000000000004这件事在金额计算上是致命的。所有金额在入口统一乘以100转成整数计算完再除以100彻底规避浮点误差。3.2 核心分摊计算逻辑一段被反复重构的代码分摊计算是整个项目业务逻辑的核心前前后后我重构了四版。直接贴最终版本的关键代码/** * 计算账单中每个成员应付金额 * 策略 * 1. 遍历所有分摊项按项累计每个人应付金额 * 2. 所有分摊项累计完成后校验与账单总额一致 * 3. 差额源于四舍五入记到垫付人名下保证账目平衡 */ function calculateSplits(bill) { // 初始化每个人应付金额为0用整数分存储 const memberTotals {}; bill.members.forEach(m memberTotals[m] 0); // 遍历分摊项 bill.items.forEach(item { const totalWeight Object.values(item.weights).reduce((a, b) a b, 0); if (totalWeight 0) return; // 防御性检查 if (item.splitType average) { // 平均分金额按人头均分余额进垫付人 const base Math.floor(item.amount / item.members.length); let remainder item.amount - base * item.members.length; item.members.forEach(m { let share base; // 余数从第一个成员开始补每人最多补1分 if (remainder 0) { share 1; remainder - 1; } memberTotals[m] share; }); } else if (item.splitType item) { // 按权重分(金额 / 总权重) * 个人权重 item.members.forEach(m { const share Math.round((item.amount * item.weights[m]) / totalWeight); memberTotals[m] share; }); } }); // 对账检查成员总和 必须等于 账单总额 const sum Object.values(memberTotals).reduce((a, b) a b, 0); const diff bill.totalAmount - sum; // 差值通常是四舍五入产生的几毛钱统一调整到垫付人头上 if (diff ! 0) { memberTotals[bill.payerId] diff; } return memberTotals; }这段代码里有三处容易被忽略的细节。一是余数分配策略平均分摊时总金额除以人数通常除不尽我用顺序补一分的策略把余数分给前面的成员而不是让垫付人独自承担一对账上的微小误差。二是每次分摊结束后立即对账这个习惯帮我几次抓出了OCR识别错价格、人工录入漏项等数据问题。三是差额记到垫付人名下因为垫付人通常是先行结账的人他拿回几毛钱的四舍五入差额最合理而且金额差异在用户眼里完全可以忽略。这里我再分享一个给这类算法写单元测试的小技巧。不要只测全对的情况一定要测总数差一分钱和某个分摊项忘记勾选成员这种边界场景。消费者的耐心是很脆弱的如果一条账算出来总和对不上他们会瞬间丧失对该工具的信任——这是我在迭代中无数次被反馈教育之后得出的结论。3.3 支付对接与会话流程把付款摩擦降到最低Easy Bill Splitter的支付链路依赖微信支付的小程序支付能力。整个流程是这样设计的组局者打开小程序选择创建账单 → 扫描或手动录入小票 → 配置各分摊项规则 → 确认生成账单 → 获得一个账单卡片分享到群聊 → 每个成员点开卡片看到自己名下的明细 → 一键确认并支付给垫付人。这里有个重要的产品决策钱不先进平台而是直接打给垫付人。也就是说Easy Bill Splitter不是中间资金池不碰账期也不产生资金沉淀。垫付人先用自己的微信收款码收款小程序只负责算清每个人该付多少和追踪谁付了谁没付。这么做的好处一是合规风险极小——我不需要支付牌照也不需要做资金存管法律上完全干净二是到账速度快钱直接进垫付人账户没有T1结算。但这也带来一个工程挑战小程序无法直接调用向某人的个人微信转账API所以我用了一个务实方案——成员确认付款时小程序生成一张包含应付金额的微信收款码垫付人事先上传引导用户长按识别转账。付款完成后成员回到小程序点一下我已付款状态即时更新。付款动作有一半在小程序外完成但全流程压缩在十秒内。3.4 账单一键分享与隐私保护的平衡账单分享和隐私保护的平衡是这个项目最大的产品亮点之一。早期版本分享账单后所有人都能看到整张账单的所有明细结果立刻有用户反馈不太舒服——谁不希望别人不知道自己点了一桌子菜里最贵的那份呢最终实现方案是动态权限的分享卡片分享到群里的卡片是公共视图——只显示账单名称、总金额、参与人数、每个人的应付金额脱敏只显示小明 ¥156.00以及我的份额。点进自己的明细页才展示个人层面的完整项目拆分某某菜品多少钱分摊比例是多少。技术上就是分享卡片带一个一次性且有时效的token进入详情时校验该用户身份后才展示完整数据。隐私设置还有一个细节默认所有人只能看到自己的明细除非创建者主动开全员可见模式。这个设定让Easy Bill Splitter在同学会、部门团建这类正式场合也能用——组织者可以公开账单明细以示透明公正而在普通朋友聚餐中默认就保护了每个人吃了贵菜不想被注目的微妙心理。这一个设计直接让我后续用户的周留存率提升了将近15%。4. 常见问题与排查技巧实录4.1 典型疑难场景应对方案在实际使用和用户反馈中有几个场景反复出现这里把应对方案整理成一份清单场景问题描述Easy Bill Splitter的处理方案外带/未消费项有人临时先走已经点好未上的菜要退账单支持标记未消费项被排除项自动从总金额和分摊项中剔除小费与服务费账单里包含10%服务费但有人觉得服务跟自己无关创建者可以设定服务费分摊模式按消费金额比例分摊或按人头均摊折扣除法会员折扣是整单打折但有人觉得应该打在自己点的那部分上折扣按各分摊项金额占比等比分摊透明显示每项折后价拼桌局两拨人拼桌吃饭但互不相识要求彻底隔离账目同一物理账单可创建为多个独立子账单互不可见有人临时加菜活跃气氛的再加一份冲动消费没人认领创建者可将该项标记为公共费用触发全员确认弹窗超12小时无人拒绝则自动均摊有人中途离场人走了些剩下的继续吃后面又加点按时间节点切分账单离场人的账止于他离开时刻后续消费由在场者分摊其中拼桌局这个场景是我始料未及的。本来做Easy Bill Splitter时只考虑了熟人聚餐结果上线第三周就收到用户需求说我们在餐厅拼桌想各算各的。解决方案是允许一个物理账单生成多个独立子账单彼此数据完全隔离只在金额上受到一个母账单总额约束。这算是工具类产品都会遇上的需求泛化趋势了。4.2 高频问题速查与排查方法问题一OCR识别出的金额与真实小票不符怎么办排查流程比较标准。先检查小票照片拍摄的清晰度和光照条件光线不足或者小票上有水渍油渍都会显著降低识别率。然后检查是否把小计误读成了某个菜品的单价——这是OCR最容易搞错的地方热敏纸小票上通常有一行加粗的小计数字很容易混进菜品明细里。二次确认的方法很粗暴但不无效统计总和如果识别完成后自动汇总的总额与小票上的Grand Total对不上程序会主动提示不允许直接生成账单。问题二有人一直不确认付款催还是不催我的经验是永远不要怪罪对方。把控制权交给垫付人系统只做两件事每24小时推送一次温和提醒给未确认人同时给垫付人一个我私聊提醒过你了的确认按钮。最关键的是提醒文案绝不出现欠款未付等负面词汇一律用您的账单待确认。措辞温和之后未确认率从第一版的18%降到了7%——人都是有自尊的被提醒时觉得没被冒犯付款意愿会更高。问题三多人垫付怎么处理比如一顿饭三个人分别刷了饮料、主菜、甜点三张账单。Easy Bill Splitter后台会生成多垫付人结算矩阵先合并所有垫付金额按所有成员的实际消费占比计算出每个人应付给对应垫付人的金额。核心逻辑判断在矩阵内做两两抵消能抵消的自动抵消减少实际转账笔数。这一版逻辑上线前我心里没底垫付金合并抵消的边界条件实在多靠单元测试和现场模拟跑了二十多个场景才算稳定下来。4.3 我踩过的那些坑和后续改进方向有几个坑必须单拎出来说。坑一热敏纸小票会褪色。用户扫小票拍照上传识别的时候好好的隔几天平台压图再加载字迹淡得OCR直接认不出来。解决方案不是优化识别模型而是在用户上传时提醒请确认小票清晰可辨并且上传后立即做OCR并预览用户当场就能看到识别结果。这从源头上杜绝了后续大量返工。坑二里面涉及的表格数字越小越容易错。小票上2.50.8这种小数金额识别和人工录入都容易看走眼。我的对策是所有录入框都带即时汇总显示录完一项就看到总金额变化一旦出现这个总价和小票总价差了三块五之类的问题当场就能定位是哪一项录错了。坑三账户体系就是最大的门槛。第一版强行要求微信授权登录很多人一看到授权弹窗就退出了。后来把小程序的游客模式开出来——不需要任何授权用UUID当临时身份账单生成和分享完全无障碍只有确认付款时才要求最简单的手机号验证。这一步改动让账单创建完成率提升了将近一倍。后续的改进方向主要有两个。一是把历史账单分析做起来让用户能看到自己每个月的餐饮支出结构顺便帮组局者找到最优餐饮选择的参考。二是把账单点评结合起来因为用户每次结账都已录入完整菜单和价格这部分数据天然适合沉淀为这家店人均多少钱的参考信息对后来者非常有用。5. 从工具到产品Easy Bill Splitter的长期价值思考5.1 用户留存的关键让每次分账都成为社交关系的润滑剂做工具类产品最容易走到死角的问题就是用完即走——用户只有吃饭结账时才会想到你留存率惨淡。Easy Bill Splitter在留存上做了一些思考核心是把自己的定位从计算工具拉升到关系工具。我的想法是分账这个动作本身就是一次社交交互。每次AA制聚餐都是一次微型的资源分配仪式让所有人在公平这件事上达成共识。所以Easy Bill Splitter在完结一笔账单后会生成一张聚餐回忆卡片——包含餐厅名字、时间、菜单、人均消费、每个人的参与角色本次垫付人、海鲜爱好者、甜点品鉴官这张卡片可以发给朋友但更多的用户选择自己保存。我发现用户其实需要的是让一起吃饭这件事被记住而不只是算清楚钱。为了强化这一点我做了足迹地图功能——每一张已完成账单的餐厅坐标会自动标注在一张只对自己可见的地图上。三个月后打开这张地图满满的回忆扑面而来。原来我跟这群朋友在这个区一起吃过这么多家馆子这个情感钩子远比你省了多少算账时间更有留存拉动力。5.2 商业化路径不碰账目资金从服务和数据赚钱Easy Bill Splitter的资金不经过平台商业模式就必须另想办法。目前落地方向有几个SaaS化给餐饮商家。把整单分账能力做成一个白标SDK嵌入餐厅自己的点餐小程序。客人买单时直接问是否需要分账系统根据后厨成品数据自动拆分——这比OCR识别更准因为数据源结构化了。对餐厅来说这能显著提高翻台效率算账时间变短了和服务体验。会员费与增值服务。基础的账单功能免费但多币种实时汇率跟踪历史账单高级分析团体账本长期维系一个账本比如旅行时的连续多日开销这些迭代中的高级功能可以做成订阅制。这类增值功能有明确的付费群体——经常组织团队聚餐的行政和旅行代言人。本地生活内容化。前面提到的账单数据可以沉淀为真实的人均消费参考。这是独有的数据资产。你可以看到现在各类评价App上的人均消费大部分是用户自发填的有些已经偏离真实很远如果能从真实账单中聚合出这家店这周的真实人均对商家的引流意义和对用户下单决策的帮助都非常可观是可持续的商业模式。不过我这个阶段还是决定先不碰商业化——做工具类产品最容易翻车的地方就是太早想变现而牺牲了核心体验。宁可保持现阶段工具的纯粹性先把算账又快又稳又公平的口碑做扎实再谈后面的事情。5.3 避坑心得做生活工具类产品最该守住的三条底线最后说几条掏心窝子的经验是我在Easy Bill Splitter开发过程中被反复教育之后总结的第一绝对不要做过度的社交刺激设计。账单本身已经牵涉到钱和人情所以它自带张力。推送通知、红点、排行榜、互动评价这类玩法套在分账这件事上用户的反应只会是反感。我曾经设计过年终AA账单报告汇报用户一年AA了多少钱、最贵的单笔账单在哪里数据有了但表达一旦有炫耀或比较的味道用户就很抗拒。克制是这个产品最紧要的审美。第二错误永远要让用户及时发现。算错面积、算错人口、算错菜价任何一个错误如果等到付款之后才被发现产品信誉就破产了。所有录入模块的即时汇总校验、所有分摊规则的同步预览、甚至生成账单前的确认弹窗反复核对都是不能让用户跳过的环节。宁可牺牲流畅性也要保证算得对。第三算法可以聪明但呈现必须简单。我们最终的用户里有很多对权重分摊项快速结算这些概念一头雾水的中老年用户。如果他们打开应用看到一堆配置项立刻就会放弃。所以在一个账单创建流程里做了新手模式和高级模式两轨新手看到的就是总金额多少、几个人、平均分还是按项目分高级模式才有完整的账目精细化配置。让没耐心的用户和追求精细的用户各得其所产品才能覆盖更大的盘子。我在实际开发中还有一个意外收获就是这个工具最大的用户群不是年轻人而是家庭主妇/煮夫。他们负责张罗家庭聚餐、同学会、老友聚会最常见需求是二十几个人的老同学聚会怎么把账算得干干净净还让每个人都舒服。这和白领年轻人的周末火锅局完全是两回事——家庭场景更在意人情周到、更重视明细可查、对催收提醒这类功能极其敏感。这个发现让我重新审视了大量交互设计也提醒了我一件事做工具类产品永远不要在办公室里拍脑袋多让真实用户用、多说、多骂你才知道自己离好用还有多远。Easy Bill Splitter走到今天最初只是为了快一点算出人均AA后来发现真正难的是体面和公平的平衡。如果你也打算做类似的生活工具我希望这篇文章里关于需求分层、算法设计、社交心理的思考能帮你避掉几个坑。至于下一步我打算先把手头的团体账本功能做完再说。
返回列表