微信小程序支付

1、背景

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

2、文档地址

  1)小程序支付:

  2)小程序调起支付api:

  3)支付下单:

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

  5)盛派sdk:

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()github:https://github.com/senparc/weixinmpsdk

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

  如果对您有帮助,点个推荐呗!