微信小程序支付(JSAPI支付)

 

開發環境:.NET MVC+ ORM框架(EF)php

1、參考文檔:

  一、微信JSAPI支付官方文檔:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1;小程序

 

2、開發前準備:

   一、必須申請微信公衆平臺(企業用戶開通);微信小程序

  二、必須開通小程序平臺,並與微信公衆平臺進行綁定;api

  三、必須開通微信商戶平臺,並妥善保管號商戶號和商戶密鑰;服務器

   (值得說明的是:微信商戶密鑰在拿到手以後,請首先重置三次以上,具體緣由不清楚,可是不重置的話,後期開發的時候,微信統一下單接口會頻繁報錯,並且錯誤信息莫名其妙,主要報錯信息爲:「簽名錯誤」,即便你的參數簽名在微信的簽名校驗工具中校驗經過,也會提示你「簽名錯誤」,可是此時你無需對接口作任何改動,只須要重置商戶密鑰3次以上,此問題即可解決微信

  四、網站升級https協議,由於調用微信支付成功後,微信服務器會對你傳值的回調地址(notify_url字段,能夠理解爲具體的業務邏輯處理方法路徑)進行回調,(雖然目前統一下單接口能夠回調http協議接口,可是仍然建議網站升級爲https協議)app

 

3、支付流程

  一、統一下單接口,後臺經過統一下單接口,向微信請求下單支付,微信後臺接到參數後,會生成一個商戶訂單,並將預下單id(prepay_id 這個返回字段很重要)返回給後臺;微信公衆平臺

  二、後臺接收微信返回值,進行二次簽名,並將簽名的參數返回給小程序前臺;框架

  三、小程序端接收到簽名參數後,調用 wx.requestPayment 方法,傳入參數,調起收銀臺;dom

  四、用戶支付後,微信服務器處理本次支付狀況,並回調後臺業務處理接口。

 

4、代碼實現

  一、微信支付model類:

    public  class PayModel
    {

        /// <summary>
        /// 統一下單API
        /// </summary>
        public static string orderUrl = ConfigurationManager.AppSettings["WXunifiedorder"].ToString();

        /// <summary>
        /// 支付結果通知API  
        /// </summary>
        public static string notifyUrl = ConfigurationManager.AppSettings["WxPayNotifyurl"].ToString();

        /// <summary>
        /// 查詢訂單API
        /// </summary>
        public static string queryUrl = ConfigurationManager.AppSettings["WxPayQueryOrder"].ToString();

        /// <summary>
        /// 小程序惟一標識
        /// </summary>
        public static string AppID = ConfigurationManager.AppSettings["WxAppId"].ToString();

        /// <summary>
        /// 小程序的 app secret
        /// </summary>
        public static string secret = ConfigurationManager.AppSettings["WxSecret"].ToString();

        /// <summary>
        /// 商戶號(微信支付分配的商戶號) 
        /// </summary>
        public static string mchid = ConfigurationManager.AppSettings["WxMerchantNo"].ToString();

        /// <summary>
        ///商戶平臺設置的密鑰key   
        /// </summary>
        public static string WxMerchantKey = ConfigurationManager.AppSettings["WxMerchantKey"].ToString();

        /// <summary>
        /// 隨機字符串不長於 32 位
        /// </summary>
        public static string nonceStr = RandomNum.CreateRandomNum(32).ToUpper();

        /// <summary>
        /// 時間戳 從1970年1月1日00:00:00至今的秒數,即當前的時間
        /// </summary>
        public static string timeStamp = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalSeconds).ToString();

        /// <summary>
        /// 交易類型 小程序取值以下:JSAPI
        /// </summary>
        public static string tradeType = "JSAPI";

        /// <summary>
        /// 簽名類型 默認爲MD5,支持HMAC-SHA256和MD5。
        /// </summary>
        public static string signType = "MD5";

        /// <summary>
        /// 商品描述 商品簡單描述,該字段請按照規範傳遞
        /// </summary>
        public static string body = "挪威躺椅";

        /// <summary>
        /// 附加數據 在查詢API和支付通知中原樣返回
        /// </summary>
        public static string attach = "商城支付";

        /// <summary>
        /// 簽名,參與簽名參數:appid,mch_id,transaction_id,out_trade_no,nonce_str,key
        /// </summary>
        public string sign = "";

        /// <summary>
        /// 微信訂單號,優先使用
        /// </summary>
        public string transactionid = "";

        /// <summary>
        /// 商戶系統內部訂單號
        /// </summary>
        public string out_trade_no = "";
    /// <summary>
        /// 訂單金額
        /// </summary>
        public decimal totalfee=0;

    }
