背景介紹:APP內商城,需要對接第三方快遞,公司本身和順豐有月結賬戶合作,故選用順豐。
開發之前
開始要到豐橋官網註冊賬號,豐橋會提供客戶編碼和校驗碼到你的郵箱;官方有接入教程,沒啥可糾結的,一路申請到底。
因爲我這邊散戶多,用戶量小,沒有對接面單打印SDK,和我一樣需求的,可申請免面單,完成審覈即可;
可在“我的API”中添加需要的接口,最後添加順豐月結賬號,以配置生產環境。
接入SDK
在豐橋官網–幫助中心–軟件下載中,有一個官方提供的sdk,下載下來,如圖所示:
將SF-CSIM-EXPRESS-SDK-V1.6.jar
放在項目lib下,在pom文件中添加依賴
<dependency>
<groupId>ShunFeng</groupId>
<artifactId>ShunFeng</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/src/lib/SF-CSIM-EXPRESS-SDK-V1.6.jar</systemPath>
</dependency>
添加一橋靜態變量配置類,替換成你的參數.
package com.chain.express.config;
/**
* @Auther: songweichao
* @Date: 2020/1/7 12:21
* @Description:
*/
public class ShunFenConfig {
// 月結卡號
public static String cardId="********";
//豐橋平臺顧客編碼
public static String clientCode="********";
//豐橋平臺校驗碼
public static String checkword="********";
//豐橋平臺下訂單url
public static String accessUrl="http://bsp-oisp.sf-express.com/bsp-oisp/sfexpressService";
}
2.1、工具類
順豐使用的數據交換格式是xml,需要一個xml轉實體的工具類。
package com.chain.express.utils;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import java.io.Reader;
import java.io.StringReader;
/**
* XML轉實體互轉工具類
* @Auther: songweichao
* @Date: 2020/1/8 10:20
* @Description:
*/
public class XmlBuilder {
/**
* 將XML轉爲指定的BEAN
* @param clazz
* @param xmlStr
* @return
* @throws Exception
*/
public static Object xmlStrToOject(Class<?> clazz, String xmlStr) throws Exception {
Object xmlObject = null;
Reader reader = null;
JAXBContext context = JAXBContext.newInstance(clazz);
// XML 轉爲對象的接口
Unmarshaller unmarshaller = context.createUnmarshaller();
reader = new StringReader(xmlStr);
xmlObject = unmarshaller.unmarshal(reader);
if (null != reader) {
reader.close();
}
return xmlObject;
}
}
添加一個順豐組裝請求xml的工具類
package com.chain.express.utils;
import com.chain.base.utils.BaseLogger;
import com.chain.express.config.ShunFenConfig;
import com.chain.order.entity.Order;
/**
* 順豐工具類
* @Auther: songweichao
* @Date: 2020/1/7 12:02
* @Description:
*/
public class ExpressUtils extends BaseLogger{
/**
* 下訂單
* order 是我自己的訂單類,換成你業務中有的POJO
* @param order
* @return
*/
public static String produce(Order order){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
strBuilder.append("<Request service='OrderService' lang='zh-CN'>");
strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
strBuilder.append("<Body>");
strBuilder.append("<Order").append(" ");
strBuilder.append("orderid='" + order.getOrderNumber() + "'").append(" ");
// 1 代表順丰標快 -- 詳情查看平臺 快件產品類別表,注意:標快支持子母單,速配不支持
strBuilder.append("express_type='1'").append(" ");
// 寄件方信息
strBuilder.append("j_province='北京'").append(" ");
strBuilder.append("j_city='北京市'").append(" ");
strBuilder.append("j_county='中國'").append(" ");
strBuilder.append("j_company='超琦科技有限公司'").append(" ");
strBuilder.append("j_contact='客服007'").append(" ");
strBuilder.append("j_tel='18888888888'").append(" ");
strBuilder.append("j_address='北京市朝陽區'").append(" ");
// 收件方信息
strBuilder.append("d_province='" + order.getReceiverProvince() + "'").append(" ");
strBuilder.append("d_city='" + order.getReceiverCity() + "'").append(" ");
strBuilder.append("d_county='" + order.getReceiverCounty() + "'").append(" ");
strBuilder.append("d_county=''").append(" ");
strBuilder.append("d_company='ceshi'").append(" ");
strBuilder.append("d_tel='" + order.getReceiverPhone() + "'").append(" ");
strBuilder.append("d_contact='" + order.getReceiverPhone() + "'").append(" ");
strBuilder.append("d_address='" + order.getReceiverAddress() + "'").append(" ");
strBuilder.append("remark='" + order.getRemark() + "'").append(" ");
strBuilder.append("parcel_quantity='1'").append(" ");
strBuilder.append("pay_method='1'").append(" ");
strBuilder.append("custid ='"+ ShunFenConfig.cardID+"'").append(" ");
strBuilder.append("customs_batchs=''").append(" ");
// is_docall這個參數,當你沒有申請面單打印,那就取值爲1,表示由快遞小哥上門取件
strBuilder.append("is_docall='1'").append(">");
strBuilder.append("</Order>");
strBuilder.append("</Body>");
strBuilder.append("</Request>");
return strBuilder.toString();
}
/**
* 訂單結果查詢
* 訂單查詢接口是在下單後沒有返回運單號時主動查詢運單號使用
* @param orderId
* @return
*/
public static String query(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
strBuilder.append("<Request service='OrderSearchService' lang='zh-CN'>");
strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
strBuilder.append("<Body>");
strBuilder.append("<OrderSearch orderid='"+orderId+"'/>");
strBuilder.append("</Body>");
strBuilder.append("</Request>");
return strBuilder.toString();
}
/**
* 客戶在確定將貨物交付給順豐託運後,將面單上的一些重要信息,如快件重量通過此接口發送給順豐
* @param orderId
* @return
*/
public static String confirm(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
strBuilder.append("<Request service='OrderConfirmService' lang='zh-CN'>");
strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
strBuilder.append("<Body>");
strBuilder.append("<OrderConfirm orderid='"+orderId+"'dealtype='1' />");
strBuilder.append("</Body>");
strBuilder.append("</Request>");
return strBuilder.toString();
}
/**
* 客戶在發貨前取消訂單
* @param orderId
* @return
*/
public static String cancle(String orderId){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
strBuilder.append("<Request service='OrderConfirmService' lang='zh-CN'>");
strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
strBuilder.append("<Body>");
strBuilder.append("<OrderConfirm orderid='"+orderId+"' dealtype='2' >");
strBuilder.append("</OrderConfirm>");
strBuilder.append("</Body>");
strBuilder.append("</Request>");
return strBuilder.toString();
}
/**
* 客戶可通過此接口查詢順豐運單路由,系統將返回當前時間點已發生的路由信息
* @param expressNumber
* @return
*/
public static String trace(String expressNumber){
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("<?xml version='1.0' encoding='UTF-8'?>");
strBuilder.append("<Request service='RouteService' lang='zh-CN'>");
strBuilder.append("<Head>" + ShunFenConfig.clientCode + "</Head>");
strBuilder.append("<Body>");
strBuilder.append("<RouteRequest tracking_type='1' method_type='1' tracking_number='"+expressNumber+"' />");
strBuilder.append("</RouteRequest>");
strBuilder.append("</Body>");
strBuilder.append("</Request>");
return strBuilder.toString();
}
}
2.2、測試中
測試下單接口:
public static void main(String[] args) {
/****************************** 下單測試*****************************/
Order order = new Order();
order.setOrderNumber("TEST20200110");
order.setReceiverAddress("北京市豐臺區花鄉橋");
order.setReceiver("Test");
order.setReceiverCounty("中國");
order.setReceiverProvince("北京市");
order.setReceiverCity("北京市");
order.setReceiverPhone("18888888886");
String produceBody = ExpressUtils.produce(order);
System.out.println("下單請求報文:" + produceBody);
String respXml = CallExpressServiceTools.callSfExpressServiceByCSIM(ShunFenConfig.accessUrl, produceBody, ShunFenConfig.clientCode, ShunFenConfig.checkword);
System.out.println("下單響應報文:" + respXml);
SfExpressResponse sfExpressResponse = null;
try {
sfExpressResponse = (SfExpressResponse) XmlBuilder.xmlStrToOject(SfExpressResponse.class, respXml);
OrderResponse orderResponse = sfExpressResponse.getBody().getOrderResponse();
System.out.println("下訂單返回狀態碼:"+orderResponse.getFilterResult() + "順豐單號:"+ orderResponse.getMailNo());
} catch (Exception e) {
System.out.println("下訂單返回異常狀態碼:"+sfExpressResponse.getERROR().getCode() + "響應正文:"+sfExpressResponse.getERROR().getText());
e.printStackTrace();
}
}
返回結果:
// 堆棧信息省略......
下單請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='OrderService' lang='zh-CN'><Head>SXWXJS</Head><Body><Order orderid='TEST202001101443' express_type='1' j_province='北京' j_city='北京市' j_county='中國' j_company='超琦科技有限公司' j_contact='客服007' j_tel='18888888888' j_address='北京市朝陽區' d_province='北京市' d_city='北京市' d_county='中國' d_county='' d_company='ceshi' d_tel='18888888886' d_contact='18888888886' d_address='北京市豐臺區花鄉橋' parcel_quantity='1' pay_method='1' custid ='**********' customs_batchs='' is_docall='1'></Order></Body></Request>
14:43:37.505 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
14:43:37.539 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
//中間日誌省略了......
下單響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="OrderService"><Head>OK</Head><Body><OrderResponse filter_result="2" destcode="010" mailno="SF7444402649723" origincode="010" orderid="TEST202001101443"><rls_info rls_errormsg="SF7444402649723:" invoke_result="OK" rls_code="1000"><rls_detail waybillNo="SF7444402649723" sourceCityCode="010" destCityCode="010" destDeptCode="010FT" destDeptCodeMapping="010WB" destTeamCode="044" destTransferCode="010WB" destRouteLabel="010WB-010FT" proName="順丰標快" cargoTypeCode="C201" limitTypeCode="T4" expressTypeCode="B1" codingMapping="B1B" xbFlag="0" printFlag="000000000" twoDimensionCode="MMM={'k1':'010WB','k2':'010FT','k3':'044','k4':'T4','k5':'SF7444402649723','k6':'','k7':'166127c4'}" proCode="T4" printIcon="00000000" checkCode="166127c4" destGisDeptCode="010FT"/></rls_info></OrderResponse></Body></Response>
下訂單返回狀態碼:2順豐單號:SF7444402649723
當然了,你要以同樣的單號再次下單,會返回重複下單的錯誤
//堆棧信息省略......
下單請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='OrderService' lang='zh-CN'><Head>SXWXJS</Head><Body><Order orderid='TEST202001101443' express_type='1' j_province='北京' j_city='北京市' j_county='中國' j_company='超琦科技有限公司' j_contact='客服007' j_tel='18888888888' j_address='北京市朝陽區' d_province='北京市' d_city='北京市' d_county='中國' d_county='' d_company='ceshi' d_tel='18888888886' d_contact='18888888886' d_address='北京市豐臺區花鄉橋' parcel_quantity='1' pay_method='1' custid ='**********' customs_batchs='' is_docall='1'></Order></Body></Request>
//中間信息省略......
下單響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="OrderService"><Head>ERR</Head><ERROR code="8016">重複下單</ERROR></Response>
java.lang.NullPointerException
at com.chain.pay.service.impl.AliPayServiceImpl.main(AliPayServiceImpl.java:397)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
下訂單返回異常狀態碼:8016響應正文:重複下單
路由接口測試:
public static void main(String[] args) {
/******************************* 路由查詢測試******************************/
System.out.println("-----------------------------------------------");
String traceBody = ExpressUtils.trace("SF7444402649981");
System.out.println("請求報文:" + traceBody);
String resXml3 = CallExpressServiceTools.callSfExpressServiceByCSIM(ShunFenConfig.accessUrl, traceBody, ShunFenConfig.clientCode, ShunFenConfig.checkword);
System.out.println("響應報文:" + resXml3);
try {
SfExpressResponse sfExpressResponse = (SfExpressResponse) XmlBuilder.xmlStrToOject(SfExpressResponse.class, resXml3);
List<Route> list = sfExpressResponse.getBody().getRouteResponse().getRoute();
for (int i = 0; i < list.size(); i++) {
Route route = list.get(i);
System.out.println("-----------路由消息第" + i + "條:-------------");
System.out.println("發生時間:"+route.getAcceptTime());
System.out.println("節點操作碼:"+route.getOpcode());
System.out.println("具體描述:"+route.getRemark());
}
} catch (Exception e) {
e.printStackTrace();
}
返回結果:
//堆棧信息省略...
-----------------------------------------------
請求報文:<?xml version='1.0' encoding='UTF-8'?><Request service='RouteService' lang='zh-CN'><Head>SXWXJS</Head><Body><RouteRequest tracking_type='1' method_type='1' tracking_number='SF7444402649981' /></RouteRequest></Body></Request>
//中間信息省略...
響應報文:<?xml version='1.0' encoding='UTF-8'?><Response service="RouteService"><Head>OK</Head><Body><RouteResponse mailno="SF7444402649981" orderid="TEST202001101505"><Route remark="順豐速運 已收取快件(測試數據)" accept_time="2018-05-01 08:01:44" accept_address="廣東省深圳市軟件產業基地" opcode="50"/><Route remark="已簽收,感謝使用順豐,期待再次爲您服務(測試數據)" accept_time="2018-05-02 12:01:44" accept_address="廣東省深圳市軟件產業基地" opcode="80"/></RouteResponse></Body></Response>
-----------路由消息第0條:-------------
發生時間:2018-05-01 08:01:44
節點操作碼:50
具體描述:順豐速運 已收取快件(測試數據)
-----------路由消息第1條:-------------
發生時間:2018-05-02 12:01:44
節點操作碼:80
具體描述:已簽收,感謝使用順豐,期待再次爲您服務(測試數據)
2.3 、JavaBean
下面幾個類是關於順豐響應碼的實體類
package com.chain.express.entity;
import javax.xml.bind.annotation.*;
import java.io.Serializable;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:04
* @Description:
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Response")
public class SfExpressResponse implements Serializable {
private static final long serialVersionUID = 1L;
//響應狀態
@XmlElement(name = "Head")
private String Head;
//響應失敗原因
@XmlElement(name = "ERROR")
private ERROR ERROR;
//響應結果
@XmlElement(name = "Body")
private Body Body;
public String getHead() {
return Head;
}
public void setHead(String head) {
Head = head;
}
public com.chain.express.entity.ERROR getERROR() {
return ERROR;
}
public void setERROR(com.chain.express.entity.ERROR ERROR) {
this.ERROR = ERROR;
}
public com.chain.express.entity.Body getBody() {
return Body;
}
public void setBody(com.chain.express.entity.Body body) {
Body = body;
}
}
package com.chain.express.entity;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:05
* @Description:
*/
@XmlAccessorType(XmlAccessType.NONE)
public class ERROR {
@XmlAttribute(name = "code")
private String code;
@XmlValue
private String text;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
package com.chain.express.entity;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:06
* @Description:
*/
@XmlAccessorType(XmlAccessType.NONE)
public class Body {
@XmlElement(name = "OrderResponse")
private OrderResponse OrderResponse;
@XmlElement(name = "RouteResponse")
private RouteResponse RouteResponse;
public com.chain.express.entity.OrderResponse getOrderResponse() {
return OrderResponse;
}
public void setOrderResponse(com.chain.express.entity.OrderResponse orderResponse) {
OrderResponse = orderResponse;
}
public com.chain.express.entity.RouteResponse getRouteResponse() {
return RouteResponse;
}
public void setRouteResponse(com.chain.express.entity.RouteResponse routeResponse) {
RouteResponse = routeResponse;
}
}
package com.chain.express.entity;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:06
* @Description:
*/
@XmlRootElement(name="OrderResponse")
@XmlAccessorType(XmlAccessType.NONE)
public class OrderResponse {
//訂單號
@XmlAttribute(name = "orderid")
private String orderId;
//運單號
@XmlAttribute(name = "mailno")
private String mailNo;
//原寄地區域代碼(可用於順豐電子運單標籤打印)
@XmlAttribute(name = "origincode")
private String originCode;
//目的地區域代碼(可用於順豐電子運單標籤打印)
@XmlAttribute(name = "destcode")
private String destCode;
//篩單結果:1:人工確認 2:可收派 3:不可以收派
@XmlAttribute(name = "filter_result")
private String filterResult;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getMailNo() {
return mailNo;
}
public void setMailNo(String mailNo) {
this.mailNo = mailNo;
}
public String getOriginCode() {
return originCode;
}
public void setOriginCode(String originCode) {
this.originCode = originCode;
}
public String getDestCode() {
return destCode;
}
public void setDestCode(String destCode) {
this.destCode = destCode;
}
public String getFilterResult() {
return filterResult;
}
public void setFilterResult(String filterResult) {
this.filterResult = filterResult;
}
}
package com.chain.express.entity;
import javax.xml.bind.annotation.*;
import java.util.List;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:07
* @Description:
*/
@XmlRootElement(name="RouteResponse")
@XmlAccessorType(XmlAccessType.NONE)
public class RouteResponse {
//運單號
@XmlAttribute(name = "mailno")
private String mailNo;
//路由
@XmlElement(name = "Route")
private List<Route> Route ;
public String getMailNo() {
return mailNo;
}
public void setMailNo(String mailNo) {
this.mailNo = mailNo;
}
public List<com.chain.express.entity.Route> getRoute() {
return Route;
}
public void setRoute(List<com.chain.express.entity.Route> route) {
Route = route;
}
}
package com.chain.express.entity;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @Auther: songweichao
* @Date: 2020/1/8 10:08
* @Description:
*/
@XmlRootElement(name="Route")
@XmlAccessorType(XmlAccessType.NONE)
public class Route {
//路由節點發生的時間
@XmlAttribute(name = "accept_time")
private String acceptTime;
//路由節點具體描述
@XmlAttribute(name = "remark")
private String remark;
//路由節點操作碼
@XmlAttribute(name = "opcode")
private String opcode;
public String getAcceptTime() {
return acceptTime;
}
public void setAcceptTime(String acceptTime) {
this.acceptTime = acceptTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getOpcode() {
return opcode;
}
public void setOpcode(String opcode) {
this.opcode = opcode;
}
}
記錄下,方面下次使用。拿走拿走別客氣。