21
This commit is contained in:
parent
b7c5fe1bab
commit
eecdcf6f19
|
|
@ -14,7 +14,7 @@ using Microsoft.Extensions.Options;
|
|||
namespace MiAssessment.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD>
|
||||
/// 微信支付服务实现
|
||||
/// </summary>
|
||||
public class WechatPayService : IWechatPayService
|
||||
{
|
||||
|
|
@ -29,29 +29,29 @@ public class WechatPayService : IWechatPayService
|
|||
private readonly Lazy<IWechatPayV3Service>? _v3ServiceLazy;
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ͳһ<EFBFBD>µ<EFBFBD>API<EFBFBD><EFBFBD>ַ
|
||||
/// 微信统一下单API地址
|
||||
/// </summary>
|
||||
private const string UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD>ŷ<EFBFBD><EFBFBD><EFBFBD>֪ͨAPI<EFBFBD><EFBFBD>ַ
|
||||
/// 微信发货通知API地址
|
||||
/// </summary>
|
||||
private const string SHIPPING_NOTIFY_URL = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info";
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܶ<EFBFBD><EFBFBD><EFBFBD>Redis<EFBFBD><EFBFBD>ǰ
|
||||
/// 发货失败订单Redis键前缀
|
||||
/// </summary>
|
||||
private const string FAILED_SHIPPING_KEY_PREFIX = "post_order:";
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܶ<EFBFBD><EFBFBD><EFBFBD>Redis<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>䣨3<EFBFBD>죩
|
||||
/// 发货失败订单Redis过期时间(3天)
|
||||
/// </summary>
|
||||
private static readonly TimeSpan FAILED_SHIPPING_EXPIRY = TimeSpan.FromDays(3);
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>
|
||||
/// 测试用户支付金额(分)
|
||||
/// </summary>
|
||||
private const int TEST_USER_PAY_AMOUNT = 1; // 0.01Ԫ = 1<><31>
|
||||
private const int TEST_USER_PAY_AMOUNT = 1; // 0.01元 = 1分
|
||||
|
||||
public WechatPayService(
|
||||
MiAssessmentDbContext dbContext,
|
||||
|
|
@ -80,58 +80,58 @@ public class WechatPayService : IWechatPayService
|
|||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}",
|
||||
_logger.LogInformation("开始创建微信支付订单: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}",
|
||||
request.OrderNo, request.UserId, request.Amount);
|
||||
|
||||
// 1. <EFBFBD><EFBFBD><EFBFBD>ݶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ż<EFBFBD>ȡ<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD>汾
|
||||
// 1. 根据订单号获取商户配置,判断支付版本
|
||||
var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo);
|
||||
|
||||
// 2. <EFBFBD>汾·<EFBFBD>ɣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ V3 <20><> V3 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD><C3A3><EFBFBD>ʹ<EFBFBD><CAB9> V3 <20><><EFBFBD><EFBFBD>
|
||||
// 2. 版本路由:如果配置为 V3 且 V3 服务可用,则使用 V3 流程
|
||||
if (merchantConfig.PayVersion == "V3" && _v3ServiceLazy != null)
|
||||
{
|
||||
_logger.LogInformation("<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ V3 <20>汾<EFBFBD><E6B1BE>·<EFBFBD>ɵ<EFBFBD> V3 <20><><EFBFBD><EFBFBD>: MchId={MchId}", merchantConfig.MchId);
|
||||
_logger.LogInformation("商户配置为 V3 版本,路由到 V3 流程: MchId={MchId}", merchantConfig.MchId);
|
||||
return await _v3ServiceLazy.Value.CreateJsapiOrderAsync(request);
|
||||
}
|
||||
|
||||
// 3. ʹ<EFBFBD><EFBFBD> V2 <20><><EFBFBD><EFBFBD>
|
||||
_logger.LogDebug("ʹ<EFBFBD><EFBFBD> V2 ֧<><D6A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: MchId={MchId}, PayVersion={PayVersion}",
|
||||
// 3. 使用 V2 流程
|
||||
_logger.LogDebug("使用 V2 支付流程: MchId={MchId}, PayVersion={PayVersion}",
|
||||
merchantConfig.MchId, merchantConfig.PayVersion);
|
||||
|
||||
// 4. <EFBFBD><EFBFBD>ȡ<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD>OpenId
|
||||
// 4. 获取用户信息和OpenId
|
||||
var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == request.UserId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: UserId={UserId}", request.UserId);
|
||||
_logger.LogWarning("用户不存在: UserId={UserId}", request.UserId);
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = "<EFBFBD>û<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
Msg = "用户不存在"
|
||||
};
|
||||
}
|
||||
|
||||
var openId = string.IsNullOrEmpty(request.OpenId) ? user.OpenId : request.OpenId;
|
||||
if (string.IsNullOrEmpty(openId))
|
||||
{
|
||||
_logger.LogWarning("<EFBFBD>û<EFBFBD>OpenIdΪ<EFBFBD><EFBFBD>: UserId={UserId}", request.UserId);
|
||||
_logger.LogWarning("用户OpenId为空: UserId={UserId}", request.UserId);
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = "<EFBFBD>û<EFBFBD>OpenId<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
Msg = "用户OpenId不存在"
|
||||
};
|
||||
}
|
||||
|
||||
// 5. ʹ<EFBFBD><EFBFBD><EFBFBD>ѻ<EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 5. 使用已获取的商户配置
|
||||
var appId = merchantConfig.AppId;
|
||||
var mchId = merchantConfig.MchId;
|
||||
var merchantKey = merchantConfig.Key;
|
||||
|
||||
_logger.LogDebug("ʹ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: MchId={MchId}, AppId={AppId}", mchId, appId);
|
||||
_logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId);
|
||||
|
||||
// 3. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD>
|
||||
// 6. 生成随机字符串
|
||||
var nonceStr = GenerateNonceStr();
|
||||
var callbackNonceStr = GenerateNonceStr();
|
||||
|
||||
// 4. <20><><EFBFBD>ɻص<C9BB>֪ͨURL
|
||||
// 7. 生成回调通知URL
|
||||
var notifyUrl = GenerateNotifyUrl(request.Attach, request.UserId, request.OrderNo, callbackNonceStr);
|
||||
|
||||
// 验证回调通知URL
|
||||
|
|
@ -141,17 +141,17 @@ public class WechatPayService : IWechatPayService
|
|||
return new WechatPayResult { Status = 0, Msg = "支付回调URL未配置,请联系管理员在后台配置" };
|
||||
}
|
||||
|
||||
// 5. <20><><EFBFBD><EFBFBD>֪ͨ<CDA8><D6AA>¼<EFBFBD><C2BC>order_notify<66><79>
|
||||
// 8. 保存通知记录(order_notify)
|
||||
await SaveOrderNotifyAsync(request.OrderNo, notifyUrl, callbackNonceStr, request.Amount, request.Attach, openId);
|
||||
|
||||
// 6. <20><><EFBFBD><EFBFBD>ͳһ<CDB3>µ<EFBFBD><C2B5><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 9. 构建统一下单参数
|
||||
var body = TruncateBody(request.Body, 30);
|
||||
var totalFee = (int)Math.Round(request.Amount * 100); // ת<EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>
|
||||
var totalFee = (int)Math.Round(request.Amount * 100); // 转换为分
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>Ի<EFBFBD><EFBFBD><EFBFBD><EFBFBD>£<EFBFBD>IsTest=2 <20><><EFBFBD>û<EFBFBD>֧<EFBFBD><D6A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϊ 0.01 Ԫ
|
||||
// 测试环境下,IsTest=2 的用户支付金额为 0.01 元
|
||||
if (_appSettings.IsTestEnvironment && user.IsTest == 2)
|
||||
{
|
||||
_logger.LogInformation("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: UserId={UserId}, ԭ<><D4AD><EFBFBD>={OriginalAmount}<7D><>, <20><><EFBFBD><EFBFBD>Ϊ={TestAmount}<7D><>",
|
||||
_logger.LogInformation("测试用户支付金额调整: UserId={UserId}, 原金额={OriginalAmount}分, 调整为={TestAmount}分",
|
||||
request.UserId, totalFee, TEST_USER_PAY_AMOUNT);
|
||||
totalFee = TEST_USER_PAY_AMOUNT;
|
||||
}
|
||||
|
|
@ -171,64 +171,64 @@ public class WechatPayService : IWechatPayService
|
|||
{ "openid", openId }
|
||||
};
|
||||
|
||||
// 7. <20><><EFBFBD><EFBFBD>ǩ<EFBFBD><C7A9>
|
||||
// 10. 生成签名
|
||||
unifiedOrderParams["sign"] = MakeSign(unifiedOrderParams, merchantKey);
|
||||
|
||||
// 8. ת<><D7AA>ΪXML<4D><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><CEA2>API
|
||||
// 11. 转换为XML并调用微信API
|
||||
var requestXml = DictionaryToXml(unifiedOrderParams);
|
||||
_logger.LogDebug("ͳһ<EFBFBD>µ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>XML: {Xml}", requestXml);
|
||||
_logger.LogDebug("统一下单请求XML: {Xml}", requestXml);
|
||||
|
||||
var responseXml = await PostXmlAsync(UNIFIED_ORDER_URL, requestXml);
|
||||
_logger.LogDebug("ͳһ<EFBFBD>µ<EFBFBD><EFBFBD><EFBFBD>ӦXML: {Xml}", responseXml);
|
||||
_logger.LogDebug("统一下单响应XML: {Xml}", responseXml);
|
||||
|
||||
// 9. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ
|
||||
// 12. 解析响应
|
||||
var responseData = XmlToDictionary(responseXml);
|
||||
if (responseData == null)
|
||||
{
|
||||
_logger.LogError("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧʧ<EFBFBD><EFBFBD>");
|
||||
_logger.LogError("解析微信响应失败");
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧʧ<D3A6><CAA7>)"
|
||||
Msg = "网络故障,请稍后重试(解析响应失败)"
|
||||
};
|
||||
}
|
||||
|
||||
// 10. <20><>鷵<EFBFBD>ؽ<EFBFBD><D8BD>
|
||||
// 13. 检查返回结果
|
||||
if (!responseData.TryGetValue("return_code", out var returnCode) || returnCode != "SUCCESS")
|
||||
{
|
||||
var returnMsg = responseData.GetValueOrDefault("return_msg", "δ֪<EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
_logger.LogWarning("ͳһ<EFBFBD>µ<EFBFBD>ʧ<EFBFBD><EFBFBD>: return_code={ReturnCode}, return_msg={ReturnMsg}", returnCode, returnMsg);
|
||||
var returnMsg = responseData.GetValueOrDefault("return_msg", "未知错误");
|
||||
_logger.LogWarning("统一下单失败: return_code={ReturnCode}, return_msg={ReturnMsg}", returnCode, returnMsg);
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = $"<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>({returnMsg})"
|
||||
Msg = $"网络故障,请稍后重试({returnMsg})"
|
||||
};
|
||||
}
|
||||
|
||||
if (!responseData.TryGetValue("result_code", out var resultCode) || resultCode != "SUCCESS")
|
||||
{
|
||||
var errCode = responseData.GetValueOrDefault("err_code", "");
|
||||
var errCodeDes = responseData.GetValueOrDefault("err_code_des", "δ֪<EFBFBD><EFBFBD><EFBFBD><EFBFBD>");
|
||||
_logger.LogWarning("ͳһ<EFBFBD>µ<EFBFBD>ҵ<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>: err_code={ErrCode}, err_code_des={ErrCodeDes}", errCode, errCodeDes);
|
||||
var errCodeDes = responseData.GetValueOrDefault("err_code_des", "未知错误");
|
||||
_logger.LogWarning("统一下单业务失败: err_code={ErrCode}, err_code_des={ErrCodeDes}", errCode, errCodeDes);
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = $"֧<EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>({GetErrorMessage(errCode, errCodeDes)})"
|
||||
Msg = $"支付失败({GetErrorMessage(errCode, errCodeDes)})"
|
||||
};
|
||||
}
|
||||
|
||||
// 11. <20><>ȡprepay_id
|
||||
// 14. 获取prepay_id
|
||||
if (!responseData.TryGetValue("prepay_id", out var prepayId) || string.IsNullOrEmpty(prepayId))
|
||||
{
|
||||
_logger.LogError("ͳһ<EFBFBD>µ<EFBFBD><EFBFBD>ɹ<EFBFBD><EFBFBD><EFBFBD>prepay_idΪ<EFBFBD><EFBFBD>");
|
||||
_logger.LogError("统一下单成功但prepay_id为空");
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>(prepay_idΪ<64><CEAA>)"
|
||||
Msg = "网络故障,请稍后重试(prepay_id为空)"
|
||||
};
|
||||
}
|
||||
|
||||
// 12. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD>ǰ<EFBFBD>˵<EFBFBD>֧<EFBFBD><D6A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 15. 构建返回给前端的支付参数
|
||||
var timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
|
||||
var payNonceStr = GenerateNonceStr();
|
||||
|
||||
|
|
@ -241,10 +241,10 @@ public class WechatPayService : IWechatPayService
|
|||
{ "signType", "MD5" }
|
||||
};
|
||||
|
||||
// 13. <20><><EFBFBD><EFBFBD>֧<EFBFBD><D6A7>ǩ<EFBFBD><C7A9>
|
||||
// 16. 生成支付签名
|
||||
var paySign = MakeSign(payParams, merchantKey);
|
||||
|
||||
_logger.LogInformation("<EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɹ<EFBFBD>: OrderNo={OrderNo}, PrepayId={PrepayId}", request.OrderNo, prepayId);
|
||||
_logger.LogInformation("微信支付订单创建成功: OrderNo={OrderNo}, PrepayId={PrepayId}", request.OrderNo, prepayId);
|
||||
|
||||
return new WechatPayResult
|
||||
{
|
||||
|
|
@ -264,17 +264,17 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣: OrderNo={OrderNo}", request.OrderNo);
|
||||
_logger.LogError(ex, "创建微信支付订单异常: OrderNo={OrderNo}", request.OrderNo);
|
||||
return new WechatPayResult
|
||||
{
|
||||
Status = 0,
|
||||
Msg = "ϵͳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ժ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"
|
||||
Msg = "系统错误,请稍后重试"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>ɻص<EFBFBD>֪ͨURL
|
||||
/// 生成回调通知URL
|
||||
/// </summary>
|
||||
private string GenerateNotifyUrl(string attach, long userId, string orderNo, string nonceStr)
|
||||
{
|
||||
|
|
@ -288,7 +288,7 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>涩<EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD>¼
|
||||
/// 保存订单通知记录
|
||||
/// </summary>
|
||||
private async Task SaveOrderNotifyAsync(string orderNo, string notifyUrl, string nonceStr, decimal amount, string attach, string openId)
|
||||
{
|
||||
|
|
@ -310,20 +310,19 @@ public class WechatPayService : IWechatPayService
|
|||
_dbContext.OrderNotifies.Add(orderNotify);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogDebug("<EFBFBD><EFBFBD><EFBFBD>涩<EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD>¼: OrderNo={OrderNo}, NotifyUrl={NotifyUrl}", orderNo, notifyUrl);
|
||||
_logger.LogDebug("保存订单通知记录: OrderNo={OrderNo}, NotifyUrl={NotifyUrl}", orderNo, notifyUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD>ض<EFBFBD><EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>
|
||||
/// 截断商品描述(微信有最大长度限制)
|
||||
/// </summary>
|
||||
private static string TruncateBody(string body, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(body))
|
||||
{
|
||||
return "<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
|
||||
return "商品购买";
|
||||
}
|
||||
|
||||
// ʹ<><CAB9><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֽ<EFBFBD><D6BD><EFBFBD>
|
||||
if (body.Length <= maxLength)
|
||||
{
|
||||
return body;
|
||||
|
|
@ -333,7 +332,7 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>32λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 生成32位随机字符串
|
||||
/// </summary>
|
||||
private static string GenerateNonceStr(int length = 32)
|
||||
{
|
||||
|
|
@ -350,17 +349,17 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ȡ<EFBFBD>ͻ<EFBFBD><EFBFBD><EFBFBD>IP
|
||||
/// 获取客户端IP
|
||||
/// </summary>
|
||||
private static string GetClientIp()
|
||||
{
|
||||
// <EFBFBD><EFBFBD>ʵ<EFBFBD>ʻ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ<EFBFBD>ô<EFBFBD>HttpContext<EFBFBD><EFBFBD>ȡ
|
||||
// <EFBFBD><EFBFBD><EFBFBD>ﷵ<EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>ʵ<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD>Ҫͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD><EFBFBD>ȡIHttpContextAccessor
|
||||
// 在实际环境中应该从HttpContext获取
|
||||
// 这里返回默认值,实际使用时需要通过依赖注入获取IHttpContextAccessor
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD>ת<EFBFBD><EFBFBD>ΪXML
|
||||
/// 将字典转换为XML
|
||||
/// </summary>
|
||||
private static string DictionaryToXml(Dictionary<string, string> parameters)
|
||||
{
|
||||
|
|
@ -374,7 +373,7 @@ public class WechatPayService : IWechatPayService
|
|||
continue;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͳ<EFBFBD><EFBFBD><EFBFBD>ҪCDATA
|
||||
// 数字类型不需要CDATA
|
||||
if (int.TryParse(kvp.Value, out _) || decimal.TryParse(kvp.Value, out _))
|
||||
{
|
||||
sb.Append($"<{kvp.Key}>{kvp.Value}</{kvp.Key}>");
|
||||
|
|
@ -390,7 +389,7 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>XMLת<EFBFBD><EFBFBD>Ϊ<EFBFBD>ֵ<EFBFBD>
|
||||
/// 将XML转换为字典
|
||||
/// </summary>
|
||||
private static Dictionary<string, string>? XmlToDictionary(string xml)
|
||||
{
|
||||
|
|
@ -428,7 +427,7 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>XML POST<53><54><EFBFBD><EFBFBD>
|
||||
/// 发送XML POST请求
|
||||
/// </summary>
|
||||
private async Task<string> PostXmlAsync(string url, string xml, int timeout = 30)
|
||||
{
|
||||
|
|
@ -444,34 +443,34 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>XML<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><EFBFBD>: Url={Url}", url);
|
||||
_logger.LogError(ex, "发送XML请求失败: Url={Url}", url);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ
|
||||
/// 获取错误信息
|
||||
/// </summary>
|
||||
private static string GetErrorMessage(string errCode, string errCodeDes)
|
||||
{
|
||||
var errorMessages = new Dictionary<string, string>
|
||||
{
|
||||
{ "NOAUTH", "<EFBFBD>̻<EFBFBD>δ<EFBFBD><EFBFBD>ͨ<EFBFBD>˽ӿ<EFBFBD>Ȩ<EFBFBD><EFBFBD>" },
|
||||
{ "NOTENOUGH", "<EFBFBD>û<EFBFBD><EFBFBD>ʺ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "ORDERNOTEXIST", "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ų<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "ORDERPAID", "<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "ORDERCLOSED", "<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹرգ<EFBFBD><EFBFBD><EFBFBD>֧<EFBFBD><EFBFBD>" },
|
||||
{ "SYSTEMERROR", "ϵͳ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>!ϵͳ<CFB5><CDB3>ʱ" },
|
||||
{ "APPID_NOT_EXIST", "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><EFBFBD>APPID" },
|
||||
{ "MCHID_NOT_EXIST", "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><EFBFBD>MCHID" },
|
||||
{ "APPID_MCHID_NOT_MATCH", "appid<EFBFBD><EFBFBD>mch_id<EFBFBD><EFBFBD>ƥ<EFBFBD><EFBFBD>" },
|
||||
{ "LACK_PARAMS", "ȱ<EFBFBD>ٱ<EFBFBD>Ҫ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "OUT_TRADE_NO_USED", "ͬһ<EFBFBD>ʽ<EFBFBD><EFBFBD>ײ<EFBFBD><EFBFBD>ܶ<EFBFBD><EFBFBD><EFBFBD>ύ" },
|
||||
{ "SIGNERROR", "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷ" },
|
||||
{ "XML_FORMAT_ERROR", "XML<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "REQUIRE_POST_METHOD", "δʹ<EFBFBD><EFBFBD>post<EFBFBD><EFBFBD><EFBFBD>ݲ<EFBFBD><EFBFBD><EFBFBD>" },
|
||||
{ "POST_DATA_EMPTY", "post<EFBFBD><EFBFBD><EFBFBD>ݲ<EFBFBD><EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>" },
|
||||
{ "NOT_UTF8", "δʹ<EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ" }
|
||||
{ "NOAUTH", "商户未开通此接口权限" },
|
||||
{ "NOTENOUGH", "用户账号余额不足" },
|
||||
{ "ORDERNOTEXIST", "订单号不存在" },
|
||||
{ "ORDERPAID", "商户订单已支付,请勿重复提交" },
|
||||
{ "ORDERCLOSED", "当前订单已关闭,无法支付" },
|
||||
{ "SYSTEMERROR", "系统错误!系统超时" },
|
||||
{ "APPID_NOT_EXIST", "参数中缺少APPID" },
|
||||
{ "MCHID_NOT_EXIST", "参数中缺少MCHID" },
|
||||
{ "APPID_MCHID_NOT_MATCH", "appid和mch_id不匹配" },
|
||||
{ "LACK_PARAMS", "缺少必要的请求参数" },
|
||||
{ "OUT_TRADE_NO_USED", "同一笔交易不能多次提交" },
|
||||
{ "SIGNERROR", "参数签名结果不正确" },
|
||||
{ "XML_FORMAT_ERROR", "XML格式错误" },
|
||||
{ "REQUIRE_POST_METHOD", "未使用post传递参数" },
|
||||
{ "POST_DATA_EMPTY", "post数据不能为空" },
|
||||
{ "NOT_UTF8", "未使用指定编码格式" }
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(errCode) && errorMessages.TryGetValue(errCode, out var message))
|
||||
|
|
@ -487,7 +486,7 @@ public class WechatPayService : IWechatPayService
|
|||
{
|
||||
if (string.IsNullOrEmpty(sign))
|
||||
{
|
||||
_logger.LogWarning("ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤ʧ<EFBFBD>ܣ<EFBFBD>ǩ<EFBFBD><EFBFBD>Ϊ<EFBFBD><EFBFBD>");
|
||||
_logger.LogWarning("签名验证失败:签名为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -496,7 +495,7 @@ public class WechatPayService : IWechatPayService
|
|||
|
||||
if (!isValid)
|
||||
{
|
||||
_logger.LogWarning("ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֤ʧ<EFBFBD>ܣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><EFBFBD>={CalculatedSign}<7D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǩ<EFBFBD><C7A9>={Sign}", calculatedSign, sign);
|
||||
_logger.LogWarning("签名验证失败:计算签名={CalculatedSign},传入签名={Sign}", calculatedSign, sign);
|
||||
}
|
||||
|
||||
return isValid;
|
||||
|
|
@ -505,35 +504,35 @@ public class WechatPayService : IWechatPayService
|
|||
/// <inheritdoc />
|
||||
public string MakeSign(Dictionary<string, string> parameters, string? merchantKey = null)
|
||||
{
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ
|
||||
// 获取商户密钥
|
||||
var key = merchantKey ?? _settings.DefaultMerchant.Key;
|
||||
|
||||
// ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ASCII<EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// <EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>sign<EFBFBD>ֶ<EFBFBD>
|
||||
// 签名步骤一:将字典按参数名ASCII码从小到大排序
|
||||
// 过滤掉空值和sign字段
|
||||
var sortedParams = parameters
|
||||
.Where(p => !string.IsNullOrEmpty(p.Value) && !string.Equals(p.Key, "sign", StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(p => p.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
// ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƴ<EFBFBD><EFBFBD>ΪURL<EFBFBD><EFBFBD>ʽ key=value&key=value
|
||||
// 签名步骤二:将参数拼接为URL格式 key=value&key=value
|
||||
var urlParams = ToUrlParams(sortedParams);
|
||||
|
||||
// ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>string<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>KEY
|
||||
// 签名步骤三:在string后面加上KEY
|
||||
var signString = $"{urlParams}&key={key}";
|
||||
|
||||
// ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģ<EFBFBD>MD5<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 签名步骤四:MD5加密
|
||||
using var md5 = MD5.Create();
|
||||
var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signString));
|
||||
|
||||
// ǩ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>壺<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD>תΪ<EFBFBD><EFBFBD>д
|
||||
// 签名步骤五:所有字符转为大写
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToUpper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƴ<EFBFBD><EFBFBD>ΪURL<EFBFBD><EFBFBD>ʽ: key=value&key=value
|
||||
/// 将参数拼接为URL格式: key=value&key=value
|
||||
/// </summary>
|
||||
/// <param name="parameters"><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>б<EFBFBD></param>
|
||||
/// <returns>URL<EFBFBD><EFBFBD>ʽ<EFBFBD>ַ<EFBFBD><EFBFBD><EFBFBD></returns>
|
||||
/// <param name="parameters">排序后的参数列表</param>
|
||||
/// <returns>URL格式字符串</returns>
|
||||
private static string ToUrlParams(IEnumerable<KeyValuePair<string, string>> parameters)
|
||||
{
|
||||
var parts = parameters.Select(p => $"{p.Key}={p.Value}");
|
||||
|
|
@ -541,10 +540,10 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>ݶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ż<EFBFBD>ȡ<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ
|
||||
/// 根据订单号获取商户密钥
|
||||
/// </summary>
|
||||
/// <param name="orderNo"><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
|
||||
/// <returns><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ</returns>
|
||||
/// <param name="orderNo">订单号</param>
|
||||
/// <returns>商户密钥</returns>
|
||||
public string GetMerchantKeyByOrderNo(string orderNo)
|
||||
{
|
||||
var merchant = GetMerchantByOrderNo(orderNo);
|
||||
|
|
@ -552,13 +551,13 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>֤<EFBFBD>Żص<EFBFBD>ǩ<EFBFBD><EFBFBD>
|
||||
/// 验证微信回调签名
|
||||
/// </summary>
|
||||
/// <param name="notifyData"><EFBFBD>ص<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
|
||||
/// <returns><EFBFBD>Ƿ<EFBFBD><EFBFBD><EFBFBD>֤ͨ<EFBFBD><EFBFBD></returns>
|
||||
/// <param name="notifyData">回调数据</param>
|
||||
/// <returns>是否验证通过</returns>
|
||||
public bool VerifyNotifySign(WechatNotifyData notifyData)
|
||||
{
|
||||
// <EFBFBD>ӻص<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD>
|
||||
// 从回调数据构建参数字典
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "return_code", notifyData.ReturnCode },
|
||||
|
|
@ -579,7 +578,7 @@ public class WechatPayService : IWechatPayService
|
|||
{ "time_end", notifyData.TimeEnd }
|
||||
};
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD>ѡ<EFBFBD>ֶ<EFBFBD>
|
||||
// 添加可选字段
|
||||
if (!string.IsNullOrEmpty(notifyData.ErrCode))
|
||||
parameters["err_code"] = notifyData.ErrCode;
|
||||
if (!string.IsNullOrEmpty(notifyData.ErrCodeDes))
|
||||
|
|
@ -587,34 +586,34 @@ public class WechatPayService : IWechatPayService
|
|||
if (!string.IsNullOrEmpty(notifyData.SignType))
|
||||
parameters["sign_type"] = notifyData.SignType;
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD>Ż<EFBFBD>ȡ<EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Կ
|
||||
// 根据商户号获取对应的密钥
|
||||
var merchantKey = GetMerchantKeyByMchId(notifyData.MchId);
|
||||
|
||||
return VerifySign(parameters, notifyData.Sign, merchantKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD>Ż<EFBFBD>ȡ<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ
|
||||
/// 根据商户号获取商户密钥
|
||||
/// </summary>
|
||||
/// <param name="mchId"><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD></param>
|
||||
/// <returns><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ</returns>
|
||||
/// <param name="mchId">商户号</param>
|
||||
/// <returns>商户密钥</returns>
|
||||
private string GetMerchantKeyByMchId(string mchId)
|
||||
{
|
||||
// <EFBFBD>ȴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD>б<EFBFBD><EFBFBD>в<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 先从配置的商户列表中查找
|
||||
var merchant = _settings.Merchants.FirstOrDefault(m => m.MchId == mchId);
|
||||
if (merchant != null)
|
||||
{
|
||||
return merchant.Key;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>û<EFBFBD>ҵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD>
|
||||
// 如果没找到,检查默认商户
|
||||
if (_settings.DefaultMerchant.MchId == mchId)
|
||||
{
|
||||
return _settings.DefaultMerchant.Key;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĭ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD>Կ
|
||||
_logger.LogWarning("δ<EFBFBD>ҵ<EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD> {MchId} <20><><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD>ʹ<EFBFBD><CAB9>Ĭ<EFBFBD><C4AC><EFBFBD>̻<EFBFBD><CCBB><EFBFBD>Կ", mchId);
|
||||
// 返回默认商户密钥
|
||||
_logger.LogWarning("未找到商户号 {MchId} 的配置,使用默认商户密钥", mchId);
|
||||
return _settings.DefaultMerchant.Key;
|
||||
}
|
||||
|
||||
|
|
@ -623,49 +622,49 @@ public class WechatPayService : IWechatPayService
|
|||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD><EFBFBD>Ͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ: OrderNo={OrderNo}, OpenId={OpenId}",
|
||||
_logger.LogInformation("开始发送订单发货通知: OrderNo={OrderNo}, OpenId={OpenId}",
|
||||
request.OrderNo, request.OpenId);
|
||||
|
||||
// 1. <EFBFBD><EFBFBD><EFBFBD>ݶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ż<EFBFBD>ȡ<EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 1. 根据订单号获取商户配置
|
||||
var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo);
|
||||
var mchId = merchantConfig.MchId;
|
||||
var appId = merchantConfig.AppId;
|
||||
|
||||
_logger.LogDebug("ʹ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: MchId={MchId}, AppId={AppId}", mchId, appId);
|
||||
_logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId);
|
||||
|
||||
// 2. <EFBFBD><EFBFBD>ȡaccess_token
|
||||
// 2. 获取access_token
|
||||
var accessToken = await _wechatService.GetAccessTokenAsync(appId);
|
||||
if (string.IsNullOrEmpty(accessToken))
|
||||
{
|
||||
_logger.LogError("<EFBFBD><EFBFBD>ȡaccess_tokenʧ<EFBFBD><EFBFBD>: AppId={AppId}", appId);
|
||||
_logger.LogError("获取access_token失败: AppId={AppId}", appId);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>
|
||||
await SaveFailedShippingOrderAsync(request, merchantConfig, -1, "<EFBFBD><EFBFBD>ȡaccess_tokenʧ<EFBFBD><EFBFBD>");
|
||||
// 保存到自动重试队列
|
||||
await SaveFailedShippingOrderAsync(request, merchantConfig, -1, "获取access_token失败");
|
||||
|
||||
return new OrderShippingNotifyResult
|
||||
{
|
||||
Success = false,
|
||||
ErrCode = -1,
|
||||
ErrMsg = "<EFBFBD><EFBFBD>ȡaccess_tokenʧ<EFBFBD><EFBFBD>",
|
||||
ErrMsg = "获取access_token失败",
|
||||
QueuedForRetry = true
|
||||
};
|
||||
}
|
||||
|
||||
// 3. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD>Ϣ
|
||||
// 3. 构建发货通知消息
|
||||
var itemDesc = GetShippingItemDesc(request);
|
||||
|
||||
// 4. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 4. 构建请求参数
|
||||
var uploadTime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00";
|
||||
var requestBody = new
|
||||
{
|
||||
order_key = new
|
||||
{
|
||||
order_number_type = 1, // ʹ<EFBFBD><EFBFBD><EFBFBD>̻<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
order_number_type = 1, // 使用商户订单号
|
||||
mchid = mchId,
|
||||
out_trade_no = request.OrderNo
|
||||
},
|
||||
logistics_type = request.LogisticsType, // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͣ<EFBFBD>4=<3D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʒ
|
||||
delivery_mode = request.DeliveryMode, // <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>1=ͳһ<CDB3><D2BB><EFBFBD><EFBFBD>
|
||||
logistics_type = request.LogisticsType, // 物流类型,4=虚拟商品
|
||||
delivery_mode = request.DeliveryMode, // 发货模式,1=统一发货
|
||||
shipping_list = new[]
|
||||
{
|
||||
new
|
||||
|
|
@ -682,32 +681,32 @@ public class WechatPayService : IWechatPayService
|
|||
|
||||
var requestJson = JsonSerializer.Serialize(requestBody);
|
||||
|
||||
// 5. <EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>־
|
||||
_logger.LogDebug("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>: OrderNo={OrderNo}, MchId={MchId}, Request={Request}",
|
||||
// 5. 记录请求日志
|
||||
_logger.LogDebug("发货通知请求: OrderNo={OrderNo}, MchId={MchId}, Request={Request}",
|
||||
request.OrderNo, mchId, requestJson);
|
||||
|
||||
// 6. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>API
|
||||
// 6. 调用微信API
|
||||
var requestUrl = $"{SHIPPING_NOTIFY_URL}?access_token={accessToken}";
|
||||
var content = new StringContent(requestJson, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync(requestUrl, content);
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// 7. <EFBFBD><EFBFBD>¼<EFBFBD><EFBFBD>Ӧ<EFBFBD><EFBFBD>־
|
||||
_logger.LogDebug("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD><EFBFBD>Ӧ: OrderNo={OrderNo}, Response={Response}",
|
||||
// 7. 记录响应日志
|
||||
_logger.LogDebug("发货通知响应: OrderNo={OrderNo}, Response={Response}",
|
||||
request.OrderNo, responseContent);
|
||||
|
||||
// 8. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ
|
||||
// 8. 解析响应
|
||||
using var jsonDoc = JsonDocument.Parse(responseContent);
|
||||
var root = jsonDoc.RootElement;
|
||||
|
||||
var errCode = root.TryGetProperty("errcode", out var errCodeProp) ? errCodeProp.GetInt32() : -1;
|
||||
var errMsg = root.TryGetProperty("errmsg", out var errMsgProp) ? errMsgProp.GetString() ?? "unknown" : "unknown";
|
||||
|
||||
// 9. <EFBFBD>жϽ<EFBFBD><EFBFBD>
|
||||
// 9. 判断结果
|
||||
if (errCode == 0 && errMsg == "ok")
|
||||
{
|
||||
_logger.LogInformation("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD>ɹ<EFBFBD>: OrderNo={OrderNo}", request.OrderNo);
|
||||
_logger.LogInformation("发货通知成功: OrderNo={OrderNo}", request.OrderNo);
|
||||
return new OrderShippingNotifyResult
|
||||
{
|
||||
Success = true,
|
||||
|
|
@ -717,10 +716,10 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨʧ<EFBFBD><EFBFBD>: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}",
|
||||
_logger.LogWarning("发货通知失败: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}",
|
||||
request.OrderNo, errCode, errMsg);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 保存到自动重试队列
|
||||
await SaveFailedShippingOrderAsync(request, merchantConfig, errCode, errMsg);
|
||||
|
||||
return new OrderShippingNotifyResult
|
||||
|
|
@ -734,9 +733,9 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>֪ͨ<EFBFBD>쳣: OrderNo={OrderNo}", request.OrderNo);
|
||||
_logger.LogError(ex, "发货通知异常: OrderNo={OrderNo}", request.OrderNo);
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>
|
||||
// 尝试保存到自动重试队列
|
||||
try
|
||||
{
|
||||
var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo);
|
||||
|
|
@ -744,7 +743,7 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
catch (Exception saveEx)
|
||||
{
|
||||
_logger.LogError(saveEx, "<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: OrderNo={OrderNo}", request.OrderNo);
|
||||
_logger.LogError(saveEx, "保存失败订单到自动重试队列时发生错误: OrderNo={OrderNo}", request.OrderNo);
|
||||
}
|
||||
|
||||
return new OrderShippingNotifyResult
|
||||
|
|
@ -758,41 +757,45 @@ public class WechatPayService : IWechatPayService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 获取发货商品描述
|
||||
/// </summary>
|
||||
private static string GetShippingItemDesc(OrderShippingNotifyRequest request)
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ָ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֱ<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD>
|
||||
// 如果请求中指定了描述,直接使用
|
||||
if (!string.IsNullOrEmpty(request.ItemDesc))
|
||||
{
|
||||
return request.ItemDesc;
|
||||
}
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD>ݶ<EFBFBD><EFBFBD><EFBFBD>ǰ<EFBFBD>ж<EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// 根据订单前缀判断消息内容
|
||||
if (request.OrderNo.StartsWith("FH_"))
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
return "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD>ͷ<EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ";
|
||||
// 发货订单
|
||||
return "您购买的实物商品正在处理中,请联系客服获取物流信息";
|
||||
}
|
||||
else
|
||||
{
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>
|
||||
return "<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>IJ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѿ<EFBFBD>ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>в鿴";
|
||||
// 虚拟商品(测评服务等)
|
||||
return "您购买的测评服务已开通,请在小程序中查看";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <EFBFBD><EFBFBD><EFBFBD>淢<EFBFBD><EFBFBD>ʧ<EFBFBD>ܵĶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Redis<EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><EFBFBD><EFBFBD>
|
||||
/// 保存发货失败订单到Redis自动重试队列
|
||||
/// </summary>
|
||||
/// <param name="request">发货通知请求</param>
|
||||
/// <param name="merchantConfig">商户配置</param>
|
||||
/// <param name="errCode">错误码</param>
|
||||
/// <param name="errMsg">错误信息</param>
|
||||
private async Task SaveFailedShippingOrderAsync(
|
||||
OrderShippingNotifyRequest request,
|
||||
OrderShippingNotifyRequest request,
|
||||
WechatPayMerchantConfig merchantConfig,
|
||||
int errorCode,
|
||||
string errorMsg)
|
||||
int errCode,
|
||||
string errMsg)
|
||||
{
|
||||
try
|
||||
{
|
||||
var key = $"{FAILED_SHIPPING_KEY_PREFIX}{request.OrderNo}";
|
||||
var itemDesc = GetShippingItemDesc(request);
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
var failedOrder = new FailedShippingOrderData
|
||||
|
|
@ -801,38 +804,44 @@ public class WechatPayService : IWechatPayService
|
|||
AppId = merchantConfig.AppId,
|
||||
OrderNo = request.OrderNo,
|
||||
MchId = merchantConfig.MchId,
|
||||
ItemDesc = GetShippingItemDesc(request),
|
||||
ErrorCode = errorCode,
|
||||
ErrorMsg = errorMsg,
|
||||
ItemDesc = itemDesc,
|
||||
ErrorCode = errCode,
|
||||
ErrorMsg = errMsg,
|
||||
RetryCount = 0,
|
||||
LastRetryTime = now,
|
||||
CreateTime = now
|
||||
};
|
||||
|
||||
var redisKey = $"{FAILED_SHIPPING_KEY_PREFIX}{request.OrderNo}";
|
||||
var json = JsonSerializer.Serialize(failedOrder);
|
||||
await _redisService.SetStringAsync(key, json, FAILED_SHIPPING_EXPIRY);
|
||||
|
||||
_logger.LogInformation("<22>ѽ<EFBFBD><D1BD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD>ܶ<EFBFBD><DCB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD>: OrderNo={OrderNo}, Key={Key}",
|
||||
request.OrderNo, key);
|
||||
await _redisService.SetAsync(redisKey, json, FAILED_SHIPPING_EXPIRY);
|
||||
|
||||
_logger.LogInformation("已保存发货失败订单到重试队列: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}",
|
||||
request.OrderNo, errCode, errMsg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "<22><><EFBFBD>淢<EFBFBD><E6B7A2>ʧ<EFBFBD>ܶ<EFBFBD><DCB6><EFBFBD><EFBFBD><EFBFBD>Redisʱ<73><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: OrderNo={OrderNo}", request.OrderNo);
|
||||
throw;
|
||||
_logger.LogError(ex, "保存发货失败订单到Redis异常: OrderNo={OrderNo}", request.OrderNo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatPayMerchantConfig GetMerchantByOrderNo(string orderNo)
|
||||
{
|
||||
// ʹ<><CAB9><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD>ȡ<EFBFBD>̻<EFBFBD><CCBB><EFBFBD><EFBFBD><EFBFBD>
|
||||
return _configService.GetMerchantByOrderNo(orderNo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatNotifyData ParseNotifyXml(string xmlData)
|
||||
{
|
||||
var result = new WechatNotifyData();
|
||||
var notifyData = new WechatNotifyData();
|
||||
|
||||
if (string.IsNullOrEmpty(xmlData))
|
||||
{
|
||||
_logger.LogWarning("回调XML数据为空");
|
||||
return notifyData;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -840,43 +849,53 @@ public class WechatPayService : IWechatPayService
|
|||
doc.LoadXml(xmlData);
|
||||
|
||||
var root = doc.DocumentElement;
|
||||
if (root == null) return result;
|
||||
|
||||
result.ReturnCode = GetXmlNodeValue(root, "return_code");
|
||||
result.ReturnMsg = GetXmlNodeValue(root, "return_msg");
|
||||
result.ResultCode = GetXmlNodeValue(root, "result_code");
|
||||
result.ErrCode = GetXmlNodeValue(root, "err_code");
|
||||
result.ErrCodeDes = GetXmlNodeValue(root, "err_code_des");
|
||||
result.AppId = GetXmlNodeValue(root, "appid");
|
||||
result.MchId = GetXmlNodeValue(root, "mch_id");
|
||||
result.NonceStr = GetXmlNodeValue(root, "nonce_str");
|
||||
result.Sign = GetXmlNodeValue(root, "sign");
|
||||
result.SignType = GetXmlNodeValue(root, "sign_type");
|
||||
result.OpenId = GetXmlNodeValue(root, "openid");
|
||||
result.TradeType = GetXmlNodeValue(root, "trade_type");
|
||||
result.BankType = GetXmlNodeValue(root, "bank_type");
|
||||
result.FeeType = GetXmlNodeValue(root, "fee_type");
|
||||
result.TransactionId = GetXmlNodeValue(root, "transaction_id");
|
||||
result.OutTradeNo = GetXmlNodeValue(root, "out_trade_no");
|
||||
result.Attach = GetXmlNodeValue(root, "attach");
|
||||
result.TimeEnd = GetXmlNodeValue(root, "time_end");
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EEA3A8>λ<EFBFBD><CEBB><EFBFBD>֣<EFBFBD>
|
||||
if (int.TryParse(GetXmlNodeValue(root, "total_fee"), out var totalFee))
|
||||
if (root == null)
|
||||
{
|
||||
result.TotalFee = totalFee;
|
||||
_logger.LogWarning("回调XML根节点为空");
|
||||
return notifyData;
|
||||
}
|
||||
if (int.TryParse(GetXmlNodeValue(root, "cash_fee"), out var cashFee))
|
||||
|
||||
notifyData.ReturnCode = GetXmlNodeValue(root, "return_code");
|
||||
notifyData.ReturnMsg = GetXmlNodeValue(root, "return_msg");
|
||||
notifyData.ResultCode = GetXmlNodeValue(root, "result_code");
|
||||
notifyData.ErrCode = GetXmlNodeValue(root, "err_code");
|
||||
notifyData.ErrCodeDes = GetXmlNodeValue(root, "err_code_des");
|
||||
notifyData.AppId = GetXmlNodeValue(root, "appid");
|
||||
notifyData.MchId = GetXmlNodeValue(root, "mch_id");
|
||||
notifyData.NonceStr = GetXmlNodeValue(root, "nonce_str");
|
||||
notifyData.Sign = GetXmlNodeValue(root, "sign");
|
||||
notifyData.SignType = GetXmlNodeValue(root, "sign_type");
|
||||
notifyData.OpenId = GetXmlNodeValue(root, "openid");
|
||||
notifyData.TradeType = GetXmlNodeValue(root, "trade_type");
|
||||
notifyData.BankType = GetXmlNodeValue(root, "bank_type");
|
||||
notifyData.FeeType = GetXmlNodeValue(root, "fee_type");
|
||||
notifyData.TransactionId = GetXmlNodeValue(root, "transaction_id");
|
||||
notifyData.OutTradeNo = GetXmlNodeValue(root, "out_trade_no");
|
||||
notifyData.Attach = GetXmlNodeValue(root, "attach");
|
||||
notifyData.TimeEnd = GetXmlNodeValue(root, "time_end");
|
||||
|
||||
// 解析金额字段
|
||||
var totalFeeStr = GetXmlNodeValue(root, "total_fee");
|
||||
if (int.TryParse(totalFeeStr, out var totalFee))
|
||||
{
|
||||
result.CashFee = cashFee;
|
||||
notifyData.TotalFee = totalFee;
|
||||
}
|
||||
|
||||
var cashFeeStr = GetXmlNodeValue(root, "cash_fee");
|
||||
if (int.TryParse(cashFeeStr, out var cashFee))
|
||||
{
|
||||
notifyData.CashFee = cashFee;
|
||||
}
|
||||
|
||||
_logger.LogDebug("解析回调XML成功: OutTradeNo={OutTradeNo}, TransactionId={TransactionId}",
|
||||
notifyData.OutTradeNo, notifyData.TransactionId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "<22><><EFBFBD><EFBFBD><EFBFBD>Żص<C5BB>XMLʧ<4C><CAA7>: {XmlData}", xmlData);
|
||||
_logger.LogError(ex, "解析回调XML异常");
|
||||
}
|
||||
|
||||
return result;
|
||||
return notifyData;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -885,6 +904,12 @@ public class WechatPayService : IWechatPayService
|
|||
return $"<xml><return_code><![CDATA[{returnCode}]]></return_code><return_msg><![CDATA[{returnMsg}]]></return_msg></xml>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取XML节点值
|
||||
/// </summary>
|
||||
/// <param name="root">XML根节点</param>
|
||||
/// <param name="nodeName">节点名称</param>
|
||||
/// <returns>节点值,不存在则返回空字符串</returns>
|
||||
private static string GetXmlNodeValue(XmlElement root, string nodeName)
|
||||
{
|
||||
var node = root.SelectSingleNode(nodeName);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user