UnionPay,ChinaPay 最新 銀聯支付介面C#\Asp.net\MVC 版本

jackchain發表於2015-10-16
1.概念普及

一、理解什麼是UnionPay、ChinaPay

這兩個概念如果搞不清楚,絕對夠你瞎折騰一段時間的。

UnionPay:中國銀聯,最大的機構;他本身也提供系統介面但都是B2B的,對於單個商戶他們不提供客服,也不提供技術解決,更不會提供商戶後臺(可查消費記錄等);但他的技術介面文件比較齊全,而且也可以使用,警惕不要使用這些介面。

ChinaPay:銀聯電子支付公司,第三方的支付公司,UnionPay的所有介面和服務都託管給類似的第三方公司,ChinaPay再向商戶服務,ChinaPay有自己的介面標準,但是非常不完善,目前只有java版本的案例;並且從UnionPay官網開通商戶後,預設根據地區會自動轉到諸如“ChinaPay”這樣的第三方支付公司,後面的事情全由ChinaPay代管。

2.ChinaPay商戶後臺

開通商戶之後,一般銷售會傳送郵件給你,一般郵件內容包含開戶名稱、技術支援聯絡方式等,另附帶一個壓縮包,主要包括如下內容:

txt:登入賬號資訊

cp.cer:加密公鑰

NetPayClient:.Net的元件和properity配置檔案

doc:介面文件等

logo:按鈕的標準圖片

3.登入ChinaPay商戶後臺獲取交易證照

http://merchant.chinapay.com ,建議用IE或者FireFox登入,需要先下載和安裝證照,安裝ActiveX控制元件,登入證照只提供兩份,多餘兩份的請聯絡銷售;

登入成功之後可以看到訂單、退款單等賬單;

要實現支付介面,必須先獲取一個“交易證照

點選交易申請證照,獲取到證照之後,需要將證照的上傳回商戶後臺,同時本地需要匯出證照私鑰(帶密碼),以備用。

4.RSA公鑰私鑰加密

本地商城等和ChinaPay介面通訊的時候都需要對資料進行加密和驗證有效性。這裡就牽扯加密的知識了。

RSA公鑰私鑰知識只需要記住下面這三點即可

1.一個端有公鑰和私鑰兩個檔案(或者兩個字串),通訊時候可以用公鑰或者私鑰加密

2.公鑰加密資料傳送,私鑰解密:保證資訊的完整性和保密性,保證加密後的資訊第三方劫持後無法檢視內容,例如郵件等。

3.私鑰加密資料傳送,公鑰解密:保證資料來源可靠,對資訊進行簽名,chinapay即使利用此方式,常用於公告、群發等操作。

5.準備工作

將NetPayClient檔案的dll引用到你的專案,在程式裡建立一個chinapay的資料夾,放入ChinaPay的公鑰(cp.cer)、私鑰(交易證照匯出的pfx檔案,有密碼),以及security.properties檔案

security.properties,開啟修改具體的cer、pfx檔案的路徑以及私鑰密碼

6.付款流程

