Java對接拼多多開放平臺API(加密上雲等全流程)

2022-07-16 12:00:31

前言


 

本文為【小小赫下士 blog】原創,搬運請保留本段,或請在醒目位置設定原文地址和原作者。

作者:小小赫下士

原文地址:Java對接拼多多開放平臺API(加密上雲等全流程)


 

本文章為企業ERP(ISV軟體服務商)對接拼多多開放平臺流程總結,文章包括開放平臺入駐、商家授權介面流程、API呼叫流程(訂單同步、發貨回傳以及其他介面的呼叫)、密文改造流程、拼多多電子面單呼叫流程、密文環境下發貨流程、拼多多雲服務以及服務入雲流程。

(自研或者其他型別服務商請勿參考)

一、開放平臺入駐

open平臺地址:拼多多 開放平臺

注意事項:

1:手機號如果繫結為子賬號不能再次註冊開放平臺賬號:;

2:一個公司主體只能入駐一次,請謹慎選擇入駐資質;

3:入駐資質暫時僅支援境內企業。

平臺新手指南指導檔案:新手指南

使用手機號註冊,密碼為英文加數位加特殊符號組合,註冊好後跳轉到首頁。

點選立即入駐。

選擇開發者角色為【電商軟體服務商】,入駐組織資質勾選【企業開發者】。

 

開發者角色的認證業務選擇【電商軟體服務商】,開發者資質選擇企業。 

需要的資料如下圖所示,按照要求填寫上傳。

完成後提交稽核,然後可以在上方【工單支援】裡發工單給平臺技術支援人員,讓平臺同學加急稽核一下。

稽核完成後即可開始後續工作。

二、官方溝通工具Knock

資質申請好後先不要著急建立應用開始對接,先了解一下Knock的使用,這個工具是和官方工作人員溝通的一個途徑(另一個途徑是平臺工單功能),平臺更具不同的業務場景提供不同的官方群組,先準備好工具以及掌握使用可以提高後續的開發效率。

官方Knock介紹檔案:KNOCK使用指南

子賬號管理指導檔案:子賬號管理

三、建立應用

注意事項:

1:要求需要上傳MRD檔案(市場需求檔案)以及PRD檔案(產品需求說明書),建立應用頁面會有模板提供下載,我自己儲存的模板下載地址:拼多多開放平臺應用建立所需檔案模板

2:需要提供一個小於100M的視訊,內容要求包括需要包含公司門頭、職工辦公場景,視訊需要一鏡到底,禁止剪輯。

【應用列表】進入【建立應用】,在【電商軟體服務商】欄目下選擇【企業ERP】。

官方企業ERP對接檔案:企業ERP

應用建立好後依舊需要稽核,可以工單催審一下。

應用資訊填寫只需要注意回撥地址一項,回撥地址就是用來接收授權資訊的服務介面地址,官方解釋為:

 應用建立好後,平臺會根據應用許可權集來提供Java開發SDK。

下載後自行設定到maven或者其他依賴管理工具裡。

應用詳情頁會提供 client_id、client_secret 這兩個應用祕鑰,儲存好並且不要洩露。

四、對接前準備

對接前請認真查閱以下官方提供的檔案,遵守平臺規則,切勿產生違規的行為:

根據平臺2021-04-13 11:44:11釋出的規則變更公告【應用安全規範進一步改造通知】一文,要求如下:

官方改造指南檔案地址:開放平臺應用安全規範改造指南 

新入駐的話可以直接按要求進行對應的開發,減少後續改造工作。

第一要求:確保敏感介面呼叫入雲,也就是涉及到敏感資料的介面呼叫必須在拼多多雲內呼叫,也就是需要購買拼多多平臺的雲主機服務,然後在雲主機上去呼叫這些介面;

第二要求:如上圖所示;

第三要求:規定不同的應用需要分別部署到不同的雲資源上,這個其實是可以部署在同一個雲主機上的,雲資料庫需要區分開。

第四要求和第五要求:沒有特殊需求的話只需要使用設定IP白名單就可以。

官方列舉的介面包括以下這些:

相關敏感介面(都是包含收件人資訊的介面,敏感資料也就是買家收件人資訊資料,雖然是密文...):

  • pdd.mall.info.get

  • pdd.order.information.get

  • pdd.order.list.get

  • pdd.order.number.list.get

  • pdd.order.number.list.increment.get

  • pdd.order.status.get

  • pdd.goods.information.get

  • pdd.goods.detail.get

  • pdd.goods.list.get

  • pdd.refund.address.list.get

  • pdd.refund.information.get

  • pdd.refund.list.increment.get

  • pdd.oversea.clearance.get

  • pdd.open.decrypt.batch

開發者需要確保應用對這些介面的呼叫是在多多雲內發起。(開發測試除外)

需要訂購的雲服務有(一下推薦設定可根據實際業務程度按需升配或者降配):

1:雲主機,服務部署使用,官方提供linux和windows兩種,推薦使用windows系統。

規格推薦:2核4G,Windows Server 2008 R2 SP1 64位元 伺服器版,磁碟規格60GB,大概2460/年。

官方購買雲主機指導檔案:購買雲主機

2:雲資料庫,店鋪授權後,設定好資料推播繫結,資料庫會同步店鋪的訂單資訊,後續需要在此資料庫內獲取店鋪的訂單資料,配合使用出雲存取服務可以把訂單資料發出雲,本地可以開發一個介面接收資料,並進行後續的操作。

規格推薦:1核2000M,25G儲存,大概2211/年。

官方購買雲資料庫指導檔案:購買資料庫

平臺雲資料庫的型別列表如下:

3:物件儲存 OSS服務,部署使用,Windows 雲主機需要通過物件儲存 OSS 來上傳與下載部署所需的程式包以及其他的檔案,通過OSS下載雲主機上的檔案的時候是需要通過稽核的。

OSS服務是按使用量每日計算費用,需要部署服務的時候需要用到,其他時間不計費,使用的當天大概也就幾分錢。

官方指導檔案:開通物件儲存 OSS

4:出雲存取服務 EGW,平臺為了安全考慮,部署在雲服務上的應用無法直接對外網發起存取,如果需要存取外網,比如公司的私有云服務,需要把訂單資料發到公司原生的服務介面(剛需),需要使用EGW服務,推薦使用基礎版。

EGW服務基礎版也是按使用量收費。

其他服務按需訂購使用,基礎的對接開發使用以上四個服務足夠。


本文為【小小赫下士 blog】原創,搬運請保留本段,或請在醒目位置設定原文地址和原作者。

作者:小小赫下士

原文地址:Java對接拼多多開放平臺API(加密上雲等全流程)


五、對接

0、拼多多密文規則

對接前請先熟知平臺敏感資訊加密策略,對應檔案地址:加密接入流程

官方提供的企業ERP接入流程方案:企業 ERP 類應用接入方案

密文規則:

