using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Edu.Common; using Edu.Common.API; using Edu.Common.Plugin; using Edu.Common.WeChatPayAPIv3; using Edu.Common.WeChatPayAPIv3.Model; using Edu.Common.WeChatPayAPIv3.Model.GenerateOrder; using Edu.Common.WeChatPayAPIv3.Model.QueryOrder; using Edu.Common.WeChatPayAPIv3.Model.QueryRefunds; using Edu.Common.WeChatPayAPIv3.Model.Refunds; using Edu.Common.WeChatPayAPIv3.Model.RefundsCallback; using Edu.Common.WeChatPayAPIv3.Model.WxPayCallback; using Edu.Model.Entity.Finance; using Edu.Model.ViewModel.Finance; using Edu.Model.ViewModel.User; using Edu.Module.Course; using Edu.Module.User; using Edu.WebApi.Filter; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; namespace Edu.WebApi.Controllers.WeChatPay { [Route("api/[controller]/[action]")] [ApiExceptionFilter] [ApiController] [EnableCors("AllowCors")] public class WeChatPayController : BaseController { /// <summary> /// 订单处理类对象 /// </summary> private readonly EducationContractModule educationContractModule = AOP.AOPHelper.CreateAOPObject<EducationContractModule>(); /// <summary> /// 订单处理类对象 /// </summary> private readonly EmployeeModule employeeModule = AOP.AOPHelper.CreateAOPObject<EmployeeModule>(); /// <summary> /// 订单处理类对象 /// </summary> private readonly OrderModule orderModule = AOP.AOPHelper.CreateAOPObject<OrderModule>(); private readonly object _lock = new object(); /// <summary> /// 统一下单接口 /// </summary> /// <returns></returns> [HttpPost] [AllowAnonymous] public ApiResult GenerateOrder() { JObject jobj = JObject.Parse(RequestParm.Msg.ToString()); int contractId = jobj.GetInt("contractId"); var orderModle = educationContractModule.GetEducationContractModule(contractId); if (orderModle.Status == 4) { return ApiResult.Failed("合同已取消"); } var orderNumber = $"{DateTime.Now:yyyyMMddHHmmssfff}{contractId}"; LogHelper.WriteInfo("合同Id:" + contractId + " 商户订单号:" + orderNumber); var helper = new WxPayHelper(WxPayConst.appid, WxPayConst.mchid, WxPayConst.serialNo, WxPayConst.privateKey); var notify_url = Config.sTenpayNotifyUrl;//ConfigurationManager.AppSettings["notify_url"]; //这个放在配置文件,从配置文件读取比较灵活,或者写到数据库中 Task<WxPayRespModel> payodel = helper.UnionGenerateOrder(orderModle.CourseName, Convert.ToInt32(orderModle.Money * 100), orderNumber, notify_url, orderModle.OrderId.ToString()); #region 调用微信支付 var signModel = WxPayForAppHelper.GetSign(WxPayConst.appid, payodel.Result.code_url, WxPayConst.privateKey); #endregion return ApiResult.Success(data: signModel); } /// <summary> /// 微信支付成功结果回调接口 /// </summary> /// <returns>退款通知http应答码为200且返回状态码为SUCCESS才会当做商户接收成功,否则会重试。注意:重试过多会导致微信支付端积压过多通知而堵塞,影响其他正常通知。</returns> [HttpPost] [HttpGet] [AllowAnonymous] public WxPayCallbackRespModel WxPayCallback(WxPayNotifyModel wxPayNotifyModel) { var viewModel = new WxPayCallbackRespModel(); try { // var wxPayNotifyModel = Common.Plugin.JsonHelper.DeserializeObject<WxPayNotifyModel>(Request.Body.ToString()); //str.ToObject<WxPayNotifyModel>(); var resource = wxPayNotifyModel?.resource ?? new WxPayResourceModel(); var decryptStr = AesGcmHelper.AesGcmDecrypt(resource.associated_data, resource.nonce, resource.ciphertext, WxPayConst.APIV3Key); LogHelper.WriteInfo("订单回调信息decryptStr" + decryptStr); var payModel = Common.Plugin.JsonHelper.DeserializeObject<WxPayResourceDecryptModel>(decryptStr); //decryptStr.ToObject<WxPayResourceDecryptModel>(); if (payModel.trade_state == "SUCCESS") { viewModel.code = "SUCCESS"; viewModel.message = ""; } if (string.IsNullOrEmpty(payModel.out_trade_no)) { viewModel.code = "FAIL"; viewModel.message = "数据解密失败"; } else { if (payModel != null && payModel.trade_state == "SUCCESS")//生成财务单据 { //然后进行数据库更新处理……等等其他操作 UpdateFinance(payModel);//生成财务单据以及生成支付记录 viewModel.code = "SUCCESS"; viewModel.message = ""; } } } catch (Exception ex) { Common.Plugin.LogHelper.Write(ex, "WxPayCallback"); viewModel.code = "FAIL"; viewModel.message = "数据解密失败"; } return viewModel; } /// <summary> /// 生成财务单据以及生成支付记录 /// </summary> /// <param name="payModel"></param> /// <returns></returns> [AllowAnonymous] public void UpdateFinance(WxPayResourceDecryptModel payModel) { LogHelper.WriteInfo("我是回调" + System.DateTime.Now); int contractId = Convert.ToInt32(payModel.out_trade_no[17..]); bool isAdd = false; if (!Cache.User.UserReidsCache.Exists(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no)) { LogHelper.WriteInfo(payModel.out_trade_no + "我是回调:" + System.DateTime.Now); Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1800); isAdd = true; Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1800); var oldOrderRecordModel = educationContractModule.GetOrderRecordList(new RB_Finance_OrderRecord { ContractId = contractId, OutTradeNo = payModel.out_trade_no, Type = 1 }).FirstOrDefault(); if (oldOrderRecordModel == null || oldOrderRecordModel.ID == 0) { isAdd = true; } if (isAdd) { LogHelper.WriteInfo(payModel.out_trade_no + "我要新增财务单据啦...." + System.DateTime.Now); var orderModle = educationContractModule.GetEducationContractModule(contractId); var orderModel = orderModule.GetClassOrderInfoModule(orderModle.OrderId); var financeConfig = educationContractModule.GetFinanceConfigList(new RB_Finance_Config_ViewModel { Group_Id = orderModle.Group_Id, Type = Common.Enum.Course.FinanceConfigTypeEnum.Tuition }).FirstOrDefault(); if (financeConfig == null) { financeConfig = new RB_Finance_Config_ViewModel(); } var userInfo = new Employee_ViewModel(); if (orderModel.CreateBy > 0) { userInfo = employeeModule.GetEmployeeListModule(new Model.ViewModel.User.Employee_ViewModel { Id = orderModel.CreateBy, Group_Id = orderModel.Group_Id }).ToList().FirstOrDefault(); } else if (orderModel.EnterID > 0) { userInfo = employeeModule.GetEmployeeListModule(new Model.ViewModel.User.Employee_ViewModel { Id = orderModel.EnterID, Group_Id = orderModel.Group_Id }).ToList().FirstOrDefault(); } else if (orderModel.CourseConsultantId > 0) { userInfo = employeeModule.GetEmployeeListModule(new Model.ViewModel.User.Employee_ViewModel { Id = orderModel.CourseConsultantId, Group_Id = orderModel.Group_Id }).ToList().FirstOrDefault(); } var OriginalFee = Math.Round((Convert.ToDecimal(Config.SettlementRate) / 100) * (Convert.ToDecimal(payModel.amount.payer_total) / 100), 2, MidpointRounding.AwayFromZero);//手续费 RB_Online_Trade_Detail model = new RB_Online_Trade_Detail(); RB_Finance_OrderRecord orderRecordModel = new RB_Finance_OrderRecord { ID = 0, Type = 1, FinanceId = 0, OrderId = orderModle.OrderId, ContractId = orderModle.Id, CreateDate = System.DateTime.Now, RB_Group_Id = orderModle.Group_Id, RB_School_Id = orderModle.School_Id, OutTradeNo = payModel.out_trade_no, TransactionId = payModel.transaction_id, TotalPrice = Convert.ToDecimal(payModel.amount.payer_total) / 100, ServiceFee = OriginalFee }; #region 财务单据数据组装 var detailList = new List<object> { new { CostTypeId = financeConfig?.CostTypeId ?? 533, Number = 1, OriginalMoney = Convert.ToDecimal(payModel.amount.payer_total) / 100, UnitPrice = Convert.ToDecimal(payModel.amount.payer_total) / 100, Remark = orderModle.StudentName + orderModle.CourseName + payModel.out_trade_no } }; var financeObj = new { IsPublic = 1, BType = 1,//item.AccountType, AccountId = financeConfig?.ClientID ?? 54, WBMoney = Convert.ToDecimal(payModel.amount.payer_total) / 100, TradeDate = System.DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd"), AccountNumber = System.DateTime.Now.AddDays(-1).ToString("yyyyMMdd"), TemplateId = financeConfig?.TempleteId ?? 139, OrderSource = 17, Remark = System.DateTime.Now.ToString("yyyy年MM月dd日") + "自动生成财务单据", detailList, OriginalFee, CreateBy = userInfo.Id, RemitterName = orderModle.StudentName, RB_Branch_Id = userInfo.School_Id, RB_Group_Id = userInfo.Group_Id, RB_Depart_Id = userInfo.Dept_Id, RB_CreateByName = userInfo.EmployeeName, RB_DepartName = userInfo.DeptName, RB_BranchName = userInfo.SchoolName, RB_GroupName = userInfo.GroupName, FinanceType = 2, orderModle.GuestId, orderModle.OrderId, TCIDList = orderModel.ClassId > 0 ? new List<int>() { orderModel.ClassId } : new List<int>(), }; #endregion string sign = EncryptionHelper.AesEncrypt(JsonHelper.Serialize(financeObj), Config.FinanceKey); var resultInfo = new { msg = sign, }; string apiResult = Common.Plugin.HttpHelper.HttpPost(Config.ReadConfigKey("IncomeFinanceApi"), JsonHelper.Serialize(resultInfo), ""); JObject parmsJob = JObject.Parse(apiResult); string resultCode = parmsJob.GetStringValue("resultCode"); int frid = parmsJob.GetInt("data", 0); if (resultCode == "1" && frid > 0)//新增记录 { orderRecordModel.FinanceId = frid; model = new RB_Online_Trade_Detail { ID = 0, Type = 1, OrderSource = 18, OrderId = orderModle.OrderId, Pay_Order = "", Trade_Order = payModel.out_trade_no, Third_Order = payModel.transaction_id, Money = Convert.ToDecimal(payModel.amount.payer_total) / 100, Pay_Way = 1, Interface_Company = 0, Version = "", Mch_Id = WxPayConst.mchid, User_Id = orderModle.GuestId.ToString(), Is_follow = 0, AppId = WxPayConst.appid, OpenId = payModel?.payer?.openid ?? "", Currency_Type = payModel.amount.payer_currency, Institution_Type = "", Card_Type = 0, Remarks = "教育订单信息财务单据:" + frid, Pay_Result = 0, Pay_Date = !string.IsNullOrWhiteSpace(payModel?.success_time ?? "") ? DateTime.SpecifyKind(Convert.ToDateTime(payModel.success_time), DateTimeKind.Utc) : Convert.ToDateTime("1990-01-01"), Payer_Id = orderModle.GuestId, Payer_Type = 0, Data_Source = frid, RB_Group_Id = orderModle.Group_Id, RB_Branch_Id = orderModle.School_Id, IsRefund = 0, RefundTrade_Order = "", RefundMoney = 0, RefundStatus = 0, FinanceId = 0, }; } bool result = educationContractModule.SetEducationContractFinance(model, orderRecordModel); Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1); } } else { LogHelper.WriteInfo("存在Key" + System.DateTime.Now); } } /// <summary> /// 根据商户订单号查询订单支付信息 /// </summary> /// <param name="orderNumber"></param> /// <returns></returns> [AllowAnonymous] [AllowRepeatAttribute] public ApiResult QueryOrder() { JObject jobj = JObject.Parse(RequestParm.Msg.ToString()); string orderNumber = jobj.GetStringValue("orderNumber"); var helper = new WxPayHelper(WxPayConst.appid, WxPayConst.mchid, WxPayConst.serialNo, WxPayConst.privateKey); var payModel = helper.QueryOrder(orderNumber).Result; return ApiResult.Success(data: payModel); } ///// <summary> ///// 根据商户订单号查询订单支付信息 ///// </summary> ///// <param name="orderNumber"></param> ///// <returns></returns> //[AllowAnonymous] //public async Task<WxPayStatusRespModel> QueryOrder() //{ // JObject jobj = JObject.Parse(RequestParm.Msg.ToString()); // string orderNumber = jobj.GetStringValue("orderNumber"); // var helper = new WxPayHelper(WxPayConst.appid, WxPayConst.mchid, WxPayConst.serialNo, WxPayConst.privateKey); // var payModel = await helper.QueryOrder(orderNumber); // return payModel; //} /// <summary> /// 申请支付退款 /// </summary> /// <param name="orderNumber"></param> /// <returns></returns> [HttpPost] [AllowAnonymous] public ApiResult Refunds() { JObject jobj = JObject.Parse(RequestParm.Msg.ToString()); int contractId = 0; //jobj.GetInt("contractId"); string outTradeNo = jobj.GetStringValue("OutTradeNo"); decimal RefundsPrice = jobj.GetDecimal("RefundsPrice"); if (!string.IsNullOrWhiteSpace(outTradeNo)) { contractId = Convert.ToInt32(outTradeNo[17..]); } var orderModle = educationContractModule.GetEducationContractModule(contractId); var orderRecordModel = educationContractModule.GetOrderRecordList(new RB_Finance_OrderRecord { ContractId = contractId, OutTradeNo = outTradeNo, Type = 2 }).FirstOrDefault(); if (orderRecordModel == null) { return ApiResult.Failed("未查询到当前商户订单号对应的支付记录"); } if (RefundsPrice > orderRecordModel.TotalPrice) { return ApiResult.Failed("退款金额不能大于收款金额"); } var helper = new WxPayHelper(WxPayConst.appid, WxPayConst.mchid, WxPayConst.serialNo, WxPayConst.privateKey); var refundNumber = $"{DateTime.Now:yyyyMMddHHmmssfff}{contractId}"; var payModel = helper.Refunds(orderRecordModel.OutTradeNo, refundNumber, orderModle.CourseName + "退款", Convert.ToInt32(RefundsPrice * 100), Convert.ToInt32(orderRecordModel.TotalPrice * 100), Config.sTenpayNotifyRefundUrl).Result; return ApiResult.Success(data: new { Result = payModel, MCh_Id = WxPayConst.mchid }); // return payModel; } /// <summary> /// 退款通知回调接口 /// </summary> /// <returns>退款通知http应答码为200且返回状态码为SUCCESS才会当做商户接收成功,否则会重试。注意:重试过多会导致微信支付端积压过多通知而堵塞,影响其他正常通知。</returns> [HttpPost] [HttpGet] [AllowAnonymous] public RefundsCallbackRespModel RefundsCallback(RefundsCallbackModel wxPayNotifyModel) { //我没有使用官方的那种验证数据安全性的方法,我解密出来数据之后,直接拿着商户退款订单号再去查询一下订单状态,然后再更新到数据库中。我嫌麻烦…… var resource = wxPayNotifyModel?.resource ?? new RefundsCallbackResourceModel(); var decryptStr = AesGcmHelper.AesGcmDecrypt(resource.associated_data, resource.nonce, resource.ciphertext, WxPayConst.APIV3Key); var payModel = Common.Plugin.JsonHelper.DeserializeObject<RefundsCallbackDecryptModel>(decryptStr); //decryptStr.ToObject<RefundsCallbackDecryptModel>(); var viewModel = new RefundsCallbackRespModel(); if (string.IsNullOrEmpty(payModel.out_trade_no)) { viewModel.code = "FAIL"; viewModel.message = "数据解密失败"; } else { // var payModel = await QueryRefunds(decryptModel.out_refund_no); if (payModel != null && payModel.refund_status == "SUCCESS")//生成财务单据 { UpdateRefundsCallbackFinance(payModel); //int contractId = Convert.ToInt32(payModel.out_trade_no[17..]); //var orderModle = educationContractModule.GetEducationContractModule(contractId); //var orderModel = orderModule.GetClassOrderInfo(orderModle.OrderId); //var oldOrderRecordModel = educationContractModule.GetOrderRecordList(new RB_Finance_OrderRecord { ContractId = contractId, OutTradeNo = payModel.out_trade_no }).FirstOrDefault(); //var userInfo = employeeModule.GetEmployeeListModule(new Model.ViewModel.User.Employee_ViewModel { Id = orderModel.EnterID, Group_Id = orderModel.Group_Id }).ToList().FirstOrDefault(); //var OriginalFee = Math.Round(Convert.ToDecimal((Convert.ToDecimal(Config.SettlementRate) / 100) * (Convert.ToDecimal(payModel.amount.payer_total) / 100)), 2, MidpointRounding.AwayFromZero);//手续费 //RB_Online_Trade_Detail model = new RB_Online_Trade_Detail(); //RB_Finance_OrderRecord orderRecordModel = new RB_Finance_OrderRecord //{ // ID = 0, // Type = 2, // FinanceId = 0, // OrderId = orderModle.OrderId, // ContractId = orderModle.Id, // CreateDate = System.DateTime.Now, // RB_Group_Id = orderModle.Group_Id, // RB_School_Id = orderModle.School_Id, // OutTradeNo = payModel.out_refund_no, // OrderRecordId = oldOrderRecordModel?.ID ?? 0, // TransactionId = payModel.refund_id, // TotalPrice = Convert.ToDecimal(payModel.amount.payer_total) / 100, // ServiceFee = OriginalFee //}; //bool result = educationContractModule.SetEducationContractFinance(model, orderRecordModel); viewModel.code = "SUCCESS"; viewModel.message = ""; } //然后进行数据库更新处理……等等其他操作 } return viewModel; } [AllowAnonymous] public void UpdateRefundsCallbackFinance(RefundsCallbackDecryptModel payModel) { int contractId = Convert.ToInt32(payModel.out_trade_no[17..]); bool isAdd = false; if (!Cache.User.UserReidsCache.Exists(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no)) { LogHelper.WriteInfo(payModel.out_trade_no + "我是回调:" + System.DateTime.Now); Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1800); isAdd = true; Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1800); var oldOrderRecordModel = educationContractModule.GetOrderRecordList(new RB_Finance_OrderRecord { ContractId = contractId, OutTradeNo = payModel.out_trade_no, Type = 2 }).FirstOrDefault(); if (oldOrderRecordModel == null || oldOrderRecordModel.ID == 0) { isAdd = true; } if (isAdd) { var orderModle = educationContractModule.GetEducationContractModule(contractId); var orderModel = orderModule.GetClassOrderInfoModule(orderModle.OrderId); var payOrderRecordModel = educationContractModule.GetOrderRecordList(new RB_Finance_OrderRecord { ContractId = contractId, OutTradeNo = payModel.out_trade_no, Type = 1 }).FirstOrDefault(); var userInfo = employeeModule.GetEmployeeListModule(new Model.ViewModel.User.Employee_ViewModel { Id = orderModel.EnterID, Group_Id = orderModel.Group_Id }).ToList().FirstOrDefault(); var OriginalFee = Math.Round(Convert.ToDecimal((Convert.ToDecimal(Config.SettlementRate) / 100) * (Convert.ToDecimal(payModel.amount.payer_total) / 100)), 2, MidpointRounding.AwayFromZero);//手续费 RB_Online_Trade_Detail model = new RB_Online_Trade_Detail(); RB_Finance_OrderRecord orderRecordModel = new RB_Finance_OrderRecord { ID = 0, Type = 2, FinanceId = 0, OrderId = orderModle.OrderId, ContractId = orderModle.Id, CreateDate = System.DateTime.Now, RB_Group_Id = orderModle.Group_Id, RB_School_Id = orderModle.School_Id, OutTradeNo = payModel.out_refund_no, OrderRecordId = payOrderRecordModel?.ID ?? 0, TransactionId = payModel.refund_id, TotalPrice = Convert.ToDecimal(payModel.amount.payer_refund) / 100, ServiceFee = OriginalFee }; bool result = educationContractModule.SetEducationContractFinance(model, orderRecordModel); Cache.User.UserReidsCache.Set(Cache.CacheKey.WeChatPay_Callback_Key + payModel.out_trade_no, payModel.out_trade_no, 1); } } else { LogHelper.WriteInfo("存在Key" + System.DateTime.Now); } } /// <summary> /// 查询退款结果接口 /// </summary> /// <param name="refundNumber">商户系统内部的退款单号,商户系统内部唯一</param> /// <returns></returns> [HttpGet] public async Task<QueryRefundsOrderRespModel> QueryRefunds(string refundNumber) { var helper = new WxPayHelper(WxPayConst.appid, WxPayConst.mchid, WxPayConst.serialNo, WxPayConst.privateKey); var payModel = await helper.QueryRefundsOrder(refundNumber); return payModel; } } }