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; /// /// 微信支付配置服务实现 /// 从数据库读取配置,支持多商户、多小程序 /// public class WechatPayConfigService : IWechatPayConfigService { private readonly AdminConfigReadDbContext _adminConfigDbContext; private readonly IRedisService _redisService; private readonly ILogger _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 logger) { _adminConfigDbContext = adminConfigDbContext; _redisService = redisService; _logger = logger; } private async Task> LoadMerchantsAsync() { var cachedJson = await _redisService.GetStringAsync(MERCHANTS_CACHE_KEY); if (!string.IsNullOrEmpty(cachedJson)) { try { var cached = JsonSerializer.Deserialize>(cachedJson); if (cached != null && cached.Count > 0) return cached; } catch { } } var merchants = new List(); 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(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(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> LoadMiniprogramsAsync() { var cachedJson = await _redisService.GetStringAsync(MINIPROGRAMS_CACHE_KEY); if (!string.IsNullOrEmpty(cachedJson)) { try { var cached = JsonSerializer.Deserialize>(cachedJson); if (cached != null && cached.Count > 0) return cached; } catch { } } var miniprograms = new List(); 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(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() }); } } } _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 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? 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? 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? Merchants { get; set; } } }