敏感欄位列表

  • card_info_list:卡號、卡密
  • inner_transaction_id:支付申報訂單號
  • pay_no:支付單號
  • receiver_name:收件人姓名
  • receiver_phone:收件人電話
  • receiver_address:收件人地址,不拼接省市區
  • address:收件詳細地址
  • id_card_name:身份證姓名
  • id_card_num:身份證號

例如原收件人姓名為 【張三】,則相關介面收件人姓名欄位不在推播【張三】這個明文資訊, 改為密文【~AgAAAAEj89wFUIsEOACIVyeI2r7XMNRS+DzOX5wSpiE=~j8+QVnFC~5~~】這種形式;

可通過下文的獲取檢索串工具來獲取密文的檢索串,不同訂單但是收件人姓名相同則密文不同但是檢索串相同,可通過檢索串來進行判斷合併訂單等操作;

檢索串樣式【j8+QVnFC】;

然後可通過獲取脫敏資料介面:pdd.open.decrypt.mask.batch 批次資料解密脫敏介面

來獲取密文的脫敏的資料,可以用來前端展示,或者報表、匯出的檔案展示使用,樣式為【張*】

因為密文長度比較長,要把原資料庫對應欄位長度變更,並且需要新增兩個用來儲存檢索串以及脫敏資料的欄位。

而且密文改造後拼多多的訂單要必須使用拼多多電子面單來取號列印發貨,所以整個流轉過程不需要明文資訊,如果有特殊需求需要呼叫解密介面。

因為部分介面呼叫需要在雲上呼叫的原因,這裡提供的一個解決方案為,本地提供一個openAPI介面,雲上部署一個調取訂單資料以及傳送到openAPI上的執行緒任務。

簡單點來說就是:

本地伺服器部署/開發用來接收雲伺服器請求的API介面,雲伺服器部署一個專門用來往介面傳送資料的任務,其他業務操作在本地伺服器執行,介面呼叫在雲伺服器執行。

下面會列舉幾個剛需的介面對接實現以及方案,實際對接可以參考一下。

1、授權

官方指導檔案地址:授權說明

授權的具體流程:

客戶角度:

客戶在服務市場訂購本ERP服務後,可進入平臺給出的授權頁面:

(或在系統使用也面自行新增授權按鈕,自主組裝授權頁URL也是 )

(拼多多店鋪web端授權頁面範例)

(拼多多店鋪h5端授權頁面範例)

系統角度:

使用店鋪賬號授權後,授權碼code將返回到回撥地址中,以引數code形式組裝至回撥地址中,應用可以獲取並使用該code去換取access_token(介面呼叫需要使用的呼叫令牌)。

例如:我們在應用詳情頁填寫的回撥地址為 http://www.erp.com:80/pddaccess

則授權後會以 http://www.erp.com:80/pddaccess?code=asdf123asdf123 的形式請求過來。

code授權碼十分鐘內有效,授權碼可以用來存取獲取呼叫令牌(access_token)的介面。

code有效期內多次使用code換取access_token是一樣的。

簡單接收授權碼code

@Controller
public class PddAccess {
    @GetMapping("/pddaccess")
    @ResponseBody
    public void PddRe(HttpServletRequest request,HttpServletResponse response){
        try{
            int contentlen = request.getContentLength();
            String decode = "";
            if(-1 != contentlen) {
                BufferedReader reader = request.getReader();
                char[] buf = new char[contentlen];
                int len = 0;
                StringBuffer contentBuffer = new StringBuffer();
                while ((len = reader.read(buf)) != -1) {
                    contentBuffer.append(buf, 0, len);
                }
                String content = contentBuffer.toString();
                if (content == null) {
                    content = "";
                }
                decode = URLDecoder.decode(content, "UTF-8");
            }
            String code = request.getParameter("code");

            System.out.println(code);

        } catch (Exception e) {
            logger.error("拼多多授權異常:"+e.getMessage());
        }
    }
}

使用授權碼code 獲取存取介面獲取呼叫令牌(access_token)

(以及使用獲取到的access_token獲取授權的店鋪資訊,以便維護,資料按需儲存)

API檔案地址:pdd.pop.auth.token.create 獲取 Access Token

獲取店鋪資訊介面:pdd.mall.info.get 店鋪資訊介面

呼叫直接使用SDK封裝好的方法,簡單易用,每個API的檔案下都有請求範例,幾乎直接copy就可以。

// 應用的 client_id
private String clientId = "1111111111";

// 應用的 client_secret
private String clientSecret = "2222222222";

@Controller
public class PddAccess {
    @GetMapping("/pddaccess")
    @ResponseBody
    public void PddRe(HttpServletRequest request,HttpServletResponse response){
        try{
            int contentlen = request.getContentLength();
            String decode = "";
            if(-1 != contentlen) {
                BufferedReader reader = request.getReader();
                char[] buf = new char[contentlen];
                int len = 0;
                StringBuffer contentBuffer = new StringBuffer();
                while ((len = reader.read(buf)) != -1) {
                    contentBuffer.append(buf, 0, len);
                }
                String content = contentBuffer.toString();
                if (content == null) {
                    content = "";
                }
                decode = URLDecoder.decode(content, "UTF-8");
            }
            String code = request.getParameter("code");

            if(null != code && "".equals(code)){
                PopClient client = new PopHttpClient(clientId, clientSecret);

                PddPopAuthTokenCreateRequest pddPopAuthTokenCreateRequest = new PddPopAuthTokenCreateRequest();
                pddPopAuthTokenCreateRequest.setCode(code);
                PddPopAuthTokenCreateResponse pddPopAuthTokenCreateResponse = client.syncInvoke(pddPopAuthTokenCreateRequest);

                String ownerId = popAuthTokenCreateResponse.getOwnerId();   // 商家店鋪id,店鋪唯一標識
                String ownerName = popAuthTokenCreateResponse.getOwnerName();   // 商家賬號名稱
                String accessToken = popAuthTokenCreateResponse.getAccessToken();   // access_token
                String refreshToken = popAuthTokenCreateResponse.getRefreshToken(); // refresh token,可用來重新整理access_token
                Integer expiresIn = popAuthTokenCreateResponse.getExpiresIn();  // access_token過期時間段,10(表示10秒後過期,現令牌過期時間已與訂購時間相同)
                Date expiresDate = new Date(expiresIn * 1000);
                Integer refreshTokenExpiresIn = popAuthTokenCreateResponse.getRefreshTokenExpiresIn();  // refresh_token過期時間段,10表示10秒後過期
                Date refreshTokenExpiresDate = new Date(refreshTokenExpiresIn * 1000);

                // 通過店鋪資訊介面獲取店鋪詳細資訊
                PddMallInfoGetRequest pddMallInfoGetRequest = new PddMallInfoGetRequest();
                PddMallInfoGetResponse pddMallInfoGetResponse = client.syncInvoke(pddMallInfoGetRequest, accessToken);

                PddMallInfoGetResponse.MallInfoGetResponse mallInfoGetResponse = pddMallInfoGetResponse.getMallInfoGetResponse();

                String logo = mallInfoGetResponse.getLogo();    // 店鋪logo圖片連結
                Integer mallCharacter = mallInfoGetResponse.getMallCharacter(); // 店鋪身份,0:廠商 1:分銷商 2:都不是 3:都是
                String mallDesc = mallInfoGetResponse.getMallDesc();    // 店鋪描述
                Long mallId = mallInfoGetResponse.getMallId();  // 店鋪id
                String mallName = mallInfoGetResponse.getMallName();    // 店鋪名稱
                Integer merchantType = mallInfoGetResponse.getMerchantType();   // 店鋪型別,1:個人 2:企業 3:旗艦店 4:專賣店 5:專營店 6:普通店

              
                // 儲存店鋪資訊(所有獲取到的資料已在上方陳列,按需儲存)
                // 儲存步驟省略,根據業務需求自行儲存
                
                // 業務流轉完成可以執行重定向到系統登入頁面或系統使用地址
                StringBuilder sb = new StringBuilder();
                sb.append("<html>");
                sb.append("<head>");
                sb.append("</head>");
                sb.append("<body>");
                sb.append("訂購成功,<a href=\"http://www.erp.com\">點選登陸</a>");
                sb.append("</body>");
                sb.append("</html>");
                OutputStream outputStream = response.getOutputStream();
                response.setHeader("content-type", "text/html;charset=UTF-8");

                byte[] dataByteArr = sb.toString().getBytes("UTF-8");
                outputStream.write(dataByteArr);
                return;
            }
        } catch (Exception e) {
            logger.error("拼多多授權異常:"+e.getMessage());
        }
    }
}

