新增顺丰同城的业务代码

This commit is contained in:
Jack 2024-11-19 17:37:31 +08:00
parent 9da192c6a3
commit 7279e39aa2
2 changed files with 528 additions and 0 deletions

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2024. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.shop.sfexpress.service;
import com.suisung.mall.common.pojo.res.SFExpressApiRes;
import java.util.Map;
public interface SFExpressApiService {
/**
* 店铺创建顺丰同城订单
*
* @param shopOrderId 商家订单号
* @return
*/
SFExpressApiRes createOrder(String shopOrderId);
/**
* 取消订单当商家处发生异常需要取消配送时可调用此接口对订单进行取消操作同步返回结果
*
* @param sfOrderId 顺丰订单号
* @param cancelCode 取消码参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @param cancelReason 取消原因
* @return
*/
SFExpressApiRes cancelOrder(String sfOrderId, Integer cancelCode, String cancelReason);
/**
* 取消订单当商家处发生异常需要取消配送时可调用此接口对订单进行取消操作同步返回结果
*
* @param params 综合参数顺丰订单号必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes cancelOrder(Map<String, Object> params);
/**
* 订单加小费订单创建后骑士未接单的情况下通过该接口对订单进行加小费促进订单接单截止订单完成前都可以对订单加小费
*
* @param params 综合参数顺丰订单号order_id订单小费 gratuity_fee必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes addOrderGratuityFee(Map<String, Object> params);
/**
* 催单当订单为配送状态中可通过该接口发起催单
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes reminderOrder(Map<String, Object> params);
/**
* 获取配送员实时坐标接口此接口用于获取订单配送员的实时经纬度坐标一般情况下骑士经纬度30s更新一次
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes riderLatestPosition(Map<String, Object> params);
/**
* 获取配送员轨迹H5此接口可获取一个订单的骑士位置H5链接可进行内嵌或发送给用户内嵌时无法保证界面的兼容性如发现兼容性问题可使用获取配送员坐标接口自行开发轨迹H5
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes riderViewV2(Map<String, Object> params);
/**
* 改单支持店铺和企业客户改单,当订单生成后可通过该接口修改收件人信息,可修改字段:收件地址物品重量等,详情可参考请求参数列表
*
* @param params 综合参数请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
SFExpressApiRes changeOrder(Map<String, Object> params);
// 顺丰同城回调相关业务
/**
* 接收顺丰原因订单取消回调
* @param jsonData
* @param sign
* @return
*/
SFExpressApiRes receiveCancelOrderNotify(String jsonData, String sign);
/**
* 接收顺丰配送状态更改回调
* @param jsonData
* @param sign
* @return
*/
SFExpressApiRes receiveRiderOrderStatusNotify(String jsonData, String sign);
/**
* 接收顺丰订单完成回调
* @param jsonData
* @param sign
* @return
*/
SFExpressApiRes receiveOrderCompleteNotify(String jsonData, String sign);
}

View File

