海关跨境电商进口统一版信息化系统平台数据实时获取接口(试行) java版

海关跨境电商进口统一版信息化系统平台数据实时获取接口(试行) java版

海关总署公告:
http://www.customs.gov.cn/customs/302249/302266/302267/2134975/index.html 165号

http://www.customs.gov.cn/customs/302249/302266/302267/2155884/index.html 179号

这个接口对接已经懒得吐槽了,搞不懂一个技术文档怎么就写的这么官方!!!连个具体的sdk都没有!!!好了 我承认我有吐槽了。
现有的对接方法基本可看下网上的信息,可以了解80%
https://blog.csdn.net/u010955036/article/details/88712577
https://www.cnblogs.com/whtydn/p/10220209.html
尤其是第二个可以仔细看下 干货很多,流程也很细,唯一的缺点就是是用C#实现的报文,java的报文虽然有提及,但没有具体的实现方式,所以本文打算作一个补充说明。

  1. 加签验签会包含两个工具,一个html+js加签工具 主要是加密报文和获取序列号,由于给到海关需要数字证书,所以这个工具没啥用,可以略过。第二个,debug工具,非常有用,怎么用看上面第二链接文章,注意的一点,一定好在插IC卡的电脑启动工具,其次获取数字证书的时候依次打卡卡-》验证口令-》读取证书-》证书序列号就行了,***注意不需要浏览添加报文,和签名***我第一次在这个地方费了好久搞报文!!!
  2. 如果是C#写的可以按照第二篇文章第5步拼接报文,如果是java的就需要按照其他语言给的格式用webSocket发送报文,这也是我后面要实现的。
  3. 如果是java语言写的需要用到张弓写的webSocket实现类,具体可以下载:
    Java 张工版下载地址: https://pan.baidu.com/s/1beifsbtA7fXmi4vJ3c2Kjw 提取码: jdev
package omni.purcotton.omni.customs.api;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.purcotton.omni.common.exception.CommonException;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import omni.purcotton.omni.customs.config.CustomsConfig;
import omni.purcotton.omni.customs.feign.OrderService;
import omni.purcotton.omni.customs.feign.PayService;
import omni.purcotton.omni.customs.pojo.data.OrderItemVo;
import omni.purcotton.omni.customs.pojo.data.OrderMainVo;
import omni.purcotton.omni.customs.pojo.data.PayInfoVo;
import omni.purcotton.omni.customs.pojo.data.SignReqDTO;
import omni.purcotton.omni.customs.pojo.request.GoodsInfo;
import omni.purcotton.omni.customs.pojo.request.PayExInfoStr;
import omni.purcotton.omni.customs.pojo.request.PayExchangeInfoHead;
import omni.purcotton.omni.customs.pojo.request.PayExchangeInfoList;
import omni.purcotton.omni.customs.pojo.response.RealTimeDataUploadResponse;
import omni.purcotton.omni.customs.socket.WebCallback;
import omni.purcotton.omni.customs.socket.WebSocketClientHandle;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.*;