public class OrderReloadModel { public string return_msg { get; set; } public string return_code { get; set; } public string orderNo { get; set; } public string AppID { get; set; } public string package { get; set; } public string timeStamp { get; set; } public string nonecStr { get; set; } public string signType { get; set; } public string paysign { get; set; } }

 

   二、微信支付幫助類

    public class PayHelper
    {
        #region 生成簽名
        /// <summary>
        /// 獲取簽名數據
        ///</summary>
        /// <param name="strParam"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GetSignInfo(Dictionary<string, string> strParam, string key)
        {
            int i = 0;
            string sign = string.Empty;
            StringBuilder sb = new StringBuilder();
            try
            {
                foreach (KeyValuePair<string, string> temp in strParam)
                {
                    if (temp.Value == "" || temp.Value == null || temp.Key.ToLower() == "sign")
                    {
                        continue;
                    }
                    i++;
                    sb.Append(temp.Key.Trim() + "=" + temp.Value.Trim() + "&");
                }
                sb.Append("key=" + key.Trim() + "");
                sign = CryptoService.Md5EncryptStr(sb.ToString()).ToUpper();
                LogHelper.Writer("服務端參數:" + sb.ToString() + "簽名:" + sign, "", 5);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return sign;
        }
        #endregion

        #region XML 處理
        /// <summary>
        /// 獲取XML值
        /// </summary>
        /// <param name="strXml">XML字符串</param>
        /// <param name="strData">字段值</param>
        /// <returns></returns>
        public static string GetXmlValue(string strXml, string strData)
        {
            string xmlValue = string.Empty;
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(strXml);
            var selectSingleNode = xmlDocument.DocumentElement.SelectSingleNode(strData);
            if (selectSingleNode != null)
            {
                xmlValue = selectSingleNode.InnerText;
            }
            return xmlValue;
        }

        /// <summary>
        /// 集合轉換XML數據 (拼接成XML請求數據)
        /// </summary>
        /// <param name="strParam">參數</param>
        /// <returns></returns>
        public static string CreateXmlParam(Dictionary<string, string> strParam)
        {
            StringBuilder sb = new StringBuilder();
            try
            {
                sb.Append("<xml>");
                foreach (KeyValuePair<string, string> k in strParam)
                {
                    if (k.Key == "attach" || k.Key == "body" || k.Key == "sign")
                    {
                        sb.Append("<" + k.Key + "><![CDATA[" + k.Value + "]]></" + k.Key + ">");
                    }
                    else
                    {
                        sb.Append("<" + k.Key + ">" + k.Value + "</" + k.Key + ">");
                    }
                }
                sb.Append("</xml>");
            }
            catch (Exception ex)
            {
                throw ex;
                // AddLog("PayHelper", "CreateXmlParam", ex.Message, ex);
            }
            var a = sb.ToString();
            return sb.ToString();
        }

        /// <summary>
        /// XML數據轉換集合(XML數據拼接成字符串)
        /// </summary>
        /// <param name="xmlString"></param>
        /// <returns></returns>
        public static Dictionary<string, string> GetFromXml(string xmlString)
        {
            Dictionary<string, string> sParams = new Dictionary<string, string>();
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(xmlString);
                XmlElement root = doc.DocumentElement;
                int len = root.ChildNodes.Count;
                for (int i = 0; i < len; i++)
                {
                    string name = root.ChildNodes[i].Name;
                    if (!sParams.ContainsKey(name))
                    {
                        sParams.Add(name.Trim(), root.ChildNodes[i].InnerText.Trim());
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return sParams;
        }

        /// <summary>
        /// 返回通知 XML
        /// </summary>
        /// <param name="returnCode"></param>
        /// <param name="returnMsg"></param>
        /// <returns></returns>
        public static string GetReturnXml(string returnCode, string returnMsg)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("<xml>");
            sb.Append("<return_code><![CDATA[" + returnCode + "]]></return_code>");
            sb.Append("<return_msg><![CDATA[" + returnMsg + "]]></return_msg>");
            sb.Append("</xml>");
            return sb.ToString();
        }
        #endregion

        /// <summary>
        /// 得到Post過來的數據  
        /// </summary>
        /// <returns></returns>
        public static string GetPostStr()
        {
            Int32 intLen = Convert.ToInt32(HttpContext.Current.Request.InputStream.Length);
            byte[] b = new byte[intLen];
            HttpContext.Current.Request.InputStream.Read(b, 0, intLen);
            return Encoding.UTF8.GetString(b);
        }

        /// <summary>  
        /// 模擬POST提交 (不須要微信API證書) 
        /// </summary>  
        /// <param name="url">請求地址</param>  
        /// <param name="xmlParam">xml參數</param>  
        /// <returns>返回結果</returns>  
        public static string PostHttpResponse(string url, string xmlParam)
        {
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create(url);
            myHttpWebRequest.Method = "POST";
            myHttpWebRequest.ContentType = "application/x-www-form-urlencoded;charset=utf-8";

            // Encode the data  
            byte[] encodedBytes = Encoding.UTF8.GetBytes(xmlParam);
            myHttpWebRequest.ContentLength = encodedBytes.Length;

            // Write encoded data into request stream  
            Stream requestStream = myHttpWebRequest.GetRequestStream();
            requestStream.Write(encodedBytes, 0, encodedBytes.Length);
            requestStream.Close();

            HttpWebResponse result;

            try
            {
                result = (HttpWebResponse)myHttpWebRequest.GetResponse();
            }
            catch
            {
                return string.Empty;
            }

            if (result.StatusCode == HttpStatusCode.OK)
            {
                using (Stream mystream = result.GetResponseStream())
                {
                    using (StreamReader reader = new StreamReader(mystream))
                    {
                        return reader.ReadToEnd();
                    }
                }
            }
            return null;
        }/// <summary>
        /// 獲取客戶端IP
        /// </summary>
        /// <returns></returns>
        public static string GetUserIP()
        {
            string ipv4 = String.Empty;
            foreach (IPAddress ip in Dns.GetHostAddresses(GetClientIP()))
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    ipv4 = ip.ToString();
                    break;
                }
            }

            if (ipv4 != String.Empty)
            {
                return ipv4;
            }
            // 利用 Dns.GetHostEntry 方法,由獲取的 IPv6 位址反查 DNS 紀錄,
            // 再逐一判斷何者爲 IPv4 協議,便可轉爲 IPv4 位址。
            foreach (IPAddress ip in Dns.GetHostEntry(GetClientIP()).AddressList)
            //foreach (IPAddress ip in Dns.GetHostAddresses(Dns.GetHostName()))
            {
                if (ip.AddressFamily.ToString() == "InterNetwork")
                {
                    ipv4 = ip.ToString();
                    break;
                }
            }

            return ipv4;
        }

    }

  

  三、微信支付加密類(MD5加密):  

        public static string Md5EncryptStr(string input)
        {
            var result = string.Empty;
            if (string.IsNullOrEmpty(input)) return result;
            using (var md5 = MD5.Create())
            {
                result = GetMd5Hash(md5, input);
            }
            return result;
        }

 

  四、統一下單接口