@ -0,0 +1,418 @@
/*
* Copyright (c) 2024. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.shop.sfexpress.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.suisung.mall.common.pojo.req.*;
import com.suisung.mall.common.pojo.res.SFExpressApiRes;
import com.suisung.mall.common.utils.JsonUtil;
import com.suisung.mall.shop.sfexpress.service.SFExpressApiService;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class SFExpressApiServiceImpl implements SFExpressApiService {
private final static String sfExpressApiDomain = "https://openic.sf-express.com/open/api/external/";
private static final Logger logger = LoggerFactory.getLogger(SFExpressApiServiceImpl.class);
@Value("${sf-express.appid}")
private Long appId;
@Value("${sf-express.appkey}")
private String appKey;
@Value("${sf-express.dev_id}")
private String devId;
@Override
public SFExpressApiRes createOrder(String shopOrderId) {
// 组织请求参数
// Map<String, Object> params = buildCommonParams();
// params.put("app_id", appId);
// params.put("app_key", appKey);
// params.put("device_id", devId);
Long now = DateUtil.currentSeconds();
SFCreateOrderReq param = new SFCreateOrderReq();
param.setDev_id(1711573316);
param.setVersion(19);
param.setOrder_time(now);
param.setPush_time(now);
param.setRemark("测试顺丰同城发单,请不要通知骑手接单!");
param.setOrder_sequence("000000123");
param.setShop_id("3243279847393");
param.setShop_order_id(shopOrderId); //"DD-20241118-00001"
param.setReturn_flag(511);
SFOrderDetailReq orderDetail = new SFOrderDetailReq();
orderDetail.setTotal_price(2000);
orderDetail.setProduct_type(6);
orderDetail.setWeight_gram(0); // 重量一律传 0kg 谢总本地协商好的
orderDetail.setProduct_num(2);
orderDetail.setProduct_type_num(1);
// 产品详情
SFOrderProductDetailReq orderProductDetail = new SFOrderProductDetailReq();
orderProductDetail.setProduct_id(1L);
orderProductDetail.setProduct_name("猪腿肉500g");
orderProductDetail.setProduct_num(2);
List<SFOrderProductDetailReq> orderProductList = new ArrayList<>();
orderProductList.add(orderProductDetail);
orderDetail.setProduct_detail(orderProductList);
param.setOrder_detail(orderDetail);
SFOrderShopReq shop = new SFOrderShopReq();
shop.setShop_name("顺丰同城开放平台");
shop.setShop_address("蜂巢工场西区");
shop.setShop_phone("13203559287");
shop.setShop_lng("116.327914");
shop.setShop_lat("40.045488");
param.setShop(shop);
SFOrderReceiveReq receive = new SFOrderReceiveReq();
receive.setUser_name("顺丰同城");
receive.setUser_phone("13881979410");
receive.setUser_address("北京市海淀区学清嘉创大厦A座15层");
receive.setUser_lng("116.352843");
receive.setUser_lat("40.015028");
param.setReceive(receive);
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(param);
// 根据参数生成请求签名
String send_url = buildUrl("createorder", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城:创建订单异常,无返回值!");
return null;
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 取消订单
*
* @param sfOrderId 顺丰订单号
* @param cancelCode 取消码参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @param cancelReason 取消原因
* @return
*/
@Override
public SFExpressApiRes cancelOrder(String sfOrderId, Integer cancelCode, String cancelReason) {
Map<String, Object> params = buildCommonParams();
return cancelOrder(params);
}
/**
* 取消订单
*
* @param params 综合参数请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes cancelOrder(Map<String, Object> params) {
if (params == null || params.get("order_id") == null) {
return new SFExpressApiRes().fail(1003,"请求参数有误!");
}
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("cancelorder", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城:取消订单异常,无返回值!");
return new SFExpressApiRes().fail(-1,"顺丰同城:无返回值!");
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 订单加小费订单创建后骑士未接单的情况下通过该接口对订单进行加小费促进订单接单截止订单完成前都可以对订单加小费
*
* @param params 综合参数顺丰订单号order_id订单小费 gratuity_fee必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes addOrderGratuityFee(Map<String, Object> params) {
if (params == null || params.get("order_id") == null || params.get("gratuity_fee") == null) {
return new SFExpressApiRes().fail(1003,"请求参数有误!");
}
params.putAll(buildCommonParams());
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("addordergratuityfee", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城:订单加小费,无返回值!");
return new SFExpressApiRes().fail(-1,"顺丰同城:无返回值!");
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 催单当订单为配送状态中可通过该接口发起催单
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes reminderOrder(Map<String, Object> params) {
if (params == null || params.get("order_id") == null) {
return new SFExpressApiRes().fail(1003,"请求参数有误!");
}
params.putAll(buildCommonParams());
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("reminderorder", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城:催单异常,无返回值!");
return new SFExpressApiRes().fail(-1,"顺丰同城:无返回值!");
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 获取配送员实时坐标接口此接口用于获取订单配送员的实时经纬度坐标一般情况下骑士经纬度30s更新一次
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes riderLatestPosition(Map<String, Object> params) {
if (params == null || params.get("order_id") == null) {
return new SFExpressApiRes().fail(1003,"请求参数有误!");
}
params.putAll(buildCommonParams());
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("riderlatestposition", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城:获取配送员实时坐标异常,无返回值!");
return new SFExpressApiRes().fail(-1,"顺丰同城:无返回值!");
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 获取配送员轨迹H5此接口可获取一个订单的骑士位置H5链接可进行内嵌或发送给用户内嵌时无法保证界面的兼容性如发现兼容性问题可使用获取配送员坐标接口自行开发轨迹H5
*
* @param params 综合参数顺丰订单号order_id必填项请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes riderViewV2(Map<String, Object> params) {
if (params == null || params.get("order_id") == null) {
return new SFExpressApiRes().fail(1003,"请求参数有误!");
}
params.putAll(buildCommonParams());
// 转换 json 字符串参数
String paramJSON = JsonUtil.toJSONString(params);
// 根据参数生成请求签名
String send_url = buildUrl("riderviewv2", paramJSON);
String retRespStr = HttpUtil.post(send_url, paramJSON);
if (StrUtil.isEmpty(retRespStr)) {
logger.error("顺丰同城获取配送员轨迹H5异常无返回值");
return new SFExpressApiRes().fail(-1,"顺丰同城:无返回值!");
}
return JsonUtil.json2object(retRespStr, SFExpressApiRes.class);
}
/**
* 改单支持店铺和企业客户改单,当订单生成后可通过该接口修改收件人信息,可修改字段:收件地址物品重量等,详情可参考请求参数列表
*
* @param params 综合参数请参考https://openic.sf-express.com/open/api/docs/index/#/apidoc
* @return
*/
@Override
public SFExpressApiRes changeOrder(Map<String, Object> params) {
return null;
}
/**
* 接收顺丰原因订单取消回调
*
* @param jsonData
* @param sign
* @return
*/
@Override
public SFExpressApiRes receiveCancelOrderNotify(String jsonData, String sign) {
if (StrUtil.isBlank(jsonData) || StrUtil.isBlank(sign)) {
return new SFExpressApiRes().fail(1003, "缺少必要参数!");
}
if (!checkOpenSign(sign, jsonData)) {
return new SFExpressApiRes().fail(2002, "请求签名sign校验失败");
}
logger.info("接收顺丰原因订单取消回调返回的 JSON 数据:{}", jsonData);
return new SFExpressApiRes().success("success");
}
/**
* 接收顺丰配送状态更改回调
*
* @param jsonData
* @param sign
* @return
*/
@Override
public SFExpressApiRes receiveRiderOrderStatusNotify(String jsonData, String sign) {
if (StrUtil.isBlank(jsonData) || StrUtil.isBlank(sign)) {
return new SFExpressApiRes().fail(1003, "缺少必要参数!");
}
if (!checkOpenSign(sign, jsonData)) {
return new SFExpressApiRes().fail(2002, "请求签名sign校验失败");
}
logger.info("接收顺丰原因订单取消回调返回的 JSON 数据:{}", jsonData);
return new SFExpressApiRes().success("success");
}
/**
* 接收顺丰订单完成回调
*
* @param jsonData
* @param sign
* @return
*/
@Override
public SFExpressApiRes receiveOrderCompleteNotify(String jsonData, String sign) {
if (StrUtil.isBlank(jsonData) || StrUtil.isBlank(sign)) {
return new SFExpressApiRes().fail(1003, "缺少必要参数!");
}
if (!checkOpenSign(sign, jsonData)) {
return new SFExpressApiRes().fail(2002, "请求签名sign校验失败");
}
logger.info("接收顺丰原因订单取消回调返回的 JSON 数据:{}", jsonData);
return new SFExpressApiRes().success("success");
}
// 私有方法
/**
* 生成顺丰同城请求签名参考官网https://commit-openic.sf-express.com/#/apidoc
*
* @param postData
* @return
*/
private String generateOpenSign(String postData) {
try {
String sb = postData +
"&" + appId + "&" + appKey;
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5 = md.digest(sb.getBytes(StandardCharsets.UTF_8));
int i;
StringBuffer buf = new StringBuffer();
for (byte b : md5) {
i = b;
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
return Base64.encodeBase64String(buf.toString().getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
logger.error(e.getMessage());
return "";
}
}
/**
* 验证签名是否一致
*
* @param sign
* @param postData
* @return
*/
private boolean checkOpenSign(String sign, String postData) {
if (StrUtil.isBlank(sign) || StrUtil.isBlank(postData)) {
return false;
}
String newSign = generateOpenSign(postData);
return sign.equals(newSign);
}
private Map<String, Object> buildCommonParams() {
Map<String, Object> params = new HashMap<>();
params.put("dev_id", devId);
params.put("push_time", DateUtil.currentSeconds());
return params;
}
/**
* 组装请求地址
*
* @param urlPath
* @param postData
* @return
*/
private String buildUrl(String urlPath, String postData) {
String sb = sfExpressApiDomain + urlPath +
"?sign=" +
generateOpenSign(postData);
return sb;
}
}