跟其他產品一樣,根據引數構造一個form,然後提交到目標url中,提交同時加入簽名。

       log.Debug("開始支付...");
            string payUrl = "https://payment.chinapay.com/CTITS/service/rest/page/nref/000000000017/0/0/0/0/0";
            Hashtable myMap = new Hashtable();
            myMap.Add("MerId", ChinaPayHelper.merchantCode);
            string billNO = "CO" + DateTime.Now.ToString("yyyyMMddHHmmssfff");
            myMap.Add("MerOrderNo", billNO);
            myMap.Add("TranDate", DateTime.Now.ToString("yyyyMMdd"));//交易日期
            myMap.Add("TranTime", DateTime.Now.ToString("HHmmss"));//交易時間
            myMap.Add("OrderAmt", "20");
            myMap.Add("TranType", "0001");
            myMap.Add("BusiType", "0001");
            myMap.Add("MerPageUrl", Request.Url.Scheme + "://" + Request.Url.Authority + "/Home/PayResult");
            myMap.Add("MerBgUrl", Request.Url.Scheme + "://" + Request.Url.Authority + "/Home/PayBackRcv");
            myMap.Add("CurryNo", "CNY");

            myMap.Add("PayTimeOut", "145");
            myMap.Add("Version", "20140728");
            myMap.Add("CommodityMsg", "ChinaPay測試-商品資訊");
            myMap.Add("MerResv", "ChinaPay測試-商戶保留域");
            //myMap.Add("TranReserved", "{\"Referred\":\"www.chinapay.com\",\"BusiId\":\"0001\",\"TimeStamp\":\"1438915150976\",\"Remoteputr\":\"172.16.9.44\"}");  
            
            /***********************/
            //坑1:這裡注意,如果採用chinpay自帶的簽名,必須在伺服器的瀏覽器上獲取交易私鑰,否則由開發環境轉到生產環境會出錯!!!此坑注意
            //chinapaysecure.SecssUtil su = new chinapaysecure.SecssUtil();
            //string path = Server.MapPath("/cer/security.properties");
            //log.Debug(path);
            //bool flag = su.init(path);
            //su.sign(myMap);
            //if ("00" != su.getErrCode())
            //{
            //    log.Error(su.getErrCode() + "=" + su.getErrMsg());
            //    ViewBag.ChinaPay = "簽名過程發生錯誤,錯誤資訊為-->" + su.getErrMsg();
            //    return View();
            //}
            //myMap.Add("Signature", su.getSign());
            /*******************************************/

            //以下為採用自己擴充套件的簽名方法,無需關心測試還是生產環境的問題!!!
            string signValue = ChinaPayHelper.Sign(myMap);
myMap.Add("Signature", signValue); string sHtmlText = ChinaPayHelper.BuildRequest(myMap, payUrl); Response.Write(sHtmlText);

 

支付成功,前後臺通知頁面

//同步通知方法
        public ActionResult PayResult()
        {
            NameValueCollection coll = Request.Form;
            string[] requestItem = coll.AllKeys;
            Hashtable myMap = new Hashtable();
            for (int i = 0; i < requestItem.Length; i++)
            {
                myMap.Add(requestItem[i], Request.Form[requestItem[i]]);//前臺方法接收引數無需UrlDecode,但非同步方法必須轉換
            }
            SecssUtil su = new SecssUtil();
            su.init(Server.MapPath("/ChinaPay/CERS/security.properties"));
            su.verify(myMap);
            string billNo = myMap["MerOrderNo"].ToString();
            string billId = myMap["MerResv"].ToString();
            if ("00" != su.getErrCode())
            {
                log.Error("ChinaPayReturn銀聯支付返回資料驗證失敗:" + billNo + ";" + billId + ";" + su.getErrCode() + su.getErrMsg());
                ViewBag.ChinaPayResult = "ChinaPayReturn銀聯支付返回資料驗證失敗";
                return View();
            }
            StringBuilder sbHtml = new StringBuilder();
            sbHtml.Append("<table>");
            foreach (DictionaryEntry de in myMap)
            {
                sbHtml.Append("<tr><td>" + de.Key.ToString() + "</td><td>" + Server.UrlDecode(de.Value.ToString()) + "</td></tr>");
            }
            sbHtml.Append("</table>");
            ViewBag.ChinaPayResult=sbHtml.ToString();
            return View();
        }

        //非同步後臺方法,用於接收支付成功和退款成功的通知
        [HttpPost]
        public void PayBackRcv()
        {
            NameValueCollection coll = Request.Form;
            string[] requestItem = coll.AllKeys;
            Hashtable myMap = new Hashtable();
            for (int i = 0; i < requestItem.Length; i++)
            {
                log.Debug(requestItem[i] + "=" + HttpUtility.UrlDecode(Request.Form[requestItem[i]]));
                myMap.Add(requestItem[i], HttpUtility.UrlDecode(Request.Form[requestItem[i]]));//需要UrlDecode,否則驗籤失敗!!!
            }
            SecssUtil su = new SecssUtil();
            su.init(Server.MapPath("/ChinaPay/CERS/security.properties"));
            su.verify(myMap);
            string billNo = myMap["MerOrderNo"].ToString();
            string billId = myMap["MerResv"].ToString();
            if ("00" != su.getErrCode())
            {
                log.Error("ChinaPayNotify銀聯支付返回資料驗證失敗:" + billNo + ";" + billId + ";" + su.getErrCode() + su.getErrMsg());
                Response.Write("fail");
                return;
            }
            //處理您的業務邏輯

            //END
        }

