门店扫码核销系统怎么搭?小程序 + .NET WebAPI + WPF 全栈实战
一、需求拆解一句话需求背后的三个坑「顾客出示一个码店员一扫就完成」——这是甲方最常说的一句话需求。落到工程上其实是三件事扫码端谁扫、扫什么、扫完怎么提示。判定端这个码有效吗、被扫过没有、并发时会不会重复生效。管理端今天核销了多少、谁操作的、能不能出纸质凭证。三件事对应三个技术角色小程序、.NET WebAPI、WPF 桌面端。下面按这个顺序把一套可上线的实现讲清楚。需要说明的是很多团队一开始想「一个后端全包」结果门店断网就查不了历史、出不了小票店员只好回到纸笔。把管理端放到桌面端正是因为它不依赖浏览器、不依赖网速门店电脑一开机就能干活。这也是为什么我说「三端解耦」不是过度设计而是门店场景的真实需要。二、小程序端扫码 授权 订阅消息1. 扫码拿核销码Page({scan(){wx.scanCode({onlyFromCamera:true,scanType:[qrCode,barCode],success:(res)this.verify(res.result)})},asyncverify(code){consttokenwx.getStorageSync(token)const{data}awaitrequest({url:https://api.yourdomain.com/api/verify,method:POST,header:{Authorization:Bearer token},data:{code}})wx.showToast({title:data.ok?核销成功:(data.msg||失败),icon:data.ok?success:none})}})2. 微信授权code2session 换 openid 必须在后端做wx.login拿到的code有效期只有 5 分钟且只能换一次。后端用它与appid、secret请求微信接口[HttpPost(api/auth/wxlogin)]publicasyncTaskIActionResultWxLogin([FromBody]WxLoginDtodto){varurl$https://api.weixin.qq.com/sns/jscode2session$?appid{AppId}secret{AppSecret}js_code{dto.Code}grant_typeauthorization_code;varrJsonSerializer.DeserializeCode2SessionResult(await_http.GetStringAsync(url));if(r.errcode!0)returnBadRequest(r.errmsg);// r.session_key 仅服务端用于解密绝不下发前端returnOk(new{token_user.IssueToken(r.openid)});}3. 订阅消息核销成功后下发提醒access_token必须服务端缓存7200 秒有效期建议提前 200 秒过期复用publicasyncTaskSendVerifyNotice(stringopenid){varbodynew{touseropenid,template_idTEMPLATE_ID_HERE,pagepages/verify/result,datanew{thing1new{value核销成功},time2new{valueDateTime.Now.ToString(yyyy-MM-dd HH:mm)}}};varurl$https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token{awaitGetAccessToken()};await_http.PostAsJsonAsync(url,body);}三、服务端核心核销接口的幂等实现重复核销是最高频事故。方案核销码唯一 行级锁 状态判定。[HttpPost(api/verify)]publicasyncTaskIActionResultVerify([FromBody]VerifyDtodto){awaitusingvartxawait_db.Database.BeginTransactionAsync();varcawait_db.Coupons.FromSqlInterpolated($SELECT * FROM Coupons WITH (UPDLOCK, HOLDLOCK) WHERE Code{dto.Code}).FirstOrDefaultAsync();if(cnull)returnNotFound(new{msg核销码不存在});if(c.Status1)returnConflict(new{msg该码已核销});// 第二次扫直接拦c.Status1;c.VerifiedAtDateTime.Now;c.Operatordto.Operator;await_db.SaveChangesAsync();awaittx.CommitAsync();_SendVerifyNotice(c.OpenId);returnOk(new{oktrue,noc.No});}SQL Server 用UPDLOCK HOLDLOCK锁行MySQL 用FOR UPDATESQLite 单机靠唯一约束 事务。重复请求返回409前端提示「已核销」体验远好于笼统的「失败」。四、微信支付回调验签AES-GCM 解密支付结果通过回调通知回调体用 AES-256-GCM 解密tag 在密文末尾 16 字节publicstaticstringDecryptResource(stringcipher,stringnonce,stringaad,stringapiv3Key){varkeyEncoding.UTF8.GetBytes(apiv3Key);varcbConvert.FromBase64String(cipher);usingvaraesnewAesGcm(key);varplainnewbyte[cb.Length-16];aes.Decrypt(Encoding.UTF8.GetBytes(nonce),cb[..^16],cb[^16..],Encoding.UTF8.GetBytes(aad),plain);returnEncoding.UTF8.GetString(plain);}商户私钥只用于给请求头签名平台证书用于验签回调来源私钥仅存服务端。签名拼接格式以微信支付 v3 官方文档为准。五、WPF 门店后台本地 SQLite 看板 小票门店网络不稳本地 SQLite 存核销记录离线可查、可打印。publicclassVerifyLogRepo{privateconststringCsData Sourcestore.db;;publicListVerifyLogToday()newSQLiteConnection(Cs).QueryVerifyLog(SELECT * FROM VerifyLog WHERE date(VerifiedAt)date(now) ORDER BY VerifiedAt DESC).ToList();}看板 XAMLDataGridItemsSource{Binding TodayLogs}AutoGenerateColumnsFalseIsReadOnlyTrueDataGridTextColumnHeader单号Binding{Binding No}/DataGridTextColumnHeader时间Binding{Binding VerifiedAt}/DataGridTextColumnHeader操作员Binding{Binding Operator}//DataGrid小票走串口 ESC/POS热敏机通用usingvarspnewSerialPort(COM3,9600,Parity.None,8,StopBits.One);sp.Open();sp.Write(Encoding.ASCII.GetBytes(\x1B\x40));// 初始化sp.Write($单号{log.No}\r\n时间{log.VerifiedAt}\r\n);sp.Write(\x1D\x56\x00);// 切纸sp.Close();六、上线配置域名白名单与隐私指引代码写完只是第一步小程序能跑起来还要过两道配置关服务器域名白名单小程序后台「开发管理 → 开发设置 → 服务器域名」里把https://api.yourdomain.com加进request合法域名。本地调试可临时勾「不校验合法域名」但真机提审必须备案且配置好否则线上请求全部被拦。隐私指引只要收集openid等个人信息必须在小程序后台「设置 → 服务内容声明 → 用户隐私保护指引」中勾选对应信息采集项并提交审核否则提审会被打回。备案服务器域名需完成 ICP 备案微信对未备案域名不放开request权限。七、部署WebAPI 放哪.NET WebAPI 可部署到云服务器Linux Nginx 反代或 Windows IIS。注意三点回调接口必须是HTTPS且公网可达微信支付才能推过来。access_token、商户私钥等敏感配置放环境变量或密钥管理别进代码仓库。核销接口建议加接口级限流 核销码失效时间防止恶意刷码。八、小结门店核销闭环最小可行组合小程序扫码 .NET WebAPI 幂等核销接口 WPF 门店后台本地 SQLite 小票打印。三端解耦各自可独立升级其中幂等接口、支付回调验签、域名白名单与隐私指引是上线前最该测透和补齐的四块。把并发防重做扎实再盯紧提审四条高频驳回理由这套骨架能复用到餐饮取餐、票务核验、会员消费等多数门店场景。本文为技术分享更多实战细节欢迎在个人主页查看商务合作请通过站内推广卡片联系。