package jdeclpapi import ( "crypto/md5" "fmt" "net/http" "sort" "strings" "time" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) const ( prodURL = "https://api.jd.com/routerjson" prodURL2 = "https://oauth.jd.com/oauth" sigKey = "sign" IsvSource = "ISV0020000000068" DepartmentNo = "EBU4418046609452" ShopNo = "ESP0020000066348" WarehouseNo = "110014006" CustomerCode = "028K595510" OrderMark = "00000000000000000000000000000000000000000000000000" // CustomerCode2 = "028K588716" state = "1212" orderType10 = "10" //订单号类型(10:代表ECLP订单号, 20:代表商家订单号) orderType20 = "20" SalePlatformSource = "6" SalePlatSourceDelivery = "0030001" SoStatusCode10010 = "10010" //订单初始化 SoStatusCode10022 = "10022" //暂停 SoStatusCode10014 = "10014" //已下发库房 SoStatusCode10015 = "10015" //任务已分配 SoStatusCode10016 = "10016" //拣货下架 SoStatusCode10019 = "10019" //交接发货 SoStatusCode10018 = "10018" //货品已打包 SoStatusCode10017 = "10017" //复核 SoStatusCode10054 = "10054" //分拣中心发货 SoStatusCode10033 = "10033" //站点验收 SoStatusCode10034 = "10034" //妥投 SoStatusCode10038 = "10038" //逆向完成 ) 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 string `json:"price"` //商品金额,会在订单面单上线上商品单价 Quantity string `json:"quantity"` //商品的出库数量,必须>0 // IsvGoodsNo string `json:"isvGoodsNo"` //商家商品编码 } type AddOrderResult struct { EclpSoNo string `json:"eclpSoNo"` //订单的出库单号 } type CancelOrderResult struct { Code string `json:"code"` Msg string `json:"msg"` } type QueryOrderStatusResult struct { OrderStatusList []struct { SoStatusCode string `json:"soStatusCode"` OperateTime string `json:"operateTime"` SoStatusName string `json:"soStatusName"` OperateUser string `json:"operateUser"` } `json:"orderStatusList"` IsvUUID string `json:"isvUUID"` EclpSoNo string `json:"eclpSoNo"` } type GetTrackMessagePlusByOrderResult struct { ResultData []struct { OpeTitle string `json:"opeTitle"` OpeTime string `json:"opeTime"` OpeRemark string `json:"opeRemark"` OpeName string `json:"opeName"` WaybillCode string `json:"waybillCode"` } `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 { DeptNo string `json:"deptNo"` StockNum string `json:"stockNum"` GoodsNo string `json:"goodsNo"` WarehouseNo string `json:"warehouseNo"` ShopNo string `json:"shopNo"` } `json:"data"` PageSize string `json:"pageSize"` PageNumber string `json:"pageNumber"` } type QueryStockResult struct { RecordCount int `json:"recordCount"` IsvSku string `json:"isvSku"` DeptName string `json:"deptName"` SellerGoodsSign string `json:"sellerGoodsSign"` GoodsNo string `json:"goodsNo"` UsableNum int `json:"usableNum"` DeptNo string `json:"deptNo"` WarehouseName string `json:"warehouseName"` TotalNum int `json:"totalNum"` StockType string `json:"stockType"` StockStatus string `json:"stockStatus"` GoodsLevel string `json:"goodsLevel"` WarehouseNo string `json:"warehouseNo"` GoodsName string `json:"goodsName"` } type WaybillReceiveParam struct { SalePlat string `json:"salePlat"` //是 无 销售平台 CustomerCode string `json:"customerCode"` //是 无 商家编码 OrderID string `json:"orderId"` //是 无 订单号 // ThrOrderID string `json:"thrOrderId"` //否 无 销售平台订单号(例如京东订单号或天猫订单号等等。总长度不要超过100。如果有多个单号,用英文,间隔。例如:7898675,7898676) SenderName string `json:"senderName"` //是 无 寄件人姓名,说明:不能为生僻字 SenderAddress string `json:"senderAddress"` //是 无 寄件人地址,说明:不能为生僻字 SenderTel string `json:"senderTel"` //否 无 寄件人电话 // SenderMobile string `json:"senderMobile"` //否 无 寄件人手机(寄件人电话、手机至少有一个不为空) // SenderPostcode string `json:"senderPostcode"` //否 100000 寄件人邮编,长度:6位 ReceiveName string `json:"receiveName"` //是 无 收件人名称,说明:不能为生僻字 ReceiveAddress string `json:"receiveAddress"` //是 无 收件人地址,说明:不能为生僻字 // Province string `json:"province"` //否 无 收件人省 // City string `json:"city"` //否 无 收件人市 // County string `json:"county"` //否 无 收件人县 // Town string `json:"town"` //否 无 收件人镇 // ProvinceID int `json:"provinceId"` //否 无 收件人省编码 // CityID int `json:"cityId"` //否 无 收件人市编码 // CountyID int `json:"countyId"` //否 无 收件人县编码 // TownID int `json:"townId"` //否 无 收件人镇编码 // SiteType int `json:"siteType"` //否 无 站点类型 // SiteID int `json:"siteId"` //否 无 站点编码 // SiteName string `json:"siteName"` //否 无 站点名称 ReceiveTel string `json:"receiveTel"` //否 无 收件人电话 // ReceiveMobile string `json:"receiveMobile"` //否 无 收件人手机号(收件人电话、手机至少有一个不为空) // Postcode string `json:"postcode"` //否 100000 收件人邮编,长度:6 PackageCount int `json:"packageCount"` //是 无 包裹数(大于0,小于1000) Weight int `json:"weight"` //是 2.5 重量(单位:kg,保留小数点后两位) // VloumLong int `json:"vloumLong"` //否 无 包裹长(单位:cm,保留小数点后两位) // VloumWidth int `json:"vloumWidth"` //否 无 包裹宽(单位:cm,保留小数点后两位) // VloumHeight int `json:"vloumHeight"` //否 无 包裹高(单位:cm,保留小数点后两位) Vloumn int `json:"vloumn"` //是 10000 体积(单位:cm3,保留小数点后两位) Description string `json:"description"` //否 无 商品描述 // CollectionValue int `json:"collectionValue"` //否 1 是否代收货款(是:1,否:0。不填或者超出范围,默认是0) // CollectionMoney int `json:"collectionMoney"` //否 98.00 代收货款金额(保留小数点后两位) // GuaranteeValue int `json:"guaranteeValue"` //否 1 是否保价(是:1,否:0。不填或者超出范围,默认是0) // GuaranteeValueAmount int `json:"guaranteeValueAmount"` //否 100.00 保价金额(保留小数点后两位) // SignReturn int `json:"signReturn"` //否 1 签单返还(签单返还类型:0 不返单,1 普通返单,2 校验身份返单,3 电子签返单,4 电子返单+普通返单) Aging int `json:"aging"` //否 1 时效(普通:1,工作日:2,非工作日:3,晚间:4。O2O一小时达:5。O2O定时达:6。不填或者超出范围,默认是1) // TransType int `json:"transType"` //否 1 运输类型(陆运:1,航空:2。不填或者超出范围,默认是1) // Remark string `json:"remark"` //否 无 运单备注,长度:20,说明:打印面单时备注内容也会显示在快递面单上 // GoodsType int `json:"goodsType"` //否 无 配送业务类型( 1:普通,3:填仓,4:特配,6:控温,7:冷藏,8:冷冻,9:深冷)默认是1 // OrderType int `json:"orderType"` //否 无 运单类型。(普通外单:0,O2O外单:1)默认为0 // ShopCode string `json:"shopCode"` //否 无 门店编码(只O2O运单需要传,普通运单不需要传) // OrderSendTime string `json:"orderSendTime"` //否 2014 09 18 08:30:00 预约配送时间(格式:yyyy MM dd HH:mm:ss) // WarehouseCode string `json:"warehouseCode"` //否 无 发货仓编码 // AreaProvID int `json:"areaProvId"` //否 无 接货省ID // AreaCityID int `json:"areaCityId"` //否 无 接货市ID // ShipmentStartTime time.Time `json:"shipmentStartTime"` //否 1 配送起始时间 // ShipmentEndTime time.Time `json:"shipmentEndTime"` //否 1 配送结束时间 // Idint string `json:"idint"` //否 无 身份证号 // AddedService string `json:"addedService"` //否 无 扩展服务 // ExtendField1 string `json:"extendField1"` //否 无 扩展字段1 // ExtendField2 string `json:"extendField2"` //否 无 扩展字段2 // ExtendField3 string `json:"extendField3"` //否 无 扩展字段3 // ExtendField4 int `json:"extendField4"` //否 无 扩展字段4 // ExtendField5 int `json:"extendField5"` //否 无 扩展字段5 // SenderCompany string `json:"senderCompany"` //否 北京市大兴区亦庄经济开发区京东大厦 寄件人公司,长度:50,说明:不能为生僻字 // ReceiveCompany string `json:"receiveCompany"` //否 北京市大兴区亦庄经济开发区京东大厦 收件人公司,长度:50,说明:不能为生僻字 // Goods string `json:"goods"` //否 服装、3C等 托寄物名称,长度:200,说明:为避免托运物品在铁路、航空安检中被扣押,请务必下传商品类目或名称,例如:服装、3C等 // GoodsCount int `json:"goodsCount"` //否 无 寄托物数量 PromiseTimeType int `json:"promiseTimeType"` //否 无 产品类型(1:特惠送 2:特快送 4:城际闪送 5:同城当日达 6:次晨达 7:微小件 8: 生鲜专送 16:生鲜特快 17、生鲜特惠 21:特惠小包) // Freight int `json:"freight"` //否 无 运费 // PickUpStartTime time.Time `json:"pickUpStartTime"` //否 1 预约取件开始时间 // PickUpEndTime time.Time `json:"pickUpEndTime"` //否 1 预约取件结束时间 // UnpackingInspection string `json:"unpackingInspection"` //否 无 开箱验货标识 // BoxCode []string `json:"boxCode"` //否 无 商家箱号,多个箱号请用逗号分隔,例如三个箱号为:a123,b456,c789 // FileURL string `json:"fileUrl"` //否 无 文件url } type CancelWayBillParam struct { // UserPin string `json:"userPin"` //否 pin 用户唯一标识,与下单一致可不填写 WaybillCode string `json:"waybillCode"` //是 VA12345678 运单号 CustomerCode string `json:"customerCode"` //是 010K00000 商家编码 Source string `json:"source"` //是 JOS 来源 CancelReason string `json:"cancelReason"` //是 客户取消 取消原因 OperatorName string `json:"operatorName"` //是 张三 操作人 } 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, url string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { params := make(map[string]interface{}) var fullURL string if url == prodURL2 { params = utils.MergeMaps(params, bizParams) fullURL = utils.GenerateGetURL(url, action, nil) } else { params["access_token"] = a.accessToken params["app_key"] = a.appKey params["timestamp"] = utils.Time2Str(time.Now()) params["method"] = action params["v"] = "2.0" params = utils.MergeMaps(params, bizParams) signStr := a.signParam(params) params["sign"] = signStr fullURL = utils.GenerateGetURL(url, "", 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 { if jsonResult1["error_response"] != nil { errLevel = platformapi.ErrLevelGeneralFail err = utils.NewErrorCode(jsonResult1["error_response"].(map[string]interface{})["zh_desc"].(string), jsonResult1["error_response"].(map[string]interface{})["code"].(string)) baseapi.SugarLogger.Debugf("jdeclp 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", prodURL, nil) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_master_querySpSource_responce"].(map[string]interface{})["queryspsource_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) (eclpSoNo string, err error) { result, err := a.AccessAPI("jingdong.eclp.order.addOrder", prodURL, utils.Struct2FlatMap(addOrderParam)) if err == nil { return result["jingdong_eclp_order_addOrder_responce"].(map[string]interface{})["eclpSoNo"].(string), err } return "", 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", prodURL, map[string]interface{}{ "eclpSoNo": eclpSoNo, }) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_order_cancelOrder_responce"].(map[string]interface{})["cancelorder_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", prodURL, map[string]interface{}{ "eclpSoNo": eclpSoNo, }) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_order_queryOrderStatus_responce"].(map[string]interface{})["queryorderstatus_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", prodURL, map[string]interface{}{ "bizCode": bizCode, "customerCode": CustomerCode, "type": orderType20, }) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_order_getTrackMessagePlusByOrder_responce"].(map[string]interface{})["getTrackMessagePlusByOrder_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(goodsNo string) (searchShopStockResult *SearchShopStockResult, err error) { params := map[string]interface{}{ "requestId": utils.GetUUID(), "deptNo": DepartmentNo, "shopNo": ShopNo, "pageSize": 100, "pageNumber": 1, } if goodsNo != "" { params["goodsNo"] = goodsNo } result, err := a.AccessAPI("jingdong.eclp.stock.searchShopStock", prodURL, params) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_stock_searchShopStock_responce"].(map[string]interface{})["shopStockSearchResponse"], &searchShopStockResult, false) } return searchShopStockResult, err } //查询仓库商品库存 //https://open.jd.com/home/home#/doc/api?apiCateId=138&apiId=3396&apiName=jingdong.eclp.stock.searchShopStock func (a *API) QueryStock(goodsNo string) (queryStockResult []*QueryStockResult, err error) { params := map[string]interface{}{ "deptNo": DepartmentNo, "warehouseNo": WarehouseNo, "returnZeroStock": 2, //表示返回库存为0的数据 "stockType": 1, "stockStatus": 1, //良品 } if goodsNo != "" { params["goodsNo"] = goodsNo } result, err := a.AccessAPI("jingdong.eclp.stock.queryStock", prodURL, params) if err == nil { utils.Map2StructByJson(result["jingdong_eclp_stock_queryStock_responce"].(map[string]interface{})["querystock_result"], &queryStockResult, false) } return queryStockResult, err } //京东物流接单接口 //https://open.jd.com/home/home#/doc/api?apiCateId=64&apiId=2122&apiName=jingdong.ldop.waybill.receive func (a *API) WaybillReceive(waybillReceiveParam *WaybillReceiveParam) (deliveryId string, err error) { result, err := a.AccessAPI("jingdong.ldop.waybill.receive", prodURL, utils.Struct2FlatMap(waybillReceiveParam)) if err == nil { deliveryId = result["jingdong_ldop_waybill_receive_responce"].(map[string]map[string]string)["receiveorderinfo_result"]["deliveryId"] } return deliveryId, err } //京东物流取消接口 //https://open.jd.com/home/home#/doc/api?apiCateId=75&apiId=3482&apiName=jingdong.ldop.delivery.provider.cancelWayBill func (a *API) CancelWayBill(cancelWayBillParam *CancelWayBillParam) (err error) { _, err = a.AccessAPI("jingdong.ldop.delivery.provider.cancelWayBill", prodURL, utils.Struct2FlatMap(cancelWayBillParam)) return err } //京东物流全程跟踪接口 //https://open.jd.com/home/home#/doc/api?apiCateId=64&apiId=4257&apiName=jingdong.trace.dynamicQueryService.queryDynamicTraceInfo func (a *API) QueryDynamicTraceInfo(waybillCode string) (err error) { _, err = a.AccessAPI("jingdong.trace.dynamicQueryService.queryDynamicTraceInfo", prodURL, map[string]interface{}{ "waybillCode": waybillCode, }) return err }