非同步方法無需返回特定的code給chinapay,chinapay是根據httpstatus判斷的,如果是200,都認為收到訊息了。

 

坑2:如果出現簽名失敗,需要新增登錄檔許可權,一般本機測試用管理員開啟vs不存在這個問題,部署到伺服器之後需要加許可權

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Security

對這兩條加入Network service的讀寫許可權。

 

之後可以在Window日誌中除錯支付中碰到的問題了。

7. 退款
string cpRefundUrl = "https://payment.chinapay.com/CTITS/service/rest/forward/syn/000000000065/0/0/0/0/0";
            Hashtable myMap = new Hashtable();
            myMap.Add("MerId", ChinaPayHelper.merchantCode);
            myMap.Add("MerOrderNo", "RO"+DateTime.Now.ToString("yyyyMMddHHmmssfff"));
            myMap.Add("TranDate", DateTime.Now.ToString("yyyyMMdd"));//交易日期
            myMap.Add("TranTime", DateTime.Now.ToString("HHmmss"));//交易時間

            myMap.Add("OriOrderNo", "20151010152643514");//改成單號
            myMap.Add("OriTranDate", "20151010");
            myMap.Add("RefundAmt", "10");
            //myMap.Add("OrderAmt", "10");

            myMap.Add("TranType", "0401");
            myMap.Add("BusiType", "0001");
            myMap.Add("MerResv", Guid.NewGuid().ToString());

            myMap.Add("MerBgUrl", Request.Url.Scheme + "://" + Request.Url.Authority + "/Home/PayBackRcv");
            myMap.Add("CurryNo", "CNY");
            myMap.Add("Version", "20140728");


            string param = ChinaPayHelper.sort(myMap, null);
            String chkValue = ChinaPayHelper.Sign(myMap);
            myMap.Add("Signature", chkValue);
            param += "&Signature=" + HttpUtility.UrlEncode(chkValue);
            log.Debug("銀聯退款:" + param);
            string rst = ChinaPayHelper.Post(cpRefundUrl + "?" + param, "");
            log.Debug(rst);
            Hashtable rstMap = ChinaPayHelper.Str2HashMap(rst);
            if ("0000,1022,1003".Contains(rstMap["respCode"].ToString()))
            {
                return View(("[" + rstMap["respCode"].ToString() + "-" + rstMap["respMsg"].ToString() + "] 銀聯退款已提交,退款成功後將自動返回錢款到客戶銀聯卡!"));
            }
            else
            {
                return View(("[" + rstMap["respCode"].ToString() + "-" + rstMap["respMsg"].ToString() + "] 銀聯退款失敗!"));
            }

注意修改原始單號和原始交易日期以及退款金額:

myMap.Add("OriOrderNo", "20151010152643514");//改成單號
myMap.Add("OriTranDate", "20151010");
myMap.Add("RefundAmt", "10");

一般退款會在24小時內完成
8.退款沒有非同步通知???

真碰到過,聯絡客服之後,說是介面問題,他們修復好了之後便正常了,所以如果遇到什麼問題,實在解決不了,趕緊電話客服,chinapay不是那麼靠譜的。。。。!!!

9.自己封裝的工具類,除了sign是override官方的,其他都是官方原始檔裡複製除了的方法,部分方法值得優化哦
using chinapaysecure;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;

