- 删除无数据库表的实体: UserDetail, UserAddress, PaymentOrder, Admin, AdminLoginLog, AdminOperationLog, Picture, Delivery - 删除关联服务: AddressService, PaymentService, PaymentOrderService, PaymentRewardDispatcher, DefaultPaymentRewardHandler - 删除关联接口: IAddressService, IPaymentService, IPaymentOrderService, IPaymentRewardHandler, IPaymentRewardDispatcher - 删除关联控制器: AddressController - 删除关联DTO: AddressModels, CreatePaymentOrderRequest, PaymentOrderDto, PaymentOrderQueryRequest - 删除关联测试: PaymentOrderServicePropertyTests, PaymentRewardDispatcherPropertyTests - 修复实体字段映射: User, UserLoginLog, UserRefreshToken, Config, OrderNotify - 更新 NotifyController 移除 IPaymentOrderService 依赖 - 更新 ServiceModule 移除已删除服务的DI注册 - 更新 MiAssessmentDbContext 移除已删除实体的DbSet和OnModelCreating配置
430 lines
16 KiB
C#
430 lines
16 KiB
C#
using System.Text.Json;
|
||
using MiAssessment.Core.Interfaces;
|
||
using MiAssessment.Model.Data;
|
||
using MiAssessment.Model.Entities;
|
||
using MiAssessment.Model.Models.Payment;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
|
||
namespace MiAssessment.Core.Services;
|
||
|
||
/// <summary>
|
||
/// 支付回调服务实现
|
||
/// 负责处理微信支付回调通知,验证签名,记录通知
|
||
/// 业务处理逻辑由具体业务模块实现
|
||
/// </summary>
|
||
public class PaymentNotifyService : IPaymentNotifyService
|
||
{
|
||
private readonly MiAssessmentDbContext _dbContext;
|
||
private readonly IWechatPayService _wechatPayService;
|
||
private readonly IWechatPayV3Service _wechatPayV3Service;
|
||
private readonly IWechatPayConfigService _wechatPayConfigService;
|
||
private readonly ILogger<PaymentNotifyService> _logger;
|
||
|
||
public PaymentNotifyService(
|
||
MiAssessmentDbContext dbContext,
|
||
IWechatPayService wechatPayService,
|
||
IWechatPayV3Service wechatPayV3Service,
|
||
IWechatPayConfigService wechatPayConfigService,
|
||
ILogger<PaymentNotifyService> logger)
|
||
{
|
||
_dbContext = dbContext;
|
||
_wechatPayService = wechatPayService;
|
||
_wechatPayV3Service = wechatPayV3Service;
|
||
_wechatPayConfigService = wechatPayConfigService;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<NotifyResult> HandleWechatNotifyAsync(string notifyBody, WechatPayNotifyHeaders? headers = null)
|
||
{
|
||
// 自动识别回调格式
|
||
var version = _wechatPayV3Service.DetectNotifyVersion(notifyBody);
|
||
|
||
_logger.LogInformation("检测到微信支付回调版本: {Version}", version);
|
||
|
||
return version switch
|
||
{
|
||
NotifyVersion.V3 when headers != null => await HandleWechatV3NotifyAsync(notifyBody, headers),
|
||
NotifyVersion.V3 => new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "V3 回调缺少请求头",
|
||
JsonResponse = JsonSerializer.Serialize(new WechatPayV3NotifyResponse { Code = "FAIL", Message = "缺少请求头" })
|
||
},
|
||
NotifyVersion.V2 => await HandleWechatV2NotifyAsync(notifyBody),
|
||
_ => new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "无法识别的回调格式",
|
||
XmlResponse = _wechatPayService.GenerateNotifyResponseXml("FAIL", "无法识别的回调格式")
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<NotifyResult> HandleWechatV2NotifyAsync(string xmlData)
|
||
{
|
||
var successResponse = _wechatPayService.GenerateNotifyResponseXml("SUCCESS", "OK");
|
||
var failResponse = _wechatPayService.GenerateNotifyResponseXml("FAIL", "处理失败");
|
||
|
||
try
|
||
{
|
||
// 1. 检查XML数据是否为空
|
||
if (string.IsNullOrEmpty(xmlData))
|
||
{
|
||
_logger.LogWarning("微信支付 V2 回调数据为空");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "回调数据为空",
|
||
XmlResponse = failResponse
|
||
};
|
||
}
|
||
|
||
// 2. 解析XML数据
|
||
var notifyData = _wechatPayService.ParseNotifyXml(xmlData);
|
||
if (notifyData == null || string.IsNullOrEmpty(notifyData.OutTradeNo))
|
||
{
|
||
_logger.LogWarning("解析微信支付 V2 回调XML失败");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "解析回调数据失败",
|
||
XmlResponse = failResponse
|
||
};
|
||
}
|
||
|
||
var orderNo = notifyData.OutTradeNo;
|
||
var attach = notifyData.Attach;
|
||
|
||
_logger.LogInformation("收到微信支付 V2 回调: OrderNo={OrderNo}, Attach={Attach}, TotalFee={TotalFee}",
|
||
orderNo, attach, notifyData.TotalFee);
|
||
|
||
// 3. 验证签名
|
||
if (!_wechatPayService.VerifyNotifySign(notifyData))
|
||
{
|
||
_logger.LogWarning("微信支付 V2 回调签名验证失败: OrderNo={OrderNo}", orderNo);
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "签名验证失败",
|
||
XmlResponse = failResponse
|
||
};
|
||
}
|
||
|
||
// 4. 检查返回状态
|
||
if (notifyData.ReturnCode != "SUCCESS" || notifyData.ResultCode != "SUCCESS")
|
||
{
|
||
_logger.LogWarning("微信支付回调状态异常: OrderNo={OrderNo}, ReturnCode={ReturnCode}, ResultCode={ResultCode}",
|
||
orderNo, notifyData.ReturnCode, notifyData.ResultCode);
|
||
// 即使支付失败,也返回成功响应,避免微信重复通知
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "支付未成功",
|
||
XmlResponse = successResponse
|
||
};
|
||
}
|
||
|
||
// 5. 幂等性检查 - 检查订单是否已处理
|
||
if (await IsOrderProcessedAsync(orderNo))
|
||
{
|
||
_logger.LogInformation("订单已处理,跳过重复回调: OrderNo={OrderNo}", orderNo);
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "订单已处理",
|
||
XmlResponse = successResponse
|
||
};
|
||
}
|
||
|
||
// 6. 记录回调通知
|
||
await RecordNotifyAsync(orderNo, notifyData);
|
||
|
||
// 7. 返回成功,业务处理由具体业务模块实现
|
||
_logger.LogInformation("微信支付 V2 回调记录成功: OrderNo={OrderNo}, Attach={Attach}", orderNo, attach);
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "处理成功",
|
||
XmlResponse = successResponse,
|
||
OrderNo = orderNo,
|
||
Attach = attach,
|
||
NotifyData = notifyData
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理微信支付回调异常");
|
||
// 异常情况也返回成功,避免微信重复通知
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = $"处理异常: {ex.Message}",
|
||
XmlResponse = successResponse
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<NotifyResult> HandleWechatV3NotifyAsync(string jsonData, WechatPayNotifyHeaders headers)
|
||
{
|
||
var successResponse = JsonSerializer.Serialize(new WechatPayV3NotifyResponse { Code = "SUCCESS", Message = "成功" });
|
||
var failResponse = JsonSerializer.Serialize(new WechatPayV3NotifyResponse { Code = "FAIL", Message = "处理失败" });
|
||
|
||
try
|
||
{
|
||
// 1. 检查数据是否为空
|
||
if (string.IsNullOrEmpty(jsonData))
|
||
{
|
||
_logger.LogWarning("微信支付 V3 回调数据为空");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "回调数据为空",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
|
||
// 2. 验证签名
|
||
if (!_wechatPayV3Service.VerifyNotifySignature(
|
||
headers.Timestamp,
|
||
headers.Nonce,
|
||
jsonData,
|
||
headers.Signature,
|
||
headers.Serial))
|
||
{
|
||
_logger.LogWarning("微信支付 V3 回调签名验证失败");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "签名验证失败",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
|
||
// 3. 解析回调通知
|
||
var notification = JsonSerializer.Deserialize<WechatPayV3Notification>(jsonData);
|
||
if (notification == null || notification.Resource == null)
|
||
{
|
||
_logger.LogWarning("解析微信支付 V3 回调数据失败");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "解析回调数据失败",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
|
||
_logger.LogInformation("收到微信支付 V3 回调: Id={Id}, EventType={EventType}",
|
||
notification.Id, notification.EventType);
|
||
|
||
// 4. 获取商户配置并解密数据
|
||
var merchantConfig = _wechatPayConfigService.GetDefaultConfig();
|
||
if (string.IsNullOrEmpty(merchantConfig.ApiV3Key))
|
||
{
|
||
_logger.LogError("APIv3 密钥未配置");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "APIv3 密钥未配置",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
|
||
var decryptedJson = _wechatPayV3Service.DecryptNotifyResource(
|
||
notification.Resource.Ciphertext,
|
||
notification.Resource.Nonce,
|
||
notification.Resource.AssociatedData,
|
||
merchantConfig.ApiV3Key);
|
||
|
||
_logger.LogDebug("V3 回调解密成功: {DecryptedJson}", decryptedJson);
|
||
|
||
// 5. 解析支付结果
|
||
var paymentResult = JsonSerializer.Deserialize<WechatPayV3PaymentResult>(decryptedJson);
|
||
if (paymentResult == null || string.IsNullOrEmpty(paymentResult.OutTradeNo))
|
||
{
|
||
_logger.LogWarning("解析 V3 支付结果失败");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = "解析支付结果失败",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
|
||
var orderNo = paymentResult.OutTradeNo;
|
||
var attach = paymentResult.Attach;
|
||
|
||
_logger.LogInformation("V3 支付结果: OrderNo={OrderNo}, TradeState={TradeState}, Attach={Attach}",
|
||
orderNo, paymentResult.TradeState, attach);
|
||
|
||
// 6. 检查支付状态
|
||
if (paymentResult.TradeState != WechatPayV3TradeState.Success)
|
||
{
|
||
_logger.LogWarning("V3 支付状态非成功: OrderNo={OrderNo}, TradeState={TradeState}",
|
||
orderNo, paymentResult.TradeState);
|
||
// 即使支付失败,也返回成功响应,避免微信重复通知
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "支付未成功",
|
||
JsonResponse = successResponse
|
||
};
|
||
}
|
||
|
||
// 7. 幂等性检查 - 检查订单是否已处理
|
||
if (await IsOrderProcessedAsync(orderNo))
|
||
{
|
||
_logger.LogInformation("订单已处理,跳过重复回调: OrderNo={OrderNo}", orderNo);
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "订单已处理",
|
||
JsonResponse = successResponse
|
||
};
|
||
}
|
||
|
||
// 8. 记录回调通知(转换为 V2 格式以复用现有逻辑)
|
||
var notifyData = ConvertV3ToV2NotifyData(paymentResult);
|
||
await RecordNotifyAsync(orderNo, notifyData);
|
||
|
||
// 9. 返回成功,业务处理由具体业务模块实现
|
||
_logger.LogInformation("微信支付 V3 回调记录成功: OrderNo={OrderNo}, Attach={Attach}", orderNo, attach);
|
||
return new NotifyResult
|
||
{
|
||
Success = true,
|
||
Message = "处理成功",
|
||
JsonResponse = successResponse,
|
||
OrderNo = orderNo,
|
||
Attach = attach,
|
||
NotifyData = notifyData
|
||
};
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogError(ex, "V3 回调解密失败");
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = $"解密失败: {ex.Message}",
|
||
JsonResponse = failResponse
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理微信支付 V3 回调异常");
|
||
// 异常情况也返回成功,避免微信重复通知
|
||
return new NotifyResult
|
||
{
|
||
Success = false,
|
||
Message = $"处理异常: {ex.Message}",
|
||
JsonResponse = successResponse
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 V3 支付结果转换为 V2 格式(复用现有处理逻辑)
|
||
/// </summary>
|
||
private static WechatNotifyData ConvertV3ToV2NotifyData(WechatPayV3PaymentResult v3Result)
|
||
{
|
||
return new WechatNotifyData
|
||
{
|
||
ReturnCode = "SUCCESS",
|
||
ResultCode = v3Result.TradeState == WechatPayV3TradeState.Success ? "SUCCESS" : "FAIL",
|
||
OutTradeNo = v3Result.OutTradeNo,
|
||
TransactionId = v3Result.TransactionId,
|
||
TotalFee = v3Result.Amount.Total,
|
||
OpenId = v3Result.Payer.OpenId,
|
||
Attach = v3Result.Attach,
|
||
NonceStr = Guid.NewGuid().ToString("N")[..32]
|
||
};
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> IsOrderProcessedAsync(string orderNo)
|
||
{
|
||
var notify = await _dbContext.OrderNotifies
|
||
.FirstOrDefaultAsync(n => n.OrderNo == orderNo && n.Status == 1);
|
||
return notify != null;
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> RecordNotifyAsync(string orderNo, WechatNotifyData notifyData)
|
||
{
|
||
try
|
||
{
|
||
// 检查是否已存在记录
|
||
var existingNotify = await _dbContext.OrderNotifies
|
||
.FirstOrDefaultAsync(n => n.OrderNo == orderNo);
|
||
|
||
if (existingNotify != null)
|
||
{
|
||
// 更新现有记录
|
||
existingNotify.TransactionId = notifyData.TransactionId;
|
||
existingNotify.PayTime = DateTime.Now;
|
||
existingNotify.PayAmount = notifyData.TotalFee / 100m;
|
||
existingNotify.RawData = null; // 可选:存储原始XML
|
||
existingNotify.UpdateTime = DateTime.Now;
|
||
}
|
||
else
|
||
{
|
||
// 创建新记录
|
||
var notify = new OrderNotify
|
||
{
|
||
OrderNo = orderNo,
|
||
TransactionId = notifyData.TransactionId,
|
||
NonceStr = notifyData.NonceStr,
|
||
PayTime = DateTime.Now,
|
||
PayAmount = notifyData.TotalFee / 100m,
|
||
Status = 0, // 待处理
|
||
Attach = notifyData.Attach,
|
||
OpenId = notifyData.OpenId,
|
||
RawData = null,
|
||
CreateTime = DateTime.Now,
|
||
UpdateTime = DateTime.Now
|
||
};
|
||
|
||
_dbContext.OrderNotifies.Add(notify);
|
||
}
|
||
|
||
await _dbContext.SaveChangesAsync();
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "记录支付回调通知失败: {OrderNo}", orderNo);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<bool> UpdateNotifyStatusAsync(string orderNo, byte status, string? message = null)
|
||
{
|
||
try
|
||
{
|
||
var notify = await _dbContext.OrderNotifies
|
||
.FirstOrDefaultAsync(n => n.OrderNo == orderNo);
|
||
|
||
if (notify != null)
|
||
{
|
||
notify.Status = status;
|
||
notify.ErrorMessage = message;
|
||
notify.UpdateTime = DateTime.Now;
|
||
await _dbContext.SaveChangesAsync();
|
||
return true;
|
||
}
|
||
|
||
_logger.LogWarning("更新订单通知状态失败,未找到记录: OrderNo={OrderNo}", orderNo);
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "更新订单通知状态异常: OrderNo={OrderNo}", orderNo);
|
||
return false;
|
||
}
|
||
}
|
||
}
|