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;
///
/// 微信服务实现
///
public class WechatService : IWechatService
{
private readonly HttpClient _httpClient;
private readonly ILogger _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 logger,
IOptions 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));
}
///
/// 获取微信openid和unionid
///
public async Task 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 = "系统错误"
};
}
}
///
/// 获取微信授权的手机号
///
public async Task 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 = "系统错误"
};
}
}
///
/// 获取小程序接口调用凭证(access_token)
///
/// 小程序AppId(可选,不传则使用数据库默认配置)
/// access_token,失败返回null
public async Task 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_token:AppId或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;
}
}
///
/// 根据AppId获取对应的AppSecret(异步版本,从数据库读取)
///
private async Task 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;
}
///
/// 创建支付订单(原生微信支付)
///
public async Task 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
{
{ "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
{
{ "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
};
}
}
///
/// 从数据库获取微信小程序配置
/// 仅从 miniprogram_setting 读取,无配置则返回 null
///
private async Task 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(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;
}
}
///
/// 获取商户配置(从数据库读取)
///
private async Task 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(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;
}
}
///
/// 生成随机字符串
///
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());
}
///
/// 生成回调通知URL
///
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}";
}
///
/// 生成签名(MD5)
///
private static string MakeSign(SortedDictionary 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();
}
///
/// 字典转XML
///
private static string DictToXml(SortedDictionary dict)
{
var sb = new System.Text.StringBuilder();
sb.Append("");
foreach (var kvp in dict)
{
sb.Append($"<{kvp.Key}>{kvp.Key}>");
}
sb.Append("");
return sb.ToString();
}
///
/// XML转字典
///
private static Dictionary XmlToDict(string xml)
{
var dict = new Dictionary();
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;
}
///
/// 生成订单号
/// 格式:前缀(3位) + 商户前缀(3位) + 项目前缀(2位) + 支付类型(3位) + 时间戳 + 随机数
///
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}";
}
}