Fix downstream forecast: pass auth + cache itfx app secret
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
28d14034bd
commit
fffecb5964
@ -1,7 +1,7 @@
|
|||||||
namespace Oms.Application.Dtos.Channels;
|
namespace Oms.Application.Dtos.Channels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 服务渠道下游对接元数据(发布快照固化,不含 AppSecret)。
|
/// 服务渠道下游对接元数据(发布快照固化)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServiceChannelDownstreamSnapshotDto
|
public class ServiceChannelDownstreamSnapshotDto
|
||||||
{
|
{
|
||||||
@ -25,6 +25,11 @@ public class ServiceChannelDownstreamSnapshotDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? AppKey { get; set; }
|
public string? AppKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// itfx 应用 AppSecret。
|
||||||
|
/// </summary>
|
||||||
|
public string? AppSecret { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// itfx 应用名称。
|
/// itfx 应用名称。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Oms.Application.Integrations.Itfx;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 让后台任务也能把当前用户 Bearer 透传给 itfx 元数据/转发请求。
|
||||||
|
/// </summary>
|
||||||
|
internal static class ItfxAuthorizationContext
|
||||||
|
{
|
||||||
|
private static readonly AsyncLocal<string?> AuthorizationValue = new();
|
||||||
|
|
||||||
|
public static string? Authorization
|
||||||
|
{
|
||||||
|
get => AuthorizationValue.Value;
|
||||||
|
set => AuthorizationValue.Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -63,7 +63,9 @@ internal sealed class ItfxForwardClient(
|
|||||||
|
|
||||||
private void ForwardAuthorization(HttpRequestMessage request)
|
private void ForwardAuthorization(HttpRequestMessage request)
|
||||||
{
|
{
|
||||||
var authorization = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
|
var authorization =
|
||||||
|
ItfxAuthorizationContext.Authorization
|
||||||
|
?? httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
|
||||||
if (!string.IsNullOrWhiteSpace(authorization))
|
if (!string.IsNullOrWhiteSpace(authorization))
|
||||||
{
|
{
|
||||||
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
||||||
|
|||||||
@ -102,7 +102,9 @@ internal sealed class ItfxMetadataClient(
|
|||||||
|
|
||||||
private void ForwardAuthorization(HttpClient client)
|
private void ForwardAuthorization(HttpClient client)
|
||||||
{
|
{
|
||||||
var authorization = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
|
var authorization =
|
||||||
|
ItfxAuthorizationContext.Authorization
|
||||||
|
?? httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
|
||||||
if (!string.IsNullOrWhiteSpace(authorization))
|
if (!string.IsNullOrWhiteSpace(authorization))
|
||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
||||||
|
|||||||
@ -20,6 +20,11 @@ public class OrderBatchJobIntegrationEvent : IntegrationEvent
|
|||||||
|
|
||||||
public string? PayloadJson { get; set; }
|
public string? PayloadJson { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 调用第三方轨迹网关所需的鉴权(Bearer 等)。
|
||||||
|
/// </summary>
|
||||||
|
public string? Authorization { get; set; }
|
||||||
|
|
||||||
public int OperatorUserId { get; set; }
|
public int OperatorUserId { get; set; }
|
||||||
|
|
||||||
public string MessageId { get; set; } = string.Empty;
|
public string MessageId { get; set; } = string.Empty;
|
||||||
@ -33,6 +38,7 @@ public class OrderBatchJobIntegrationEvent : IntegrationEvent
|
|||||||
OrderId = message.OrderId,
|
OrderId = message.OrderId,
|
||||||
CustomerId = message.CustomerId,
|
CustomerId = message.CustomerId,
|
||||||
PayloadJson = message.PayloadJson,
|
PayloadJson = message.PayloadJson,
|
||||||
|
Authorization = message.Authorization,
|
||||||
OperatorUserId = message.OperatorUserId,
|
OperatorUserId = message.OperatorUserId,
|
||||||
MessageId = message.MessageId,
|
MessageId = message.MessageId,
|
||||||
};
|
};
|
||||||
@ -46,6 +52,7 @@ public class OrderBatchJobIntegrationEvent : IntegrationEvent
|
|||||||
OrderId = OrderId,
|
OrderId = OrderId,
|
||||||
CustomerId = CustomerId,
|
CustomerId = CustomerId,
|
||||||
PayloadJson = PayloadJson,
|
PayloadJson = PayloadJson,
|
||||||
|
Authorization = Authorization,
|
||||||
OperatorUserId = OperatorUserId,
|
OperatorUserId = OperatorUserId,
|
||||||
MessageId = MessageId,
|
MessageId = MessageId,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,11 @@ public class OrderBatchJobMessage
|
|||||||
|
|
||||||
public string? PayloadJson { get; set; }
|
public string? PayloadJson { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 调用第三方轨迹网关所需的鉴权(Bearer 等)。
|
||||||
|
/// </summary>
|
||||||
|
public string? Authorization { get; set; }
|
||||||
|
|
||||||
public int OperatorUserId { get; set; }
|
public int OperatorUserId { get; set; }
|
||||||
|
|
||||||
public string MessageId { get; set; } = string.Empty;
|
public string MessageId { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -83,6 +83,12 @@ public sealed class OrderBatchActionExecutor(
|
|||||||
request = await smallBagTrackUpsertRequestBuilder.BuildAsync(order, cancellationToken: cancellationToken);
|
request = await smallBagTrackUpsertRequestBuilder.BuildAsync(order, cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Authorization)
|
||||||
|
&& !string.IsNullOrWhiteSpace(message.Authorization))
|
||||||
|
{
|
||||||
|
request.Authorization = message.Authorization.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
var trackResult = await smallBagTrackOrderClient.UpsertDebugAsync(request, cancellationToken);
|
var trackResult = await smallBagTrackOrderClient.UpsertDebugAsync(request, cancellationToken);
|
||||||
var serviceNo = request.OrderNo?.Trim();
|
var serviceNo = request.OrderNo?.Trim();
|
||||||
|
|
||||||
@ -124,61 +130,74 @@ public sealed class OrderBatchActionExecutor(
|
|||||||
OrderBatchJobMessage message,
|
OrderBatchJobMessage message,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
OrderParcelCreateInput body;
|
var authorization = message.Authorization;
|
||||||
Order order;
|
if (!string.IsNullOrWhiteSpace(authorization))
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
|
|
||||||
{
|
{
|
||||||
body = JsonConvert.DeserializeObject<OrderParcelCreateInput>(message.PayloadJson!)
|
ItfxAuthorizationContext.Authorization = authorization.Trim();
|
||||||
?? throw new BusinessException("下游预报消息体无效");
|
|
||||||
|
|
||||||
// 凭证解析仍需订单上的渠道/下游绑定字段;有 payload 时只查主表一次
|
|
||||||
var existing = await orderRepository.FirstOrDefaultAsync(x => x.Id == message.OrderId, cancellationToken);
|
|
||||||
if (existing == null)
|
|
||||||
{
|
|
||||||
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
order = existing;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var bundles = await orderRepository.GetDownstreamCreateOneBundlesAsync([message.OrderId], cancellationToken);
|
OrderParcelCreateInput body;
|
||||||
if (!bundles.TryGetValue(message.OrderId, out var bundle))
|
Order order;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(message.PayloadJson))
|
||||||
{
|
{
|
||||||
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
|
body = JsonConvert.DeserializeObject<OrderParcelCreateInput>(message.PayloadJson!)
|
||||||
|
?? throw new BusinessException("下游预报消息体无效");
|
||||||
|
|
||||||
|
// 凭证解析仍需订单上的渠道/下游绑定字段;有 payload 时只查主表一次
|
||||||
|
var existing = await orderRepository.FirstOrDefaultAsync(x => x.Id == message.OrderId, cancellationToken);
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
order = existing;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var bundles = await orderRepository.GetDownstreamCreateOneBundlesAsync([message.OrderId], cancellationToken);
|
||||||
|
if (!bundles.TryGetValue(message.OrderId, out var bundle))
|
||||||
|
{
|
||||||
|
return OrderBatchActionExecuteResult.BusinessFail("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
order = bundle.Order;
|
||||||
|
body = await OrderDownstreamCreateOneBodyBuilder.BuildAsync(
|
||||||
|
bundle.Order,
|
||||||
|
bundle.Sender,
|
||||||
|
bundle.Recipient,
|
||||||
|
bundle.Declarations,
|
||||||
|
bundle.Weights,
|
||||||
|
countryRepository,
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
order = bundle.Order;
|
if (string.IsNullOrWhiteSpace(order.ServiceNo))
|
||||||
body = await OrderDownstreamCreateOneBodyBuilder.BuildAsync(
|
{
|
||||||
bundle.Order,
|
return OrderBatchActionExecuteResult.BusinessFail("订单运单号为空,无法预报下游");
|
||||||
bundle.Sender,
|
}
|
||||||
bundle.Recipient,
|
|
||||||
bundle.Declarations,
|
var creds = await downstreamCredentialResolver.ResolveAsync(order, cancellationToken);
|
||||||
bundle.Weights,
|
var forward = await itfxForwardClient.ForwardCreateOrderOneAsync(
|
||||||
countryRepository,
|
creds.SupplierCode,
|
||||||
|
creds.AppKey,
|
||||||
|
creds.AppSecret,
|
||||||
|
body,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(order.ServiceNo))
|
if (!forward.Success)
|
||||||
|
{
|
||||||
|
return OrderBatchActionExecuteResult.BusinessFail(
|
||||||
|
$"下游预报失败: HTTP {forward.StatusCode} {forward.ResponseBody}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return OrderBatchActionExecuteResult.Ok();
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
return OrderBatchActionExecuteResult.BusinessFail("订单运单号为空,无法预报下游");
|
ItfxAuthorizationContext.Authorization = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var creds = await downstreamCredentialResolver.ResolveAsync(order, cancellationToken);
|
|
||||||
var forward = await itfxForwardClient.ForwardCreateOrderOneAsync(
|
|
||||||
creds.SupplierCode,
|
|
||||||
creds.AppKey,
|
|
||||||
creds.AppSecret,
|
|
||||||
body,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
if (!forward.Success)
|
|
||||||
{
|
|
||||||
return OrderBatchActionExecuteResult.BusinessFail(
|
|
||||||
$"下游预报失败: HTTP {forward.StatusCode} {forward.ResponseBody}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return OrderBatchActionExecuteResult.Ok();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ public sealed class OrderBatchJobCoordinator(
|
|||||||
public async Task<OrderBatchJobOutput> CreateAndEnqueueAsync(
|
public async Task<OrderBatchJobOutput> CreateAndEnqueueAsync(
|
||||||
OrderBatchJobCreateInput input,
|
OrderBatchJobCreateInput input,
|
||||||
int operatorUserId,
|
int operatorUserId,
|
||||||
|
string? authorization = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!publisher.IsEnabled)
|
if (!publisher.IsEnabled)
|
||||||
@ -142,6 +143,7 @@ public sealed class OrderBatchJobCoordinator(
|
|||||||
OrderId = item.OrderId,
|
OrderId = item.OrderId,
|
||||||
CustomerId = order?.CustomerId,
|
CustomerId = order?.CustomerId,
|
||||||
PayloadJson = item.PayloadJson,
|
PayloadJson = item.PayloadJson,
|
||||||
|
Authorization = authorization,
|
||||||
OperatorUserId = operatorUserId,
|
OperatorUserId = operatorUserId,
|
||||||
MessageId = item.MessageId ?? $"{job.Id}:{item.OrderId}:{action}",
|
MessageId = item.MessageId ?? $"{job.Id}:{item.OrderId}:{action}",
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.Security.Claims;
|
using Fake.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
using Oms.Application.Dtos.Shared;
|
using Oms.Application.Dtos.Shared;
|
||||||
@ -75,7 +76,18 @@ public partial class OrderService
|
|||||||
public Task<OrderBatchJobOutput> BatchTrackUpsertAsync([FromBody] OrderBatchJobCreateInput input)
|
public Task<OrderBatchJobOutput> BatchTrackUpsertAsync([FromBody] OrderBatchJobCreateInput input)
|
||||||
{
|
{
|
||||||
input.Action = OrderBatchActionCodes.TrackUpsertSmallBag;
|
input.Action = OrderBatchActionCodes.TrackUpsertSmallBag;
|
||||||
return BatchJobCoordinator.CreateAndEnqueueAsync(input, CurrentUser.GetId<int>());
|
var authorization = LazyServiceProvider
|
||||||
|
.GetRequiredService<IHttpContextAccessor>()
|
||||||
|
.HttpContext?
|
||||||
|
.Request
|
||||||
|
.Headers
|
||||||
|
.Authorization
|
||||||
|
.ToString();
|
||||||
|
|
||||||
|
return BatchJobCoordinator.CreateAndEnqueueAsync(
|
||||||
|
input,
|
||||||
|
CurrentUser.GetId<int>(),
|
||||||
|
string.IsNullOrWhiteSpace(authorization) ? null : authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -85,7 +97,18 @@ public partial class OrderService
|
|||||||
public Task<OrderBatchJobOutput> BatchDownstreamForwardAsync([FromBody] OrderBatchJobCreateInput input)
|
public Task<OrderBatchJobOutput> BatchDownstreamForwardAsync([FromBody] OrderBatchJobCreateInput input)
|
||||||
{
|
{
|
||||||
input.Action = OrderBatchActionCodes.DownstreamForwardCreateOne;
|
input.Action = OrderBatchActionCodes.DownstreamForwardCreateOne;
|
||||||
return BatchJobCoordinator.CreateAndEnqueueAsync(input, CurrentUser.GetId<int>());
|
var authorization = LazyServiceProvider
|
||||||
|
.GetRequiredService<IHttpContextAccessor>()
|
||||||
|
.HttpContext?
|
||||||
|
.Request
|
||||||
|
.Headers
|
||||||
|
.Authorization
|
||||||
|
.ToString();
|
||||||
|
|
||||||
|
return BatchJobCoordinator.CreateAndEnqueueAsync(
|
||||||
|
input,
|
||||||
|
CurrentUser.GetId<int>(),
|
||||||
|
string.IsNullOrWhiteSpace(authorization) ? null : authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<OrderBatchJobOutput> EnqueueOrderBatchAsync(string action, OrderBatchJobCreateInput input)
|
private Task<OrderBatchJobOutput> EnqueueOrderBatchAsync(string action, OrderBatchJobCreateInput input)
|
||||||
|
|||||||
@ -161,6 +161,11 @@ public class AllocationProviderSnapshotEnricher(
|
|||||||
snapshot.AppKey = app.AppKey;
|
snapshot.AppKey = app.AppKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(app?.AppSecret))
|
||||||
|
{
|
||||||
|
snapshot.AppSecret = app.AppSecret;
|
||||||
|
}
|
||||||
|
|
||||||
snapshot.AppName = NullIfEmpty(app?.Name);
|
snapshot.AppName = NullIfEmpty(app?.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using Fake.DependencyInjection;
|
using Fake.DependencyInjection;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
|
using Oms.Application.Dtos.Channels;
|
||||||
using Oms.Application.Integrations.Itfx;
|
using Oms.Application.Integrations.Itfx;
|
||||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||||
using Oms.Domain.OrdersAggregate.Entities;
|
using Oms.Domain.OrdersAggregate.Entities;
|
||||||
@ -25,6 +26,12 @@ public class OrderDownstreamCredentialResolver(
|
|||||||
{
|
{
|
||||||
public async Task<OrderDownstreamCredentials> ResolveAsync(Order order, CancellationToken cancellationToken = default)
|
public async Task<OrderDownstreamCredentials> ResolveAsync(Order order, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
var fromSnapshot = await TryResolveFromChannelRulesSnapshotAsync(order, cancellationToken);
|
||||||
|
if (fromSnapshot != null)
|
||||||
|
{
|
||||||
|
return fromSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
|
if (!string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
|
||||||
&& !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
|
&& !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
|
||||||
&& order.DownstreamAppId is > 0)
|
&& order.DownstreamAppId is > 0)
|
||||||
@ -95,4 +102,78 @@ public class OrderDownstreamCredentialResolver(
|
|||||||
AppSecret = appInfo.AppSecret.Trim()
|
AppSecret = appInfo.AppSecret.Trim()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<OrderDownstreamCredentials?> TryResolveFromChannelRulesSnapshotAsync(
|
||||||
|
Order order,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(order.ChannelCode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var channel = await serviceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim(), cancellationToken);
|
||||||
|
if (channel == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 规则快照里固化了 itfx 应用信息(含 AppSecret)。
|
||||||
|
const string rulesOperatorType = "rules";
|
||||||
|
var bindTime = order.CreateTime == default ? DateTime.Now : order.CreateTime;
|
||||||
|
var record = await serviceChannelRepository.GetEffectivePublishRecordAsync(
|
||||||
|
channel.Id,
|
||||||
|
rulesOperatorType,
|
||||||
|
bindTime,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
if (record == null || string.IsNullOrWhiteSpace(record.Content))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot = OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(record.Content);
|
||||||
|
var downstream = snapshot?.Downstream;
|
||||||
|
if (downstream == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单绑定的 AppId 与快照不一致时,直接回退旧逻辑。
|
||||||
|
if (order.DownstreamAppId is > 0
|
||||||
|
&& downstream.AppId is > 0
|
||||||
|
&& order.DownstreamAppId.Value != downstream.AppId.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var appSecret = downstream.AppSecret?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(appSecret))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var appKey = !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
|
||||||
|
? order.DownstreamAppKey.Trim()
|
||||||
|
: downstream.AppKey?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(appKey))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var supplierCode = !string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
|
||||||
|
? order.DownstreamSupplierCode.Trim()
|
||||||
|
: downstream.SupplierCode?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(supplierCode))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OrderDownstreamCredentials
|
||||||
|
{
|
||||||
|
SupplierCode = supplierCode,
|
||||||
|
AppKey = appKey,
|
||||||
|
AppSecret = appSecret
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user