首先,我们先看下效果,以下是服务端的收款二维码的发起示例演示:
其次,我们再看看手机端 微信扫码支付的演示:
我们手机端会将收款的消息推送到服务器API。其中接口信息定义大概如下:
{"title":"微信支付","time":"2020-05-08 23:34:11","money":"0.80","deviceid":"mydevice","content":"[2条]微信支付: 微信支付收款0.80元(朋友到店)"}
以上视频是让大家有个效果感观,下面我们将详细讲解具体实现原理与细节。
如果您对本专题有兴趣,可以按照下面的思路实现。
同时,您也可以在 文章结尾处 查看获取源码的方法 供用于学习研究用途的 完整源码ZIP。
源码ZIP包括:
一、主源码-服务端Api (基于.net core webapi,用于处理支付逻辑)
二、前端基于boostrap的发起二维码扫码界面UI
三、Android Apk 源码 (java,用于监控手机消息)
四、apk (编译完成可用的apk, 如果你不熟悉android,可以直接用这个已经编译好的apk )
API处理源码示例如下,
using System; using System.Linq; using System.Linq.Expressions; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Lyn.Pay.Api.Domain; using Lyn.Pay.Api.Utils; using Lyn.Pay.Api.DAL; namespace Lyn.Pay.Api.Controllers { /// <summary> /// 控制器 /// </summary> [Route("v1/[controller]/[action]")] public class PayController : Controller { //https://github.com/stulzq/snowflake-net private static Snowflake.Core.IdWorker worker = new Snowflake.Core.IdWorker(1, 1); public PayController() { } #region 业务应用 [HttpPost] [AllowAnonymous] public JsonResult QueryAlreadyBuy([FromBody]AddOrderVo vo) { var remoteUserIp = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); var userIp = vo.IP.HasValue() ? vo.IP : remoteUserIp; //检查这个IP是否已经购买过此文章了 var alreadyBuy = Service.QueryAny("SELECT 1 FROM [ORDER] d WHERE d.TradeProduct = @TradeProduct AND d.IP = @IP AND d.TradeStatus=1 ", new { TradeProduct = vo.ProductName, IP = userIp }); if (alreadyBuy) { return Json(Result.Fail(ResultCode.AlreadyBuy)); } else { return Json(Result.Fail(ResultCode.Fail)); } } /// <summary> /// 产生新支付订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult AddOrder([FromBody]AddOrderVo vo) { var remoteUserIp = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); var userIp = vo.IP.HasValue() ? vo.IP : remoteUserIp; //检查这个IP是否已经购买过此文章了 var alreadyBuy = Service.QueryAny("SELECT 1 FROM [ORDER] d WHERE d.TradeProduct = @TradeProduct AND d.IP = @IP AND d.TradeStatus=1 ", new { TradeProduct = vo.ProductName, IP = userIp }); if (alreadyBuy) { return Json(Result.Fail(ResultCode.AlreadyBuy)); } var money = 0; var sqlGetValidMoney = @" SELECT TOP 1 * FROM Product p WHERE (p.[NAME]=@ProductName OR p.[NAME]='Gobal') AND NOT EXISTS( SELECT 1 FROM [ORDER] d WHERE (d.TradeProduct = p.[NAME] OR p.[NAME]='Gobal') AND d.TradeMoney = p.Money AND d.TradeStatus=0 ) ORDER BY p.IsGobal ASC , p.Money ASC "; var r = new Order(); //先将过期的更新为过期状态 Service.Execute("UPDATE [ORDER] SET TradeStatus=3,ModifyTime=GETDATE() WHERE TradeStatus=0 AND datediff(ss,CreateTime,GETDATE())>120", null); var p = Service.QuerySingle<Product>(sqlGetValidMoney, new { ProductName = vo.ProductName }); if (p != null) { r.Id = worker.NextId(); long.TryParse(r.Id.ToString().Substring(1), out long shortid); r.Id = shortid; r.TradeNo = r.Id.ToString();// vo.TradeNo; r.TradeProduct = vo.ProductName; r.TradeMoney = p.Money; r.TradeStatus = 0; r.Remark = remoteUserIp; r.IP = userIp; r.City = vo.City; r.CreateTime = DateTime.Now; Service.Execute("INSERT INTO [ORDER](Id,TradeNo,TradeProduct,TradeMoney,Remark,IP,City,TradeStatus,CreateTime)VALUES(@Id,@TradeNo,@TradeProduct,@TradeMoney,@Remark,@IP,@City,@TradeStatus,@CreateTime)" , new { Id = r.Id, TradeNo = r.TradeNo, TradeProduct = r.TradeProduct, TradeMoney = r.TradeMoney, Remark = r.Remark, IP = r.IP, City = r.City, TradeStatus = r.TradeStatus, CreateTime = r.CreateTime }); money = p.Money; } if (money > 0) { return Json(Result.Success(new { TradeNo = r.Id, Money = money , MoneyYuan = money/100.0, PayQRCode = $"/PayQRCode/{money}.jpg" })); } else { return Json(Result.Fail(ResultCode.Fail)); } } /// <summary> /// 产生新支付订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult DiscardOrder([FromBody]DiscardOrderVo vo) { //先将过期的更新为过期状态 Service.Execute("UPDATE [ORDER] SET TradeStatus=3,ModifyTime=GETDATE() WHERE TradeStatus=0 AND datediff(ss,CreateTime,GETDATE())>120", null); var d = Service.QuerySingle<Order>("SELECT * FROM [ORDER] WHERE TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); if (d != null && d.TradeStatus == 0) { Service.Execute("UPDATE [ORDER] SET TradeStatus=2,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); return Json(Result.Success()); } return Json(Result.Fail()); } /// <summary> /// 查询订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult QueryOrder([FromBody]DiscardOrderVo vo) { var d = Service.QuerySingle<Order>("SELECT * FROM [ORDER] WHERE TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); if (d != null && d.TradeStatus == 1) { return Json(Result.Success()); } return Json(Result.Fail()); } [HttpPost] [AllowAnonymous] public JsonResult PayNotify([FromBody]PayNotifyVo vo) { if (vo.title.IndexOf("微信支付")>=0 && vo.money.HasValue()) { //{"title":"微信支付","time":"2019-06-19 21:45:23","money":"0.10","encrypt":"0","deviceid":"ffffffff-c818-83fb-ffff-ffffbbd87511","content":"微信支付收款0.10元(朋友到店)"} var moneyFen = Convert.ToInt32(decimal.Parse(vo.money) * 100); Service.Execute("UPDATE [ORDER] SET TradeStatus=1,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeMoney = @TradeMoney", new { TradeMoney = moneyFen }); } if (vo.title.IndexOf("微信收款助手")>=0) { //{ "title":"微信收款助手","time":"2019-06-20 22:24:54","money":"null","deviceid":"ffffffff-c818-83fb-ffff-ffffbbd87511","content":"[店员消息]收款到账0.01元"} var content = vo.content; var money = content.Substring(content.IndexOf("收款到账"), content.IndexOf("元") - content.IndexOf("收款到账")).Replace("收款到账", ""); var moneyFen = Convert.ToInt32(decimal.Parse(money) * 100); Service.Execute("UPDATE [ORDER] SET TradeStatus=1,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeMoney = @TradeMoney", new { TradeMoney = moneyFen }); } return Json(Result.Success()); } #endregion } }
下图是支付回调的发起与结果的接收示例:
细节原理请仔细往下看.....
作为一名程序员,我们或多或少都希望建立自己的个人技术网站、技术博客等等,用于记录自己的汗水点滴。
同时,如果我们希望为自己的网站增添微信扫码收款功能,用于收取一些服务费用,为个人网站提供自动化有偿服务的话,那我们有哪些方案呢?
一、注册公司,在微信公众平台申请支付权限
二、通过微信个人收款码实现个人收款接口
本文我们分析第二种方法,通过微信个人收款码实现个人收款接口。
这种方法的实现成本非常低,但也只是适用于一些个人网站,小并发量的收款服务,当然了,如果你的网站有大量用户向你支付,你还不主动去申请注册公司么。
言归正传,哪么怎么实现收款接口呢?
首先,我们看一个演示示例:
可复制链接打开体验 http://letyouknow.net/serverfarm/serverfarm-tutorial3.html
此示例是技术文章内容付费示例,用户试读部分后,点击 展开阅读更多 并且扫码支付成功后,展示全部内容。
首先,我们需要制作出一套专业的UI,用于展示收款码
一、当我们点击展开阅读更多按钮后,我们需要显示一获取二维码的示意图
二、根据预设的资费情况,从后台拉取对应的个人收款二维码,并设置收款码有效期,此示例默认2分钟。
三、设置超时失效机制,引导重新发起支付
四、预设个人收款二维码
我们需要将同一个金额照不同的收款备注或不同的金额尾数设置多个,然后保存到服务端,由前端UI的产品拉取对应的金额的二维码图片,显示给用户
五、微信收款通知 回调服务器API
我们可以用android apk 用于监控收款通知,并实时回调我们的服务器,修改用户订单的支付状态。
我们将apk安装在手机上后,当有用户扫码付款后,我们的微信APP便收到收款通知,同时,我们回调服务器。
此方案特性:
这种实现办法适合小额,支付频率不高的场景。比如针对 1元这个金额生成了100个有不同收款备注信息的二维码,那么也就是说5分钟内最多只能有100个人同时支付,1分钟内20个同时支付。对于一些小网站可以满足需求。
此方案的核心是设计思想,另外就是我们如何实时获取到收款通知。我们用android apk 用于监控收款通知,并实时回调我们的服务器,修改用户订单的支付状态。有关实时获取收款通知的实现方法,我们后续将另起一个篇章重点介绍。
六、接口定义示例
{"title":"微信支付","time":"2020-05-08 23:34:11","money":"0.80","deviceid":"mydevice","content":"[2条]微信支付: 微信支付收款0.80元(朋友到店)"}
七、api源码的说明
下载源码后,用vs2017or2019打开项目,F5运行即可,
http://localhost:54914/Demo.html
using System; using System.Linq; using System.Linq.Expressions; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Lyn.Pay.Api.Domain; using Lyn.Pay.Api.Utils; using Lyn.Pay.Api.DAL; namespace Lyn.Pay.Api.Controllers { /// <summary> /// 控制器 /// </summary> [Route("v1/[controller]/[action]")] public class PayController : Controller { //https://github.com/stulzq/snowflake-net private static Snowflake.Core.IdWorker worker = new Snowflake.Core.IdWorker(1, 1); public PayController() { } #region 业务应用 [HttpPost] [AllowAnonymous] public JsonResult QueryAlreadyBuy([FromBody]AddOrderVo vo) { var remoteUserIp = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); var userIp = vo.IP.HasValue() ? vo.IP : remoteUserIp; //检查这个IP是否已经购买过此文章了 var alreadyBuy = Service.QueryAny("SELECT 1 FROM [ORDER] d WHERE d.TradeProduct = @TradeProduct AND d.IP = @IP AND d.TradeStatus=1 ", new { TradeProduct = vo.ProductName, IP = userIp }); if (alreadyBuy) { return Json(Result.Fail(ResultCode.AlreadyBuy)); } else { return Json(Result.Fail(ResultCode.Fail)); } } /// <summary> /// 产生新支付订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult AddOrder([FromBody]AddOrderVo vo) { var remoteUserIp = HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); var userIp = vo.IP.HasValue() ? vo.IP : remoteUserIp; //检查这个IP是否已经购买过此文章了 var alreadyBuy = Service.QueryAny("SELECT 1 FROM [ORDER] d WHERE d.TradeProduct = @TradeProduct AND d.IP = @IP AND d.TradeStatus=1 ", new { TradeProduct = vo.ProductName, IP = userIp }); if (alreadyBuy) { return Json(Result.Fail(ResultCode.AlreadyBuy)); } var money = 0; var sqlGetValidMoney = @" SELECT TOP 1 * FROM Product p WHERE (p.[NAME]=@ProductName OR p.[NAME]='Gobal') AND NOT EXISTS( SELECT 1 FROM [ORDER] d WHERE (d.TradeProduct = p.[NAME] OR p.[NAME]='Gobal') AND d.TradeMoney = p.Money AND d.TradeStatus=0 ) ORDER BY p.IsGobal ASC , p.Money ASC "; var r = new Order(); //先将过期的更新为过期状态 Service.Execute("UPDATE [ORDER] SET TradeStatus=3,ModifyTime=GETDATE() WHERE TradeStatus=0 AND datediff(ss,CreateTime,GETDATE())>120", null); var p = Service.QuerySingle<Product>(sqlGetValidMoney, new { ProductName = vo.ProductName }); if (p != null) { r.Id = worker.NextId(); long.TryParse(r.Id.ToString().Substring(1), out long shortid); r.Id = shortid; r.TradeNo = r.Id.ToString();// vo.TradeNo; r.TradeProduct = vo.ProductName; r.TradeMoney = p.Money; r.TradeStatus = 0; r.Remark = remoteUserIp; r.IP = userIp; r.City = vo.City; r.CreateTime = DateTime.Now; Service.Execute("INSERT INTO [ORDER](Id,TradeNo,TradeProduct,TradeMoney,Remark,IP,City,TradeStatus,CreateTime)VALUES(@Id,@TradeNo,@TradeProduct,@TradeMoney,@Remark,@IP,@City,@TradeStatus,@CreateTime)" , new { Id = r.Id, TradeNo = r.TradeNo, TradeProduct = r.TradeProduct, TradeMoney = r.TradeMoney, Remark = r.Remark, IP = r.IP, City = r.City, TradeStatus = r.TradeStatus, CreateTime = r.CreateTime }); money = p.Money; } if (money > 0) { return Json(Result.Success(new { TradeNo = r.Id, Money = money , MoneyYuan = money/100.0, PayQRCode = $"/PayQRCode/{money}.jpg" })); } else { return Json(Result.Fail(ResultCode.Fail)); } } /// <summary> /// 产生新支付订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult DiscardOrder([FromBody]DiscardOrderVo vo) { //先将过期的更新为过期状态 Service.Execute("UPDATE [ORDER] SET TradeStatus=3,ModifyTime=GETDATE() WHERE TradeStatus=0 AND datediff(ss,CreateTime,GETDATE())>120", null); var d = Service.QuerySingle<Order>("SELECT * FROM [ORDER] WHERE TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); if (d != null && d.TradeStatus == 0) { Service.Execute("UPDATE [ORDER] SET TradeStatus=2,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); return Json(Result.Success()); } return Json(Result.Fail()); } /// <summary> /// 查询订单 /// </summary> /// <param name="vo">订单</param> /// <returns>列表数据</returns> [HttpPost] [AllowAnonymous] public JsonResult QueryOrder([FromBody]DiscardOrderVo vo) { var d = Service.QuerySingle<Order>("SELECT * FROM [ORDER] WHERE TradeNo = @TradeNo", new { TradeNo = vo.TradeNo }); if (d != null && d.TradeStatus == 1) { return Json(Result.Success()); } return Json(Result.Fail()); } [HttpPost] [AllowAnonymous] public JsonResult PayNotify([FromBody]PayNotifyVo vo) { if (vo.title.IndexOf("微信支付")>=0 && vo.money.HasValue()) { //{"title":"微信支付","time":"2019-06-19 21:45:23","money":"0.10","encrypt":"0","deviceid":"ffffffff-c818-83fb-ffff-ffffbbd87511","content":"微信支付收款0.10元(朋友到店)"} var moneyFen = Convert.ToInt32(decimal.Parse(vo.money) * 100); Service.Execute("UPDATE [ORDER] SET TradeStatus=1,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeMoney = @TradeMoney", new { TradeMoney = moneyFen }); } if (vo.title.IndexOf("微信收款助手")>=0) { //{ "title":"微信收款助手","time":"2019-06-20 22:24:54","money":"null","deviceid":"ffffffff-c818-83fb-ffff-ffffbbd87511","content":"[店员消息]收款到账0.01元"} var content = vo.content; var money = content.Substring(content.IndexOf("收款到账"), content.IndexOf("元") - content.IndexOf("收款到账")).Replace("收款到账", ""); var moneyFen = Convert.ToInt32(decimal.Parse(money) * 100); Service.Execute("UPDATE [ORDER] SET TradeStatus=1,ModifyTime=GETDATE() WHERE TradeStatus=0 AND TradeMoney = @TradeMoney", new { TradeMoney = moneyFen }); } return Json(Result.Success()); } #endregion } }
八、源码ZIP仅供用于学习与研究用途
一、主源码-服务端Api (基于.net core webapi,用于处理支付逻辑)
二、前端基于boostrap的发起二维码扫码界面UI
三、Android Apk 源码 (java,用于监控手机消息)
四、apk (编译完成可用的apk, 如果你不熟悉android,可以直接用这个已经编译好的apk )
九、如何获取源码?
扫码关注的dotNet框架学苑公众号,直接在公众号文章中付费阅读对应的文章,文章尾部有源码压缩包。