微信小程序支付
发布于 15 天前 作者 yan 31 次浏览

1、背景

因业务需要接入微信支付功能(客户端是微信小程序),因公司服务器版本较低,服务端采用.Net Framework 版本(并采用盛派微信SDK)

2、文档地址

1)小程序支付:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

2)小程序调起支付API:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5

3)支付下单:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

4)发起微信支付:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html

5)盛派SDK:http://sdk.weixin.senparc.com/

3、代码实现

小程序支付的交互图如下:

3.1 服务端实现

1)引入包:

2)注册:在 Global.asmx 里进行注册

/*  
 
            * CO2NET 全局注册开始 
 
            * 建议按照以下顺序进行注册 
 
            */ 
 


 
           /* 
 
            * CO2NET 是从 Senparc.Weixin 分离的底层公共基础模块,经过了长达 6 年的迭代优化。 
 
            * 关于 CO2NET 在所有项目中的通用设置可参考 CO2NET 的 Sample: 
 
            * https://github.com/Senparc/Senparc.CO2NET/blob/master/Sample/Senparc.CO2NET.Sample.netcore/Startup.cs 
 
            */ 
 


 


 
           //设置全局 Debug 状态 
 
           var isGLobalDebug = true; 
 
           //全局设置参数,将被储存到 Senparc.CO2NET.Config.SenparcSetting 
 
           var senparcSetting = SenparcSetting.BuildFromWebConfig(isGLobalDebug); 
 
           //也可以通过这种方法在程序任意位置设置全局 Debug 状态: 
 
           //Senparc.CO2NET.Config.IsDebug = isGLobalDebug; 
 


 


 
           //CO2NET 全局注册,必须!! 
 
           IRegisterService register = RegisterService.Start(senparcSetting).UseSenparcGlobal(); 
 


 
           #region  全局缓存配置(按需) 
 


 
           #region 配置和使用 Redis          -- DPBMARK Redis 
 


 
           //配置全局使用Redis缓存(按需,独立) 
 
           var redisConfigurationStr = senparcSetting.Cache_Redis_Configuration; 
 
           var useRedis = !string.IsNullOrEmpty(redisConfigurationStr) && redisConfigurationStr != "Redis配置"; 
 
           if (useRedis)//这里为了方便不同环境的开发者进行配置,做成了判断的方式,实际开发环境一般是确定的,这里的if条件可以忽略 
 
           { 
 
               /* 说明: 
 
                * 1、Redis 的连接字符串信息会从 Config.SenparcSetting.Cache_Redis_Configuration 自动获取并注册,如不需要修改,下方方法可以忽略 
 
               /* 2、如需手动修改,可以通过下方 SetConfigurationOption 方法手动设置 Redis 链接信息(仅修改配置,不立即启用) 
 
                */ 
 
               Senparc.CO2NET.Cache.Redis.Register.SetConfigurationOption(redisConfigurationStr); 
 


 
               //以下会立即将全局缓存设置为 Redis 
 
               Senparc.CO2NET.Cache.Redis.Register.UseKeyValueRedisNow();//键值对缓存策略(推荐) 
 
               //Senparc.CO2NET.Cache.Redis.Register.UseHashRedisNow();//HashSet储存格式的缓存策略 
 


 
               //也可以通过以下方式自定义当前需要启用的缓存策略 
 
               //CacheStrategyFactory.RegisterObjectCacheStrategy(() => RedisObjectCacheStrategy.Instance);//键值对 
 
               //CacheStrategyFactory.RegisterObjectCacheStrategy(() => RedisHashSetObjectCacheStrategy.Instance);//HashSet 
 
           } 
 
           //如果这里不进行Redis缓存启用,则目前还是默认使用内存缓存  
 


 
           #endregion                        // DPBMARK_END 
 


 
           #region 配置和使用 Memcached      -- DPBMARK Memcached 
 


 
           //配置Memcached缓存(按需,独立) 
 
           var memcachedConfigurationStr = senparcSetting.Cache_Memcached_Configuration; 
 
           var useMemcached = !string.IsNullOrEmpty(memcachedConfigurationStr) && memcachedConfigurationStr != "Memcached配置"; 
 


 
           if (useMemcached) //这里为了方便不同环境的开发者进行配置,做成了判断的方式,实际开发环境一般是确定的,这里的if条件可以忽略 
 
           { 
 
               /* 说明: 
 
               * 1、Memcached 的连接字符串信息会从 Config.SenparcSetting.Cache_Memcached_Configuration 自动获取并注册,如不需要修改,下方方法可以忽略 
 
              /* 2、如需手动修改,可以通过下方 SetConfigurationOption 方法手动设置 Memcached 链接信息(仅修改配置,不立即启用) 
 
               */ 
 
               Senparc.CO2NET.Cache.Memcached.Register.SetConfigurationOption(memcachedConfigurationStr); 
 


 
               //以下会立即将全局缓存设置为 Memcached 
 
               Senparc.CO2NET.Cache.Memcached.Register.UseMemcachedNow(); 
 


 
               //也可以通过以下方式自定义当前需要启用的缓存策略 
 
               CacheStrategyFactory.RegisterObjectCacheStrategy(() => MemcachedObjectCacheStrategy.Instance); 
 
           } 
 


 
           #endregion                        //  DPBMARK_END 
 


 
           #endregion 
 


 
           #region 注册日志(按需,建议) 
 


 
           register.RegisterTraceLog(ConfigWeixinTraceLog);//配置TraceLog 
 


 
           #endregion 
 


 


 
           /* 微信配置开始 
 
            * 建议按照以下顺序进行注册 
 
            */ 
 


 
           //设置微信 Debug 状态 
 
           var isWeixinDebug = true; 
 
           //全局设置参数,将被储存到 Senparc.Weixin.Config.SenparcWeixinSetting 
 
           var senparcWeixinSetting = SenparcWeixinSetting.BuildFromWebConfig(isWeixinDebug); 
 
           //也可以通过这种方法在程序任意位置设置微信的 Debug 状态: 
 
           //Senparc.Weixin.Config.IsDebug = isWeixinDebug; 
 


 
           //微信全局注册,必须!! 
 
           register.UseSenparcWeixin(senparcWeixinSetting, senparcSetting) 
 


 


 
           #region 注册公众号或小程序(按需) 
 


 
               //注册公众号(可注册多个)                                                -- DPBMARK MP 
 
               .RegisterMpAccount(senparcWeixinSetting, "【盛派网络小助手】公众号")// DPBMARK_END 
 


 


 
               //注册多个公众号或小程序(可注册多个)                                        -- DPBMARK MiniProgram 
 
               .RegisterWxOpenAccount(senparcWeixinSetting, "小程序名称")// DPBMARK_END 
 


 
               //除此以外,仍然可以在程序任意地方注册公众号或小程序: 
 
               //AccessTokenContainer.Register(appId, appSecret, name);//命名空间:Senparc.Weixin.MP.Containers 
 
           #endregion 
 


 
                                   // DPBMARK_END 
 


 
           #region 注册微信支付(按需)        -- DPBMARK TenPay 
 


 
               //注册旧微信支付版本(V2)(可注册多个) 
 
               .RegisterTenpayOld(senparcWeixinSetting, "标记名称")//这里的 name 和第一个 RegisterMpAccount() 中的一致,会被记录到同一个 SenparcWeixinSettingItem 对象中 
 


 
               //注册最新微信支付版本(V3)(可注册多个) 
 
               .RegisterTenpayV3(senparcWeixinSetting, "标记名称")//记录到同一个 SenparcWeixinSettingItem 对象中 
 


 
           #endregion                          // DPBMARK_END 
 


 
                                // DPBMARK_END 
 


 
           ; 
 


 
           /* 微信配置结束 */ 
