feat: 实现微信小程序发货信息录入功能
- IWechatService 添加 UploadShippingInfoAsync 接口 - WechatService 实现调用微信 upload_shipping_info API - PaymentNotifyService 支付成功后自动调用发货接口 - 发货失败时保存到 Redis 等待重试(3天过期) - 添加 WechatShippingRequest/WechatShippingResult 模型
This commit is contained in:
parent
113247a1e3
commit
27613ab5b2
|
|
@ -30,10 +30,6 @@
|
|||
|
||||
</view>
|
||||
|
||||
<view class="" style="width: 100%; margin: 44rpx 32rpx;">
|
||||
<image :src="$img1('my/ic_alipay.png')" style="width: 320rpx; height: 96rpx;" mode=""></image>
|
||||
</view>
|
||||
|
||||
<view class="center"
|
||||
style="width: 686rpx; height: 92rpx; margin: 46rpx auto 0; background-color: #333333; border-radius: 16rpx;"
|
||||
@click="pay" :class="{ 'btn-active': isPaying }">
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@ public interface IWechatService
|
|||
/// <param name="width">二维码宽度,默认430</param>
|
||||
/// <returns>小程序码图片字节数组,失败返回null</returns>
|
||||
Task<byte[]?> GetWxaCodeUnlimitAsync(string scene, string? page = null, int width = 430);
|
||||
|
||||
/// <summary>
|
||||
/// 上传微信小程序发货信息
|
||||
/// 调用微信API upload_shipping_info 将订单标记为已发货
|
||||
/// </summary>
|
||||
/// <param name="request">发货信息请求</param>
|
||||
/// <returns>发货结果</returns>
|
||||
Task<WechatShippingResult> UploadShippingInfoAsync(WechatShippingRequest request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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<PaymentNotifyService> _logger;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -51,6 +54,8 @@ public class PaymentNotifyService : IPaymentNotifyService
|
|||
IWechatPayConfigService wechatPayConfigService,
|
||||
IPaymentService paymentService,
|
||||
ILotteryEngine lotteryEngine,
|
||||
IWechatService wechatService,
|
||||
IRedisService redisService,
|
||||
ILogger<PaymentNotifyService> 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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传微信发货信息
|
||||
/// 支付成功后调用微信接口将订单标记为已发货
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存发货重试信息到Redis
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -947,4 +947,201 @@ public class WechatService : IWechatService
|
|||
var random = new Random().Next(1000, 9999);
|
||||
return $"{prefix}{merchantPrefix}{projectPrefix}{payType}{timestamp}{random}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传微信小程序发货信息
|
||||
/// 调用微信API upload_shipping_info 将订单标记为已发货
|
||||
/// </summary>
|
||||
public async Task<WechatShippingResult> 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据订单号获取商户配置
|
||||
/// </summary>
|
||||
private async Task<WechatPayMerchantConfig?> GetMerchantConfigByOrderNoAsync(string orderNo)
|
||||
{
|
||||
// 直接使用现有的 GetMerchantConfigAsync 方法获取默认商户配置
|
||||
// 该方法已经从数据库读取配置并支持缓存
|
||||
return await GetMerchantConfigAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认商品描述
|
||||
/// </summary>
|
||||
private static string GetDefaultItemDesc(string orderNo)
|
||||
{
|
||||
// 发货订单(FH_开头)使用不同的描述
|
||||
if (orderNo.StartsWith("FH_", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "本单购买的商品正在打包,请联系客服获取物流信息";
|
||||
}
|
||||
return "本单购买商品已发放至[小程序盒柜]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否需要重试
|
||||
/// </summary>
|
||||
private static bool ShouldRetry(int errCode)
|
||||
{
|
||||
// 以下错误码可以重试
|
||||
// 40001: access_token无效(可能过期)
|
||||
// -1: 系统繁忙
|
||||
return errCode switch
|
||||
{
|
||||
40001 => true, // access_token无效
|
||||
-1 => true, // 系统繁忙
|
||||
42001 => true, // access_token过期
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,8 +288,10 @@ public class ServiceModule : Module
|
|||
var wechatPayConfigService = c.Resolve<IWechatPayConfigService>();
|
||||
var paymentService = c.Resolve<IPaymentService>();
|
||||
var lotteryEngine = c.Resolve<ILotteryEngine>();
|
||||
var wechatService = c.Resolve<IWechatService>();
|
||||
var redisService = c.Resolve<IRedisService>();
|
||||
var logger = c.Resolve<ILogger<PaymentNotifyService>>();
|
||||
return new PaymentNotifyService(dbContext, wechatPayService, wechatPayV3Service, wechatPayConfigService, paymentService, lotteryEngine, logger);
|
||||
return new PaymentNotifyService(dbContext, wechatPayService, wechatPayV3Service, wechatPayConfigService, paymentService, lotteryEngine, wechatService, redisService, logger);
|
||||
}).As<IPaymentNotifyService>().InstancePerLifetimeScope();
|
||||
|
||||
// 注册充值服务
|
||||
|
|
|
|||
|
|
@ -46,3 +46,66 @@ public class WechatMobileResult
|
|||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序发货信息请求
|
||||
/// </summary>
|
||||
public class WechatShippingRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商户订单号
|
||||
/// </summary>
|
||||
public string OrderNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 商户号(可选,不传则根据订单号自动匹配)
|
||||
/// </summary>
|
||||
public string? MchId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 小程序AppId(可选,不传则使用默认配置)
|
||||
/// </summary>
|
||||
public string? AppId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品描述(可选,默认为"本单购买商品已发放至[小程序盒柜]")
|
||||
/// </summary>
|
||||
public string? ItemDesc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 物流类型:1=实体物流,2=同城配送,3=虚拟商品,4=用户自提
|
||||
/// 默认为4(虚拟商品场景)
|
||||
/// </summary>
|
||||
public int LogisticsType { get; set; } = 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序发货信息结果
|
||||
/// </summary>
|
||||
public class WechatShippingResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误码(0表示成功)
|
||||
/// </summary>
|
||||
public int ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要重试(发货失败时为true)
|
||||
/// </summary>
|
||||
public bool NeedRetry { get; set; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user