欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微信小程序支付

程序员文章站 2022-06-13 11:28:31
微信小程序支付 1、背景 因业务需要接入微信支付功能(客户端是微信小程序),因公司服务器版本较低,服务端采用.Net Framework 版本(并采用盛派微信SDK) 2、文档地址 1)小程序支付:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api. ......

微信小程序支付

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 类似,有何疑问也可以与我取得联系

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

  微信小程序支付