以下程式碼的功能是使用者可以實現業務中使用者虛擬錢包的錢提現到使用者銀行卡,其實本質上是把商戶的賬戶資金劃轉到使用者銀行卡,其實就是銀行轉賬,相關程式碼如下:
///此方法存在部分業務程式碼,核心方法是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; } } } }