HaniBlindBox/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs
2026-01-27 13:18:10 +08:00

849 lines
33 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using HoneyBox.Core.Interfaces;
using HoneyBox.Model.Data;
using HoneyBox.Model.Entities;
using HoneyBox.Model.Models.Auth;
using HoneyBox.Model.Models.Payment;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace HoneyBox.Core.Services;
/// <summary>
/// 微信服务实现
/// </summary>
public class WechatService : IWechatService
{
private readonly HttpClient _httpClient;
private readonly ILogger<WechatService> _logger;
private readonly WechatPaySettings _wechatPaySettings;
private readonly IRedisService _redisService;
private readonly HoneyBoxDbContext _dbContext;
// 微信API端点
private const string WechatCodeToSessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
private const string WechatGetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
private const string WechatGetAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token";
// Redis缓存键前缀
private const string AccessTokenCacheKeyPrefix = "wechat:access_token:";
public WechatService(
HttpClient httpClient,
ILogger<WechatService> logger,
IOptions<WechatPaySettings> wechatPaySettings,
IRedisService redisService,
HoneyBoxDbContext dbContext)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_wechatPaySettings = wechatPaySettings?.Value ?? throw new ArgumentNullException(nameof(wechatPaySettings));
_redisService = redisService ?? throw new ArgumentNullException(nameof(redisService));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
}
/// <summary>
/// 获取微信openid和unionid
/// </summary>
public async Task<WechatAuthResult> GetOpenIdAsync(string code)
{
_logger.LogInformation("[微信登录] 开始处理code={Code}", code);
if (string.IsNullOrWhiteSpace(code))
{
_logger.LogWarning("[微信登录] code为空");
return new WechatAuthResult
{
Success = false,
ErrorMessage = "授权code不能为空"
};
}
try
{
// 从数据库获取微信配置
var wechatConfig = await GetWechatSettingFromDbAsync();
if (wechatConfig == null)
{
_logger.LogError("[微信登录] 未找到小程序配置,请在后台管理系统中配置 miniprogram_setting");
return new WechatAuthResult
{
Success = false,
ErrorMessage = "小程序配置未设置,请联系管理员"
};
}
var appId = wechatConfig.AppId;
var appSecret = wechatConfig.AppSecret;
// 记录配置信息(脱敏)
var maskedAppId = appId?.Length > 8
? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}"
: "未配置";
var maskedSecret = string.IsNullOrEmpty(appSecret)
? "未配置"
: $"{appSecret.Substring(0, 4)}****";
_logger.LogInformation("[微信登录] 配置信息: AppId={AppId}, AppSecret={AppSecret}, 来源=数据库",
maskedAppId, maskedSecret);
var url = $"{WechatCodeToSessionUrl}?appid={appId}&secret={appSecret}&js_code={code}&grant_type=authorization_code";
_logger.LogInformation("[微信登录] 调用微信API: {Url}", WechatCodeToSessionUrl);
var response = await _httpClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
_logger.LogInformation("[微信登录] 微信API响应状态码: {StatusCode}", response.StatusCode);
_logger.LogInformation("[微信登录] 微信API响应内容: {Content}", content);
if (!response.IsSuccessStatusCode)
{
_logger.LogError("[微信登录] 微信API返回HTTP错误 {StatusCode}: {Content}", response.StatusCode, content);
return new WechatAuthResult
{
Success = false,
ErrorMessage = "微信API调用失败"
};
}
using var jsonDoc = JsonDocument.Parse(content);
var root = jsonDoc.RootElement;
// 检查是否有错误
if (root.TryGetProperty("errcode", out var errCode) && errCode.GetInt32() != 0)
{
var errMsg = root.TryGetProperty("errmsg", out var msg) ? msg.GetString() : "未知错误";
_logger.LogWarning("[微信登录] 微信API返回业务错误: errcode={ErrorCode}, errmsg={ErrorMessage}", errCode.GetInt32(), errMsg);
return new WechatAuthResult
{
Success = false,
ErrorMessage = $"微信授权失败: {errMsg}"
};
}
// 提取openid和unionid
var openId = root.TryGetProperty("openid", out var openIdProp) ? openIdProp.GetString() : null;
var unionId = root.TryGetProperty("unionid", out var unionIdProp) ? unionIdProp.GetString() : null;
var sessionKey = root.TryGetProperty("session_key", out var sessionKeyProp) ? sessionKeyProp.GetString() : null;
_logger.LogInformation("[微信登录] 解析结果: openid={OpenId}, unionid={UnionId}, session_key={SessionKey}",
openId ?? "null",
unionId ?? "null",
string.IsNullOrEmpty(sessionKey) ? "null" : "已获取");
if (string.IsNullOrEmpty(openId))
{
_logger.LogError("[微信登录] 微信API响应中缺少openid");
return new WechatAuthResult
{
Success = false,
ErrorMessage = "微信返回数据异常"
};
}
_logger.LogInformation("[微信登录] 成功获取openid: {OpenId}", openId);
return new WechatAuthResult
{
Success = true,
OpenId = openId,
UnionId = unionId
};
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "[微信登录] HTTP请求异常: {Message}", ex.Message);
return new WechatAuthResult
{
Success = false,
ErrorMessage = "网络连接失败"
};
}
catch (JsonException ex)
{
_logger.LogError(ex, "[微信登录] JSON解析异常: {Message}", ex.Message);
return new WechatAuthResult
{
Success = false,
ErrorMessage = "响应数据格式错误"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "[微信登录] 未知异常: {Message}", ex.Message);
return new WechatAuthResult
{
Success = false,
ErrorMessage = "系统错误"
};
}
}
/// <summary>
/// 获取微信授权的手机号
/// </summary>
public async Task<WechatMobileResult> GetMobileAsync(string code)
{
if (string.IsNullOrWhiteSpace(code))
{
_logger.LogWarning("GetMobileAsync called with empty code");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "授权code不能为空"
};
}
try
{
// 1. 先获取 access_token
var accessToken = await GetAccessTokenAsync();
if (string.IsNullOrEmpty(accessToken))
{
_logger.LogError("Failed to get access_token for phone number API");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "获取access_token失败"
};
}
// 2. 使用 access_token 作为 URL 参数code 放在请求体中
var url = $"{WechatGetPhoneNumberUrl}?access_token={accessToken}";
var requestBody = new { code = code };
var jsonContent = new StringContent(
JsonSerializer.Serialize(requestBody),
System.Text.Encoding.UTF8,
"application/json");
_logger.LogInformation("Calling WeChat API to get phone number with access_token");
var response = await _httpClient.PostAsync(url, jsonContent);
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
_logger.LogError("WeChat API returned error status {StatusCode}: {Content}", response.StatusCode, content);
return new WechatMobileResult
{
Success = false,
ErrorMessage = "微信API调用失败"
};
}
using var jsonDoc = JsonDocument.Parse(content);
var root = jsonDoc.RootElement;
// 检查是否有错误
if (root.TryGetProperty("errcode", out var errCode) && errCode.GetInt32() != 0)
{
var errMsg = root.TryGetProperty("errmsg", out var msg) ? msg.GetString() : "未知错误";
_logger.LogWarning("WeChat API returned error: {ErrorCode} - {ErrorMessage}", errCode.GetInt32(), errMsg);
return new WechatMobileResult
{
Success = false,
ErrorMessage = $"获取手机号失败: {errMsg}"
};
}
// 提取手机号
string? mobile = null;
if (root.TryGetProperty("phone_info", out var phoneInfo))
{
mobile = phoneInfo.TryGetProperty("phoneNumber", out var phoneNumber)
? phoneNumber.GetString()
: null;
}
if (string.IsNullOrEmpty(mobile))
{
_logger.LogError("WeChat API response missing phone number");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "微信返回数据异常"
};
}
_logger.LogInformation("Successfully retrieved phone number from WeChat API");
return new WechatMobileResult
{
Success = true,
Mobile = mobile
};
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP request error when calling WeChat API");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "网络连接失败"
};
}
catch (JsonException ex)
{
_logger.LogError(ex, "JSON parsing error when processing WeChat API response");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "响应数据格式错误"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error when calling WeChat API");
return new WechatMobileResult
{
Success = false,
ErrorMessage = "系统错误"
};
}
}
/// <summary>
/// 获取小程序接口调用凭证access_token
/// </summary>
/// <param name="appId">小程序AppId可选不传则使用数据库默认配置</param>
/// <returns>access_token失败返回null</returns>
public async Task<string?> GetAccessTokenAsync(string? appId = null)
{
try
{
// 从数据库获取配置
var wechatConfig = await GetWechatSettingFromDbAsync();
if (wechatConfig == null)
{
_logger.LogError("无法获取access_token未找到小程序配置请在后台管理系统中配置 miniprogram_setting");
return null;
}
// 确定使用哪个AppId和AppSecret
var targetAppId = appId ?? wechatConfig.AppId;
var targetAppSecret = await GetAppSecretByAppIdAsync(targetAppId);
if (string.IsNullOrEmpty(targetAppId) || string.IsNullOrEmpty(targetAppSecret))
{
_logger.LogError("无法获取access_tokenAppId或AppSecret为空");
return null;
}
// 尝试从Redis缓存获取
var cacheKey = $"{AccessTokenCacheKeyPrefix}{targetAppId}";
var cachedToken = await _redisService.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cachedToken))
{
_logger.LogDebug("从缓存获取access_token: AppId={AppId}", targetAppId);
return cachedToken;
}
// 调用微信API获取新的access_token
var url = $"{WechatGetAccessTokenUrl}?grant_type=client_credential&appid={targetAppId}&secret={targetAppSecret}";
_logger.LogInformation("调用微信API获取access_token: AppId={AppId}", targetAppId);
var response = await _httpClient.GetAsync(url);
var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
_logger.LogError("微信API返回错误状态 {StatusCode}: {Content}", response.StatusCode, content);
return null;
}
using var jsonDoc = JsonDocument.Parse(content);
var root = jsonDoc.RootElement;
// 检查是否有错误
if (root.TryGetProperty("errcode", out var errCode) && errCode.GetInt32() != 0)
{
var errMsg = root.TryGetProperty("errmsg", out var msg) ? msg.GetString() : "未知错误";
_logger.LogWarning("微信API返回错误: {ErrorCode} - {ErrorMessage}", errCode.GetInt32(), errMsg);
return null;
}
// 提取access_token和过期时间
var accessToken = root.TryGetProperty("access_token", out var tokenProp) ? tokenProp.GetString() : null;
var expiresIn = root.TryGetProperty("expires_in", out var expiresProp) ? expiresProp.GetInt32() : 7200;
if (string.IsNullOrEmpty(accessToken))
{
_logger.LogError("微信API响应中缺少access_token");
return null;
}
// 缓存access_token提前5分钟过期避免边界问题
var cacheExpiry = TimeSpan.FromSeconds(Math.Max(expiresIn - 300, 60));
await _redisService.SetStringAsync(cacheKey, accessToken, cacheExpiry);
_logger.LogInformation("成功获取access_token: AppId={AppId}, ExpiresIn={ExpiresIn}s", targetAppId, expiresIn);
return accessToken;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "获取access_token时HTTP请求错误");
return null;
}
catch (JsonException ex)
{
_logger.LogError(ex, "获取access_token时JSON解析错误");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取access_token时发生未知错误");
return null;
}
}
/// <summary>
/// 根据AppId获取对应的AppSecret异步版本从数据库读取
/// </summary>
private async Task<string> GetAppSecretByAppIdAsync(string appId)
{
// 从数据库获取配置
var wechatConfig = await GetWechatSettingFromDbAsync();
if (wechatConfig != null && wechatConfig.AppId == appId)
{
return wechatConfig.AppSecret;
}
// 从小程序配置列表中查找
var miniprogram = _wechatPaySettings.Miniprograms.FirstOrDefault(m => m.AppId == appId);
if (miniprogram != null)
{
return miniprogram.AppSecret;
}
// 如果都没找到返回数据库默认配置的AppSecret
_logger.LogWarning("未找到AppId {AppId} 的配置,使用数据库默认配置", appId);
return wechatConfig?.AppSecret ?? string.Empty;
}
/// <summary>
/// 创建支付订单(原生微信支付)
/// </summary>
public async Task<CreatePayResult> CreatePayOrderAsync(CreatePayRequest request)
{
try
{
_logger.LogInformation("[创建支付订单] 开始处理: UserId={UserId}, Price={Price}, Title={Title}, Attach={Attach}",
request.UserId, request.Price, request.Title, request.Attach);
// 如果金额为0直接返回成功免费订单
if (request.Price <= 0)
{
var freeOrderNo = GenerateOrderNo(request.Prefix, "MON", "YD", "MP0");
_logger.LogInformation("[创建支付订单] 免费订单,直接返回成功: OrderNo={OrderNo}", freeOrderNo);
return new CreatePayResult
{
Status = 1,
OrderNo = freeOrderNo,
Res = null
};
}
// 获取商户配置(优先从数据库读取)
var merchantConfig = await GetMerchantConfigAsync();
if (merchantConfig == null)
{
_logger.LogError("[创建支付订单] 未找到商户配置");
return new CreatePayResult
{
Status = 0,
Message = "支付配置错误",
OrderNo = string.Empty
};
}
// 生成订单号:前缀 + 商户前缀 + 小程序前缀 + 支付类型 + 时间戳 + 随机数
var orderNo = GenerateOrderNo(request.Prefix, merchantConfig.OrderPrefix, "YD", "MP0");
// 截取标题最多30个字符
var title = request.Title.Length > 30 ? request.Title.Substring(0, 30) : request.Title;
// 生成随机字符串
var nonceStr = GenerateNonceStr();
var callbackNonceStr = GenerateNonceStr();
// 生成回调通知URL
var notifyUrl = GenerateNotifyUrl(request.Attach, request.UserId, orderNo, callbackNonceStr);
// 获取客户端IP
var clientIp = "127.0.0.1"; // 实际应从请求中获取
// 构建统一下单参数
var unifiedOrderParams = new SortedDictionary<string, string>
{
{ "appid", merchantConfig.AppId },
{ "mch_id", merchantConfig.MchId },
{ "nonce_str", nonceStr },
{ "body", title },
{ "attach", request.Attach },
{ "out_trade_no", orderNo },
{ "notify_url", notifyUrl },
{ "total_fee", ((int)(request.Price * 100)).ToString() }, // 转换为分
{ "spbill_create_ip", clientIp },
{ "trade_type", "JSAPI" },
{ "openid", request.OpenId }
};
// 生成签名
var sign = MakeSign(unifiedOrderParams, merchantConfig.Key);
unifiedOrderParams.Add("sign", sign);
// 转换为XML
var xmlData = DictToXml(unifiedOrderParams);
_logger.LogInformation("[创建支付订单] 调用微信统一下单API: OrderNo={OrderNo}", orderNo);
// 调用微信统一下单API
var content = new StringContent(xmlData, System.Text.Encoding.UTF8, "application/xml");
var response = await _httpClient.PostAsync(_wechatPaySettings.UnifiedOrderUrl, content);
var responseContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation("[创建支付订单] 微信统一下单响应: {Response}", responseContent);
// 解析响应
var result = XmlToDict(responseContent);
if (result.TryGetValue("return_code", out var returnCode) && returnCode == "SUCCESS" &&
result.TryGetValue("result_code", out var resultCode) && resultCode == "SUCCESS")
{
// 获取 prepay_id
if (!result.TryGetValue("prepay_id", out var prepayId))
{
_logger.LogError("[创建支付订单] 微信返回数据缺少 prepay_id");
return new CreatePayResult
{
Status = 0,
Message = "微信返回数据异常",
OrderNo = string.Empty
};
}
// 保存订单通知记录
var orderNotify = new OrderNotify
{
OrderNo = orderNo,
NotifyUrl = notifyUrl,
NonceStr = callbackNonceStr,
PayTime = DateTime.UtcNow,
PayAmount = request.Price,
Status = 0, // 待支付
RetryCount = 0,
Attach = request.Attach,
OpenId = request.OpenId,
Extend = JsonSerializer.Serialize(new { orderType = request.Attach, title = title }),
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_dbContext.OrderNotifies.Add(orderNotify);
await _dbContext.SaveChangesAsync();
// 生成前端支付参数
var timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var payNonceStr = GenerateNonceStr();
var payParams = new SortedDictionary<string, string>
{
{ "appId", merchantConfig.AppId },
{ "timeStamp", timeStamp },
{ "nonceStr", payNonceStr },
{ "package", $"prepay_id={prepayId}" },
{ "signType", "MD5" }
};
var paySign = MakeSign(payParams, merchantConfig.Key);
_logger.LogInformation("[创建支付订单] 订单创建成功: OrderNo={OrderNo}, PrepayId={PrepayId}", orderNo, prepayId);
return new CreatePayResult
{
Status = 1,
OrderNo = orderNo,
Res = new NativePayParams
{
AppId = merchantConfig.AppId,
TimeStamp = timeStamp,
NonceStr = payNonceStr,
Package = $"prepay_id={prepayId}",
SignType = "MD5",
PaySign = paySign
}
};
}
else
{
// 解析错误信息
var errorMsg = "微信支付接口返回异常";
if (result.TryGetValue("return_msg", out var returnMsg))
{
errorMsg = returnMsg;
}
else if (result.TryGetValue("err_code_des", out var errCodeDes))
{
errorMsg = errCodeDes;
}
_logger.LogError("[创建支付订单] 微信统一下单失败: {Error}, Response: {Response}", errorMsg, responseContent);
return new CreatePayResult
{
Status = 0,
Message = errorMsg,
OrderNo = string.Empty
};
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[创建支付订单] 创建失败: UserId={UserId}", request.UserId);
return new CreatePayResult
{
Status = 0,
Message = "创建支付订单失败",
OrderNo = string.Empty
};
}
}
/// <summary>
/// 从数据库获取微信小程序配置
/// 仅从 miniprogram_setting 读取,无配置则返回 null
/// </summary>
private async Task<WechatSettings?> GetWechatSettingFromDbAsync()
{
try
{
// 从 miniprogram_setting 读取小程序配置
var miniprogramConfig = await _dbContext.Configs
.Where(c => c.ConfigKey == "miniprogram_setting")
.Select(c => c.ConfigValue)
.FirstOrDefaultAsync();
if (string.IsNullOrEmpty(miniprogramConfig))
{
_logger.LogWarning("[微信配置] 数据库中未找到 miniprogram_setting 配置");
return null;
}
var config = JsonSerializer.Deserialize<JsonElement>(miniprogramConfig);
if (!config.TryGetProperty("miniprograms", out var miniprograms) || miniprograms.ValueKind != JsonValueKind.Array)
{
_logger.LogWarning("[微信配置] miniprogram_setting 配置格式错误,缺少 miniprograms 数组");
return null;
}
// 查找默认小程序配置 (is_default = 1)
foreach (var mp in miniprograms.EnumerateArray())
{
var isDefault = mp.TryGetProperty("is_default", out var isDefaultProp) &&
(isDefaultProp.ValueKind == JsonValueKind.Number ? isDefaultProp.GetInt32() == 1 : isDefaultProp.GetString() == "1");
if (isDefault)
{
var appId = mp.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var appSecret = mp.TryGetProperty("appsecret", out var appSecretProp) ? appSecretProp.GetString() : null;
if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret))
{
_logger.LogDebug("[微信配置] 从 miniprogram_setting 读取默认小程序配置: AppId={AppId}",
appId.Length > 8 ? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}" : appId);
return new WechatSettings
{
AppId = appId,
AppSecret = appSecret
};
}
}
}
// 如果没有默认配置,使用第一个小程序配置
var firstMp = miniprograms.EnumerateArray().FirstOrDefault();
if (firstMp.ValueKind == JsonValueKind.Object)
{
var appId = firstMp.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var appSecret = firstMp.TryGetProperty("appsecret", out var appSecretProp) ? appSecretProp.GetString() : null;
if (!string.IsNullOrEmpty(appId) && !string.IsNullOrEmpty(appSecret))
{
_logger.LogDebug("[微信配置] 从 miniprogram_setting 读取第一个小程序配置: AppId={AppId}",
appId.Length > 8 ? $"{appId.Substring(0, 4)}****{appId.Substring(appId.Length - 4)}" : appId);
return new WechatSettings
{
AppId = appId,
AppSecret = appSecret
};
}
}
_logger.LogWarning("[微信配置] miniprogram_setting 中未找到有效的小程序配置");
return null;
}
catch (Exception ex)
{
_logger.LogError(ex, "[微信配置] 从数据库读取配置失败");
return null;
}
}
/// <summary>
/// 获取商户配置(从数据库读取)
/// </summary>
private async Task<WechatPayMerchantConfig?> GetMerchantConfigAsync()
{
try
{
// 从数据库读取 weixinpay 配置
var weixinpayConfig = await _dbContext.Configs
.Where(c => c.ConfigKey == "weixinpay")
.Select(c => c.ConfigValue)
.FirstOrDefaultAsync();
if (string.IsNullOrEmpty(weixinpayConfig))
{
_logger.LogError("[微信支付] 数据库中未找到 weixinpay 配置");
return null;
}
var config = JsonSerializer.Deserialize<JsonElement>(weixinpayConfig);
var mchId = config.TryGetProperty("mch_id", out var mchIdProp) ? mchIdProp.GetString() : null;
var appId = config.TryGetProperty("appid", out var appIdProp) ? appIdProp.GetString() : null;
var key = config.TryGetProperty("keys", out var keysProp) ? keysProp.GetString() : null;
if (string.IsNullOrEmpty(mchId) || string.IsNullOrEmpty(key))
{
_logger.LogError("[微信支付] weixinpay 配置不完整,缺少 mch_id 或 keys");
return null;
}
// 如果 weixinpay 中没有 appid从 miniprogram_setting 获取
if (string.IsNullOrEmpty(appId))
{
var wechatConfig = await GetWechatSettingFromDbAsync();
appId = wechatConfig?.AppId;
}
if (string.IsNullOrEmpty(appId))
{
_logger.LogError("[微信支付] 未找到有效的 AppId 配置");
return null;
}
_logger.LogInformation("[微信支付] 从数据库读取配置: MchId={MchId}, AppId={AppId}", mchId, appId);
return new WechatPayMerchantConfig
{
Name = "数据库配置",
MchId = mchId,
AppId = appId,
Key = key,
OrderPrefix = "MYH",
Weight = 1
};
}
catch (Exception ex)
{
_logger.LogError(ex, "[微信支付] 从数据库读取配置失败");
return null;
}
}
/// <summary>
/// 生成随机字符串
/// </summary>
private static string GenerateNonceStr(int length = 32)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
/// <summary>
/// 生成回调通知URL
/// </summary>
private string GenerateNotifyUrl(string orderType, int userId, string orderNo, string nonceStr)
{
var baseUrl = _wechatPaySettings.NotifyBaseUrl.TrimEnd('/');
return $"{baseUrl}/api/pay/notify?payment_type=wxpay&order_type={orderType}&user_id={userId}&order_no={orderNo}&nonce_str={nonceStr}";
}
/// <summary>
/// 生成签名MD5
/// </summary>
private static string MakeSign(SortedDictionary<string, string> parameters, string key)
{
var sb = new System.Text.StringBuilder();
foreach (var kvp in parameters)
{
if (!string.IsNullOrEmpty(kvp.Value) && kvp.Key != "sign")
{
sb.Append($"{kvp.Key}={kvp.Value}&");
}
}
sb.Append($"key={key}");
using var md5 = System.Security.Cryptography.MD5.Create();
var inputBytes = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToUpper();
}
/// <summary>
/// 字典转XML
/// </summary>
private static string DictToXml(SortedDictionary<string, string> dict)
{
var sb = new System.Text.StringBuilder();
sb.Append("<xml>");
foreach (var kvp in dict)
{
sb.Append($"<{kvp.Key}><![CDATA[{kvp.Value}]]></{kvp.Key}>");
}
sb.Append("</xml>");
return sb.ToString();
}
/// <summary>
/// XML转字典
/// </summary>
private static Dictionary<string, string> XmlToDict(string xml)
{
var dict = new Dictionary<string, string>();
try
{
var doc = System.Xml.Linq.XDocument.Parse(xml);
if (doc.Root != null)
{
foreach (var element in doc.Root.Elements())
{
dict[element.Name.LocalName] = element.Value;
}
}
}
catch
{
// 解析失败返回空字典
}
return dict;
}
/// <summary>
/// 生成订单号
/// 格式:前缀(3位) + 商户前缀(3位) + 项目前缀(2位) + 支付类型(3位) + 时间戳 + 随机数
/// </summary>
private string GenerateOrderNo(string prefix, string merchantPrefix, string projectPrefix, string payType)
{
var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss");
var random = new Random().Next(1000, 9999);
return $"{prefix}{merchantPrefix}{projectPrefix}{payType}{timestamp}{random}";
}
}