完整版(例外處理+系統內頁面構建授權連結,自主組合拼接授權頁面):

自主組合拼接授權頁面流程:系統內頁面新增點選授權超連結,點選後依然請求授權地址,通過請求是否攜帶code來判斷是執行授權流程還是進入拼接平臺授權頁面流程。

平臺給出的組裝範例

https://{授權頁連結}?response_type=code&client_id={應用client_id}&redirect_uri={client_id對應的回撥地址}&state={自定義引數}
https://fuwu.pinduoduo.com/service-market/auth?response_type=code&client_id=4b6**********************672e4c9a&redirect_uri=https%3A%2F%2Fwww.oauth.net%2F2%2F&state=1212

要求組裝的引數以及可組裝的引數如下:

// 應用的 client_id
private String clientId = "1111111111";

// 應用的 client_secret
private String clientSecret = "2222222222";

@Controller
public class PddAccess {
    @GetMapping("/pddaccess")
    @ResponseBody
    public void PddRe(HttpServletRequest request,HttpServletResponse response){
        try{
            int contentlen = request.getContentLength();
            String decode = "";
            if(-1 != contentlen) {
                BufferedReader reader = request.getReader();
                char[] buf = new char[contentlen];
                int len = 0;
                StringBuffer contentBuffer = new StringBuffer();
                while ((len = reader.read(buf)) != -1) {
                    contentBuffer.append(buf, 0, len);
                }
                String content = contentBuffer.toString();
                if (content == null) {
                    content = "";
                }
                decode = URLDecoder.decode(content, "UTF-8");
            }
            String code = request.getParameter("code");

            if(null != code && "".equals(code)){
                // 通過code授權碼獲取access_token
                PopClient client = new PopHttpClient(clientId, clientSecret);

                PddPopAuthTokenCreateRequest pddPopAuthTokenCreateRequest = new PddPopAuthTokenCreateRequest();
                pddPopAuthTokenCreateRequest.setCode(code);
                PddPopAuthTokenCreateResponse pddPopAuthTokenCreateResponse = client.syncInvoke(pddPopAuthTokenCreateRequest);
                if(null == pddPopAuthTokenCreateResponse.getErrorResponse()){
                    PddPopAuthTokenCreateResponse.PopAuthTokenCreateResponse popAuthTokenCreateResponse = pddPopAuthTokenCreateResponse.getPopAuthTokenCreateResponse();

                    String ownerId = popAuthTokenCreateResponse.getOwnerId();   // 商家店鋪id,店鋪唯一標識
                    String ownerName = popAuthTokenCreateResponse.getOwnerName();   // 商家賬號名稱
                    String accessToken = popAuthTokenCreateResponse.getAccessToken();   // access_token
                    String refreshToken = popAuthTokenCreateResponse.getRefreshToken(); // refresh token,可用來重新整理access_token
                    Integer expiresIn = popAuthTokenCreateResponse.getExpiresIn();  // access_token過期時間段,10(表示10秒後過期,現令牌過期時間已與訂購時間相同)
                    Date expiresDate = new Date(expiresIn * 1000);
                    Integer refreshTokenExpiresIn = popAuthTokenCreateResponse.getRefreshTokenExpiresIn();  // refresh_token過期時間段,10表示10秒後過期
                    Date refreshTokenExpiresDate = new Date(refreshTokenExpiresIn * 1000);

                    // 通過店鋪資訊介面獲取店鋪詳細資訊
                    PddMallInfoGetRequest pddMallInfoGetRequest = new PddMallInfoGetRequest();
                    PddMallInfoGetResponse pddMallInfoGetResponse = client.syncInvoke(pddMallInfoGetRequest, accessToken);
                    if(null == pddMallInfoGetResponse.getErrorResponse()){
                        PddMallInfoGetResponse.MallInfoGetResponse mallInfoGetResponse = pddMallInfoGetResponse.getMallInfoGetResponse();

                        String logo = mallInfoGetResponse.getLogo();    // 店鋪logo圖片連結
                        Integer mallCharacter = mallInfoGetResponse.getMallCharacter(); // 店鋪身份,0:廠商 1:分銷商 2:都不是 3:都是
                        String mallDesc = mallInfoGetResponse.getMallDesc();    // 店鋪描述
                        Long mallId = mallInfoGetResponse.getMallId();  // 店鋪id
                        String mallName = mallInfoGetResponse.getMallName();    // 店鋪名稱
                        Integer merchantType = mallInfoGetResponse.getMerchantType();   // 店鋪型別,1:個人 2:企業 3:旗艦店 4:專賣店 5:專營店 6:普通店

              
                        // 儲存店鋪資訊(所有獲取到的資料已在上方陳列,按需儲存)
                        // 儲存步驟省略,根據業務需求自行儲存
                
                        // 業務流轉完成可以執行重定向到系統登入頁面或系統使用地址
                        StringBuilder sb = new StringBuilder();
                        sb.append("<html>");
                        sb.append("<head>");
                        sb.append("</head>");
                        sb.append("<body>");
                        sb.append("訂購成功,<a href=\"http://www.erp.com\">點選登陸</a>");
                        sb.append("</body>");
                        sb.append("</html>");
                        OutputStream outputStream = response.getOutputStream();
                        response.setHeader("content-type", "text/html;charset=UTF-8");

                        byte[] dataByteArr = sb.toString().getBytes("UTF-8");
                        outputStream.write(dataByteArr);
                        return;
                    }else{
                        logger.error("拼多多店鋪授權獲取店鋪資訊異常,code = "+code+",error_msg = "+pddMallInfoGetResponse.getErrorResponse().getErrorMsg());
                        String error = toErrorHtmlNotice(returl, "授權異常,請聯絡ERP系統技術人員!");
                        OutputStream outputStream = response.getOutputStream();
                        response.setHeader("content-type", "text/html;charset=UTF-8");

                        byte[] dataByteArr = error.getBytes("UTF-8");
                        outputStream.write(dataByteArr);
                        return;
                    }
                }else{
                    logger.error("拼多多店鋪授權獲取AccessToken異常,code = "+code+",error_msg = "+pddPopAuthTokenCreateResponse.getErrorResponse().getErrorMsg());
                    String error = toErrorHtmlNotice(returl, "授權異常,請聯絡ERP系統技術人員!");
                    OutputStream outputStream = response.getOutputStream();
                    response.setHeader("content-type", "text/html;charset=UTF-8");

                    byte[] dataByteArr = error.getBytes("UTF-8");
                    outputStream.write(dataByteArr);
                    return;
                }
            }
            
            // 如果請求不包含code引數,則執行以下授權頁面跳轉流程,引導商家進入授權頁面
            String returl = "https://fuwu.pinduoduo.com/service-market/auth?response_type=code&client_id="
                    + clientId + "&redirect_uri=" 
                    + URLEncoder.encode("http://www.erp.com/pddaccess", "utf-8");
            returl = StringEscapeUtils.unescapeHtml(returl);
            if(null == request.getParameter("error"))
            {
                response.sendRedirect(returl);
                return;
            }else {
                switch (request.getParameter("error")) {
                    default:
                        response.sendRedirect(returl);
                        break;
                    case "invalid_client":
                        StringBuilder sb = new StringBuilder();
                        sb.append("<script>");
                        sb.append("alert('您還沒訂購!點確定前往訂購!');");
                        sb.append("window.location.href='");
                        sb.append(fuwuurl);    // ERP應用的服務市場地址,跳轉到此位置
                        sb.append("';");
                        sb.append("</script>");

                        OutputStream outputStream = response.getOutputStream();
                        response.setHeader("content-type", "text/html;charset=UTF-8");

                        byte[] dataByteArr = sb.toString().getBytes("UTF-8");
                        outputStream.write(dataByteArr);
                        return;
                }
            }
        } catch (Exception e) {
            logger.error("拼多多授權異常:"+e.getMessage());
        }
    }
}

