小程序支付

摘要:
=undefined){wx.getUserInfo;}else{console.info;}},fail:function{console.info;console.log;}})}}})},wxpay{this.getOpenId();vard={gfee:'1',gname:'999',openId:wx.getStorageSync};varurl=this.data.url+'wxPay/api/order';wx.request服务端主要完成预下单和回调通知这两个动作:这里要注意的就是在预下单后返回给小程序的字段中package字段由预下单返回的prepay_id组成,类型下面这种:字段名:package字段值:prepay_id=wx261422495332917f0574b9a23c5ca40000这里借用了官网提供的demo中的一个帮助类来完成,这里一定要把里边的签名方式改成MD5帮助类:publicclassWxPayApi{publicstaticWxPayDataMicropay{stringurl="https://api.mch.weixin.qq.com/pay/micropay";//检测必填参数if(!inputObj.IsSet){thrownewWxPayException("提交被扫支付API接口中,缺少必填参数body!

一、背景

​ 最近一朋友咨询项目小程序需要调起微信支付,所以就研究了下,一开始拿到手的信息有:

  • 公众平台appId,appSecret 有了,服务器域名,业务域名(我配置打开的,可能用不到没管了)有了
  • 公众平台的微信支付模块中关联了商户号,并且微信支付已申请成功
  • 微信商户平台已获得商户秘钥,后面我加入了接口安全域名的配置(应该叫这名不太记得了)
    image-20200927224350579
    image-20200927224350579
    image-20200927224350579

二、实现

目前能找的最权威资料就是在官网开发平台,入口https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

截取下来支付的主要动作:

1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API

2、商户server调用支付统一下单,api参见公共api【统一下单API

3、商户server调用再次签名,api参见公共api【再次签名

4、商户server接收支付通知,api参见公共api【支付结果通知API

5、商户server查询支付结果,api参见公共api【查询订单API

这其中采坑最多的是字段大小写的问题,还有支付签名验证失败的问题,以及官网提供JSAPI的demo(.net 版本),采坑:

1、统一下单接口返回的数据字段大小写跟调起支付接口的字段大小写区别大,没注意坑惨了

2、如果遇到签名验证失败的情况,可参考平台提供的接口签名校验工具

3、demo中的签名算法HMAC-SHA256 但小程序要MD5才行

下面附上代码

小程序端

getOpenId:function(){
    wx.login({
      success: function (res) {
        var d = {
          appid: that.data.appId, //AppID
          secret: that.data.appSecret,//secret密钥
          grant_type: 'authorization_code',
          js_code: res.code
        };
        if (res.code) {
          wx.request({//getOpenid
            url: 'https://api.weixin.qq.com/sns/jscode2session',
            data: d,
            header: { 'content-type': 'application/json' },
            success: function (res) {
              console.dir(res)
              var openid = res.data.openid; //登录之后返回的openid
              console.log(openid + '我的openid')
              wx.setStorageSync('openid', openid) //储存openid
              if (openid != null & openid != undefined) {
                wx.getUserInfo({
                  success: function (res) {

                  },
                  fail: function (res) {
                    //console.info('用户拒绝授权');
                  }
                });
              } else {
                console.info('获取用户openid失败');
              }
            },
            fail: function (res) {
              console.info('获取用户openid失败');
              console.log(error);
            }
          })
        }
      }
    })
  },
  wxpay(evt){
    this.getOpenId();
    var d = {
      gfee: '1',
      gname: '999',
      openId: wx.getStorageSync('openid')
    };
    var url = this.data.url +'wxPay/api/order';
      wx.request({
        url: url,
        method: "POST",
        data: d,
        success: function (res){
           //发起支付
           wx.requestPayment({
             'timeStamp': res.data.timeStamp,
             'nonceStr': res.data.nonce_str,
             'package': res.data.package,
             'signType': 'MD5',
             'paySign': res.data.paySign,
             success: function (res) {
               console.info(res)
             },
             fail: function (res) {
               console.info(res)
             },
             complete: function (res) {
               console.info(res)
             }
           })
         },
         fail:function(ret){
            console.dir(ret)
         },
         complete:function(ret){
           console.dir(ret)
         }
      })

服务端主要完成预下单和回调通知这两个动作:

这里要注意的就是在预下单后返回给小程序的字段中package字段由预下单返回的prepay_id组成,类型下面这种:

字段名:package 字段值:prepay_id=wx261422495332917f0574b9a23c5ca40000

这里借用了官网提供的demo中的一个帮助类来完成,这里一定要把里边的签名方式改成MD5

帮助类:

public class WxPayApi
{
    public static WxPayData Micropay(WxPayData inputObj, int timeOut = 10)
    {
        string url = "https://api.mch.weixin.qq.com/pay/micropay";
        //检测必填参数
        if (!inputObj.IsSet("body"))
        {
        ​    throw new WxPayException("提交被扫支付API接口中,缺少必填参数body!");
        }
        else if (!inputObj.IsSet("out_trade_no"))
        {
        ​    throw new WxPayException("提交被扫支付API接口中,缺少必填参数out_trade_no!");
        }
        else if (!inputObj.IsSet("total_fee"))
        {
        ​    throw new WxPayException("提交被扫支付API接口中,缺少必填参数total_fee!");
        }
        else if (!inputObj.IsSet("auth_code"))
        {
        ​    throw new WxPayException("提交被扫支付API接口中,缺少必填参数auth_code!");
        }
        
        inputObj.SetValue("spbill_create_ip", WxPayConfig.GetConfig().GetIp());//终端ip
        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
        string xml = inputObj.ToXml();

        var start = DateTime.Now;//请求开始时间

        Log.Debug("WxPayApi", "MicroPay request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
        Log.Debug("WxPayApi", "MicroPay response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时

        //将xml格式的结果转换为对象以返回
        WxPayData result = new WxPayData();
        result.FromXml(response);

        ReportCostTime(url, timeCost, result);//测速上报

        return result;
    }

    /**
    *    
    * 查询订单
    * @param WxPayData inputObj 提交给查询订单API的参数
    * @param int timeOut 超时时间
    * @throws WxPayException
    * @return 成功时返回订单查询结果,其他抛异常
    */
    public static WxPayData OrderQuery(WxPayData inputObj, int timeOut = 6)
    {
        string url = "https://api.mch.weixin.qq.com/pay/orderquery";
        //检测必填参数
        if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
        {
            throw new WxPayException("订单查询接口中,out_trade_no、transaction_id至少填一个!");
        }

        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("nonce_str", WxPayApi.GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
        string xml = inputObj.ToXml();

        var start = DateTime.Now;

        Log.Debug("WxPayApi", "OrderQuery request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口提交数据
        Log.Debug("WxPayApi", "OrderQuery response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时

        //将xml格式的数据转化为对象以返回
        WxPayData result = new WxPayData();
        result.FromXml(response);

        ReportCostTime(url, timeCost, result);//测速上报

        return result;
    }
    /**
    * 
    * 撤销订单API接口
    * @param WxPayData inputObj 提交给撤销订单API接口的参数,out_trade_no和transaction_id必填一个
    * @param int timeOut 接口超时时间
    * @throws WxPayException
    * @return 成功时返回API调用结果,其他抛异常
    */
    public static WxPayData Reverse(WxPayData inputObj, int timeOut = 6)
    {
        string url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
        //检测必填参数
        if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
        {
            throw new WxPayException("撤销订单API接口中,参数out_trade_no和transaction_id必须填写一个!");
        }

        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
        string xml = inputObj.ToXml();

        var start = DateTime.Now;//请求开始时间

        Log.Debug("WxPayApi", "Reverse request : " + xml);

        string response = HttpService.Post(xml, url, true, timeOut);

        Log.Debug("WxPayApi", "Reverse response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);

        WxPayData result = new WxPayData();
        result.FromXml(response);

        ReportCostTime(url, timeCost, result);//测速上报

        return result;
    } 

    /**
    * 
    * 申请退款
    * @param WxPayData inputObj 提交给申请退款API的参数
    * @param int timeOut 超时时间
    * @throws WxPayException
    * @return 成功时返回接口调用结果,其他抛异常
    */
    public static WxPayData Refund(WxPayData inputObj, int timeOut = 6)
    {
        string url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        //检测必填参数
        if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id"))
        {
            throw new WxPayException("退款申请接口中,out_trade_no、transaction_id至少填一个!");
        }
        else if (!inputObj.IsSet("out_refund_no"))
        {
            throw new WxPayException("退款申请接口中,缺少必填参数out_refund_no!");
        }
        else if (!inputObj.IsSet("total_fee"))
        {
            throw new WxPayException("退款申请接口中,缺少必填参数total_fee!");
        }
        else if (!inputObj.IsSet("refund_fee"))
        {
            throw new WxPayException("退款申请接口中,缺少必填参数refund_fee!");
        }
        else if (!inputObj.IsSet("op_user_id"))
        {
            throw new WxPayException("退款申请接口中,缺少必填参数op_user_id!");
        }

        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
        
        string xml = inputObj.ToXml();
        var start = DateTime.Now;

        Log.Debug("WxPayApi", "Refund request : " + xml);
        string response = HttpService.Post(xml, url, true, timeOut);//调用HTTP通信接口提交数据到API
        Log.Debug("WxPayApi", "Refund response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时

        //将xml格式的结果转换为对象以返回
        WxPayData result = new WxPayData();
        result.FromXml(response);

        ReportCostTime(url, timeCost, result);//测速上报

        return result;
    }

    /**
    * 
    * 查询退款
    * 提交退款申请后,通过该接口查询退款状态。退款有一定延时,
    * 用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
    * out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
    * @param WxPayData inputObj 提交给查询退款API的参数
    * @param int timeOut 接口超时时间
    * @throws WxPayException
    * @return 成功时返回,其他抛异常
    */
    public static WxPayData RefundQuery(WxPayData inputObj, int timeOut = 6)
    {
	    string url = "https://api.mch.weixin.qq.com/pay/refundquery";
	    //检测必填参数
	    if(!inputObj.IsSet("out_refund_no") && !inputObj.IsSet("out_trade_no") &&
		    !inputObj.IsSet("transaction_id") && !inputObj.IsSet("refund_id"))
        {
		    throw new WxPayException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!");
	    }

	    inputObj.SetValue("appid",WxPayConfig.GetConfig().GetAppID());//公众账号ID
	    inputObj.SetValue("mch_id",WxPayConfig.GetConfig().GetMchID());//商户号
	    inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名

	    string xml = inputObj.ToXml();
	
	    var start = DateTime.Now;//请求开始时间

        Log.Debug("WxPayApi", "RefundQuery request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
        Log.Debug("WxPayApi", "RefundQuery response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end-start).TotalMilliseconds);//获得接口耗时

        //将xml格式的结果转换为对象以返回
	    WxPayData result = new WxPayData();
        result.FromXml(response);

	    ReportCostTime(url, timeCost, result);//测速上报
	
	    return result;
    }

    /**
    * 下载对账单
    * @param WxPayData inputObj 提交给下载对账单API的参数
    * @param int timeOut 接口超时时间
    * @throws WxPayException
    * @return 成功时返回,其他抛异常
    */
    public static WxPayData DownloadBill(WxPayData inputObj, int timeOut = 6)
    {
        string url = "https://api.mch.weixin.qq.com/pay/downloadbill";
        //检测必填参数
        if (!inputObj.IsSet("bill_date"))
        {
            throw new WxPayException("对账单接口中,缺少必填参数bill_date!");
        }

        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名

        string xml = inputObj.ToXml();

        Log.Debug("WxPayApi", "DownloadBill request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API
        Log.Debug("WxPayApi", "DownloadBill result : " + response);

        WxPayData result = new WxPayData();
        //若接口调用失败会返回xml格式的结果
        if (response.Substring(0, 5) == "<xml>")
        {
            result.FromXml(response);
        }
        //接口调用成功则返回非xml格式的数据
        else
            result.SetValue("result", response);

        return result;
    }

    /**
    * 
    * 转换短链接
    * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX),
    * 减小二维码数据量,提升扫描速度和精确度。
    * @param WxPayData inputObj 提交给转换短连接API的参数
    * @param int timeOut 接口超时时间
    * @throws WxPayException
    * @return 成功时返回,其他抛异常
    */
    public static WxPayData ShortUrl(WxPayData inputObj, int timeOut = 6)
    {
	    string url = "https://api.mch.weixin.qq.com/tools/shorturl";
	    //检测必填参数
	    if(!inputObj.IsSet("long_url"))
        {
		    throw new WxPayException("需要转换的URL,签名用原串,传输需URL encode!");
	    }

	    inputObj.SetValue("appid",WxPayConfig.GetConfig().GetAppID());//公众账号ID
	    inputObj.SetValue("mch_id",WxPayConfig.GetConfig().GetMchID());//商户号
	    inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串	
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
	    string xml = inputObj.ToXml();
	
	    var start = DateTime.Now;//请求开始时间

        Log.Debug("WxPayApi", "ShortUrl request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);
        Log.Debug("WxPayApi", "ShortUrl response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);

        WxPayData result = new WxPayData();
        result.FromXml(response);
		ReportCostTime(url, timeCost, result);//测速上报
	
	    return result;
    }

    /**
    * 
    * 统一下单
    * @param WxPaydata inputObj 提交给统一下单API的参数
    * @param int timeOut 超时时间
    * @throws WxPayException
    * @return 成功时返回,其他抛异常
    */
    public static WxPayData UnifiedOrder(WxPayData inputObj, int timeOut = 6)
    {
        string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        //检测必填参数
        if (!inputObj.IsSet("out_trade_no"))
        {
            throw new WxPayException("缺少统一支付接口必填参数out_trade_no!");
        }
        else if (!inputObj.IsSet("body"))
        {
            throw new WxPayException("缺少统一支付接口必填参数body!");
        }
        else if (!inputObj.IsSet("total_fee"))
        {
            throw new WxPayException("缺少统一支付接口必填参数total_fee!");
        }
        else if (!inputObj.IsSet("trade_type"))
        {
            throw new WxPayException("缺少统一支付接口必填参数trade_type!");
        }

        //关联参数
        if (inputObj.GetValue("trade_type").ToString() == "JSAPI" && !inputObj.IsSet("openid"))
        {
            throw new WxPayException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
        }
        if (inputObj.GetValue("trade_type").ToString() == "NATIVE" && !inputObj.IsSet("product_id"))
        {
            throw new WxPayException("统一支付接口中,缺少必填参数product_id!trade_type为JSAPI时,product_id为必填参数!");
        }

        //异步通知url未设置,则使用配置文件中的url
        if (!inputObj.IsSet("notify_url"))
        {
            inputObj.SetValue("notify_url", WxPayConfig.GetConfig().GetNotifyUrl());//异步通知url
        }

        inputObj.SetValue("appid", WxPayConfig.GetConfig().GetAppID());//公众账号ID
        inputObj.SetValue("mch_id", WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("spbill_create_ip", WxPayConfig.GetConfig().GetIp());//终端ip	  	    
        inputObj.SetValue("nonce_str", GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_MD5);//签名类型

        //签名
        inputObj.SetValue("sign", inputObj.MakeSign(WxPayData.SIGN_TYPE_MD5));
        string xml = inputObj.ToXml();

        var start = DateTime.Now;

        Log.Debug("WxPayApi", "UnfiedOrder request : " + xml);
        string response = HttpService.Post(xml, url, false, timeOut);
        Log.Debug("WxPayApi", "UnfiedOrder response : " + response);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);

        WxPayData result = new WxPayData();
        result.FromXmlNoCheckSign(response);

        //ReportCostTime(url, timeCost, result);//测速上报

        return result;
    }

    /**
    * 
    * 关闭订单
    * @param WxPayData inputObj 提交给关闭订单API的参数
    * @param int timeOut 接口超时时间
    * @throws WxPayException
    * @return 成功时返回,其他抛异常
    */
    public static WxPayData CloseOrder(WxPayData inputObj, int timeOut = 6)
    {
	    string url = "https://api.mch.weixin.qq.com/pay/closeorder";
	    //检测必填参数
	    if(!inputObj.IsSet("out_trade_no"))
        {
		    throw new WxPayException("关闭订单接口中,out_trade_no必填!");
	    }

	    inputObj.SetValue("appid",WxPayConfig.GetConfig().GetAppID());//公众账号ID
	    inputObj.SetValue("mch_id",WxPayConfig.GetConfig().GetMchID());//商户号
	    inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串		
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
	    string xml = inputObj.ToXml();
	
	    var start = DateTime.Now;//请求开始时间

        string response = HttpService.Post(xml, url, false, timeOut);

        var end = DateTime.Now;
        int timeCost = (int)((end - start).TotalMilliseconds);

        WxPayData result = new WxPayData();
        result.FromXml(response);

	    ReportCostTime(url, timeCost, result);//测速上报
	
	    return result;
    }    

    /**
    * 
    * 测速上报
    * @param string interface_url 接口URL
    * @param int timeCost 接口耗时
    * @param WxPayData inputObj参数数组
    */
    private static void ReportCostTime(string interface_url, int timeCost, WxPayData inputObj)
    {
	    //如果不需要进行上报
	    if(WxPayConfig.GetConfig().GetReportLevel() == 0)
        {
		    return;
	    } 

	    //如果仅失败上报
	    if(WxPayConfig.GetConfig().GetReportLevel() == 1 && inputObj.IsSet("return_code") && inputObj.GetValue("return_code").ToString() == "SUCCESS" &&
		 inputObj.IsSet("result_code") && inputObj.GetValue("result_code").ToString() == "SUCCESS")
        {
	 	    return;
	    }
	 
	    //上报逻辑
	    WxPayData data = new WxPayData();
        data.SetValue("interface_url",interface_url);
	    data.SetValue("execute_time_",timeCost);
	    //返回状态码
	    if(inputObj.IsSet("return_code"))
        {
		    data.SetValue("return_code",inputObj.GetValue("return_code"));
	    }
	    //返回信息
        if(inputObj.IsSet("return_msg"))
        {
		    data.SetValue("return_msg",inputObj.GetValue("return_msg"));
	    }
	    //业务结果
        if(inputObj.IsSet("result_code"))
        {
		    data.SetValue("result_code",inputObj.GetValue("result_code"));
	    }
	    //错误代码
        if(inputObj.IsSet("err_code"))
        {
		    data.SetValue("err_code",inputObj.GetValue("err_code"));
	    }
	    //错误代码描述
        if(inputObj.IsSet("err_code_des"))
        {
		    data.SetValue("err_code_des",inputObj.GetValue("err_code_des"));
	    }
	    //商户订单号
        if(inputObj.IsSet("out_trade_no"))
        {
		    data.SetValue("out_trade_no",inputObj.GetValue("out_trade_no"));
	    }
	    //设备号
        if(inputObj.IsSet("device_info"))
        {
		    data.SetValue("device_info",inputObj.GetValue("device_info"));
	    }
	
	    try
        {
		    Report(data);
	    }
        catch (WxPayException ex)
        {
		    //不做任何处理
	    }
    }
    /**
    * 
    * 测速上报接口实现
    * @param WxPayData inputObj 提交给测速上报接口的参数
    * @param int timeOut 测速上报接口超时时间
    * @throws WxPayException
    * @return 成功时返回测速上报接口返回的结果,其他抛异常
    */
    public static WxPayData Report(WxPayData inputObj, int timeOut = 1)
    {
	    string url = "https://api.mch.weixin.qq.com/payitil/report";
	    //检测必填参数
	    if(!inputObj.IsSet("interface_url"))
        {
		    throw new WxPayException("接口URL,缺少必填参数interface_url!");
	    } 
        if(!inputObj.IsSet("return_code"))
        {
		    throw new WxPayException("返回状态码,缺少必填参数return_code!");
	    } 
        if(!inputObj.IsSet("result_code"))
        {
		    throw new WxPayException("业务结果,缺少必填参数result_code!");
	    } 
        if(!inputObj.IsSet("user_ip"))
        {
		    throw new WxPayException("访问接口IP,缺少必填参数user_ip!");
	    } 
        if(!inputObj.IsSet("execute_time_"))
        {
		    throw new WxPayException("接口耗时,缺少必填参数execute_time_!");
	    }

	    inputObj.SetValue("appid",WxPayConfig.GetConfig().GetAppID());//公众账号ID
	    inputObj.SetValue("mch_id",WxPayConfig.GetConfig().GetMchID());//商户号
        inputObj.SetValue("user_ip",WxPayConfig.GetConfig().GetIp());//终端ip
	    inputObj.SetValue("time",DateTime.Now.ToString("yyyyMMddHHmmss"));//商户上报时间	 
	    inputObj.SetValue("nonce_str",GenerateNonceStr());//随机字符串
        inputObj.SetValue("sign_type", WxPayData.SIGN_TYPE_HMAC_SHA256);//签名类型
        inputObj.SetValue("sign", inputObj.MakeSign());//签名
	    string xml = inputObj.ToXml();

        Log.Info("WxPayApi", "Report request : " + xml);

        string response = HttpService.Post(xml, url, false, timeOut);

        Log.Info("WxPayApi", "Report response : " + response);

        WxPayData result = new WxPayData();
        result.FromXml(response);
	    return result;
    }

    /**
    * 根据当前系统时间加随机序列来生成订单号
     * @return 订单号
    */
    public static string GenerateOutTradeNo()
    {
        var ran = new Random();
        return string.Format("{0}{1}{2}", WxPayConfig.GetConfig().GetMchID(), DateTime.Now.ToString("yyyyMMddHHmmss"), ran.Next(999));
    }

    /**
    * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
     * @return 时间戳
    */
    public static string GenerateTimeStamp()
    {
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        return Convert.ToInt64(ts.TotalSeconds).ToString();
    }

    /**
    * 生成随机串,随机串包含字母或数字
    * @return 随机串
    */
    public static string GenerateNonceStr()
    {
        RandomGenerator randomGenerator = new RandomGenerator();
        return randomGenerator.GetRandomUInt().ToString();
    }
}
    /// <summary>
    /// 微信支付协议接口数据类,所有的API接口通信都依赖这个数据结构,
    /// 在调用接口之前先填充各个字段的值,然后进行接口通信,
    /// 这样设计的好处是可扩展性强,用户可随意对协议进行更改而不用重新设计数据结构,
    /// 还可以随意组合出不同的协议数据包,不用为每个协议设计一个数据包结构
    /// </summary>
    public class WxPayData
    {
        public  const string SIGN_TYPE_MD5 = "MD5";
        public  const string SIGN_TYPE_HMAC_SHA256 = "HMAC-SHA256";
        public WxPayData()
        {
    
    }

    //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
    private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();

    /**
    * 设置某个字段的值
    * @param key 字段名
     * @param value 字段值
    */
    public void SetValue(string key, object value)
    {
        m_values[key] = value;
    }

    /**
    * 根据字段名获取某个字段的值
    * @param key 字段名
     * @return key对应的字段值
    */
    public object GetValue(string key)
    {
        object o = null;
        m_values.TryGetValue(key, out o);
        return o;
    }

    /**
     * 判断某个字段是否已设置
     * @param key 字段名
     * @return 若字段key已被设置,则返回true,否则返回false
     */
    public bool IsSet(string key)
    {
        object o = null;
        m_values.TryGetValue(key, out o);
        if (null != o)
            return true;
        else
            return false;
    }

    /**
    * @将Dictionary转成xml
    * @return 经转换得到的xml串
    * @throws WxPayException
    **/
    public string ToXml()
    {
        //数据为空时不能转化为xml格式
        if (0 == m_values.Count)
        {
            Log.Error(this.GetType().ToString(), "WxPayData数据为空!");
            throw new WxPayException("WxPayData数据为空!");
        }

        string xml = "<xml>";
        foreach (KeyValuePair<string, object> pair in m_values)
        {
            //字段值不能为null,会影响后续流程
            if (pair.Value == null)
            {
                Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                throw new WxPayException("WxPayData内部含有值为null的字段!");
            }

            if (pair.Value.GetType() == typeof(int))
            {
                xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
            }
            else if (pair.Value.GetType() == typeof(string))
            {
                xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
            }
            else//除了string和int类型不能含有其他数据类型
            {
                Log.Error(this.GetType().ToString(), "WxPayData字段数据类型错误!");
                throw new WxPayException("WxPayData字段数据类型错误!");
            }
        }
        xml += "</xml>";
        return xml;
    }

    /**
    * @将xml转为WxPayData对象并返回对象内部的数据
    * @param string 待转换的xml串
    * @return 经转换得到的Dictionary
    * @throws WxPayException
    */
    public SortedDictionary<string, object> FromXml(string xml)
    {
        if (string.IsNullOrEmpty(xml))
        {
            Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
            throw new WxPayException("将空的xml串转换为WxPayData不合法!");
        }
        SafeXmlDocument xmlDoc = new SafeXmlDocument();
        xmlDoc.LoadXml(xml);
        XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
        XmlNodeList nodes = xmlNode.ChildNodes;
        foreach (XmlNode xn in nodes)
        {
            XmlElement xe = (XmlElement)xn;
            m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
        }
        try
        {
			//2015-06-29 错误是没有签名
			if(m_values["return_code"] != "SUCCESS")
			{
				return m_values;
			}
            CheckSign(SIGN_TYPE_MD5);//验证签名,不通过会抛异常
        }
        catch(WxPayException ex)
        {
            throw new WxPayException(ex.Message);
        }

        return m_values;
    }
    /**
   * @将xml转为WxPayData对象并返回对象内部的数据
   * @param string 待转换的xml串
   * @return 经转换得到的Dictionary
   * @throws WxPayException
   */
    public SortedDictionary<string, object> FromXmlNoCheckSign(string xml)
    {
        if (string.IsNullOrEmpty(xml))
        {
            Log.Error(this.GetType().ToString(), "将空的xml串转换为WxPayData不合法!");
            throw new WxPayException("将空的xml串转换为WxPayData不合法!");
        }
        SafeXmlDocument xmlDoc = new SafeXmlDocument();
        xmlDoc.LoadXml(xml);
        XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
        XmlNodeList nodes = xmlNode.ChildNodes;
        foreach (XmlNode xn in nodes)
        {
            XmlElement xe = (XmlElement)xn;
            m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
        }

        try
        {
            //2015-06-29 错误是没有签名
            if (m_values["return_code"] != "SUCCESS")
            {
                return m_values;
            }
        }
        catch (WxPayException ex)
        {
            throw new WxPayException(ex.Message);
        }

        return m_values;
    }
    /**
    * @Dictionary格式转化成url参数格式
    * @ return url格式串, 该串不包含sign字段值
    */
    public string ToUrl()
    {
        string buff = "";
        foreach (KeyValuePair<string, object> pair in m_values)
        {
            if (pair.Value == null)
            {
                Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                throw new WxPayException("WxPayData内部含有值为null的字段!");
            }

            if (pair.Key != "sign" && pair.Value.ToString() != "")
            {
                buff += pair.Key + "=" + pair.Value + "&";
            }
        }
        buff = buff.Trim('&');
        return buff;
    }
    /**
    * @Dictionary格式化成Json
     * @return json串数据
    */
    public string ToJson()
    {
        string jsonStr = JsonMapper.ToJson(m_values);
        return jsonStr;

    }

    /**
    * @values格式化成能在Web页面上显示的结果(因为web页面上不能直接输出xml格式的字符串)
    */
    public string ToPrintStr()
    {
        string str = "";
        foreach (KeyValuePair<string, object> pair in m_values)
        {
            if (pair.Value == null)
            {
                Log.Error(this.GetType().ToString(), "WxPayData内部含有值为null的字段!");
                throw new WxPayException("WxPayData内部含有值为null的字段!");
            }
            str += string.Format("{0}={1}
", pair.Key, pair.Value.ToString());
        }
        str = HttpUtility.HtmlEncode(str);
        Log.Debug(this.GetType().ToString(), "Print in Web Page : " + str);
        return str;
    }
    /**
    * @生成签名,详见签名生成算法
    * @return 签名, sign字段不参加签名
    */
    public string MakeSign(string signType){
        //转url格式
        string str = ToUrl();
        //在string后加入API KEY
        str += "&key=" + WxPayConfig.GetConfig().GetKey();
        Log.Error("签名前", str);
        if (signType == SIGN_TYPE_MD5)
        {
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
                sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }
        else if(signType==SIGN_TYPE_HMAC_SHA256)
        {
            return CalcHMACSHA256Hash(str, WxPayConfig.GetConfig().GetKey());
        }else{
            throw new WxPayException("sign_type 不合法");
        }
    }

    /**
    * @生成签名,详见签名生成算法
    * @return 签名, sign字段不参加签名 SHA256
    */
    public string MakeSign()
    {
        return MakeSign(SIGN_TYPE_HMAC_SHA256);
    }
    /**
    * 
    * 检测签名是否正确
    * 正确返回true,错误抛异常
    */
    public bool CheckSign(string signType)
    {
        //如果没有设置签名,则跳过检测
        if (!IsSet("sign"))
        {
            Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
            throw new WxPayException("WxPayData签名存在但不合法!");
        }
        //如果设置了签名但是签名为空,则抛异常
        else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
        {
            Log.Error(this.GetType().ToString(), "WxPayData签名存在但不合法!");
            throw new WxPayException("WxPayData签名存在但不合法!");
        }

        //获取接收到的签名
        string return_sign = GetValue("sign").ToString();

        //在本地计算新的签名
        string cal_sign = MakeSign(signType);

        if (cal_sign == return_sign)
        {
            return true;
        }

        Log.Error(this.GetType().ToString(), "WxPayData签名验证错误!");
        throw new WxPayException("WxPayData签名验证错误!");
    }
    /**
    * 
    * 检测签名是否正确
    * 正确返回true,错误抛异常
    */
    public bool CheckSign()
    {
        return CheckSign(SIGN_TYPE_HMAC_SHA256);
    }

    /**
    * @获取Dictionary
    */
    public SortedDictionary<string, object> GetValues()
    {
        return m_values;
    }

    public void Remove(string key)
    {
        if (m_values.Keys.Contains(key))
        {
            m_values.Remove(key);
        }
    }

    private  string CalcHMACSHA256Hash(string plaintext, string salt)
    {
        string result = "";
        var enc = Encoding.Default;
        byte[]
        baText2BeHashed = enc.GetBytes(plaintext),
        baSalt = enc.GetBytes(salt);
        System.Security.Cryptography.HMACSHA256 hasher = new HMACSHA256(baSalt);
        byte[] baHashedText = hasher.ComputeHash(baText2BeHashed);
        result = string.Join("", baHashedText.ToList().Select(b => b.ToString("x2")).ToArray());
        return result;
    }
  }

预下单接口关键代码:

            WxPayData payData = new WxPayData();
            try
            {
                var orderno = WxPayApi.GenerateOutTradeNo();
                payData.SetValue("out_trade_no", orderno);//商户订单号 32个字符
                payData.SetValue("body", "999感冒灵");//商品描述
                payData.SetValue("total_fee", 1);//总金额 1 分钱
                payData.SetValue("trade_type", "JSAPI");//支付类型 小程序支付
                payData.SetValue("openid", openId);
            
            
            var result = WxPayApi.UnifiedOrder(payData);
            string strCode = result.GetValue("return_code").ToString();
            string strMsg = result.GetValue("return_msg").ToString();
            //响应字段
            //appid--------
            //mch_id--------
            //nonce_str--------
            //prepay_id--------
            //result_code--------SUCCESS
            //return_code--------SUCCESS
            //return_msg--------OK
            //sign--------A5D5BFEB0E4C3648DD61672CFA76DCA9
            //trade_type--------JSAPI
            var paySign = "";
            TimeSpan timeSpan = (DateTime.UtcNow - new DateTime(1970, 1, 1));
            string timeStamp = ((int)timeSpan.TotalSeconds).ToString();
            //if (strCode == "SUCCESS")
            //{
            //去掉非签名字段
            result.Remove("result_code");
            result.Remove("return_code");
            result.Remove("return_msg");
            result.Remove("return_msg");
            result.Remove("sign");

            //一定要注意返回的数据和签名的参数名大小写区别 巨坑
            var appid = result.GetValue("appid");
            result.Remove("appid");
            result.SetValue("appId", appid);

            var nonceStr = result.GetValue("nonce_str");
            result.Remove("nonce_str");
            result.SetValue("nonceStr", nonceStr);

            var trade_type = result.GetValue("trade_type");
            result.Remove("trade_type");

            var mch_id = result.GetValue("mch_id");
            result.Remove("mch_id");

            var prepay_id = result.GetValue("prepay_id");
            result.Remove("prepay_id");

            result.SetValue("signType", "MD5");
            result.SetValue("package", "prepay_id=" + prepay_id);
            result.SetValue("timeStamp", timeStamp);

            //需签名字段
            //appId,timeStamp,nonceStr,package,signType
            paySign = result.MakeSign(WxPayData.SIGN_TYPE_MD5);
            //}
            var strJson = "{"package":"" + result.GetValue("package") + "","timeStamp":"" + timeStamp + "","transaction_id":"" + orderno + "","appid":"" + result.GetValue("appId") + "","mch_id":"" + mch_id + "","nonce_str":"" + nonceStr + "","trade_type":"" + result.GetValue("trade_type") + "","paySign":"" + paySign + ""}";
            return JObject.Parse(strJson);

免责声明:文章转载自《小程序支付》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇SwiftUI 官方教程(三)libevent源码学习(8):event_signal_map解析下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

字符串包括

字符串包括 题目:如果这有一个各种字母组成的字符串A,和另外一个字符串B,字符串里B的字母数相对少一些。什么方法能最快的查出全部小字符串B 里的字母在大字符串A里都有? <<<<<<<<<<<<<<<<<<<<<<&l...

unity中读写二进制文件

都半年没写博客了,自从上个项目开始,感觉时间好紧迫。在这感慨下[自从用了unity之后,代码架构能力越来越差。unity的一切对象都是component,要恶补coding]。 废话不说了。今天记录下用NPOI将excel写入二进制。unity中读取二进制内容。 go...... NPOI 不用多介绍了,一年前用过一次做打印,都是不深入的使用。 首先是规定...

jmeter之BeanShell Sampler实现当前时间加1写法和指定日期

首先获取当前时间: import java.util.*;import java.text.SimpleDateFormat;String str1 = (new SimpleDateFormat("yyyy-MM-dd")).format(new Date());String str2 = (new SimpleDateFormat("hh:mm:ss...

Hibernate的查询语言之HQL(一)——快速入门

  Hibernate提供异常强大的查询体系,使用Hibernat有多种查询方式可以选择:即可以使用Hibernate的HQL查询,也可以使用条件查询,甚至可以使用原生的SQL查询语句。不仅如此, Hibernate还提供了一种数据过滤功能,这些都用于筛选目标数据。   Hibernate是 Hibernate Query Language的缩写,HQL的...

ActiveMQ---知识点整理

本文来自于csdn,文章通过介绍ActiveMQ的安装,使用,搭建等等,简单整理了ActiveMQ。 本文转自:http://www.uml.org.cn/zjjs/201802111.asp 一.背景介绍 1.1 java消息服务: 不同系统之间的信息交换,是我们开发中比较常见的场景,比如系统A要把数据发送给系统B,这个问题我们应该如何去处理? 1999...

java函数式编程

1.函数式接口 1.1概念:java中有且只有一个抽象方法的接口。 1.2格式: 修饰符 interface接口名称 { public abstract返回值类型 方法名称(可选参数信息); //其他非抽象方法内容 } //或者 public interfaceMyFunctionalInterface { voidmyMethod(); }...