1、前言
上一篇写了《Core3.1 微信v3 JSAPI支付》,这个属于v3的接口规则,现在研究了下退款的接口我写的时候它属于v2接口规则文档。但凡微信支付文档里面写清楚点我也不会在这里记录一下。
2、干货
接口文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 现在看看是v3接口的了 我的天,他们在逗我玩一样,v2的写好了v3就放出来了。命途多舛啊。 v2请求的是xml文档格式的,这里又要温习一下旧知识了。
当时我记得清清楚楚 就说了请求要加签名,证书双向验证。官网文档没有写。Net的怎么加载证书 就说了一句windows下面直接安装证书就好了。结果 都是Bad Request 还是人工咨询吧 到最后才把代码找给我。我问人工客户地址给我看看,他说没有地址(肺都气炸。。。)
3、代码
这是封装的一个请求 ,代码加在上篇文章里面的那个请求里面
/// <summary> ///postXML请求 /// </summary> /// <param name="url">地址</param> /// <param name="requestString">参数(json格式)</param> /// <param name="path">p12文件路径</param> /// <param name="certPwd">密码</param> /// <returns>string</returns> public string PostXml(string url, string requestString,string path, string certPwd) { var handler = new HttpClientHandler { ClientCertificateOptions = ClientCertificateOption.Manual, SslProtocols = SslProtocols.Tls12, ServerCertificateCustomValidationCallback = (x, y, z, m) => true, }; //var path = Path.Combine(AppContext.BaseDirectory, "cert\iot3rd.p12"); handler.ClientCertificates.Add(new X509Certificate2(path, certPwd)); var client = new HttpClient(handler); var content = new StringContent(requestString); content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); //GetAwaiter().GetResult(); var httpResponseMessage = client.PostAsync(url, content).Result.Content.ReadAsStringAsync().Result; return httpResponseMessage; }
最主要的就是加了认证文件,p12文件路径跟密码,一般密码都是商户号。这就是微信文档说的双向验证
拼接请求参数,官网文档有验证这个的。排序的话我自己手动固定排序的,就那几个字段ABCDEFG都可以看到0.0。
/// <summary> /// 退费需要的字符 /// </summary> /// <param name="hospInfo"></param> /// <param name="regOrder"></param> /// <returns></returns> private string RequestRetreatParmar(Entity.Models.HospInfo hospInfo, RegOrder regOrder) { var guid = Guid.NewGuid().ToString("N").Substring(0, 30); var retreatNo = OrderHelper.GenerateNo("CFTF"); int money = Convert.ToInt32(regOrder.OwnFee * 100); //签名 string message = $"appid={hospInfo.WxAppid}&mch_id={hospInfo.WxMchid}&nonce_str={guid}&out_refund_no={retreatNo}&refund_desc=客户预约挂号退款&refund_fee={money}&total_fee={money}&transaction_id={regOrder.PlatformTradeId}&key={hospInfo.WxKey}"; var signMd5 = SecurityHelper.MD5EncrytString(message).ToUpper(); var dto = new { xml= new { appid = hospInfo.WxAppid, mch_id = hospInfo.WxMchid, nonce_str = guid, sign = signMd5, transaction_id = regOrder.PlatformTradeId, out_refund_no = retreatNo, total_fee = money, refund_fee = money, refund_desc = "客户预约挂号退款", } }; var json = JsonConvert.SerializeObject(dto); return json; }
我把这里MD5加密的代码也贴上
/// md5加密 /// </summary> /// <param name="inputString">字符串</param> /// <returns>加密过的字符串(不可以解密)</returns> public static string MD5EncrytString(string inputString) { MD5 md5 = System.Security.Cryptography.MD5.Create(); byte[] buffer = Encoding.UTF8.GetBytes(inputString); byte[] md5Buffer = md5.ComputeHash(buffer); md5.Clear(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < md5Buffer.Length; i++) { sb.Append(md5Buffer[i].ToString("x2")); } return sb.ToString(); }
请求这里,只要Return_code等于SUCCESS下面就可以做自己的业务逻辑了。里面返回的字段肯定有一个是你自己写了传进去的。通过这个字段查询数据库完成自己的逻辑。
//微信退费操作 var url = RequestUrl.PAYREFUND; var json = RequestRetreatParmar(hospInfoMoidel, regOrderModel); var xml = JsonConvert.DeserializeXmlNode(json); var postData = xml.InnerXml; var pathfile = _webHostEnvironment.WebRootPath + "/arsjkll/apiclient_cert.p12"; var wxPostResult = _httpClientFactoryHelper.PostXml(url, postData, pathfile, hospInfoMoidel.WxSslcertpassword); SaveLog("WeChatTuiFei", wxPostResult); var resultRep = wxPostResult.Replace("<![CDATA[", "").Replace("]]>", ""); XmlDocument doc = new XmlDocument(); doc.LoadXml(resultRep); string jsonText = JsonConvert.SerializeXmlNode(doc); SaveLog("WeChatTuiFei", jsonText); var resultModel = JsonConvert.DeserializeObject<RetreatNoResulDtoXml>(jsonText); var resultModelXml = resultModel.Xml; if (resultModelXml.Result_Code == "SUCCESS") { regOrderModel.RefundNo = resultModelXml.Out_Refund_No; regOrderModel.RefundState = 1; regOrderModel.RefundResult = resultModelXml.Return_Code; regOrderModel.RefundTime = DateTime.Now; payLogModel.RefundNo = resultModelXml.Out_Refund_No; payLogModel.RefundState = 1; payLogModel.RefundResult = resultModelXml.Return_Code; payLogModel.RefundTime = DateTime.Now; await _db.SaveChangesAsync(); return Result.ToSuccess(resultModel); } else return Result.ToFail(resultModelXml.Return_Msg);
我这里没有用notify_url返回接口 直接得到结果。下面是接收的Dto
public class RetreatNoResulDtoXml { public RetreatNoResulDto Xml { get; set; } } /// <summary> /// 退号|退费结果 /// </summary> public class RetreatNoResulDto { /// <summary> /// 返回状态码 /// SUCCESS:退款申请接收成功,结果通过退款查询接口查询 /// FAIL:提交业务失败 /// 此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断 /// 示例值:SUCCESS /// </summary> public string Return_Code { get; set; } /// <summary> /// 返回信息 /// 返回信息,如非空,为错误原因 /// 签名失败 /// 参数格式校验错误 /// 示例值:签名失败 /// </summary> public string Return_Msg { get; set; } //返回状态码(return_code)为SUCCESS的时候,包含以下字段 /// <summary> /// 业务结果 /// SUCCESS/FAIL /// 示例值:SUCCESS /// </summary> public string Result_Code { get; set; } /// <summary> /// 错误代码 /// 示例值:SYSTEMERROR /// </summary> public string Err_Code { get; set; } /// <summary> /// 错误代码描述 /// 结果信息描述 ///示例值:系统错误 /// </summary> public string Err_Code_Des { get; set; } public string Appid { get; set; } public string Mch_Id { get; set; } public string Sub_Appid { get; set; } public string Sub_Mch_Id { get; set; } public string Nonce_Str { get; set; } /// <summary> /// 签名 /// </summary> public string Sign { get; set; } public string Transaction_Id { get; set; } public string Out_Trade_No { get; set; } public string Out_Refund_No { get; set; } public string Refund_Id { get; set; } public int Refund_Fee { get; set; } public int Settlement_Refund_Fee { get; set; } public int Total_Fee { get; set; } public int Settlement_Total_Fee { get; set; } public string Fee_Type { get; set; } public int Cash_Fee { get; set; } }
返回的结果我存了日志 真不知道中间<![CDATA[SUCCESS]]> 这个花里胡哨的是咋想的 我直接 全部替换 "" 了。
<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> <appid><![CDATA[wx6ed9112323b1f1d3e04]]></appid> <mch_id><![CDATA[16045678769]]></mch_id> <nonce_str><![CDATA[9xonPINPBGO4y8WW]]></nonce_str> <sign><![CDATA[DABC2420B7B03FBA4D0027A9EBBF54D7]]></sign> <result_code><![CDATA[SUCCESS]]></result_code> <transaction_id><![CDATA[42000009呜呜呜呜276477024]]></transaction_id> <out_trade_no><![CDATA[YYGH20210129164746001]]></out_trade_no> <out_refund_no><![CDATA[CFTF20210129164806002]]></out_refund_no> <refund_id><![CDATA[50300407262021012905921959108]]></refund_id> <refund_channel><![CDATA[]]></refund_channel> <refund_fee>440</refund_fee> <coupon_refund_fee>0</coupon_refund_fee> <total_fee>440</total_fee> <cash_fee>440</cash_fee> <coupon_refund_count>0</coupon_refund_count> <cash_refund_fee>440</cash_refund_fee> </xml>
4、总结
看着几行代码 还给我排队等了好久终于问人工解决的,之前都没说请求头里面加载用户证书,可能是v3出来了没太在意吧。后面看看 v3接口 看样子应该跟统一下单一样了吧只要调用请求那个方法就可以了!
时间,抓起了就是黄金,虚度了就是流水;书,看了就是知识,没看就是废纸;理想,努力了才叫梦想,放弃了那只是妄想。努力,虽然未必会收获,但放弃,就一定一无所获。