快递模块对接费用等接口

optimize
zhangxiaofeng 11 months ago
parent 2ddd852fa3
commit 2206f7ab88

@ -0,0 +1,49 @@
using System;
using SqlSugar;
using System.ComponentModel;
using Myshipping.Core.Entity;
namespace Myshipping.Application.Entity
{
/// <summary>
/// 快递费用
/// </summary>
[SugarTable("express_delivery_fee")]
[Description("快递费用")]
public class ExpressDeliveryFee : DBEntityTenant
{
/// <summary>
/// 快递订单记录主键
/// </summary>
public long? OrderId { get; set; }
/// <summary>
/// 快递运单号
/// </summary>
public string KDNO { get; set; }
/// <summary>
/// 费用类型编码
/// </summary>
public string FeeTypeCode { get; set; }
/// <summary>
/// 费用类型名称
/// </summary>
public string FeeTypeName { get; set; }
/// <summary>
/// 费用金额
/// </summary>
public decimal FeeAmount { get; set; }
/// <summary>
/// 付款账号
/// </summary>
public string Account { get; set; }
/// <summary>
/// 结费类型代码1现结2月结3到付
/// </summary>
public string SettleAccountsTypeCode { get; set; }
}
}

@ -107,10 +107,17 @@ namespace Myshipping.Application.Entity
/// 寄件数量
/// </summary>
public int? KDNum { get; set; }
///// <summary>
///// 快递费
///// </summary>
//public decimal? KDFee { get; set; }
/// <summary>
/// 快递费
/// 计费重量
/// </summary>
public decimal? KDFee { get; set; }
public float? FeeWeight { get; set; }
/// <summary>
/// 备注
/// </summary>

@ -0,0 +1,33 @@
using Myshipping.Application.Entity;
using System.Threading.Tasks;
namespace Myshipping.Application
{
/// <summary>
/// 快递对接接口
/// </summary>
public interface IDeliverySend
{
/// <summary>
/// 快递下单
/// </summary>
/// <param name="expressDeliveryOrder">快递订单记录</param>
/// <returns>快递单号</returns>
Task<string> CreateOrder(ExpressDeliveryOrder expressDeliveryOrder);
/// <summary>
/// 快递消单
/// </summary>
/// <param name="orderId">快递订单记录主键</param>
/// <returns>是否消单成功</returns>
Task<bool> CancelOrder(string orderId);
/// <summary>
/// 下载快递面单
/// </summary>
/// <param name="waybillNo">快递单号</param>
/// <param name="savePath">快递面单保存地址</param>
/// <returns></returns>
Task DownloadWaybillFile(string waybillNo,string savePath);
}
}

@ -0,0 +1,289 @@
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);
}
}
}

@ -108,10 +108,17 @@ namespace Myshipping.Application.Service.ExpressDelivery.Dto
/// 寄件数量
/// </summary>
public int? KDNum { get; set; }
/// <summary>
/// 快递费
/// 计费重量
/// </summary>
public decimal? KDFee { get; set; }
public float? FeeWeight { get; set; }
///// <summary>
///// 快递费
///// </summary>
//public decimal? KDFee { get; set; }
/// <summary>
/// 备注
/// </summary>
@ -156,6 +163,8 @@ namespace Myshipping.Application.Service.ExpressDelivery.Dto
public List<ExpressDeliveryBusiness> Business { get; set; }
public List<ExpressDeliveryFee> FeeList { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (SettleAccountsTypeCode == "2" && string.IsNullOrWhiteSpace(MonthlyCard))

@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace Myshipping.Application.Service.ExpressDelivery.Dto.SF
{
public class ReceiveSfFeeDto
{
public string content { get; set; }
}
public class ContentModel
{
public string orderNo { get; set; }
public string waybillNo { get; set; }
public string meterageWeightQty { get; set; }
public List<Fee> feeList { get; set; }
}
public class Fee
{
public decimal feeAmt { get; set; }
public string settlementTypeCode { get; set; }
public string feeTypeCode { get; set; }
public string customerAcctCode { get; set; }
public string waybillNo { get; set; }
}
}

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Myshipping.Application.Service.ExpressDelivery.Dto.SF
{
public class ReceiveSfOrderStateDto
{
public string requestId { get; set; }
public List<OrderState> orderState { get; set; }
public class OrderState
{
public string orderNo { get; set; }
public string waybillNo { get; set; }
public string orderStateCode { get; set; }
public string orderStateDesc { get; set; }
}
}
}

@ -2,9 +2,8 @@
namespace Myshipping.Application.Service.ExpressDelivery.Dto
{
public class SFSendBooking
public class SFCreateOrderDto
{
public List<CargoDetailsItem> cargoDetails { get; set; }
/// <summary>
///

@ -3,19 +3,18 @@
namespace Myshipping.Application.Service.ExpressDelivery.Dto
{
/// <summary>
/// 顺丰接收快递状态Dto类
/// 顺丰接收路由动态Dto类
/// </summary>
public partial class SFAddOrderStatus
public class SFReceiveRouteDto
{
public Body Body { get; set; }
}
public partial class Body
public class Body
{
public List<WaybillRoute> WaybillRoute { get; set; }
}
public partial class WaybillRoute
public class WaybillRoute
{
public string AcceptAddress { get; set; }
public string AcceptTime { get; set; }

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Myshipping.Application.Service.ExpressDelivery.Dto;
using Myshipping.Core;
using System.Collections.Generic;
using System.Threading.Tasks;
@ -7,16 +8,14 @@ namespace Myshipping.Application
{
public interface IExpressDeliveryService
{
Task<dynamic> Get(long Id);
Task<dynamic> Page([FromQuery] ExpressDeliveryInput input);
Task<dynamic> QueryKDSchedule(long Id);
Task<dynamic> Save(ExpressDeliveryDto input);
Task<ExpressDeliveryDto> Get(long Id);
Task<SqlSugarPagedList<ExpressDeliveryDto>> Page([FromQuery] ExpressDeliveryInput input);
//Task<dynamic> QueryKDSchedule(long Id);
Task<ExpressDeliveryDto> Save(ExpressDeliveryDto input);
Task<dynamic> SendBooking(long Id);
Task<object> AddSfStatus(SFAddOrderStatus orderStatus);
Task DeleteAddress(string ids);
Task<List<ExpressDeliveryAddressDto>> GetAddressList();
Task SaveOrUpdateAddress(ExpressDeliveryAddressDto input);
Task Delete(string Ids);
//IActionResult PrintWaybill(string accessToken, int orderId);
}
}
Loading…
Cancel
Save