fix: 修复微信支付回调无法接收的问题

1. 添加 /api/pay/notify 回调路由(兼容微信配置的回调地址)
2. 修复 attach 值匹配逻辑,支持 order_{type} 和 infinite_{type} 格式
3. 添加钻石订单 (order_product) 的回调处理逻辑
4. 添加 OrderAttachType.OrderProduct 常量
This commit is contained in:
zpc 2026-02-09 16:22:56 +08:00
parent e67602b3c4
commit e4a1f055c1
4 changed files with 304 additions and 16 deletions

View File

@ -11,7 +11,8 @@
// 测试环境配置 - .NET 10 后端
const testing = {
baseUrl: 'https://app.zpc-xy.com/honey/api',
// baseUrl: 'https://app.zpc-xy.com/honey/api',
baseUrl: 'https://api.hanimanghe.top',
// baseUrl: 'http://192.168.1.24:5238',
// baseUrl: 'http://192.168.195.15:2822',
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',

View File

@ -18,18 +18,84 @@ public class PayController : ControllerBase
{
private readonly IWechatPayService _wechatPayService;
private readonly IRechargeService _rechargeService;
private readonly IPaymentNotifyService _paymentNotifyService;
private readonly ILogger<PayController> _logger;
public PayController(
IWechatPayService wechatPayService,
IRechargeService rechargeService,
IPaymentNotifyService paymentNotifyService,
ILogger<PayController> logger)
{
_wechatPayService = wechatPayService;
_rechargeService = rechargeService;
_paymentNotifyService = paymentNotifyService;
_logger = logger;
}
/// <summary>
/// 微信支付回调接口(兼容旧路由)
/// POST /api/pay/notify
/// 接收微信支付结果通知,处理订单状态更新
/// </summary>
[HttpPost("pay/notify")]
public async Task<IActionResult> PayNotify()
{
try
{
// 读取请求体
using var reader = new StreamReader(Request.Body);
var notifyBody = await reader.ReadToEndAsync();
_logger.LogInformation("收到微信支付回调请求 [/api/pay/notify],数据长度: {Length}, ContentType: {ContentType}",
notifyBody?.Length ?? 0, Request.ContentType);
// 提取 V3 回调请求头(如果存在)
WechatPayNotifyHeaders? headers = null;
if (Request.Headers.TryGetValue("Wechatpay-Timestamp", out var timestamp) &&
Request.Headers.TryGetValue("Wechatpay-Nonce", out var nonce) &&
Request.Headers.TryGetValue("Wechatpay-Signature", out var signature) &&
Request.Headers.TryGetValue("Wechatpay-Serial", out var serial))
{
headers = new WechatPayNotifyHeaders
{
Timestamp = timestamp.ToString(),
Nonce = nonce.ToString(),
Signature = signature.ToString(),
Serial = serial.ToString()
};
_logger.LogDebug("检测到 V3 回调请求头: Timestamp={Timestamp}, Serial={Serial}",
headers.Timestamp, headers.Serial);
}
// 调用服务处理回调(自动识别 V2/V3 格式)
var result = await _paymentNotifyService.HandleWechatNotifyAsync(notifyBody ?? string.Empty, headers);
_logger.LogInformation("微信支付回调处理完成: Success={Success}, Message={Message}",
result.Success, result.Message);
// 根据回调版本返回对应格式的响应
if (!string.IsNullOrEmpty(result.JsonResponse))
{
// V3 返回 JSON
return Content(result.JsonResponse, "application/json");
}
else
{
// V2 返回 XML
return Content(result.XmlResponse ?? "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>", "application/xml");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理微信支付回调异常");
// 返回成功响应,避免微信重复通知
var successResponse = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
return Content(successResponse, "application/xml");
}
}
/// <summary>
/// 微信支付统一下单接口
/// POST /api/pay

View File

@ -3,6 +3,7 @@ using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Lottery;
using HoneyBox.Model.Models.Mall;
using HoneyBox.Model.Models.Payment;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -418,31 +419,60 @@ public class PaymentNotifyService : IPaymentNotifyService
await RecordPaymentAsync(user.Id, orderNo, notifyData.TotalFee / 100m, attach);
// 根据attach类型路由处理
// attach格式: order_{type} 或 infinite_{type} 或固定字符串
if (attach == OrderAttachType.UserRecharge)
{
// 余额充值
return await ProcessRechargeOrderAsync(orderNo);
}
else if (LotteryOrderTypes.Contains(attach))
{
// 一番赏类订单
return await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
else if (InfiniteOrderTypes.Contains(attach))
{
// 无限赏类订单
return await ProcessInfiniteOrderByOrderNoAsync(orderNo);
}
else if (attach == OrderAttachType.OrderCkj)
{
// 抽卡机订单
return await ProcessCardExtractorOrderByOrderNoAsync(orderNo);
}
else if (attach == OrderAttachType.OrderListSend)
{
// 发货运费订单
return await ProcessShippingFeeOrderAsync(orderNo);
}
else if (attach == OrderAttachType.OrderProduct)
{
// 钻石商品订单
return await ProcessDiamondOrderAsync(orderNo, user.Id);
}
else if (attach.StartsWith("order_"))
{
// 一番赏类订单 (order_1, order_3, order_4, order_5, order_6, order_10, order_11 等)
// 提取类型数字
var typeStr = attach.Replace("order_", "");
if (int.TryParse(typeStr, out var orderType))
{
// 类型 4 是抽卡机
if (orderType == 4)
{
return await ProcessCardExtractorOrderByOrderNoAsync(orderNo);
}
// 其他类型按一番赏处理
return await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
// 如果不是数字格式,尝试匹配旧格式
if (LotteryOrderTypes.Contains(attach))
{
return await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
_logger.LogWarning("未知的order_类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
return false;
}
else if (attach.StartsWith("infinite_"))
{
// 无限赏类订单 (infinite_2, infinite_7, infinite_8, infinite_9 等)
return await ProcessInfiniteOrderByOrderNoAsync(orderNo);
}
else if (LotteryOrderTypes.Contains(attach))
{
// 兼容旧格式 order_yfs, order_lts 等
return await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
else if (InfiniteOrderTypes.Contains(attach))
{
// 兼容旧格式 order_wxs, order_fbs 等
return await ProcessInfiniteOrderByOrderNoAsync(orderNo);
}
else
{
_logger.LogWarning("未知的订单类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
@ -998,6 +1028,192 @@ public class PaymentNotifyService : IPaymentNotifyService
}
}
/// <summary>
/// 处理钻石商品订单
/// </summary>
/// <param name="orderNo">订单号</param>
/// <param name="userId">用户ID</param>
/// <returns>是否处理成功</returns>
public async Task<bool> ProcessDiamondOrderAsync(string orderNo, int userId)
{
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
// 1. 查找钻石订单
var diamondOrder = await _dbContext.DiamondOrders
.FirstOrDefaultAsync(o => o.OrderNo == orderNo && o.Status == DiamondOrderStatus.Pending);
if (diamondOrder == null)
{
_logger.LogWarning("未找到待支付的钻石订单: OrderNo={OrderNo}", orderNo);
return false;
}
// 2. 查找钻石商品配置
var diamondProduct = await _dbContext.DiamondProducts
.FirstOrDefaultAsync(p => p.Id == diamondOrder.DiamondId);
if (diamondProduct == null)
{
_logger.LogWarning("钻石商品不存在: DiamondId={DiamondId}", diamondOrder.DiamondId);
return false;
}
// 3. 更新订单状态为已支付
diamondOrder.Status = DiamondOrderStatus.Success;
diamondOrder.PaidAt = DateTime.Now;
// 4. 解析并发放奖励
var rewardLog = await GrantDiamondRewardsAsync(userId, diamondProduct, diamondOrder.IsFirstCharge == 1);
diamondOrder.RewardLog = rewardLog;
// 5. 更新订单通知状态
await UpdateOrderNotifyStatusAsync(orderNo, 1, "处理成功");
await _dbContext.SaveChangesAsync();
await transaction.CommitAsync();
_logger.LogInformation("钻石订单处理成功: OrderNo={OrderNo}, UserId={UserId}, RewardLog={RewardLog}",
orderNo, userId, rewardLog);
return true;
}
catch (Exception ex)
{
await transaction.RollbackAsync();
_logger.LogError(ex, "处理钻石订单失败: OrderNo={OrderNo}", orderNo);
return false;
}
}
/// <summary>
/// 发放钻石商品奖励
/// </summary>
private async Task<string> GrantDiamondRewardsAsync(int userId, DiamondProduct product, bool isFirstCharge)
{
var rewardLogs = new List<string>();
try
{
// 解析基础奖励 (格式: "type:value,type:value" 如 "1:100,2:50")
if (!string.IsNullOrEmpty(product.BaseReward))
{
var baseRewards = ParseRewards(product.BaseReward);
foreach (var reward in baseRewards)
{
var log = await GrantSingleRewardAsync(userId, reward.Type, reward.Value, "购买钻石商品");
if (!string.IsNullOrEmpty(log))
rewardLogs.Add(log);
}
}
// 如果是首充,发放首充额外奖励
if (isFirstCharge && !string.IsNullOrEmpty(product.FirstBonusReward))
{
var bonusRewards = ParseRewards(product.FirstBonusReward);
foreach (var reward in bonusRewards)
{
var log = await GrantSingleRewardAsync(userId, reward.Type, reward.Value, "首充奖励");
if (!string.IsNullOrEmpty(log))
rewardLogs.Add(log);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "发放钻石奖励异常: UserId={UserId}, ProductId={ProductId}", userId, product.Id);
rewardLogs.Add($"发放异常: {ex.Message}");
}
return string.Join("; ", rewardLogs);
}
/// <summary>
/// 解析奖励配置字符串
/// </summary>
private static List<(int Type, decimal Value)> ParseRewards(string rewardStr)
{
var rewards = new List<(int Type, decimal Value)>();
if (string.IsNullOrEmpty(rewardStr))
return rewards;
var parts = rewardStr.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
var kv = part.Split(':');
if (kv.Length == 2 && int.TryParse(kv[0], out var type) && decimal.TryParse(kv[1], out var value))
{
rewards.Add((type, value));
}
}
return rewards;
}
/// <summary>
/// 发放单个奖励
/// </summary>
private async Task<string> GrantSingleRewardAsync(int userId, int rewardType, decimal value, string source)
{
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
return string.Empty;
switch (rewardType)
{
case 1: // 钻石 (Money2)
user.Money2 = (user.Money2 ?? 0) + value;
// 记录钻石变动
_dbContext.ProfitMoney2s.Add(new ProfitMoney2
{
UserId = userId,
ChangeMoney = value,
Money = user.Money2 ?? 0,
Type = 1,
Content = source,
ShareUid = 0,
CreatedAt = DateTime.Now
});
return $"钻石+{value}";
case 2: // UU币/余额 (Money)
user.Money += value;
// 记录余额变动
_dbContext.ProfitMoneys.Add(new ProfitMoney
{
UserId = userId,
ChangeMoney = value,
Money = user.Money,
Type = 1,
Content = source,
ShareUid = 0,
CreatedAt = DateTime.Now
});
return $"余额+{value}";
case 3: // 哈尼券/达达券 - 暂不支持,记录到积分
case 4: // 积分 (Integral)
user.Integral += value;
// 记录积分变动
_dbContext.ProfitIntegrals.Add(new ProfitIntegral
{
UserId = userId,
ChangeMoney = value,
Money = user.Integral,
Type = 1,
Content = source,
ShareUid = 0,
CreatedAt = DateTime.Now
});
return rewardType == 3 ? $"哈尼券+{value}" : $"积分+{value}";
default:
_logger.LogWarning("未知的奖励类型: Type={Type}", rewardType);
return string.Empty;
}
}
/// <inheritdoc />
public async Task<bool> IsOrderProcessedAsync(string orderNo)
{

View File

@ -776,6 +776,11 @@ public static class OrderAttachType
/// 发货运费
/// </summary>
public const string OrderListSend = "order_list_send";
/// <summary>
/// 钻石商品订单
/// </summary>
public const string OrderProduct = "order_product";
}
#endregion