diff --git a/GDZZ.Application/Entity/ReFund.cs b/GDZZ.Application/Entity/ReFund.cs new file mode 100644 index 0000000..97f7c97 --- /dev/null +++ b/GDZZ.Application/Entity/ReFund.cs @@ -0,0 +1,43 @@ +using System; +using SqlSugar; +using System.ComponentModel; +using GDZZ.Core.Entity; +namespace GDZZ.Application.Entity +{ + /// + /// 退款表 + /// + [SugarTable("Mini_Refund")] + [Description("退款表")] + public class ReFund : DEntityBase + { + /// + /// 退款金额 + /// + public decimal Amount { get; set; } + /// + /// 退款状态 + /// + public RefundStatusEnum State { get; set; } + /// + /// 退款消息 + /// + public string Message { get; set; } + /// + /// 退款单号 + /// + public string PayNo { get; set; } + /// + /// 商户退款单号 + /// + public string refundNo { get; set; } + /// + /// 微信订单号 + /// + public string TransactionNo { get; set; } + /// + /// 微信退款单号 + /// + public string RefundID { get; set; } + } +} \ No newline at end of file diff --git a/GDZZ.Application/Enum/RefundStatusEnum.cs b/GDZZ.Application/Enum/RefundStatusEnum.cs new file mode 100644 index 0000000..fe7b2c9 --- /dev/null +++ b/GDZZ.Application/Enum/RefundStatusEnum.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GDZZ.Application +{ + /// + /// 退款状态 + /// + public enum RefundStatusEnum + { + /// + /// 退款中 + /// + [Description("退款中")] REDIND = 0, + /// + /// 退款失败 + /// + [Description("退款失败")] REFAILED =1, + /// + /// 退款成功 + /// + [Description("退款成功")] RESUCCESS = 2, + } +} diff --git a/GDZZ.Application/GDZZ.Application.xml b/GDZZ.Application/GDZZ.Application.xml index 8815606..0b5c5fc 100644 --- a/GDZZ.Application/GDZZ.Application.xml +++ b/GDZZ.Application/GDZZ.Application.xml @@ -614,6 +614,46 @@ 推荐标题 + + + 退款表 + + + + + 退款金额 + + + + + 退款状态 + + + + + 退款消息 + + + + + 退款单号 + + + + + 商户退款单号 + + + + + 微信订单号 + + + + + 微信退款单号 + + 职业表 @@ -764,6 +804,26 @@ 储值 + + + 退款状态 + + + + + 退款中 + + + + + 退款失败 + + + + + 退款成功 + + 已发布 @@ -1293,6 +1353,17 @@ + + + 微信小程序退款 + + + + + + 退款回调 + + 折扣 @@ -1358,6 +1429,26 @@ 公司信息 + + + 订单编号 + + + + + 退款单号 + + + + + 真实支付金额 + + + + + 退款消息 + + 基础用户服务 diff --git a/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs b/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs new file mode 100644 index 0000000..5fb8388 --- /dev/null +++ b/GDZZ.Application/Service/Auth/DTO/WxRefundInput.cs @@ -0,0 +1,35 @@ +using GDZZ.Application.Enum; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GDZZ.Application +{ + public class WxRefundInput + { + + /// + /// 订单编号 + /// + public long OrderID { get; set; } + + /// + /// 退款单号 + /// + public string PayNo { get; set; } + + /// + /// 真实支付金额 + /// + public decimal RealPic { get; set; } + + /// + /// 退款消息 + /// + public string Message { get; set; } + + + } +} diff --git a/GDZZ.Application/Service/WXPay/WXPayService.cs b/GDZZ.Application/Service/WXPay/WXPayService.cs index ee413c4..80cb57d 100644 --- a/GDZZ.Application/Service/WXPay/WXPayService.cs +++ b/GDZZ.Application/Service/WXPay/WXPayService.cs @@ -23,7 +23,7 @@ using Microsoft.AspNetCore.Authorization; using System.Collections.Generic; using Mapster; using Furion.FriendlyException; - +using Senparc.CO2NET.Cache.Redis; namespace GDZZ.Application.Service.WXPay { @@ -42,7 +42,7 @@ namespace GDZZ.Application.Service.WXPay private readonly SqlSugarRepository rechargeRep; //充值仓储 private readonly SqlSugarRepository payTakeRep; //支付仓储 - + private readonly SqlSugarRepository refundRep; //退款仓储 private readonly WechatOAuth _wechatOAuth; //微信权限服务 private readonly IHttpContextAccessor _httpContextAccessor; //http服务 @@ -66,11 +66,13 @@ namespace GDZZ.Application.Service.WXPay SqlSugarRepository Self, SqlSugarRepository rechargeRep, SqlSugarRepository payTakeRep, + SqlSugarRepository refundRep, WechatOAuth wechatOAuth, IHttpContextAccessor _httpContextAccessor, IEventPublisher eventPublisher) { this.self = Self; + this.refundRep= refundRep; this.balance = balance; this.Baseuser = Baseuser; this.ComsumeRep = ComsumeRep; @@ -158,6 +160,10 @@ namespace GDZZ.Application.Service.WXPay } + + + #region 小程序支付 + /// ///微信小程序支付 /// @@ -192,7 +198,7 @@ namespace GDZZ.Application.Service.WXPay string nonceStr = TenPayV3Util.GetNoncestr(); TenPayV3UnifiedorderRequestData xmlDataInfo = new TenPayV3UnifiedorderRequestData(Config.SenparcWeixinSetting.WxOpenAppId, - Config.SenparcWeixinSetting.TenPayV3_MchId,"余额充值", sp_billno, + Config.SenparcWeixinSetting.TenPayV3_MchId, "余额充值", sp_billno, pMoney, "127.0.0.1", Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, TenPayV3Type.JSAPI, authUserInput.OpenID, @@ -218,6 +224,7 @@ namespace GDZZ.Application.Service.WXPay } + /// /// 微信小程序支付回调 /// @@ -226,23 +233,21 @@ namespace GDZZ.Application.Service.WXPay [Route("/Mini/NotifyUrl")] [UnifyResult(typeof(string))] [AllowAnonymous] - public async Task NotifyUrl() + public async Task NotifyUrl() { - + ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext); + ContentResult content = new ContentResult(); + string return_code = resHandler.GetParameter("return_code"); + string return_msg = resHandler.GetParameter("return_msg"); + resHandler.SetKey(Config.SenparcWeixinSetting.TenPayV3_Key); try { - ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext); - - string return_code = resHandler.GetParameter("return_code"); - string return_msg = resHandler.GetParameter("return_msg"); - //var res = ""; - resHandler.SetKey(Config.SenparcWeixinSetting.TenPayV3_Key); //验证请求是否从微信发过来(安全) if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS") { //res = "success";//正确的订单处理 - //直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息! + //直到这里,才能认为交易真正成功了,可以进行数据库操作,但是别忘了返回规定格式的消息! Console.WriteLine("回调成功"); //attach var paymentId = long.Parse(resHandler.GetParameter("attach")); @@ -273,65 +278,201 @@ namespace GDZZ.Application.Service.WXPay }); } } + content.Content = string.Format(@" + + + ", return_code, return_msg); + this.SendTemplate(0, return_code, resHandler); } else { - Console.WriteLine("回调失败"); - //res = "wrong";//错误的订单处理 + content.Content = "回调失败"; + } - /* 这里可以进行订单处理的逻辑 */ + this.LogRecord(resHandler); + } + catch (Exception ex) + { + content.Content = ex.Message; + } + return content; + + } + #endregion - //发送支付成功的模板消息 - try - { - string appId = Config.SenparcWeixinSetting.TenPayV3_AppId;//与微信公众账号后台的AppId设置保持一致,区分大小写。 - string openId = resHandler.GetParameter("openid"); - var templateData = new WeixinTemplate_PaySuccess("https://weixin.senparc.com", "微信支付 V2 购买商品", "状态:" + return_code); - Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息参数", appId + " , " + openId); + #region 小程序退款 + /// + ///微信小程序退款 + /// + /// + [HttpPost] + [Route("Mini/v1/refund")] + public async Task WxRefund(WxRefundInput wxRefundInput) + { + //查询订单 + var order = this.payTakeRep.FirstOrDefault(x => x.OrderId == wxRefundInput.OrderID); + + if (order == null) + return new Exception("查询无此订单,退款失败"); + + //生成退款单 + var res = await this.refundRep.InsertReturnEntityAsync(new ReFund() + { + State = RefundStatusEnum.REDIND, + Amount = order.PaymentMoney, + Message = "退款审核中", + PayNo = order.Id.ToString(), + }); + if (res == null) + return new Exception("生成退款单失败"); + + string notifyUrl = string.Format(Config.SenparcWeixinSetting.TenPayV3_TenpayNotify, "RefundNotifyUrl"); + string opUserId = Config.SenparcWeixinSetting.TenPayV3_MchId; + //退款金额 + int refundFee = (int)(order.PaymentMoney * 100); + //支付时的总金额 + int totalFee = (int)(order.PaymentMoney * 100); + + string outRefundNo = res.Id.ToString();//新退款单号 + string outTradeNo = order.OrderId.ToString(); + var nonceStr = TenPayV3Util.GetNoncestr(); + + var dataInfo = new TenPayV3RefundRequestData(Config.SenparcWeixinSetting.WxOpenAppId, Config.SenparcWeixinSetting.TenPayV3_MchId, + Config.SenparcWeixinSetting.TenPayV3_Key, null, nonceStr, null, outTradeNo, outRefundNo, totalFee, refundFee, opUserId, null, + notifyUrl: notifyUrl); + + return new + { + message = "退款请求完成", + }; + } + + + /// + /// 退款回调 + /// + [HttpPost("RefundNotifyUrl")] + [UnifyResult(typeof(string))] + public async Task RefundNotifyUrl() + { + ResponseHandler resHandler = new ResponseHandler(this._httpContextAccessor.HttpContext); + ContentResult content = new ContentResult(); + string return_code = resHandler.GetParameter("return_code"); + string return_msg = resHandler.GetParameter("return_msg"); + var mch_key = Senparc.Weixin.Config.SenparcWeixinSetting.TenPayV3_Key; + try + { + if (return_code.ToUpper() == "SUCCESS") + { + string req_info = resHandler.GetParameter("req_info"); + var decodeReqInfo = TenPayV3Util.DecodeRefundReqInfo(req_info, mch_key); + var decodeDoc = System.Xml.Linq.XDocument.Parse(decodeReqInfo); + var refundNotifyXml = decodeDoc.Serialize(); + + //获取接口中需要用到的信息 + string out_trade_no = decodeDoc.Root.Element("out_trade_no").Value; + string transaction_id = decodeDoc.Root.Element("transaction_id").Value; + string refund_id = decodeDoc.Root.Element("refund_id").Value; + int total_fee = int.Parse(decodeDoc.Root.Element("total_fee").Value); + int refund_fee = int.Parse(decodeDoc.Root.Element("refund_fee").Value); + string out_refund_no = decodeDoc.Root.Element("out_refund_no").Value; + + + var refun = this.refundRep.FirstOrDefault(x => x.PayNo == out_refund_no); + refun.PayNo = out_refund_no; + refun.Amount = ((float)refund_fee.ParseToInt() / 100).ParseToDecimal(); + refun.Message = "退款完成"; + refun.RefundID = refund_id; + refun.refundNo = out_refund_no; + refun.TransactionNo = transaction_id; + refun.State = RefundStatusEnum.RESUCCESS; + + if (this.refundRep.Update(refun) > 0) + { + content.Content = string.Format(@" + + + ", return_code, return_msg); + return content; + } - var result = Senparc.Weixin.MP.AdvancedAPIs.TemplateApi.SendTemplateMessage(appId, openId, templateData); } - catch (Exception ex) + else { - Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息", ex.ToString()); + content.Content = "回调处理失败!"; } + } + catch (Exception e) + { - #region 记录日志 + content.Content = e.Message; + } + return content; + } - var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/TenPayNotify/{0}", SystemTime.Now.ToString("yyyyMMdd"))); - if (!Directory.Exists(logDir)) - { - Directory.CreateDirectory(logDir); - } + #endregion - var logPath = Path.Combine(logDir, string.Format("{0}-{1}-{2}.txt", SystemTime.Now.ToString("yyyyMMdd"), SystemTime.Now.ToString("HHmmss"), Guid.NewGuid().ToString("n").Substring(0, 8))); - using (var fileStream = System.IO.File.OpenWrite(logPath)) - { - var notifyXml = resHandler.ParseXML(); - - fileStream.Write(Encoding.Default.GetBytes(notifyXml), 0, Encoding.Default.GetByteCount(notifyXml)); - fileStream.Close(); - } - #endregion - string xml = string.Format(@" - - - ", return_code, return_msg); - return xml; + #region 附加服务 + + public void SendTemplate(int type, string return_code, ResponseHandler resHandler) + { + + switch (type) + { + + case 0: + //发送支付成功的模板消息 + try + { + string appId = Config.SenparcWeixinSetting.TenPayV3_AppId;//与微信公众账号后台的AppId设置保持一致,区分大小写。 + string openId = resHandler.GetParameter("openid"); + var templateData = new WeixinTemplate_PaySuccess("https://weixin.senparc.com", "微信支付 V2 购买商品", "状态:" + return_code); + + Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息参数", appId + " , " + openId); + + var result = Senparc.Weixin.MP.AdvancedAPIs.TemplateApi.SendTemplateMessage(appId, openId, templateData); + } + catch (Exception ex) + { + Senparc.Weixin.WeixinTrace.SendCustomLog("支付成功模板消息", ex.ToString()); + } + break; + default: + break; } - catch (Exception ex) + + } + + + private void LogRecord(ResponseHandler resHandler) + { + + #region 记录日志 + + var logDir = ServerUtility.ContentRootMapPath(string.Format("~/App_Data/TenPayNotify/{0}", SystemTime.Now.ToString("yyyyMMdd"))); + if (!Directory.Exists(logDir)) { - WeixinTrace.WeixinExceptionLog(new WeixinException(ex.Message, ex)); - throw; + Directory.CreateDirectory(logDir); } + var logPath = Path.Combine(logDir, string.Format("{0}-{1}-{2}.txt", SystemTime.Now.ToString("yyyyMMdd"), SystemTime.Now.ToString("HHmmss"), Guid.NewGuid().ToString("n").Substring(0, 8))); + + using (var fileStream = System.IO.File.OpenWrite(logPath)) + { + var notifyXml = resHandler.ParseXML(); + + fileStream.Write(Encoding.Default.GetBytes(notifyXml), 0, Encoding.Default.GetByteCount(notifyXml)); + fileStream.Close(); + } + #endregion } + #endregion } }