public JsonResult GetPrayPayId(string openId, string orderNo)
{
Result result = new Result();
List<OrderReloadModel> listMsg = new List<OrderReloadModel>();
try
{

#region 統一下單接口API接口調用參數準備
//獲取請求數據
Dictionary<string, string> strParam = new Dictionary<string, string>();
//小程序ID
strParam.Add("appid", PayModel.AppID);//商品描述 對商品的簡單描述,次字段值會在 訂單詳情--商品 字段展現出來
strParam.Add("body", PayModel.body);
//商戶號 微信商戶平臺商戶號,10位數字
strParam.Add("mch_id", PayModel.mchid);
//隨機字符串
strParam.Add("nonce_str", PayModel.nonceStr);
//通知地址 (異步接收微信支付結果通知的回調地址,能夠看做一個後臺方法的完整路徑,通知url必須爲外網可訪問的url,不能攜帶參數。)
strParam.Add("notify_url", PayModel.notifyUrl);
//用戶標識
strParam.Add("openid", openId);
//商戶訂單號 必須保持惟一,若是針對同一個訂單重複提交,在訂單信息(如訂單金額)發生改變時,會致使prepay_id爲空
strParam.Add("out_trade_no", orderNo);
//終端IP
strParam.Add("spbill_create_ip", PayHelper.GetUserIP());
//標價金額 已分爲單位
strParam.Add("total_fee", order_cent.ToString());
//交易類型
strParam.Add("trade_type", PayModel.tradeType);
strParam.Add("sign", PayHelper.GetSignInfo(strParam, PayModel.WxMerchantKey));
#endregion

#region 統一下單接口返回結果
//獲取預支付ID
string preInfo = PayHelper.PostHttpResponse(PayModel.orderUrl, PayHelper.CreateXmlParam(strParam));
string return_code = PayHelper.GetXmlValue(preInfo, "return_code");
string return_msg = PayHelper.GetXmlValue(preInfo, "return_msg");

LogHelper.Writer("統一下單請求發送至微信,微信接口統一下單方法返回結果:/WXWecahtPay/GetPrayPayId 參數【" + strParam + "】,微信平臺返回結果【" + preInfo + "】", "", 5);

#endregion

#region 將統一下單的返回參數返回給小程序前臺

OrderReloadModel info = new OrderReloadModel();
if (return_code == "SUCCESS")
{
//再次簽名
string nonecStr = PayModel.nonceStr;
string timeStamp = PayModel.timeStamp;
string package = "prepay_id=" + PayHelper.GetXmlValue(preInfo, "prepay_id");
Dictionary<string, string> singInfo = new Dictionary<string, string>();
singInfo.Add("appId", PayModel.AppID);
singInfo.Add("nonceStr", nonecStr);
singInfo.Add("package", package);
singInfo.Add("signType", PayModel.signType);
singInfo.Add("timeStamp", timeStamp);
//將二次簽名後的參數返回給小程序
info.return_msg = return_msg;
info.return_code = return_code;
info.orderNo = orderNo;
info.AppID = PayModel.AppID;
info.package = package;
info.timeStamp = timeStamp;
info.nonecStr = nonecStr;
info.signType = PayModel.signType;
info.paysign = PayHelper.GetSignInfo(singInfo, PayModel.WxMerchantKey);
listMsg.Add(info);
if (listMsg.Count > 0)
{
result.ResultCode = Infrastructure.Enum.ReusltCode.OK;
result.ResultObj = listMsg;
}
}
else
{
info = new OrderReloadModel();
info.return_msg = return_msg;
info.return_code = return_code;
info.orderNo = orderNo;
listMsg.Add(info);
result.ResultCode = Infrastructure.Enum.ReusltCode.Fail;
result.ResultObj = listMsg;
}
#endregion
LogHelper.Writer("返回小程序支付參數接口 方法:/WXWecahtPay/GetPrayPayId 參數【string openId=" + openId + ", string orderNo=" + orderNo + "】,返回結果【" + JsonConvert.SerializeObject(result) + "】", "", 5);
}
catch (Exception ex)
{
result.ResultCode = Infrastructure.Enum.ReusltCode.Exception;
result.ResultMsg = ex.Message;
LogHelper.Writer("返回小程序支付參數接口:/WXWecahtPay/GetPrayPayId 接口異常 異常信息【" + ex.Message + "】", "", 5);
}
return new IMG_JsonResult(result);
}

  五、支付成功回調方法:

        /// <summary>
        /// 支付結果通知API
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public JsonResult OrderNotify()
        {
            string strResult = string.Empty;
            try
            {
                //1.將微信小程序請求支付的參數傳入body中,提交到微信服務器,並獲取微信通知的參數
                string strXML = PayHelper.GetPostStr();
                if (string.IsNullOrEmpty(strXML))
                {
                    strResult = "未檢測到支付業務!";
                    return new JsonResult(strResult);
                }

                LogHelper.Writer("支付請求發送至微信,微信接口回調方法:/WXWecahtPay/OrderNotify 參數【" + strXML + "", "", 5);

                //判斷是否請求成功
                if (PayHelper.GetXmlValue(strXML, "return_code") == "SUCCESS")
                {
                    //判斷是否支付成功
                    if (PayHelper.GetXmlValue(strXML, "result_code") == "SUCCESS")
                    {
                        //得到本次支付的簽名
                        string getSign = PayHelper.GetXmlValue(strXML, "sign");
                        //驗證簽名是否有效
                        string sign = PayHelper.GetSignInfo(PayHelper.GetFromXml(strXML), PayModel.WxMerchantKey);
                        if (sign == getSign)
                        {
                            //校驗訂單信息
                            string wxOrderNum = PayHelper.GetXmlValue(strXML, "transaction_id"); //微信訂單號
                            string orderNum = PayHelper.GetXmlValue(strXML, "out_trade_no");    //商戶訂單號
                            string orderTotal = PayHelper.GetXmlValue(strXML, "total_fee");
                            string openid = PayHelper.GetXmlValue(strXML, "openid");
                            string payMark = PayHelper.GetXmlValue(strXML, "transaction_id");   //微信支付訂單號
           //2.更新訂單的相關狀態
                            你的業務處理代碼...//3.返回一個xml格式的結果給微信服務器,完成支付流程,避免微信重複回調咱們的服務器,形成服務器沒必要要的開支
                            strResult = PayHelper.GetReturnXml("SUCCESS", "OK");
                        }
                        else
                        {
                            strResult = PayHelper.GetReturnXml("FAIL", "簽名不一致!");
                        }
                    }
                    else
                    {
                        strResult = PayHelper.GetReturnXml("FAIL", "支付通知失敗!");
                    }
                }
                else
                {
                    strResult = PayHelper.GetReturnXml("FAIL", "支付通知失敗!");
                }
                LogHelper.Writer("支付請求發送至微信,微信接口回調方法:/WXWecahtPay/OrderNotify 參數【" + strXML + "】 接口處理結果【" + strResult + "", "", 5);
            }
            catch (Exception ex)
            {
                strResult = ex.Message;
                LogHelper.Writer("支付請求發送至微信,微信接口回調方法:/WXWecahtPay/OrderNotify 接口異常 異常信息【" + ex.Message + "", "", 5);
            }
            return new JsonResult(strResult);
        }
相關文章
相關標籤/搜索