ARTICLE DETAIL

资讯详情

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

外卖商城微信小程序开发全攻略:从登录到支付的核心实践

外卖商城微信小程序开发全攻略:从登录到支付的核心实践 在做 weixin129 外卖商城平台时我做的第一件事不是搭项目骨架而是先和团队吵了一架到底做 App 还是做微信小程序当时外卖业务刚起步老板觉得做 App 更像一个“平台”但我很清楚对大多数本地商户和用户来说微信小程序才是那个能先跑通的入口。实际运营数据后来也支持了这个判断所以这篇文章就围绕 weixin129 外卖商城平台的微信小程序开发过程聊聊业务模块、登录体系、购物车、微信支付、审核维护这些关键环节。如果你是第一次做外卖类小程序或者想做一套能落地的微信小程序外卖商城这篇文章基本可以当成一份踩坑地图来用。1. 为什么 weixin129 一上来就锚定了微信小程序1.1 项目背景本地商户的态度决定了入口选择weixin129 是我手里一个外卖商城平台项目的内部代号面向的是本地几家餐饮和生鲜商户。用户通过微信小程序完成浏览、点餐、支付、订单跟踪不需要额外下载 App。项目启动时团队对“平台”二字有执念总觉得必须有独立应用才算完整但我的结论恰恰相反先做微信小程序。原因很简单外卖用户的决策链路很短大多数人是看到商户桌贴、朋友圈转发、公众号推文里的二维码扫码进来下单。如果让他们去应用商店搜索、下载、注册、登录这一步就能过滤掉一大半用户。微信小程序天然处在这个链路里扫码即开、用完即走不用安装也省去了“新用户下载 App”的决策成本。后来首月数据显示超七成复购用户是从分享卡片和小程序码进来的App 版本根本排不上优先级。1.2 比起 Android/iOS/鸿蒙三端微信小程序起步成本低太多如果按原生方式同时做 Android、iOS、鸿蒙对一个小团队来说非常吃力三套客户端、三套发布流程、三套适配规则光维护成本就够喝一壶。weixin129 前期只有两名前端根本撑不起多端原生开发。微信小程序虽然也是“一个新端”但它有完整的开发、审核、发布和运营体系一套代码就能覆盖微信内用户支付和订阅消息也跟微信生态打通。有人会提 uniapp说做一套代码可以同时编译小程序、App、H5。这是个好选项但不是没有代价。uniapp 的抽象层在多数业务场景很顺畅可一旦遇到“微信原生能力较深”的功能比如自定义导航栏、手机号授权、订阅消息经常要写条件编译或者调用平台特有 API调试成本比纯微信小程序高。weixin129 最终选择的是微信原生小程序因为核心订单、支付、配送通知全部依赖微信生态短期内也不准备发布苹果和安卓。如果未来需要多端再迁移或借助 uniapp 重新包装比一开始硬上多端框架省心。1.3 角色端拆分先想清楚谁在用这套平台这个平台并非只有用户端微信小程序还涉及商家端、骑手端和后台管理。不过项目标题是“外卖商城平台的微信小程序”所以用户端小程序是主战场。商家端也可以用小程序给商家接单骑手端初期甚至可以用 H5 简化。角色使用端核心职责用户微信小程序选购、下单、支付、订单跟踪、评价商家微信小程序/H5菜品管理、接单、出餐、门店信息骑手H5/微信小程序接单、导航、送达确认平台运营Web 管理后台商品审核、优惠券、结算、数据这种拆分意味着用户端小程序要承担最高频的交易动作稳定性比功能丰富更优先。开发时我会把核心下单链路单独拉出来做专项测试而不是把时间平均花在所有页面上。2. 外卖商城的模块拆解与订单主链路2.1 用户端首页、菜单、购物车、订单至少四大块用户端小程序第一屏是首页通常包含定位商圈、商家分类、商家列表和轮播位。商家列表要支持按距离、销量、评分排序如果用户从某篇推广文章点进某个商家还需要通过页面路径参数shopId直达不能只落在一个通用首页上。第二层是商家详情页这里最容易做砸的是“菜单加载”和“购物车”联动。菜单要按分类分组每个菜品包含价格、月售、图片、规格选项加购时不能只传“菜名数量”必须把 skuId、规格、加料、备注一起带上。我的习惯是菜单数据请求完成后做一个本地增量缓存用户重复进入时省掉一次 loading 时间但价格和库存每次进入商家页都要求后台刷新。订单页需要按状态分 Tab待支付、待接单、配送中、已完成、售后。外卖订单的状态变化快前端要配合下拉刷新和定时轮询。在用户端尤其要避免“支付成功后页面卡在待支付”这种体验这非常影响信任感。2.2 商家端和配送环节商家端虽然也是小程序但它的定位不是“展示”而是“操作台”接单、拒单、设置营业状态、上下架菜品、打印小票。外卖平台的出餐效率很大程度取决于商家端能否在第一时间收到新订单。这里的方案有两种商家在小程序里轮询新订单或者后端通过微信订阅消息推送“新订单提醒”。对于单量高的商家我更推荐接入第三方点单打印机用 WebSocket 或 MQTT 推送但复杂度会更高。weixin129 初版先用了订阅消息加轮询兜底等功能稳定后再接打印机。骑手端如果刚开始没有自建运力可以先用第三方同城配送回调来简化。weixin129 初版没有独立骑手 App直接由商家自己选择跑腿平台后台记录配送单号状态更新靠电话沟通。等订单量上来再去考虑自建调度系统。2.3 一个完整的订单链路用户从打开小程序到订单完成主链路大致如下用户定位、选择门店选择菜品、规格加入购物车确认购物车、地址、配送时间后端创建订单状态变为“待支付”微信支付回调成功后状态变为“已支付/待接单”商家端接单开始备餐备餐完成配送员取餐状态变为“配送中”用户确认送达订单变为“已完成”如果超时未支付定时器自动关闭订单商家未接单超时系统自动取消或转单。这条链路是所有订单业务的地基。我们在这条路上吃过不少亏支付回调重复、商家拒单后金额退回、状态跳转不确定。解决办法是每个状态变更都记录操作人和触发源不允许前端直接改订单状态所有状态变更必须由后端在带校验的接口里完成。3. 登录、Token、请求封装和缓存这层不能省3.1 wx.login 与手机号授权要分开看外卖平台需要用户手机号目的是联系配送和售后。微信小程序常见做法是用wx.login拿到的 code 换 openid然后在小程序里用button open-typegetPhoneNumber获取手机号手机号不能直接由前端获得更不能把用户手机号密码暴露给客户端。实际开发中有个坑很多人以为调用一次wx.login后后端再拿 code 换 session_key就能永久识别用户。实际上 session_key 会过期换到的 openid 才是稳定的。为了避免每次登录都重新生成业务 Token我们通常这样设计首次进入用wx.login换 code后端用 code 换取 openid 并生成平台 Token 返回给前端后续接口请求带上 Token后端不再依赖微信的 code。需要手机号时再通过手机号快速验证组件把得到的动态 code 发给后端后端调微信接口换取真实手机号并和用户账号绑定。3.2 Token 过期、静默刷新与并发重放Token 一定会过期尤其是微信小程序这种“即用即走”的场景用户可能隔几小时再回来。如果后端返回 401 后前端直接弹窗让用户重新登录体验会非常差。最好做成静默刷新。请求模块需要统一处理 401收到 401 后判断是否有 refreshToken如果有先暂停当前请求队列调用刷新 Token 接口拿到新 Token 后重放刚才失败的请求。这里必须处理并发问题如果同时有 5 个请求都返回 4015 个请求都去调用刷新接口会造成重复刷新。常见做法是用一个isRefreshing标志位加一个等待数组let isRefreshing false let refreshQueue [] function handle401(request) { if (!isRefreshing) { isRefreshing true return refreshToken().finally(() { isRefreshing false refreshQueue.forEach(cb cb()) refreshQueue [] }) } return new Promise(resolve refreshQueue.push(resolve)) }这样只有第一个 401 会触发刷新其他请求等待刷新完成后再重放避免并发刷新导致多个 Token 同时失效。3.3 请求封装需要处理的三类场景外卖商城接口少说也有几十个如果每个页面直接写wx.request后续切域名、加鉴权、统一错误处理都会非常痛苦。我把请求封装收敛到一个request.js里统一处理三类异常网络不通wx.request的fail回调提示“网络连接失败”但不要频繁 toast超时设置timeout普通查询 10 秒下单接口 15 秒下单时按钮要防重复提交用户点击后立刻置灰业务码异常后端返回code ! 0不能统一弹“系统错误”要根据码表给可读文案。比如SOLD_OUT提示“菜品已售罄”ORDER_FINISHED提示“订单已完成”。另外每个请求都要带一个请求序列号或时间戳方便联调时在后端日志里定位问题。微信小程序联调时如果后端看不到请求 ID排查问题会非常难受。3.4 缓存时间的设计不是所有数据都能缓存微信小程序 API 里只有wx.setStorageSync没有自带过期机制。我们封装了一个带 TTL 的缓存方法存值时写入expireAt读取时判断是否过期。外卖商城里商家列表可以缓存 5 分钟菜单缓存 2 分钟首页轮播缓存 10 分钟但购物车和订单状态绝对不能缓存必须实时请求。为什么要注意这个因为外卖的库存、价格、营业状态变化很快。如果菜单缓存时间设置太长用户看到有货但下单时提示已售罄会对平台失去信任。而且微信小程序本地缓存有体积上限过期的数据要及时清理避免后续写入失败。对网络质量差的用户页面可以先显示旧缓存再后台刷新新数据如果刷新失败至少还有内容可看不会白屏。4. 购物车、SKU 规格和订单状态机4.1 购物车不能只存一个“数量”加购看似简单实际在外卖场景很复杂。不同门店的菜不能放在同一个订单里所以购物车首先要按shopId分组。同一个菜品可能有不同规格大杯、小杯、加冰、少冰不同规格价格不同。购物车item的结构至少是这样{ shopId: shop_001, shopName: 某茶饮, items: [{ skuId: sku_1001, spec: [大杯, 少冰], extraIds: [珍珠, 奶盖], price: 1800, // 单位分 count: 1 }] }加购、减购时不要通过遍历所有商品找匹配项最好为每个商品生成一个唯一的cartKeyskuId spec extraIds 备注用 Map 做加减。最后下单时后端还要重新校验价格和库存不能信任前端传来的金额。4.2 SKU 库存与超卖外卖菜品库存和电商 SKU 不太一样很多餐饮店希望“上午设置当日库存卖完即止”。项目里用后端库存扣减不是前端控制按钮。当用户提交订单时后端检查库存扣库存时使用条件更新UPDATE product_sku SET stock stock - #{count} WHERE sku_id #{skuId} AND stock #{count}如果影响行数为 0说明库存不足立即返回售罄。订单支付超时后需要做反向操作把库存释放。这里要注意预占库存的订单如果一直不支付会造成其他用户无法购买所以必须有一个定时任务关闭超时未支付订单并回补库存。4.3 订单状态机的核心流转用表格列一下基础状态状态值含义触发者前置条件0待支付用户提交订单购物车有效1已支付待接单支付回调支付验签通过2已接单备餐中商家操作状态为13配送中骑手/商家操作状态为24已完成用户确认/系统自动状态为3-1已取消用户/超时状态为0-2已退款平台/售后状态可为1/2/3这个状态机不允许任意跳转比如用户不能从“备餐中”直接取消只能申请退款。支付回调触发 0 到 1 时必须幂等否则回调重复到达订单状态会被覆盖。我们的做法记录微信侧transaction_id更新订单时带上条件WHERE status 0如果更新行数为 0说明订单已经处理过直接返回成功。5. 微信支付、退款与结算流程里的那些坑5.1 金额单位与支付参数微信支付和相关接口的金额单位都是“分”。前端展示用“元”后端算费用时必须转成整数分并且避免浮点数运算。比如用户订单金额 28.9 元后端要按 2890 分传给支付接口。很多初学者直接把total_fee设为29.00最后对不上账。预支付订单参数中会用到openid、out_trade_no、total_fee、notify_url。out_trade_no要用后端生成的唯一订单号不要用前端传过来的订单号拼接否则容易被伪造。签名和证书一定只放在服务端小程序端只需要拿wx.requestPayment所需的支付参数。如果后端在处理回调时发现金额不一致要主动调微信支付接口查询真实支付金额再去更新订单不能直接信任回调里带的金额。5.2 回调验证、幂等与补偿wx.requestPayment成功只能作为“用户已完成支付”的提示不能作为订单状态的最终依据。微信支付成功通知是异步的可能延迟也可能重复推送。服务端收到通知后必须校验回调签名、校验商户号、校验订单金额再更新订单。一个比较稳的方案是支付回调只负责置为“已支付”不做太多业务动作。同时加一个定时任务每隔一段时间扫描“用户已支付但订单状态还是待支付”的订单拿着订单号向微信支付查询如果确实已支付则补更状态。前端轮询订单状态可以使用“支付成功后每秒查一次连续查 5 次”如果还是待支付就提醒用户“支付结果确认中请稍后刷新”不要直接宣布失败更不要让用户二次支付。5.3 退款、优惠券与商家结算外卖平台的退款比电商还难因为涉及商家、平台、配送费三方分摊。用户申请整单退款时如果商家已出餐通常会拒绝如果商家未接单平台自动退款。退款接口也需要做幂等微信支付退款后会有退款回调需要用out_refund_no去重。优惠券的处理也要提前约定用掉的满减券在退款后是否退回一般约定一次性优惠券不退避免用户“退款后继续用券”的漏洞。商家结算是按周期计算“用户实付金额 - 平台抽佣 - 配送费”遇到退款订单要先冻结商家收益等退款结果确认后再做对应扣减。不提前想清楚这层后台财务模块很容易在第一个月就崩。6. 导航栏、定位、图片和 iOS 请求失败这些细节决定体验6.1 自定义顶部导航栏的高度适配外卖商城的首页通常要把搜索框放进顶部导航栏所以需要自定义导航栏。微信小程序的胶囊按钮和状态栏高度在不同机型差别很大。如果写死padding-top: 20pxiPhone 刘海屏会顶上去老机型又太空。建议调用wx.getWindowInfo()取statusBarHeight再调用wx.getMenuButtonBoundingClientRect()取胶囊位置动态计算导航栏高度const statusBarHeight wx.getWindowInfo().statusBarHeight const menuButton wx.getMenuButtonBoundingClientRect() const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height把计算出的高度放到全局数据里页面用内联样式设置padding-top这样能兼容绝大多数设备。虽然也有现成组件但外卖首页的导航栏通常还要嵌入搜索框和定位按钮自己封装反而更好用。6.2 定位权限和配送范围别让后端背锅外卖商品的配送范围判断不只是前端的事。用户点“我要定位”后wx.getLocation会弹授权框。如果首次拒绝下一次再调用不会自动弹需要在页面上做引导先检查wx.getSetting若授权已关闭就提供一个“去设置”按钮调用wx.openSetting打开设置页。拿到经纬度后前端可以展示距离但能否配送应该由后端判断。前端拿到地址坐标可能在商家配送范围边缘导致下单后被拒。我们是在后端把每个门店的配送半径配好根据经纬度计算距离。如果超出范围下单页直接提示“当前地址超出该门店配送范围”并推荐附近的同类商家而不是等用户填完地址再报错。6.3 图片处理CDN、压缩和 canvas小程序主包限制很严格外卖商城图片极多。所有菜品图、商家 logo、广告图都要走 CDN不能塞进本地包。开发时可以用相对路径上线前必须全部切到 HTTPS 的 CDN 域名并在小程序后台配置downloadFile合法域名。列表页的图片要按尺寸压缩比如商家列表缩略图宽度 350px菜品缩略图 200px详情页再用原图。不要前端直接加载原图这会消耗用户流量也很容易在 iOS 大图上导致页面卡顿。分享海报用 canvas 绘制时图片必须先在本地downloadFile得到临时文件再画不能直接画网络图片否则某些机型的 canvas 会空白。6.4 iOS 网络请求失败率偏高的排查经验有段时间 weixin129 收到反馈说 iPhone 上某些页面网络请求失败率明显比安卓高。排查下来主要原因是接口域名证书链不完整或者部分接口还走 HTTP被系统的传输安全策略拦截。微信小程序环境同样有安全要求线上环境必须是备案过的 HTTPS 域名。另一个常见原因小程序后台的 request 合法域名配置错误或者域名证书缺少中级证书。验证方法是直接用 iPhone 浏览器访问接口域名看有没有证书报错再到微信开发者工具“详情-域名信息”里检查。另外WebSocket 连接也需要配置 socket 合法域名如果频繁断连大概率是域名配置或证书问题而不是代码逻辑问题。7. 上架审核、年审和运维别等项目火了才想起7.1 资质材料和体验账号要提前准备外卖商城小程序审核时平台会要求提供对应资质。餐饮外卖需要《食品经营许可证》或类似证件资质主体要和小程序认证主体一致。类目选错很容易被驳回建议在准备阶段先查最新的微信小程序类目要求比如“餐饮-外卖服务”“生活服务-同城配送”等。第一版提交审核前最好做一个“体验账号说明文档”把商家后台账号和测试门店地址提供给审核人员。审核人员不会记住每个门店的菜名所以要给可复现步骤打开小程序、定位门店、下单、支付、取消。如果没有说明文档对方可能卡在找不到商家或无法验证功能上。7.2 审核驳回的典型原因外卖平台容易碰到这些驳回点用户隐私政策缺失、收集位置和手机号时未声明用途、分享卡片涉嫌诱导分享、营销弹窗误导、虚拟商品支付。尤其是“支付”环节小程序平台对虚拟支付管控很严格但外卖属于真实生活服务正常微信支付即可。不要搞“先充值再消费”的预充值模式审核风险较高。另一个隐蔽问题首页或详情页不能夸大宣传比如“零利润”“全网最低价”这类文案审核和后续投诉都很麻烦。小程序的宣传文案要基于真实活动不能为了曝光写绝对化用语。7.3 年审、灰度发布和监控微信小程序认证每年需要年审年审到期时微信官方会提醒。如果平台主体资质没有变化按时提交即可但很多人会忽略导致搜索和支付能力受影响。这里提醒不要拖到过期再处理。版本发布时不要一上来就全量发布。可以用微信后台的“分阶段发布”先放 10% 的用户观察错误率和用户反馈再逐步放量。后端要有按渠道统计错误日志的能力至少监控下单接口、支付回调、订单状态查询的成功率和耗时。有一次我们发现 iOS 用户支付成功率高但订单创建失败率高最后定位到是某个页面参数在 iOS 上被转成了NaN如果没有监控这个问题会被用户骂很久才发现。最后放一个我最想说的提醒不要在线上环境为了省事打开开发版或者使用未审核的版本所有业务验证都要走正式版和体验版。外卖商城是强交易产品一个不起眼的缓存错误都可能造成用户下了单却收不到餐。做 weixin129 的过程中体验版在我们手里过了几百遍真正筛选出来的基本都是这类很细但很关键的稳定性问题。
返回列表