微信app支付android客戶端以及.net服務端實現

因爲公司運營須要,須要在客戶端(android/ios)增長微信以及支付寶支付,在調用微信app支付時遇到一些問題,也算是一些踩過的坑,記錄下來php

,但願能對.net開發者服務端網站更快的集成微信app支付。html

1.開發所需資料:微信開放平臺應用的appid以及appsecert,商戶平臺的商戶號以及api安全裏面裏面設置的key,詳見 微信支付帳戶相關信息;android

2.微信開發者平臺完善應用平臺的相關信息,android應用簽名必須用打包簽名過的發佈版本apk(這一步很重用),包名必須一致,能夠用微信提供的簽名工具得到,簽名工具下載地址https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apkios

如下是交互時序圖,統一下單API、支付結果通知API和查詢訂單API等都涉及簽名過程,調用都必須在商戶服務器端完成(來源微信支付開發文檔):git

商戶系統和微信支付支付系統主要交互:github

步驟1:用戶在商戶APP中選擇商品,提交訂單,選擇微信支付。商戶後臺收到訂單時須要調用微信的贊成下單接口,生成預支付單;api

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

相關文章
相關標籤/搜索