fix: 修复微信支付回调无法接收的问题
1. 添加 /api/pay/notify 回调路由(兼容微信配置的回调地址)
2. 修复 attach 值匹配逻辑,支持 order_{type} 和 infinite_{type} 格式
3. 添加钻石订单 (order_product) 的回调处理逻辑
4. 添加 OrderAttachType.OrderProduct 常量
This commit is contained in:
parent
e67602b3c4
commit
e4a1f055c1
|
|
@ -11,7 +11,8 @@
|
||||||
|
|
||||||
// 测试环境配置 - .NET 10 后端
|
// 测试环境配置 - .NET 10 后端
|
||||||
const testing = {
|
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.1.24:5238',
|
||||||
// baseUrl: 'http://192.168.195.15:2822',
|
// baseUrl: 'http://192.168.195.15:2822',
|
||||||
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
|
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
|
||||||
|
|
|
||||||
|
|
@ -18,18 +18,84 @@ public class PayController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IWechatPayService _wechatPayService;
|
private readonly IWechatPayService _wechatPayService;
|
||||||
private readonly IRechargeService _rechargeService;
|
private readonly IRechargeService _rechargeService;
|
||||||
|
private readonly IPaymentNotifyService _paymentNotifyService;
|
||||||
private readonly ILogger<PayController> _logger;
|
private readonly ILogger<PayController> _logger;
|
||||||
|
|
||||||
public PayController(
|
public PayController(
|
||||||
IWechatPayService wechatPayService,
|
IWechatPayService wechatPayService,
|
||||||
IRechargeService rechargeService,
|
IRechargeService rechargeService,
|
||||||
|
IPaymentNotifyService paymentNotifyService,
|
||||||
ILogger<PayController> logger)
|
ILogger<PayController> logger)
|
||||||
{
|
{
|
||||||
_wechatPayService = wechatPayService;
|
_wechatPayService = wechatPayService;
|
||||||
_rechargeService = rechargeService;
|
_rechargeService = rechargeService;
|
||||||
|
_paymentNotifyService = paymentNotifyService;
|
||||||
_logger = logger;
|
_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>
|
/// <summary>
|
||||||
/// 微信支付统一下单接口
|
/// 微信支付统一下单接口
|
||||||
/// POST /api/pay
|
/// POST /api/pay
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using HoneyBox.Core.Interfaces;
|
||||||
using HoneyBox.Model.Data;
|
using HoneyBox.Model.Data;
|
||||||
using HoneyBox.Model.Entities;
|
using HoneyBox.Model.Entities;
|
||||||
using HoneyBox.Model.Models.Lottery;
|
using HoneyBox.Model.Models.Lottery;
|
||||||
|
using HoneyBox.Model.Models.Mall;
|
||||||
using HoneyBox.Model.Models.Payment;
|
using HoneyBox.Model.Models.Payment;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -418,31 +419,60 @@ public class PaymentNotifyService : IPaymentNotifyService
|
||||||
await RecordPaymentAsync(user.Id, orderNo, notifyData.TotalFee / 100m, attach);
|
await RecordPaymentAsync(user.Id, orderNo, notifyData.TotalFee / 100m, attach);
|
||||||
|
|
||||||
// 根据attach类型路由处理
|
// 根据attach类型路由处理
|
||||||
|
// attach格式: order_{type} 或 infinite_{type} 或固定字符串
|
||||||
if (attach == OrderAttachType.UserRecharge)
|
if (attach == OrderAttachType.UserRecharge)
|
||||||
{
|
{
|
||||||
// 余额充值
|
// 余额充值
|
||||||
return await ProcessRechargeOrderAsync(orderNo);
|
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)
|
else if (attach == OrderAttachType.OrderListSend)
|
||||||
{
|
{
|
||||||
// 发货运费订单
|
// 发货运费订单
|
||||||
return await ProcessShippingFeeOrderAsync(orderNo);
|
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
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("未知的订单类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
|
_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 />
|
/// <inheritdoc />
|
||||||
public async Task<bool> IsOrderProcessedAsync(string orderNo)
|
public async Task<bool> IsOrderProcessedAsync(string orderNo)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -776,6 +776,11 @@ public static class OrderAttachType
|
||||||
/// 发货运费
|
/// 发货运费
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string OrderListSend = "order_list_send";
|
public const string OrderListSend = "order_list_send";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 钻石商品订单
|
||||||
|
/// </summary>
|
||||||
|
public const string OrderProduct = "order_product";
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user