前言 如果我們希望為自己的網站增添微信掃碼收款功能,用於收取一些服務費用,為個人網站提供自動化有償服務的話,那我們有哪些方案呢? 首先,我們先看下效果,以下是服務端的收款二維碼的發起示例演示: 其次,我們再看看手機端 微信掃碼支付的演示: 我們手機端會將收款的消息推送到伺服器API。其中介面信息定 ...
前言 如果我們希望為自己的網站增添微信掃碼收款功能,用於收取一些服務費用,為個人網站提供自動化有償服務的話,那我們有哪些方案呢?
首先,我們先看下效果,以下是服務端的收款二維碼的發起示例演示:
其次,我們再看看手機端 微信掃碼支付的演示:
我們手機端會將收款的消息推送到伺服器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框架學苑公眾號,直接在公眾號文章中付費閱讀對應的文章,文章尾部有源碼壓縮包。