2、雲工作臺開啟訂單同步

只有開啟訂單同步服務後訂購的雲資料庫才能同步到授權商家的訂單資訊,開啟步驟參考下方訂單同步使用手冊檔案。

商家店鋪授權完成後需要在商家管理裡新增商家店鋪ID,按照實際需要選擇同步歷史訂單資訊,新增完成,同步完成歷史訂單資訊後雲資料庫就會實時的新增商家店鋪的新訂單資訊。

後續同步商家訂單資訊只需要在雲主機上查詢雲資料庫內的資料並行送到我們設定好的外部API服務上即可。

3、雲主機訂單資料操作Java執行緒處理任務方案

為了介面API安全性,請自行設定加密策略或其他安全策略來接收傳遞相關資料,以下流程為直接傳遞(非安全)。

線上程處理任務程式中,查詢雲資料庫內的訂單列表,可以根據傳統的時間段呼叫方式。

首次啟動從兩天前的時間點以半小時的時間段查詢一次,然後把查詢出的資料傳遞給API,API在本地對訂單資料進行序列化儲存到本地資料庫內,查詢可以根據訂單更新時間 update_at 欄位來判斷,本地訂單如果存在,並且更新時間一致則跳過,不一致並且本地儲存的訂單更新時間早於新的資料更新時間的話則把新的訂單詳情更新到本地資料庫。

執行成功會返回請求結果資訊,如果執行成功,則根據當前時間前進半個小時繼續執行,以此類推,直到執行到當前時間,則以當前時間前二十分以及後十分鐘為維度,慢慢查詢遍歷執行。

具體Java執行緒自動執行任務,可以根據實際系統設定進行開發,或自行尋找流程,這邊不在展示架構。

雲伺服器部署的自動處理執行緒任務:

public class PddOrderSyncTask implements Runnable {
    // 記錄開始時間
    private Date lastjdpmodify = new Date();
    // 記錄最後更新時間
    private Date newUpdateTime;