/// <summary> 
 
       /// 配置微信跟踪日志 
 
       /// </summary> 
 
       private void ConfigWeixinTraceLog() 
 
       { 
 
           //Senparc.CO2NET.Config.IsDebug = false; 
 


 
           //这里设为Debug状态时,/App_Data/WeixinTraceLog/目录下会生成日志文件记录所有的API请求日志,正式发布版本建议关闭 
 
           Senparc.Weixin.WeixinTrace.SendCustomLog("系统日志", "系统启动");//只在Senparc.Weixin.Config.IsDebug = true的情况下生效 
 


 
           //自定义日志记录回调 
 
           Senparc.Weixin.WeixinTrace.OnLogFunc = () => 
 
           { 
 
               //加入每次触发Log后需要执行的代码 
 
           }; 
 


 
           //当发生基于WeixinException的异常时触发 
 
           Senparc.Weixin.WeixinTrace.OnWeixinExceptionFunc = ex => 
 
           { 
 
               //加入每次触发WeixinExceptionLog后需要执行的代码 
 


 
               //发送模板消息给管理员 
 
               var eventService = new EventService(); 
 
               eventService.ConfigOnWeixinExceptionFunc(ex); 
 
           }; 
 
       } 

3)统一下单:

*** 因实际代码设计隐私问题,因此剔除了,如有问题请联系我。

