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

  • 相关阅读:
    delphi 不规则窗体与桌面宠物
    delphi窗体透明但上面的控件不透明怎么实现
    IIS错误:在唯一密钥属性“fileExtension”设置为“.json”时,无法添加类型为“mimeMap”的重复集合项
    putty连接centos慢
    centos systemd占用大量内存
    laravel 163发送邮件设置及常见错误
    laravel 163发送邮件
    laravel npm run dev 错误 npm run dev error [npm ERR! code ELIFECYCLE]
    linux 如何指定nologin用户执行命令
    laravel Method IlluminateValidationValidator::validateReuqired does not exist.
  • 原文地址:https://www.cnblogs.com/qidian10/p/4885038.html
Copyright © 2011-2022 走看看