/**
 * [简要描述]:
* [详细描述]:
*/ @Slf4j @RestController @RequestMapping("/api/customs/realtime") @Log public class RealTimeDataUploadApi { @Autowired private WebSocketClientHandle webSocketClientHandle; @Autowired private OrderService orderService; @Autowired private PayService payService; private static final Logger LOGGER = LoggerFactory.getLogger(RealTimeDataUploadApi.class); @Autowired private CustomsConfig customsConfig; static Gson gson = new GsonBuilder().disableHtmlEscaping().create(); @PostMapping(value = "/platDataOpen", headers = "content-type=application/x-www-form-urlencoded") public JSONObject platDataOpen(@RequestParam String openReq) { openReq = StringUtils.replace(openReq, """, "\""); JSONObject jsonObject = JSONObject.parseObject(openReq); String orderNo = jsonObject.getString("orderNo"); String sessionID = jsonObject.getString("sessionID"); Long serviceTime = jsonObject.getLong("serviceTime"); PayExInfoStr payExInfoStr = getPayExInfoStr(orderNo, sessionID, customsConfig); SignReqDTO signReqDTO = new SignReqDTO(); signReqDTO.setKey(payExInfoStr.getSessionID()); signReqDTO.setMessage(getSendData(payExInfoStr, customsConfig)); LOGGER.info("入参-{}", signReqDTO); webSocketClientHandle.send(signReqDTO.getKey(), signReqDTO.getMessage(), new ImplWebCallback(payExInfoStr)); JSONObject result = new JSONObject(); result.put("code", "10000"); result.put("message", ""); result.put("serviceTime", System.currentTimeMillis()); return result; } private void setPostData(PayExInfoStr payExInfoStr) { CloseableHttpClient client = HttpClients.createDefault(); URIBuilder uriBuilder = null; CloseableHttpResponse response = null; List nameValuePairList = new ArrayList<>(); String json = gson.toJson(payExInfoStr); LOGGER.info("上传给海关的json:" + json); nameValuePairList.add(new BasicNameValuePair("payExInfoStr", json)); try { uriBuilder = new URIBuilder(customsConfig.getRealTimeUrl()); uriBuilder.addParameters(nameValuePairList); HttpPost httpPost = new HttpPost(uriBuilder.build()); httpPost.setHeader(new BasicHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")); try { response = client.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity, "UTF-8"); RealTimeDataUploadResponse responseData = gson.fromJson(result, RealTimeDataUploadResponse.class); LOGGER.info(gson.toJson(responseData)); } } catch (IOException e) { e.printStackTrace(); } } catch (URISyntaxException e) { e.printStackTrace(); } } public static String getSignJson(PayExInfoStr payExInfoStr) { StringBuilder buff = new StringBuilder(); buff.append("\"sessionID\"").append(":").append("\"" + payExInfoStr.getSessionID() + "\"").append("||"); buff.append("\"payExchangeInfoHead\"").append(":").append("\"" + gson.toJson(payExInfoStr.getPayExchangeInfoHead()) + "\"").append("||"); buff.append("\"payExchangeInfoLists\"").append(":").append("\"" + gson.toJson(payExInfoStr.getPayExchangeInfoLists()) + "\"").append("||"); buff.append("\"serviceTime\"").append(":").append("\"" + payExInfoStr.getServiceTime() + "\""); LOGGER.info("加签准备报文:-------" + buff.toString()); return buff.toString(); } public static String getSendData(PayExInfoStr payExInfoStr, CustomsConfig customsConfig) { LinkedHashMap map = new LinkedHashMap<>(); Map data = new HashMap<>(16); map.put("_method", "cus-sec_SpcSignDataAsPEM"); map.put("_id", 1); data.put("inData", getSignJson(payExInfoStr)); data.put("passwd", customsConfig.getPassword()); map.put("args", data); return gson.toJson(map); } private PayExInfoStr getPayExInfoStr(String orderNo, String sessionID, CustomsConfig customsConfig) { OrderMainVo orderMainVo = orderService.find(orderNo); if (orderMainVo == null) { LOGGER.info("订单:" + orderNo + "查询不到订单信息"); throw new CommonException(201, "订单:" + orderNo + "查询不到订单信息"); } PayInfoVo payInfoVo = payService.getPayInfo(orderNo); if (payInfoVo == null) { LOGGER.info("订单:" + orderNo + "查询不到支付信息"); throw new CommonException(201, "订单:" + orderNo + "查询不到支付信息"); } //todo 拼接报文信息 PayExInfoStr payExInfoStr = new PayExInfoStr(); payExInfoStr.setSessionID(sessionID); PayExchangeInfoHead payExchangeInfoHead = new PayExchangeInfoHead(); payExchangeInfoHead.setGuid(UUID.randomUUID().toString()); payExchangeInfoHead.setInitalRequest(payInfoVo.getInitalRequest()); payExchangeInfoHead.setInitalResponse(payInfoVo.getInitalResponse()); payExchangeInfoHead.setEbpCode(customsConfig.getEbpEntNo()); payExchangeInfoHead.setPayCode(customsConfig.getPayCode()); payExchangeInfoHead.setPayTransactionId(payInfoVo.getPayTransactionId()); payExchangeInfoHead.setTotalAmount(payInfoVo.getTotalAmount().doubleValue()); payExchangeInfoHead.setCurrency("142"); payExchangeInfoHead.setVerDept("3"); payExchangeInfoHead.setPayType("3"); payExchangeInfoHead.setTradingTime(DateUtil.format(payInfoVo.getTradingTime(), DatePattern.PURE_DATETIME_PATTERN)); payExchangeInfoHead.setNote(""); payExInfoStr.setPayExchangeInfoHead(payExchangeInfoHead); PayExchangeInfoList payExchangeInfoList = new PayExchangeInfoList(); payExchangeInfoList.setOrderNo(orderNo); List orderItemVos = orderMainVo.getOrderItemVos(); List goodsInfos = new ArrayList<>(); for (OrderItemVo orderItemVo : orderItemVos) { GoodsInfo goodsInfo = new GoodsInfo(); goodsInfo.setGname(orderItemVo.getProductName()); goodsInfo.setItemLink(orderItemVo.getImagePath()); goodsInfos.add(goodsInfo); } payExchangeInfoList.setGoodsInfo(goodsInfos); payExchangeInfoList.setRecpAccount(customsConfig.getRecpAccount()); payExchangeInfoList.setRecpCode(customsConfig.getRecpCode()); payExchangeInfoList.setRecpName(customsConfig.getRecpName()); List payExchangeInfoLists = new ArrayList<>(); payExchangeInfoLists.add(payExchangeInfoList); payExInfoStr.setPayExchangeInfoLists(payExchangeInfoLists); payExInfoStr.setServiceTime(System.currentTimeMillis()); payExInfoStr.setCertNo(customsConfig.getCertNo()); return payExInfoStr; } class ImplWebCallback implements WebCallback { PayExInfoStr payExInfoStr; public ImplWebCallback(PayExInfoStr payExInfoStr) { this.payExInfoStr = payExInfoStr; } @Override public void message(String key, Object param) { LOGGER.info("key-{}, message-{}", key, param); String signValue = null; if (!StringUtils.isEmpty(param)) { JSONObject jsonObject = JSON.parseObject(String.valueOf(param)); Integer id = jsonObject.getInteger("_id"); if (id != null && id == 1) { JSONObject args = jsonObject.getJSONObject("_args"); JSONArray data = args.getJSONArray("Data"); signValue = data.getString(0); LOGGER.info("key-{}, signValue-{}", key, signValue); payExInfoStr.setSignValue(signValue); setPostData(payExInfoStr); } } // todo 放入处理队列 } @Override public void error(Object param) { LOGGER.info("error-{}", param); } } }

上面接口是经过修改联调后的最终版,最关键的报文拼接和websocket发送机制,大家可以参考一下,避免躺坑。

补充说明:
上面的代码经过测试有两个问题,现在修复如下:
1、CustomsConfig 是一个自定义配置信息,相关的海关参数配置都包含在里面,涉及系统敏感信息就不贴了
2、海关给出的报文属性是payExchangeInfoList!!!注意是错的 应该是payExchangeInfoLists,需要加s.
海关跨境电商进口统一版信息化系统平台数据实时获取接口(试行) java版_第1张图片

你可能感兴趣的:(java)