334 lines
14 KiB
C#
334 lines
14 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using MiAssessment.Core.Interfaces;
|
|
using MiAssessment.Model.Data;
|
|
using MiAssessment.Model.Models.Payment;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace MiAssessment.Core.Services;
|
|
|
|
/// <summary>
|
|
/// 微信支付配置服务实现
|
|
/// 从数据库读取配置,支持多商户、多小程序
|
|
/// </summary>
|
|
public class WechatPayConfigService : IWechatPayConfigService
|
|
{
|
|
private readonly AdminConfigReadDbContext _adminConfigDbContext;
|
|
private readonly IRedisService _redisService;
|
|
private readonly ILogger<WechatPayConfigService> _logger;
|
|
private readonly Random _random = new();
|
|
|
|
private const string MERCHANTS_CACHE_KEY = "wechatpay:merchants";
|
|
private const string MINIPROGRAMS_CACHE_KEY = "wechatpay:miniprograms";
|
|
private static readonly TimeSpan CACHE_DURATION = TimeSpan.FromMinutes(5);
|
|
|
|
public WechatPayConfigService(
|
|
AdminConfigReadDbContext adminConfigDbContext,
|
|
IRedisService redisService,
|
|
ILogger<WechatPayConfigService> logger)
|
|
{
|
|
_adminConfigDbContext = adminConfigDbContext;
|
|
_redisService = redisService;
|
|
_logger = logger;
|
|
}
|
|
|
|
private async Task<List<WechatPayMerchantConfig>> LoadMerchantsAsync()
|
|
{
|
|
var cachedJson = await _redisService.GetStringAsync(MERCHANTS_CACHE_KEY);
|
|
if (!string.IsNullOrEmpty(cachedJson))
|
|
{
|
|
try
|
|
{
|
|
var cached = JsonSerializer.Deserialize<List<WechatPayMerchantConfig>>(cachedJson);
|
|
if (cached != null && cached.Count > 0) return cached;
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
var merchants = new List<WechatPayMerchantConfig>();
|
|
try
|
|
{
|
|
var settingConfig = await _adminConfigDbContext.AdminConfigs
|
|
.Where(c => c.ConfigKey == "weixinpay_setting")
|
|
.Select(c => c.ConfigValue)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (!string.IsNullOrEmpty(settingConfig))
|
|
{
|
|
var setting = JsonSerializer.Deserialize<DbWeixinPaySetting>(settingConfig, JsonOptions);
|
|
if (setting?.Merchants != null)
|
|
{
|
|
foreach (var m in setting.Merchants.Where(x => x.IsEnabled == "1"))
|
|
{
|
|
merchants.Add(new WechatPayMerchantConfig
|
|
{
|
|
Name = m.Name ?? "",
|
|
MchId = m.MchId ?? "",
|
|
Key = m.ApiKey ?? "",
|
|
OrderPrefix = m.OrderPrefix ?? "",
|
|
PayVersion = m.PayVersion ?? "V2",
|
|
ApiV3Key = m.ApiV3Key,
|
|
CertSerialNo = m.CertSerialNo,
|
|
PrivateKeyPath = m.PrivateKeyPath,
|
|
PrivateKeyContent = m.PrivateKeyContent,
|
|
WechatPublicKeyId = m.WechatPublicKeyId,
|
|
WechatPublicKeyPath = m.WechatPublicKeyPath,
|
|
WechatPublicKeyContent = m.WechatPublicKeyContent,
|
|
NotifyUrl = m.NotifyUrl ?? ""
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
var weixinpayConfig = await _adminConfigDbContext.AdminConfigs
|
|
.Where(c => c.ConfigKey == "weixinpay")
|
|
.Select(c => c.ConfigValue)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (!string.IsNullOrEmpty(weixinpayConfig))
|
|
{
|
|
var config = JsonSerializer.Deserialize<DbWeixinPayConfig>(weixinpayConfig, JsonOptions);
|
|
if (config != null && !string.IsNullOrEmpty(config.MchId) && !merchants.Any(m => m.MchId == config.MchId))
|
|
{
|
|
merchants.Add(new WechatPayMerchantConfig
|
|
{
|
|
Name = "默认商户",
|
|
MchId = config.MchId,
|
|
AppId = config.AppId ?? "",
|
|
Key = config.Keys ?? "",
|
|
OrderPrefix = "MYH",
|
|
PayVersion = "V2",
|
|
NotifyUrl = ""
|
|
});
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation("从数据库加载了 {Count} 个商户配置", merchants.Count);
|
|
if (merchants.Count > 0)
|
|
{
|
|
await _redisService.SetStringAsync(MERCHANTS_CACHE_KEY, JsonSerializer.Serialize(merchants), CACHE_DURATION);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "加载商户配置失败");
|
|
}
|
|
|
|
return merchants;
|
|
}
|
|
|
|
private async Task<List<MiniprogramConfig>> LoadMiniprogramsAsync()
|
|
{
|
|
var cachedJson = await _redisService.GetStringAsync(MINIPROGRAMS_CACHE_KEY);
|
|
if (!string.IsNullOrEmpty(cachedJson))
|
|
{
|
|
try
|
|
{
|
|
var cached = JsonSerializer.Deserialize<List<MiniprogramConfig>>(cachedJson);
|
|
if (cached != null && cached.Count > 0) return cached;
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
var miniprograms = new List<MiniprogramConfig>();
|
|
try
|
|
{
|
|
var settingConfig = await _adminConfigDbContext.AdminConfigs
|
|
.Where(c => c.ConfigKey == "miniprogram_setting")
|
|
.Select(c => c.ConfigValue)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (!string.IsNullOrEmpty(settingConfig))
|
|
{
|
|
var setting = JsonSerializer.Deserialize<DbMiniprogramSetting>(settingConfig, JsonOptions);
|
|
if (setting?.Miniprograms != null)
|
|
{
|
|
foreach (var m in setting.Miniprograms)
|
|
{
|
|
miniprograms.Add(new MiniprogramConfig
|
|
{
|
|
Name = m.Name ?? "",
|
|
AppId = m.AppId ?? "",
|
|
AppSecret = m.AppSecret ?? "",
|
|
OrderPrefix = m.OrderPrefix ?? "",
|
|
IsDefault = m.IsDefault == 1,
|
|
Merchants = m.Merchants ?? new List<string>()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation("从数据库加载了 {Count} 个小程序配置", miniprograms.Count);
|
|
if (miniprograms.Count > 0)
|
|
{
|
|
await _redisService.SetStringAsync(MINIPROGRAMS_CACHE_KEY, JsonSerializer.Serialize(miniprograms), CACHE_DURATION);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "加载小程序配置失败");
|
|
}
|
|
|
|
return miniprograms;
|
|
}
|
|
|
|
public WechatPayMerchantConfig GetDefaultConfig()
|
|
{
|
|
var merchants = LoadMerchantsAsync().GetAwaiter().GetResult();
|
|
return merchants.FirstOrDefault() ?? new WechatPayMerchantConfig();
|
|
}
|
|
|
|
public WechatPayMerchantConfig GetMerchantByOrderNo(string orderNo)
|
|
{
|
|
var merchants = LoadMerchantsAsync().GetAwaiter().GetResult();
|
|
var miniprograms = LoadMiniprogramsAsync().GetAwaiter().GetResult();
|
|
|
|
if (string.IsNullOrEmpty(orderNo) || merchants.Count == 0)
|
|
return GetDefaultConfig();
|
|
|
|
var miniprogram = miniprograms.FirstOrDefault(m => m.IsDefault) ?? miniprograms.FirstOrDefault();
|
|
WechatPayMerchantConfig? selectedMerchant = null;
|
|
string appId = miniprogram?.AppId ?? "";
|
|
|
|
if (miniprogram != null && miniprogram.Merchants.Count > 0)
|
|
{
|
|
var associatedMerchants = merchants.Where(m => miniprogram.Merchants.Contains(m.MchId)).ToList();
|
|
if (associatedMerchants.Count > 0)
|
|
{
|
|
selectedMerchant = GetRandomMerchant(associatedMerchants);
|
|
_logger.LogDebug("从小程序关联商户中选择: MchId={MchId}", selectedMerchant?.MchId);
|
|
}
|
|
}
|
|
|
|
selectedMerchant ??= merchants.FirstOrDefault();
|
|
if (selectedMerchant == null) return new WechatPayMerchantConfig();
|
|
|
|
return new WechatPayMerchantConfig
|
|
{
|
|
Name = selectedMerchant.Name,
|
|
MchId = selectedMerchant.MchId,
|
|
AppId = appId,
|
|
Key = selectedMerchant.Key,
|
|
OrderPrefix = selectedMerchant.OrderPrefix,
|
|
Weight = selectedMerchant.Weight,
|
|
NotifyUrl = selectedMerchant.NotifyUrl,
|
|
PayVersion = selectedMerchant.PayVersion,
|
|
ApiV3Key = selectedMerchant.ApiV3Key,
|
|
CertSerialNo = selectedMerchant.CertSerialNo,
|
|
PrivateKeyPath = selectedMerchant.PrivateKeyPath,
|
|
PrivateKeyContent = selectedMerchant.PrivateKeyContent,
|
|
WechatPublicKeyId = selectedMerchant.WechatPublicKeyId,
|
|
WechatPublicKeyPath = selectedMerchant.WechatPublicKeyPath,
|
|
WechatPublicKeyContent = selectedMerchant.WechatPublicKeyContent
|
|
};
|
|
}
|
|
|
|
public WechatPayMerchantConfig? GetMerchantByPrefix(string merchantPrefix)
|
|
{
|
|
if (string.IsNullOrEmpty(merchantPrefix)) return null;
|
|
var merchants = LoadMerchantsAsync().GetAwaiter().GetResult();
|
|
return merchants.FirstOrDefault(m => !string.IsNullOrEmpty(m.OrderPrefix) && m.OrderPrefix.Equals(merchantPrefix, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
public MiniprogramConfig? GetMiniprogramByPrefix(string miniprogramPrefix)
|
|
{
|
|
if (string.IsNullOrEmpty(miniprogramPrefix)) return null;
|
|
var miniprograms = LoadMiniprogramsAsync().GetAwaiter().GetResult();
|
|
return miniprograms.FirstOrDefault(m => !string.IsNullOrEmpty(m.OrderPrefix) && m.OrderPrefix.Equals(miniprogramPrefix, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
public MiniprogramConfig? GetMiniprogramByDomain(string domain) => GetDefaultMiniprogram();
|
|
|
|
public MiniprogramConfig? GetDefaultMiniprogram()
|
|
{
|
|
var miniprograms = LoadMiniprogramsAsync().GetAwaiter().GetResult();
|
|
return miniprograms.FirstOrDefault(m => m.IsDefault) ?? miniprograms.FirstOrDefault();
|
|
}
|
|
|
|
public OrderPrefixInfo? ExtractOrderPrefix(string orderNo)
|
|
{
|
|
if (string.IsNullOrEmpty(orderNo) || (!orderNo.StartsWith("MH_") && !orderNo.StartsWith("FH_")))
|
|
return null;
|
|
if (orderNo.Length < 6) return null;
|
|
return new OrderPrefixInfo
|
|
{
|
|
MerchantPrefix = orderNo.Substring(3, 3),
|
|
MiniprogramPrefix = orderNo.Length >= 8 ? orderNo.Substring(6, 2) : null
|
|
};
|
|
}
|
|
|
|
public WechatPayMerchantConfig? GetRandomMerchant(IEnumerable<WechatPayMerchantConfig> merchants)
|
|
{
|
|
var list = merchants.ToList();
|
|
if (list.Count == 0) return null;
|
|
if (list.Count == 1) return list[0];
|
|
|
|
var totalWeight = list.Sum(m => m.Weight > 0 ? m.Weight : 1);
|
|
var randomWeight = _random.Next(1, totalWeight + 1);
|
|
var currentWeight = 0;
|
|
|
|
foreach (var merchant in list)
|
|
{
|
|
currentWeight += merchant.Weight > 0 ? merchant.Weight : 1;
|
|
if (randomWeight <= currentWeight) return merchant;
|
|
}
|
|
return list[0];
|
|
}
|
|
|
|
public (WechatPayMerchantConfig Merchant, string AppId) GetWxPayConfig()
|
|
{
|
|
var merchant = GetMerchantByOrderNo("");
|
|
return (merchant, merchant.AppId);
|
|
}
|
|
|
|
public (WechatPayMerchantConfig? Merchant, string AppId) GetFixedWxPayConfig(string orderPrefix)
|
|
{
|
|
var merchant = GetMerchantByPrefix(orderPrefix);
|
|
if (merchant == null)
|
|
{
|
|
var config = GetWxPayConfig();
|
|
return (config.Merchant, config.AppId);
|
|
}
|
|
var miniprogram = GetDefaultMiniprogram();
|
|
return (merchant, miniprogram?.AppId ?? merchant.AppId);
|
|
}
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
|
|
|
private class DbWeixinPaySetting { [JsonPropertyName("merchants")] public List<DbMerchantConfig>? Merchants { get; set; } }
|
|
private class DbMerchantConfig
|
|
{
|
|
[JsonPropertyName("name")] public string? Name { get; set; }
|
|
[JsonPropertyName("mch_id")] public string? MchId { get; set; }
|
|
[JsonPropertyName("order_prefix")] public string? OrderPrefix { get; set; }
|
|
[JsonPropertyName("api_key")] public string? ApiKey { get; set; }
|
|
[JsonPropertyName("is_enabled")] public string? IsEnabled { get; set; }
|
|
[JsonPropertyName("pay_version")] public string? PayVersion { get; set; }
|
|
[JsonPropertyName("api_v3_key")] public string? ApiV3Key { get; set; }
|
|
[JsonPropertyName("cert_serial_no")] public string? CertSerialNo { get; set; }
|
|
[JsonPropertyName("private_key_path")] public string? PrivateKeyPath { get; set; }
|
|
[JsonPropertyName("private_key_content")] public string? PrivateKeyContent { get; set; }
|
|
[JsonPropertyName("wechat_public_key_id")] public string? WechatPublicKeyId { get; set; }
|
|
[JsonPropertyName("wechat_public_key_path")] public string? WechatPublicKeyPath { get; set; }
|
|
[JsonPropertyName("wechat_public_key_content")] public string? WechatPublicKeyContent { get; set; }
|
|
[JsonPropertyName("notify_url")] public string? NotifyUrl { get; set; }
|
|
}
|
|
private class DbWeixinPayConfig
|
|
{
|
|
[JsonPropertyName("appid")] public string? AppId { get; set; }
|
|
[JsonPropertyName("mch_id")] public string? MchId { get; set; }
|
|
[JsonPropertyName("keys")] public string? Keys { get; set; }
|
|
}
|
|
private class DbMiniprogramSetting { [JsonPropertyName("miniprograms")] public List<DbMiniprogramConfig>? Miniprograms { get; set; } }
|
|
private class DbMiniprogramConfig
|
|
{
|
|
[JsonPropertyName("name")] public string? Name { get; set; }
|
|
[JsonPropertyName("appid")] public string? AppId { get; set; }
|
|
[JsonPropertyName("appsecret")] public string? AppSecret { get; set; }
|
|
[JsonPropertyName("order_prefix")] public string? OrderPrefix { get; set; }
|
|
[JsonPropertyName("is_default")] public int IsDefault { get; set; }
|
|
[JsonPropertyName("merchants")] public List<string>? Merchants { get; set; }
|
|
}
|
|
}
|