/******
jackchain
jackchain@chinacloudtech.com
2015-10-08
*****/
namespace ZPS.Tools { public class ChinaPayHelper { #region 基礎配置 public static string merchantCode = "481601509175565"; //商戶號 private static string privateKeyPath = "/ChinaPay/CERS/donoratico.pfx"; //私鑰檔案地址 private static string privateKeyPwd = "byby1231818"; //私鑰密碼 #endregion /// <summary> /// 簽名,注意官方給出的簽名方法,要求私鑰必須是安裝在伺服器的,假若安裝在本地測試機器上,等釋出到生產環境中後會出現證照無法使用的錯誤 /// </summary> /// <param name="param">需要加密的字串</param> /// <returns></returns> public static string Sign(Hashtable myMap) { string param = sort(myMap, null); X509Certificate2 certificate = new X509Certificate2(System.Web.HttpContext.Current.Server.MapPath(privateKeyPath), privateKeyPwd, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); RSAParameters key = ((RSACryptoServiceProvider)certificate.PrivateKey).ExportParameters(true); return Convert.ToBase64String(HashAndSignBytes(Encoding.UTF8.GetBytes(param), key)); } /// <summary> /// 構造提交表單 /// </summary> /// <param name="myMap"></param> /// <param name="actionUrl"></param> /// <returns></returns> public static string BuildRequest(Hashtable myMap, string actionUrl) { StringBuilder sbHtml = new StringBuilder(); sbHtml.Append("<form id='cpsubmit' name='cpsubmit' action='" + actionUrl + "' method='POST'>"); foreach (DictionaryEntry de in myMap) { sbHtml.Append("<input type='hidden' name='" + de.Key.ToString() + "' value='" + de.Value.ToString() + "'/>"); } //submit按鈕控制元件請不要含有name屬性 sbHtml.Append("<input type='submit' value='CHINAPAY' style='display:none;'></form>"); sbHtml.Append("<script>document.forms['cpsubmit'].submit();</script>"); return sbHtml.ToString(); } /// <summary> /// 呼叫遠端Restful服務 /// </summary> /// <param name="url">url地址</param> /// <param name="param">引數</param> /// <param name="time">超時時間</param> /// <returns></returns> public static string Post(string url, string param, int time = 60000) { Uri address = new Uri(url); HttpWebRequest request = WebRequest.Create(address) as HttpWebRequest; request.Method = "POST"; request.ContentType = "application/json;charset=utf-8"; //"application/x-www-form-urlencoded"; request.Timeout = time; byte[] byteData = UTF8Encoding.UTF8.GetBytes(param == null ? "" : param); request.ContentLength = byteData.Length; using (Stream postStream = request.GetRequestStream()) { postStream.Write(byteData, 0, byteData.Length); } string result = ""; using (HttpWebResponse response = request.GetResponse() as HttpWebResponse) { StreamReader reader = new StreamReader(response.GetResponseStream()); result = reader.ReadToEnd(); } return (result); } /// <summary> /// 銀聯應答引數轉換為雜湊表 /// </summary> /// <param name="param"></param> /// <returns></returns> public static Hashtable Str2HashMap(string param) { string[] kv = param.Split('&'); Hashtable map = new Hashtable(); foreach (string str in kv) { string[] temp = str.Split('='); if (temp != null && temp.Length >= 2) { map.Add(temp[0], temp[1]); } } return map; } #region 以下方法ChinaPay private static byte[] HashAndSignBytes(byte[] DataToSign, RSAParameters Key) { try { RSACryptoServiceProvider provider = new RSACryptoServiceProvider(); provider.ImportParameters(Key); return provider.SignData(DataToSign, new SHA512CryptoServiceProvider()); } catch (CryptographicException exception) { Console.WriteLine(exception.Message); return null; } } public static string sort(Hashtable paramHashTable, string[] invalidList) { IDictionaryEnumerator enumerator = paramHashTable.GetEnumerator(); ArrayList list = new ArrayList(); while (enumerator.MoveNext()) { string str = enumerator.Key.ToString(); enumerator.Value.ToString(); if (((invalidList == null) || (invalidList.Length <= 0)) || !invalidList.Contains<string>(str)) { list.Add(str); } } IComparer comparer = new myReverserClass(); list.Sort(comparer); string str2 = string.Empty; for (int i = 0; i < list.Count; i++) { string str3 = list[i].ToString(); string str4 = paramHashTable[str3].ToString(); if (i == (list.Count - 1)) { str2 = str2 + str3 + "=" + str4; } else { string str5 = str2; str2 = str5 + str3 + "=" + str4 + "&"; } } StringBuilder builder = new StringBuilder(str2); return builder.ToString(); } #endregion } }

 

11.總結

1.理解unionpay、chinapay可以少走很多彎路

2.理解RSA方法、警惕交易證照申請和安裝,否則很可能導致測試環境能用、正式環境無法sign

3.伺服器登錄檔許可權,EventLog需要新增IIS許可權

4.多問客服

 

一個不小的支付公司,卻沒有全語言的demo,真是悲哀,只好貢獻一個.Net的了(MVC)下載demo http://download.csdn.net/detail/ovenj/9186317

相關文章