From 81db7b0bb3b3ae8179aaf619b82965f8ce5c70ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=B0=B9=E5=B2=9A?= <770236076@qq.com> Date: Thu, 5 Mar 2020 16:50:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=AC=E4=B8=9Ceclp=E7=89=A9=E6=B5=81?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformapi/jdeclpapi/jdeclpapi.go | 266 ++++++++++++++++++++++++ platformapi/jdeclpapi/jdeclpapi_test.go | 40 ++++ 2 files changed, 306 insertions(+) create mode 100644 platformapi/jdeclpapi/jdeclpapi.go create mode 100644 platformapi/jdeclpapi/jdeclpapi_test.go diff --git a/platformapi/jdeclpapi/jdeclpapi.go b/platformapi/jdeclpapi/jdeclpapi.go new file mode 100644 index 00000000..79f2e713 --- /dev/null +++ b/platformapi/jdeclpapi/jdeclpapi.go @@ -0,0 +1,266 @@ +package jdeclpapi + +import ( + "crypto/md5" + "fmt" + "net/http" + "sort" + "strings" + "time" + + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + prodURL = "https://api.jd.com/routerjson" + + sigKey = "sign" + IsvSource = "ISV0020000000068" + DepartmentNo = "EBU4418046609452" + ShopNo = "ESP0020000066348" + WarehouseNo = "110014006" + CustomerCode = "028K595510" + OrderMark = "00000" + // CustomerCode2 = "028K588716" + + orderType10 = "10" //订单号类型(10:代表ECLP订单号, 20:代表商家订单号) + orderType20 = "20" +) + +type API struct { + accessToken string + appKey string + appSecret string + client *http.Client + config *platformapi.APIConfig +} + +type QuerySpSourceResult struct { + Website string `json:"website"` //销售平台网址 + SpSourceName string `json:"spSourceName"` //销售平台名称 + SpSourceNo string `json:"spSourceNo"` //销售平台编号(必填,长度:20) + Reserve5 string `json:"reserve5"` + Reserve4 string `json:"reserve4"` + Reserve3 string `json:"reserve3"` + Reserve2 string `json:"reserve2"` + Reserve1 string `json:"reserve1"` //预留字段1 +} + +type AddOrderParam struct { + IsvUUID string `json:"isvUUID"` //isv出库单号(商家出库单号),作为isv出库的唯一性校验码:长度不能超125 + IsvSource string `json:"isvSource"` //ISV来源编号 + ShopNo string `json:"shopNo"` //店铺编号(B2C订单必填,B2B(soType=2)订单非必填) + DepartmentNo string `json:"departmentNo"` //事业部编号,且与pin匹配 + WarehouseNo string `json:"warehouseNo"` //库房编号,事业部开启寻源拆分服务可不填;否则必填; + SalesPlatformOrderNo string `json:"salesPlatformOrderNo"` //销售平台订单号,如果销售平台来源为京东平台,则该字段不能为空,长度不超过200 + SalePlatformSource string `json:"salePlatformSource"` //销售平台来源 + ConsigneeName string `json:"consigneeName"` //收货人姓名,长度不能超20 + ConsigneeMobile string `json:"consigneeMobile"` //收货人手机(收货人电话、手机至少有一个不为空),长度不能超30 + ConsigneeAddress string `json:"consigneeAddress"` //收货人地址 ,长度不能超100 + OrderMark string `json:"orderMark"` //订单标记位,首位为1代表货到付款 + GoodsNo []string `json:"goodsNo"` //ECLP商品编号,与商家商品编号二选一必填(两者都指定,以goodsNo为准) + Price []int `json:"price"` //商品金额,会在订单面单上线上商品单价 + Quantity []int `json:"quantity"` //商品的出库数量,必须>0 +} + +type AddOrderResult struct { + EclpSoNo string `json:"eclpSoNo"` //订单的出库单号 +} + +type CancelOrderResult struct { + Code string `json:"code"` + Msg string `json:"msg"` +} + +type QueryOrderStatusResult struct { + OrderStatusList []struct { + OrderStatus struct { + SoStatusCode string `json:"soStatusCode"` + OperateTime string `json:"operateTime"` + SoStatusName string `json:"soStatusName"` + OperateUser string `json:"operateUser"` + } `json:"orderStatus"` + } `json:"orderStatusList"` + IsvUUID string `json:"isvUUID"` + EclpSoNo string `json:"eclpSoNo"` +} + +type GetTrackMessagePlusByOrderResult struct { + ResultData []struct { + ___ struct { + OpeTitle string `json:"opeTitle"` + OpeTime string `json:"opeTime"` + OpeRemark string `json:"opeRemark"` + OpeName string `json:"opeName"` + WaybillCode string `json:"waybillCode"` + } `json:"___"` + } `json:"resultData"` + ResultCode string `json:"resultCode"` + ResultMessage string `json:"resultMessage"` +} + +type SearchShopStockParam struct { + RequestId string `json:"requestId"` + DeptNo string `json:"deptNo"` + ShopNo string `json:"shopNo"` + GoodsNo string `json:"goodsNo"` + PageSize int `json:"pageSize"` + PageNumber int `json:"pageNumber"` +} + +type SearchShopStockResult struct { + PageCount string `json:"pageCount"` + ResponseCode string `json:"responseCode"` + RequestID string `json:"requestId"` + ErrMsg string `json:"errMsg"` + Data []struct { + ___ struct { + DeptNo string `json:"deptNo"` + StockNum string `json:"stockNum"` + GoodsNo string `json:"goodsNo"` + WarehouseNo string `json:"warehouseNo"` + ShopNo string `json:"shopNo"` + } `json:"___"` + } `json:"data"` + PageSize string `json:"pageSize"` + PageNumber string `json:"pageNumber"` +} + +func New(accessToken, appKey, appSecret string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ + accessToken: accessToken, + appKey: appKey, + appSecret: appSecret, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) signParam(params map[string]interface{}) (sig string) { + var valueList []string + for k, v := range params { + if k != sigKey { + if str := fmt.Sprint(v); str != "" { + valueList = append(valueList, fmt.Sprintf("%s%s", k, str)) + } + } + } + sort.Sort(sort.StringSlice(valueList)) + valueList = append(valueList, fmt.Sprintf("%s", a.appSecret)) + var valueList2 = make([]string, len(valueList)+1) + at := copy(valueList2, valueList[:0]) + at += copy(valueList2[at:], []string{a.appSecret}) + copy(valueList2[at:], valueList[0:]) + sig = strings.Join(valueList2, "") + binSig := md5.Sum([]byte(sig)) + sig = fmt.Sprintf("%X", binSig) + return sig +} + +func (a *API) AccessAPI(action string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { + params := make(map[string]interface{}) + params["access_token"] = a.accessToken + params["app_key"] = a.appKey + params["timestamp"] = utils.Time2Str(time.Now()) + params = utils.MergeMaps(params, bizParams) + signStr := a.signParam(params) + params["sign"] = signStr + fullURL := utils.GenerateGetURL(prodURL, "method="+action, nil) + err = platformapi.AccessPlatformAPIWithRetry(a.client, + func() *http.Request { + request, _ := http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode())) + request.Header.Set("charset", "UTF-8") + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return request + }, + a.config, + func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + if err == nil { + // returnCode := utils.Interface2String(jsonResult1["trxstatus"]) + // if returnCode != TrxStatusSuccess { + // errLevel = platformapi.ErrLevelGeneralFail + // err = utils.NewErrorCode(utils.Interface2String(jsonResult1["errmsg"])+utils.Interface2String(jsonResult1["retmsg"]), returnCode) + // baseapi.SugarLogger.Debugf("tonglianpay AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) + // } + retVal = jsonResult1 + } + return errLevel, err + }) + return retVal, err +} + +//获取销售平台信息 +//https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=947&apiName=jingdong.eclp.master.querySpSource +func (a *API) QuerySpSource() (querySpSourceResult *QuerySpSourceResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.master.querySpSource", nil) + if err == nil { + utils.Map2StructByJson(result, &querySpSourceResult, false) + } + return querySpSourceResult, err +} + +//销售出库单下发 +//https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=928&apiName=jingdong.eclp.order.addOrder +func (a *API) AddOrder(addOrderParam *AddOrderParam) (addOrderResult *AddOrderResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.order.addOrder", utils.Struct2FlatMap(addOrderParam)) + if err == nil { + utils.Map2StructByJson(result, &addOrderResult, false) + } + return addOrderResult, err +} + +//销售出库单取消 +//https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=957&apiName=jingdong.eclp.order.cancelOrder +func (a *API) CancelOrder(eclpSoNo string) (cancelOrderResult *CancelOrderResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.order.cancelOrder", map[string]interface{}{ + "eclpSoNo": eclpSoNo, + }) + if err == nil { + utils.Map2StructByJson(result, &cancelOrderResult, false) + } + return cancelOrderResult, err +} + +//销售出库单状态查询 +//https://jos.jd.com/api/detail.htm?apiName=jingdong.eclp.order.queryOrderStatus&id=929 +func (a *API) QueryOrderStatus(eclpSoNo string) (queryOrderStatus *QueryOrderStatusResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.order.queryOrderStatus", map[string]interface{}{ + "eclpSoNo": eclpSoNo, + }) + if err == nil { + utils.Map2StructByJson(result, &queryOrderStatus, false) + } + return queryOrderStatus, err +} + +//查询物流跟踪消息 +//https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=2378&apiName=jingdong.eclp.order.getTrackMessagePlusByOrder +func (a *API) GetTrackMessagePlusByOrder(bizCode string) (getTrackMessagePlusByOrderResult *GetTrackMessagePlusByOrderResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.order.getTrackMessagePlusByOrder", map[string]interface{}{ + "bizCode": bizCode, + "customerCode": CustomerCode, + "type": orderType20, + }) + if err == nil { + utils.Map2StructByJson(result, &getTrackMessagePlusByOrderResult, false) + } + return getTrackMessagePlusByOrderResult, err +} + +//查询仓库商品库存 +//https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=3396&apiName=jingdong.eclp.stock.searchShopStock +func (a *API) SearchShopStock(searchShopStockParam *SearchShopStockParam) (searchShopStockResult *SearchShopStockResult, err error) { + result, err := a.AccessAPI("jingdong.eclp.order.searchShopStock", utils.Struct2FlatMap(searchShopStockParam)) + if err == nil { + utils.Map2StructByJson(result, &searchShopStockResult, false) + } + return searchShopStockResult, err +} diff --git a/platformapi/jdeclpapi/jdeclpapi_test.go b/platformapi/jdeclpapi/jdeclpapi_test.go new file mode 100644 index 00000000..b5133b29 --- /dev/null +++ b/platformapi/jdeclpapi/jdeclpapi_test.go @@ -0,0 +1,40 @@ +package jdeclpapi + +import ( + "testing" + + "git.rosy.net.cn/baseapi" + "go.uber.org/zap" +) + +var ( + api *API + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + baseapi.Init(sugarLogger) + api = New("", "", "") +} + +func TestQuerySpSource(t *testing.T) { + api.QuerySpSource() +} + +func TestAddOrder(t *testing.T) { + api.AddOrder(&AddOrderParam{}) +} +func TestCancelOrder(t *testing.T) { + api.CancelOrder("") +} +func TestQueryOrderStatus(t *testing.T) { + api.QueryOrderStatus("") +} +func TestGetTrackMessagePlusByOrder(t *testing.T) { + api.GetTrackMessagePlusByOrder("") +} +func TestSearchShopStock(t *testing.T) { + api.SearchShopStock(&SearchShopStockParam{}) +}