zoukankan      html  css  js  c++  java
  • 微信app支付android客户端以及.net服务端实现

    由于公司运营需要,需要在客户端(android/ios)增加微信以及支付宝支付,在调用微信app支付时遇到一些问题,也算是一些踩过的坑,记录下来

    ,希望能对.net开发者服务端网站更快的集成微信app支付。

    1.开发所需资料:微信开放平台应用的appid以及appsecert,商户平台的商户号以及api安全里面里面设置的key,详见 微信支付账户相关信息;

    2.微信开发者平台完善应用平台的相关信息,android应用签名必须用打包签名过的发布版本apk(这一步很重用),包名必须一致,可以用微信提供的签名工具获得,签名工具下载地址https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk

    以下是交互时序图,统一下单API、支付结果通知API和查询订单API等都涉及签名过程,调用都必须在商户服务器端完成(来源微信支付开发文档):

    商户系统和微信支付支付系统主要交互:

    步骤1:用户在商户APP中选择商品,提交订单,选择微信支付。商户后台收到订单时需要调用微信的同意下单接口,生成预支付单;

    C#大概代码如下(相关代码参考自JeffreySu/WeiXinMPSDK ):

    private WechatPayResult generatePayResult(string mchappid,string mchid
                ,string body,string orderno,int total,string ip,string notify,string mchkey,string nonce)
            {
                DateTime start = DateTime.Now,end= DateTime.Now.AddMinutes(15);
                var xmlDataInfo = new TenPayV3UnifiedorderRequestData(mchappid
                   ,mchid, body, orderno, total
                   ,ip,notify, TenPayV3Type.APP
                   , null, mchkey, nonce, null,start,end);
                var result = TenPayV3.Unifiedorder(xmlDataInfo);//调用统一订单接口
                return new WechatPayResult
                {
                    appid=mchappid,
                    body=body,
                    CreatedOn=DateTime.Now,
                    mch_id=mchid,
                    prepay_id=result.prepay_id,
                    spbill_create_ip=ip,
                    nonce_str=nonce,
                    timeStamp= TenPayV3Util.GetTimestamp(),
                    out_trade_no=orderno,
                    time_start=start,
                    time_expire=end,
                    total_fee=total,
                    trade_type=result.trade_type
                };
            }
    View Code
      public class RequestHandler
        {
    
            public RequestHandler()
            {
                Parameters = new Hashtable();
            }
    
    
            public RequestHandler(HttpContext httpContext)
            {
                Parameters = new Hashtable();
    
                this.HttpContext = httpContext ?? HttpContext.Current;
    
            }
            /// <summary>
            /// 密钥
            /// </summary>
            private string Key;
    
            protected HttpContext HttpContext;
    
            /// <summary>
            /// 请求的参数
            /// </summary>
            protected Hashtable Parameters;
    
            /// <summary>
            /// debug信息
            /// </summary>
            private string DebugInfo;
    
            /// <summary>
            /// 初始化函数
            /// </summary>
            public virtual void Init()
            {
            }
            /// <summary>
            /// 获取debug信息
            /// </summary>
            /// <returns></returns>
            public String GetDebugInfo()
            {
                return DebugInfo;
            }
            /// <summary>
            /// 获取密钥
            /// </summary>
            /// <returns></returns>
            public string GetKey()
            {
                return Key;
            }
            /// <summary>
            /// 设置密钥
            /// </summary>
            /// <param name="key"></param>
            public void SetKey(string key)
            {
                this.Key = key;
            }
    
            /// <summary>
            /// 设置参数值
            /// </summary>
            /// <param name="parameter"></param>
            /// <param name="parameterValue"></param>
            public void SetParameter(string parameter, string parameterValue)
            {
                if (parameter != null && parameter != "")
                {
                    if (Parameters.Contains(parameter))
                    {
                        Parameters.Remove(parameter);
                    }
    
                    Parameters.Add(parameter, parameterValue);
                }
            }
    
    
            /// <summary>
            /// 当参数不为null或空字符串时,设置参数值
            /// </summary>
            /// <param name="parameter"></param>
            /// <param name="parameterValue"></param>
            public void SetParameterWhenNotNull(string parameter, string parameterValue)
            {
                if (!string.IsNullOrEmpty(parameterValue))
                {
                    SetParameter(parameter, parameterValue);
                }
            }
    
            /// <summary>
            /// 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名
            /// </summary>
            /// <param name="key">参数名</param>
            /// <param name="value">参数值</param>
            /// key和value通常用于填充最后一组参数
            /// <returns></returns>
            public virtual string CreateMd5Sign(string key, string value)
            {
                StringBuilder sb = new StringBuilder();
    
                ArrayList akeys = new ArrayList(Parameters.Keys);
                akeys.Sort();
    
                foreach (string k in akeys)
                {
                    string v = (string)Parameters[k];
                    if (null != v && "".CompareTo(v) != 0
                        && "sign".CompareTo(k) != 0 
                        //&& "sign_type".CompareTo(k) != 0
                        && "key".CompareTo(k) != 0)
                    {
                        sb.Append(k + "=" + v + "&");
                    }
                }
    
                sb.Append(key + "=" + value);
                string sign = EncryptHelper.GetMD5(sb.ToString(), GetCharset()).ToUpper();
                //string sign = MD5UtilHelper.GetMD5(sb.ToString(), GetCharset()).ToUpper();
                return sign;
            }
            public virtual string CreateMd5Sign()
            {
                StringBuilder sb = new StringBuilder();
    
                ArrayList akeys = new ArrayList(Parameters.Keys);
                akeys.Sort();
    
                foreach (string k in akeys)
                {
                    string v = (string)Parameters[k];
                    if (null != v && "".CompareTo(v) != 0
                        && "sign".CompareTo(k) != 0
                        //&& "sign_type".CompareTo(k) != 0
                        && "key".CompareTo(k) != 0)
                    {
                        sb.Append(k + "=" + v + "&");
                    }
                }
                string sign = EncryptHelper.GetMD5(sb.ToString().Substring(0,sb.Length-1), GetCharset()).ToUpper();
                return sign;
            }
            /// <summary>
            /// 输出XML
            /// </summary>
            /// <returns></returns>
            public string ParseXML()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("<xml>");
                foreach (string k in Parameters.Keys)
                {
                    string v = (string)Parameters[k];
                    if (v != null && Regex.IsMatch(v, @"^[0-9.]$"))
                    {
    
                        sb.Append("<" + k + ">" + v + "</" + k + ">");
                    }
                    else
                    {
                        sb.Append("<" + k + "><![CDATA[" + v + "]]></" + k + ">");
                    }
    
                }
                sb.Append("</xml>");
                return sb.ToString();
            }
    
    
    
            /// <summary>
            /// 设置debug信息
            /// </summary>
            /// <param name="debugInfo"></param>
            public void SetDebugInfo(String debugInfo)
            {
                this.DebugInfo = debugInfo;
            }
    
            public Hashtable GetAllParameters()
            {
                return this.Parameters;
            }
    
            protected virtual string GetCharset()
            {
                if (this.HttpContext == null)
                {
                    return Encoding.UTF8.BodyName;
                }
    
                return this.HttpContext.Request.ContentEncoding.BodyName;
            }
        }
    View Code
     /// <summary>
        /// 微信支付提交的XML Data数据[统一下单]
        /// </summary>
        public class TenPayV3UnifiedorderRequestData
        {
            /// <summary>
            /// 公众账号ID
            /// </summary>
            public string AppId { get; set; }
            /// <summary>
            /// 商户号
            /// </summary>
            public string MchId { get; set; }
            /// <summary>
            /// 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB",String(32)如:013467007045764
            /// </summary>
            public string DeviceInfo { get; set; }
            /// <summary>
            /// 随机字符串
            /// </summary>
            public string NonceStr { get; set; }
            /// <summary>
            /// 签名类型,默认为MD5,支持HMAC-SHA256和MD5。(使用默认)
            /// </summary>
            public string SignType { get; set; }
            /// <summary>
            /// 商品信息
            /// </summary>
            public string Body { get; set; }
            /// <summary>
            /// 商品详细列表,使用Json格式,传输签名前请务必使用CDATA标签将JSON文本串保护起来。
            ///cost_price Int 可选 32 订单原价,商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的支付金额。当订单原价与支付金额不相等则被判定为拆单,无法享/受/优/惠。
            /// receipt_id String 可选 32 商家小票ID
            ///goods_detail 服务商必填[]:
            ///└ goods_id String 必填 32 商品的编号
            ///└ wxpay_goods_id String 可选 32 微信支付定义的统一商品编号
            ///└ goods_name String 可选 256 商品名称 
            ///└ quantity Int 必填  32 商品数量
            ///└ price Int 必填 32 商品单价,如果商户有优惠,需传输商户优惠后的单价
            ///注意:单品总金额应&lt;=订单总金额total_fee,否则会无法享受优惠。
            /// String(6000)
            /// </summary>
            public string Detail { get; set; }
            /// <summary>
            /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。String(127),如:深圳分店
            /// </summary>
            public string Attach { get; set; }
            /// <summary>
            /// 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型。String(16),如:CNY
            /// </summary>
            public string FeeType { get; set; }
            /// <summary>
            /// 商家订单号
            /// </summary>
            public string OutTradeNo { get; set; }
            /// <summary>
            /// 商品金额,以分为单位(money * 100).ToString()
            /// </summary>
            public int TotalFee { get; set; }
            /// <summary>
            /// 用户的公网ip,不是商户服务器IP
            /// </summary>
            public string SpbillCreateIP { get; set; }
            /// <summary>
            /// 订单生成时间,最终生成格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则。
            /// 如果为空,则默认为当前服务器时间
            /// </summary>
            public string TimeStart { get; set; }
            /// <summary>
            /// 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
            /// 注意:最短失效时间间隔必须大于5分钟。
            /// 留空则不设置失效时间
            /// </summary>
            public string TimeExpire { get; set; }
            /// <summary>
            /// 商品标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠。String(32),如:WXG
            /// </summary>
            public string GoodsTag { get; set; }
            /// <summary>
            /// 接收财付通通知的URL
            /// </summary>
            public string NotifyUrl { get; set; }
            /// <summary>
            /// 交易类型
            /// </summary>
            public TenPayV3Type TradeType { get; set; }
            /// <summary>
            /// trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
            /// String(32),如:12235413214070356458058
            /// </summary>
            public string ProductId { get; set; }
            /// <summary>
            /// 上传此参数no_credit--可限制用户不能使用信用卡支付
            /// </summary>
            public string LimitPay { get; set; }
            /// <summary>
            /// 用户的openId
            /// </summary>
            public string OpenId { get; set; }
    
            /// <summary>
            /// 
            /// </summary>
            public string Key { get; set; }
    
            public readonly RequestHandler PackageRequestHandler;
            public readonly string Sign;
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="appId"></param>
            /// <param name="mchId"></param>
            /// <param name="body"></param>
            /// <param name="outTradeNo"></param>
            /// <param name="totalFee">单位:分</param>
            /// <param name="spbillCreateIp"></param>
            /// <param name="notifyUrl"></param>
            /// <param name="tradeType"></param>
            /// <param name="openid"></param>
            /// <param name="key"></param>
            /// <param name="nonceStr"></param>
            /// <param name="deviceInfo">自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB",String(32)如:013467007045764</param>
            /// <param name="timeStart">订单生成时间,如果为空,则默认为当前服务器时间</param>
            /// <param name="timeExpire">订单失效时间,留空则不设置失效时间</param>
            /// <param name="detail">商品详细列表</param>
            /// <param name="attach">附加数据</param>
            /// <param name="feeType">符合ISO 4217标准的三位字母代码,默认人民币:CNY</param>
            /// <param name="goodsTag">商品标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠。String(32),如:WXG</param>
            /// <param name="productId">trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。String(32),如:12235413214070356458058</param>
            /// <param name="limitPay">是否限制用户不能使用信用卡支付</param>
            public TenPayV3UnifiedorderRequestData(string appId, string mchId, string body, string outTradeNo, int totalFee, string spbillCreateIp,
                string notifyUrl, TenPayV3Type tradeType, string openid, string key, string nonceStr,
                string deviceInfo = null, DateTime? timeStart = null, DateTime? timeExpire = null,
               string detail = null, string attach = null, string feeType = "CNY", string goodsTag = null, string productId = null, bool limitPay = false)
            {
                AppId = appId;
                MchId = mchId;
                DeviceInfo = deviceInfo;
                NonceStr = nonceStr;
                SignType = "MD5";
                Body = body ?? "";
                Detail = detail;
                Attach = attach;
                OutTradeNo = outTradeNo;
                FeeType = feeType;
                TotalFee = totalFee;
                SpbillCreateIP = spbillCreateIp;
                TimeStart = (timeStart ?? DateTime.Now).ToString("yyyyMMddHHmmss");
                TimeExpire = timeExpire.HasValue ? timeExpire.Value.ToString("yyyyMMddHHmmss") : null;
                GoodsTag = goodsTag;
                NotifyUrl = notifyUrl;
                TradeType = tradeType;
                ProductId = productId;
                LimitPay = limitPay ? "no_credit" : null;
                OpenId = openid;
                Key = key;
    
                #region 设置RequestHandler
    
                //创建支付应答对象
                PackageRequestHandler = new RequestHandler(null);
                //初始化
                PackageRequestHandler.Init();
    
                //设置package订单参数
                //以下设置顺序按照官方文档排序,方便维护:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
                PackageRequestHandler.SetParameter("appid", this.AppId);                       //公众账号ID
                PackageRequestHandler.SetParameter("mch_id", this.MchId);                      //商户号
                PackageRequestHandler.SetParameterWhenNotNull("device_info", this.DeviceInfo); //自定义参数
                PackageRequestHandler.SetParameter("nonce_str", this.NonceStr);                //随机字符串
                PackageRequestHandler.SetParameterWhenNotNull("sign_type", this.SignType);     //签名类型,默认为MD5
                PackageRequestHandler.SetParameter("body", this.Body);                         //商品信息
                PackageRequestHandler.SetParameterWhenNotNull("detail", this.Detail);          //商品详细列表
                PackageRequestHandler.SetParameterWhenNotNull("attach", this.Attach);          //附加数据
                PackageRequestHandler.SetParameter("out_trade_no", this.OutTradeNo);           //商家订单号
                PackageRequestHandler.SetParameterWhenNotNull("fee_type", this.FeeType);       //符合ISO 4217标准的三位字母代码,默认人民币:CNY
                PackageRequestHandler.SetParameter("total_fee", this.TotalFee.ToString());  //商品金额,以分为单位(money * 100).ToString()
                PackageRequestHandler.SetParameter("spbill_create_ip", this.SpbillCreateIP);   //用户的公网ip,不是商户服务器IP
                PackageRequestHandler.SetParameterWhenNotNull("time_start", this.TimeStart);   //订单生成时间
                PackageRequestHandler.SetParameterWhenNotNull("time_expire", this.TimeExpire);  //订单失效时间
                PackageRequestHandler.SetParameterWhenNotNull("goods_tag", this.GoodsTag);     //商品标记
                PackageRequestHandler.SetParameter("notify_url", this.NotifyUrl);              //接收财付通通知的URL
                PackageRequestHandler.SetParameter("trade_type", this.TradeType.ToString());   //交易类型
                PackageRequestHandler.SetParameterWhenNotNull("product_id", this.ProductId);   //trade_type=NATIVE时(即扫码支付),此参数必传。
                PackageRequestHandler.SetParameterWhenNotNull("limit_pay", this.LimitPay);     //上传此参数no_credit--可限制用户不能使用信用卡支付
                PackageRequestHandler.SetParameter("openid", this.OpenId);          //用户的openId,trade_type=JSAPI时(即公众号支付),此参数必传
                Sign = PackageRequestHandler.CreateMd5Sign("key", this.Key);
                PackageRequestHandler.SetParameter("sign", Sign);                              //签名
                #endregion
            }
        }
    统一下单
     /// <summary>
            /// 统一支付接口
            /// 统一支付接口,可接受JSAPI/NATIVE/APP 下预支付订单,返回预支付订单号。NATIVE 支付返回二维码code_url。
            /// </summary>
            /// <param name="dataInfo">微信支付需要post的Data数据</param>
            /// <param name="timeOut"></param>
            /// <returns></returns>
            public static UnifiedorderResult Unifiedorder(TenPayV3UnifiedorderRequestData dataInfo, int timeOut = Config.TIME_OUT)
            {
                var urlFormat = "https://api.mch.weixin.qq.com/pay/unifiedorder";
                var data = dataInfo.PackageRequestHandler.ParseXML();//获取XML
                //throw new Exception(data.HtmlEncode());
                MemoryStream ms = new MemoryStream();
                var formDataBytes = data == null ? new byte[0] : Encoding.UTF8.GetBytes(data);
                ms.Write(formDataBytes, 0, formDataBytes.Length);
                ms.Seek(0, SeekOrigin.Begin);//设置指针读取位置
    
                var resultXml = RequestUtility.HttpPost(urlFormat, null, ms, timeOut: timeOut);
                return new UnifiedorderResult(resultXml);
            }
    统一支付接口

    步骤2:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay,签名的字段数量必须跟你app调起微信支付所传的参数一致:

     /// <summary>
            /// 生成微信app支付前面
            /// </summary>
            /// <param name="appId"></param>
            /// <param name="timeStamp"></param>
            /// <param name="nonceStr"></param>
            /// <param name="package"></param>
            /// <param name="key"></param>
            /// <param name="partnerid"></param>
            /// <param name="prepayid"></param>
            /// <param name="signType"></param>
            /// <returns></returns>
            public static string GetAppPaySign(string appId
                , string timeStamp, string nonceStr, string package,
                string key,string partnerid,string prepayid,string signType = "MD5"
                )
            {
                RequestHandler paySignReqHandler = new RequestHandler(null);
                paySignReqHandler.SetParameter("appid", appId.Trim());
                paySignReqHandler.SetParameter("timestamp", timeStamp.Trim());
                paySignReqHandler.SetParameter("noncestr", nonceStr.Trim());
                paySignReqHandler.SetParameter("partnerid", partnerid);
                paySignReqHandler.SetParameter("prepayid", prepayid);
                paySignReqHandler.SetParameter("package", package.Trim());
                //paySignReqHandler.SetParameter("signtype", "MD5");
                var paySign = paySignReqHandler.CreateMd5Sign("key", key);
                return paySign;
            }
    生成微信app支付签名

    步骤3:商户APP调起微信支付。详细介绍参见微信支付官方文档

    关键代码如下:

     public   static  IWXAPI api;
    api= WXAPIFactory.createWXAPI(this, Constants.APP_ID,true);
            api.registerApp(Constants.APP_ID); 
    Result result = JSON.parseObject(bytes, Result.class);
                        if (null != result) {
                            if (result.isSuccess()) {
                                prepay prepay = JSON.parseObject(result.getData().toString(), prepay.class);
                                PayReq request = new PayReq();
                                request.appId = prepay.getAppid();
                                request.partnerId =prepay.getPartnerid();
                                request.prepayId= prepay.getPrepayid();
                                request.packageValue = prepay.getPackages();
                                request.nonceStr= prepay.getNoncestr();
                                request.timeStamp=prepay.getTimestamp();
                                request.sign= prepay.getSign();
                                api.sendReq(request);
                            }
                        }
    android调起支付相关代码

    其中result.getData()表示服务端返回的预支付订单的相关参数,用这参数调用微信支付,该activity必须实现接口IWXAPIEventHandler,并实现

    @Override
    public void onResp(BaseResp baseResp) {
    if(baseResp.getType()== ConstantsAPI.COMMAND_PAY_BY_WX){
    Log.d("dd","onPayFinish,errCode="+baseResp.errCode);
    }
    }
    用于判断微信支付结果,安全起见,建议微信app回调后请求商户api相关接口确定支付结果
    支付成功时微信后台会请求生成预支付单时传递的通知url(即payProvider.TenpayNotify)通知商户支付结果,商户后台校验相关信息
     [AllowAnonymous]
            public ActionResult PayResult()
            {
                ResponseHandler resHandler = new ResponseHandler(null);
                string return_code = resHandler.GetParameter("return_code");
                string return_msg = resHandler.GetParameter("return_msg");
                string res = null;
                EngineContext.Current.Resolve<ILogger>().Information("收到微信支付通知"+resHandler.ParseXML());
                //EngineContext.Current.Resolve<ILoggingService>().Write(LogType.Pay, resHandler.ParseXML());
                string orderno = resHandler.GetParameter("out_trade_no");
                string payno = resHandler.GetParameter("transaction_id");
                string nonce_str = resHandler.GetParameter("nonce_str");
                var order = _wechatService.GetWechatPayResult(orderno);
                if (order != null)
                {
                    var first = order.FirstOrDefault(r => r.nonce_str == nonce_str);
                    if (first != null)
                    {
                        var payProvider = _wechatService.GetWechatPayProvider(first.appid);
                        resHandler.SetKey(payProvider.MchKey);
                            if (resHandler.IsTenpaySign())
                            {
                                if (return_code == TenPayTypeResult.SUCCESS.ToString())
                                {
                                    int totaled= int.Parse(resHandler.GetParameter("total_fee"));
                                    if (totaled == first.total_fee)
                                    {
                                        first.IsSuccess = true;
                                        first.time_end = DateTime.Now;
                                        first.total_fee_ed = int.Parse(resHandler.GetParameter("total_fee"));
                                        first.transaction_id = payno;
                                        first.Content = resHandler.ParseXML();
                                        _wechatService.UpdateWechatPayResult(first);
                                        EngineContext.Current.Resolve<IEventPublisher>().Publish(resultEvent);
                                    }else
                                    {
                                        EngineContext.Current.Resolve<ILogger>().Information("微信支付金额与订单金额不匹配" + resHandler.ParseXML());
                                    }
                                    
                                }
                    }
                }
                
                //验证请求是否从微信发过来(安全)
                string xml = string.Format(@"<xml>
       <return_code><![CDATA[{0}]]></return_code>
       <return_msg><![CDATA[{1}]]></return_msg>
    </xml>", return_code, return_msg);
                return Content(xml, "text/xml");
            }
    微信回调通知参考代码

    微信支付官方文档

    微信.NET集成可参考现有轮子 JeffreySu/WeiXinMPSDK

  • 相关阅读:
    分页
    uuid算法
    mysql卸载安装
    安装和破解收费版idea
    css样式一开始不显示,点击显示隐藏
    各种技术网址
    vue加载高德地图,加标记点,点出文本框
    Security Traps 玩耍剧透(持续更新)
    最近做的一个小玩意踩的坑
    最近刷题的一个总结
  • 原文地址:https://www.cnblogs.com/shatanku/p/6763341.html
Copyright © 2011-2022 走看看