32
This commit is contained in:
parent
885f7f29e3
commit
53a61f6298
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
// 测试环境配置 - .NET 10 后端
|
||||
const testing = {
|
||||
baseUrl: 'https://app.zpc-xy.com/honey/api',
|
||||
// baseUrl: 'http://192.168.1.24:5238',
|
||||
// baseUrl: 'https://app.zpc-xy.com/honey/api',
|
||||
baseUrl: 'http://192.168.1.24:5238',
|
||||
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
|
||||
loginPage: '',
|
||||
wxAppId: ''
|
||||
|
|
|
|||
|
|
@ -110,6 +110,8 @@ export interface WeixinPayMerchant {
|
|||
cert_path?: string
|
||||
/** 是否启用 */
|
||||
is_enabled?: string
|
||||
/** 支付回调地址 */
|
||||
notify_url?: string
|
||||
|
||||
// ===== V3 新增字段 =====
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,16 @@
|
|||
<div class="form-tip">V3版本使用更安全的RSA-SHA256签名和AES-GCM加密</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="回调地址" prop="notify_url">
|
||||
<el-input
|
||||
v-model="merchant.notify_url"
|
||||
placeholder="例如: https://api.example.com/api/notify/order_notify"
|
||||
@input="handleChange"
|
||||
/>
|
||||
<div class="form-tip">支付成功后微信回调通知的地址,留空使用默认值</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- V2 配置项 -->
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ const createDefaultMerchant = (): WeixinPayMerchant => ({
|
|||
api_key: '',
|
||||
cert_path: '',
|
||||
is_enabled: '1',
|
||||
notify_url: '',
|
||||
// V3 字段默认值
|
||||
pay_version: PayVersion.V2,
|
||||
api_v3_key: '',
|
||||
|
|
@ -111,6 +112,7 @@ const loadData = async () => {
|
|||
api_key: m.api_key || '',
|
||||
cert_path: m.cert_path || '',
|
||||
is_enabled: m.is_enabled || '1',
|
||||
notify_url: m.notify_url || '',
|
||||
// V3 字段回显
|
||||
pay_version: m.pay_version || PayVersion.V2,
|
||||
api_v3_key: m.api_v3_key || '',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using HoneyBox.Core.Interfaces;
|
||||
using HoneyBox.Model.Models.Payment;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace HoneyBox.Api.Controllers;
|
||||
|
|
@ -23,60 +24,63 @@ public class NotifyController : ControllerBase
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付回调接口
|
||||
/// 微信支付回调接口(支持 V2 XML 和 V3 JSON 格式)
|
||||
/// POST /api/notify/order_notify
|
||||
/// 接收微信支付结果通知,处理订单状态更新
|
||||
/// Requirements: 2.1-2.9
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 微信支付回调流程:
|
||||
/// 1. 接收微信发送的XML格式回调数据
|
||||
/// 2. 验证签名确保数据安全
|
||||
/// 3. 根据订单类型(attach字段)路由到对应处理方法
|
||||
/// 4. 更新订单状态、扣减用户资产、触发抽奖等
|
||||
/// 5. 返回XML格式响应给微信
|
||||
///
|
||||
/// 支持的订单类型(attach值):
|
||||
/// - user_recharge: 余额充值
|
||||
/// - order_yfs: 一番赏订单
|
||||
/// - order_lts: 擂台赏订单
|
||||
/// - order_zzs: 转转赏订单
|
||||
/// - order_flw: 福利屋订单
|
||||
/// - order_scs: 商城赏订单
|
||||
/// - order_wxs: 无限赏订单
|
||||
/// - order_fbs: 翻倍赏订单
|
||||
/// - order_ckj: 抽卡机订单
|
||||
/// - order_list_send: 发货运费
|
||||
/// </remarks>
|
||||
/// <returns>XML格式响应</returns>
|
||||
[HttpPost("order_notify")]
|
||||
[Consumes("application/xml", "text/xml")]
|
||||
[Produces("application/xml")]
|
||||
public async Task<IActionResult> OrderNotify()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取请求体中的XML数据
|
||||
// 读取请求体
|
||||
using var reader = new StreamReader(Request.Body);
|
||||
var xmlData = await reader.ReadToEndAsync();
|
||||
var notifyBody = await reader.ReadToEndAsync();
|
||||
|
||||
_logger.LogInformation("收到微信支付回调请求,数据长度: {Length}", xmlData?.Length ?? 0);
|
||||
_logger.LogInformation("收到微信支付回调请求,数据长度: {Length}, ContentType: {ContentType}",
|
||||
notifyBody?.Length ?? 0, Request.ContentType);
|
||||
|
||||
// 调用服务处理回调
|
||||
var result = await _paymentNotifyService.HandleWechatNotifyAsync(xmlData ?? string.Empty);
|
||||
// 提取 V3 回调请求头(如果存在)
|
||||
WechatPayNotifyHeaders? headers = null;
|
||||
if (Request.Headers.TryGetValue("Wechatpay-Timestamp", out var timestamp) &&
|
||||
Request.Headers.TryGetValue("Wechatpay-Nonce", out var nonce) &&
|
||||
Request.Headers.TryGetValue("Wechatpay-Signature", out var signature) &&
|
||||
Request.Headers.TryGetValue("Wechatpay-Serial", out var serial))
|
||||
{
|
||||
headers = new WechatPayNotifyHeaders
|
||||
{
|
||||
Timestamp = timestamp.ToString(),
|
||||
Nonce = nonce.ToString(),
|
||||
Signature = signature.ToString(),
|
||||
Serial = serial.ToString()
|
||||
};
|
||||
_logger.LogDebug("检测到 V3 回调请求头: Timestamp={Timestamp}, Serial={Serial}",
|
||||
headers.Timestamp, headers.Serial);
|
||||
}
|
||||
|
||||
// 调用服务处理回调(自动识别 V2/V3 格式)
|
||||
var result = await _paymentNotifyService.HandleWechatNotifyAsync(notifyBody ?? string.Empty, headers);
|
||||
|
||||
_logger.LogInformation("微信支付回调处理完成: Success={Success}, Message={Message}",
|
||||
result.Success, result.Message);
|
||||
|
||||
// 返回XML响应给微信
|
||||
return Content(result.XmlResponse, "application/xml");
|
||||
// 根据回调版本返回对应格式的响应
|
||||
if (!string.IsNullOrEmpty(result.JsonResponse))
|
||||
{
|
||||
// V3 返回 JSON
|
||||
return Content(result.JsonResponse, "application/json");
|
||||
}
|
||||
else
|
||||
{
|
||||
// V2 返回 XML
|
||||
return Content(result.XmlResponse ?? "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>", "application/xml");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "处理微信支付回调异常");
|
||||
|
||||
// 即使发生异常,也返回成功响应,避免微信重复通知
|
||||
// 后续通过其他机制(如定时任务)处理失败的订单
|
||||
// 返回成功响应,避免微信重复通知
|
||||
var successResponse = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
|
||||
return Content(successResponse, "application/xml");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>..\..</DockerfileContext>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||
|
|
@ -22,10 +22,27 @@
|
|||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\HoneyBox.Model\HoneyBox.Model.csproj" />
|
||||
<ProjectReference Include="..\HoneyBox.Core\HoneyBox.Core.csproj" />
|
||||
<ProjectReference Include="..\HoneyBox.Infrastructure\HoneyBox.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="cert\apiclient_cert.p12">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="cert\apiclient_cert.pem">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="cert\apiclient_key.pem">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="cert\pub_key.pem">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
25
server/HoneyBox/src/HoneyBox.Api/cert/apiclient_cert.pem
Normal file
25
server/HoneyBox/src/HoneyBox.Api/cert/apiclient_cert.pem
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEPTCCAyWgAwIBAgIUcxO5U5v6gITLJb2T8GKiVbfA+AswDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
|
||||
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
|
||||
Q0EwHhcNMjYwMTI1MDg1NjAxWhcNMzEwMTI0MDg1NjAxWjCBljETMBEGA1UEAwwK
|
||||
MTczODcyNTgwMTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMUIwQAYDVQQL
|
||||
DDnmoZPlj7Dljr/lk4jlsLznlLXlrZDllYbliqHlt6XkvZzlrqTvvIjkuKrkvZPl
|
||||
t6XllYbmiLfvvIkxCzAJBgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANm51GTtTzcs/3m4WuCTppqv+QGk
|
||||
fYXaMzQakc1ZpG1gdmOjEYTZmzUNz6vnbrCbp+T5mow45o/c6/x2ChhZvQXj/ud+
|
||||
RGPKpySuT1hdQoq+l6OfNbS/u35iDGgjD1A1gbRNCG+cNaGpedruvvHMMdrBVCL2
|
||||
nvtprj5s5Vc+72nYtjLVCrELOzHNN8DaoJ3PkCSKGNLG2OwDXWe0wP+0KJ4GFPpN
|
||||
0OKEAY2vvEzOo1ENkBOn16mGBLwXnkn13J8hdih7KPcgmBeMHceDjCGfVo6Z+fES
|
||||
C7SIL8obtt9HMXRqkVuWPcl+y3UmAsujWIjIHxEQDUlyj2TB5s2CefVb330CAwEA
|
||||
AaOBuTCBtjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGN
|
||||
oIGKoIGHhoGEaHR0cDovL2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2Ny
|
||||
bD9DQT0xQkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNn
|
||||
PUhBQ0M0NzFCNjU0MjJFMTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQCDOzEAS8OtTib+gRbYRDMw3mZ/dRR7RYuE8d1Rxf2y
|
||||
Xgv+C7NoHAFHxhoKmWGw9ImOMXM4YViHAWlkEZHqndF5ETNne2wl6X8wYpQIr1a1
|
||||
U0BlyxKOvgRquikPZp6mE2tOIxj6P2tngu2o9wljt7kuzDHsjdr1to4Omom1i514
|
||||
EgU2GoI37YgGIEeSy5c3h0j1vSQKy+fuKZKFWxPX1oOMTwVJFqtS/nrPBPftNMsf
|
||||
fIXBXjbKaLrjyBJiV/fD84nPENgOgkdnGp6/WaVy3kNydosTNINL4Es+0pUTTm9z
|
||||
EXRNzOxfvpYxGFGJyVEZmGwAOw/IVePN+J38FbSVyHyC
|
||||
-----END CERTIFICATE-----
|
||||
28
server/HoneyBox/src/HoneyBox.Api/cert/apiclient_key.pem
Normal file
28
server/HoneyBox/src/HoneyBox.Api/cert/apiclient_key.pem
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZudRk7U83LP95
|
||||
uFrgk6aar/kBpH2F2jM0GpHNWaRtYHZjoxGE2Zs1Dc+r526wm6fk+ZqMOOaP3Ov8
|
||||
dgoYWb0F4/7nfkRjyqckrk9YXUKKvpejnzW0v7t+YgxoIw9QNYG0TQhvnDWhqXna
|
||||
7r7xzDHawVQi9p77aa4+bOVXPu9p2LYy1QqxCzsxzTfA2qCdz5AkihjSxtjsA11n
|
||||
tMD/tCieBhT6TdDihAGNr7xMzqNRDZATp9ephgS8F55J9dyfIXYoeyj3IJgXjB3H
|
||||
g4whn1aOmfnxEgu0iC/KG7bfRzF0apFblj3Jfst1JgLLo1iIyB8REA1Jco9kwebN
|
||||
gnn1W999AgMBAAECggEATpKsnruxgcUAcYnhafh/AIYPA9O75OlI3z3TblsyZrKQ
|
||||
Jwb7VIk/ZNcWIgCERsH1xkF5z67dLf/ZPiPPItiHya9tF1fPEIBa73bkdYw6bl23
|
||||
1bmoJRGodUSnG5HDffvBUjMWn0itZikGK8dLK3G4cCyi03dTCoIp+qdL4L96oSSE
|
||||
uHChH9jSq2+LiR4P32GrSWO6z8dwS+2vonaepQoHbfEFbuSjNrdv78kt1DhJeY6u
|
||||
ZkRqDR/T10+BLZX2gxuQ0ddH/YgeO2E6K99a7YWCGGVH7C0U4T2ZR5HJjAgxGsQ6
|
||||
8KQvzXwhHYNyxkpBaRCO6dogofVe1PXxW/Xi3rlJFQKBgQDvpCpDB8P5RUvq2Us/
|
||||
7GnmeuwWuO8T5byNpTSBHNwVH/vCQqvorFPYJXicvk/1yjriXwiPnfw0j11uaZsd
|
||||
1ZJxQeXiS0ASsrihu5m6AxisFOU0cJNpl6njW5Y2JQAZdgg4APDzXfSLp2Ev5h6K
|
||||
vuRMfmmse0gWeWDFUZEamInyVwKBgQDolq8XtfyDAZPEPldJmbVMiuBu7nJLDhHz
|
||||
mL0tU5dPiKSduqFEbcuOSPREZb2wIw5MR1gsuCPk5rwx69DNY2Oztz+VcK3Vo1oN
|
||||
4MufsPXKOTOSbdV8JjcVLxHxn+b8QIbDribncsg15I7n8P3wcZ640WWGom/H9spZ
|
||||
HC3//DEgSwKBgHkRwWA4DiRjhCVUPpY/BImy1I/uQqsUyBvvuQT55Z6ul+ze7icQ
|
||||
2RM8ayEVbSRKVVGEnbihIogTXiqoI/wAqImbt16KkgZgULM1Kkc1xUM7E0lZDsCs
|
||||
JOJ+pPcZ3mD+psxUfWcWsrPTjmA6rHeAVarnus+vQQ5JqEBIIz0Cj77lAoGBAJPj
|
||||
lAuIjLmkHBfw58GFubCksVX3ybaNiL6SRN94QkKxCLK+A2KmSYL8QkznQDip4aKA
|
||||
zsEIiNI4IDvBzK973d5cy1IzJmUsC8u9PtwYQgDGZFNcAR2Ckw2mM0umt9F3Gfl8
|
||||
V4JdCo6x+GfkZSMoq5qqklqMGHVWJ42HjHwzF+2HAoGAE3FjmHDw5eGqu2aFNDUD
|
||||
ulh+ikSkjH+1hLKR6amDqssCackET1gYIRnUXAQO7FKg/W96enffg7jZF+7E3OOT
|
||||
TQo/obfpQPVaGKJ0CmqlSNyarZD6BFpBJKEiT8mlgkZ6XoyXZaAR2FjieoGd8xxi
|
||||
1mIIXb9fbcQO1lIhXnHlZUQ=
|
||||
-----END PRIVATE KEY-----
|
||||
9
server/HoneyBox/src/HoneyBox.Api/cert/pub_key.pem
Normal file
9
server/HoneyBox/src/HoneyBox.Api/cert/pub_key.pem
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0KeKMd6Yxovf4kPI0c1Q
|
||||
Islyq9fi/Wg60dodzPNkRRoraqmqbbW7uQcKHkHvIZi5Z9fK8SGkezyhcjiR3o8z
|
||||
uwnH5QiFuMw6P+1XB1koFfbxxCc6Eh0iuRI5BqNfyRwXwn9wIEUNwfF/SAPJGTkk
|
||||
hCzViil3tOmnJDMxQUJitt4RsnL6BvQ3afWcm7oqt7MLlcIhIW8jAsSFeWPuZcW5
|
||||
Hj+o2udrTUaTRkw7AEsHr9xyePhsqYjGxbi9fTlghkUYnRUNikSydtQoHbGHP70Q
|
||||
tz4HbPqH4gpsCqabPVuANFGH5a8uidOH3XKq2iPLggbPci1nFI8xMmHMaT88u/o5
|
||||
GQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -5,6 +5,7 @@ using HoneyBox.Model.Models;
|
|||
using HoneyBox.Model.Models.Goods;
|
||||
using HoneyBox.Model.Models.Lottery;
|
||||
using HoneyBox.Model.Models.Order;
|
||||
using HoneyBox.Model.Models.Payment;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ public class OrderService : IOrderService
|
|||
private readonly HoneyBoxDbContext _dbContext;
|
||||
private readonly ILogger<OrderService> _logger;
|
||||
private readonly ILotteryEngine _lotteryEngine;
|
||||
private readonly IWechatPayService _wechatPayService;
|
||||
|
||||
// 抽奖赏品ID范围 [10, 33]
|
||||
private static readonly int[] ShangPrizeIdRange = { 10, 33 };
|
||||
|
|
@ -28,11 +30,16 @@ public class OrderService : IOrderService
|
|||
// 无限赏商品类型
|
||||
private static readonly int[] InfiniteGoodsTypes = { 2, 8, 9, 10, 16, 17 };
|
||||
|
||||
public OrderService(HoneyBoxDbContext dbContext, ILogger<OrderService> logger, ILotteryEngine lotteryEngine)
|
||||
public OrderService(
|
||||
HoneyBoxDbContext dbContext,
|
||||
ILogger<OrderService> logger,
|
||||
ILotteryEngine lotteryEngine,
|
||||
IWechatPayService wechatPayService)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
_lotteryEngine = lotteryEngine;
|
||||
_wechatPayService = wechatPayService;
|
||||
}
|
||||
|
||||
#region 订单金额计算
|
||||
|
|
@ -1034,21 +1041,37 @@ public class OrderService : IOrderService
|
|||
|
||||
if (paymentResult.Price > 0)
|
||||
{
|
||||
// 需要微信支付
|
||||
// 注意:实际的微信支付参数生成需要调用微信支付API
|
||||
// 这里返回占位数据,实际实现需要集成微信支付SDK
|
||||
// 需要微信支付 - 调用微信支付服务
|
||||
var payRequest = new WechatPayRequest
|
||||
{
|
||||
UserId = userId,
|
||||
OrderNo = orderNum,
|
||||
Amount = paymentResult.Price,
|
||||
Body = goods.Title,
|
||||
Attach = $"order_{goods.Type}"
|
||||
};
|
||||
|
||||
var payResult = await _wechatPayService.CreatePaymentAsync(payRequest);
|
||||
|
||||
if (payResult.Status != 1 || payResult.Data == null)
|
||||
{
|
||||
// 支付创建失败,回滚事务
|
||||
await transaction.RollbackAsync();
|
||||
throw new InvalidOperationException(payResult.Msg ?? "创建支付订单失败");
|
||||
}
|
||||
|
||||
response = new OrderBuyResponseDto
|
||||
{
|
||||
Status = 1, // 需要支付
|
||||
OrderNum = orderNum,
|
||||
Res = new WechatPayParamsDto
|
||||
{
|
||||
AppId = "", // 从配置获取
|
||||
TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
|
||||
NonceStr = Guid.NewGuid().ToString("N"),
|
||||
Package = $"prepay_id=placeholder_{orderNum}",
|
||||
SignType = "RSA",
|
||||
PaySign = "" // 需要实际签名
|
||||
AppId = payResult.Data.AppId,
|
||||
TimeStamp = payResult.Data.TimeStamp,
|
||||
NonceStr = payResult.Data.NonceStr,
|
||||
Package = payResult.Data.Package,
|
||||
SignType = payResult.Data.SignType,
|
||||
PaySign = payResult.Data.PaySign
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1453,21 +1476,37 @@ public class OrderService : IOrderService
|
|||
|
||||
if (paymentResult.Price > 0)
|
||||
{
|
||||
// 需要微信支付
|
||||
// 注意:实际的微信支付参数生成需要调用微信支付API
|
||||
// 这里返回占位数据,实际实现需要集成微信支付SDK
|
||||
// 需要微信支付 - 调用微信支付服务
|
||||
var payRequest = new WechatPayRequest
|
||||
{
|
||||
UserId = userId,
|
||||
OrderNo = orderNum,
|
||||
Amount = paymentResult.Price,
|
||||
Body = goods.Title,
|
||||
Attach = $"infinite_{goods.Type}"
|
||||
};
|
||||
|
||||
var payResult = await _wechatPayService.CreatePaymentAsync(payRequest);
|
||||
|
||||
if (payResult.Status != 1 || payResult.Data == null)
|
||||
{
|
||||
// 支付创建失败,回滚事务
|
||||
await transaction.RollbackAsync();
|
||||
throw new InvalidOperationException(payResult.Msg ?? "创建支付订单失败");
|
||||
}
|
||||
|
||||
response = new OrderBuyResponseDto
|
||||
{
|
||||
Status = 1, // 需要支付
|
||||
OrderNum = orderNum,
|
||||
Res = new WechatPayParamsDto
|
||||
{
|
||||
AppId = "", // 从配置获取
|
||||
TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
|
||||
NonceStr = Guid.NewGuid().ToString("N"),
|
||||
Package = $"prepay_id=placeholder_{orderNum}",
|
||||
SignType = "RSA",
|
||||
PaySign = "" // 需要实际签名
|
||||
AppId = payResult.Data.AppId,
|
||||
TimeStamp = payResult.Data.TimeStamp,
|
||||
NonceStr = payResult.Data.NonceStr,
|
||||
Package = payResult.Data.Package,
|
||||
SignType = payResult.Data.SignType,
|
||||
PaySign = payResult.Data.PaySign
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,373 +1,327 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using HoneyBox.Core.Interfaces;
|
||||
using HoneyBox.Model.Data;
|
||||
using HoneyBox.Model.Models.Payment;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace HoneyBox.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付配置服务实现
|
||||
/// 负责处理多商户配置、订单前缀匹配等逻辑
|
||||
/// 从数据库读取配置,支持多商户、多小程序
|
||||
/// </summary>
|
||||
public class WechatPayConfigService : IWechatPayConfigService
|
||||
{
|
||||
private readonly WechatPaySettings _settings;
|
||||
private readonly HoneyBoxDbContext _dbContext;
|
||||
private readonly IRedisService _redisService;
|
||||
private readonly ILogger<WechatPayConfigService> _logger;
|
||||
private readonly Random _random = new();
|
||||
|
||||
// 订单前缀常量
|
||||
private const string MH_PREFIX = "MH_";
|
||||
private const string FH_PREFIX = "FH_";
|
||||
private const int MERCHANT_PREFIX_LENGTH = 3;
|
||||
private const int MINIPROGRAM_PREFIX_LENGTH = 2;
|
||||
private const int TOTAL_PREFIX_LENGTH = MERCHANT_PREFIX_LENGTH + MINIPROGRAM_PREFIX_LENGTH;
|
||||
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(
|
||||
IOptions<WechatPaySettings> settings,
|
||||
HoneyBoxDbContext dbContext,
|
||||
IRedisService redisService,
|
||||
ILogger<WechatPayConfigService> logger)
|
||||
{
|
||||
_settings = settings.Value;
|
||||
_dbContext = dbContext;
|
||||
_redisService = redisService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatPayMerchantConfig GetDefaultConfig()
|
||||
private async Task<List<WechatPayMerchantConfig>> LoadMerchantsAsync()
|
||||
{
|
||||
return _settings.DefaultMerchant;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatPayMerchantConfig GetMerchantByOrderNo(string orderNo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(orderNo))
|
||||
var cachedJson = await _redisService.GetStringAsync(MERCHANTS_CACHE_KEY);
|
||||
if (!string.IsNullOrEmpty(cachedJson))
|
||||
{
|
||||
return _settings.DefaultMerchant;
|
||||
}
|
||||
|
||||
// 检查是否是MH_或FH_开头的订单号
|
||||
if (!orderNo.StartsWith(MH_PREFIX) && !orderNo.StartsWith(FH_PREFIX))
|
||||
{
|
||||
// 尝试直接匹配订单前缀
|
||||
foreach (var m in _settings.Merchants)
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m.OrderPrefix) &&
|
||||
orderNo.StartsWith(m.OrderPrefix))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
var cached = JsonSerializer.Deserialize<List<WechatPayMerchantConfig>>(cachedJson);
|
||||
if (cached != null && cached.Count > 0) return cached;
|
||||
}
|
||||
return _settings.DefaultMerchant;
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 提取订单前缀信息
|
||||
var prefixInfo = ExtractOrderPrefix(orderNo);
|
||||
if (prefixInfo == null)
|
||||
var merchants = new List<WechatPayMerchantConfig>();
|
||||
try
|
||||
{
|
||||
return _settings.DefaultMerchant;
|
||||
}
|
||||
var settingConfig = await _dbContext.Configs
|
||||
.Where(c => c.ConfigKey == "weixinpay_setting")
|
||||
.Select(c => c.ConfigValue)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
WechatPayMerchantConfig? merchant = null;
|
||||
string appId = string.Empty;
|
||||
|
||||
// 优先根据小程序前缀获取配置
|
||||
if (!string.IsNullOrEmpty(prefixInfo.MiniprogramPrefix))
|
||||
{
|
||||
var miniprogramConfig = GetMiniprogramByPrefix(prefixInfo.MiniprogramPrefix);
|
||||
if (miniprogramConfig != null)
|
||||
if (!string.IsNullOrEmpty(settingConfig))
|
||||
{
|
||||
appId = miniprogramConfig.AppId;
|
||||
|
||||
// 如果有商户前缀,使用指定商户
|
||||
if (!string.IsNullOrEmpty(prefixInfo.MerchantPrefix))
|
||||
var setting = JsonSerializer.Deserialize<DbWeixinPaySetting>(settingConfig, JsonOptions);
|
||||
if (setting?.Merchants != null)
|
||||
{
|
||||
merchant = GetMerchantByPrefix(prefixInfo.MerchantPrefix);
|
||||
}
|
||||
|
||||
// 如果没有找到商户,从小程序关联的商户中选择
|
||||
if (merchant == null && miniprogramConfig.Merchants.Count > 0)
|
||||
{
|
||||
var associatedMerchants = _settings.Merchants
|
||||
.Where(m => miniprogramConfig.Merchants.Contains(m.MchId))
|
||||
.ToList();
|
||||
|
||||
if (associatedMerchants.Count > 0)
|
||||
foreach (var m in setting.Merchants.Where(x => x.IsEnabled == "1"))
|
||||
{
|
||||
merchant = GetRandomMerchant(associatedMerchants);
|
||||
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,
|
||||
WechatPublicKeyId = m.WechatPublicKeyId,
|
||||
WechatPublicKeyPath = m.WechatPublicKeyPath,
|
||||
NotifyUrl = !string.IsNullOrEmpty(m.NotifyUrl) ? m.NotifyUrl : "https://api.zfunbox.cn/api/notify"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有通过小程序前缀获取到配置,则回退到商户前缀
|
||||
if (merchant == null && !string.IsNullOrEmpty(prefixInfo.MerchantPrefix))
|
||||
{
|
||||
merchant = GetMerchantByPrefix(prefixInfo.MerchantPrefix);
|
||||
}
|
||||
var weixinpayConfig = await _dbContext.Configs
|
||||
.Where(c => c.ConfigKey == "weixinpay")
|
||||
.Select(c => c.ConfigValue)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
// 如果还是没有找到,返回默认配置
|
||||
if (merchant == null)
|
||||
{
|
||||
return _settings.DefaultMerchant;
|
||||
}
|
||||
|
||||
// 如果有小程序AppId,覆盖商户的AppId
|
||||
if (!string.IsNullOrEmpty(appId))
|
||||
{
|
||||
// 创建一个新的配置对象,避免修改原始配置
|
||||
return new WechatPayMerchantConfig
|
||||
if (!string.IsNullOrEmpty(weixinpayConfig))
|
||||
{
|
||||
Name = merchant.Name,
|
||||
MchId = merchant.MchId,
|
||||
AppId = appId,
|
||||
Key = merchant.Key,
|
||||
OrderPrefix = merchant.OrderPrefix,
|
||||
Weight = merchant.Weight,
|
||||
NotifyUrl = merchant.NotifyUrl,
|
||||
// V3 字段映射
|
||||
PayVersion = merchant.PayVersion,
|
||||
ApiV3Key = merchant.ApiV3Key,
|
||||
CertSerialNo = merchant.CertSerialNo,
|
||||
PrivateKeyPath = merchant.PrivateKeyPath,
|
||||
WechatPublicKeyId = merchant.WechatPublicKeyId,
|
||||
WechatPublicKeyPath = merchant.WechatPublicKeyPath
|
||||
};
|
||||
}
|
||||
|
||||
return merchant;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatPayMerchantConfig? GetMerchantByPrefix(string merchantPrefix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(merchantPrefix))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _settings.Merchants.FirstOrDefault(m =>
|
||||
!string.IsNullOrEmpty(m.OrderPrefix) &&
|
||||
m.OrderPrefix.Equals(merchantPrefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MiniprogramConfig? GetMiniprogramByPrefix(string miniprogramPrefix)
|
||||
{
|
||||
if (string.IsNullOrEmpty(miniprogramPrefix))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _settings.Miniprograms.FirstOrDefault(m =>
|
||||
!string.IsNullOrEmpty(m.OrderPrefix) &&
|
||||
m.OrderPrefix.Equals(miniprogramPrefix, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MiniprogramConfig? GetMiniprogramByDomain(string domain)
|
||||
{
|
||||
if (string.IsNullOrEmpty(domain))
|
||||
{
|
||||
return GetDefaultMiniprogram();
|
||||
}
|
||||
|
||||
foreach (var miniprogram in _settings.Miniprograms)
|
||||
{
|
||||
if (string.IsNullOrEmpty(miniprogram.Domain))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 分割多个域名
|
||||
var domains = miniprogram.Domain.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var d in domains)
|
||||
{
|
||||
var trimmedDomain = d.Trim();
|
||||
if (DomainMatch(trimmedDomain, domain))
|
||||
var config = JsonSerializer.Deserialize<DbWeixinPayConfig>(weixinpayConfig, JsonOptions);
|
||||
if (config != null && !string.IsNullOrEmpty(config.MchId) && !merchants.Any(m => m.MchId == config.MchId))
|
||||
{
|
||||
return miniprogram;
|
||||
merchants.Add(new WechatPayMerchantConfig
|
||||
{
|
||||
Name = "默认商户",
|
||||
MchId = config.MchId,
|
||||
AppId = config.AppId ?? "",
|
||||
Key = config.Keys ?? "",
|
||||
OrderPrefix = "MYH",
|
||||
PayVersion = "V2",
|
||||
NotifyUrl = "https://api.zfunbox.cn/api/notify"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GetDefaultMiniprogram();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MiniprogramConfig? GetDefaultMiniprogram()
|
||||
{
|
||||
// 查找默认小程序配置
|
||||
var defaultMiniprogram = _settings.Miniprograms.FirstOrDefault(m => m.IsDefault);
|
||||
|
||||
// 如果没有设置默认配置,返回第一个配置
|
||||
if (defaultMiniprogram == null && _settings.Miniprograms.Count > 0)
|
||||
{
|
||||
return _settings.Miniprograms[0];
|
||||
}
|
||||
|
||||
return defaultMiniprogram;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public OrderPrefixInfo? ExtractOrderPrefix(string orderNo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(orderNo))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否是MH_或FH_开头
|
||||
if (!orderNo.StartsWith(MH_PREFIX) && !orderNo.StartsWith(FH_PREFIX))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取MH_或FH_后的字符
|
||||
// 订单格式: MH_ABC12... 或 FH_ABC12...
|
||||
// 其中ABC是商户前缀(3位),12是小程序前缀(2位,可选)
|
||||
var prefixStart = 3; // "MH_" 或 "FH_" 的长度
|
||||
|
||||
if (orderNo.Length < prefixStart + MERCHANT_PREFIX_LENGTH)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new OrderPrefixInfo();
|
||||
|
||||
// 提取商户前缀(前3位)
|
||||
result.MerchantPrefix = orderNo.Substring(prefixStart, MERCHANT_PREFIX_LENGTH);
|
||||
|
||||
// 如果有足够长度,提取小程序前缀(后2位)
|
||||
if (orderNo.Length >= prefixStart + TOTAL_PREFIX_LENGTH)
|
||||
{
|
||||
result.MiniprogramPrefix = orderNo.Substring(
|
||||
prefixStart + MERCHANT_PREFIX_LENGTH,
|
||||
MINIPROGRAM_PREFIX_LENGTH);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public WechatPayMerchantConfig? GetRandomMerchant(IEnumerable<WechatPayMerchantConfig> merchants)
|
||||
{
|
||||
var merchantList = merchants.ToList();
|
||||
|
||||
if (merchantList.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 只有一个商户,直接返回
|
||||
if (merchantList.Count == 1)
|
||||
{
|
||||
return merchantList[0];
|
||||
}
|
||||
|
||||
// 计算总权重
|
||||
var totalWeight = merchantList.Sum(m => m.Weight > 0 ? m.Weight : 1);
|
||||
|
||||
// 生成随机数
|
||||
var randomWeight = _random.Next(1, totalWeight + 1);
|
||||
|
||||
// 根据权重选择商户
|
||||
var currentWeight = 0;
|
||||
foreach (var merchant in merchantList)
|
||||
{
|
||||
var weight = merchant.Weight > 0 ? merchant.Weight : 1;
|
||||
currentWeight += weight;
|
||||
|
||||
if (randomWeight <= currentWeight)
|
||||
_logger.LogInformation("从数据库加载了 {Count} 个商户配置", merchants.Count);
|
||||
if (merchants.Count > 0)
|
||||
{
|
||||
return merchant;
|
||||
await _redisService.SetStringAsync(MERCHANTS_CACHE_KEY, JsonSerializer.Serialize(merchants), CACHE_DURATION);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "加载商户配置失败");
|
||||
}
|
||||
|
||||
// 默认返回第一个商户
|
||||
return merchantList[0];
|
||||
return merchants;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public (WechatPayMerchantConfig Merchant, string AppId) GetWxPayConfig()
|
||||
private async Task<List<MiniprogramConfig>> LoadMiniprogramsAsync()
|
||||
{
|
||||
// 获取当前域名对应的小程序配置
|
||||
var miniprogram = GetDefaultMiniprogram();
|
||||
WechatPayMerchantConfig? merchant = null;
|
||||
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 _dbContext.Configs
|
||||
.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 = _settings.Merchants
|
||||
.Where(m => miniprogram.Merchants.Contains(m.MchId))
|
||||
.ToList();
|
||||
|
||||
var associatedMerchants = merchants.Where(m => miniprogram.Merchants.Contains(m.MchId)).ToList();
|
||||
if (associatedMerchants.Count > 0)
|
||||
{
|
||||
merchant = GetRandomMerchant(associatedMerchants);
|
||||
selectedMerchant = GetRandomMerchant(associatedMerchants);
|
||||
_logger.LogDebug("从小程序关联商户中选择: MchId={MchId}", selectedMerchant?.MchId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有关联商户,则从所有商户中随机选择
|
||||
if (merchant == null && _settings.Merchants.Count > 0)
|
||||
selectedMerchant ??= merchants.FirstOrDefault();
|
||||
if (selectedMerchant == null) return new WechatPayMerchantConfig();
|
||||
|
||||
return new WechatPayMerchantConfig
|
||||
{
|
||||
merchant = GetRandomMerchant(_settings.Merchants);
|
||||
}
|
||||
|
||||
// 如果还是没有商户,使用默认商户
|
||||
merchant ??= _settings.DefaultMerchant;
|
||||
|
||||
// 获取AppId - 优先使用小程序配置中的AppId
|
||||
var appId = miniprogram?.AppId ?? merchant.AppId;
|
||||
|
||||
return (merchant, appId);
|
||||
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,
|
||||
WechatPublicKeyId = selectedMerchant.WechatPublicKeyId,
|
||||
WechatPublicKeyPath = selectedMerchant.WechatPublicKeyPath
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public (WechatPayMerchantConfig? Merchant, string AppId) GetFixedWxPayConfig(string orderPrefix)
|
||||
{
|
||||
// 获取当前域名对应的小程序配置
|
||||
var miniprogram = GetDefaultMiniprogram();
|
||||
|
||||
// 尝试查找与订单前缀匹配的商户
|
||||
var merchant = GetMerchantByPrefix(orderPrefix);
|
||||
|
||||
// 如果没有找到匹配的商户,则使用随机选择
|
||||
if (merchant == null)
|
||||
{
|
||||
var config = GetWxPayConfig();
|
||||
return (config.Merchant, config.AppId);
|
||||
}
|
||||
|
||||
// 获取AppId - 优先使用小程序配置中的AppId
|
||||
var appId = miniprogram?.AppId ?? merchant.AppId;
|
||||
|
||||
return (merchant, appId);
|
||||
var miniprogram = GetDefaultMiniprogram();
|
||||
return (merchant, miniprogram?.AppId ?? merchant.AppId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查域名是否匹配
|
||||
/// </summary>
|
||||
/// <param name="pattern">配置中的域名模式</param>
|
||||
/// <param name="domain">当前请求的域名</param>
|
||||
/// <returns>是否匹配</returns>
|
||||
private static bool DomainMatch(string pattern, string domain)
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
private class DbWeixinPaySetting { [JsonPropertyName("merchants")] public List<DbMerchantConfig>? Merchants { get; set; } }
|
||||
private class DbMerchantConfig
|
||||
{
|
||||
if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(domain))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 简单的域名匹配,支持通配符 * (例如: *.example.com)
|
||||
if (pattern.Contains('*'))
|
||||
{
|
||||
var regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
|
||||
.Replace("\\*", ".*") + "$";
|
||||
return System.Text.RegularExpressions.Regex.IsMatch(
|
||||
domain,
|
||||
regexPattern,
|
||||
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
return string.Equals(pattern, domain, StringComparison.OrdinalIgnoreCase);
|
||||
[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("wechat_public_key_id")] public string? WechatPublicKeyId { get; set; }
|
||||
[JsonPropertyName("wechat_public_key_path")] public string? WechatPublicKeyPath { 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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,7 +220,8 @@ public class ServiceModule : Module
|
|||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var logger = c.Resolve<ILogger<OrderService>>();
|
||||
var lotteryEngine = c.Resolve<ILotteryEngine>();
|
||||
return new OrderService(dbContext, logger, lotteryEngine);
|
||||
var wechatPayService = c.Resolve<IWechatPayService>();
|
||||
return new OrderService(dbContext, logger, lotteryEngine, wechatPayService);
|
||||
}).As<IOrderService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册仓库服务
|
||||
|
|
@ -235,8 +236,14 @@ public class ServiceModule : Module
|
|||
|
||||
// ========== 支付系统服务注册 ==========
|
||||
|
||||
// 注册微信支付配置服务
|
||||
builder.RegisterType<WechatPayConfigService>().As<IWechatPayConfigService>().InstancePerLifetimeScope();
|
||||
// 注册微信支付配置服务(从数据库读取配置)
|
||||
builder.Register(c =>
|
||||
{
|
||||
var dbContext = c.Resolve<HoneyBoxDbContext>();
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
var logger = c.Resolve<ILogger<WechatPayConfigService>>();
|
||||
return new WechatPayConfigService(dbContext, redisService, logger);
|
||||
}).As<IWechatPayConfigService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册微信支付 V3 服务
|
||||
builder.Register(c =>
|
||||
|
|
@ -258,8 +265,8 @@ public class ServiceModule : Module
|
|||
var wechatService = c.Resolve<IWechatService>();
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
var settings = c.Resolve<Microsoft.Extensions.Options.IOptions<WechatPaySettings>>();
|
||||
// 使用 Lazy 延迟解析 V3 服务,避免循环依赖
|
||||
var v3ServiceLazy = new Lazy<IWechatPayV3Service>(() => c.Resolve<IWechatPayV3Service>());
|
||||
// Autofac 原生支持 Lazy<T>,直接解析即可
|
||||
var v3ServiceLazy = c.Resolve<Lazy<IWechatPayV3Service>>();
|
||||
return new WechatPayService(dbContext, httpClientFactory.CreateClient(), logger, configService, wechatService, redisService, settings, v3ServiceLazy);
|
||||
}).As<IWechatPayService>().InstancePerLifetimeScope();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user