使用招商银行云直连服务提现(.Net6)

以下代码的功能是用户可以实现业务中用户虚拟钱包的钱提现到用户银行卡,其实本质上是把商户的账户资金划转到用户银行卡,其实就是银行转账,相关代码如下:

///此方法存在部分业务代码,核心方法是doProcess方法且基本是可以复用的,其它的代码可以根据自身业务场景灵活处理;
public
async Task<WithDrawResultDto> WithDrawToBank(TransferToBankDto transferToBankDto) { decimal balance = await _repository.WalletRepository.GetWalletBalance(new Contract.User.UserBasicDto() { WeChatId = transferToBankDto.WeChatId, }); if (balance < transferToBankDto.Amount) { return new WithDrawResultDto() { WithDrawResult = false, Message = "可用余额不够,请重新输入提现金额。" }; } long userId = await _repository.UserRepository.GetUserIdByWeChatId(transferToBankDto.WeChatId); if (userId == 0) { return new WithDrawResultDto() { WithDrawResult = false, Message = "用户不存在" }; } //bool walletToBankResult = await _walletService.WalletToBankAccount(transferToBankDto.WeChatId, userId, transferToBankDto.Amount, (int)WalletCashType.Withdrawing, yurRef); //if (walletToBankResult) //{ Log.Information("begin WithDraw call cloud direct"); string reqId = DCHelper.GetTime() + RandomHelper.GeneratorRandomStr(7); decimal amount = transferToBankDto.Amount; string bankCode = await _repository.BankRepository.QueryBankCodeByBankAccount(userId, transferToBankDto.BankAccount); string bankHolder = await _repository.BankRepository.QueryCardHolder(userId, transferToBankDto.BankAccount); Log.Information($"bankHolder:{bankHolder}"); //记录业务编号 //业务编号记录到数据库 // 组织发送报文 JObject obj = new JObject(); JObject req = new JObject(); JArray bb1paybmx1BodyArray = new JArray(); JObject bb1paybmx1Body = new JObject(); JObject bb1paybmx1Body2 = new JObject(); JArray bb1payopx1BodyArray = new JArray(); JObject bb1payopx1Body = new JObject(); JObject bb1payopx1Body2 = new JObject(); JObject head = new JObject(); head.Add("funcode", FunCode);// FunCode = "BB1PAYOP";功能代码 head.Add("userid", UID);//用户编号,与商户相关 head.Add("reqid", reqId); bb1paybmx1Body.Add("busCod", BusCod);//BusCod = "N02030";业务编码 bb1paybmx1Body.Add("busMod", BusMod);///业务模式 bb1paybmx1BodyArray.Add(bb1paybmx1Body); //bb1paybmx1Body2.Add("bb1paybmx1", bb1paybmx1BodyArray); JArray bb1payopx1 = new JArray(); bb1payopx1Body.Add("dbtAcc", CompanyCard);//公司账号 bb1payopx1Body.Add("crtAcc", transferToBankDto.BankAccount);//收款人账号 bb1payopx1Body.Add("crtNam", bankHolder);//持卡人姓名 bb1payopx1Body.Add("ccyNbr", CurrencyCode);//币种 CurrencyCode = "10";//10代表人民币 bb1payopx1Body.Add("trsAmt", amount.ToString("#0.00")); //bb1payopx1Body.Add("crtBnk", "招商银行深圳分行"); bb1payopx1Body.Add("bnkFlg", (bankCode == CMBCode ? "Y" : "N"));//CMBCodeCMBCode = "CMB";如果是招商银行,bnkFlg的取值是Y bb1payopx1Body.Add("stlChn", StlChn);//StlChn为R代表及时到账 bb1payopx1Body.Add("nusAge", "钱包提现到银行卡"); //bb1payopx1Body.Add("yurRef", "202008260078000033"); bb1payopx1Body.Add("yurRef", yurRef);//业务流水号,可以使用雪花算法生成 bb1payopx1BodyArray.Add(bb1payopx1Body); JObject jobjectBodyList = new JObject(); jobjectBodyList.Add("bb1paybmx1", bb1paybmx1BodyArray); jobjectBodyList.Add("bb1payopx1", bb1payopx1BodyArray); JObject jobjectBody = new JObject(); jobjectBody.Add("body", jobjectBodyList); jobjectBody.Add("head", head); JObject requestBody = new JObject(); requestBody.Add("request", jobjectBody); //bb1payopx1Body2.Add("bb1payopx1", bb1payopx1BodyArray); // 请求发送接收 var withDrawResult = doProcess(requestBody, FunCode); if (withDrawResult.WithDrawResult == true) { bool walletToBankResult = await _walletService.WalletToBankAccount(transferToBankDto.WeChatId, userId, transferToBankDto.Amount, (int)WalletCashType.Withdrawing, yurRef); WithDrawToBankRecord withDrawToBankRecord = new WithDrawToBankRecord (userId: userId, funcode: FunCode, reqid: reqId, busCod: BusCod, busMod: BusMod, dbtAcc: CompanyCard, crtAcc: transferToBankDto.BankAccount, crtNam: bankHolder, ccyNbr: CurrencyCode, trsAmt: amount, bnkFlg: (bankCode == CMBCode ? "Y" : "N"), stlChn: StlChn, nusAge: "钱包提现到银行卡", yurRef: yurRef.ToString(), withdrawStatus: 0,openId: transferToBankDto.OpenId); await _repository.RedPackageRepository.CreateWithDrawToBankRecord(withDrawToBankRecord); await WithDrawSendMsgToClient.SendMsgToClient(openId: transferToBankDto.OpenId, templageId: SuccessTemplateId, amount: amount, withdrawDatetime: DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), WithdrawToBankStatus.Success); } //更新出金时间 await _repository.BankRepository.UpdateWithDrawDateTimeAsync(userId, transferToBankDto.BankAccount); return withDrawResult; //} //else //{ // return new WithDrawResultDto() { WithDrawResult = false,Message="提现失败,请稍后重试." }; //} }

DCHelper辅助类

using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace Demo.Utils
{
    public class DCHelper
    {

        public static string SerialJsonOrdered(JObject json)
        {
            List<string> keyList = new List<string>();
            foreach (var x in json)
            {
                keyList.Add(x.Key);
            }
            string[] keyArray = keyList.ToArray();
            Array.Sort(keyArray, string.CompareOrdinal);
            StringBuilder appender = new StringBuilder();
            appender.Append("{");
            bool isFirstEle = true;
            foreach (var key in keyArray)
            {
                if (!isFirstEle)
                {
                    appender.Append(",");
                }
                Object val = json[key];
                if (val is JObject)
                {
                    appender.Append("\"").Append(key).Append("\":");
                    appender.Append(SerialJsonOrdered((JObject)val));
                }
                else if (val is JArray)
                {
                    JArray jarray = (JArray)val;
                    appender.Append("\"").Append(key).Append("\":[");
                    bool isFirstArrEle = true;
                    for (int i = 0; i < jarray.Count; i++)
                    {
                        if (!isFirstArrEle)
                        {
                            appender.Append(",");
                        }
                        Object obj = jarray[i];
                        if (obj is JObject)
                        {
                            appender.Append(SerialJsonOrdered((JObject)obj));
                        }
                        else
                        {
                            appender.Append(obj.ToString());
                        }
                        isFirstArrEle = false;
                    }
                    appender.Append("]");
                }
                else if (((JToken)val).Type == JTokenType.String)
                {
                    string value = val.ToString();
                    appender.Append("\"").Append(key).Append("\":").Append("\"").Append(value.Replace("\\", "\\\\")).Append("\"");
                }
                else if (((JToken)val).Type == JTokenType.Boolean)
                {
                    string value = val.ToString().ToLower();
                    appender.Append("\"").Append(key).Append("\":").Append(value);
                }
                else
                {
                    string value = val.ToString();
                    appender.Append("\"").Append(key).Append("\":").Append(value);
                }
                isFirstEle = false;
            }
            appender.Append("}");
            return appender.ToString();
        }


        public static String GetTime()
        {
            return DateTime.Now.ToString("yyyyMMddHHmmssfff");
        }
        public static String GetTime2()
        {
            return DateTime.Now.ToString("yyyyMMddHHmmss");
        }
        // 发送Post请求
        public static String DoPostForm(String httpUrl, Hashtable param)
        {
            Encoding encoding = Encoding.GetEncoding("utf-8");
            HttpWebResponse response = PostHttps(httpUrl, param, encoding);
            Stream stream = response.GetResponseStream();
            StreamReader sr = new StreamReader(stream);
            return sr.ReadToEnd();
        }

        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            return true; //信任所有https站点证书,不安全,请勿在生产环境中使用。
        }

        private static HttpWebResponse PostHttps(string url, Hashtable param, Encoding encoding)
        {
            HttpWebRequest request = null;
            //ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); // 请勿在生产环境中使用该行程序
            request = WebRequest.Create(url) as HttpWebRequest;
            request.ProtocolVersion = HttpVersion.Version11;
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.Timeout = 15000;
            request.KeepAlive = false;
            request.ReadWriteTimeout = 60000;

            byte[] data = encoding.GetBytes(CreateLinkString(param));
            request.ContentLength = data.Length;
            using (Stream stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            WebResponse res;
            try { res = request.GetResponse(); }
            catch (WebException e)
            {
                res = e.Response;
            }
            return (HttpWebResponse)res;
        }

        private static string CreateLinkString(Hashtable param)
        {
            IEnumerator keys = param.Keys.GetEnumerator();
            StringBuilder prestr = new StringBuilder();
            int i = 0;
            while (keys.MoveNext())
            {
                i++;
                string key = keys.Current as string;
                string value = param[key] as string;
                if (i == param.Count)
                {
                    prestr.Append(key).Append("=").Append(value);
                }
                else
                {
                    prestr.Append(key).Append("=").Append(value).Append("&");
                }
            }
            return prestr.ToString();
        }
    }
}

RandomHelper辅助类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Demo.Utils
{
    public static class RandomHelper
    {
        public static string GeneratorRandomStr(int len)
        {
            string chars = "0123456789abcdefghijklmnopqrstuvwxyz";

            Random randrom = new Random((int)DateTime.Now.Ticks);

            string str = "";
            for (int i = 0; i < len; i++)
            {
                str += chars[randrom.Next(chars.Length)];
            }
            return str;
        }
      
    }
}

 

核心方法:

 private WithDrawResultDto doProcess(JObject jObject, string FUNCODE)
        {
            JObject obj = new JObject();
            // 签名
            obj.Add("sigdat", "__signature_sigdat__");
            obj.Add("sigtim", DCHelper.GetTime2());
            jObject.Add("signature", obj);
            string source = DCHelper.SerialJsonOrdered(jObject);
            Console.WriteLine("签名原文: " + source);
            Log.Information($"{FUNCODE} request data is {source}");
            Encoding encoding = Encoding.UTF8;
            byte[] signature1 = DCCryptor.CMBSM2SignWithSM3(GetID_IV(), Convert.FromBase64String(privkey), encoding.GetBytes(source));//privkey是私钥
            string sigdat1 = Convert.ToBase64String(signature1);
            Console.WriteLine("签名结果: " + sigdat1);
            obj["sigdat"] = sigdat1;

            // SM4-CBC加密
            string plaintxt = jObject.ToString();
            Console.WriteLine("加密前req:  " + plaintxt);

            byte[] enInput = DCCryptor.CMBSM4EncryptWithCBC(encoding.GetBytes(sm4key), GetID_IV(), encoding.GetBytes(plaintxt));//sm4key是用户的对称密钥
            string req = Convert.ToBase64String(enInput);
            Console.WriteLine("加密后req:  " + req);
            Log.Information("begin send request to cloud direct");
            // 发送请求
            Hashtable map = new Hashtable();
            map.Add("UID", UID);
            map.Add("ALG", ALG_SM);//ALG_SM = "SM";采用国密算法
            map.Add("DATA", HttpUtility.UrlEncode(req, encoding));
            map.Add("FUNCODE", FUNCODE);
            string res = DCHelper.DoPostForm(URL, map);//访问招商银行的api请求地址:(测试环境:http://cdctest.cmburl.cn/cdcserver/api/v2;正式环境:https://cdc.cmbchina.com/cdcserver/api/v2)
            Log.Information("end request");
            Console.WriteLine("res:  " + res);
            Log.Information("DoPostForm {@res}", res);
            try
            {
                Convert.FromBase64String(res);
            }

            catch (Exception ex)
            {
                Console.WriteLine("访问返回错误.");
                Log.Information(" Convert.FromBase64String error  {@ex}", ex);
                return new()
                {
                    WithDrawResult = false,
                    Message = "访问返回错误."
                };
            }

            // 解密请求
            string resplain = encoding.GetString(DCCryptor.CMBSM4DecryptWithCBC(encoding.GetBytes(sm4key), GetID_IV(), Convert.FromBase64String(res)));
            Console.WriteLine("res decrypt: " + resplain);
            Log.Information($"{FUNCODE} response data is {resplain}");
            // 验签
            JObject object2 = JObject.Parse(resplain);
            JObject? object3 = object2["signature"] as JObject;
            string? resSign = object3?["sigdat"]?.ToString();
            object3["sigdat"] = "__signature_sigdat__";
            object2["signature"] = object3;
            string resSignSource = DCHelper.SerialJsonOrdered(object2);
            Console.WriteLine("验签原文: " + resSignSource);
            Console.WriteLine("验签签名值: " + resSign);
            bool verify = DCCryptor.CMBSM2VerifyWithSM3(GetID_IV(), Convert.FromBase64String(bankpubkey), encoding.GetBytes(resSignSource), Convert.FromBase64String(resSign));//bankpubkey:银行公钥
            Console.WriteLine("验签结果: " + verify);
            Log.Information($"验签结果:{verify}");
            if (verify)
            {
                //验签成功以后正常判断
                if (object2["response"]?["head"]?["resultcode"] is not null)
                {
                    string? resultCode = object2["response"]?["head"]?["resultcode"]?.ToString();
                    if (resultCode != "SUC0000")
                    {
                        Log.Information($"withdraw hava error,resultmsg is {object2["response"]?["head"]?["resultmsg"]?.ToString()}," +
                               $"resultcode is {object2["response"]?["head"]?["resultcode"]?.ToString()}");
                        return new()
                        {
                            WithDrawResult = false,
                            Message = object2["response"]?["head"]?["resultmsg"]?.ToString()
                        };
                    }
                    else
                    {
                        string[] errors = new[] { "F", "B", "R", "D", "C" };
                        string? rtnFlg = object2["response"]?["body"]?["bb1payopz1"][0]?["rtnFlg"]?.ToString();
                        if (object2["response"]?["body"]?["bb1payopz1"][0]?["reqSts"]?.ToString() == "FIN" && errors.Contains(rtnFlg))
                        {
                            Log.Information($"withdraw hava error,errTxt is {object2["response"]?["body"]?["bb1payopz1"][0]?["errTxt"]?.ToString()}," +
                                $"errCod is {object2["response"]?["body"]?["bb1payopz1"][0]?["errCod"]?.ToString()}");
                            return new()
                            {
                                WithDrawResult = false,
                                Message = object2["response"]?["body"]?["bb1payopz1"][0]?["errTxt"]?.ToString()
                            };
                        }
                    }
                }
            }
            else
            {
                Log.Error("验签失败");
                return new()
                {
                    WithDrawResult = false,
                    Message = "验签失败,请联系系统管理员。"
                };
            }
            return new() { WithDrawResult = true };
        }

私有方法

 private byte[] GetID_IV()
        {
            String uid = UID; // 请替换为实际的用户UID
            String userid = uid + "0000000000000000";
            return Encoding.UTF8.GetBytes(userid.Substring(0, 16));
        }

 

DCCryptor辅助类

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Demo.Utils
{
    public class DCCryptor
    {
        public static byte[] CMBSM4EncryptWithCBC(byte[] key, byte[] iv, byte[] input)
        {
            if (key == null || iv == null || input == null)
            {
                throw new Exception("CMBSM4EncryptWithCBC 非法输入");
            }
            return CMBSM4Crypt(key, iv, input, true);
        }

        public static byte[] CMBSM4DecryptWithCBC(byte[] key, byte[] iv, byte[] input)
        {
            if (key == null || iv == null || input == null)
            {
                throw new Exception("CMBSM4DecryptWithCBC 非法输入");
            }
            return CMBSM4Crypt(key, iv, input, false);
        }

        public static byte[] CMBSM2SignWithSM3(byte[] id, byte[] privkey, byte[] msg)
        {
            if (privkey == null || msg == null)
            {
                throw new Exception("CMBSM2SignWithSM3 input error");
            }
            ECPrivateKeyParameters privateKey = EncodePrivateKey(privkey);
            SM2Signer signer = new SM2Signer();
            ParametersWithID parameters = new ParametersWithID(privateKey, id);
            signer.Init(true, parameters);
            signer.BlockUpdate(msg, 0, msg.Length);
            return DecodeDERSignature(signer.GenerateSignature());
        }

        public static bool CMBSM2VerifyWithSM3(byte[] id, byte[] pubkey, byte[] msg, byte[] signature)
        {

            if (pubkey == null || msg == null || signature == null)
            {
                throw new Exception("CMBSM2VerifyWithSM3 input error");
            }
            ECPublicKeyParameters publicKey = EncodePublicKey(pubkey);
            SM2Signer signer = new SM2Signer();
            ParametersWithID parameters = new ParametersWithID(publicKey, id);
            signer.Init(false, parameters);
            signer.BlockUpdate(msg, 0, msg.Length);
            return signer.VerifySignature(EncodeDERSignature(signature));
        }

        private static byte[] CMBSM4Crypt(byte[] keyBytes, byte[] iv, byte[] input, bool forEncrypt)
        {
            KeyParameter key = ParameterUtilities.CreateKeyParameter("SM4", keyBytes);
            ParametersWithIV ivParameterSpec = new ParametersWithIV(key, iv);
            IBufferedCipher cipher = CipherUtilities.GetCipher("SM4/CBC/PKCS7Padding");
            cipher.Init(forEncrypt, ivParameterSpec);
            return cipher.DoFinal(input);
        }

        private static ECPrivateKeyParameters EncodePrivateKey(byte[] value)
        {
            BigInteger d = new BigInteger(1, value);
            X9ECParameters spec = ECNamedCurveTable.GetByName("sm2p256v1");
            ECDomainParameters ecParameters = new ECDomainParameters(spec.Curve, spec.G, spec.N, spec.H, spec.GetSeed());
            return new ECPrivateKeyParameters(d, ecParameters);
        }

        public static ECPublicKeyParameters EncodePublicKey(byte[] value)
        {
            byte[] x = new byte[32];
            byte[] y = new byte[32];
            Array.Copy(value, 1, x, 0, 32);
            Array.Copy(value, 33, y, 0, 32);
            BigInteger X = new BigInteger(1, x);
            BigInteger Y = new BigInteger(1, y);
            X9ECParameters spec = ECNamedCurveTable.GetByName("sm2p256v1");
            ECPoint Q = spec.Curve.CreatePoint(X, Y);
            ECDomainParameters ecParameters = new ECDomainParameters(spec.Curve, spec.G, spec.N, spec.H, spec.GetSeed());
            return new ECPublicKeyParameters(Q, ecParameters);
        }

        private static byte[] DecodeDERSignature(byte[] signature)
        {
            Asn1InputStream stream = new Asn1InputStream(signature);
            Asn1Sequence primitive = (Asn1Sequence)stream.ReadObject();
            System.Collections.IEnumerator enumeration = primitive.GetEnumerator();
            enumeration.MoveNext();
            BigInteger R = ((DerInteger)enumeration.Current).Value;
            enumeration.MoveNext();
            BigInteger S = ((DerInteger)enumeration.Current).Value;
            byte[] bytes = new byte[64];
            byte[] r = Format(R.ToByteArray());
            byte[] s = Format(S.ToByteArray());
            Array.Copy(r, 0, bytes, 0, 32);
            Array.Copy(s, 0, bytes, 32, 32);
            return bytes;
        }

        private static byte[] EncodeDERSignature(byte[] signature)
        {
            byte[] r = new byte[32];
            byte[] s = new byte[32];
            Array.Copy(signature, 0, r, 0, 32);
            Array.Copy(signature, 32, s, 0, 32);
            Asn1EncodableVector vector = new Asn1EncodableVector();
            vector.Add(new DerInteger(new BigInteger(1, r)));
            vector.Add(new DerInteger(new BigInteger(1, s)));
            return (new DerSequence(vector)).GetEncoded();
        }

        private static byte[] Format(byte[] value)
        {
            if (value.Length == 32)
            {
                return value;
            }
            else
            {
                byte[] bytes = new byte[32];
                if (value.Length > 32)
                {
                    Array.Copy(value, value.Length - 32, bytes, 0, 32);
                }
                else
                {
                    Array.Copy(value, 0, bytes, 32 - value.Length, value.Length);
                }
                return bytes;
            }
        }
    }
}

 

posted @ 2023-03-04 23:06  tuohaibei  阅读(719)  评论(1编辑  收藏  举报