public async Task<ActionResult> GetWxOpenPrepayid(string sessionId,string cost) 
 
       { 
 
            
 
           try 
 
           { 
 
               var sessionBag = SessionContainer.GetSession(sessionId); 
 
               var openId = sessionBag.OpenId; 
 


 
               //生成订单10位序列号,此处用时间和随机数生成,商户根据自己调整,保证唯一 
 
               var sp_billno = string.Format("{0}{1}{2}", "商户号" /*10位*/, SystemTime.Now.ToString("yyyyMMddHHmmss"), 
 
                       TenPayV3Util.BuildRandomStr(6)); 
 


 
               var timeStamp = TenPayV3Util.GetTimestamp(); 
 
               var nonceStr = TenPayV3Util.GetNoncestr(); 
 
               var price = Convert.ToInt32(Convert.ToDecimal(cost) * 100);//单位:分 
 
              
 
               var xmlDataInfo = new TenPayV3UnifiedorderRequestData( 
 
                   "小程序AppId", "小程序商户号", body, sp_billno, 
 
                   price, "127.0.0.1", "回调地址", TenPayV3Type.JSAPI, openId, "小程序商户key", nonceStr, attach: "附加数据"); 
 


 
               var result = TenPayV3.Unifiedorder(xmlDataInfo);//调用统一订单接口 
 


 
               //  WeixinTrace.SendCustomLog("统一订单接口调用结束", "请求:" + xmlDataInfo.ToJson() + "\r\n\r\n返回结果:" + result.ToJson()); 
 


 
               var packageStr = "prepay_id=" + result.prepay_id; 
 


 
               //var cacheStrategy = CacheStrategyFactory.GetObjectCacheStrategyInstance(); 
 
               //cacheStrategy.Set($"WxOpenUnifiedorderRequestData-{openId}", xmlDataInfo, TimeSpan.FromDays(4));//3天内可以发送模板消息 
 
               //cacheStrategy.Set($"WxOpenUnifiedorderResultData-{openId}", result, TimeSpan.FromDays(4));//3天内可以发送模板消息 
 
               return ToSuccess(data: new 
 
               { 
 
                   result.prepay_id, 
 
                   appId = "小程序AppId", 
 
                   timeStamp, 
 
                   nonceStr, 
 
                   tradeId = sp_billno, 
 
                   package = packageStr, 
 
                   //signType = "MD5", 
 
                   paySign = TenPayV3.GetJsPaySign("小程序AppId", timeStamp, nonceStr, packageStr, "小程序支付key") 
 
               }); 
 
           } 
 
           catch (Exception ex) 
 
           { 
 
               return ToError(msg: ex.Message); 
 
           } 

*** 这个接口 在什么地方用呢(A:在小程序客户端调用微信支付的时候需要用到【而且是必须的】,也就是下午的 小程序客户端调用:wx.requestPayment 的时候)

4)回调地址(支付结果通知):

