From 8ae2abaabd101847d2b54229c60e530c64f0794d Mon Sep 17 00:00:00 2001 From: coolcalf <28551@qq.com> Date: Sun, 30 Jun 2024 23:48:23 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Admin.NET/Admin.NET.Co?= =?UTF-8?q?re/Entity/SysWechatPay.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs index 62e5c127..467385e0 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs @@ -154,7 +154,7 @@ public partial class SysWechatPay : EntityBase /// /// 子商户AppId /// - [SugarColumn(ColumnDescription = "回调通知地址")] + [SugarColumn(ColumnDescription = "子商户AppId")] public string? SubAppId { get; set; } /// From 2180363d091d262db38afb8ca88a5f210a353db0 Mon Sep 17 00:00:00 2001 From: coolcalf <28551@qq.com> Date: Sun, 30 Jun 2024 23:56:04 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Admin.NET/Admin.NET.Co?= =?UTF-8?q?re/Entity/SysWechatPay.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs index 467385e0..c0ef2c84 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs @@ -11,8 +11,15 @@ namespace Admin.NET.Core; /// [SugarTable(null, "系统微信支付表")] [SysTable] +[SugarIndex("sys_wechat_pay_order_id", nameof(OrderId), OrderByType.Desc)] public partial class SysWechatPay : EntityBase { + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + [SugarColumn(ColumnDescription = "OrderStatus")] + public virtual int OrderStatus { get; set; } + /// /// 微信商户号 /// From 72a99e199aabc2090feb32de6c5c926ef8f9c954 Mon Sep 17 00:00:00 2001 From: coolcalf <28551@qq.com> Date: Mon, 1 Jul 2024 00:19:48 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=94=AF=E4=BB=98=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=AE=A2=E5=8D=95=E5=8F=B7(OrderId)?= =?UTF-8?q?=E5=92=8C=E8=AE=A2=E5=8D=95=E7=8A=B6=E6=80=81(OrderStatus)=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=80=80=E6=AC=BE=E7=9B=B8=E5=85=B3=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E5=8F=8A=E6=94=AF=E4=BB=98=E7=BB=93=E6=9E=9C=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin.NET.Core/Entity/SysWechatPay.cs | 6 + .../Admin.NET.Core/Entity/SysWechatRefund.cs | 121 +++++++ .../Service/Wechat/Dto/WechatPayInput.cs | 101 ++++++ .../Service/Wechat/Dto/WechatPayOutput.cs | 8 +- .../Service/Wechat/SysWechatPayService.cs | 311 +++++++++++++++--- 5 files changed, 494 insertions(+), 53 deletions(-) create mode 100644 Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs index c0ef2c84..aff68a15 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs @@ -14,6 +14,12 @@ namespace Admin.NET.Core; [SugarIndex("sys_wechat_pay_order_id", nameof(OrderId), OrderByType.Desc)] public partial class SysWechatPay : EntityBase { + /// + /// 关联的商户订单号 + /// + [SugarColumn(ColumnDescription = "OrderId")] + public virtual long OrderId { get; set; } + /// /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) /// diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs new file mode 100644 index 00000000..8a309673 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs @@ -0,0 +1,121 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 微信退款表 +/// +[SugarTable(null, "系统微信退款表")] +[SysTable] +[SugarIndex("sys_wechat_refund_order_id", nameof(OrderId), OrderByType.Desc)] +public class SysWechatRefund : EntityBase +{ + /// + /// 微信支付订单号(原支付交易对应的微信订单号) + /// + [SugarColumn(ColumnDescription = "微信支付订单号", Length = 32)] + [Required] + public string TransactionId { get; set; } + + /// + /// 商户订单号(原交易对应的商户付款单号) + /// + [SugarColumn(ColumnDescription = "商户付款单号", Length = 32)] + [Required] + public string OutTradeNumber { get; set; } + + /// + /// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 + /// + [SugarColumn(ColumnDescription = "商户退款单号", Length = 64)] + [Required] + public string OutRefundNo { get; set; } + + /// + /// 退款原因,示例:商品已售完 + /// + [SugarColumn(ColumnDescription = "退款原因", Length = 80)] + public string Reason { get; set; } + + /// + /// 退款金额 + /// + [SugarColumn(ColumnDescription = "退款金额")] + public int Refund { get; set; } + + /// + /// 原订单总金额 + /// + [SugarColumn(ColumnDescription = "订单总金额")] + public int Total { get; set; } + + /// + /// 退款结果回调url + /// + [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)] + public string? NotifyUrl { get; set; } + + /// + /// 退款资金来源, 可不传,默认使用未结算资金退款(仅对老资金流商户适用) + /// + [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)] + public string FundsAccount { get; set; } + + /// + /// 关联的商户订单号 + /// + [SugarColumn(ColumnDescription = "关联的用户订单号")] + public long OrderId { get; set; } + + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + [SugarColumn(ColumnDescription = "关联的商户订单状态")] + public int OrderStatus { get; set; } + + /// + /// 关联的商户商品编码 + /// + [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)] + public string MerchantGoodsId { get; set; } + + /// + /// 关联的商户商品名称 + /// + [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)] + public string GoodsName { get; set; } + + /// + /// 关联的商户商品单价 + /// + [SugarColumn(ColumnDescription = "关联的商户商品单价")] + public int UnitPrice { get; set; } + + /// + /// 关联的商户商品退款金额 + /// + [SugarColumn(ColumnDescription = "关联的商户商品退款金额")] + public int RefundAmount { get; set; } + + /// + /// 关联的商户商品退货数量 + /// + [SugarColumn(ColumnDescription = "关联的商户商品退货数量")] + public int RefundQuantity { get; set; } = 1; + + /// + /// 附加数据 + /// + [SugarColumn(ColumnDescription = "附加数据")] + public string? Attachment { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs index 4d29621a..84fe10af 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs @@ -32,6 +32,16 @@ public class WechatPayTransactionInput /// 优惠标记 /// public string GoodsTag { get; set; } + + /// + /// 关联的商户订单号 + /// + public long OrderId { get; set; } + + /// + /// 关联的商户订单付款状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + public int OrderStatus { get; set; } = 0; } public class WechatPayParaInput @@ -40,4 +50,95 @@ public class WechatPayParaInput /// 订单Id /// public string PrepayId { get; set; } +} + + +public class RefundRequestInput // : WechatTenpayRequest +{ + /// + /// 商户订单号(原支付交易对应的付款单号) + /// + public string OutTradeNumber { get; set; } + ///// + ///// 退款单号 + ///// + //public string OutRefundNumber { get; set; } + /// + /// 原订单金额(分) + /// + public int Total { get; set; } + /// + /// 退款金额(分) + /// + public int Refund { get; set; } + /// + /// 退款原因 + /// + public string Reason { get; set; } + /// + /// 关联的商户订单号 + /// + public long OrderId { get; set; } + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + public int OrderStatus { get; set; } + /// + /// 关联的商户商品编码 + /// + public string MerchantGoodsId { get; set; } + /// + /// 关联的商户商品名称 + /// + public string GoodsName { get; set; } + /// + /// 关联的商户商品单价 + /// + public int UnitPrice { get; set; } = 0; + /// + /// 关联的商户商品退款金额 + /// + public int RefundAmount { get; set; } = 0; + /// + /// 关联的商户商品退货数量 + /// + public int RefundQuantity { get; set; } = 1; + /// + /// 附加数据 + /// + public string Attachment { get; set; } + /// + /// 备注 + /// + public string Remark { get; set; } +} + +public class PageSysWechatPayInput : BasePageInput +{ + /// + /// order_id + /// + /// + public long? OrderId { get; set; } = -1; + /// + /// order_status + /// + /// + public long? OrderStatus { get; set; } = -1; + + /// + /// out_trade_number + /// + /// + public string OutTradeNumber { get; set; } + /// + /// success_time范围 + /// + /// + public List SuccessTimeRange { get; set; } + /// + /// expire_time范围 + /// + /// + public List ExpireTimeRange { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs index b00e16aa..5492515d 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs @@ -27,4 +27,10 @@ public class WechatPayOutput /// 优惠标记 /// public string GoodsTag { get; set; } -} \ No newline at end of file +} + +public class CreatePayTransactionOutput +{ + public string PrepayId { get; set; } + public string OutTradeNumber { get; set; } +} diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs index 957af35e..1a6d9360 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs @@ -12,18 +12,20 @@ namespace Admin.NET.Core.Service; [ApiDescriptionSettings(Order = 210)] public class SysWechatPayService : IDynamicApiController, ITransient { - private readonly SqlSugarRepository _sysWechatPayUserRep; - + private readonly SqlSugarRepository _sysWechatPayRep; + private readonly SqlSugarRepository _sysWechatRefundRep; private readonly WechatPayOptions _wechatPayOptions; private readonly PayCallBackOptions _payCallBackOptions; private readonly WechatTenpayClient _wechatTenpayClient; - public SysWechatPayService(SqlSugarRepository sysWechatPayUserRep, - IOptions wechatPayOptions, - IOptions payCallBackOptions) + public SysWechatPayService(SqlSugarRepository sysWechatPayRep + , SqlSugarRepository sysWechatRefundRep + , IOptions wechatPayOptions + , IOptions payCallBackOptions) { - _sysWechatPayUserRep = sysWechatPayUserRep; + _sysWechatPayRep = sysWechatPayRep; + this._sysWechatRefundRep = sysWechatRefundRep; _wechatPayOptions = wechatPayOptions.Value; _payCallBackOptions = payCallBackOptions.Value; @@ -64,11 +66,20 @@ public class SysWechatPayService : IDynamicApiController, ITransient /// 微信支付统一下单获取Id(商户直连) 🔖 /// [DisplayName("微信支付统一下单获取Id(商户直连)")] - public async Task CreatePayTransaction([FromBody] WechatPayTransactionInput input) + public async Task CreatePayTransaction([FromBody] WechatPayTransactionInput input) { + string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一) + + //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus); + if (wechatPay != null) + { + outTradeNumber = wechatPay.OutTradeNumber; + } + var request = new CreatePayTransactionJsapiRequest() { - OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + OutTradeNumber = outTradeNumber, AppId = _wechatPayOptions.AppId, Description = input.Description, Attachment = input.Attachment, @@ -80,27 +91,32 @@ public class SysWechatPayService : IDynamicApiController, ITransient }; var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request); if (!response.IsSuccessful()) - throw Oops.Oh(response.ErrorMessage); + throw Oops.Oh(response.ErrorMessage); - // 保存订单信息 - var wechatPay = new SysWechatPay() + if (wechatPay == null) { - AppId = _wechatPayOptions.AppId, - MerchantId = _wechatPayOptions.MerchantId, - OutTradeNumber = request.OutTradeNumber, - Description = input.Description, - Attachment = input.Attachment, - GoodsTag = input.GoodsTag, - Total = input.Total, - OpenId = input.OpenId, - TransactionId = "" - }; - await _sysWechatPayUserRep.InsertAsync(wechatPay); + // 保存订单信息 + wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "", + OrderId = input.OrderId, + OrderStatus = input.OrderStatus + }; + await _sysWechatPayRep.InsertAsync(wechatPay); + } - return new + return new CreatePayTransactionOutput { - response.PrepayId, - request.OutTradeNumber + PrepayId = response.PrepayId, + OutTradeNumber = request.OutTradeNumber }; } @@ -108,11 +124,20 @@ public class SysWechatPayService : IDynamicApiController, ITransient /// 微信支付统一下单获取Id(服务商模式) 🔖 /// [DisplayName("微信支付统一下单获取Id(服务商模式)")] - public async Task CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input) + public async Task CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input) { + string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一) + + //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus); + if (wechatPay != null) + { + outTradeNumber = wechatPay.OutTradeNumber; + } + var request = new CreatePayPartnerTransactionJsapiRequest() { - OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + OutTradeNumber = outTradeNumber, AppId = _wechatPayOptions.AppId, MerchantId = _wechatPayOptions.MerchantId, SubAppId = _wechatPayOptions.AppId, @@ -127,29 +152,31 @@ public class SysWechatPayService : IDynamicApiController, ITransient }; var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request); if (!response.IsSuccessful()) - throw Oops.Oh(response.ErrorMessage); - - // 保存订单信息 - var wechatPay = new SysWechatPay() + throw Oops.Oh($"JSAPI 下单失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})"); + if (wechatPay == null) { - AppId = _wechatPayOptions.AppId, - MerchantId = _wechatPayOptions.MerchantId, - SubAppId = _wechatPayOptions.AppId, - SubMerchantId = _wechatPayOptions.MerchantId, - OutTradeNumber = request.OutTradeNumber, - Description = input.Description, - Attachment = input.Attachment, - GoodsTag = input.GoodsTag, - Total = input.Total, - OpenId = input.OpenId, - TransactionId = "" - }; - await _sysWechatPayUserRep.InsertAsync(wechatPay); + // 保存订单信息 + wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + SubAppId = _wechatPayOptions.AppId, + SubMerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "" + }; + await _sysWechatPayRep.InsertAsync(wechatPay); + } - return new + return new CreatePayTransactionOutput { - response.PrepayId, - request.OutTradeNumber + PrepayId = response.PrepayId, + OutTradeNumber = request.OutTradeNumber }; } @@ -161,7 +188,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient [DisplayName("获取支付订单详情")] public async Task GetPayInfo(string tradeId) { - return await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); + return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); } /// @@ -183,7 +210,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); if (wechatPay == null) return null; //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 @@ -198,7 +225,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); return new WechatPayOutput() { @@ -230,7 +257,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); if (wechatPay == null) return; //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 @@ -245,7 +272,187 @@ public class SysWechatPayService : IDynamicApiController, ITransient wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); } } + + /// + /// 退款申请 + /// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/create.html + /// + /// + /// + [DisplayName("微信退款申请)")] + public async Task Refund(RefundRequestInput input) + { + var request = new CreateRefundDomesticRefundRequest() + { + OutTradeNumber = input.OutTradeNumber, + OutRefundNumber = "REFUND_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"), + Amount = new CreateRefundDomesticRefundRequest.Types.Amount() + { + Total = input.Total, + Refund = input.Refund + }, + Reason = input.Reason, + }; + var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request); + + if (!response.IsSuccessful()) + throw Oops.Oh($"JSAPI 退款申请失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})"); + + var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber); + if (wechatRefund == null) + { + // 保存退款申请信息 + wechatRefund = new SysWechatRefund() + { + TransactionId = input.OutTradeNumber, + OutTradeNumber = request.OutTradeNumber, + OutRefundNo = request.OutTradeNumber, //每笔付款只退一次,所以这里直接用付款单号 + Reason = request.Reason, + Refund = input.Refund, + Total = input.Total, + //NotifyUrl = "", + OrderId = input.OrderId, + OrderStatus = input.OrderStatus, + MerchantGoodsId = input.MerchantGoodsId, + GoodsName = input.GoodsName, + UnitPrice = input.UnitPrice, + RefundAmount = input.RefundAmount, + RefundQuantity = input.RefundQuantity, + Attachment = input.Attachment, + Remark = input.Remark + }; + await _sysWechatRefundRep.InsertAsync(wechatRefund); + } + } + + /// + /// 查询单笔退款(通过商户退款单号) + /// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/query-by-out-refund-no.html + /// + /// + /// + [DisplayName("微信查询单笔退款)")] + public async Task GetRefundByOutRefundNumber(string outRefundNumber) + { + var request = new GetRefundDomesticRefundByOutRefundNumberRequest() + { + OutRefundNumber = outRefundNumber + }; + return await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request); + } + + /// + /// 微信支付订单号查询(校正) + /// https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id} + /// + /// + /// + [DisplayName("微信支付订单号查询(校正)")] + public async Task GetPayTransactionByIdAsync(string transactionId) + { + var request = new GetPayTransactionByIdRequest() + { + MerchantId = _wechatPayOptions.MerchantId, + TransactionId = transactionId, + WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber + }; + var response = await _wechatTenpayClient.ExecuteGetPayTransactionByIdAsync(request); + if (response.TradeState == "SUCCESS") + { + // 修正订单支付状态 + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.TransactionId == transactionId && u.MerchantId == request.MerchantId); + if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState)) + { + wechatPay.TransactionId = response.TransactionId; // 支付订单号 + wechatPay.TradeType = response.TradeType; // 交易类型 + wechatPay.TradeState = response.TradeState; // 交易状态 + wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = response.BankType; // 付款银行类型 + wechatPay.Total = response.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间 + + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + return wechatPay.Adapt(); + } + } + return response.Adapt(); + } + + /// + /// 商户订单号查询(校正) + /// https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no} + /// + /// + /// + [DisplayName("微信商户订单号查询(校正)")] + public async Task GetPayTransactionByOutTradeNumberAsync(string outTradeNumber) + { + var request = new GetPayTransactionByOutTradeNumberRequest() + { + MerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = outTradeNumber, + WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber + }; + var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request); + if (response.TradeState == "SUCCESS") + { + // 修正订单支付状态 + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == outTradeNumber && u.MerchantId == request.MerchantId); + if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState)) + { + wechatPay.TransactionId = response.TransactionId; // 支付订单号 + wechatPay.TradeType = response.TradeType; // 交易类型 + wechatPay.TradeState = response.TradeState; // 交易状态 + wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = response.BankType; // 付款银行类型 + wechatPay.Total = response.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间 + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + return wechatPay.Adapt(); + } + } + return response.Adapt(); + } + + /// + /// 获取支付记录列表(分页) + /// + /// PageSysWechatPayInput + /// + [DisplayName("获取支付记录列表(分页)")] + public async Task> PageAsync(PageSysWechatPayInput input) + { + var query = _sysWechatPayRep.AsQueryable() + .WhereIF(input.OrderId > 0, u => u.OrderId == input.OrderId) + .WhereIF(input.OrderStatus > 0, u => u.OrderStatus == input.OrderStatus) + .WhereIF(!string.IsNullOrWhiteSpace(input.OutTradeNumber), u => u.OutTradeNumber.Contains(input.OutTradeNumber.Trim())); + + if (input.SuccessTimeRange != null && input.SuccessTimeRange.Count > 0) + { + DateTime? start = input.SuccessTimeRange[0]; + query = query.WhereIF(start.HasValue, u => u.SuccessTime > start); + if (input.SuccessTimeRange.Count > 1 && input.SuccessTimeRange[1].HasValue) + { + var end = input.SuccessTimeRange[1].Value.AddDays(1); + query = query.Where(u => u.SuccessTime < end); + } + } + if (input.ExpireTimeRange != null && input.ExpireTimeRange.Count > 0) + { + DateTime? start = input.ExpireTimeRange[0]; + query = query.WhereIF(start.HasValue, u => u.ExpireTime > start); + if (input.ExpireTimeRange.Count > 1 && input.ExpireTimeRange[1].HasValue) + { + var end = input.ExpireTimeRange[1].Value.AddDays(1); + query = query.Where(u => u.ExpireTime < end); + } + } + query = query.OrderByDescending(u => u.CreateTime); + return await query.ToPagedListAsync(input.Page, input.PageSize); + } } \ No newline at end of file From fd63430a8606c1bb7c93f9bd5c804ad569907460 Mon Sep 17 00:00:00 2001 From: coolcalf <28551@qq.com> Date: Mon, 1 Jul 2024 00:19:48 +0800 Subject: [PATCH 4/4] =?UTF-8?q?WechatPayOutput=E6=94=AF=E4=BB=98=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E4=B8=AD=E5=A2=9E=E5=8A=A0=E8=AE=A2=E5=8D=95=E5=8F=B7?= =?UTF-8?q?(OrderId)=E5=92=8C=E8=AE=A2=E5=8D=95=E7=8A=B6=E6=80=81(OrderSta?= =?UTF-8?q?tus)=20=E5=A2=9E=E5=8A=A0=E9=80=80=E6=AC=BE=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=8F=8A=E6=94=AF=E4=BB=98=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin.NET.Core/Entity/SysWechatPay.cs | 6 + .../Admin.NET.Core/Entity/SysWechatRefund.cs | 121 +++++++ .../Service/Wechat/Dto/WechatPayInput.cs | 101 ++++++ .../Service/Wechat/Dto/WechatPayOutput.cs | 40 ++- .../Service/Wechat/SysWechatPayService.cs | 311 +++++++++++++++--- 5 files changed, 523 insertions(+), 56 deletions(-) create mode 100644 Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs index c0ef2c84..aff68a15 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatPay.cs @@ -14,6 +14,12 @@ namespace Admin.NET.Core; [SugarIndex("sys_wechat_pay_order_id", nameof(OrderId), OrderByType.Desc)] public partial class SysWechatPay : EntityBase { + /// + /// 关联的商户订单号 + /// + [SugarColumn(ColumnDescription = "OrderId")] + public virtual long OrderId { get; set; } + /// /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) /// diff --git a/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs new file mode 100644 index 00000000..8a309673 --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Entity/SysWechatRefund.cs @@ -0,0 +1,121 @@ +// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。 +// +// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。 +// +// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任! + +namespace Admin.NET.Core; + +/// +/// 微信退款表 +/// +[SugarTable(null, "系统微信退款表")] +[SysTable] +[SugarIndex("sys_wechat_refund_order_id", nameof(OrderId), OrderByType.Desc)] +public class SysWechatRefund : EntityBase +{ + /// + /// 微信支付订单号(原支付交易对应的微信订单号) + /// + [SugarColumn(ColumnDescription = "微信支付订单号", Length = 32)] + [Required] + public string TransactionId { get; set; } + + /// + /// 商户订单号(原交易对应的商户付款单号) + /// + [SugarColumn(ColumnDescription = "商户付款单号", Length = 32)] + [Required] + public string OutTradeNumber { get; set; } + + /// + /// 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。 + /// + [SugarColumn(ColumnDescription = "商户退款单号", Length = 64)] + [Required] + public string OutRefundNo { get; set; } + + /// + /// 退款原因,示例:商品已售完 + /// + [SugarColumn(ColumnDescription = "退款原因", Length = 80)] + public string Reason { get; set; } + + /// + /// 退款金额 + /// + [SugarColumn(ColumnDescription = "退款金额")] + public int Refund { get; set; } + + /// + /// 原订单总金额 + /// + [SugarColumn(ColumnDescription = "订单总金额")] + public int Total { get; set; } + + /// + /// 退款结果回调url + /// + [SugarColumn(ColumnDescription = "退款结果回调url", Length = 256)] + public string? NotifyUrl { get; set; } + + /// + /// 退款资金来源, 可不传,默认使用未结算资金退款(仅对老资金流商户适用) + /// + [SugarColumn(ColumnDescription = "退款资金来源", Length = 32)] + public string FundsAccount { get; set; } + + /// + /// 关联的商户订单号 + /// + [SugarColumn(ColumnDescription = "关联的用户订单号")] + public long OrderId { get; set; } + + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + [SugarColumn(ColumnDescription = "关联的商户订单状态")] + public int OrderStatus { get; set; } + + /// + /// 关联的商户商品编码 + /// + [SugarColumn(ColumnDescription = "关联的商户商品编码", Length = 32)] + public string MerchantGoodsId { get; set; } + + /// + /// 关联的商户商品名称 + /// + [SugarColumn(ColumnDescription = "关联的商户商品名称", Length = 256)] + public string GoodsName { get; set; } + + /// + /// 关联的商户商品单价 + /// + [SugarColumn(ColumnDescription = "关联的商户商品单价")] + public int UnitPrice { get; set; } + + /// + /// 关联的商户商品退款金额 + /// + [SugarColumn(ColumnDescription = "关联的商户商品退款金额")] + public int RefundAmount { get; set; } + + /// + /// 关联的商户商品退货数量 + /// + [SugarColumn(ColumnDescription = "关联的商户商品退货数量")] + public int RefundQuantity { get; set; } = 1; + + /// + /// 附加数据 + /// + [SugarColumn(ColumnDescription = "附加数据")] + public string? Attachment { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnDescription = "备注")] + public string? Remark { get; set; } +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs index 4d29621a..84fe10af 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayInput.cs @@ -32,6 +32,16 @@ public class WechatPayTransactionInput /// 优惠标记 /// public string GoodsTag { get; set; } + + /// + /// 关联的商户订单号 + /// + public long OrderId { get; set; } + + /// + /// 关联的商户订单付款状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + public int OrderStatus { get; set; } = 0; } public class WechatPayParaInput @@ -40,4 +50,95 @@ public class WechatPayParaInput /// 订单Id /// public string PrepayId { get; set; } +} + + +public class RefundRequestInput // : WechatTenpayRequest +{ + /// + /// 商户订单号(原支付交易对应的付款单号) + /// + public string OutTradeNumber { get; set; } + ///// + ///// 退款单号 + ///// + //public string OutRefundNumber { get; set; } + /// + /// 原订单金额(分) + /// + public int Total { get; set; } + /// + /// 退款金额(分) + /// + public int Refund { get; set; } + /// + /// 退款原因 + /// + public string Reason { get; set; } + /// + /// 关联的商户订单号 + /// + public long OrderId { get; set; } + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + public int OrderStatus { get; set; } + /// + /// 关联的商户商品编码 + /// + public string MerchantGoodsId { get; set; } + /// + /// 关联的商户商品名称 + /// + public string GoodsName { get; set; } + /// + /// 关联的商户商品单价 + /// + public int UnitPrice { get; set; } = 0; + /// + /// 关联的商户商品退款金额 + /// + public int RefundAmount { get; set; } = 0; + /// + /// 关联的商户商品退货数量 + /// + public int RefundQuantity { get; set; } = 1; + /// + /// 附加数据 + /// + public string Attachment { get; set; } + /// + /// 备注 + /// + public string Remark { get; set; } +} + +public class PageSysWechatPayInput : BasePageInput +{ + /// + /// order_id + /// + /// + public long? OrderId { get; set; } = -1; + /// + /// order_status + /// + /// + public long? OrderStatus { get; set; } = -1; + + /// + /// out_trade_number + /// + /// + public string OutTradeNumber { get; set; } + /// + /// success_time范围 + /// + /// + public List SuccessTimeRange { get; set; } + /// + /// expire_time范围 + /// + /// + public List ExpireTimeRange { get; set; } } \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs index b00e16aa..9a664e5d 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/Dto/WechatPayOutput.cs @@ -12,19 +12,51 @@ public class WechatPayOutput /// OpenId /// public string OpenId { get; set; } - + /// + /// 商户(支付交易)单号,微信唯一 + /// + public string OutTradeNumber { get; set; } + /// + /// 关联的商户(商品交易)订单号 + /// + public long OrderId { get; set; } + /// + /// 关联的商户订单状态(或者为第几次支付,有些订单涉及多次支付,比如先付预付款,后补尾款) + /// + public int OrderStatus { get; set; } /// /// 订单金额 /// public int Total { get; set; } - /// /// 附加数据 /// public string Attachment { get; set; } - /// /// 优惠标记 /// public string GoodsTag { get; set; } -} \ No newline at end of file + /// + /// 支付发起时间 + /// + public DateTimeOffset CreateTime { get; set; } + /// + /// 支付完成时间 + /// + public DateTimeOffset SuccessTime { get; set; } + /// + /// 交易状态(支付成功后,微信返回) + /// + public string? TradeState { get; set; } + + /// + /// 交易状态描述(支付成功后,微信返回) + /// + public string? TradeStateDescription { get; set; } +} + +public class CreatePayTransactionOutput +{ + public string PrepayId { get; set; } + public string OutTradeNumber { get; set; } +} diff --git a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs index 957af35e..1a6d9360 100644 --- a/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs +++ b/Admin.NET/Admin.NET.Core/Service/Wechat/SysWechatPayService.cs @@ -12,18 +12,20 @@ namespace Admin.NET.Core.Service; [ApiDescriptionSettings(Order = 210)] public class SysWechatPayService : IDynamicApiController, ITransient { - private readonly SqlSugarRepository _sysWechatPayUserRep; - + private readonly SqlSugarRepository _sysWechatPayRep; + private readonly SqlSugarRepository _sysWechatRefundRep; private readonly WechatPayOptions _wechatPayOptions; private readonly PayCallBackOptions _payCallBackOptions; private readonly WechatTenpayClient _wechatTenpayClient; - public SysWechatPayService(SqlSugarRepository sysWechatPayUserRep, - IOptions wechatPayOptions, - IOptions payCallBackOptions) + public SysWechatPayService(SqlSugarRepository sysWechatPayRep + , SqlSugarRepository sysWechatRefundRep + , IOptions wechatPayOptions + , IOptions payCallBackOptions) { - _sysWechatPayUserRep = sysWechatPayUserRep; + _sysWechatPayRep = sysWechatPayRep; + this._sysWechatRefundRep = sysWechatRefundRep; _wechatPayOptions = wechatPayOptions.Value; _payCallBackOptions = payCallBackOptions.Value; @@ -64,11 +66,20 @@ public class SysWechatPayService : IDynamicApiController, ITransient /// 微信支付统一下单获取Id(商户直连) 🔖 /// [DisplayName("微信支付统一下单获取Id(商户直连)")] - public async Task CreatePayTransaction([FromBody] WechatPayTransactionInput input) + public async Task CreatePayTransaction([FromBody] WechatPayTransactionInput input) { + string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一) + + //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus); + if (wechatPay != null) + { + outTradeNumber = wechatPay.OutTradeNumber; + } + var request = new CreatePayTransactionJsapiRequest() { - OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + OutTradeNumber = outTradeNumber, AppId = _wechatPayOptions.AppId, Description = input.Description, Attachment = input.Attachment, @@ -80,27 +91,32 @@ public class SysWechatPayService : IDynamicApiController, ITransient }; var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request); if (!response.IsSuccessful()) - throw Oops.Oh(response.ErrorMessage); + throw Oops.Oh(response.ErrorMessage); - // 保存订单信息 - var wechatPay = new SysWechatPay() + if (wechatPay == null) { - AppId = _wechatPayOptions.AppId, - MerchantId = _wechatPayOptions.MerchantId, - OutTradeNumber = request.OutTradeNumber, - Description = input.Description, - Attachment = input.Attachment, - GoodsTag = input.GoodsTag, - Total = input.Total, - OpenId = input.OpenId, - TransactionId = "" - }; - await _sysWechatPayUserRep.InsertAsync(wechatPay); + // 保存订单信息 + wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "", + OrderId = input.OrderId, + OrderStatus = input.OrderStatus + }; + await _sysWechatPayRep.InsertAsync(wechatPay); + } - return new + return new CreatePayTransactionOutput { - response.PrepayId, - request.OutTradeNumber + PrepayId = response.PrepayId, + OutTradeNumber = request.OutTradeNumber }; } @@ -108,11 +124,20 @@ public class SysWechatPayService : IDynamicApiController, ITransient /// 微信支付统一下单获取Id(服务商模式) 🔖 /// [DisplayName("微信支付统一下单获取Id(服务商模式)")] - public async Task CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input) + public async Task CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input) { + string outTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000); // 微信需要的订单号(唯一) + + //检查订单信息是否已存在(使用“商户交易单号+状态”唯一性判断) + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OrderId == input.OrderId && u.OrderStatus == input.OrderStatus); + if (wechatPay != null) + { + outTradeNumber = wechatPay.OutTradeNumber; + } + var request = new CreatePayPartnerTransactionJsapiRequest() { - OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号 + OutTradeNumber = outTradeNumber, AppId = _wechatPayOptions.AppId, MerchantId = _wechatPayOptions.MerchantId, SubAppId = _wechatPayOptions.AppId, @@ -127,29 +152,31 @@ public class SysWechatPayService : IDynamicApiController, ITransient }; var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request); if (!response.IsSuccessful()) - throw Oops.Oh(response.ErrorMessage); - - // 保存订单信息 - var wechatPay = new SysWechatPay() + throw Oops.Oh($"JSAPI 下单失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})"); + if (wechatPay == null) { - AppId = _wechatPayOptions.AppId, - MerchantId = _wechatPayOptions.MerchantId, - SubAppId = _wechatPayOptions.AppId, - SubMerchantId = _wechatPayOptions.MerchantId, - OutTradeNumber = request.OutTradeNumber, - Description = input.Description, - Attachment = input.Attachment, - GoodsTag = input.GoodsTag, - Total = input.Total, - OpenId = input.OpenId, - TransactionId = "" - }; - await _sysWechatPayUserRep.InsertAsync(wechatPay); + // 保存订单信息 + wechatPay = new SysWechatPay() + { + AppId = _wechatPayOptions.AppId, + MerchantId = _wechatPayOptions.MerchantId, + SubAppId = _wechatPayOptions.AppId, + SubMerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = request.OutTradeNumber, + Description = input.Description, + Attachment = input.Attachment, + GoodsTag = input.GoodsTag, + Total = input.Total, + OpenId = input.OpenId, + TransactionId = "" + }; + await _sysWechatPayRep.InsertAsync(wechatPay); + } - return new + return new CreatePayTransactionOutput { - response.PrepayId, - request.OutTradeNumber + PrepayId = response.PrepayId, + OutTradeNumber = request.OutTradeNumber }; } @@ -161,7 +188,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient [DisplayName("获取支付订单详情")] public async Task GetPayInfo(string tradeId) { - return await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); + return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId); } /// @@ -183,7 +210,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); if (wechatPay == null) return null; //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 @@ -198,7 +225,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); return new WechatPayOutput() { @@ -230,7 +257,7 @@ public class SysWechatPayService : IDynamicApiController, ITransient var callbackResource = _wechatTenpayClient.DecryptEventResource(callbackModel); // 修改订单支付状态 - var wechatPay = await _sysWechatPayUserRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber && u.MerchantId == callbackResource.MerchantId); if (wechatPay == null) return; //wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识 @@ -245,7 +272,187 @@ public class SysWechatPayService : IDynamicApiController, ITransient wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额 wechatPay.SuccessTime = callbackResource.SuccessTime; // 支付完成时间 - await _sysWechatPayUserRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); } } + + /// + /// 退款申请 + /// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/create.html + /// + /// + /// + [DisplayName("微信退款申请)")] + public async Task Refund(RefundRequestInput input) + { + var request = new CreateRefundDomesticRefundRequest() + { + OutTradeNumber = input.OutTradeNumber, + OutRefundNumber = "REFUND_" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff"), + Amount = new CreateRefundDomesticRefundRequest.Types.Amount() + { + Total = input.Total, + Refund = input.Refund + }, + Reason = input.Reason, + }; + var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request); + + if (!response.IsSuccessful()) + throw Oops.Oh($"JSAPI 退款申请失败(状态码:{response.GetRawStatus()},错误代码:{response.ErrorCode},错误描述:{response.ErrorMessage})"); + + var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutTradeNumber == input.OutTradeNumber); + if (wechatRefund == null) + { + // 保存退款申请信息 + wechatRefund = new SysWechatRefund() + { + TransactionId = input.OutTradeNumber, + OutTradeNumber = request.OutTradeNumber, + OutRefundNo = request.OutTradeNumber, //每笔付款只退一次,所以这里直接用付款单号 + Reason = request.Reason, + Refund = input.Refund, + Total = input.Total, + //NotifyUrl = "", + OrderId = input.OrderId, + OrderStatus = input.OrderStatus, + MerchantGoodsId = input.MerchantGoodsId, + GoodsName = input.GoodsName, + UnitPrice = input.UnitPrice, + RefundAmount = input.RefundAmount, + RefundQuantity = input.RefundQuantity, + Attachment = input.Attachment, + Remark = input.Remark + }; + await _sysWechatRefundRep.InsertAsync(wechatRefund); + } + } + + /// + /// 查询单笔退款(通过商户退款单号) + /// https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/query-by-out-refund-no.html + /// + /// + /// + [DisplayName("微信查询单笔退款)")] + public async Task GetRefundByOutRefundNumber(string outRefundNumber) + { + var request = new GetRefundDomesticRefundByOutRefundNumberRequest() + { + OutRefundNumber = outRefundNumber + }; + return await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request); + } + + /// + /// 微信支付订单号查询(校正) + /// https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id} + /// + /// + /// + [DisplayName("微信支付订单号查询(校正)")] + public async Task GetPayTransactionByIdAsync(string transactionId) + { + var request = new GetPayTransactionByIdRequest() + { + MerchantId = _wechatPayOptions.MerchantId, + TransactionId = transactionId, + WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber + }; + var response = await _wechatTenpayClient.ExecuteGetPayTransactionByIdAsync(request); + if (response.TradeState == "SUCCESS") + { + // 修正订单支付状态 + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.TransactionId == transactionId && u.MerchantId == request.MerchantId); + if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState)) + { + wechatPay.TransactionId = response.TransactionId; // 支付订单号 + wechatPay.TradeType = response.TradeType; // 交易类型 + wechatPay.TradeState = response.TradeState; // 交易状态 + wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = response.BankType; // 付款银行类型 + wechatPay.Total = response.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间 + + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + return wechatPay.Adapt(); + } + } + return response.Adapt(); + } + + /// + /// 商户订单号查询(校正) + /// https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no} + /// + /// + /// + [DisplayName("微信商户订单号查询(校正)")] + public async Task GetPayTransactionByOutTradeNumberAsync(string outTradeNumber) + { + var request = new GetPayTransactionByOutTradeNumberRequest() + { + MerchantId = _wechatPayOptions.MerchantId, + OutTradeNumber = outTradeNumber, + WechatpayCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber + }; + var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request); + if (response.TradeState == "SUCCESS") + { + // 修正订单支付状态 + var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == outTradeNumber && u.MerchantId == request.MerchantId); + if (wechatPay != null && string.IsNullOrEmpty(wechatPay.TradeState)) + { + wechatPay.TransactionId = response.TransactionId; // 支付订单号 + wechatPay.TradeType = response.TradeType; // 交易类型 + wechatPay.TradeState = response.TradeState; // 交易状态 + wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述 + wechatPay.BankType = response.BankType; // 付款银行类型 + wechatPay.Total = response.Amount.Total; // 订单总金额 + wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额 + wechatPay.SuccessTime = response.SuccessTime; // 支付完成时间 + await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync(); + return wechatPay.Adapt(); + } + } + return response.Adapt(); + } + + /// + /// 获取支付记录列表(分页) + /// + /// PageSysWechatPayInput + /// + [DisplayName("获取支付记录列表(分页)")] + public async Task> PageAsync(PageSysWechatPayInput input) + { + var query = _sysWechatPayRep.AsQueryable() + .WhereIF(input.OrderId > 0, u => u.OrderId == input.OrderId) + .WhereIF(input.OrderStatus > 0, u => u.OrderStatus == input.OrderStatus) + .WhereIF(!string.IsNullOrWhiteSpace(input.OutTradeNumber), u => u.OutTradeNumber.Contains(input.OutTradeNumber.Trim())); + + if (input.SuccessTimeRange != null && input.SuccessTimeRange.Count > 0) + { + DateTime? start = input.SuccessTimeRange[0]; + query = query.WhereIF(start.HasValue, u => u.SuccessTime > start); + if (input.SuccessTimeRange.Count > 1 && input.SuccessTimeRange[1].HasValue) + { + var end = input.SuccessTimeRange[1].Value.AddDays(1); + query = query.Where(u => u.SuccessTime < end); + } + } + if (input.ExpireTimeRange != null && input.ExpireTimeRange.Count > 0) + { + DateTime? start = input.ExpireTimeRange[0]; + query = query.WhereIF(start.HasValue, u => u.ExpireTime > start); + if (input.ExpireTimeRange.Count > 1 && input.ExpireTimeRange[1].HasValue) + { + var end = input.ExpireTimeRange[1].Value.AddDays(1); + query = query.Where(u => u.ExpireTime < end); + } + } + query = query.OrderByDescending(u => u.CreateTime); + return await query.ToPagedListAsync(input.Page, input.PageSize); + } } \ No newline at end of file