    @Override
    public void run() {

        pdptbTradeService = (IPdptbTradeService)applicationContext.getBean("PdptbTradeService");

        while (true) {
            try {
                SyncPddOrderStateFrom();
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void SyncPddOrderStateFrom() throws Exception {
        String url = "http://www.erp.com/pddOrderApi";// 遠端API介面地址

        Date nowdate = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(nowdate);
        // 時間推後一天
        calendar.add(Calendar.DAY_OF_MONTH, -2);
        lastjdpmodify = calendar.getTime();

        // 如果最後更新時間不為空,並且記錄的開始時間小於記錄的更新時間,則使用最後更新時間進行後續操作
        if(newUpdateTime != null){
            if (lastjdpmodify.getTime() <= newUpdateTime.getTime()) {
                Date needtime = new Date(newUpdateTime.getTime());
                Calendar c = new GregorianCalendar();
                c.setTime(needtime);
                c.add(Calendar.SECOND, -900);
                lastjdpmodify = c.getTime();
            }
        }

        Date updateTime  = lastjdpmodify;
        long timedetect = new Date().getTime() - updateTime.getTime();
        int timeNumber = (int) (timedetect/(30*60*1000));

        // 半個小時一個步伐,從記錄的時間開始往當前時間遍歷訂單
        // 例如當前時間為 2022-1-20 10:00:00,記錄的時間lastjdpmodify = 2022-1-20 07:00:00
        // 則訂單更新時間判斷開始時間為 2022-1-20 07:00:00,結束時間為 2022-1-20 07:30:00,以此類推,直到當前時間。

        for(int k=0;k<=timeNumber;k++) {
            long startLong = updateTime.getTime()+k*30*60*1000;
            long endLong = startLong+30*60*1000;
            String start_date = sdf.format(startLong);
            String end_date = sdf.format(endLong);

            // 雲資料庫查詢
            //  SELECT * FROM pdp_tb_trade where updated_at >= '${start_date}' updated_at <= '${end_date}' order by updated_at

            List<PdptbTradeInfo> pdptbTradeInfoList = pdptbTradeService.selectAll(start_date,end_date);
            if(pdptbTradeInfoList!=null && pdptbTradeInfoList.size()!=0){
                Date updated_at = null;
                boolean sign = false;
                for (int i = 0; i < pdptbTradeInfoList.size(); i++) {
                    PdptbTradeInfo pdptbTradeInfo =  pdptbTradeInfoList.get(i);

                    String param = JSONArray.toJSONString(pdptbTradeInfo);

                    // 把訂單資料傳送到API介面上
                    String retpost = LoadJson(url,param);

                    // 返回資訊
                    if(retpost!=null){
                        PddResponse resp = JSONObject.parseObject(retpost, PddResponse.class);
                        // 如果成功,更新 updated_at 訂單更新時間記錄
                        if(resp.getCode().equals("success")){
                            updated_at = pdptbTradeInfo.getUpdated_at();
                            sign=true;
                        }
                        // 如果失敗,則不更新,並且從當前訂單中斷,重新遍歷
                        if(resp.getCode().equals("error")){
                            logger.error(resp.getMessages());
                            sign=false;
                        }
                    }else{
                        break;
                    }
                }
                if(updated_at!=null && sign){
                    newUpdateTime =updated_at;
                }
                if(!sign){
                    break;
                }
            }
        }
    }

    // post 請求
    public String LoadJson(String url,String param){
        StringBuilder json = new StringBuilder();
        PrintWriter out = null;
        BufferedReader in = null;
        try {
            // Post請求的url,與get不同的是不需要帶引數
            URL urlload = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) urlload.openConnection();
            // 傳送POST請求必須設定如下兩行
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod("POST"); // 設定請求方式
            connection.setRequestProperty("Content-Type", "application/json"); // 設定接收資料的格式
            connection.connect();
            out = new PrintWriter(connection.getOutputStream());
            out.print(param);
            // flush輸出流的緩衝
            out.flush();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
            String inputLine = null;
            while ( (inputLine = in.readLine()) != null) {
                json.append(inputLine);
            }
        } catch (Exception e) {
            System.out.println("傳送 POST 請求出現異常!" + e);
            return "";
        }
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return json.toString();
    }
}

本地伺服器部署的API接收介面:

根據平臺檔案:加密資料檢索方案 自定義一個判斷以及獲取密文索引串的工具類

public class PddUtils {

    /** 拼多多敏感資料判斷是否密文 **/
    public static boolean isEncryptData(String data){
        char SEP_PHONE = '$';
        char SEP_ID = '#';
        char SEP_NORMAL = '~';
        Pattern BASE64_PATTERN = Pattern.compile("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
        if (null == data || data.length() < 44) {
            return false;
        }
        if (data.charAt(0) != data.charAt(data.length() - 1)) {
            return false;
        }
        char separator = data.charAt(0);
        String[] dataArray = StringUtils.split(data,separator);
        if (dataArray.length < 2 || !StringUtils.isNumeric(dataArray[dataArray.length - 1])) {
            return false;
        }
        if (separator == SEP_PHONE || separator == SEP_ID) {
            if (dataArray.length != 3) {
                return false;
            }
            if (data.charAt(data.length() - 2) == separator) {
                return BASE64_PATTERN.matcher(dataArray[0]).matches() && BASE64_PATTERN.matcher(dataArray[1]).matches() && dataArray[1].length()>=44;
            } else {
                return BASE64_PATTERN.matcher(dataArray[1]).matches() && dataArray[1].length()>=44;
            }
        }
        if (separator == SEP_NORMAL) {
            if (data.charAt(data.length() - 2) == separator) {
                if (dataArray.length != 3) {
                    return false;
                }
                return BASE64_PATTERN.matcher(dataArray[0]).matches() && BASE64_PATTERN.matcher(dataArray[1]).matches() && dataArray[0].length()>=44;
            } else {
                if (dataArray.length != 2) {
                    return false;
                }
                return BASE64_PATTERN.matcher(dataArray[0]).matches() && dataArray[0].length()>=44;
            }
        }
        return false;
    }

    /** 拼多多敏感資料獲取密文檢索串 **/
    public static String extractIndex(String encryptedData) {
        if (encryptedData == null || encryptedData.length() < 4) {
            return null;
        }
        char sepInData = encryptedData.charAt(0);
        if (encryptedData.charAt(encryptedData.length() - 2) != sepInData) {
            return null;
        }
        String[] parts = StringUtils.split(encryptedData, sepInData);
        if (sepInData == '$' || sepInData == '#') {
            return parts[0];
        } else {
            return parts[1];
        }
    }

}

通過介面獲取訂單敏感資料密文的脫敏資料(例:王*虎)

介面檔案地址:pdd.open.decrypt.mask.batch 批次資料解密脫敏介面

// 應用的 client_id
private String clientId = "1111111111";

// 應用的 client_secret
private String clientSecret = "2222222222";

@Service("PddEncryptionService")
public class PddEncryptionServiceImpl implements IPddEncryptionService {
    // 密文脫敏
    @Override
    public OrderInfo getDecryptMask(OrderInfo orderInfo) throws Exception {
        PopClient client = new PopHttpClient("http://gw-api.pinduoduo.com/api/router",clientId , clientSecret );
        ShopInfo shopInfo = shopService.selectById(orderInfo.getShopid());
        String accessToken = shopInfo.getSessionkey();

        PddOpenDecryptMaskBatchRequest request = new PddOpenDecryptMaskBatchRequest();
        List<PddOpenDecryptMaskBatchRequest.DataListItem> dataList = new ArrayList<PddOpenDecryptMaskBatchRequest.DataListItem>();
        boolean success = true;
        //判斷收貨人 (不為空並且是密文)
        if(orderInfo.getReceivername()!=null && PddUtils.isEncryptData(orderInfo.getReceivername())){
            PddOpenDecryptMaskBatchRequest.DataListItem item0 = new PddOpenDecryptMaskBatchRequest.DataListItem();
            item0.setDataTag(orderInfo.getId());
            item0.setEncryptedData(orderInfo.getReceivername());
            dataList.add(item0);
            success = false;
        }
        //判斷收貨地址 (不為空並且是密文)
        if(orderInfo.getReceiveraddress()!=null && PddUtils.isEncryptData(orderInfo.getReceiveraddress())){
            PddOpenDecryptMaskBatchRequest.DataListItem item1 = new PddOpenDecryptMaskBatchRequest.DataListItem();
            item1.setDataTag(orderInfo.getId());
            item1.setEncryptedData(orderInfo.getReceiveraddress());
            dataList.add(item1);
            success = false;
        }
        //判斷買家暱稱 (不為空並且是密文)
        if(orderInfo.getBuyernick()!=null && PddUtils.isEncryptData(orderInfo.getBuyernick())){
            PddOpenDecryptMaskBatchRequest.DataListItem item2 = new PddOpenDecryptMaskBatchRequest.DataListItem();
            item2.setDataTag(orderInfo.getId());
            item2.setEncryptedData(orderInfo.getBuyernick());
            dataList.add(item2);
            success = false;
        }
        //判斷手機號 (不為空並且是密文)
        if(orderInfo.getReceivermobile()!=null && PddUtils.isEncryptData(orderInfo.getReceivermobile())){
            PddOpenDecryptMaskBatchRequest.DataListItem item3 = new PddOpenDecryptMaskBatchRequest.DataListItem();
            item3.setDataTag(orderInfo.getId());
            item3.setEncryptedData(orderInfo.getReceivermobile());
            dataList.add(item3);
            success = false;
        }
        //判斷電話 (不為空並且是密文)
        if(orderInfo.getReceiverphone()!=null && PddUtils.isEncryptData(orderInfo.getReceiverphone())){
            PddOpenDecryptMaskBatchRequest.DataListItem item4 = new PddOpenDecryptMaskBatchRequest.DataListItem();
            item4.setDataTag(orderInfo.getId());
            item4.setEncryptedData(orderInfo.getReceiverphone());
            dataList.add(item4);
            success = false;
        }
        if(success){
            return orderInfo;
        }

        request.setDataList(dataList);
        PddOpenDecryptMaskBatchResponse response = client.syncInvoke(request, accessToken);

        PddOpenDecryptMaskBatchResponse.OpenDecryptMaskBatchResponse openDecryptMaskBatchResponse = response.getOpenDecryptMaskBatchResponse();
        if (null == openDecryptMaskBatchResponse || null == openDecryptMaskBatchResponse.getDataDecryptList()){
            return orderInfo;
        }

        List<PddOpenDecryptMaskBatchResponse.OpenDecryptMaskBatchResponseDataDecryptListItem> dataDecryptList = openDecryptMaskBatchResponse.getDataDecryptList();
        for (int i = 0; i < dataDecryptList.size(); i++) {
            PddOpenDecryptMaskBatchResponse.OpenDecryptMaskBatchResponseDataDecryptListItem openListItem =  dataDecryptList.get(i);
            if(openListItem.getEncryptedData().equals(orderInfo.getAlipayno()) && openListItem.getErrorCode() == 0){
                orderInfo.setAlipaynomask(openListItem.getDecryptedData());
                orderInfo.setAlipaynosearchstring(PddUtils.extractIndex(orderInfo.getAlipayno()));
            }
            if(openListItem.getEncryptedData().equals(orderInfo.getBuyernick()) && openListItem.getErrorCode() == 0){
                String replace = openListItem.getDecryptedData().replace("'", "’").replace("/", "");
                replace  = filterOffUtf8Mb4(replace);
                if(StringUtils.isBlank(replace)){
                    replace = "未知";
                }
                orderInfo.setBuyernickmask(replace);
                String s = PddUtils.extractIndex(orderInfo.getBuyernick());
                orderInfo.setBuyernicksearchstring(s);
            }
            if(openListItem.getEncryptedData().equals(orderInfo.getReceivername()) && openListItem.getErrorCode() == 0){
                String replace = openListItem.getDecryptedData().replace("'", "’").replace("/", "");
                replace  = filterOffUtf8Mb4(replace);
                if(StringUtils.isBlank(replace)){
                    replace = "未知";
                }
                orderInfo.setReceivernamemask(replace);
                String s = PddUtils.extractIndex(orderInfo.getReceivername());
                orderInfo.setNamesearchstring(s);
            }
            if(openListItem.getEncryptedData().equals(orderInfo.getReceiveraddress()) && openListItem.getErrorCode() == 0){
                String replace = openListItem.getDecryptedData().replace("'", "’").replace("/", "");
                replace  = filterOffUtf8Mb4(replace);
                orderInfo.setReceiveraddressmask(replace);
                orderInfo.setAddresssearchstring(PddUtils.extractIndex(orderInfo.getReceiveraddress()));
            }
            if(openListItem.getEncryptedData().equals(orderInfo.getReceivermobile()) && openListItem.getErrorCode() == 0){
                String replace = openListItem.getDecryptedData().replace("'", "’").replace("/", "");
                replace  = filterOffUtf8Mb4(replace);
                orderInfo.setReceivermobilemask(replace);
                String s = PddUtils.extractIndex(orderInfo.getReceivermobile());
                orderInfo.setMobilesearchstring(s);
            }
            if(openListItem.getEncryptedData().equals(orderInfo.getReceiverphone()) && openListItem.getErrorCode() == 0){
                String replace = openListItem.getDecryptedData().replace("'", "’").replace("/", "");
                replace  = filterOffUtf8Mb4(replace);
                orderInfo.setReceiverphonemask(replace);
                String s = PddUtils.extractIndex(orderInfo.getReceivermobile());
                orderInfo.setPhonesearchstring(s);
            }
        }
        return orderInfo;
    }

    // 過濾特殊字元,emoji表情等
    public String filterOffUtf8Mb4(String text) throws UnsupportedEncodingException {
        byte[] bytes = text.getBytes("utf-8");
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
        int i = 0;
        while (i < bytes.length) {
            short b = bytes[i];
            if (b > 0) {
                buffer.put(bytes[i++]);
                continue;
            }

            b += 256; // 去掉符號位

            if (((b >> 5) ^ 0x6) == 0) {
                buffer.put(bytes, i, 2);
                i += 2;
            } else if (((b >> 4) ^ 0xE) == 0) {
                buffer.put(bytes, i, 3);
                i += 3;
            } else if (((b >> 3) ^ 0x1E) == 0) {
                i += 4;
            } else if (((b >> 2) ^ 0x3E) == 0) {
                i += 5;
            } else if (((b >> 1) ^ 0x7E) == 0) {
                i += 6;
            } else {
                buffer.put(bytes[i++]);
            }
        }
        buffer.flip();
        return new String(buffer.array(), "utf-8");
    }
}

業務處理介面:

@Controller
public class PddOrderController{
    @PostMapping("pddOrderApi")
    @ResponseBody
    public String PddRespon (HttpServletRequest request, HttpServletResponse response){
        PddResponse resp = new PddResponse();
        String plantform = "PDD";
        try{
            int contentlen = request.getContentLength();
            BufferedReader reader = request.getReader();
            char[] buf = new char[contentlen];
            int len = 0;
            StringBuffer contentBuffer = new StringBuffer();
            while ((len = reader.read(buf)) != -1) {
                contentBuffer.append(buf, 0, len);
            }
            String content = contentBuffer.toString();
            if(content == null){
                content = "";
            }
            PdptbTradeInfo pdptbTradeInfo = JSONObject.parseObject(content, PdptbTradeInfo.class);

            //店鋪id為"PDD"+mallid拼接而成
            String shopid = plantform+pdptbTradeInfo.getMall_id();
            //根據店鋪id查詢店鋪是否存在或給以後的其他判斷作準備
            ShopInfo shopInfo = shopService.selectOneByShopCode(shopid);

            if(shopInfo == null){
                resp.setCode("error");
                resp.setMessages("[店鋪【"+shopInfo.getShopname()+"】:不存在!]");
                logger.error(resp.getMessages());
            }else if(shopInfo.getExpiretime()==null || shopInfo.getExpiretime().before(new Date())){
                resp.setCode("error");
                resp.setMessages("[店鋪【"+shopInfo.getShopname()+"】:已過期!]");
                logger.error(resp.getMessages());
            }else if(shopInfo.getShoptype()==0){
                resp.setCode("error");
                resp.setMessages("[店鋪【"+shopInfo.getShopname()+"】:已關閉!]");
                logger.error(resp.getMessages());
            }else{
                // 查詢訂單是否存在,如果存在並且新推來的訂單更新時間小於或者等於本地儲存的訂單更新時間,則直接跳過
                OrderInfo orderInfo = orderService.selectOrderInfoById(pdptbTradeInfo.getOrder_sn(), shopInfo.getId());
                if(orderInfo!=null){
                    long a = orderInfo.getJdpmodified().getTime()/1000;
                    long b = pdptbTradeInfo.getUpdated_at().getTime()/1000;
                    if(a==b || a>b){
                        resp.setCode("success");
                        return JSONArray.toJSONString(resp);
                    }
                }

                // 進入訂單操作流程,儲存或者修改本地訂單狀態等
                String s = SyncDataToSql(shopInfo, pdptbTradeInfo);
                if(s.equals("success")){
                    resp.setCode("success");
                } else if(s.equals("error")){
                    resp.setCode("error");
                }
            }
        } catch (Exception e) {
            resp.setCode("error");
            resp.setMessages("[發生異常]");
            logger.error(e.getMessage());
            e.printStackTrace();
        }

        return JSONArray.toJSONString(resp);
    }

    // 訂單操作流程,儲存或者修改本地訂單狀態等
    public String SyncDataToSql(ShopInfo shopInfo,PdptbTradeInfo pdptbTradeInfo) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        // 儲存一份原始訂單資料
        PddOrderInfo pddOrderInfo = new PddOrderInfo();
        String pdp_response = pdptbTradeInfo.getPdp_response();
        JSONObject pddOrderItem = JSONObject.parseObject(pdp_response);

        pddOrderInfo.setId(pddOrderItem.getString("order_sn"));
        pddOrderInfo.setTenantid(shopInfo.getTenantid());
        pddOrderInfo.setShopid(shopInfo.getId());
        pddOrderInfo.setTidstr(pddOrderItem.getString("order_sn"));
        //1:待發貨,2:已發貨待簽收,3:已簽收
        pddOrderInfo.setStatus(pddOrderItem.getString("order_status"));
        pddOrderInfo.setSellernick(shopInfo.getShopnick());
        String remark = pddOrderItem.getString("remark");
        String buyer_memo = pddOrderItem.getString("buyer_memo");
        String receiver_name = pddOrderItem.getString("receiver_name");
        String address = pddOrderItem.getString("address");
        String receiver_address = pddOrderItem.getString("receiver_address");
        try{
            if(StringUtils.isNotBlank(remark)){
                remark = pddOrderItem.getString("remark").replace("'","’").replace("/","//");
                remark  = filterOffUtf8Mb4(remark);
            }
            if(StringUtils.isNotBlank(buyer_memo)){
                buyer_memo = pddOrderItem.getString("buyer_memo").replace("'","’").replace("/","//");
                buyer_memo  = filterOffUtf8Mb4(buyer_memo);
            }
            String item_list_str = pddOrderItem.getString("item_list");
            JSONArray itemList = JSONArray.parseArray(item_list_str);
            for(int j=0;j<itemList.size();j++) {
                JSONObject orderStock = itemList.getJSONObject(j);
                if(StringUtils.isNotBlank(orderStock.getString("goods_name"))){
                    String goods_name = orderStock.getString("goods_name").replace("'", "’").replace("/", "//");
                    orderStock.remove("goods_name");
                    orderStock.put("goods_name",goods_name);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        pddOrderItem.remove("receiver_name");
        pddOrderItem.put("receiver_name",receiver_name);
        pddOrderItem.remove("remark");
        pddOrderItem.put("remark",remark);
        pddOrderItem.remove("buyer_memo");
        pddOrderItem.put("buyer_memo",buyer_memo);
        pddOrderItem.remove("receiver_address");
        pddOrderItem.put("receiver_address",receiver_address);
        pddOrderItem.remove("address");
        pddOrderItem.put("address",address);

        pddOrderInfo.setCreated(pddOrderItem.getString("created_time"));
        pddOrderInfo.setModified(pddOrderItem.getString("updated_at"));
        pddOrderInfo.setJdpcreated(pddOrderItem.getString("created_time"));
        pddOrderInfo.setJdpmodified(pddOrderItem.getString("updated_at"));
        pddOrderInfo.setJdpresponse(pddOrderItem.toString().replace("'","''"));

        pddOrderService.createOrUpdate(pddOrderInfo);


        //判斷訂單是否為平臺風控訂單 如果是平臺風控訂單 直接跳過
        if (StringUtils.isNotBlank(pddOrderItem.getString("risk_control_status")) && pddOrderItem.getString("risk_control_status").equals("1")){
            logger.error("拼多多訂單號:"+pddOrderItem.getString("order_sn")+"為平臺風控訂單跳過該訂單 等平臺稽核完畢");
            return "success";
        }

        // 開始儲存到本地或者更改本地訂單狀態
        // 此步驟省略。。。。。。。。。。。。
        // 大致流程為序列化為實體類,然後呼叫工具類的獲取檢索串工具以及呼叫通過密文獲取脫敏資料介面來獲取對應的檢索串以及脫敏資料
        // 然後中間流程需要根據訂單狀態 order_status 欄位來判斷訂單有沒有發生退款售後等
        return "success";
    }
}

3、訂單發貨通知介面(回傳物流單號/回傳發貨狀態)

流程與上面的訂單不同流程一直,依然是一個部署在拼多多雲伺服器上的自動處理任務,以及一個部署在原生的介面,雲主機上的任務先去介面獲取需要發貨回傳的訂單資訊,然後自動處理任務根據訂單資訊呼叫【訂單發貨通知介面】,進行發貨回傳。

訂單發貨通知介面檔案地址:訂單發貨通知

獲取快遞公司編碼呼叫快遞公司檢視介面:快遞公司檢視介面

自動發貨回傳處理任務:

public class PddStateSendTask implements Runnable {

    // 應用的 client_id
    private String clientId = "1111111111";

    // 應用的 client_secret
    private String clientSecret = "2222222222";

    @Override
    public void run(){
        while (true)
        {
            try {
                SyncPddStorageStateFromApi();
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(12 * 1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void SyncPddStorageStateFromApi()throws Exception {
        try {
            BooleanReason reason =  DeliveryDStateSync(商家授權令牌, 訂單ID, 物流單號, 快遞公司編碼);
            if(reason.getValue()){
                logger.error("拼多多發貨介面操作成功:" + item.getId());
                storageSendState = "1";
            }else{
                logger.error("拼多多發貨介面返回錯誤:" + item.getId() + "," + reason.getReasons().get(0));
                storageSendState = "2";
                if (reason.getReasons().get(0).contains("訂單已發貨") || reason.getReasons().get(0).contains("訂單已簽收")) {
                    storageSendState = "1";
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
            logger.error("拼多多發貨介面呼叫異常:" + e.getMessage());
            storageSendState = "2";
        }
        Thread.sleep(2000);
        // 此位置修改訂單發貨狀態為發貨成功或者發貨失敗
    }

    // 呼叫發貨通知介面
    public BooleanReason DeliveryDStateSync(String access, String order_sn, String tracking_number, Long logistics_id) throws Exception {
        BooleanReason reason = new BooleanReason();
        reason.setValue(false);

        PopClient client = new PopHttpClient(clientId, clientSecret);
        PddLogisticsOnlineSendRequest request = new PddLogisticsOnlineSendRequest();
        request.setLogisticsId(logistics_id);
        request.setOrderSn(order_sn);
        request.setTrackingNumber(tracking_number);

        PddLogisticsOnlineSendResponse response = client.syncInvoke(request, access);

        if(null == response.getErrorResponse()){
            reason.setValue(true);
        }else{
            reason.addReason(response.getErrorResponse().getErrorMsg());
        }
        return reason;
    }
}

 六、拼多多電子面單

1、接入前準備工作

因為密文的原因,拼多多的訂單必須要使用拼多多電子面單系統進行取單列印發貨(拼多多電子面單支援直接入參密文來取號以及列印)。

官方單子面單接入指南檔案:電子面單接入指南

首先引導商家前往店鋪後臺開通電子面單服務,繫結月結卡號或者網點資訊,等待物流商稽核通過,並且確保已充值上面單餘額。 (如果ERP支援多店鋪管理,同一商家多個拼多多店鋪的話,可以只需要一個店鋪開通就行,其他店鋪可共用這個店鋪的電子面單)

引導地址:引導地址

2、電子面單自定義區域設定

開通成功後引導商家前往拼多多面單編輯器系統:https://mms.pinduoduo.com

自定義區模板就是列印的面單最下面一部分可以自由編輯的區域。

一般在此位置展示的是訂單號,商品編碼/數量,以及其他作業相關的序列號,波次號等。 

新建與開通的物流服務商對應的電子面大模板,並且編輯商家自定義區。

需要提供給商家自定義區的引數以及位置排版,此方式需要提前引導商家制作自定義區模板。

引數格式是 <%=data.訂單號%>,可以支援中文,data.欄位名,比如需要展示訂單號,可以在伺服器端封裝好 ["訂單號":orderid],下文有詳細步驟。

自定義區模板也可以在開放平臺ISV預設定好模板,然後在列印時獲取需要展示的資訊進行列印即可。系統商家共同使用開放平臺設定好的模板,這種方式統一度比較高。

3、列印元件

拼多多電子面單列印需要使用拼多多電子面單列印元件,系統提供列印功能,商家在需要作業的電腦上安裝列印元件並且設定好預設的印表機,然後就可通過系統來操作列印了。

拼多多列印元件官方下載地址:拼多多列印元件

官方列印元件互動協定指導檔案(下文到列印流程會詳細介紹):拼多多列印元件互動協定

4、獲取快遞單號

獲取快遞單號支援使用密文入參。

因為平臺面單號支援自動回收,所以沒有特殊情況不需要對接取消單號的流程,而且可以設定自動任務,把需要發貨的訂單自動獲取快遞單號。

檢視快遞公司列表:

用來獲取拼多多側的物流名稱和物流編碼。

介面:pdd.logistics.companies.get

 

獲取所有標準電子面單模板

介面:pdd.cloudprint.stdtemplates.get

 

 

獲取商家的自定義區模板資訊

介面:pdd.cloudprint.customares.get

 

 

獲取電子面單列印資訊包括快遞單號

介面:pdd.waybill.get

列印元件互動以及列印範例:

  JSONArray docu = new JSONArray();

        StringBuilder codeStr = new StringBuilder();
        List<OrderStockInfo>orderStockInfoList = orderStockService.selectByOrderCode(orderVo.getOrdercode());
        for (OrderStockInfo orderStockInfo : orderStockInfoList) {
            codeStr.append("條碼:").append(orderStockInfo.getItemcode()).append(";數量:").append(orderStockInfo.getNum()).append("\n");
        }
        JSONObject backDataJSON = JSON.parseObject(print_data); // 電子面單列印資訊 返回值中的 print_data

        // 自定義區的顯示內容
        JSONObject dataJSON = new JSONObject();
        dataJSON.put("訂單號","1111111");
        dataJSON.put("序號",1);
        dataJSON.put("備貨任務單號",2222);
        dataJSON.put("數量",1);
        dataJSON.put("條碼",3333333);

        JSONObject cusJSON = new JSONObject();
        cusJSON.put("data",dataJSON);
        cusJSON.put("templateURL",custom_area_url); // 商家自定義區模板連結

        CnCloudPrintDocuments documents = new CnCloudPrintDocuments();
        documents.setDocumentID(waybill_code);  //檔案唯一ID,拼多多標準面單建議使用面單號
        JSONArray contents = new JSONArray();
        contents.add(JSON.toJSON(backDataJSON));
        contents.add(JSON.toJSON(cusJSON));
        documents.setContents(contents);
        docu.add(documents);

        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
            String taskid = sdf.format(new Date());
            String uri = "ws://127.0.0.1:5000"; //拼多多列印元件Websocket地址:ws://127.0.0.1:5000
            WebSocketClientManager websocket = new WebSocketClientManager(new URI(uri),new Draft_6455());
            websocket.connect();
            PddCloudPrintVo pddCloudPrintVo = new PddCloudPrintVo();
            PddCloudPrintVo.task task = new PddCloudPrintVo.task();
            task.setTaskID(taskid);
            task.setPrinter(printername); // 印表機名稱
            task.setDocuments(docu);
            // 預覽列印
            // task.setPreview("true");
            // task.setPreviewType("image");
            pddCloudPrintVo.setTask(JSON.toJSON(task));
            pddCloudPrintVo.setCmd("print");
            pddCloudPrintVo.setRequestID(taskid);
            pddCloudPrintVo.setVersion("1.0");
            while (!websocket.getReadyState().equals(ReadyState.OPEN)) {
                try{
                    Thread.sleep(1000);
                }
                catch(Exception e){
                    e.printStackTrace();
                }
//                        logger.debug("連線中···請稍後");
            }
            websocket.send(JsonUtils.toJson(pddCloudPrintVo));
        }catch (URISyntaxException url){
            url.printStackTrace();
        }

本文為【小小赫下士 blog】原創,搬運請保留本段,或請在醒目位置設定原文地址和原作者。

作者:小小赫下士

原文地址:Java對接拼多多開放平臺API(加密上雲等全流程)