|
|
|
|
using Furion.DependencyInjection;
|
|
|
|
|
using Furion.FriendlyException;
|
|
|
|
|
using Furion.RemoteRequest.Extensions;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Myshipping.Application.Entity;
|
|
|
|
|
using Myshipping.Application.Service.ExpressDelivery.Dto;
|
|
|
|
|
using Myshipping.Core;
|
|
|
|
|
using Myshipping.Core.Service;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
using StackExchange.Profiling.Internal;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Myshipping.Application
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 快递
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SFDeliverySend : IDeliverySend, ITransient
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Redis中用于保存访问顺丰API的Token的键名
|
|
|
|
|
/// </summary>
|
|
|
|
|
private const string SF_API_TOKEN_KEY = $"SfApiToken";
|
|
|
|
|
|
|
|
|
|
private readonly ISysCacheService cache;
|
|
|
|
|
private readonly ILogger<ExpressDeliveryService> logger;
|
|
|
|
|
|
|
|
|
|
public SFDeliverySend(ISysCacheService cache, ILogger<ExpressDeliveryService> logger)
|
|
|
|
|
{
|
|
|
|
|
this.cache = cache;
|
|
|
|
|
this.logger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region IDeliverySend接口实现
|
|
|
|
|
public async Task<string> CreateOrder(ExpressDeliveryOrder order)
|
|
|
|
|
{
|
|
|
|
|
SFCreateOrderDto sFSend = new SFCreateOrderDto()
|
|
|
|
|
{
|
|
|
|
|
orderId = order.Id.ToString(),
|
|
|
|
|
language = "zh_CN",
|
|
|
|
|
cargoDetails = new List<CargoDetailsItem>()
|
|
|
|
|
{
|
|
|
|
|
new CargoDetailsItem()
|
|
|
|
|
{
|
|
|
|
|
count = (int)order.KDNum,
|
|
|
|
|
name = order.GOODSNAME
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if (order.SettleAccountsTypeCode == "2")
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(order.MonthlyCard))
|
|
|
|
|
{
|
|
|
|
|
throw Oops.Bah("当结费类型为月结时,月结卡号不能为空");
|
|
|
|
|
}
|
|
|
|
|
sFSend.monthlyCard = order.MonthlyCard;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<ContactInfoListItem> contactList = new()
|
|
|
|
|
{
|
|
|
|
|
new ContactInfoListItem
|
|
|
|
|
{
|
|
|
|
|
address = $"{order.FJProvince}{order.FJCity}{order.FJAddress}",
|
|
|
|
|
contact = order.FJPeople,
|
|
|
|
|
contactType = 1,
|
|
|
|
|
country = "CN",
|
|
|
|
|
tel = order.FJTel,
|
|
|
|
|
},
|
|
|
|
|
new ContactInfoListItem
|
|
|
|
|
{
|
|
|
|
|
address = $"{order.SJProvince}{order.SJCity}{order.SJAddress}",
|
|
|
|
|
contact = order.SJPeople,
|
|
|
|
|
contactType = 2,
|
|
|
|
|
country = "CN",
|
|
|
|
|
tel = order.SJTel,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
sFSend.contactInfoList = contactList;
|
|
|
|
|
|
|
|
|
|
(string url, object body) apiParam = await BuildApiParamAsync("EXP_RECE_CREATE_ORDER", sFSend);
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("调用顺丰下单接口,参数:" + apiParam.body.ToJson());
|
|
|
|
|
var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync();
|
|
|
|
|
logger.LogInformation("调用顺丰下单接口,返回:" + strRtn);
|
|
|
|
|
|
|
|
|
|
var jobj = strRtn.ToJObject();
|
|
|
|
|
if (jobj.GetStringValue("apiResultCode") == "A1000")
|
|
|
|
|
{
|
|
|
|
|
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
|
|
|
|
|
bool isSuccess = resultDataObj.GetBooleanValue("success");
|
|
|
|
|
if (isSuccess)
|
|
|
|
|
{
|
|
|
|
|
var waybillNoInfoObj = resultDataObj.GetJObjectValue("msgData").GetJArrayValue("waybillNoInfoList")[0].Value<JObject>();
|
|
|
|
|
|
|
|
|
|
var waybillNo = waybillNoInfoObj.GetStringValue("waybillNo");
|
|
|
|
|
return waybillNo;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var errorMsg = resultDataObj.GetStringValue("errorMsg");
|
|
|
|
|
logger.LogInformation("调用顺丰下单接口,返回异常,errorMsg:" + errorMsg);
|
|
|
|
|
throw Oops.Bah(errorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
|
|
|
|
|
logger.LogInformation("调用顺丰下单接口,返回异常,apiErrorMsg:" + apiErrorMsg);
|
|
|
|
|
throw Oops.Bah(apiErrorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<bool> CancelOrder(string orderId)
|
|
|
|
|
{
|
|
|
|
|
var param = new
|
|
|
|
|
{
|
|
|
|
|
dealType = 2,
|
|
|
|
|
orderId
|
|
|
|
|
};
|
|
|
|
|
(string url, object body) apiParam = await BuildApiParamAsync("EXP_RECE_UPDATE_ORDER", param);
|
|
|
|
|
|
|
|
|
|
logger.LogInformation($"调用顺丰订单取消接口,参数:{apiParam.body.ToJson()}");
|
|
|
|
|
var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync();
|
|
|
|
|
logger.LogInformation($"调用顺丰订单取消接口,返回:{strRtn}");
|
|
|
|
|
|
|
|
|
|
var jobj = strRtn.ToJObject();
|
|
|
|
|
if (jobj.GetStringValue("apiResultCode") == "A1000")
|
|
|
|
|
{
|
|
|
|
|
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
|
|
|
|
|
if (resultDataObj.GetBooleanValue("success"))
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var errorMsg = resultDataObj.GetStringValue("errorMsg");
|
|
|
|
|
logger.LogInformation($"顺丰取消快递下单接口,返回异常,errorMsg:{errorMsg}");
|
|
|
|
|
throw Oops.Bah(errorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
|
|
|
|
|
logger.LogInformation($"顺丰取消快递下单接口,返回异常:{apiErrorMsg}");
|
|
|
|
|
throw Oops.Bah(apiErrorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task DownloadWaybillFile(string waybillNo, string savePath)
|
|
|
|
|
{
|
|
|
|
|
logger.LogInformation($"调用顺丰面单PDF下载接口开始...");
|
|
|
|
|
var waybillDownloadInfo = await GetWaybillDownloadInfo(waybillNo);
|
|
|
|
|
|
|
|
|
|
var header = new Dictionary<string, object>() { { "X-Auth-token", waybillDownloadInfo.Token } };
|
|
|
|
|
using var stream = await waybillDownloadInfo.Url.SetHeaders(header).GetAsStreamAsync();
|
|
|
|
|
|
|
|
|
|
using FileStream fs = new FileStream(savePath, FileMode.Create);
|
|
|
|
|
await stream.CopyToAsync(fs);
|
|
|
|
|
logger.LogInformation($"顺丰面单PDF文件保存完成,保存地址:{savePath}");
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 请求获取顺丰面单Pdf下载地址及访问Token
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private async Task<(string Url, string Token)> GetWaybillDownloadInfo(string masterWaybillNo)
|
|
|
|
|
{
|
|
|
|
|
SFPrintWaybillDto sFSend = new SFPrintWaybillDto()
|
|
|
|
|
{
|
|
|
|
|
fileType = "pdf",
|
|
|
|
|
sync = true,
|
|
|
|
|
templateCode = "fm_150_standard_DSWYR7LUN7FB",
|
|
|
|
|
version = "2.0",
|
|
|
|
|
documents = new List<SFPrintWaybillDto.Document>() {
|
|
|
|
|
new SFPrintWaybillDto.Document(masterWaybillNo)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
(string url, object body) apiParam = await BuildApiParamAsync("COM_RECE_CLOUD_PRINT_WAYBILLS", sFSend);
|
|
|
|
|
logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,参数:" + apiParam.body.ToJson());
|
|
|
|
|
var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync();
|
|
|
|
|
logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回:" + strRtn);
|
|
|
|
|
|
|
|
|
|
var jobj = strRtn.ToJObject();
|
|
|
|
|
if (jobj.GetStringValue("apiResultCode") == "A1000")
|
|
|
|
|
{
|
|
|
|
|
var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject();
|
|
|
|
|
bool isSuccess = resultDataObj.GetBooleanValue("success");
|
|
|
|
|
if (isSuccess)
|
|
|
|
|
{
|
|
|
|
|
var fileObj = resultDataObj.GetJObjectValue("obj").GetJArrayValue("files")[0].Value<JObject>();
|
|
|
|
|
var token = fileObj.GetStringValue("token");
|
|
|
|
|
var url = fileObj.GetStringValue("url");
|
|
|
|
|
return (url, token);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var errorMsg = resultDataObj.GetStringValue("errorMsg");
|
|
|
|
|
logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回异常,errorMsg:" + errorMsg);
|
|
|
|
|
throw Oops.Bah(errorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var apiErrorMsg = jobj.GetStringValue("apiErrorMsg");
|
|
|
|
|
logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回异常,apiErrorMsg:" + apiErrorMsg);
|
|
|
|
|
throw Oops.Bah(apiErrorMsg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 构建请求顺丰接口所需的Url及Body参数
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="apiCode">接口编码</param>
|
|
|
|
|
/// <param name="businessData">参数</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private async Task<(string url, object body)> BuildApiParamAsync(string apiCode, object businessData)
|
|
|
|
|
{
|
|
|
|
|
var cacheDictData = await cache.GetAllDictData();
|
|
|
|
|
|
|
|
|
|
var url = cacheDictData.FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == "sf_api_url")?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(url))
|
|
|
|
|
{
|
|
|
|
|
throw Oops.Bah("需配置顺丰Api接口字典参数:url地址(sf_api_url)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var partnerId = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "partnerId")?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(partnerId))
|
|
|
|
|
{
|
|
|
|
|
throw Oops.Bah("需配置顺丰Api接口字典参数:合作伙伴编码,即顾客编码(partnerId)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string accessToken;
|
|
|
|
|
if (cache.Exists(SF_API_TOKEN_KEY))
|
|
|
|
|
{
|
|
|
|
|
accessToken = cache.Get(SF_API_TOKEN_KEY);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var secret = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "secret")?.Value;
|
|
|
|
|
if (string.IsNullOrEmpty(partnerId))
|
|
|
|
|
{
|
|
|
|
|
throw Oops.Bah("需配置顺丰Api接口字典参数:合作伙伴密钥,即校验码(secret)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var getTokenParam = new Dictionary<string, string>()
|
|
|
|
|
{
|
|
|
|
|
{ "partnerID",partnerId },
|
|
|
|
|
{ "grantType", "password"},
|
|
|
|
|
{ "secret", secret}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("调用顺丰Token查询接口,参数:" + getTokenParam.ToJson());
|
|
|
|
|
var strRtn = await (url + "/oauth2/accessToken").SetBody(getTokenParam, "application/x-www-form-urlencoded").PostAsStringAsync();
|
|
|
|
|
logger.LogInformation("调用顺丰Token查询接口,返回:" + strRtn);
|
|
|
|
|
|
|
|
|
|
var jobj = strRtn.ToJObject();
|
|
|
|
|
if (jobj.GetStringValue("apiResultCode") == "A1000")
|
|
|
|
|
{
|
|
|
|
|
accessToken = jobj.GetStringValue("accessToken");
|
|
|
|
|
|
|
|
|
|
//设置token过期时间2小时
|
|
|
|
|
await cache.SetTimeoutAsync(SF_API_TOKEN_KEY, accessToken, TimeSpan.FromHours(2));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw Oops.Bah("调用顺丰Token查询接口时发生异常:" + jobj.GetStringValue("apiErrorMsg"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dict = new Dictionary<string, object>()
|
|
|
|
|
{
|
|
|
|
|
{ "partnerID", partnerId },
|
|
|
|
|
{ "serviceCode", apiCode},
|
|
|
|
|
{ "requestID", Guid.NewGuid().ToString()},
|
|
|
|
|
{ "timestamp", DateTimeOffset.Now.ToUnixTimeSeconds()},
|
|
|
|
|
{ "accessToken", accessToken},
|
|
|
|
|
{ "msgData", businessData}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (url + "/std/service", dict);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|