zoukankan      html  css  js  c++  java
  • UnionPay,ChinaPay 最新 银联支付接口C#Asp.netMVC 版本

    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_MACHINESYSTEMCurrentControlSetserviceseventlog
    HKEY_LOCAL_MACHINESYSTEMCurrentControlSetserviceseventlogSecurity

    对这两条加入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

  • 相关阅读:
    【纯水题】POJ 1852 Ants
    【树形DP】BZOJ 1131 Sta
    【不知道怎么分类】HDU
    【树形DP】CF 1293E Xenon's Attack on the Gangs
    【贪心算法】CF Emergency Evacuation
    【思维】UVA 11300 Spreading the Wealth
    【树形DP】NOI2003 逃学的小孩
    【树形DP】BZOJ 3829 Farmcraft
    【树形DP】JSOI BZOJ4472 salesman
    【迷宫问题】CodeForces 1292A A NEKO's Maze Game
  • 原文地址:https://www.cnblogs.com/qidian10/p/4885038.html
Copyright © 2011-2022 走看看