feat(order): downstream createOrderOne API from order DB
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e642f089ba
commit
eac7b84f6a
@ -0,0 +1,35 @@
|
||||
using Oms.Application.Dtos.Orders;
|
||||
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 下游 createOrderOne 请求体预览(由订单表组装,不含 AppSecret)。
|
||||
/// </summary>
|
||||
public class OrderDownstreamCreateOneBodyOutput
|
||||
{
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public string? ServiceNo { get; set; }
|
||||
|
||||
public string SupplierCode { get; set; } = string.Empty;
|
||||
|
||||
public OrderParcelCreateInput Body { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下游 createOrderOne 转发结果。
|
||||
/// </summary>
|
||||
public class OrderDownstreamForwardResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string SupplierCode { get; set; } = string.Empty;
|
||||
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public string RequestJson { get; set; } = string.Empty;
|
||||
|
||||
public int? StatusCode { get; set; }
|
||||
|
||||
public string ResponseBody { get; set; } = string.Empty;
|
||||
}
|
||||
24
src/Oms.Application/Integrations/Itfx/IItfxForwardClient.cs
Normal file
24
src/Oms.Application/Integrations/Itfx/IItfxForwardClient.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Oms.Application.Integrations.Itfx;
|
||||
|
||||
public sealed class ItfxForwardResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
public string RequestJson { get; set; } = string.Empty;
|
||||
|
||||
public int? StatusCode { get; set; }
|
||||
|
||||
public string ResponseBody { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public interface IItfxForwardClient
|
||||
{
|
||||
Task<ItfxForwardResult> ForwardCreateOrderOneAsync(
|
||||
string supplierCode,
|
||||
string appKey,
|
||||
string appSecret,
|
||||
object body,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -3,6 +3,8 @@ namespace Oms.Application.Integrations.Itfx;
|
||||
public sealed class ItfxAppMetadata
|
||||
{
|
||||
public string AppKey { get; set; } = string.Empty;
|
||||
|
||||
public string AppSecret { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class ItfxServiceProviderMetadata
|
||||
|
||||
72
src/Oms.Application/Integrations/Itfx/ItfxForwardClient.cs
Normal file
72
src/Oms.Application/Integrations/Itfx/ItfxForwardClient.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Fake.DependencyInjection;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Oms.Application.Integrations.Itfx;
|
||||
|
||||
internal sealed class ItfxForwardClient(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IOptions<ItfxGatewayOptions> options,
|
||||
IHttpContextAccessor httpContextAccessor) : IItfxForwardClient, IScopedDependency
|
||||
{
|
||||
public const string HttpClientName = nameof(ItfxForwardClient);
|
||||
public const string CreateOrderOneStandardCode = "createOrderOne";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public async Task<ItfxForwardResult> ForwardCreateOrderOneAsync(
|
||||
string supplierCode,
|
||||
string appKey,
|
||||
string appSecret,
|
||||
object body,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var settings = options.Value;
|
||||
if (string.IsNullOrWhiteSpace(settings.BaseUrl))
|
||||
{
|
||||
throw new InvalidOperationException("Itfx BaseUrl not configured");
|
||||
}
|
||||
|
||||
var root = $"{settings.BaseUrl.Trim().TrimEnd('/')}/InterfaceForward/ForwardWithSubscribe";
|
||||
var url =
|
||||
$"{root}?standardCode={CreateOrderOneStandardCode}&supplierCode={Uri.EscapeDataString(supplierCode)}";
|
||||
var requestJson = JsonSerializer.Serialize(body, JsonOptions);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
request.Headers.TryAddWithoutValidation("appkey", appKey);
|
||||
request.Headers.TryAddWithoutValidation("appSecret", appSecret);
|
||||
request.Headers.TryAddWithoutValidation("followName", "0");
|
||||
ForwardAuthorization(request);
|
||||
request.Content = JsonContent.Create(body, options: JsonOptions);
|
||||
|
||||
var client = httpClientFactory.CreateClient(HttpClientName);
|
||||
client.Timeout = TimeSpan.FromSeconds(Math.Max(30, settings.TimeoutSeconds));
|
||||
|
||||
using var response = await client.SendAsync(request, cancellationToken);
|
||||
var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||
return new ItfxForwardResult
|
||||
{
|
||||
Success = response.IsSuccessStatusCode,
|
||||
Url = url,
|
||||
RequestJson = requestJson,
|
||||
StatusCode = (int)response.StatusCode,
|
||||
ResponseBody = responseBody
|
||||
};
|
||||
}
|
||||
|
||||
private void ForwardAuthorization(HttpRequestMessage request)
|
||||
{
|
||||
var authorization = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(authorization))
|
||||
{
|
||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,9 +24,17 @@ internal sealed class ItfxMetadataClient(
|
||||
GetAsync<ItfxAppMetadata>($"/App/GetInfo?id={appId}", payload =>
|
||||
{
|
||||
var appKey = ReadString(payload, "appKey", "AppKey");
|
||||
return string.IsNullOrWhiteSpace(appKey)
|
||||
? null
|
||||
: new ItfxAppMetadata { AppKey = appKey.Trim() };
|
||||
var appSecret = ReadString(payload, "appSecret", "AppSecret", "secret", "Secret");
|
||||
if (string.IsNullOrWhiteSpace(appKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ItfxAppMetadata
|
||||
{
|
||||
AppKey = appKey.Trim(),
|
||||
AppSecret = appSecret?.Trim() ?? string.Empty
|
||||
};
|
||||
}, cancellationToken);
|
||||
|
||||
public Task<ItfxServiceProviderMetadata?> GetServiceProviderAsync(
|
||||
|
||||
@ -9,7 +9,9 @@ public static class ItfxServiceExtensions
|
||||
{
|
||||
services.Configure<ItfxGatewayOptions>(configuration.GetSection(ItfxGatewayOptions.SectionName));
|
||||
services.AddHttpClient(ItfxMetadataClient.HttpClientName);
|
||||
services.AddHttpClient(ItfxForwardClient.HttpClientName);
|
||||
services.AddScoped<IItfxMetadataClient, ItfxMetadataClient>();
|
||||
services.AddScoped<IItfxForwardClient, ItfxForwardClient>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
using Fake.Application;
|
||||
using Fake.Domain.Exceptions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Application.Integrations.Itfx;
|
||||
using Oms.Application.Shared;
|
||||
|
||||
namespace Oms.Application.Services.Orders;
|
||||
|
||||
public partial class OrderService
|
||||
{
|
||||
private OrderDownstreamCredentialResolver OrderDownstreamCredentialResolver =>
|
||||
LazyServiceProvider.GetRequiredService<OrderDownstreamCredentialResolver>();
|
||||
|
||||
private IItfxForwardClient ItfxForwardClient =>
|
||||
LazyServiceProvider.GetRequiredService<IItfxForwardClient>();
|
||||
|
||||
/// <summary>
|
||||
/// 按订单 Id 从数据库组装 createOrderOne 请求体(预览,不调用下游)。
|
||||
/// </summary>
|
||||
[HttpGet("/order/downstream-create-one-body")]
|
||||
public async Task<OrderDownstreamCreateOneBodyOutput> GetDownstreamCreateOneBodyAsync([FromQuery] int id)
|
||||
{
|
||||
var (order, body) = await LoadDownstreamCreateOneContextAsync(id);
|
||||
var creds = await OrderDownstreamCredentialResolver.ResolveAsync(order);
|
||||
return new OrderDownstreamCreateOneBodyOutput
|
||||
{
|
||||
OrderId = order.Id,
|
||||
ServiceNo = order.ServiceNo,
|
||||
SupplierCode = creds.SupplierCode,
|
||||
Body = body
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按订单 Id 组装 createOrderOne 并转发至 itfx 下游。
|
||||
/// </summary>
|
||||
[HttpPost("/order/downstream-forward-create-one")]
|
||||
public async Task<OrderDownstreamForwardResult> DownstreamForwardCreateOneAsync([FromBody] OrderActionInput input)
|
||||
{
|
||||
var (order, body) = await LoadDownstreamCreateOneContextAsync(input.Id);
|
||||
if (string.IsNullOrWhiteSpace(order.ServiceNo))
|
||||
{
|
||||
throw new BusinessException("订单运单号为空,无法预报下游");
|
||||
}
|
||||
|
||||
var creds = await OrderDownstreamCredentialResolver.ResolveAsync(order);
|
||||
var forward = await ItfxForwardClient.ForwardCreateOrderOneAsync(
|
||||
creds.SupplierCode,
|
||||
creds.AppKey,
|
||||
creds.AppSecret,
|
||||
body);
|
||||
|
||||
return new OrderDownstreamForwardResult
|
||||
{
|
||||
Success = forward.Success,
|
||||
SupplierCode = creds.SupplierCode,
|
||||
Url = forward.Url,
|
||||
RequestJson = forward.RequestJson,
|
||||
StatusCode = forward.StatusCode,
|
||||
ResponseBody = forward.ResponseBody
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(Domain.OrdersAggregate.Entities.Order order, OrderParcelCreateInput body)> LoadDownstreamCreateOneContextAsync(
|
||||
int orderId)
|
||||
{
|
||||
var order = await GetOrderOrThrowAsync(orderId);
|
||||
var orderIds = new[] { order.Id };
|
||||
var senders = await OrderRepository.GetSendersByOrderIdsAsync(orderIds);
|
||||
var recipients = await OrderRepository.GetRecipientsByOrderIdsAsync(orderIds);
|
||||
var declarations = await OrderRepository.GetDeclarationsByOrderIdsAsync(orderIds);
|
||||
var weights = await OrderRepository.GetWeightsByOrderIdsAsync(orderIds);
|
||||
|
||||
var body = await OrderDownstreamCreateOneBodyBuilder.BuildAsync(
|
||||
order,
|
||||
senders.FirstOrDefault(),
|
||||
recipients.FirstOrDefault(),
|
||||
declarations,
|
||||
weights,
|
||||
CountryRepository);
|
||||
|
||||
return (order, body);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 从订单表及关联子表组装 createOrderOne 标准请求体。
|
||||
/// </summary>
|
||||
public static class OrderDownstreamCreateOneBodyBuilder
|
||||
{
|
||||
public static async Task<OrderParcelCreateInput> BuildAsync(
|
||||
Order order,
|
||||
OrderSender? sender,
|
||||
OrderRecipient? recipient,
|
||||
IReadOnlyList<OrderDeclaration> declarations,
|
||||
IReadOnlyList<OrderWeight> weights,
|
||||
ICountryRepository countryRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var countryIds = new List<int>();
|
||||
if (sender?.Country is > 0)
|
||||
{
|
||||
countryIds.Add(sender.Country.Value);
|
||||
}
|
||||
|
||||
if (recipient?.Country is > 0)
|
||||
{
|
||||
countryIds.Add(recipient.Country.Value);
|
||||
}
|
||||
|
||||
var countries = countryIds.Count == 0
|
||||
? []
|
||||
: await countryRepository.GetListAsync(x => countryIds.Contains(x.Id));
|
||||
var idToCode = countries.ToDictionary(x => x.Id, x => x.CountryCode);
|
||||
|
||||
var forecast = weights.FirstOrDefault(x =>
|
||||
string.Equals(x.Type, OrderWeightTypeCodes.Forecast, StringComparison.OrdinalIgnoreCase))
|
||||
?? weights.FirstOrDefault();
|
||||
|
||||
return new OrderParcelCreateInput
|
||||
{
|
||||
RequestId = order.RequestId,
|
||||
ClientNo = order.ClientNo,
|
||||
TrackNo = order.TrackNo,
|
||||
ServiceNo = order.ServiceNo,
|
||||
ProductCode = order.ProductCode,
|
||||
ChannelCode = order.ChannelCode,
|
||||
CargoAttribute = order.CargoAttribute,
|
||||
ExportCustomsMode = order.ExportCustomsMode,
|
||||
WarehouseId = order.WarehouseId,
|
||||
CustomerId = order.CustomerId,
|
||||
Length = forecast?.Length,
|
||||
Width = forecast?.Width,
|
||||
Height = forecast?.Height,
|
||||
ActualWeight = forecast?.ActualWeight,
|
||||
VolumetricWeight = forecast?.VolumetricWeight,
|
||||
ChargeableWeight = forecast?.ChargeableWeight,
|
||||
Cubic = forecast?.Cubic,
|
||||
IsCod = order.IsCod > 0,
|
||||
CodCurrency = order.CodCurrency,
|
||||
CodSum = order.CodSum,
|
||||
DeclaredCurrency = order.DeclaredCurrency,
|
||||
DeclaredSum = order.DeclaredSum,
|
||||
Quantity = order.Quantity,
|
||||
Receiver = MapReceiver(recipient, order.ConsigneeCountry, idToCode),
|
||||
Sender = MapSender(sender, idToCode),
|
||||
Items = declarations.Select(MapItem).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private static string? ResolveCountryIso(int? countryId, string? orderConsigneeCountry, IReadOnlyDictionary<int, string> idToCode)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(orderConsigneeCountry))
|
||||
{
|
||||
var trimmed = orderConsigneeCountry.Trim();
|
||||
if (trimmed.Length == 2)
|
||||
{
|
||||
return trimmed.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
if (countryId is > 0 && idToCode.TryGetValue(countryId.Value, out var code) && !string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
return code.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static OrderParcelReceiverInput? MapReceiver(
|
||||
OrderRecipient? recipient,
|
||||
string? orderConsigneeCountry,
|
||||
IReadOnlyDictionary<int, string> idToCode)
|
||||
{
|
||||
if (recipient == null)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(orderConsigneeCountry)
|
||||
? null
|
||||
: new OrderParcelReceiverInput { CountryCode = orderConsigneeCountry.Trim().ToUpperInvariant() };
|
||||
}
|
||||
|
||||
return new OrderParcelReceiverInput
|
||||
{
|
||||
RecipientsName = recipient.RecipientsName,
|
||||
CompanyName = recipient.CompanyName,
|
||||
Phone = recipient.Phone,
|
||||
TelePhone = recipient.TelePhone,
|
||||
Email = recipient.Email,
|
||||
CountryCode = ResolveCountryIso(recipient.Country, orderConsigneeCountry, idToCode),
|
||||
Province = recipient.Province,
|
||||
City = recipient.City,
|
||||
StreetNumber = recipient.StreetNumber,
|
||||
StreetAddress1 = recipient.StreetAddress1,
|
||||
StreetAddress2 = recipient.StreetAddress2,
|
||||
StreetAddress3 = recipient.StreetAddress3,
|
||||
ZipCode = recipient.ZipCode,
|
||||
TaxType = recipient.TaxType,
|
||||
TaxNo = recipient.TaxNo,
|
||||
CertificateType = recipient.CertificateType,
|
||||
CertificateNo = recipient.CertificateNo,
|
||||
Eori = recipient.Eori,
|
||||
Vat = recipient.Vat
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderParcelSenderInput? MapSender(OrderSender? sender, IReadOnlyDictionary<int, string> idToCode)
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OrderParcelSenderInput
|
||||
{
|
||||
SenderName = sender.SenderName,
|
||||
Company = sender.Company,
|
||||
Phone = sender.Phone,
|
||||
TelePhone = sender.TelePhone,
|
||||
Email = sender.Email,
|
||||
CountryCode = ResolveCountryIso(sender.Country, null, idToCode),
|
||||
Province = sender.Province,
|
||||
City = sender.City,
|
||||
ZipCode = sender.ZipCode,
|
||||
StreetAddress1 = sender.StreetAddress1,
|
||||
StreetAddress2 = sender.StreetAddress2,
|
||||
StreetAddress3 = sender.StreetAddress3,
|
||||
HouseNumber = sender.HouseNumber
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderParcelItemInput MapItem(OrderDeclaration x) =>
|
||||
new()
|
||||
{
|
||||
CnName = x.CnName,
|
||||
EnName = x.EnName,
|
||||
Sku = x.Sku,
|
||||
Unit = x.Unit,
|
||||
UnitPrice = x.UnitPrice,
|
||||
UnitWeight = x.UnitWeight,
|
||||
Quantity = x.Quantity,
|
||||
Amount = x.Amount,
|
||||
Weight = x.Weight,
|
||||
ImHsCode = x.ImHsCode,
|
||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
BrandType = x.BrandType,
|
||||
IsMagnetic = x.IsMagnetic > 0,
|
||||
Distribution = x.Distribution,
|
||||
Material = x.Material,
|
||||
EnMaterial = x.EnMaterial,
|
||||
Purpose = x.Purpose,
|
||||
Attributes = x.Attributes,
|
||||
OriginCountry = x.OriginCountry,
|
||||
SalesLink = x.SalesLink,
|
||||
Model = x.Model,
|
||||
Specifications = x.Specifications,
|
||||
Comment = x.Comment
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Fake.Application;
|
||||
using Oms.Application.Integrations.Itfx;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
public sealed class OrderDownstreamCredentials
|
||||
{
|
||||
public string SupplierCode { get; set; } = string.Empty;
|
||||
|
||||
public string AppKey { get; set; } = string.Empty;
|
||||
|
||||
public string AppSecret { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析订单下游 itfx 凭证(优先订单绑定字段,回退渠道分组)。
|
||||
/// </summary>
|
||||
public class OrderDownstreamCredentialResolver(
|
||||
IServiceChannelRepository serviceChannelRepository,
|
||||
IServiceChannelGroupRepository serviceChannelGroupRepository,
|
||||
IItfxMetadataClient itfxMetadataClient) : IScopedDependency
|
||||
{
|
||||
public async Task<OrderDownstreamCredentials> ResolveAsync(Order order, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
|
||||
&& !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
|
||||
&& order.DownstreamAppId is > 0)
|
||||
{
|
||||
var app = await itfxMetadataClient.GetAppAsync(order.DownstreamAppId.Value, cancellationToken);
|
||||
var appSecret = app?.AppSecret?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(appSecret))
|
||||
{
|
||||
throw new BusinessException("应用 AppSecret 未配置");
|
||||
}
|
||||
|
||||
return new OrderDownstreamCredentials
|
||||
{
|
||||
SupplierCode = order.DownstreamSupplierCode.Trim(),
|
||||
AppKey = order.DownstreamAppKey.Trim(),
|
||||
AppSecret = appSecret
|
||||
};
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(order.ChannelCode))
|
||||
{
|
||||
throw new BusinessException("订单未绑定服务渠道,无法预报下游");
|
||||
}
|
||||
|
||||
var channel = await serviceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim(), cancellationToken);
|
||||
if (channel == null)
|
||||
{
|
||||
throw new BusinessException($"未找到渠道「{order.ChannelCode}」");
|
||||
}
|
||||
|
||||
if (!channel.GroupId.HasValue || channel.GroupId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException($"渠道「{order.ChannelCode}」未关联服务商分组");
|
||||
}
|
||||
|
||||
var group = await serviceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == channel.GroupId.Value, cancellationToken);
|
||||
if (group == null)
|
||||
{
|
||||
throw new BusinessException("服务商分组不存在或已删除");
|
||||
}
|
||||
|
||||
if (group.AppId <= 0)
|
||||
{
|
||||
throw new BusinessException($"服务商「{group.Name}」未配置应用");
|
||||
}
|
||||
|
||||
if (group.ApiServiceProviderId <= 0)
|
||||
{
|
||||
throw new BusinessException($"服务商「{group.Name}」未配置 API 服务商");
|
||||
}
|
||||
|
||||
var appInfo = await itfxMetadataClient.GetAppAsync(group.AppId, cancellationToken);
|
||||
if (appInfo == null || string.IsNullOrWhiteSpace(appInfo.AppKey) || string.IsNullOrWhiteSpace(appInfo.AppSecret))
|
||||
{
|
||||
throw new BusinessException("应用 AppKey / AppSecret 未配置");
|
||||
}
|
||||
|
||||
var sp = await itfxMetadataClient.GetServiceProviderAsync(group.ApiServiceProviderId, cancellationToken);
|
||||
if (sp == null || string.IsNullOrWhiteSpace(sp.Code))
|
||||
{
|
||||
throw new BusinessException("API 服务商编码(supplierCode)未配置");
|
||||
}
|
||||
|
||||
return new OrderDownstreamCredentials
|
||||
{
|
||||
SupplierCode = sp.Code.Trim(),
|
||||
AppKey = appInfo.AppKey.Trim(),
|
||||
AppSecret = appInfo.AppSecret.Trim()
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user