京东商城售后接口对接
售后
京东售后单以客户申请角度:是否收到货品
- 未收到货 : 订单最终走向为取消订单
- 退货退款 : 生成服务单
- 换货 : 售后按退货走.京东生成一个新的订单.
1.1 未收到货:
1.1.1 未发货
# 监听消息 : order_order_cancel ( 订单取消 )
# 地址 : https://open.jd.com/home/home/#/doc/msgApi?apiCateId=81&apiId=128
1.1.2 已发货,客户未收到货
# 监听消息 : order_order_cancel ( 订单取消 )
# 地址 : https://open.jd.com/home/home/#/doc/msgApi?apiCateId=81&apiId=128
1.2 已收到货
客户已收到货的情况下申请售后.原订单不会有任何变更,
会生成一个服务单.服务单没有任何消息推送.全部以刷API接口的方式获取
# 地址 : https://open.jd.com/home/home/#/doc/api?apiCateId=241&apiId=2070&apiName=jingdong.asc.audit.list
流程 :
Step1 : 申请
# 该接口只能在申请阶段拿到服务单信息,如果后台发生审核通过这个接口就抓不到了.所以审核时间一定要大于刷接口的周期时间,确保已拿到售后申请单数据,
# 拿到申请数据后.保存到本地.或缓冲中
jingdong.asc.audit.list ( 查询待审核申请单列表 )
Step2 : 状态变更
# 通过刷接口的方式获取当前服务单的后续变更,根据状态[serviceStatus字段]进行业务处理
jingdong.asc.query.view ( 查看服务单明细信息 )
Step3 : 客户发货信息
# 如果后台审核同意后.需要刷第三个接口获取物流信息
jingdong.asc.freight.view ( 查询服务单的运单信息 )
Step4 : 关闭
# 当客户取消或退款完成[通过Step2状态变更获取] 删除缓冲中的数据
1.3 示例代码
1.3.1 申请
/**
* jingdong.asc.audit.list ( 查询待审核申请单列表 )
* https://open.jd.com/home/home/#/doc/api?apiCateId=241&apiId=2070&apiName=jingdong.asc.audit.list
* @author mcc
*/
@Service
public class JdRefundAuditListTaskStrategy2 implements TaskStrategy {
@Autowired
private KafkaTemplate shopKafkaTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 执行业务
* @param account
* @param client
* @return
* @throws Exception
*/
public boolean strategy(JdShopAccount account, JdClient client) throws Exception {
// https://open.jd.com/home/home/#/doc/api?apiCateId=241&apiId=2070&apiName=jingdong.asc.audit.list
AscAuditListRequest request=new AscAuditListRequest();
int pageSize = 50;
int totalPage = 1;
int page = 1;
String storeNo = account.getId();
// 首次请求标志
boolean firstRequest = Boolean.TRUE;
request.setPageSize(pageSize);
do {
request.setPageNumber(page);
request.setBuId(storeNo);
request.setOperatePin(RefundContext.OPERATE_PIN);
request.setOperateNick(RefundContext.OPERATE_PIN);
AscAuditListResponse response = client.execute(request);
if (page == 1 && firstRequest) {
// 第一页时, 获取总页码数
int totalSize = response.getPageResult().getTotalCount();
totalPage = Double.valueOf(Math.ceil(Double.valueOf(totalSize) / pageSize)).intValue();
firstRequest = Boolean.FALSE;
// 若初始页码, 与总页码不一致时, 数据重新从最后一页进行请求
if (page != totalPage) {
page = totalPage;
continue;
}
}
List<WaitAuditApply> refundList = response.getPageResult().getData();
refundList.forEach(refund->{
logger.info("查询待审核申请单列表 :{}", JSONObject.toJSONString(refund));
Long orderId = refund.getOrderId();
RSet<String> serviceIdSet = redissonClient.getSet(RefundContext.SERVICE_ID_SET);
refund.getServiceIdList().forEach(serviceId->{
String orderIdAndServiceId = orderId + "-" + serviceId;
if (serviceIdSet.contains(orderIdAndServiceId)) {
return;
}
serviceIdSet.add(orderIdAndServiceId);
// 申请售后
this.apply( storeNo, client, orderId, serviceId,refund);
});
});
page--;
} while (page > 0);
return Boolean.TRUE;
}
/**
* 售后申请
*/
private void apply(String storeNo,JdClient clientProxy,Long orderId,Long serviceId,WaitAuditApply apply){
JSONObject backOrder = this.packageBaseInfo(storeNo,orderId,serviceId);
// 售后详情
this.findServiceOrder(backOrder, storeNo, clientProxy, serviceId, apply);
// 售后明细
this.findOrderDetail(clientProxy,backOrder, orderId, storeNo);
// 把消息送出鼎外
this.sendOut(backOrder);
}
/**
* jingdong.asc.audit.detail ( 查看待审核申请单详情 )
* @param storeNo
* @param clientProxy
* @param apply
*/
private void findServiceOrder(JSONObject backOrder,String storeNo,JdClient clientProxy,Long serviceId,WaitAuditApply apply) {
try {
AscAuditDetailRequest request = new AscAuditDetailRequest();
request.setBuId(storeNo);
request.setOperatePin(RefundContext.OPERATE_PIN);
request.setOperateNick(RefundContext.OPERATE_PIN);
request.setApplyId(apply.getApplyId());
AscAuditDetailResponse response = clientProxy.execute(request);
if (ObjectUtils.isEmpty(response)) {
throw new RuntimeException("订单不存在");
}
if (ObjectUtils.isEmpty(response.getResult())) {
throw new RuntimeException("订单不存在");
}
if (ObjectUtils.isEmpty(response.getResult().getData())) {
throw new RuntimeException("订单不存在");
}
if (ObjectUtils.isEmpty(response.getResult().getData())) {
throw new RuntimeException("订单不存在");
}
if (ListUtil.isEmpty(response.getResult().getData().getServiceIdList())){
throw new RuntimeException("订单不存在");
}
if (!response.getResult().getData().getServiceIdList().contains(serviceId)){
throw new RuntimeException("订单不存在");
}
WaitAuditDetail data = response.getResult().getData();
backOrder.put("returnReason", data.getQuestionTypeCid1Name());
backOrder.put("refundTime", data.getApplyTime().getTime());
logger.info("查询待审核申请单详情列表 :{}", JSONObject.toJSONString(response));
}catch (Exception e){
logger.error(this.getClass().getSimpleName()+" is error ,{}",e.getMessage());
}
}
/**
* 获取订单详情
*/
private void findOrderDetail(JdClient clientProxy,JSONObject backOrder, Long orderId, String venderId) {
try {
PopOrderEnGetRequest request = new PopOrderEnGetRequest();
request.setOptionalFields(
"orderId,venderId,orderType,payType,orderTotalPrice,orderSellerPrice,orderPayment,freightPrice,sellerDiscount,orderState,orderStateRemark,deliveryType,invoiceEasyInfo,invoiceInfo,invoiceCode,orderRemark,orderStartTime,orderEndTime,consigneeInfo,itemInfoList,couponDetailList,venderRemark,balanceUsed,pin,returnOrder,paymentConfirmTime,waybill,logisticsId,vatInfo,modified,directParentOrderId,parentOrderId,customs,customsModel,orderSource,storeOrder,idSopShipmenttype,scDT,serviceFee,pauseBizInfo,taxFee,tuiHuoWuYou,orderSign,storeId,realPin,orderMarkDesc,open_id,open_id_buyer");
request.setOrderId(orderId);
PopOrderEnGetResponse response = clientProxy.execute(request);
// 获取订单详情失败
if (ObjectUtils.isEmpty(response)
|| ObjectUtils.isEmpty(response.getOrderDetailInfo())
|| ObjectUtils.isEmpty(response.getOrderDetailInfo().getOrderInfo())
|| ListUtil.isEmpty(response.getOrderDetailInfo().getOrderInfo().getItemInfoList())) {
throw new RuntimeException("获取订单详情失败");
}
OrderSearchInfo responseTrade = response.getOrderDetailInfo().getOrderInfo();
// 是否发货
// 运单号
String waybill = responseTrade.getWaybill();
String logisticsId = responseTrade.getLogisticsId();
if (StringUtils.hasText(waybill) || StringUtils.hasText(logisticsId)){
backOrder.put("isSend", 1);
}
JSONArray backOrderDetailList = new JSONArray();
responseTrade.getItemInfoList().forEach(prod -> {
JSONObject item = new JSONObject();
item.put("srcId", prod.getSkuId());
item.put("prodNum", prod.getItemTotal());
item.put("chaProdName", prod.getSkuName());
backOrderDetailList.add(item);
});
backOrder.put("backOrderDetailList", backOrderDetailList);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 基本信息
* @param orderId
* @param serviceId
* @return
*/
private JSONObject packageBaseInfo(String storeNo,
Long orderId,
Long serviceId){
JSONObject backOrder = new JSONObject();
backOrder.put("channelOrderNo",orderId);
backOrder.put("channelBackOrderNo",serviceId);
backOrder.put("isSend",1);
backOrder.put("returnType",2);
backOrder.put("channelSource", 2);
backOrder.put("storeId",storeNo);
backOrder.put("tag",JdServiceStatusEnum.APPLY.getServiceStatus());
return backOrder;
}
/**
* 把消息送出鼎外
*/
private void sendOut(JSONObject backOrder) {
backOrder.put("tag", JdServiceStatusEnum.APPLY.getServiceStatus());
this.shopKafkaTemplate.send("jd_hf_refund", CryptoUtils.encode(backOrder.toJSONString()));
}
}
1.3.2 状态变更
/**
* Desc : 京东退货退款信息刷接口
* jingdong.asc.query.view ( 查看服务单明细信息 )
*
*
* Redis :
* order-serviceId,Map<,>
* - agreeStatus : agree
* - expressInfo : expressInfoDTO JSON
* @Author : jinjianghai
* @E-mail : 547114661@qq.com
* @Date : 2024/5/7 11:09
*/
@Slf4j
@Service
public class JdRefundInfoTaskStrategy2 implements TaskStrategy {
/**
* Redis
*/
@Autowired
private RedissonClient redissonClient;
/**
* kafka客户端
*/
@Autowired
protected KafkaTemplate shopKafkaTemplate;
/**
* 执行业务
* @param account
* @param client Client实例
* @return
* @throws Exception
*/
@Override
public boolean strategy(JdShopAccount account, JdClient client) throws Exception {
// 获取通过JdRefundAuditListTaskStrategy写入的服务单数据
RSet<String> serviceIdSet = redissonClient.getSet(RefundContext.SERVICE_ID_SET);
if (ObjectUtils.isEmpty(serviceIdSet)){
return Boolean.TRUE;
}
// 获取代理端
JdClient clientProxy = RetryProxy.getProxy(client, 2000, 5);
// 处理业务
this.doRefund(account.getId(),clientProxy,serviceIdSet);
return Boolean.TRUE;
}
/**
* 循环处理
* @param clientProxy
* @param serviceIdSet
*/
private void doRefund(String storeNo,JdClient clientProxy,RSet<String> serviceIdSet) throws Exception {
Iterator<String> iterator = serviceIdSet.iterator();
while (iterator.hasNext()){
String orderIdAndServiceId = iterator.next();
String[] split = orderIdAndServiceId.split("-");
Long orderId = Long.parseLong(split[0]);
Long serviceId = Long.parseLong(split[1]);
AscQueryViewRequest request = new AscQueryViewRequest();
request.setBuId(storeNo);
request.setOperatePin(RefundContext.OPERATE_PIN);
request.setOperateNick(RefundContext.OPERATE_PIN);
request.setServiceId(serviceId);
request.setOrderId(orderId);
// jingdong.asc.query.view ( 查看服务单明细信息 )
AscQueryViewResponse response = clientProxy.execute(request);
log.info("售后单明细{},{},报文{}",orderId,serviceId,JSONObject.toJSONString(response));
// 返回值为空
if (ObjectUtils.isEmpty(response)){
continue;
}
// 返回值为空
if (ObjectUtils.isEmpty(response.getResult())){
continue;
}
// 返回值为空
if (ObjectUtils.isEmpty(response.getResult().getData())){
continue;
}
// 服务单状态为空,如协商情况下是空的
if (ObjectUtils.isEmpty(response.getResult().getData().getServiceStatus())){
continue;
}
RMap<Object, Object> serviceInfo = redissonClient.getMap(RefundContext.SERVICE_INFO + orderIdAndServiceId);
JdServiceStatusEnum serviceStatus = JdServiceStatusEnum.getEnum(response.getResult().getData().getServiceStatus());
if (ObjectUtils.isEmpty(serviceStatus)){
continue;
}
// 基础信息
JSONObject backOrder = this.packageBaseInfo(storeNo, orderId, serviceId, serviceStatus);
switch (serviceStatus){
// 待收货,后台审核同意,待客户发货
case WAIT_USER_SEND:
this.doAgree(storeNo,backOrder,clientProxy,orderId,serviceId,serviceInfo);
break;
// 客户取消或客户同意取消
case CANCEL:
this.doCancel(backOrder,clientProxy,orderId,serviceId,serviceInfo);
break;
// 完成
case COMPLETE:
this.doComplete(backOrder,clientProxy,orderId,serviceId,serviceInfo);
break;
}
logger.info("查看服务单明细信息 :{}", JSONObject.toJSONString(response));
}
}
/**
* 审核同意
* @param clientProxy
* @param serviceInfo
*/
private void doAgree(String storeNo,
JSONObject backOrder,
JdClient clientProxy,
Long orderId,
Long serviceId,
RMap<Object, Object> serviceInfo){
Object agreeStatus = serviceInfo.get(RefundContext.AGREE_STATUS);
if (ObjectUtils.isEmpty(agreeStatus)){
serviceInfo.put(RefundContext.AGREE_STATUS,RefundContext.AGREE);
// 发初审同意消息给jd-sync
this.sendOut(backOrder);
return ;
}
// 获取物流信息
this.getExpressInfo(storeNo,backOrder,clientProxy,orderId,serviceId,serviceInfo);
}
/**
* 获取物流信息逻辑
* @param clientProxy
* @param orderId
* @param serviceId
* @param serviceInfo
*/
private void getExpressInfo(String storeNo,
JSONObject backOrder,
JdClient clientProxy,
Long orderId,
Long serviceId,
RMap<Object, Object> serviceInfo){
Object expressInfo = serviceInfo.get(RefundContext.EXPRESS_INFO);
if (ObjectUtils.isEmpty(expressInfo)){
// 执行获取物流信息
ExpressInfoDTO expressInfoDTO = this.doGetExpressInfo(storeNo,clientProxy, orderId, serviceId);
if (ObjectUtils.isEmpty(expressInfoDTO)){
return;
}
serviceInfo.put(RefundContext.EXPRESS_INFO,JSONObject.toJSONString(expressInfoDTO));
// 发送物流信息给鼎外
this.sendExpressInfoOut(backOrder,expressInfoDTO);
return;
}
ExpressInfoDTO oldExpressInfo = JSONObject.parseObject((String) expressInfo, ExpressInfoDTO.class);
ExpressInfoDTO expressInfoDTO = this.doGetExpressInfo(storeNo,clientProxy, orderId, serviceId);
if (ObjectUtils.isEmpty(expressInfoDTO)){
return;
}
if (oldExpressInfo.equals(expressInfoDTO)){
return;
}
serviceInfo.put(RefundContext.EXPRESS_INFO,JSONObject.toJSONString(expressInfoDTO));
// 发送物流信息给鼎外
this.sendExpressInfoOut(backOrder,expressInfoDTO);
}
/**
* 执行获取物流信息
* @param clientProxy
* @param orderId
* @param serviceId
*/
private ExpressInfoDTO doGetExpressInfo(String storeNo,
JdClient clientProxy,
Long orderId,
Long serviceId){
try {
AscFreightViewRequest request=new AscFreightViewRequest();
request.setBuId(storeNo);
request.setOperatePin(RefundContext.OPERATE_PIN);
request.setOperateNick(RefundContext.OPERATE_PIN);
request.setServiceId(serviceId);
request.setOrderId(orderId);
// jingdong.asc.freight.view ( 查询服务单的运单信息 )
AscFreightViewResponse response = clientProxy.execute(request);
if (ObjectUtils.isEmpty(response)){
return null;
}
if (ObjectUtils.isEmpty(response.getResult())){
return null;
}
if (ObjectUtils.isEmpty(response.getResult().getData())){
return null;
}
Expressage data = response.getResult().getData();
if (ObjectUtils.isEmpty(data.getExpressCompany()) || ObjectUtils.isEmpty(data.getExpressCode())){
return null;
}
ExpressInfoDTO expressInfoDTO = new ExpressInfoDTO(data.getExpressCompany(),data.getExpressCode());
logger.info("查看服务单运单信息,serviceId:{}, 报文:{}", serviceId,JSONObject.toJSONString(response));
return expressInfoDTO;
}catch (Exception e){
log.error("执行获取物流信息异常,{}", e.getMessage());
}
return null;
}
/**
* 取消
* @param clientProxy
* @param info
*/
private void doCancel(JSONObject backOrder,JdClient clientProxy,Long orderId,Long serviceId,RMap<Object, Object> info){
// 发初审同意消息给jd-sync
this.sendOut(backOrder);
// 在取消或完成的情况下去除Redis缓冲信息
this.removeRedisInfo(orderId, serviceId);
}
/**
* 完成
* @param clientProxy
* @param info
*/
private void doComplete(JSONObject backOrder,JdClient clientProxy,Long orderId,Long serviceId,RMap<Object, Object> info){
// 发初审同意消息给jd-sync
this.sendOut(backOrder);
// 在取消或完成的情况下去除Redis缓冲信息
this.removeRedisInfo(orderId, serviceId);
}
/**
* 基本信息
* @param orderId
* @param serviceId
* @param serviceStatus
* @return
*/
private JSONObject packageBaseInfo(String storeNo,
Long orderId,
Long serviceId,
JdServiceStatusEnum serviceStatus){
JSONObject backOrder = new JSONObject();
backOrder.put("channelOrderNo",orderId);
backOrder.put("channelBackOrderNo",serviceId);
backOrder.put("isSend",1);
backOrder.put("returnStatus",2);
backOrder.put("channelSource", 2);
backOrder.put("storeId",storeNo);
backOrder.put("tag",serviceStatus.getServiceStatus());
return backOrder;
}
/**
* 在取消或完成的情况下去除Redis缓冲信息
* @param orderId
* @param serviceId
*/
private void removeRedisInfo(Long orderId,Long serviceId){
String orderIdAndServiceId = orderId + "-" + serviceId;
// 获取通过JdRefundAuditListTaskStrategy写入的服务单数据
RSet<String> serviceIdSet = redissonClient.getSet(RefundContext.SERVICE_ID_SET);
if (!ObjectUtils.isEmpty(serviceIdSet)){
serviceIdSet.remove(orderIdAndServiceId);
}
RMap<Object, Object> serviceInfo = redissonClient.getMap(RefundContext.SERVICE_INFO + orderIdAndServiceId);
if (MapUtil.isEmpty(serviceInfo) || serviceInfo.isEmpty()){
return;
}
serviceInfo.delete();
}
/**
* 发送物流信息给鼎外
* @param backOrder
*/
private void sendExpressInfoOut(JSONObject backOrder,ExpressInfoDTO expressInfoDTO){
if (ObjectUtils.isEmpty(expressInfoDTO)){
return;
}
backOrder.put("tag",JdServiceStatusEnum.SYNC_EXP.getServiceStatus());
backOrder.put("expName",expressInfoDTO.getExpName());
backOrder.put("expNo",expressInfoDTO.getExpNo());
this.sendOut(backOrder);
}
/**
* 把消息送出鼎外
*/
private void sendOut(JSONObject backOrder) {
this.shopKafkaTemplate.send("jd_hf_refund_approved", CryptoUtils.encode(backOrder.toJSONString()));
}
}