diff --git a/honey_box/pages/user/recharge-page.vue b/honey_box/pages/user/recharge-page.vue
index 52280973..34ff3acc 100644
--- a/honey_box/pages/user/recharge-page.vue
+++ b/honey_box/pages/user/recharge-page.vue
@@ -30,10 +30,6 @@
-
-
-
-
diff --git a/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs b/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs
index 6c0ece11..f4388b26 100644
--- a/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs
+++ b/server/HoneyBox/src/HoneyBox.Core/Interfaces/IWechatService.cs
@@ -45,6 +45,14 @@ public interface IWechatService
/// 二维码宽度,默认430
/// 小程序码图片字节数组,失败返回null
Task GetWxaCodeUnlimitAsync(string scene, string? page = null, int width = 430);
+
+ ///
+ /// 上传微信小程序发货信息
+ /// 调用微信API upload_shipping_info 将订单标记为已发货
+ ///
+ /// 发货信息请求
+ /// 发货结果
+ Task UploadShippingInfoAsync(WechatShippingRequest request);
}
///
diff --git a/server/HoneyBox/src/HoneyBox.Core/Services/PaymentNotifyService.cs b/server/HoneyBox/src/HoneyBox.Core/Services/PaymentNotifyService.cs
index af4296f6..34df3037 100644
--- a/server/HoneyBox/src/HoneyBox.Core/Services/PaymentNotifyService.cs
+++ b/server/HoneyBox/src/HoneyBox.Core/Services/PaymentNotifyService.cs
@@ -2,6 +2,7 @@ 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.Lottery;
using HoneyBox.Model.Models.Mall;
using HoneyBox.Model.Models.Payment;
@@ -21,6 +22,8 @@ public class PaymentNotifyService : IPaymentNotifyService
private readonly IWechatPayConfigService _wechatPayConfigService;
private readonly IPaymentService _paymentService;
private readonly ILotteryEngine _lotteryEngine;
+ private readonly IWechatService _wechatService;
+ private readonly IRedisService _redisService;
private readonly ILogger _logger;
///
@@ -51,6 +54,8 @@ public class PaymentNotifyService : IPaymentNotifyService
IWechatPayConfigService wechatPayConfigService,
IPaymentService paymentService,
ILotteryEngine lotteryEngine,
+ IWechatService wechatService,
+ IRedisService redisService,
ILogger logger)
{
_dbContext = dbContext;
@@ -59,6 +64,8 @@ public class PaymentNotifyService : IPaymentNotifyService
_wechatPayConfigService = wechatPayConfigService;
_paymentService = paymentService;
_lotteryEngine = lotteryEngine;
+ _wechatService = wechatService;
+ _redisService = redisService;
_logger = logger;
}
@@ -418,22 +425,24 @@ public class PaymentNotifyService : IPaymentNotifyService
// 记录支付流水
await RecordPaymentAsync(user.Id, orderNo, notifyData.TotalFee / 100m, attach);
+ bool processResult;
+
// 根据attach类型路由处理
// attach格式: order_{type} 或 infinite_{type} 或固定字符串
if (attach == OrderAttachType.UserRecharge)
{
// 余额充值
- return await ProcessRechargeOrderAsync(orderNo);
+ processResult = await ProcessRechargeOrderAsync(orderNo);
}
else if (attach == OrderAttachType.OrderListSend)
{
// 发货运费订单
- return await ProcessShippingFeeOrderAsync(orderNo);
+ processResult = await ProcessShippingFeeOrderAsync(orderNo);
}
else if (attach == OrderAttachType.OrderProduct)
{
// 钻石商品订单
- return await ProcessDiamondOrderAsync(orderNo, user.Id);
+ processResult = await ProcessDiamondOrderAsync(orderNo, user.Id);
}
else if (attach.StartsWith("order_"))
{
@@ -445,39 +454,53 @@ public class PaymentNotifyService : IPaymentNotifyService
// 类型 4 是抽卡机
if (orderType == 4)
{
- return await ProcessCardExtractorOrderByOrderNoAsync(orderNo);
+ processResult = await ProcessCardExtractorOrderByOrderNoAsync(orderNo);
+ }
+ else
+ {
+ // 其他类型按一番赏处理
+ processResult = await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
- // 其他类型按一番赏处理
- return await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
- // 如果不是数字格式,尝试匹配旧格式
- if (LotteryOrderTypes.Contains(attach))
+ else if (LotteryOrderTypes.Contains(attach))
{
- return await ProcessLotteryOrderByOrderNoAsync(orderNo);
+ // 如果不是数字格式,尝试匹配旧格式
+ processResult = await ProcessLotteryOrderByOrderNoAsync(orderNo);
+ }
+ else
+ {
+ _logger.LogWarning("未知的order_类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
+ return false;
}
- _logger.LogWarning("未知的order_类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
- return false;
}
else if (attach.StartsWith("infinite_"))
{
// 无限赏类订单 (infinite_2, infinite_7, infinite_8, infinite_9 等)
- return await ProcessInfiniteOrderByOrderNoAsync(orderNo);
+ processResult = await ProcessInfiniteOrderByOrderNoAsync(orderNo);
}
else if (LotteryOrderTypes.Contains(attach))
{
// 兼容旧格式 order_yfs, order_lts 等
- return await ProcessLotteryOrderByOrderNoAsync(orderNo);
+ processResult = await ProcessLotteryOrderByOrderNoAsync(orderNo);
}
else if (InfiniteOrderTypes.Contains(attach))
{
// 兼容旧格式 order_wxs, order_fbs 等
- return await ProcessInfiniteOrderByOrderNoAsync(orderNo);
+ processResult = await ProcessInfiniteOrderByOrderNoAsync(orderNo);
}
else
{
_logger.LogWarning("未知的订单类型: Attach={Attach}, OrderNo={OrderNo}", attach, orderNo);
return false;
}
+
+ // 处理成功后,调用微信发货接口
+ if (processResult && !string.IsNullOrEmpty(notifyData.OpenId))
+ {
+ await UploadWechatShippingInfoAsync(notifyData.OpenId, orderNo);
+ }
+
+ return processResult;
}
catch (Exception ex)
{
@@ -1407,5 +1430,76 @@ public class PaymentNotifyService : IPaymentNotifyService
}
}
+ ///
+ /// 上传微信发货信息
+ /// 支付成功后调用微信接口将订单标记为已发货
+ ///
+ private async Task UploadWechatShippingInfoAsync(string openId, string orderNo)
+ {
+ try
+ {
+ var request = new WechatShippingRequest
+ {
+ OpenId = openId,
+ OrderNo = orderNo
+ };
+
+ var result = await _wechatService.UploadShippingInfoAsync(request);
+
+ if (result.Success)
+ {
+ _logger.LogInformation("微信发货成功: OrderNo={OrderNo}", orderNo);
+ }
+ else
+ {
+ _logger.LogWarning("微信发货失败: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}",
+ orderNo, result.ErrorCode, result.ErrorMessage);
+
+ // 发货失败,存入Redis等待重试
+ if (result.NeedRetry)
+ {
+ await SaveShippingRetryInfoAsync(openId, orderNo, result.ErrorCode, result.ErrorMessage);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "调用微信发货接口异常: OrderNo={OrderNo}", orderNo);
+ // 异常情况也存入Redis等待重试
+ await SaveShippingRetryInfoAsync(openId, orderNo, -1, ex.Message);
+ }
+ }
+
+ ///
+ /// 保存发货重试信息到Redis
+ ///
+ private async Task SaveShippingRetryInfoAsync(string openId, string orderNo, int errorCode, string? errorMessage)
+ {
+ try
+ {
+ var key = $"post_order:{orderNo}";
+ var retryData = new
+ {
+ openid = openId,
+ order_num = orderNo,
+ error_code = errorCode,
+ error_msg = errorMessage ?? "unknown",
+ retry_count = 0,
+ last_retry_time = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
+ create_time = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ };
+
+ var json = JsonSerializer.Serialize(retryData);
+ // 设置过期时间为3天
+ await _redisService.SetStringAsync(key, json, TimeSpan.FromDays(3));
+
+ _logger.LogInformation("发货重试信息已保存到Redis: OrderNo={OrderNo}, Key={Key}", orderNo, key);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "保存发货重试信息失败: OrderNo={OrderNo}", orderNo);
+ }
+ }
+
#endregion
}
diff --git a/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs b/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs
index 712a4a99..ba9e3f02 100644
--- a/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs
+++ b/server/HoneyBox/src/HoneyBox.Core/Services/WechatService.cs
@@ -947,4 +947,201 @@ public class WechatService : IWechatService
var random = new Random().Next(1000, 9999);
return $"{prefix}{merchantPrefix}{projectPrefix}{payType}{timestamp}{random}";
}
+
+ ///
+ /// 上传微信小程序发货信息
+ /// 调用微信API upload_shipping_info 将订单标记为已发货
+ ///
+ public async Task UploadShippingInfoAsync(WechatShippingRequest request)
+ {
+ const string uploadShippingUrl = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info";
+
+ try
+ {
+ // 1. 参数验证
+ if (string.IsNullOrEmpty(request.OpenId))
+ {
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = "OpenId不能为空"
+ };
+ }
+
+ if (string.IsNullOrEmpty(request.OrderNo))
+ {
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = "订单号不能为空"
+ };
+ }
+
+ // 2. 获取商户配置(根据订单号匹配)
+ var merchantConfig = await GetMerchantConfigByOrderNoAsync(request.OrderNo);
+ if (merchantConfig == null)
+ {
+ _logger.LogWarning("发货失败:未找到订单对应的商户配置, OrderNo={OrderNo}", request.OrderNo);
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = "未找到商户配置",
+ NeedRetry = true
+ };
+ }
+
+ var mchId = request.MchId ?? merchantConfig.MchId;
+ var appId = request.AppId ?? merchantConfig.AppId;
+
+ // 3. 获取access_token
+ var accessToken = await GetAccessTokenAsync(appId);
+ if (string.IsNullOrEmpty(accessToken))
+ {
+ _logger.LogWarning("发货失败:获取access_token失败, OrderNo={OrderNo}", request.OrderNo);
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = "获取access_token失败",
+ NeedRetry = true
+ };
+ }
+
+ // 4. 构建请求参数
+ var itemDesc = request.ItemDesc ?? GetDefaultItemDesc(request.OrderNo);
+ var uploadTime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00";
+
+ var requestBody = new
+ {
+ order_key = new
+ {
+ order_number_type = 1, // 使用商户订单号
+ mchid = mchId,
+ out_trade_no = request.OrderNo
+ },
+ logistics_type = request.LogisticsType, // 4=虚拟商品
+ delivery_mode = 1, // 1=统一发货
+ shipping_list = new[]
+ {
+ new { item_desc = itemDesc }
+ },
+ upload_time = uploadTime,
+ payer = new
+ {
+ openid = request.OpenId
+ }
+ };
+
+ var jsonContent = JsonSerializer.Serialize(requestBody, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ });
+
+ _logger.LogInformation("调用微信发货接口: OrderNo={OrderNo}, MchId={MchId}, OpenId={OpenId}",
+ request.OrderNo, mchId, request.OpenId);
+
+ // 5. 发送请求
+ var url = $"{uploadShippingUrl}?access_token={accessToken}";
+ var content = new StringContent(jsonContent, System.Text.Encoding.UTF8, "application/json");
+ var response = await _httpClient.PostAsync(url, content);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ _logger.LogDebug("微信发货接口响应: {Response}", responseContent);
+
+ // 6. 解析响应
+ using var jsonDoc = JsonDocument.Parse(responseContent);
+ var root = jsonDoc.RootElement;
+
+ var errCode = root.TryGetProperty("errcode", out var errCodeProp) ? errCodeProp.GetInt32() : -1;
+ var errMsg = root.TryGetProperty("errmsg", out var errMsgProp) ? errMsgProp.GetString() : "未知错误";
+
+ if (errCode == 0)
+ {
+ _logger.LogInformation("微信发货成功: OrderNo={OrderNo}", request.OrderNo);
+ return new WechatShippingResult
+ {
+ Success = true,
+ ErrorCode = 0,
+ ErrorMessage = "ok"
+ };
+ }
+ else
+ {
+ _logger.LogWarning("微信发货失败: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}",
+ request.OrderNo, errCode, errMsg);
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = errCode,
+ ErrorMessage = errMsg,
+ NeedRetry = ShouldRetry(errCode)
+ };
+ }
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "微信发货HTTP请求异常: OrderNo={OrderNo}", request.OrderNo);
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = $"HTTP请求异常: {ex.Message}",
+ NeedRetry = true
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "微信发货异常: OrderNo={OrderNo}", request.OrderNo);
+ return new WechatShippingResult
+ {
+ Success = false,
+ ErrorCode = -1,
+ ErrorMessage = $"发货异常: {ex.Message}",
+ NeedRetry = true
+ };
+ }
+ }
+
+ ///
+ /// 根据订单号获取商户配置
+ ///
+ private async Task GetMerchantConfigByOrderNoAsync(string orderNo)
+ {
+ // 直接使用现有的 GetMerchantConfigAsync 方法获取默认商户配置
+ // 该方法已经从数据库读取配置并支持缓存
+ return await GetMerchantConfigAsync();
+ }
+
+ ///
+ /// 获取默认商品描述
+ ///
+ private static string GetDefaultItemDesc(string orderNo)
+ {
+ // 发货订单(FH_开头)使用不同的描述
+ if (orderNo.StartsWith("FH_", StringComparison.OrdinalIgnoreCase))
+ {
+ return "本单购买的商品正在打包,请联系客服获取物流信息";
+ }
+ return "本单购买商品已发放至[小程序盒柜]";
+ }
+
+ ///
+ /// 判断是否需要重试
+ ///
+ private static bool ShouldRetry(int errCode)
+ {
+ // 以下错误码可以重试
+ // 40001: access_token无效(可能过期)
+ // -1: 系统繁忙
+ return errCode switch
+ {
+ 40001 => true, // access_token无效
+ -1 => true, // 系统繁忙
+ 42001 => true, // access_token过期
+ _ => false
+ };
+ }
}
diff --git a/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs b/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs
index 83420546..48d90849 100644
--- a/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs
+++ b/server/HoneyBox/src/HoneyBox.Infrastructure/Modules/ServiceModule.cs
@@ -288,8 +288,10 @@ public class ServiceModule : Module
var wechatPayConfigService = c.Resolve();
var paymentService = c.Resolve();
var lotteryEngine = c.Resolve();
+ var wechatService = c.Resolve();
+ var redisService = c.Resolve();
var logger = c.Resolve>();
- return new PaymentNotifyService(dbContext, wechatPayService, wechatPayV3Service, wechatPayConfigService, paymentService, lotteryEngine, logger);
+ return new PaymentNotifyService(dbContext, wechatPayService, wechatPayV3Service, wechatPayConfigService, paymentService, lotteryEngine, wechatService, redisService, logger);
}).As().InstancePerLifetimeScope();
// 注册充值服务
diff --git a/server/HoneyBox/src/HoneyBox.Model/Models/Auth/WechatAuthResult.cs b/server/HoneyBox/src/HoneyBox.Model/Models/Auth/WechatAuthResult.cs
index a9ed424d..35986382 100644
--- a/server/HoneyBox/src/HoneyBox.Model/Models/Auth/WechatAuthResult.cs
+++ b/server/HoneyBox/src/HoneyBox.Model/Models/Auth/WechatAuthResult.cs
@@ -46,3 +46,66 @@ public class WechatMobileResult
///
public string? ErrorMessage { get; set; }
}
+
+///
+/// 微信小程序发货信息请求
+///
+public class WechatShippingRequest
+{
+ ///
+ /// 用户OpenId
+ ///
+ public string OpenId { get; set; } = string.Empty;
+
+ ///
+ /// 商户订单号
+ ///
+ public string OrderNo { get; set; } = string.Empty;
+
+ ///
+ /// 商户号(可选,不传则根据订单号自动匹配)
+ ///
+ public string? MchId { get; set; }
+
+ ///
+ /// 小程序AppId(可选,不传则使用默认配置)
+ ///
+ public string? AppId { get; set; }
+
+ ///
+ /// 商品描述(可选,默认为"本单购买商品已发放至[小程序盒柜]")
+ ///
+ public string? ItemDesc { get; set; }
+
+ ///
+ /// 物流类型:1=实体物流,2=同城配送,3=虚拟商品,4=用户自提
+ /// 默认为4(虚拟商品场景)
+ ///
+ public int LogisticsType { get; set; } = 4;
+}
+
+///
+/// 微信小程序发货信息结果
+///
+public class WechatShippingResult
+{
+ ///
+ /// 是否成功
+ ///
+ public bool Success { get; set; }
+
+ ///
+ /// 错误码(0表示成功)
+ ///
+ public int ErrorCode { get; set; }
+
+ ///
+ /// 错误信息
+ ///
+ public string? ErrorMessage { get; set; }
+
+ ///
+ /// 是否需要重试(发货失败时为true)
+ ///
+ public bool NeedRetry { get; set; }
+}