*** 需要注意的地方已 标出了。(值得注意的就是这个地址必须是外网能访问到的)

代码实现:

public ActionResult InquiryNotify() 
 
       { 
 
           try 
 
           { 
 
               var resHandler = new ResponseHandler(null); 
 
               var return_code = resHandler.GetParameter("return_code"); 
 


 
               var return_msg = resHandler.GetParameter("return_msg"); 
 
               if (return_code != "SUCCESS") return PayResultNotify(); 
 
               var attach = resHandler.GetParameter("attach"); 
 
               if (string.IsNullOrEmpty(attach)) return PayResultNotify(); 
 


 
               //这里请签名验证,并校验返回的订单金额是否与商户侧的订单金额一致 
 


 
               var cost = resHandler.GetParameter("total_fee");//金额 
 
               var tradeId = resHandler.GetParameter("out_trade_no");//商户订单号 
 
               //验证支付状态与金额 
 
       
 
             //业务逻辑处理,采用异步回调 
 
               return PayResultNotify("SUCCESS", "OK"); 
 
           } 
 
           catch (Exception ex) 
 
           {         
 
               return PayResultNotify(); 
 
           } 
 
       } 
 


 
private ActionResult PayResultNotify(string returnCode = "FAIL", string returnMsg = "wrong") 
 
       { 
 
           var xml = $@"<xml> 
 
                      <return_code><![CDATA[{returnCode}]]></return_code> 
 
                      <return_msg><![CDATA[{returnMsg}]]></return_msg> 
 
                      </xml>"; 
 
           return Content(xml, "text/xml"); 
 
       } 

3.2、客户端实现(小程序)

/** 
 
  * 支付 
 
  */ 
 
 bindPay: function(e) { 
 
   let that = this 
 
   let url = wx.getStorageSync(config.domainName) 
 
   url += '服务端接口地址' 
 
   let data = { 
 
     sessionId: wx.getStorageSync(config.sessionId), 
 
     cost: "支付金额", 
 
   } 
 
   wxRequest.getRequest(url, data).then((res) => { 
 
     if (res.data.success) { 
 
       let payInfo = res.data.data 
 
       wx.requestPayment({ 
 
         timeStamp: payInfo.timeStamp, 
 
         nonceStr: payInfo.nonceStr, 
 
         package: payInfo.package, 
 
         paySign: payInfo.paySign, 
 
         signType: 'MD5', 
 
         success: function(res) { 
 
         /*这里一定要注意,这里的方法有可能会不执行(支付完成后 用户在不点击 支付完成页面底部的“完成”按钮时 这里的方法是不会执行到的,因此这里请不要写业务逻辑代码)*/ 
 
           that.queryPayResult(payInfo.tradeId) 
 
         }, 
 
         fail: function(res) { 
 
        /*用户取消支付后 会执行这里的方面*/ 
 
           that.payCancel(payInfo.tradeId) 
 
         } 
 
       }) 
 
     } else { 
 
       app.showToast(res.data.msg) 
 
     } 
 
   }) 
 
 } 

至此,小程序的支付就完成了。感谢 盛派网络 对微信相关接口的封装,用起来很方便

说明:

**    ** 1:如有疑问,可以与我取得联系

2:已官方文档为主,很有可能过些时间后文档及SDK会发生变化

3:官方文档已在上文中给出

4:文章首发于公众号

5:服务端使用的小程序包是盛派的SDK(https://weixin.senparc.com)GitHub:https://github.com/Senparc/WeiXinMPSDK

6:.Net Core 类似,有何疑问也可以与我取得联系

回到顶部