diff --git a/platformapi/autonavi/autonavi.go b/platformapi/autonavi/autonavi.go index ba254ba9..c8f6b157 100644 --- a/platformapi/autonavi/autonavi.go +++ b/platformapi/autonavi/autonavi.go @@ -137,7 +137,7 @@ func (a *API) AccessAPI(apiStr string, params map[string]interface{}) (retVal Re return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { status := jsonResult1["status"].(string) if status == StatusCodeSuccess { retVal = jsonResult1 @@ -179,11 +179,7 @@ func (a *API) CoordinateConvert(lng, lat float64, coordsys string) (retLng, retL // 这里的District指的是实际的District,有些市是没有区的,比如东莞,这种情况下返回的区码是一个假的区域,即市的编码加上9000000 func (a *API) GetCoordinateDistrictCode(lng, lat float64) (districtCode int) { - params := map[string]interface{}{ - "location": fmt.Sprintf("%.6f,%.6f", lng, lat), - } - result, err := a.AccessAPI("geocode/regeo", params) - // baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) + result, err := a.GetCoordinateAreaInfo(lng, lat) if err == nil { addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{}) if strAdcode, ok := addressComponent["adcode"].(string); ok { @@ -196,6 +192,25 @@ func (a *API) GetCoordinateDistrictCode(lng, lat float64) (districtCode int) { return districtCode } +func (a *API) GetCoordinateAreaInfo(lng, lat float64) (areaInfo map[string]interface{}, err error) { + params := map[string]interface{}{ + "location": fmt.Sprintf("%.6f,%.6f", lng, lat), + } + result, err := a.AccessAPI("geocode/regeo", params) + return result, err +} + +func (a *API) GetCoordinateTownInfo(lng, lat float64) (townName, townCode string) { + result, err := a.GetCoordinateAreaInfo(lng, lat) + // baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) + if err == nil { + addressComponent := result["regeocode"].(map[string]interface{})["addressComponent"].(map[string]interface{}) + townName = utils.Interface2String(addressComponent["township"]) + townCode = utils.Interface2String(addressComponent["towncode"]) + } + return townName, townCode +} + // 这里的District指的是地点,不是实际上的区,具体级别看level // 这个函数返回的可能不是同级别的地点 func (a *API) GetDistricts(subDistrict int, keywords string) (districtList []*District, err error) { diff --git a/platformapi/autonavi/autonavi_test.go b/platformapi/autonavi/autonavi_test.go index c61126db..c2155ac1 100644 --- a/platformapi/autonavi/autonavi_test.go +++ b/platformapi/autonavi/autonavi_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/utils" "go.uber.org/zap" ) @@ -63,13 +64,22 @@ func TestGetCoordinateDistrictCode(t *testing.T) { } } +func TestGetCoordinateTownInfo(t *testing.T) { + dongguanLng := 120.74567 + dongguanLat := 31.69742 + townName, townCode := autonaviAPI.GetCoordinateTownInfo(dongguanLng, dongguanLat) + if townName != "周市镇" || townCode != "320583102000" { + t.Fatalf("townName:%s, townCode:%s", townName, townCode) + } +} + func TestGetDistricts(t *testing.T) { - districtList, err := autonaviAPI.GetDistricts(1, "东莞") + districtList, err := autonaviAPI.GetDistricts(4, "常熟市") if err != nil { t.Fatal(err) } if len(districtList) == 0 { t.Fatal("should have ditrict") } - // t.Log(utils.Format4Output(districtList, false)) + t.Log(utils.Format4Output(districtList, false)) } diff --git a/platformapi/dadaapi/dadaapi.go b/platformapi/dadaapi/dadaapi.go index c5518944..28db1c6a 100644 --- a/platformapi/dadaapi/dadaapi.go +++ b/platformapi/dadaapi/dadaapi.go @@ -118,7 +118,7 @@ func (a *API) AccessAPI(action string, params interface{}) (retVal *ResponseResu return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { code := int(utils.MustInterface2Int64(jsonResult1["code"])) retVal = &ResponseResult{ Code: code, diff --git a/platformapi/dingdingapi/callback.go b/platformapi/dingdingapi/callback.go index 8ea7c086..a593e036 100644 --- a/platformapi/dingdingapi/callback.go +++ b/platformapi/dingdingapi/callback.go @@ -112,8 +112,8 @@ func (a *API) Decrypt(msg string) (decryptedMsg string, err error) { func (a *API) RegisterCallback(callbackTagList []string, token, aesKey, urlStr string) (err error) { a.locker.Lock() - oldCallbackToken := a.callbackToken - oldCallbackAESKey := a.callbackAESKey + // oldCallbackToken := a.callbackToken + // oldCallbackAESKey := a.callbackAESKey a.callbackToken = token a.callbackAESKey, _ = base64.StdEncoding.DecodeString(aesKey + "=") a.locker.Unlock() @@ -126,12 +126,12 @@ func (a *API) RegisterCallback(callbackTagList []string, token, aesKey, urlStr s "url": urlStr, }) } - if err != nil { - a.locker.Lock() - defer a.locker.Unlock() - a.callbackToken = oldCallbackToken - a.callbackAESKey = oldCallbackAESKey - } + // if err != nil { + // a.locker.Lock() + // defer a.locker.Unlock() + // a.callbackToken = oldCallbackToken + // a.callbackAESKey = oldCallbackAESKey + // } return err } diff --git a/platformapi/dingdingapi/dingdingapi.go b/platformapi/dingdingapi/dingdingapi.go index 9ab44428..c20baf19 100644 --- a/platformapi/dingdingapi/dingdingapi.go +++ b/platformapi/dingdingapi/dingdingapi.go @@ -128,7 +128,7 @@ func (a *API) AccessAPI(action string, params map[string]interface{}, bodyMap ma params2["access_token"] = accessToken } else { params2["accessKey"] = a.GetAppID() - timestamp := time.Now().Unix() + timestamp := time.Now().UnixNano() / 1000000 params2["timestamp"] = timestamp params2["signature"] = a.signParams(timestamp) } @@ -147,7 +147,7 @@ func (a *API) AccessAPI(action string, params map[string]interface{}, bodyMap ma return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { errCode := int(utils.MustInterface2Int64(jsonResult1["errcode"])) if errCode == ResponseCodeSuccess { retVal = jsonResult1 diff --git a/platformapi/ebaiapi/activity.go b/platformapi/ebaiapi/activity.go index 48da4a79..c2570323 100644 --- a/platformapi/ebaiapi/activity.go +++ b/platformapi/ebaiapi/activity.go @@ -1,7 +1,6 @@ package ebaiapi import ( - "fmt" "strings" "git.rosy.net.cn/baseapi/utils" @@ -11,15 +10,17 @@ const ( ActivityTypeDirectDown = 2 // 商品直降 ActivityTypeFullDiscount = 8 // 品类满减 - ActivityPFBaidu = 1 - ActivityPFELM = 2 - ActivityPFAll = 4 + ActivityPFBaidu = 1 // 星选 + ActivityPFELM = 2 // 饿了么 + ActivityPFAll = 4 // 星选&饿了么 - ActivityConflictShare = 0 - ActivityConfilictExclude = 1 + ActivityConflictShare = 0 // 与全店满减等其他活动同享 + ActivityConfilictExclude = 1 // 与全店满减等其他活动互斥 - ActivityShowStatusEnabled = 2 - ActivityShowStatusDisabled = 4 + MaxActivitySkuBatchSize = 100 // 使用商品id的活动商品,与商品自定义id互斥。一批最多100个 + // 下面两个是做什么用的? + // ActivityShowStatusEnabled = 2 + // ActivityShowStatusDisabled = 4 ) type ActivityRule struct { @@ -45,40 +46,50 @@ type ActivityInfo struct { PromotionSkuDesc string `json:"promotion_sku_desc,omitempty"` // 店铺页活动商品的分类名称,不超过8个字。 Rule []*ActivityRule `json:"rule,omitempty"` - BaiduShopID int64 `json:"baiduShopID,omitempty"` - ShowStatus int `json:"showStatus,omitempty"` + BaiduShopID int64 `json:"baidu_shop_id,omitempty"` // 这个用于查询时返回值,设置时这里设置的值会被忽略 + // ShowStatus int `json:"showStatus,omitempty"` } -type ActivitySkuInfo struct { +type ActivitySkuInfo4Add struct { SkuID string `json:"sku_id"` Stock int `json:"stock"` - PromotionPrice float32 `json:"special_price"` // 直降用,复用 - StoreUserLimit int `json:"store_user_limit"` // 直降用 + StoreUserLimit int `json:"store_user_limit"` + SpecialPrice int64 `json:"special_price"` } -type ActivitySkuInfoEx struct { - ActivitySkuInfo - BaiduShopID int64 `json:"baidu_shop_id"` - UpcName string `json:"upc_name"` - OriginalPrice float32 `json:"original_price"` +type ActivitySkuInfo4List struct { + BaiduShopID int64 `json:"baidu_shop_id"` + SkuID string `json:"sku_id"` + UpcName string `json:"upc_name"` + Stock int `json:"stock"` + + PromotionPrice float32 `json:"promotion_price"` // 商品活动价,单位元,品类满减活动不返回活动价。 + OriginalPrice float32 `json:"original_price"` // 添加商品时商品原价,单位元。 } type ActivitySkuInfo4Update struct { - ActivitySkuInfo - BaiduShopID int64 `json:"baidu_shop_id"` - ShopID string `json:"shop_id"` - CustomSkuID string `json:"custom_sku_id"` + BaiduShopID int64 `json:"baidu_shop_id,omitempty"` + ShopID string `json:"shop_id,omitempty"` + SkuID string `json:"sku_id,omitempty"` + CustomSkuID string `json:"custom_sku_id,omitempty"` + Stock int `json:"stock,omitempty"` + + StoreUserLimit int `json:"store_user_limit,omitempty"` + SpecialPrice int64 `json:"special_price,omitempty"` } type ActivitySkuListInfo struct { - ActivityID int64 `json:"activity_id"` - ActivityType int `json:"activity_type"` - ActivityName string `json:"activity_name"` - SkuList []*ActivitySkuInfoEx `json:"sku_list"` + ActivityID int64 `json:"activity_id"` + ActivityType int `json:"activity_type"` + ActivityName string `json:"activity_name"` + SkuList []*ActivitySkuInfo4List `json:"sku_list"` } +// 创建商品营销活动 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_create func (a *API) ActivityCreate(shopID string, baiduShopID, supplierID int64, activity *ActivityInfo) (activityID int64, err error) { + activity.BaiduShopID = 0 params := utils.Struct2FlatMap(activity) result, err := a.AccessAPI("activity.create", utils.MergeMaps(params, a.genShopIDParams(shopID, baiduShopID, supplierID))) if err == nil { @@ -87,7 +98,10 @@ func (a *API) ActivityCreate(shopID string, baiduShopID, supplierID int64, activ return 0, err } +// 更新活动信息 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_update func (a *API) ActivityUpdate(activityID int64, shopID string, baiduShopID, supplierID int64, activity *ActivityInfo) (newActivityID int64, err error) { + activity.BaiduShopID = 0 params := utils.Struct2FlatMap(activity) params[KeyActivityID] = activityID result, err := a.AccessAPI("activity.update", utils.MergeMaps(params, a.genShopIDParams(shopID, baiduShopID, supplierID))) @@ -97,6 +111,8 @@ func (a *API) ActivityUpdate(activityID int64, shopID string, baiduShopID, suppl return 0, err } +// 下线商品营销活动 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_disable func (a *API) ActivityDisable(activityID int64, shopID string, baiduShopID, supplierID int64) (err error) { params := a.genShopIDParams(shopID, baiduShopID, supplierID) params[KeyActivityID] = activityID @@ -104,6 +120,8 @@ func (a *API) ActivityDisable(activityID int64, shopID string, baiduShopID, supp return err } +// 查看活动信息 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_get func (a *API) ActivityGet(activityID int64, shopID string, baiduShopID, supplierID int64) (activityInfo *ActivityInfo, err error) { params := a.genShopIDParams(shopID, baiduShopID, supplierID) params[KeyActivityID] = activityID @@ -115,7 +133,9 @@ func (a *API) ActivityGet(activityID int64, shopID string, baiduShopID, supplier return activityInfo, err } -func (a *API) ActivitySkuAddBatch(activityID int64, shopID string, baiduShopID int64, activityType int, skuList []*ActivitySkuInfo, isSkuIDCustom bool) (successIDs []string, err error) { +// 批量添加活动商品 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_sku_add_batch +func (a *API) ActivitySkuAddBatch(activityID int64, shopID string, baiduShopID int64, activityType int, skuList []*ActivitySkuInfo4Add, isSkuIDCustom bool) (successIDs []string, err error) { params := a.genShopIDParams(shopID, baiduShopID, 0) params[KeyActivityID] = activityID skusKey := "activity_skus" @@ -130,7 +150,7 @@ func (a *API) ActivitySkuAddBatch(activityID int64, shopID string, baiduShopID i return nil, err } -func skuList2Str(activityType int, skuList []*ActivitySkuInfo, isSkuIDCustom bool) string { +func skuList2Str(activityType int, skuList []*ActivitySkuInfo4Add, isSkuIDCustom bool) string { skuList2 := []string{} for _, v := range skuList { strList := []string{ @@ -138,14 +158,18 @@ func skuList2Str(activityType int, skuList []*ActivitySkuInfo, isSkuIDCustom boo utils.Int2Str(v.Stock), } if activityType == ActivityTypeDirectDown { - strList = append(strList, fmt.Sprintf("%.2f", v.PromotionPrice)) - strList = append(strList, utils.Int2Str(v.StoreUserLimit)) + strList = append(strList, utils.Int64ToStr(v.SpecialPrice)) + if v.StoreUserLimit > 0 { + strList = append(strList, utils.Int2Str(v.StoreUserLimit)) + } } skuList2 = append(skuList2, strings.Join(strList, ":")) } return strings.Join(skuList2, ";") } +// 批量删除活动商品 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_sku_delete_batch func (a *API) ActivitySkuDeleteBatch(activityID int64, shopID string, baiduShopID int64, skuIDs []string, isSkuIDCustom bool) (successIDs []string, err error) { params := a.genShopIDParams(shopID, baiduShopID, 0) params[KeyActivityID] = activityID @@ -161,6 +185,8 @@ func (a *API) ActivitySkuDeleteBatch(activityID int64, shopID string, baiduShopI return nil, err } +// 批量添加活动商品 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_sku_add_batch func (a *API) ActivitySkuList(activityID int64, shopID string, baiduShopID, supplierID int64, skuIDs []string, isSkuIDCustom bool) (activityInfo *ActivitySkuListInfo, err error) { params := a.genShopIDParams(shopID, baiduShopID, supplierID) params[KeyActivityID] = activityID @@ -179,35 +205,17 @@ func (a *API) ActivitySkuList(activityID int64, shopID string, baiduShopID, supp params["page"] = page result, err = a.AccessAPI("activity.sku.list", params) if err == nil { - // tmpActivityInfo := &ActivitySkuListInfo{} - // if err = utils.Map2StructByJson(result.Data, tmpActivityInfo, true); err != nil { - // return nil, err - // } - // if activityInfo == nil { - // activityInfo = tmpActivityInfo - // } else { - // activityInfo.SkuList = append(activityInfo.SkuList, tmpActivityInfo.SkuList...) - // } resultMap := result.Data.(map[string]interface{}) if activityInfo == nil { - activityInfo = &ActivitySkuListInfo{ - ActivityID: activityID, - ActivityType: int(utils.Str2Int64(utils.Interface2String(resultMap["activity_type"]))), - ActivityName: utils.Interface2String(resultMap["activity_name"]), + if err = utils.Map2StructByJson(resultMap, &activityInfo, true); err != nil { + return nil, err } - } - for _, v := range resultMap["sku_list"].([]interface{}) { - skuMap := v.(map[string]interface{}) - activityInfo.SkuList = append(activityInfo.SkuList, &ActivitySkuInfoEx{ - ActivitySkuInfo: ActivitySkuInfo{ - SkuID: utils.Interface2String(skuMap["sku_id"]), - Stock: int(utils.Str2Int64(utils.Interface2String(skuMap["stock"]))), - PromotionPrice: float32(utils.Interface2Float64WithDefault(skuMap["promotion_price"], 0)), - }, - BaiduShopID: utils.Str2Int64(utils.Interface2String(skuMap["baidu_shop_id"])), - OriginalPrice: float32(utils.Interface2Float64WithDefault(skuMap["original_price"], 0)), - UpcName: utils.Interface2String(skuMap["upc_name"]), - }) + } else { + var skuList []*ActivitySkuInfo4List + if err = utils.Map2StructByJson(resultMap["sku_list"], &skuList, true); err != nil { + return nil, err + } + activityInfo.SkuList = append(activityInfo.SkuList, skuList...) } if len(activityInfo.SkuList) >= int(utils.MustInterface2Int64(resultMap["total"])) { break @@ -220,6 +228,8 @@ func (a *API) ActivitySkuList(activityID int64, shopID string, baiduShopID, supp return activityInfo, err } +// 批量更新活动商品 +// https://open-be.ele.me/dev/api/doc/v3/#api-Marketing-activity_sku_update_batch func (a *API) ActivitySkuUpdateBatch(activityID int64, actSkuInfoList []*ActivitySkuInfo4Update) (faildInfoList []string, err error) { result, err := a.AccessAPI("activity.sku.update.batch", map[string]interface{}{ KeyActivityID: activityID, diff --git a/platformapi/ebaiapi/activity_test.go b/platformapi/ebaiapi/activity_test.go index 122043b5..e5274a90 100644 --- a/platformapi/ebaiapi/activity_test.go +++ b/platformapi/ebaiapi/activity_test.go @@ -7,8 +7,10 @@ import ( "git.rosy.net.cn/baseapi/utils" ) +const testActivityID = 3000000000517188 + func TestActivityCreate(t *testing.T) { - activityInfo, err := api.ActivityCreate("", 2235877569, 0, &ActivityInfo{ + activityInfo, err := api.ActivityCreate("", testShopBaiduID, 0, &ActivityInfo{ ActivityName: "测试活动0403", StartTime: time.Now().Unix(), EndTime: time.Now().Add(24 * time.Hour).Unix(), @@ -29,8 +31,24 @@ func TestActivityCreate(t *testing.T) { } } +func TestActivitySkuAddBatch(t *testing.T) { + result, err := api.ActivitySkuAddBatch(testActivityID, "", testShopBaiduID, ActivityTypeDirectDown, []*ActivitySkuInfo4Add{ + &ActivitySkuInfo4Add{ + SkuID: "3594", + Stock: 9, + SpecialPrice: 100, + StoreUserLimit: 9, + }, + }, true) + if err != nil { + t.Fatal(err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} + func TestActivityUpdate(t *testing.T) { - activityInfo, err := api.ActivityUpdate(6000268610, "", 2235877569, 0, &ActivityInfo{ + activityInfo, err := api.ActivityUpdate(6000268610, "", testShopBaiduID, 0, &ActivityInfo{ ActivityName: "测试活动0403", StartTime: time.Now().Unix(), EndTime: time.Now().Add(24 * time.Hour).Unix(), @@ -52,7 +70,7 @@ func TestActivityUpdate(t *testing.T) { } func TestActivityGet(t *testing.T) { - activityInfo, err := api.ActivityGet(6000268610, "", 2235877569, 0) + activityInfo, err := api.ActivityGet(3000000000517188, "", testShopBaiduID, 0) if err != nil { t.Fatal(err) } else { @@ -61,14 +79,14 @@ func TestActivityGet(t *testing.T) { } func TestActivityDisable(t *testing.T) { - err := api.ActivityDisable(6000268604, "", 2235877569, 0) + err := api.ActivityDisable(6000268604, "", testShopBaiduID, 0) if err != nil { t.Fatal(err) } } func TestActivitySkuList(t *testing.T) { - activityInfo, err := api.ActivitySkuList(6000058442, "", 0, 2233065879, nil, false) + activityInfo, err := api.ActivitySkuList(3000000000517188, "", testShopBaiduID, 0, nil, false) if err != nil { t.Fatal(err) } else { diff --git a/platformapi/ebaiapi/callback.go b/platformapi/ebaiapi/callback.go index bc03351b..497400f2 100644 --- a/platformapi/ebaiapi/callback.go +++ b/platformapi/ebaiapi/callback.go @@ -37,6 +37,38 @@ type CallbackMsg struct { Cmd string `json:"cmd"` Timestamp int64 `json:"timestamp"` Body map[string]interface{} `json:"body"` + Data interface{} `json:"data"` +} + +type CBPartRefundInfo struct { + OrderID int64 `json:"order_id"` + RefundID string `json:"refund_id"` + Type int `json:"type"` + Status int `json:"status"` + AdditionReason string `json:"addition_reason"` + Photos []string `json:"photos"` + ReasonType string `json:"reason_type"` + Reason string `json:"reason"` + RefundProducts []*struct { + SkuID string `json:"sku_id"` + Upc string `json:"upc"` + CustomSkuID string `json:"custom_sku_id"` + Name string `json:"name"` + Number int `json:"number"` + TotalRefund int64 `json:"total_refund"` + ShopEleRefund int64 `json:"shop_ele_refund"` + } `json:"refund_products"` + RefundPrice int64 `json:"refund_price"` +} + +type CBUserCancelInfo struct { + OrderID int64 `json:"order_id"` + CancelReason string `json:"cancel_reason"` + AdditionReason string `json:"addition_reason"` + RefuseReason string `json:"refuse_reason"` + Pictures []string `json:"pictures"` + Type int `json:"type"` + CancelType int `json:"cancel_type"` } func (a *API) Err2CallbackResponse(cmd string, err error, data interface{}) *CallbackResponse { @@ -107,6 +139,20 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR } msg.Cmd = GetCmd(request) msg.Timestamp = utils.Str2Int64(utils.Interface2String(request.FormValue("timestamp"))) + var tmpObj interface{} + switch msg.Cmd { + case CmdOrderPartRefund: + var partRefundData CBPartRefundInfo + tmpObj = &partRefundData + case CmdOrderUserCancel: + var userCancelData CBUserCancelInfo + tmpObj = &userCancelData + } + if tmpObj != nil { + if utils.Map2StructByJson(msg.Body, tmpObj, true) == nil { + msg.Data = tmpObj + } + } return msg, nil } return nil, a.Err2CallbackResponse("", err, nil) diff --git a/platformapi/ebaiapi/ebaiapi.go b/platformapi/ebaiapi/ebaiapi.go index 85a63280..0535909a 100644 --- a/platformapi/ebaiapi/ebaiapi.go +++ b/platformapi/ebaiapi/ebaiapi.go @@ -112,7 +112,7 @@ func (a *API) AccessAPI(cmd string, body map[string]interface{}) (retVal *Respon return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { Body := jsonResult1["body"].(map[string]interface{}) retVal = &ResponseResult{ ErrNo: int(utils.MustInterface2Int64(Body["errno"])), @@ -125,7 +125,7 @@ func (a *API) AccessAPI(cmd string, body map[string]interface{}) (retVal *Respon baseapi.SugarLogger.Debugf("ebai AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) newErr := utils.NewErrorIntCode(retVal.Error, retVal.ErrNo) // todo 临时处理超过阈值错... - if newErr.IntCode() == 20501 && strings.Index(retVal.Error, "阈值") >= 0 { + if (newErr.IntCode() == 20501 && strings.Index(retVal.Error, "阈值") >= 0) || (newErr.IntCode() == 20502 && strings.Index(retVal.Error, "系统繁忙") >= 0) { return platformapi.ErrLevelExceedLimit, newErr } return platformapi.ErrLevelCodeIsNotOK, newErr diff --git a/platformapi/ebaiapi/ebaiapi_test.go b/platformapi/ebaiapi/ebaiapi_test.go index 7d450238..f94f4331 100644 --- a/platformapi/ebaiapi/ebaiapi_test.go +++ b/platformapi/ebaiapi/ebaiapi_test.go @@ -12,8 +12,8 @@ import ( ) const ( - testShopBaiduID = 2233976901 - testShopID = "100077" + testShopBaiduID = 2267254343 + testShopID = "2" ) var ( @@ -25,14 +25,13 @@ func init() { logger, _ := zap.NewDevelopment() sugarLogger = logger.Sugar() baseapi.Init(sugarLogger) - - // sandbox - api = New("62923", "aa4cdc6c1108486b") + // sandbox,果园测试门店 + api = New("62289", "d3ec2358d6a819ea") // prod // api = New("34665", "c3db75b754ea2d89") - api.SetStoreCookie("WMUSS", "AADIBAABbDlEpGl47c1EyBFcJSidBTBJHFHZEXyMSdBllJTZ9AUNOKV0tZFB9FlRVM73gEAIHRjBVagwAAHh98X2oPJ34Gal0ofFJBYXZ2Xnc6LCEXWQVnVxs7LDlaKBlFNz9DPCogYyZxJQhoHGVfVRIBa2oFUkEfDm1YZxZwLEwvZMjpB18rjw%7E3CaMQAo") - api.SetStoreCookie("WMSTOKEN", "HwXAAB9SGxnTT8pbEwWRDQsNGB3Y09_PF5rO157QUcoLRQAAAoKlgUEiVTOhIf5LjkCoFpAwwCaAAAUr-GEu-yDBeNAQAAHNneF25-uRjhYtgX4rsAAGHurh8C5GAQAA") + api.SetStoreCookie("WMUSS", "4AAPQCAAB5PF0aUGcBVzoRTCEkOFhFIhx-Yk9vN2EfPHYoLlROKBEsQmAUQjhNUgRt0ADAP5x-RFklwAAdjxGO11iOj8xKXYSSDIJb2BcPghsaklNfQwGS10JOVRFfhAiYElhEXFXIzoJKyloCGdwdFE6Qk9FRxojUFN3FVEHNjJPZJu4Bt9nxQ13cwoMbjA") + api.SetStoreCookie("WMSTOKEN", "AcAANQZAABbC04rUBZFc2UYanlocDAaP0dcfzZCeS1SHQ1qJ15ExgAA13A2dGLjdbcitBZJu4Bn6B_g6cZAAA0tyyFm8cdBaNAQAAwug8HTG0xRjwt1UZzbcAAN7ofRO") } func TestTest(t *testing.T) { diff --git a/platformapi/ebaiapi/error.go b/platformapi/ebaiapi/error.go new file mode 100644 index 00000000..68be6bad --- /dev/null +++ b/platformapi/ebaiapi/error.go @@ -0,0 +1,5 @@ +package ebaiapi + +const ( + ErrOrderIsNotPartRefund = 301006 // 该订单非部分退款订单 +) diff --git a/platformapi/ebaiapi/order.go b/platformapi/ebaiapi/order.go index 8fbd66cf..6998f165 100644 --- a/platformapi/ebaiapi/order.go +++ b/platformapi/ebaiapi/order.go @@ -30,42 +30,42 @@ const ( ) const ( - WaybillStatusNew = "2" - WaybillStatusRequestDelivery = "3" - WaybillStatusWait4Courier = "4" - WaybillStatusCourierAccepted = "7" - WaybillStatusCourierPickedup = "8" - WaybillStatusDeliveryCancled = "15" - WaybillStatusFinished = "16" - WaybillStatusExceptional = "17" - WaybillStatusSelfDelivery = "18" - WaybillStatusNotInDelivering = "19" - WaybillStatusDeliveryRejected = "20" + WaybillStatusNew = "2" // 生成运单 + WaybillStatusRequestDelivery = "3" // 请求配送 + WaybillStatusWait4Courier = "4" // 等待分配骑士 + WaybillStatusCourierAccepted = "7" // 骑士接单 + WaybillStatusCourierPickedup = "8" // 骑士取餐 + WaybillStatusDeliveryCancled = "15" // 配送取消 + WaybillStatusFinished = "16" // 配送完成 + WaybillStatusExceptional = "17" // 配送异常 + WaybillStatusSelfDelivery = "18" // 自行配送 + WaybillStatusDontDeliver = "19" // 不再配送 + WaybillStatusDeliveryRejected = "20" // 配送拒单 ) const ( // 订单下行 - order.partrefund.push-部分退款订单信息推送 - OrderPartRefuncTypeMerchant = "1" // 表示商户发起的部分退款 - OrderPartRefuncTypeCustomer = "2" // 表示用户发起的部分退款 - OrderPartRefuncTypeCS = "3" // 表示客服直接发起的部分退款 + OrderPartRefuncTypeMerchant = 1 // 表示商户发起的部分退款 + OrderPartRefuncTypeCustomer = 2 // 表示用户发起的部分退款 + OrderPartRefuncTypeCS = 3 // 表示客服直接发起的部分退款 - OrderPartRefundApply = "10" // 表示商家/用户发起部分退款申请 - OrderPartRefundSuccess = "20" // 表示部分退款成功 - OrderPartRefundUserApplyArbitration = "30" // 用户申请仲裁,客服介入 - OrderPartRefundFailed = "40" // 表示部分退款失败 - OrderPartRefundMerchantRefused = "50" // 表示商家拒绝用户发起的部分退款申请 + OrderPartRefundApply = 10 // 表示商家/用户发起部分退款申请 + OrderPartRefundSuccess = 20 // 表示部分退款成功 + OrderPartRefundUserApplyArbitration = 30 // 用户申请仲裁,客服介入 + OrderPartRefundFailed = 40 // 表示部分退款失败 + OrderPartRefundMerchantRefused = 50 // 表示商家拒绝用户发起的部分退款申请 // 订单下行 - order.user.cancel-用户申请订单取消/退款 - OrderUserCancelApply = "10" // 发起申请 - OrderUserCancelCSIntervene = "20" // 客服介入 - OrderUserCancelCSRefused = "30" // 客服拒绝 - OrderUserCancelCSAgreed = "40" // 客服同意 - OrderUserCancelMerchantRefused = "50" // 商户拒绝 - OrderUserCancelMerchantAgreed = "60" // 商家同意 - OrderUserCancelInvalid = "70" // 申请失效 + OrderUserCancelApply = 10 // 发起申请 + OrderUserCancelCSIntervene = 20 // 客服介入 + OrderUserCancelCSRefused = 30 // 客服拒绝 + OrderUserCancelCSAgreed = 40 // 客服同意 + OrderUserCancelMerchantRefused = 50 // 商户拒绝 + OrderUserCancelMerchantAgreed = 60 // 商家同意 + OrderUserCancelInvalid = 70 // 申请失效 - OrderUserCancelTypeBeforeSale = "1" // 表示订单完成前用户全单取消申请流程 - OrderUserCancelTypeAfterSale = "2" // 表示订单完成后用户全单退款申请流程 + OrderUserCancelTypeBeforeSale = 1 // 表示订单完成前用户全单取消申请流程 + OrderUserCancelTypeAfterSale = 2 // 表示订单完成后用户全单退款申请流程 SendImmediatelySelf = 6 // 饿百商家自送的配送状态 ) @@ -73,12 +73,19 @@ const ( const ( OrderFromBaidu = "1" OrderFromElm = "2" + + OrderSkuDiscountTypeZhe = "g_zhe" + OrderSkuDiscountTypeReduce = "g_reduce" ) const ( UserApplyCancelWaitMinute = 15 // 用户申请取消订单后待处理的最大等待时间(分钟),超时自动同意 ) +const ( + ListOrderPageSize = 100 // order.list的每页条数 +) + type ExpressInfo struct { OrderID string `json:"order_id"` ExpressID string `json:"express_id"` @@ -97,6 +104,205 @@ type RefundSku struct { Number string `json:"number"` } +type ProductSubsidyInfo struct { + AgentRate int `json:"agent_rate"` + BaiduRate int `json:"baidu_rate"` + Discount int `json:"discount"` + DiscountDetail []*struct { + ActivityID string `json:"activity_id"` + BaiduRate int `json:"baidu_rate"` + ShopRate int `json:"shop_rate"` + Type string `json:"type"` + } `json:"discount_detail"` + LogisticsRate int `json:"logistics_rate"` + ShopRate int `json:"shop_rate"` + UserRate int `json:"user_rate"` +} + +type OrderProductInfo struct { + BaiduProductID string `json:"baidu_product_id"` + SkuID string `json:"sku_id"` // 部分退款信息中是这个而不是BaiduProductID + + CustomSkuID string `json:"custom_sku_id"` + + PackageAmount int `json:"package_amount"` + PackageFee int `json:"package_fee"` + PackagePrice int `json:"package_price"` + PrescriptionID string `json:"prescription_id"` + + ProductAmount int `json:"product_amount"` + Number int `json:"number"` // 部分退款信息中是这个而不是ProductAmount + + ProductAttr []interface{} `json:"product_attr"` + ProductCustomIndex string `json:"product_custom_index"` + ProductFeatures []interface{} `json:"product_features"` + ProductFee int `json:"product_fee"` + ProductName string `json:"product_name"` + ProductPrice int `json:"product_price"` + ProductSubsidy *ProductSubsidyInfo `json:"product_subsidy"` + ProductType int `json:"product_type"` + SupplyType int `json:"supply_type"` + TotalFee int `json:"total_fee"` + TotalWeight int `json:"total_weight"` + Upc string `json:"upc"` + WeightType int `json:"weight_type"` +} + +type OrderDetailInfo struct { + Discount []*struct { + ActivityID string `json:"activity_id"` + AgentRate int `json:"agent_rate"` + BaiduRate int `json:"baidu_rate"` + Desc string `json:"desc"` + Fee int `json:"fee"` + LogisticsRate int `json:"logistics_rate"` + Products []*struct { + ActivityID string `json:"activity_id"` + ActivityProductNum int `json:"activity_product_num"` + BaiduProductID int64 `json:"baidu_product_id"` + NowPrice int `json:"now_price"` + OrigPrice int `json:"orig_price"` + SavePrice int `json:"save_price"` + } `json:"products,omitempty"` + RuleID string `json:"rule_id"` + ShopRate int `json:"shop_rate"` + Type string `json:"type"` + UserRate int `json:"user_rate,omitempty"` + } `json:"discount"` + Order *struct { + AtshopTime int `json:"atshop_time"` + CancelTime string `json:"cancel_time"` + ColdBoxFee string `json:"cold_box_fee"` + Commission int `json:"commission"` + ConfirmTime string `json:"confirm_time"` + CreateTime string `json:"create_time"` + DeliveryParty int `json:"delivery_party"` + DeliveryPhone string `json:"delivery_phone"` + DeliveryTime int `json:"delivery_time"` + DiscountFee int `json:"discount_fee"` + DownFlag int `json:"down_flag"` + ElemeOrderID string `json:"eleme_order_id"` + ExpectTimeMode int `json:"expect_time_mode"` + Ext struct { + TaoxiFlag int `json:"taoxi_flag"` + } `json:"ext"` + FinishedTime string `json:"finished_time"` + InvoiceTitle string `json:"invoice_title"` + IsColdBoxOrder int `json:"is_cold_box_order"` + IsPrivate int `json:"is_private"` + LatestSendTime int `json:"latest_send_time"` + MealNum string `json:"meal_num"` + NeedInvoice int `json:"need_invoice"` + OrderFlag int `json:"order_flag"` + OrderFrom string `json:"order_from"` + OrderID string `json:"order_id"` + OrderIndex string `json:"order_index"` + PackageFee int `json:"package_fee"` + PayStatus int `json:"pay_status"` + PayType int `json:"pay_type"` + PickupTime int `json:"pickup_time"` + Remark string `json:"remark"` + ResponsibleParty string `json:"responsible_party"` + SendFee int `json:"send_fee"` + SendImmediately int `json:"send_immediately"` + SendTime int `json:"send_time"` + ShopFee int `json:"shop_fee"` + Status int `json:"status"` + TaxerID string `json:"taxer_id"` + TotalFee int `json:"total_fee"` + UserFee int `json:"user_fee"` + } `json:"order"` + Products [][]*OrderProductInfo `json:"products"` + Shop *struct { + BaiduShopID string `json:"baidu_shop_id"` + ID string `json:"id"` + Name string `json:"name"` + } `json:"shop"` + Source string `json:"source"` + User *struct { + Address string `json:"address"` + City string `json:"city"` + Coord *struct { + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + } `json:"coord"` + CoordAmap *struct { + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + } `json:"coord_amap"` + District string `json:"district"` + Gender string `json:"gender"` + Name string `json:"name"` + Phone string `json:"phone"` + PrivacyPhone string `json:"privacy_phone"` + Province string `json:"province"` + UserID string `json:"user_id"` + } `json:"user"` +} + +type RefundOrderDetailInfo struct { + CustomSkuID string `json:"custom_sku_id"` + Name string `json:"name"` + Number int `json:"number"` + ProductFeatures []interface{} `json:"product_features"` + ProductFee int `json:"product_fee"` + ProductPrice int `json:"product_price"` + ProductSubsidy *ProductSubsidyInfo `json:"product_subsidy"` + SkuID string `json:"sku_id"` + TotalWeight int `json:"total_weight"` + Upc string `json:"upc"` +} + +type RefundDetailInfo struct { + ApplyTime string `json:"apply_time"` + CustomSkuID string `json:"custom_sku_id"` + Desc string `json:"desc"` + Name string `json:"name"` + Number int `json:"number"` + RefundID string `json:"refund_id"` + ShopEleRefund int `json:"shop_ele_refund"` + SkuID string `json:"sku_id"` + Status int `json:"status"` + TotalRefund int `json:"total_refund"` + Type int `json:"type"` + Upc string `json:"upc"` +} + +type PartRefundInfo struct { + Commission int `json:"commission"` + Fee int `json:"fee"` + OrderDetail []*RefundOrderDetailInfo `json:"order_detail"` + OrderID string `json:"order_id"` + PackageFee int `json:"package_fee"` + RefundDetail []*RefundDetailInfo `json:"refund_detail"` + RefundPrice int `json:"refund_price"` + SendFee int `json:"send_fee"` + ShopFee int `json:"shop_fee"` + TotalPrice int `json:"total_price"` + Type int `json:"type"` + UserFee int `json:"user_fee"` +} + +type ListOrderItemInfo struct { + BaiduShopID int64 `json:"baidu_shop_id"` + CreateTime int64 `json:"create_time"` + OrderFrom int `json:"order_from"` + OrderID string `json:"order_id"` + OrderStatus int `json:"order_status"` + PayStatus int `json:"pay_status"` + PayType int `json:"pay_type"` + ShopID string `json:"shop_id"` + Status int `json:"status"` + UserPhone string `json:"user_phone"` +} + +type ListOrderInfo struct { + Total int `json:"total"` + Page int `json:"Page"` + Pages int `json:"pages"` + List []*ListOrderItemInfo `json:"list"` +} + // 提供给合作方确认订单所用。 注:1、10分钟内未确认的订单系统自动取消。2、确认失败的订单请不要做餐。 2016年7月4号起,将由百度外卖负责完成订单。届时,对接方无需调用完成订单接口,继续调用可能导致订单结算有问题。 func (a *API) OrderConfirm(orderID string) (err error) { _, err = a.AccessAPI("order.confirm", map[string]interface{}{ @@ -169,6 +375,16 @@ func (a *API) OrderGet(orderID string) (orderMap map[string]interface{}, err err return nil, err } +func (a *API) OrderGet2(orderID string) (order *OrderDetailInfo, err error) { + result, err := a.AccessAPI("order.get", map[string]interface{}{ + "order_id": orderID, + }) + if err == nil { + err = utils.Map2StructByJson(result.Data, &order, true) + } + return order, err +} + // 设置订单快递单号 func (a *API) OrderExpressCreate(shopID string, expressList []*ExpressInfo) (status int, err error) { result, err := a.AccessAPI("order.express.create", map[string]interface{}{ @@ -226,8 +442,48 @@ func (a *API) OrderIdConvert(orderID string, isElemeOrder bool) (convertedOrderI return "", err } -// 查看售后订单详情 -func (a *API) OrderPartrefundGet(orderID string) (orderMap map[string]interface{}, err error) { +// 查看订单列表 +// https://open-be.ele.me/dev/api/doc/v3/#api-Order_Up-order_list +// page从1开始 +func (a *API) OrderList(shopID string, baiduShopID int64, startTime, endTime int64, status int, page int) (listOrderInfo *ListOrderInfo, err error) { + params := a.genShopIDParams(shopID, baiduShopID, 0) + if startTime > 0 { + params["start_time"] = startTime + } + if endTime > 0 { + params["end_time"] = endTime + } + if status > 0 { + params["status"] = status + } + if page > 0 { + params["page"] = page + } + result, err := a.AccessAPI("order.list", params) + if err == nil { + err = utils.Map2StructByJson(result.Data, &listOrderInfo, true) + } + return listOrderInfo, err +} + +func (a *API) OrderListAll(shopID string, baiduShopID int64, startTime, endTime int64, status int) (listOrder []*ListOrderItemInfo, err error) { + page := 1 + for { + result, err := a.OrderList(shopID, baiduShopID, startTime, endTime, status, page) + if err != nil { + return nil, err + } + listOrder = append(listOrder, result.List...) + if result.Page == result.Pages { + break + } + page++ + } + return listOrder, nil +} + +// 查看部分退款订单详情 +func (a *API) OrderPartRefundGet(orderID string) (orderMap map[string]interface{}, err error) { result, err := a.AccessAPI("order.partrefund.get", map[string]interface{}{ "order_id": orderID, }) @@ -237,6 +493,14 @@ func (a *API) OrderPartrefundGet(orderID string) (orderMap map[string]interface{ return nil, err } +// func (a *API) OrderPartRefundGet2(orderID string) (partRefundInfo *CBPartRefundInfo, err error) { +// result, err := a.OrderPartRefundGet(orderID) +// if err == nil { +// err = utils.Map2StructByJson(result, &partRefundInfo, true) +// } +// return partRefundInfo, err +// } + func (a *API) SmartOrderIdConvert(orderID string) (convertedOrderID string, err error) { return a.OrderIdConvert(orderID, isOrderIDEleme(orderID)) } diff --git a/platformapi/ebaiapi/order_test.go b/platformapi/ebaiapi/order_test.go index d97c1b1a..16142693 100644 --- a/platformapi/ebaiapi/order_test.go +++ b/platformapi/ebaiapi/order_test.go @@ -7,7 +7,7 @@ import ( ) func TestOrderGet(t *testing.T) { - result, err := api.OrderGet("1555731848221773250") + result, err := api.OrderGet("1560213937227424971") if err != nil { t.Fatal(err) } else { @@ -15,8 +15,44 @@ func TestOrderGet(t *testing.T) { } } +func TestOrderGet2(t *testing.T) { + result, err := api.OrderGet2("1560213937227424971") + if err != nil { + t.Fatal(err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} + +func TestOrderList(t *testing.T) { + result, err := api.OrderList("", 0, 0, 0, 0, 0) + if err != nil { + t.Fatal(err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} + +func TestOrderPartRefundGet(t *testing.T) { + result, err := api.OrderPartRefundGet("1557459492221457830") + if err != nil { + t.Fatal(err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} + +// func TestOrderPartRefundGet2(t *testing.T) { +// result, err := api.OrderPartRefundGet2("1557459492221457830") +// if err != nil { +// t.Fatal(err) +// } else { +// t.Log(utils.Format4Output(result, false)) +// } +// } + func TestOrderCallDelivery(t *testing.T) { - err := api.OrderCallDelivery("15381031350154") + err := api.OrderCallDelivery("1556617836226053651") if err != nil { t.Fatal(err) } @@ -59,3 +95,15 @@ func TestSmartOrderIdConvert(t *testing.T) { } t.Log(result) } + +func TestOrderPartRefund(t *testing.T) { + err := api.OrderPartRefund("3035779376398878822", []*RefundSku{ + &RefundSku{ + CustomeSkuID: "", + Number: "1", + }, + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/platformapi/ebaiapi/shop.go b/platformapi/ebaiapi/shop.go index b88223f2..8bd38e27 100644 --- a/platformapi/ebaiapi/shop.go +++ b/platformapi/ebaiapi/shop.go @@ -37,6 +37,7 @@ const ( KeyCustomSkuID = "custom_sku_id" KeySkuID = "sku_id" + KeyUPC = "upc" KeyName = "name" KeyPhone = "phone" @@ -47,22 +48,23 @@ const ( CoordTypeAutonavi = "amap" ) +// https://open-be.ele.me/dev/api/doc/v3/#api-Shop-shop_get const ( - DeliveryTypeElmNone = 0 - DeliveryTypeElmXingHuoTrial = 1 - DeliveryTypeElmXingHuo = 4 - DeliveryTypeElmFengNiaoZS = 5 - DeliveryTypeElmFengNiaoZSKA = 6 - DeliveryTypeElmFengNiaoKS = 9 - DeliveryTypeElmXingHuoZBTrial = 10 - DeliveryTypeElmXingHuoZB = 11 - DeliveryTypeElmNewRetail = 12 - DeliveryTypeElmEBase = 13 - DeliveryTypeElmEPeiSong = 14 - DeliveryTypeElmFengNiaoHybrid = 15 - DeliveryTypeElmFengNiaoNiubee = 16 - DeliveryTypeElmXingHuoKA = 17 - DeliveryTypeElmXingHuoZBKA = 18 + DeliveryTypeElmNone = 0 // 暂无 + DeliveryTypeElmXingHuoTrial = 1 // 星火计划(试用) + DeliveryTypeElmXingHuo = 4 // 星火计划 + DeliveryTypeElmFengNiaoZS = 5 // 蜂鸟专送 + DeliveryTypeElmFengNiaoZSKA = 6 // 蜂鸟专送,KA + DeliveryTypeElmFengNiaoKS = 9 // 蜂鸟快送 + DeliveryTypeElmXingHuoZBTrial = 10 // 星火众包(试用) + DeliveryTypeElmXingHuoZB = 11 // 星火众包 + DeliveryTypeElmNewRetail = 12 // 新零售 + DeliveryTypeElmEBase = 13 // e基础 + DeliveryTypeElmEPeiSong = 14 // e配送 + DeliveryTypeElmFengNiaoHybrid = 15 // 蜂鸟混合送 + DeliveryTypeElmFengNiaoNiubee = 16 // 蜂鸟质选 + DeliveryTypeElmXingHuoKA = 17 // 星火计划KA + DeliveryTypeElmXingHuoZBKA = 18 // 星火众包KA ) type ShopInfo struct { diff --git a/platformapi/ebaiapi/shop_sku.go b/platformapi/ebaiapi/shop_sku.go index 5a5e2e59..fe3f36c1 100644 --- a/platformapi/ebaiapi/shop_sku.go +++ b/platformapi/ebaiapi/shop_sku.go @@ -1,6 +1,7 @@ package ebaiapi import ( + "fmt" "regexp" "strings" @@ -8,8 +9,9 @@ import ( ) const ( - SkuStatusOnline = 1 - SkuStatusOffline = 0 + SkuStatusOnline = 1 // 为上架 + SkuStatusOffline = 0 // 为下架 + SkuStatusDeleted = 2 // 为删除 ) const ( @@ -17,7 +19,8 @@ const ( UPCTypePrivate = 0 ) const ( - MaxLeftNum = 99999 + MaxLeftNum = 99999 + MaxSkuNameByteCount = 100 // skuname的最大字节数,注意不是字符数,超长饿百会报错:{"data":"","errno":20200,"error":"invalid param:[name]... ) type CategoryInfo struct { @@ -28,17 +31,87 @@ type CategoryInfo struct { Level int `json:"level"` } +type SkuListParams struct { + Page int `json:"page,omitempty"` + PageSize int `json:"pagesize,omitempty"` + Upc string `json:"upc,omitempty"` + SkuID int64 `json:"sku_id,omitempty"` + CustomSkuID string `json:"custom_sku_id,omitempty"` + UpcType int `json:"upc_type,omitempty"` + GetUncate int `json:"get_uncate,omitempty"` + Delete int `json:"delete,omitempty"` + Enabled int `json:"enabled,omitempty"` + StartTime int `json:"start_time,omitempty"` + EndTime int `json:"end_time,omitempty"` +} + +type SkuPhotoInfo struct { + IsMaster int `json:"is_master"` + URL string `json:"url"` +} + +type SkuCustomCatInfo struct { + CustomCatID string `json:"custom_cat_id"` + CustomCatName string `json:"custom_cat_name"` +} + +type SkuInfo struct { + CustomCatIDs string `json:"custom_cat_ids"` + CustomCatList []*SkuCustomCatInfo `json:"custom_cat_list"` + CustomSkuID string `json:"custom_sku_id"` + IsInActivity int `json:"is_in_activity"` + LeftNum int `json:"left_num"` + MarketPrice int `json:"market_price"` + Minimum int `json:"minimum"` + Name string `json:"name"` + NeedIce int `json:"need_ice"` + Photos []*SkuPhotoInfo `json:"photos"` + PreminusWeight int `json:"preminus_weight"` + PreparationTime string `json:"preparation_time"` + ProductionAddr1 string `json:"production_addr1"` + ProductionAddr2 string `json:"production_addr2"` + ProductionAddr3 string `json:"production_addr3"` + Rtf string `json:"rtf"` + SalePrice int64 `json:"sale_price"` + SaleStep string `json:"sale_step"` + SaleUnit string `json:"sale_unit"` + ShelfNumber string `json:"shelf_number"` + SkuID int64 `json:"sku_id"` + // SkuProperty []interface{} `json:"sku_property"` + Status int `json:"status"` + Summary string `json:"summary"` + Upc string `json:"upc"` + UpcType string `json:"upc_type"` + UpdateTime string `json:"update_time"` + Weight int `json:"weight"` + WeightFlag int `json:"weight_flag"` +} + type PageDataInfo struct { - Total int - Page int - Pages int - List []map[string]interface{} + Total int `json:"Total"` + Page int `json:"Page"` + Pages int `json:"Pages"` + List []*SkuInfo `json:"List"` } var ( skuExistReg = regexp.MustCompile(`\s?,\s?sku_id:(\d+)`) ) +func genSkuIDParams(skuID int64, customSkuID, upc string) map[string]interface{} { + params := map[string]interface{}{} + if skuID != 0 { + params[KeySkuID] = skuID + } else if customSkuID != "" { + params[KeyCustomSkuID] = customSkuID + } else if upc != "" { + params[KeyUPC] = upc + } else { + panic("skuID, customSkuID and upc are all no value!") + } + return params +} + // category相关的函数,shop_custom_id可重 func (a *API) ShopCategoryCreate(shopID string, parentID int64, name string, rank int) (catID int64, err error) { @@ -99,21 +172,14 @@ func (a *API) SkuGetItemsByCategoryId(shopID string, categoryID int64) (skus []m return nil, err } -func (a *API) SkuList(shopID string, params map[string]interface{}) (skuInfo *PageDataInfo, err error) { - defParams := map[string]interface{}{ - KeyShopID: shopID, - } - result, err := a.AccessAPI("sku.list", utils.MergeMaps(params, defParams)) +func (a *API) SkuList(shopID string, params *SkuListParams) (skuInfo *PageDataInfo, err error) { + paramMap := utils.Struct2FlatMap(params) + paramMap[KeyShopID] = shopID + result, err := a.AccessAPI("sku.list", paramMap) if err == nil { - data := result.Data.(map[string]interface{}) - return &PageDataInfo{ - Total: int(utils.MustInterface2Int64(data["total"])), - Page: int(utils.MustInterface2Int64(data["page"])), - Pages: int(utils.MustInterface2Int64(data["pages"])), - List: utils.Slice2MapSlice(data["list"].([]interface{})), - }, nil + err = utils.Map2StructByJson(result.Data, &skuInfo, true) } - return nil, err + return skuInfo, err } func (a *API) SkuCreate(shopID string, customSkuID int, params map[string]interface{}) (skuID int64, err error) { @@ -168,9 +234,6 @@ func (a *API) SkuDelete(shopID, skuIDsStr string) (err error) { KeySkuID: skuIDsStr, } _, err = a.AccessAPI("sku.delete", params) - if err == nil { - return nil - } return err } @@ -180,9 +243,6 @@ func (a *API) SkuDeleteByCustomIDs(shopID, customSkuIDsStr string) (err error) { KeyCustomSkuID: customSkuIDsStr, } _, err = a.AccessAPI("sku.delete", params) - if err == nil { - return nil - } return err } @@ -192,9 +252,13 @@ func (a *API) SkuOnline(shopID, skuIDsStr string) (err error) { KeySkuID: skuIDsStr, } _, err = a.AccessAPI("sku.online", params) - if err == nil { - return nil - } + return err +} + +func (a *API) SkuOnlineOne(shopID string, skuID int64, customSkuID, upc string) (err error) { + params := genSkuIDParams(skuID, customSkuID, upc) + params[KeyShopID] = shopID + _, err = a.AccessAPI("sku.online.one", params) return err } @@ -204,9 +268,41 @@ func (a *API) SkuOffline(shopID, skuIDsStr string) (err error) { KeySkuID: skuIDsStr, } _, err = a.AccessAPI("sku.offline", params) - if err == nil { - return nil + return err +} + +func (a *API) SkuOfflineOne(shopID string, skuID int64, customSkuID, upc string) (err error) { + params := genSkuIDParams(skuID, customSkuID, upc) + params[KeyShopID] = shopID + _, err = a.AccessAPI("sku.offline.one", params) + return err +} + +func genSkuPriceParams(skuPriceStr, customSkuPriceStr, upcPriceStr string) map[string]interface{} { + params := map[string]interface{}{} + if skuPriceStr != "" { + params["skuid_price"] = skuPriceStr + } else if skuPriceStr != "" { + params["custom_sku_id"] = customSkuPriceStr + } else if upcPriceStr != "" { + params["upc_price"] = upcPriceStr + } else { + panic("skuPriceStr, customSkuPriceStr and upcPriceStr are all empty!") } + return params +} + +func (a *API) SkuPriceUpdateBatch(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) { + params := genSkuPriceParams(skuPriceStr, customSkuPriceStr, upcPriceStr) + params[KeyShopID] = shopID + _, err = a.AccessAPI("sku.price.update.batch", params) + return err +} + +func (a *API) SkuPriceUpdateOne(shopID, skuPriceStr, customSkuPriceStr, upcPriceStr string) (err error) { + params := genSkuPriceParams(skuPriceStr, customSkuPriceStr, upcPriceStr) + params[KeyShopID] = shopID + _, err = a.AccessAPI("sku.price.update.one", params) return err } @@ -217,12 +313,30 @@ func (a *API) SkuShopCategoryMap(shopID string, skuID int64, categoryIDsStr stri "category_id": categoryIDsStr, } _, err = a.AccessAPI("sku.shop.category.map", params) - if err == nil { - return nil - } return err } +// 饿百的这个API有点怪,虽然有shopID参数,但返回的链接可以跨店使用 +func (a *API) SkuUploadRTF(shopID, rtfDetail string) (rtfURL string, err error) { + params := map[string]interface{}{ + KeyShopID: shopID, + "rtf_detail": rtfDetail, + } + result, err := a.AccessAPI("sku.uploadrtf", params) + if err == nil { + rtfURL = utils.Interface2String(result.Data.(map[string]interface{})["url"]) + } + return rtfURL, err +} + +func BuildRFTFromImgs(imgList ...string) string { + imgList2 := make([]string, len(imgList)) + for index, img := range imgList { + imgList2[index] = fmt.Sprintf(`%s`, img, img) + } + return strings.Join(imgList2, "\n") +} + // func interface2CatList(data interface{}, level int) (cats []*CategoryInfo) { maps, ok := data.([]interface{}) diff --git a/platformapi/ebaiapi/shop_sku_test.go b/platformapi/ebaiapi/shop_sku_test.go index a9631343..501250ff 100644 --- a/platformapi/ebaiapi/shop_sku_test.go +++ b/platformapi/ebaiapi/shop_sku_test.go @@ -48,9 +48,8 @@ func TestSkuGetItemsByCategoryId(t *testing.T) { } func TestSkuList(t *testing.T) { - result, err := api.SkuList(testShopID, map[string]interface{}{ - // KeySkuID: 153879464137191, - // "delete": 1, + result, err := api.SkuList(testShopID, &SkuListParams{ + SkuID: 15579787500720732, }) if err != nil { t.Fatal(err) @@ -59,6 +58,16 @@ func TestSkuList(t *testing.T) { } } +func TestSkuUploadRTF(t *testing.T) { + rtfDetail := BuildRFTFromImgs("https://image.jxc4.com/sijidou.jpg") + t.Log(rtfDetail) + result, err := api.SkuUploadRTF("2", rtfDetail) + if err != nil { + t.Fatal(err) + } + t.Log(result) +} + func TestSkuCreate(t *testing.T) { result, err := api.SkuCreate(testShopID, 17, map[string]interface{}{ "name": "测试商品", @@ -83,6 +92,21 @@ func TestSkuCreate(t *testing.T) { } } +func TestSkuUpdate(t *testing.T) { + // 15579787500720732 高级 + + result, err := api.SkuUpdate("2", 1557043939079105, map[string]interface{}{ + // "name": "高级商品2015a333约1100g/份", + // "rtf": "http://www.rosy.net.cn/rtf.html", + "shelf_number": 12, + }) + if err != nil { + t.Fatal(err) + } else { + t.Log(utils.Format4Output(result, false)) + } +} + func TestSkuDelete(t *testing.T) { err := api.SkuDelete(testShopID, "153922044227304") if err != nil { diff --git a/platformapi/ebaiapi/store_page.go b/platformapi/ebaiapi/store_page.go index 2dfc445c..c57fff73 100644 --- a/platformapi/ebaiapi/store_page.go +++ b/platformapi/ebaiapi/store_page.go @@ -3,6 +3,7 @@ package ebaiapi import ( "fmt" "net/http" + "strings" "time" "git.rosy.net.cn/baseapi" @@ -11,8 +12,9 @@ import ( ) const ( - storeURL = "https://be.ele.me" - getStoreURL = "https://newretail.ele.me" + storeURL = "https://be.ele.me" + getStoreURL = "https://newretail.ele.me" + swithShopURL = "crm/manager/switchshop" ) const ( @@ -32,6 +34,131 @@ const ( CommentContentNoContent = 0 ) +type PageShopUserInfo struct { + CategoryID string `json:"category_id"` + CityID string `json:"city_id"` + CityName string `json:"city_name"` + DeliveryParty string `json:"delivery_party"` + EleID string `json:"ele_id"` + EleSoaToken string `json:"ele_soa_token"` + IsAPI int `json:"is_api"` + IsStore interface{} `json:"is_store"` + MerchantID string `json:"merchant_id"` + MerchantName string `json:"merchant_name"` + Role struct { + Ename string `json:"ename"` + Name string `json:"name"` + ShopRoleID int64 `json:"shop_role_id"` + } `json:"role"` + SalesID string `json:"sales_id"` + SalesName string `json:"sales_name"` + ServicePackage struct { + BaiduSign int `json:"baiduSign"` + EleSign int `json:"eleSign"` + } `json:"service_package"` + ShopInfo struct { + AreaID string `json:"area_id"` + BaiduBusinessState string `json:"baidu_business_state"` + BaiduOnlineStatus string `json:"baidu_online_status"` + BaiduTakeoutLogo string `json:"baidu_takeout_logo"` + CategoryID string `json:"category_id"` + CategoryIds string `json:"category_ids"` + CategoryName string `json:"category_name"` + County string `json:"county"` + CreateType string `json:"create_type"` + CreatedAt string `json:"created_at"` + CrmOncallType int `json:"crm_oncall_type"` + EffectiveAt string `json:"effective_at"` + EleBusinessState string `json:"ele_business_state"` + EleDeliveryParty string `json:"ele_delivery_party"` + EleID string `json:"ele_id"` + EleOnlineStatus string `json:"ele_online_status"` + EleShopLogo interface{} `json:"ele_shop_logo"` + IsSignZhongbao string `json:"is_sign_zhongbao"` + OnlineStatus string `json:"online_status"` + Phone string `json:"phone"` + ServStatus string `json:"serv_status"` + ShopTransactionOpen int `json:"shop_transaction_open"` + SourceName string `json:"source_name"` + SupplierID string `json:"supplier_id"` + TakeoutBoxPrice string `json:"takeout_box_price"` + TakeoutDispatchTime []struct { + End string `json:"end"` + Start string `json:"start"` + } `json:"takeout_dispatch_time"` + TakeoutOncallType string `json:"takeout_oncall_type"` + TakeoutOpenTime []struct { + End string `json:"end"` + Start string `json:"start"` + } `json:"takeout_open_time"` + TakeoutServicePhone string `json:"takeout_service_phone"` + TakeoutShopLogo string `json:"takeout_shop_logo"` + TransferStatus string `json:"transfer_status"` + } `json:"shop_info"` + ShopRole int `json:"shop_role"` + ShopUserID string `json:"shop_user_id"` + SwitchFromSupplier int `json:"switch_from_supplier"` + UserName string `json:"user_name"` + UserPhone string `json:"user_phone"` + YellowPrompt struct { + Capacity int `json:"capacity"` + Complete int `json:"complete"` + Display bool `json:"display"` + Msg string `json:"msg"` + MsgCount int `json:"msg_count"` + MsgList []interface{} `json:"msg_list"` + Online int `json:"online"` + PcURL string `json:"pc_url"` + TransferStatus string `json:"transfer_status"` + Type int `json:"type"` + URL string `json:"url"` + } `json:"yellow_prompt"` +} + +type PageShopHealthInfo struct { + MerchantID string `json:"merchant_id"` + MerchantName string `json:"merchant_name"` + + BadOrderRate string `json:"bad_order_rate"` + Category2 string `json:"category_2"` + Data string `json:"data"` + EndTime string `json:"endTime"` + HasPhotoRate string `json:"has_photo_rate"` + Hours string `json:"hours"` + ID string `json:"id"` + IsButie string `json:"is_butie"` + IsCp string `json:"is_cp"` + IsDeliverFee string `json:"is_deliver_fee"` + IsGood string `json:"is_good"` + IsHealthy string `json:"is_healthy"` + IsJiedan string `json:"is_jiedan"` + IsManjian string `json:"is_manjian"` + IsQisong string `json:"is_qisong"` + IsShopNew string `json:"is_shop_new"` + IsSku string `json:"is_sku"` + IsYinye string `json:"is_yinye"` + IsYoutu string `json:"is_youtu"` + IsZs string `json:"is_zs"` + MinDeliveryFee string `json:"min_delivery_fee"` + MinDeliveryPrice string `json:"min_delivery_price"` + RestaurantSubsidy string `json:"restaurant_subsidy"` + ShopCategory string `json:"shop_category"` + ShopID string `json:"shop_id"` + SkuNum string `json:"sku_num"` + StartTime string `json:"startTime"` + TargetButie string `json:"target_butie"` + TargetCp string `json:"target_cp"` + TargetDeliverFee string `json:"target_deliver_fee"` + TargetJiedan string `json:"target_jiedan"` + TargetQisong string `json:"target_qisong"` + TargetSku string `json:"target_sku"` + TargetYinye string `json:"target_yinye"` + TargetYoutu string `json:"target_youtu"` + UnvalidOrderNum string `json:"unvalid_order_num"` + UpdateTime string `json:"update_time"` + Week string `json:"week"` +} + func (a *API) SetStoreCookie(key, value string) { a.locker.Lock() defer a.locker.Unlock() @@ -44,7 +171,7 @@ func (a *API) GetStoreCookie(key string) string { return a.storeCookies[key] } -func (a *API) AccessStorePage(subURL string) (retVal map[string]interface{}, err error) { +func (a *API) AccessStorePage2(subURL string, params map[string]interface{}, cookies map[string]string) (retVal map[string]interface{}, err error) { a.locker.RLock() storeCookieLen := len(a.storeCookies) a.locker.RUnlock() @@ -53,11 +180,15 @@ func (a *API) AccessStorePage(subURL string) (retVal map[string]interface{}, err } err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { + var request *http.Request fullURL := utils.GenerateGetURL(storeURL, subURL, nil) - // baseapi.SugarLogger.Debug(fullURL) - request, _ := http.NewRequest(http.MethodGet, fullURL, nil) - if err != nil { - return nil + if params == nil { + // baseapi.SugarLogger.Debug(fullURL) + request, _ = http.NewRequest(http.MethodGet, fullURL, nil) + } else { + 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") } a.locker.RLock() for k, v := range a.storeCookies { @@ -66,15 +197,30 @@ func (a *API) AccessStorePage(subURL string) (retVal map[string]interface{}, err Value: v, }) } + for k, v := range cookies { + request.AddCookie(&http.Cookie{ + Name: k, + Value: v, + }) + } a.locker.RUnlock() return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { retVal = jsonResult1 code := int(utils.MustInterface2Int64(jsonResult1["errno"])) if code == ResponseCodeSuccess { - retVal = jsonResult1["data"].(map[string]interface{}) + if subURL == swithShopURL { + for _, v := range response.Cookies() { + if v.Name == "SWITCH_SHOP" { + retVal = utils.Struct2FlatMap(v) + break + } + } + } else { + retVal, _ = jsonResult1["data"].(map[string]interface{}) + } return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorIntCode(jsonResult1["errmsg"].(string), code) @@ -87,6 +233,10 @@ func (a *API) AccessStorePage(subURL string) (retVal map[string]interface{}, err return retVal, err } +func (a *API) AccessStorePage(subURL string, params map[string]interface{}) (retVal map[string]interface{}, err error) { + return a.AccessStorePage2(subURL, params, nil) +} + func (a *API) GetRealMobile4Order(orderId string) (mobile string, err error) { retVal, err := a.GetStoreOrderInfo(orderId) if err == nil { @@ -96,7 +246,7 @@ func (a *API) GetRealMobile4Order(orderId string) (mobile string, err error) { } func (a *API) GetStoreOrderInfo(orderId string) (storeOrderInfo map[string]interface{}, err error) { - retVal, err := a.AccessStorePage(fmt.Sprintf("crm/orderlist?keyword=%s", orderId)) + retVal, err := a.AccessStorePage(fmt.Sprintf("crm/orderlist?keyword=%s", orderId), nil) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err == nil { resultList := retVal["order_list"].([]interface{}) @@ -123,7 +273,7 @@ func (a *API) GetStoreOrderInfoList(fromTime, toTime string, shopID string, orde } fixedURL := fmt.Sprintf(urlTemplate, params...) for { - retVal, err2 := a.AccessStorePage(fixedURL + "&page=" + utils.Int2Str(pageNo)) + retVal, err2 := a.AccessStorePage(fixedURL+"&page="+utils.Int2Str(pageNo), nil) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err = err2; err == nil { resultList := retVal["order_list"].([]interface{}) @@ -173,7 +323,7 @@ func (a *API) getCommentList(isElm bool, fromTime, toTime time.Time, shopID, sup } fixedURL := fmt.Sprintf(urlTemplate, params...) for { - retVal, err2 := a.AccessStorePage(fixedURL + "&page_num=" + utils.Int2Str(pageNo)) + retVal, err2 := a.AccessStorePage(fixedURL+"&page_num="+utils.Int2Str(pageNo), nil) if err = err2; err == nil { for _, comment := range retVal["comment_list"].([]interface{}) { commentMap := comment.(map[string]interface{}) @@ -213,7 +363,7 @@ func (a *API) PageGetSkuList(baiduShopID int64) (skuList []map[string]interface{ } fixedURL := fmt.Sprintf(urlTemplate, params...) for { - retVal, err2 := a.AccessStorePage(fixedURL + "&curpage=" + utils.Int2Str(pageNo)) + retVal, err2 := a.AccessStorePage(fixedURL+"&curpage="+utils.Int2Str(pageNo), nil) if err = err2; err == nil { for _, sku := range retVal["sku_list"].([]interface{}) { skuList = append(skuList, sku.(map[string]interface{})) @@ -236,7 +386,7 @@ func (a *API) PageGetCustomSkuList(baiduShopID int64, customCatID int64) (skuLis customCatID, } fixedURL := fmt.Sprintf(urlTemplate, params...) - retVal, err := a.AccessStorePage(fixedURL) + retVal, err := a.AccessStorePage(fixedURL, nil) if err == nil { return utils.Slice2MapSlice(retVal["sku_list"].([]interface{})), nil } @@ -249,7 +399,7 @@ func (a *API) PageGetCustomCatList(baiduShopID int64) (catList []map[string]inte baiduShopID, } fixedURL := fmt.Sprintf(urlTemplate, params...) - retVal, err := a.AccessStorePage(fixedURL) + retVal, err := a.AccessStorePage(fixedURL, nil) if err == nil { return utils.Slice2MapSlice(retVal["cat_list"].([]interface{})), nil } @@ -257,15 +407,17 @@ func (a *API) PageGetCustomCatList(baiduShopID int64) (catList []map[string]inte } func (a *API) GetStoreInfo(storeId string) (storeInfo map[string]interface{}, err error) { - retVal, err := a.AccessStorePage2(storeId) + retVal, err := a.AccessStorePageNoCookie(fmt.Sprintf("newretail/shop/getshopinfo?&lat=0&lng=0&shop_id=%s", storeId)) if err != nil { return nil, err } + if retVal != nil { + retVal["shop_id"] = storeId + } return retVal, err } -func (a *API) AccessStorePage2(storeId string) (retVal map[string]interface{}, err error) { - subURL := fmt.Sprintf("newretail/shop/getshopinfo?&lat=0&lng=0&shop_id=%s", storeId) +func (a *API) AccessStorePageNoCookie(subURL string) (retVal map[string]interface{}, err error) { err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { fullURL := utils.GenerateGetURL(getStoreURL, subURL, nil) @@ -276,16 +428,12 @@ func (a *API) AccessStorePage2(storeId string) (retVal map[string]interface{}, e return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := int(utils.MustInterface2Int64(jsonResult1["error_no"])) if code == ResponseCodeSuccess { retVal = jsonResult1["result"].(map[string]interface{}) - retVal["shop_id"] = storeId return platformapi.ErrLevelSuccess, nil } else if code == GetStoreSuccessButUnderfind { - retVal = make(map[string]interface{}) - retVal["shop_id"] = storeId - retVal["storeIsUnderfind"] = 1 return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorIntCode(jsonResult1["error_msg"].(string), code) @@ -295,29 +443,61 @@ func (a *API) AccessStorePage2(storeId string) (retVal map[string]interface{}, e } func (a *API) GetStoreList(lng string, lat string) (retVal map[string]interface{}, err error) { - retVal, err = a.AccessStorePage3(fmt.Sprintf("/newretail/main/shoplist?channel=kitchen&pn=1&rn=999&lng=%s&lat=%s", lng, lat)) + retVal, err = a.AccessStorePageNoCookie(fmt.Sprintf("/newretail/main/shoplist?channel=kitchen&pn=1&rn=999&lng=%s&lat=%s", lng, lat)) return retVal, err } -func (a *API) AccessStorePage3(subURL string) (retVal map[string]interface{}, err error) { - err = platformapi.AccessPlatformAPIWithRetry(a.client, - func() *http.Request { - fullURL := utils.GenerateGetURL(getStoreURL, subURL, nil) - request, _ := http.NewRequest(http.MethodGet, fullURL, nil) - if err != nil { - return nil - } - return request - }, - a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { - code := int(utils.MustInterface2Int64(jsonResult1["error_no"])) - if code == ResponseCodeSuccess { - retVal = jsonResult1["result"].(map[string]interface{}) - return platformapi.ErrLevelSuccess, nil - } - newErr := utils.NewErrorIntCode(jsonResult1["error_msg"].(string), code) - return platformapi.ErrLevelCodeIsNotOK, newErr - }) - return retVal, err +func (a *API) SwitchShop(baiduShopID int64) (switchShopCookie string, err error) { + result, err := a.AccessStorePage("crm/manager/switchshop", map[string]interface{}{ + "switch_shop_id": baiduShopID, + }) + if err == nil { + switchShopCookie = utils.Interface2String(result["Value"]) + } + return switchShopCookie, err +} + +func (a *API) GetShopUserInfo2(switchShopCookie string) (shopUserInfo *PageShopUserInfo, err error) { + shopInfo, err := a.AccessStorePage2("crm/account/getshopuserinfo", nil, map[string]string{ + "SWITCH_SHOP": switchShopCookie, + }) + if err == nil { + err = utils.Map2StructByJson(shopInfo, &shopUserInfo, true) + } + return shopUserInfo, err +} + +func (a *API) GetShopUserInfo(baiduShopID int64) (shopUserInfo *PageShopUserInfo, err error) { + switchShopCookie, err := a.SwitchShop(baiduShopID) + if err != nil { + return nil, err + } + return a.GetShopUserInfo2(switchShopCookie) +} + +func (a *API) GetShopHealthByDetail2(switchShopCookie string) (shopHealthDetail *PageShopHealthInfo, err error) { + shopInfo, err := a.GetShopUserInfo2(switchShopCookie) + if err != nil { + return nil, err + } + result, err := a.AccessStorePage2("crm/getshophealthydetail", map[string]interface{}{ + "shop_id": shopInfo.EleID, + }, map[string]string{ + "SWITCH_SHOP": switchShopCookie, + }) + if err == nil { + if err = utils.Map2StructByJson(result, &shopHealthDetail, true); err == nil { + shopHealthDetail.MerchantID = shopInfo.MerchantID + shopHealthDetail.MerchantName = shopInfo.MerchantName + } + } + return shopHealthDetail, err +} + +func (a *API) GetShopHealthByDetail(baiduShopID int64) (shopHealthDetail *PageShopHealthInfo, err error) { + switchShopCookie, err := a.SwitchShop(baiduShopID) + if err != nil { + return nil, err + } + return a.GetShopHealthByDetail2(switchShopCookie) } diff --git a/platformapi/ebaiapi/store_page_test.go b/platformapi/ebaiapi/store_page_test.go index 0075f5b8..a151e2e6 100644 --- a/platformapi/ebaiapi/store_page_test.go +++ b/platformapi/ebaiapi/store_page_test.go @@ -81,3 +81,43 @@ func TestPageGetCustomCatList(t *testing.T) { baseapi.SugarLogger.Debug(utils.Format4Output(catList, false)) } } + +func TestSwitchShop(t *testing.T) { + cookie, err := api.SwitchShop(2233065941) + if err != nil { + t.Fatal(err) + } + t.Log(cookie) +} + +func TestGetShopUserInfo(t *testing.T) { + result, err := api.GetShopUserInfo(32267034127) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestGetShopHealthByDetail(t *testing.T) { + result, err := api.GetShopHealthByDetail(32267034127) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestGetStoreList(t *testing.T) { + result, err := api.GetStoreList("104.057218", "30.6949") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestGetStoreInfo(t *testing.T) { + result, err := api.GetStoreInfo("170879219") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/elmapi/elmapi.go b/platformapi/elmapi/elmapi.go index dfbf8c16..08866a8e 100644 --- a/platformapi/elmapi/elmapi.go +++ b/platformapi/elmapi/elmapi.go @@ -159,7 +159,7 @@ func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *R return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { resultError, _ := jsonResult1["error"].(map[string]interface{}) retVal = &ResponseResult{ ID: jsonResult1["id"].(string), @@ -216,7 +216,7 @@ func (a *API) AcccessAPI2(baseURL string, params map[string]interface{}, method return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { retVal = jsonResult1 return platformapi.ErrLevelSuccess, nil }) diff --git a/platformapi/feieapi/feieapi.go b/platformapi/feieapi/feieapi.go index dba9e2ff..394fb759 100644 --- a/platformapi/feieapi/feieapi.go +++ b/platformapi/feieapi/feieapi.go @@ -121,7 +121,7 @@ func (a *API) AccessAPI(apiName string, apiParams map[string]interface{}) (retVa return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := int(utils.Interface2Int64WithDefault(jsonResult1["ret"], ResponseCodeSuccess)) if code == ResponseCodeSuccess { retVal = jsonResult1["data"] diff --git a/platformapi/jdapi/callback.go b/platformapi/jdapi/callback.go index 91406cce..2decd048 100644 --- a/platformapi/jdapi/callback.go +++ b/platformapi/jdapi/callback.go @@ -2,19 +2,102 @@ package jdapi import ( "fmt" + "io/ioutil" + "net/http" "net/url" + "strings" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) +const ( + CallbackPrefix = "/djsw/" +) + +// 如下的常量其实都是京东回调消息的 +const ( + OrderStatusAddComment = "12001" + OrderStatusModifyComment = "12006" + OrderStatusTipChanged = "12008" + + OrderStatusPurchased = "41000" // 也即待处理,JD的消息很怪,新订单消息发过来是32000,但如果不是自动接单的,去查却是41000?,接单后才变为32000 + + OrderStatusPayed = "31020" // 已付款 + + StatusIDNewOrder = "32000" + OrderStatusWaitOutStore = "32000" + OrderStatusAdjust = "33080" + StatusIDWaitOutStore = "32001" + OrderStatusFinishedPickup = "2" + OrderStatusDelivering = "33040" + + OrderStatusDelivered = "33060" + OrderStatusFinished = "90000" + OrderStatusCanceled = "20020" + + OrderStatusUserApplyCancel = "20030" // 这个其实不是一个状态,是一个动作 + OrderStatusLocked = "20010" + OrderStatusUnlocked = "20050" + OrderStatusInfoChanged = "1" // 订单信息变更消息 + + OrderStatusPayFinishedSettle = "330901" // 订单支付完成应结 + OrderStatusAdjustSettle = "330902" // 订单调整后应结 + OrderStatusSwitch2SelfSettle = "330903" // 订单众包配送转自送后应结 +) + const ( StatusIDAddStore = "12003" // 新增门店消息 StatusIDDelStore = "12004" // 删除门店消息 StatusIDUpdateStore = "12009" // 修改门店消息 ) +const ( + AfsServiceStateWaiting4Audit = "10" // 待审核 + AfsServiceStateWaiting4UserFeedback = "11" // 待用户反馈 + AfsServiceStateWaiting4CSFeedback = "12" // 待客服反馈 + AfsServiceStateWaiting4GetGoods = "20" // 待取件 + AfsServiceStateRefundProcessing = "30" // 退款处理中 + AfsServiceStateWaiting4MerchantReceiveGoods = "31" // 待商家收货审核 + AfsServiceStateRefundSuccess = "32" // 退款成功 + AfsServiceStateRefundFailed = "33" // 退款失败 + AfsServiceStateAuditRefused = "40" // 审核不通过-驳回 + AfsServiceStateUserCanceled = "50" // 客户取消 + AfsServiceStateMerchantFailedReceiveGoods = "60" // 商家收货审核不通过 + AfsServiceStateSolved = "70" // 已解决 + AfsServiceStateWaiting4DirectCompensate = "90" // 待直陪 + AfsServiceStateDirectCompensate = "91" // 直赔 + AfsServiceStateDirectCompensateSuccess = "92" // 直赔成功 + AfsServiceStateDirectCompensateFailed = "93" // 直赔失败 + + AfsServiceStateWaiting4ReturnGoods = "110" // 待退货 + AfsServiceStateGetGoodsSuccess = "111" // 取货成功 + AfsServiceStateGettingGoods = "1101" // 取货中 + AfsServiceStateGetGoods2Shop = "1111" // 退货成功-商品已送至门店 + AfsServiceStateGetGoodsConfirmed = "1112" // 退货成功-商家已确认收货 + + AfsServiceStateGetGoodsWaiting4Refund = "112" // 退货成功-待退款 + AfsServiceStateReturnGoodsFailed = "113" // 退货失败 + AfsServiceStateReturnGoodsSuccess = "114" // 退货成功 +) + +const ( + // 订单 + CallbackMsgDeliveryCarrierModify = "deliveryCarrierModify" // 订单转自送消息(是指转自送成功后,用处不大) + CallbackMsgOrderAccounting = "orderAccounting" // 订单应结消息 + + // 账务 + CallbackMsgEndOrderFinance = "endOrderFinance" // 订单金额拆分完成消息 + CallbackMsgFinanceAdjustment = "financeAdjustment" // 财务调整单消息 + + // 售后 + CallbackMsgNewApplyAfterSaleBill = "newApplyAfterSaleBill" // 新建售后单申请消息 + CallbackMsgUpdateApplyAfterSaleBill = "updateApplyAfterSaleBill" // 修改售后单申请消息 + CallbackMsgNewAfterSaleBill = "newAfterSaleBill" // 新建售后单消息 + CallbackMsgAfterSaleBillStatus = "afterSaleBillStatus" // 售后单状态消息 +) + type CallbackResponse struct { Code string `json:"code"` Msg string `json:"msg"` @@ -22,7 +105,7 @@ type CallbackResponse struct { } type CallbackOrderMsg struct { - ID int `json:"-"` // 用于传递Jdorder的主键值,减少一次读库操作 + MsgURL string `json:"msgURL"` BillID string `json:"billId"` OutBillID string `json:"outBillId"` StatusID string `json:"statusId"` @@ -31,6 +114,7 @@ type CallbackOrderMsg struct { } type CallbackDeliveryStatusMsg struct { + MsgURL string `json:"msgURL"` OrderID string `json:"orderId"` DeliveryStatusTime string `json:"deliveryStatusTime"` DeliveryManNo string `json:"deliveryManNo"` @@ -62,12 +146,8 @@ const ( OpenSourceBatchTask = 14 ) -const ( - SaleBillStatusRefundSuccess = "32" - SaleBillStatusSaleReturnSuccess = "114" -) - type CallbackStoreStockMsg struct { + MsgURL string `json:"msgURL"` StationNo string `json:"stationNo"` SkuId int64 `json:"skuId"` Have bool `json:"have"` @@ -115,7 +195,12 @@ func (a *API) CheckCallbackValidation(values url.Values) (callbackResponse *Call return nil } -func (a *API) getCommonOrderCallbackMsg(data []byte, msg interface{}, needDecode bool) (callbackResponse *CallbackResponse) { +func (a *API) getCommonOrderCallbackMsg(request *http.Request, msg interface{}, needDecode bool) (callbackResponse *CallbackResponse) { + data, err := ioutil.ReadAll(request.Body) + if err != nil { + return Err2CallbackResponse(err, "") + } + // baseapi.SugarLogger.Debug(string(data)) result, err := utils.HTTPBody2Values(data, needDecode) if err != nil { return FormatErrorResponse @@ -132,28 +217,34 @@ func (a *API) getCommonOrderCallbackMsg(data []byte, msg interface{}, needDecode return nil } -func (a *API) GetOrderCallbackMsg(data []byte) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) { +func (a *API) GetOrderCallbackMsg(request *http.Request) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) { msg = new(CallbackOrderMsg) - callbackResponse = a.getCommonOrderCallbackMsg(data, msg, false) + if callbackResponse = a.getCommonOrderCallbackMsg(request, msg, false); callbackResponse == nil { + msg.MsgURL = getMsgURLFromRequest(request) + } return msg, callbackResponse } -func (a *API) GetOrderApplyCancelCallbackMsg(data []byte) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) { +func (a *API) GetOrderApplyCancelCallbackMsg(request *http.Request) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) { msg = new(CallbackOrderMsg) - callbackResponse = a.getCommonOrderCallbackMsg(data, msg, true) + if callbackResponse = a.getCommonOrderCallbackMsg(request, msg, true); callbackResponse == nil { + msg.MsgURL = getMsgURLFromRequest(request) + } return msg, callbackResponse } -func (a *API) GetOrderDeliveryCallbackMsg(data []byte) (msg *CallbackDeliveryStatusMsg, callbackResponse *CallbackResponse) { - msg = new(CallbackDeliveryStatusMsg) - callbackResponse = a.getCommonOrderCallbackMsg(data, msg, true) +func (a *API) GetOrderDeliveryCallbackMsg(request *http.Request) (msg *CallbackDeliveryStatusMsg, callbackResponse *CallbackResponse) { + if callbackResponse = a.getCommonOrderCallbackMsg(request, &msg, true); callbackResponse == nil { + msg.MsgURL = getMsgURLFromRequest(request) + } return msg, callbackResponse } -func (a *API) GetStoreStockCallbackMsg(data []byte) (msg *CallbackStoreStockMsg, callbackResponse *CallbackResponse) { +func (a *API) GetStoreStockCallbackMsg(request *http.Request) (msg *CallbackStoreStockMsg, callbackResponse *CallbackResponse) { msg = new(CallbackStoreStockMsg) + msg.MsgURL = getMsgURLFromRequest(request) var tmpMsg map[string]interface{} - callbackResponse = a.getCommonOrderCallbackMsg(data, &tmpMsg, true) + callbackResponse = a.getCommonOrderCallbackMsg(request, &tmpMsg, true) if callbackResponse == nil { msg.StationNo = utils.Interface2String(tmpMsg["stationNo"]) msg.SkuId = utils.Str2Int64(utils.Interface2String(tmpMsg["skuId"])) @@ -161,12 +252,15 @@ func (a *API) GetStoreStockCallbackMsg(data []byte) (msg *CallbackStoreStockMsg, msg.OperPin = utils.Interface2String(tmpMsg["operPin"]) msg.OperTime = utils.Str2Int64(utils.Interface2String(tmpMsg["operTime"])) msg.OperSource = int(utils.Str2Int64((utils.Interface2String(tmpMsg["operSource"])))) - have := utils.Interface2String(tmpMsg["have"]) - if have == "true" { - msg.Have = true - } else { - msg.Have = false - } + msg.Have = utils.Interface2String(tmpMsg["have"]) == "true" } return msg, callbackResponse } + +func getMsgURLFromRequest(request *http.Request) (msgURL string) { + index := strings.Index(request.URL.Path, CallbackPrefix) + if index >= 0 { + msgURL = request.URL.Path[index+len(CallbackPrefix):] + } + return msgURL +} diff --git a/platformapi/jdapi/jdapi.go b/platformapi/jdapi/jdapi.go index 22455a4e..103c2862 100644 --- a/platformapi/jdapi/jdapi.go +++ b/platformapi/jdapi/jdapi.go @@ -184,7 +184,7 @@ func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := jsonResult1["code"].(string) if code == ResponseCodeSuccess { retVal = jsonResult1 diff --git a/platformapi/jdapi/jdapi_test.go b/platformapi/jdapi/jdapi_test.go index 22bab009..4f26e40f 100644 --- a/platformapi/jdapi/jdapi_test.go +++ b/platformapi/jdapi/jdapi_test.go @@ -27,7 +27,7 @@ func init() { // 天天果园 // api = New("84541069-fbe2-424b-b625-9b2ba1d4c9e6", "5d5577a2506f41b8b4ec520ba83490f5", "0b01b9eeb15b41dab1c3d05d95c17a26") - api.SetStoreCookie("YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHBUOPCIUXDX4MQEAYEPUFFOAD4WJECT4R3K22T24MKC7OMIRDLX7S55243TDVXLO25PP4UYSPTTPMNRUFXDNP4WPE566Q6V4AH32F7HT") + api.SetStoreCookie("YYJV3NHVBPHLD36FWP6F3EM5PTXJ2XZQS7U4HWRIDPP4IWGUKUIB4XG5N26CZRDLDF7PKOXBPD6BNTUAJLETLZOIWMCVFI3K6MYZIY4QBIXIMXYDJNUKFGJVQTN5356SAD6WPCIHWNQAG7DDMF7L7S3SHDYZP7PPVMRGO4VWG2JRBMKFTOGIWZ5L2XHXC3SXQ4OLX7EL4RKUPZQT6GOH63KE3EVK37L5LG7TGSDGXFQP4377YK72UB5YZG6IJH6PY25YLLCJYPMDSHKPGYBUFJ4MMMKGN6MWB37CP7XVDBBZJ3U462ENTEXH744AWCQCIG2AAE2PKYVHC") } func TestTest(t *testing.T) { diff --git a/platformapi/jdapi/order.go b/platformapi/jdapi/order.go index 3b69aa6a..cbb2a755 100644 --- a/platformapi/jdapi/order.go +++ b/platformapi/jdapi/order.go @@ -2,34 +2,15 @@ package jdapi import ( "errors" + "strings" + + "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/utils" ) const ( - OrderStatusAddComment = "12001" - OrderStatusModifyComment = "12006" - OrderStatusTipChanged = "12008" - - OrderStatusPurchased = "41000" // 也即待处理,JD的消息很怪,新订单消息发过来是32000,但如果不是自动接单的,去查却是41000?,接单后才变为32000 - OrderStatusNew = "32000" - OrderStatusAdjust = "33080" - OrderStatusWaitOutStore = "32001" - OrderStatusFinishedPickup = "2" - OrderStatusDelivering = "33040" - - OrderStatusDelivered = "33060" - OrderStatusFinished = "90000" - OrderStatusCanceled = "20020" - - OrderStatusUserApplyCancel = "20030" // 这个其实不是一个状态,是一个动作 - OrderStatusLocked = "20010" - OrderStatusUnlocked = "20050" - OrderStatusInfoChanged = "1" - - OrderStatusPayFinishedSettle = "330901" // 订单支付完成应结 - OrderStatusAdjustSettle = "330902" // 订单调整后应结 - OrderStatusSwitch2SelfSettle = "330903" // 订单众包配送转自送后应结 + AfsPicPrefix = "http://img10.360buyimg.com/o2o" ) const ( @@ -71,20 +52,139 @@ const ( QueryOrderRetryCount = 1 // 因为京东到家当前不存在的订单也返回-4,暂时不重试 ) -var ( - ErrCanNotFindOrder = errors.New("can not find order") +const ( + AfsApproveTypeRefund = 1 // 退款 + AfsApproveTypeReturnGoods = 2 // 退货 + AfsApproveTypeRefused = 3 // 驳回 ) -var ( - orderOperationResultParser = genNoPageResultParser("code", "msg", "detail", "0") +const ( + AfsReasonTypeGoodsQuality = 201 // 商品质量问题/做工粗糙/有瑕疵 + AfsReasonTypeWrongGoods = 202 // 发错货 + AfsReasonTypeMissingGoods = 203 // 部分商品未收到 + AfsReasonTypeNoGoods = 501 // 全部商品未收到 + AfsReasonTypeDamagedGoods = 208 // 外表损伤(压坏,磕坏等) + AfsReasonTypeGoodsQuantity = 207 // 缺斤少两 + AfsReasonTypeAgreedByMerchant = 209 // 与商家协商一致 + AfsReasonTypeGoodsSizeNoSame = 302 // 大小尺寸与商品描述不符 + AfsReasonTypeGoodsColorNoSame = 303 // 颜色/款式/图案与描述不符 + AfsReasonWrongPurchase = 402 // 误购 + AfsReasonNotReceivedIntime = 502 // 未在时效内送达 ) +const ( + AfsDealTypeRefund = "10" // 仅退款 + AfsDealTypeDirectCompensate = "30" // 直赔 + AfsDealTypeReturnGoodsRefund = "40" // 退货退款 +) + +type AfsSkuDiscount struct { + DetailDiscountType int `json:"detailDiscountType"` + DiscountType int `json:"discountType"` + PlatPayMoney int64 `json:"platPayMoney"` + VenderPayMoney int64 `json:"venderPayMoney"` +} + +type AfsServiceDetail struct { + SkuIDIsv string `json:"skuIdIsv"` + WareID int64 `json:"wareId"` + WareName string `json:"wareName"` + WareNum int `json:"wareNum"` + SkuCount int `json:"skuCount"` + PromotionType int `json:"promotionType"` + Weight float64 `json:"weight"` + + AfsMoney int64 `json:"afsMoney"` + PlatPayMoney int64 `json:"platPayMoney"` + + AfsSkuDiscountList []*AfsSkuDiscount `json:"afsSkuDiscountList"` +} + +type AfsServiceResponse struct { + AfsServiceID int64 `json:"afsServiceId"` + AfsServiceOrder string `json:"afsServiceOrder"` + AfsServiceState int `json:"afsServiceState"` + CreateTime *utils.JavaDate `json:"createTime"` + UpdateTime *utils.JavaDate `json:"updateTime"` + CustomerMobilePhone string `json:"customerMobilePhone"` + CustomerName string `json:"customerName"` + OrderID string `json:"orderId"` + OrderSource int `json:"orderSource"` + OrderStatus int `json:"orderStatus"` + OrderType int `json:"orderType"` + PickwareAddress string `json:"pickwareAddress"` + + OrderFreightMoney int64 `json:"orderFreightMoney"` + AfsFreight int64 `json:"afsFreight"` + PackagingMoney int64 `json:"packagingMoney"` + TongchengFreightMoney int64 `json:"tongchengFreightMoney"` + MealBoxMoney int64 `json:"mealBoxMoney"` + + QuestionDesc string `json:"questionDesc"` + QuestionPic string `json:"questionPic"` + QuestionTypeCid int `json:"questionTypeCid"` + + ApplyDeal string `json:"applyDeal"` + DeliveryState string `json:"deliveryState"` + DeliveryMan string `json:"deliveryMan"` + DeliveryMobile string `json:"deliveryMobile"` + + StationID string `json:"stationId"` + StationName string `json:"stationName"` + StationNumOutSystem string `json:"stationNumOutSystem"` + VenderID string `json:"venderId"` + + AfsDetailList []*AfsServiceDetail `json:"afsDetailList"` +} + type OAOSAdjustDTO struct { SkuID int64 `json:"skuId,omitempty"` OutSkuID string `json:"outSkuId,omitempty"` SkuCount int `json:"skuCount"` } +type VenderAfsSkuDTO struct { + SkuID int64 `json:"skuId"` + SkuCount int `json:"skuCount"` + PromotionType int `json:"promotionType"` +} + +type OrderSettlementInfo struct { + AllAfterSaleFreight int64 `json:"allAfterSaleFreight"` + AllAfterSalePackageMoney int64 `json:"allAfterSalePackageMoney"` + BillTime *utils.JavaDate `json:"billTime"` + DistanceFreightMoney int64 `json:"distanceFreightMoney"` + FreightCommission int64 `json:"freightCommission"` + GoodsCommission int64 `json:"goodsCommission"` + GuaranteedCommission int64 `json:"guaranteedCommission"` + OrderCashOnDeliveryMoney int64 `json:"orderCashOnDeliveryMoney"` + OrderGiftCardMoney int64 `json:"orderGiftCardMoney"` + OrderID int64 `json:"orderId"` + OrgCode string `json:"orgCode"` + PackageCommission int64 `json:"packageCommission"` + PackageMoney int64 `json:"packageMoney"` + PlatDeliveryFreight int64 `json:"platDeliveryFreight"` + PlatFreightDiscountMoney int64 `json:"platFreightDiscountMoney"` + PlatOrderGoodsDiscountMoney int64 `json:"platOrderGoodsDiscountMoney"` + PlatSkuGoodsDiscountMoney int64 `json:"platSkuGoodsDiscountMoney"` + SettlementAmount int64 `json:"settlementAmount"` + StationNo string `json:"stationNo"` + UserActualPaidGoodsMoney int64 `json:"userActualPaidGoodsMoney"` + VenderDeliveryFreight int64 `json:"venderDeliveryFreight"` + VenderFreightDiscountMoney int64 `json:"venderFreightDiscountMoney"` + VenderOrderGoodsDiscountMoney int64 `json:"venderOrderGoodsDiscountMoney"` + VenderPaidTips int64 `json:"venderPaidTips"` + VenderSkuGoodsDiscountMoney int64 `json:"venderSkuGoodsDiscountMoney"` +} + +var ( + ErrCanNotFindOrder = errors.New("can not find order") +) + +var ( + orderOperationResultParser = genNoPageResultParser("code", "detail", "detail", "0") +) + // 订单列表查询接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=169&apiid=ba3027848c3c4fda9674966e2a466482 func (a *API) OrderQuery(jdParams map[string]interface{}) (retVal []interface{}, totalCount int, err error) { @@ -226,6 +326,14 @@ func (a *API) OrderShoudSettlementService(orderId string) (map[string]interface{ return result.(map[string]interface{}), nil } +func (a *API) OrderShoudSettlementService2(orderId string) (orderSettlement *OrderSettlementInfo, err error) { + result, err := a.OrderShoudSettlementService(orderId) + if err == nil { + err = utils.Map2StructByJson(result, &orderSettlement, false) + } + return orderSettlement, err +} + // 查询售后单详情接口 // https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=170&apiid=6805ed690b7b4776b058067312c57d98 func (a *API) GetAfsService(orderId string) (map[string]interface{}, error) { @@ -239,23 +347,35 @@ func (a *API) GetAfsService(orderId string) (map[string]interface{}, error) { return result.(map[string]interface{}), nil } +func (a *API) GetAfsService2(afsOrderID string) (afsOrderInfo *AfsServiceResponse, err error) { + result, err := a.GetAfsService(afsOrderID) + if err == nil { + if err = utils.Map2StructByJson(result, &afsOrderInfo, false); err != nil { + baseapi.SugarLogger.Warnf("GetAfsService2, result:%s failed with error:%v", utils.Format4Output(result, true), err) + } + } + return afsOrderInfo, err +} + // 商家审核用户取消申请接口 // https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=906b430307764a3ca3698c05c72f33d0 func (a *API) OrderCancelOperate(orderId string, isAgreed bool, operator, remark string) (err error) { jdParams := map[string]interface{}{ "orderId": orderId, "isAgreed": isAgreed, - "operator": operator, + "operator": utils.GetAPIOperator(operator), "remark": remark, } _, err = a.AccessAPINoPage("ocs/orderCancelOperate", jdParams, nil, nil, nullResultParser) return err } +// 订单调整接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=169&apiid=a7378109fd7243eea9efbb6231a7401c func (a *API) AdjustOrder(orderId, operPin, remark string, oaosAdjustDTOList []*OAOSAdjustDTO) (err error) { jdParams := map[string]interface{}{ "orderId": orderId, - "operPin": operPin, + "operPin": utils.GetAPIOperator(operPin), "remark": remark, "oaosAdjustDTOList": oaosAdjustDTOList, } @@ -269,7 +389,7 @@ func (a *API) ReceiveFailedAudit(orderId string, isAgreed bool, operator, remark jdParams := map[string]interface{}{ "orderId": orderId, "isAgreed": isAgreed, - "operator": operator, + "operator": utils.GetAPIOperator(operator), "remark": remark, } _, err = a.AccessAPINoPage("order/receiveFailedAudit", jdParams, nil, nil, nullResultParser) @@ -281,7 +401,7 @@ func (a *API) ReceiveFailedAudit(orderId string, isAgreed bool, operator, remark func (a *API) UrgeDispatching(orderId, updatePin string) (err error) { jdParams := map[string]interface{}{ "orderId": orderId, - "updatePin": updatePin, + "updatePin": utils.GetAPIOperator(updatePin), } _, err = a.AccessAPINoPage("bm/urgeDispatching", jdParams, nil, nil, nullResultParser) return err @@ -297,3 +417,71 @@ func (a *API) ConfirmReceiveGoods(orderId string) (err error) { _, err = a.AccessAPINoPage("order/confirmReceiveGoods", jdParams, nil, nil, nullResultParser) return err } + +// 申请售后单审核接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=170&apiid=1690f6efc0144d59823b236e0d8506a1 +func (a *API) AfsOpenApprove(afsOrderID string, afsApproveType int, rejectReason string, optPin string) (err error) { + jdParams := map[string]interface{}{ + "serviceOrder": afsOrderID, + "approveType": afsApproveType, + "optPin": utils.GetAPIOperator(optPin), + } + if rejectReason != "" { + jdParams["rejectReason"] = rejectReason + } + _, err = a.AccessAPINoPage("afs/afsOpenApprove", jdParams, nil, nil, nullResultParser) + return err +} + +// 售后单确认收货接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=170&apiid=4826086e81934405980ae26f80d956e0 +func (a *API) ConfirmReceipt(afsOrderID, pin string) (err error) { + jdParams := map[string]interface{}{ + "serviceOrder": afsOrderID, + "pin": utils.GetAPIOperator(pin), + } + _, err = a.AccessAPINoPage("afs/confirmReceipt", jdParams, nil, nil, nullResultParser) + return err +} + +// 商家自主发起售后接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=170&apiid=b8d1ddacb03846a8a2e78c79723c752f +func (a *API) AfsSubmit(OrderID, pin, questionTypeCode, questionDesc, questionPic, customerName, customerMobilePhone, address string, skuList []*VenderAfsSkuDTO) (afsOrderID string, err error) { + jdParams := map[string]interface{}{ + "orderId": OrderID, + "pin": utils.GetAPIOperator(pin), + "questionTypeCode": questionTypeCode, + } + if questionDesc != "" { + jdParams["questionDesc"] = questionDesc + } + if questionPic != "" { + jdParams["questionPic"] = questionPic + } + if customerName != "" { + jdParams["customerName"] = customerName + } + if customerMobilePhone != "" { + jdParams["customerMobilePhone"] = customerMobilePhone + } + if address != "" { + jdParams["address"] = address + } + result, err := a.AccessAPINoPage("afs/submit", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "0")) + if err == nil { + afsOrderID = utils.Interface2String(result.(map[string]interface{})["serviceOrder"]) + } + return afsOrderID, err +} + +func ProcessQuestionPic(questionPic string) (outQuestionPic string) { + if questionPic != "" { + picList := strings.Split(questionPic, ",") + picList2 := make([]string, len(picList)) + for index, pic := range picList { + picList2[index] = AfsPicPrefix + "/" + pic + } + outQuestionPic = strings.Join(picList2, ",") + } + return outQuestionPic +} diff --git a/platformapi/jdapi/order_test.go b/platformapi/jdapi/order_test.go index afc20ad4..88de4ba8 100644 --- a/platformapi/jdapi/order_test.go +++ b/platformapi/jdapi/order_test.go @@ -50,7 +50,7 @@ func TestOrderQuery(t *testing.T) { func TestGetOrderCallbackMsg(t *testing.T) { callbackStr := "timestamp=2018-06-27+12%3A43%3A51&sign=84096ADFAB81E224D6231269AD1F6AAB&v=1.0&jd_param_json=%7B%22billId%22%3A%22815338526000221%22%2C%22statusId%22%3A%2232000%22%2C%22timestamp%22%3A%222018-06-27+12%3A42%3A34%22%7D&token=91633f2a-c5f5-4982-a925-a220d19095c3&app_key=1dba76d40cac446ca500c0391a0b6c9d&format=json" - result, resp := api.GetOrderCallbackMsg([]byte(callbackStr)) + result, resp := api.GetOrderCallbackMsg(utils.BuildRequest(http.MethodPost, "", callbackStr, "")) if resp != nil { t.Fatal(resp) @@ -60,7 +60,7 @@ func TestGetOrderCallbackMsg(t *testing.T) { func TestGetOrderDeliveryCallbackMsg(t *testing.T) { callbackStr := "timestamp=2018-06-27%2B09%253A08%253A41&sign=54D9A8515BB5650DC3B81366E3286570&v=1.0&jd_param_json=%257B%2522createPin%2522%253A%2522JD_21134dac1c251d2%2522%252C%2522deliveryCarrierName%2522%253A%2522%25E8%25BE%25BE%25E8%25BE%25BE%25E4%25B8%2593%25E9%2580%2581%2522%252C%2522deliveryCarrierNo%2522%253A%25229966%2522%252C%2522deliveryStatus%2522%253A%252210%2522%252C%2522deliveryStatusTime%2522%253A%25222018-06-27%2B09%253A08%253A41%2522%252C%2522inputTime%2522%253A%25222018-06-27%2B09%253A08%253A41%2522%252C%2522orderId%2522%253A%2522815324888000121%2522%257D&token=91633f2a-c5f5-4982-a925-a220d19095c3&app_key=1dba76d40cac446ca500c0391a0b6c9d&format=json" - result, resp := api.GetOrderDeliveryCallbackMsg([]byte(callbackStr)) + result, resp := api.GetOrderDeliveryCallbackMsg(utils.BuildRequest(http.MethodPost, "", callbackStr, "")) if resp != nil { t.Fatal(resp) @@ -70,7 +70,7 @@ func TestGetOrderDeliveryCallbackMsg(t *testing.T) { func TestGetOrderApplyCancelCallbackMsg(t *testing.T) { callbackStr := "timestamp=2018-06-27%2B13%253A35%253A29&sign=F1398D658514D8864FFB56F5EF2C4792&v=1.0&jd_param_json=%257B%2522billId%2522%253A%2522815339944000322%2522%252C%2522remark%2522%253A%2522%25E5%2595%2586%25E5%25AE%25B6%25E7%25BC%25BA%25E8%25B4%25A7%2522%252C%2522statusId%2522%253A%252220030%2522%252C%2522timestamp%2522%253A%25222018-06-27%2B13%253A35%253A24%2522%257D&token=91633f2a-c5f5-4982-a925-a220d19095c3&app_key=1dba76d40cac446ca500c0391a0b6c9d&format=json" - result, resp := api.GetOrderApplyCancelCallbackMsg([]byte(callbackStr)) + result, resp := api.GetOrderApplyCancelCallbackMsg(utils.BuildRequest(http.MethodPost, "", callbackStr, "")) if resp != nil { t.Fatal(resp) @@ -81,7 +81,7 @@ func TestGetOrderApplyCancelCallbackMsg(t *testing.T) { func TestSimulateSendNewOrderMsg(t *testing.T) { orderInfo := &CallbackOrderMsg{ BillID: "813344594000041", - StatusID: OrderStatusNew, + StatusID: OrderStatusPurchased, Timestamp: utils.GetCurTimeStr(), } params := make(url.Values) @@ -163,7 +163,7 @@ func TestOrderAcceptOperate(t *testing.T) { } func TestOrderJDZBDelivery(t *testing.T) { - result, err := api.OrderJDZBDelivery("813344594000041", "") + result, err := api.OrderJDZBDelivery("911350836000622", "") if err != nil { t.Fatal(err.Error()) } @@ -171,7 +171,7 @@ func TestOrderJDZBDelivery(t *testing.T) { } func TestModifySellerDelivery(t *testing.T) { - result, err := api.ModifySellerDelivery("813344594000041", "") + result, err := api.ModifySellerDelivery("914376668000422", "") if err != nil { t.Fatal(err.Error()) } @@ -193,3 +193,27 @@ func TestDeliveryEndOrder(t *testing.T) { } sugarLogger.Debug(result) } + +func TestGetAfsService(t *testing.T) { + result, err := api.GetAfsService("22565438") + if err != nil { + t.Fatal(err.Error()) + } + sugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestGetAfsService2(t *testing.T) { + result, err := api.GetAfsService2("22565438") + if err != nil { + t.Fatal(err.Error()) + } + sugarLogger.Debug(utils.Format4Output(result, false)) +} + +func TestOrderShoudSettlementService2(t *testing.T) { + result, err := api.OrderShoudSettlementService2("914508761000241") + if err != nil { + t.Fatal(err.Error()) + } + sugarLogger.Debug(utils.Format4Output(result, false)) +} diff --git a/platformapi/jdapi/promotion_audit.go b/platformapi/jdapi/promotion_audit.go new file mode 100644 index 00000000..e993891f --- /dev/null +++ b/platformapi/jdapi/promotion_audit.go @@ -0,0 +1,108 @@ +package jdapi + +import ( + "time" + + "git.rosy.net.cn/baseapi/utils" +) + +const ( + AuditPromotionStateNew = 101 // 待开始 + AuditPromotionStateOnGoing = 102 // 进行中 + AuditPromotionStateEnded = 103 // 结束 + AuditPromotionStateCanceled = 104 // 取消 + AuditPromotionStateAll = 105 // 全部 +) + +type OpenPlatActivityQResponse struct { + AddLadderList []struct { + BenefitMaxCount int `json:"benefitMaxCount"` + DiscountAmount int `json:"discountAmount"` + DiscountRate float32 `json:"discountRate"` + LowMoney int `json:"lowMoney"` + LowerLimitCount int `json:"lowerLimitCount"` + OrderLadder string `json:"orderLadder"` + } `json:"addLadderList"` + Awords string `json:"awords"` + BeginDate *utils.JavaDate `json:"beginDate"` + CostRadios int `json:"costRadios"` + EndDate *utils.JavaDate `json:"endDate"` + ID int `json:"id"` + IsMerchant int `json:"isMerchant"` + OrderLadder string `json:"orderLadder"` + OrgCode string `json:"orgCode"` + OutActivityID string `json:"outActivityId"` + PromotionName string `json:"promotionName"` + SkuBeanList []struct { + SkuID int64 `json:"skuId"` + SkuName int64 `json:"skuName"` + } `json:"skuBeanList"` + State int `json:"state"` + StationBeanList []struct { + OrgCode interface{} `json:"orgCode"` + OrgName string `json:"orgName"` + OutStationNo string `json:"outStationNo"` + StationName string `json:"stationName"` + StationNo int `json:"stationNo"` + } `json:"stationBeanList"` +} + +type PromotionLspQuerySkuResult struct { + BeginTime *utils.JavaDate `json:"beginTime"` + EndTime *utils.JavaDate `json:"endTime"` + LimitDaily int `json:"limitDaily"` + LimitDevice int `json:"limitDevice"` + LimitPin int `json:"limitPin"` + PlatformRatio int `json:"platformRatio"` + PromotionPrice int `json:"promotionPrice"` + PromotionState int `json:"promotionState"` + PromotionType int `json:"promotionType"` + SkuID int64 `json:"skuId"` + Source string `json:"source"` + StationNo int64 `json:"stationNo"` + StoreRatio int `json:"storeRatio"` +} + +type PromotionLspQueryInfoResult struct { + BeginTime *utils.JavaDate `json:"beginTime"` + EndTime *utils.JavaDate `json:"endTime"` + PromotionInfoID int64 `json:"promotionInfoId"` + PromotionState int `json:"promotionState"` + PromotionType int `json:"promotionType"` + SkuResultList []*PromotionLspQuerySkuResult `json:"skuResultList"` + Source string `json:"source"` +} + +// 此接口逐渐会被[新版订单级促销]中相应接口替换 +// 根据到家活动ID查询订单级活动明细接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=196&apiid=ff1ade31ac1b4a50be760854c777b567 +func (a *API) OrderDiscountQueryActivityInfoById(activityID int64, promotionType, state int, operator string) (response *OpenPlatActivityQResponse, err error) { + params := map[string]interface{}{ + "activityId": activityID, + "type": promotionType, + "traceId": utils.GetUUID(), + "operator": operator, + "requestTime": time.Now().UnixNano() / 1000000, + } + if state > 0 { + params["state"] = state + } + result, err := a.AccessAPINoPage("orderdiscount/queryActivityInfoById", params, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "data", "10000")) + if err == nil { + err = utils.Map2StructByJson(result, &response, false) + } + return response, err +} + +// 根据到家活动ID查询单品级促销活动接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=196&apiid=a47520a9757f4b0dbac1e6d36fd1103d +func (a *API) QueryPromotionInfo(promotionInfoId int64) (promotionInfo *PromotionLspQueryInfoResult, err error) { + jdParams := map[string]interface{}{ + "promotionInfoId": promotionInfoId, + } + result, err := a.AccessAPINoPage("singlePromote/queryPromotionInfo", jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "data", "0")) + if err == nil { + err = utils.Map2StructByJson(result, &promotionInfo, false) + } + return promotionInfo, err +} diff --git a/platformapi/jdapi/promotion_audit_test.go b/platformapi/jdapi/promotion_audit_test.go new file mode 100644 index 00000000..b1b670f9 --- /dev/null +++ b/platformapi/jdapi/promotion_audit_test.go @@ -0,0 +1,23 @@ +package jdapi + +import ( + "testing" + + "git.rosy.net.cn/baseapi/utils" +) + +func TestOrderDiscountQueryActivityInfoById(t *testing.T) { + result, err := api.OrderDiscountQueryActivityInfoById(1297945, OrderDiscountActivityTypeManJian, 0, "test") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestQueryPromotionInfo(t *testing.T) { + result, err := api.QueryPromotionInfo(43430316) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/jdapi/promotion_order.go b/platformapi/jdapi/promotion_order.go index 01f55d0d..4491d12c 100644 --- a/platformapi/jdapi/promotion_order.go +++ b/platformapi/jdapi/promotion_order.go @@ -1,11 +1,11 @@ package jdapi import ( - "time" - "git.rosy.net.cn/baseapi/utils" ) +// 新版订单级促销 + const ( OrderDiscountActivityTypeManJian = 1 // 满减 OrderDiscountActivityTypeHuanGou = 1202 // 换购 @@ -32,7 +32,7 @@ type OrderDiscountRuleRequest struct { DiscountRate float32 `json:"discountRate,omitempty"` AddPrice int `json:"addPrice,omitempty"` // 分 GiftList []*OrderDiscountGift `json:"giftList,omitempty"` - LadderLimit int `json:"ladderLimit,omitempty"` + LadderLimit int `json:"ladderLimit"` } type OrderDiscountActivity struct { @@ -44,11 +44,11 @@ type OrderDiscountActivity struct { ParticipationMode int `json:"participationMode"` // 必须 OutStationNos []string `json:"outStationNos,omitempty"` StationNos []string `json:"stationNos,omitempty"` - OutSkuIds []string `json:"outSkuIds,omitempty"` - SkuIds []string `json:"skuIds,omitempty"` - LimitOrderTotalNumber int `json:"limitOrderTotalNumber"` // 必须 - LimitUserTotalNumber int `json:"limitUserTotalNumber,omitempty"` - Display string `json:"display"` // 必须 + OutSkuIDs []string `json:"outSkuIds,omitempty"` + SkuIDs []string `json:"skuIds,omitempty"` + LimitOrderTotalNumber int `json:"limitOrderTotalNumber"` // 必须, 活动限购单量(0为不限,不超过10万) + LimitUserTotalNumber int `json:"limitUserTotalNumber,omitempty"` // 用户限购单量(0为不限,不超过100,仅满减和每满减有效) + Display string `json:"display"` // 必须 RuleRequestList []*OrderDiscountRuleRequest `json:"ruleRequestList,omitempty"` } @@ -69,19 +69,45 @@ type ActivityOpQueryResultResponse struct { } type ActivityOpQueryInfoResponse struct { - ActivityID int64 `json:"activityID"` - OutActivityID string `json:"outActivityId"` - PromotionName string `json:"promotionName"` - BeginDate time.Time `json:"beginDate"` - EndDate time.Time `json:"endDate"` - Awords string `json:"awords"` - State int `json:"state"` - OrderLadder string `json:"orderLadder"` - StationList []int `json:"stationList"` - SkuList []int `json:"skuList"` - LadderList []int `json:"ladderList"` + ActivityID int64 `json:"activityID"` + OutActivityID string `json:"outActivityId"` + PromotionName string `json:"promotionName"` + BeginDate *utils.JavaDate `json:"beginDate"` + EndDate *utils.JavaDate `json:"endDate"` + Awords string `json:"awords"` + State int `json:"state"` + OrderLadder string `json:"orderLadder"` + StationList []struct { + OrgName string `json:"orgName"` + StationNo int64 `json:"stationNo"` + OutStationNo string `json:"outStationNo"` + StationName string `json:"stationName"` + } `json:"stationList"` + SkuList []struct { + OrgName string `json:"orgName"` + SkuID int64 `json:"skuId"` + OutSkuID string `json:"outSkuId"` + SkuName string `json:"skuName"` + } `json:"skuList"` + LadderList []struct { + BenefitMaxCount int `json:"benefitMaxCount"` + DiscountAmount int `json:"discountAmount"` + DiscountRate float32 `json:"discountRate"` + LowMoney int `json:"lowMoney"` + LowerLimitCount int `json:"lowerLimitCount"` + OpGiftSkuList []struct { + OrgName string `json:"orgName"` + SkuID int64 `json:"skuId"` + OutSkuID string `json:"outSkuId"` + SkuName string `json:"skuName"` + StockCount int `json:"stockCount"` + } `json:"opGiftSkuList"` + OrderLadder string `json:"orderLadder"` + } `json:"ladderList"` } +// 订单级促销活动提交接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=211&apiid=cf98af94d4124ca287af6cfe48f0f3aa func (a *API) OrderDiscountSubmitActivity(actInfo *OrderDiscountActivity) (activityID int64, err error) { result, err := a.AccessAPINoPage("orderdiscount/submitActivity", utils.Struct2FlatMap(actInfo), nil, nil, nil) if err == nil { @@ -90,60 +116,46 @@ func (a *API) OrderDiscountSubmitActivity(actInfo *OrderDiscountActivity) (activ return 0, err } +// 订单级促销活动查询活动提交处理结果接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=211&apiid=b04e5426948349a78db1c5c9585d8df7 func (a *API) OrderDiscountQuerySubmitActivityResult(activityID int64) (response *ActivityOpQueryResultResponse, err error) { result, err := a.AccessAPINoPage("orderdiscount/querySubmitActivityResult", map[string]interface{}{ "activityId": activityID, - "operator": "jxc4", + "operator": utils.APIPin, "traceId": utils.GetUUID(), - }, nil, nil, nil) + }, nil, nil, genNoPageResultParser("code", "detail", "result", "0")) if err == nil { - resultMap := result.(map[string]interface{}) - response = &ActivityOpQueryResultResponse{ - SubCode: utils.Interface2String(resultMap["subCode"]), - SubMsg: utils.Interface2String(resultMap["subMsg"]), - } - for _, v := range resultMap["resultList"].([]interface{}) { - vMap := v.(map[string]interface{}) - response.ResultList = append(response.ResultList, &ActivityOpResultInfo{ - ActivityID: utils.MustInterface2Int64(vMap["activityId"]), - PromName: utils.Interface2String(vMap["promName"]), - StationNo: utils.MustInterface2Int64(vMap["stationNo"]), - SkuID: utils.MustInterface2Int64(vMap["skuId"]), - OutActivityID: utils.Interface2String(vMap["outActivityId"]), - OutSkuID: utils.Interface2String(vMap["outSkuId"]), - OutStationNo: utils.Interface2String(vMap["outStationNo"]), - }) - } + err = utils.Map2StructByJson(result.([]interface{})[0], &response, true) // todo,只取第一个? } return response, err } -// func (a *API) OrderDiscountQueryActivityInfo(activityID int64) (response *OrderDiscountResultResponse, err error) { -// result, err := a.AccessAPINoPage("orderdiscount/queryActivityInfo", map[string]interface{}{ -// "activityId": activityID, -// "sourceFrom": 3, -// "operator": "", -// "traceId": "", -// "version": "", -// }, nil, nil, nil) -// if err == nil { -// resultMap := result.(map[string]interface{}) -// response = &OrderDiscountResultResponse{ -// SubCode: utils.Interface2String(resultMap["subCode"]), -// SubMsg: utils.Interface2String(resultMap["subMsg"]), -// } -// for _, v := range resultMap["resultList"].([]interface{}) { -// vMap := v.(map[string]interface{}) -// response.ResultList = append(response.ResultList, &OrderDiscountResultInfo{ -// ActivityID: utils.MustInterface2Int64(vMap["activityId"]), -// PromName: utils.Interface2String(vMap["promName"]), -// StationNo: utils.MustInterface2Int64(vMap["stationNo"]), -// SkuID: utils.MustInterface2Int64(vMap["skuId"]), -// OutActivityId: utils.Interface2String(vMap["outActivityId"]), -// OutSkuId: utils.Interface2String(vMap["outSkuId"]), -// OutStationNo: utils.Interface2String(vMap["outStationNo"]), -// }) -// } -// } -// return response, err -// } +// 订单级促销活动查询促销详情接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=211&apiid=116d7dfe0b4e4122be300ce26b4e5381 +func (a *API) OrderDiscountQueryActivityInfo(activityID int64) (response *ActivityOpQueryInfoResponse, err error) { + result, err := a.AccessAPINoPage("orderdiscount/queryActivityInfo", map[string]interface{}{ + "activityId": activityID, + "sourceFrom": 3, + "operator": utils.APIPin, + "traceId": "", + "version": "", + }, nil, nil, genNoPageResultParser("code", "detail", "result", "0")) + if err == nil { + err = utils.Map2StructByJson(result, &response, true) + } + return response, err +} + +// 订单级促销活动取消接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=211&apiid=42509fdfeec14105a516b07b774a3055 +func (a *API) OrderDiscountCancelActivity(activityID int64, operator, traceID string) (err error) { + if traceID == "" { + traceID = utils.GetUUID() + } + _, err = a.AccessAPINoPage("orderdiscount/cancelActivity", map[string]interface{}{ + "activityId": activityID, + "operator": utils.GetAPIOperator(operator), + "traceId": traceID, + }, nil, nil, nil) + return err +} diff --git a/platformapi/jdapi/promotion_order_test.go b/platformapi/jdapi/promotion_order_test.go index e9263ba8..079bf751 100644 --- a/platformapi/jdapi/promotion_order_test.go +++ b/platformapi/jdapi/promotion_order_test.go @@ -9,21 +9,22 @@ import ( func TestOrderDiscountSubmitActivity(t *testing.T) { actInfo := &OrderDiscountActivity{ - OutActivityID: utils.GetUUID(), - PromName: "测试0404", - PromType: OrderDiscountActivityTypeManJian, - BeginTime: utils.Time2Str(time.Now().Add(1 * time.Hour)), - EndTime: utils.Time2Str(time.Now().Add(48 * time.Hour)), - ParticipationMode: ParticipationModeAllSku, - Display: "hello", - OutStationNos: []string{"25"}, - OutSkuIds: []string{"5199"}, + OutActivityID: utils.GetUUID(), + PromName: "测试0404", + PromType: OrderDiscountActivityTypeManJian, + BeginTime: utils.Time2Str(time.Now().Add(1 * time.Hour)), + EndTime: utils.Time2Str(time.Now().Add(48 * time.Hour)), + ParticipationMode: ParticipationModePartSku, + Display: "hello", + OutStationNos: []string{"2"}, + // OutSkuIDs: []string{"5199"}, + SkuIDs: []string{"2023747677"}, LimitUserTotalNumber: 1, RuleRequestList: []*OrderDiscountRuleRequest{ &OrderDiscountRuleRequest{ - LowerLimitAmount: 100, - DiscountAmount: 1000, - DiscountRate: 0.5, + LowerLimitAmount: 1000, + DiscountAmount: 100, + DiscountRate: 8.5, }, }, } @@ -35,9 +36,24 @@ func TestOrderDiscountSubmitActivity(t *testing.T) { } func TestOrderDiscountQuerySubmitActivityResult(t *testing.T) { - result, err := api.OrderDiscountQuerySubmitActivityResult(3) + result, err := api.OrderDiscountQuerySubmitActivityResult(10000051) if err != nil { t.Fatal(err) } t.Log(result) } + +func TestOrderDiscountQueryActivityInfo(t *testing.T) { + result, err := api.OrderDiscountQueryActivityInfo(10000051) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestOrderDiscountCancelActivity(t *testing.T) { + err := api.OrderDiscountCancelActivity(10000049, "test", "") + if err != nil { + t.Fatal(err) + } +} diff --git a/platformapi/jdapi/promotion_sku.go b/platformapi/jdapi/promotion_sku.go index 11951226..1af0e9a1 100644 --- a/platformapi/jdapi/promotion_sku.go +++ b/platformapi/jdapi/promotion_sku.go @@ -29,30 +29,24 @@ const ( PromotionStateEnded = 9 ) -type PromotionSkuResult struct { - LimitDaily int - LimitDevice int - LimitPin int - PlatformRatio int - PromotionPrice int - SkuId int64 - StationNo int64 - StoreRatio int -} +type PromotionSku struct { + SkuID int64 `json:"skuId,omitempty"` + OutSkuID string `json:"outSkuId,omitempty"` + StationNo int64 `json:"stationNo,omitempty"` + OutStationNo string `json:"outStationNo,omitempty"` -type PromotionInfo struct { - BeginTime time.Time - EndTime time.Time - PromotionInfoId int64 - PromotionState int - PromotionType int - Source string - SkuResultList []*PromotionSkuResult + PromotionPrice int64 `json:"promotionPrice,omitempty"` + LimitSkuCount int `json:"limitSkuCount,omitempty"` + FailReason string `json:"failReason,omitempty"` } func getPromotionCmd(inCmd string, promotionType int) (outCmd string) { if promotionType == PromotionTypeDirectDown { - outCmd = "singlePromote/" + inCmd + if inCmd == "adjustPromotionSku" || inCmd == "adjustPromotionTime" { + outCmd = "promotesku/" + inCmd + } else { + outCmd = "singlePromote/" + inCmd + } } else if promotionType == PromotionTypeLimitedTime { outCmd = "limitTime/" + inCmd } else { @@ -63,86 +57,115 @@ func getPromotionCmd(inCmd string, promotionType int) (outCmd string) { // 单品直降添加主活动信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=178&apiid=ee8685c9be9b4aa5bdc41468c5ebc33b -func (a *API) CreatePromotionInfosSingle(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) { - return a.createPromotionInfos(PromotionTypeDirectDown, name, beginDate, endDate, outInfoId, advertising) +func (a *API) CreatePromotionInfosSingle(name string, beginDate, endDate time.Time, outInfoId, advertising, traceId string) (infoId int64, err error) { + return a.createPromotionInfos(PromotionTypeDirectDown, name, beginDate, endDate, outInfoId, advertising, traceId) +} + +// 单品直降添加活动规则信息接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=178&apiid=900a9577cb304bbbb59abb7c2c33854d +func (a *API) CreatePromotionRulesSingle(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int, traceId string) (err error) { + return a.createPromotionRules(PromotionTypeDirectDown, infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, traceId) } // 单品直降添加活动商品信息接口 // 最多200条 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=178&apiid=0ad0715e0aaa42489cbeac36398e916d -func (a *API) CreatePromotionSkuSingle(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { - return a.createPromotionSku(PromotionTypeDirectDown, infoId, outInfoId, skus) +func (a *API) CreatePromotionSkuSingle(infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + return a.createPromotionSku(PromotionTypeDirectDown, infoId, outInfoId, skus, traceId) } // 单品直降活动提交保存接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=178&apiid=b84e14eb341e470db8ed9b60f78edb16 -func (a *API) ConfirmPromotionSingle(infoId int64, outInfoId string) (err error) { - return a.confirmPromotion(PromotionTypeDirectDown, infoId, outInfoId) +func (a *API) ConfirmPromotionSingle(infoId int64, outInfoId, traceId string) (err error) { + return a.confirmPromotion(PromotionTypeDirectDown, infoId, outInfoId, traceId) +} + +// 单品直降活动部分商品取消接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=178&apiid=cfd14470b3fe4625bd2405b20bf8cec4 +func (a *API) CancelPromotionSkuSingle(infoId int64, outInfoId string, cancelSkus []*PromotionSku, traceId string) (err error) { + return a.cancelPromotionSku(PromotionTypeDirectDown, infoId, outInfoId, cancelSkus, traceId) } // 单品直降整个活动取消接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=178&apiid=3d02c2acad714bea86d20d498a1aa074 -func (a *API) CancelPromotionSingle(infoId int64, outInfoId string) (err error) { - return a.cancelPromotion(PromotionTypeDirectDown, infoId, outInfoId) +func (a *API) CancelPromotionSingle(infoId int64, outInfoId, traceId string) (err error) { + return a.cancelPromotion(PromotionTypeDirectDown, infoId, outInfoId, traceId) } +// 单品实时促销活动结束时间调整接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=178&apiid=0abab0c4b81d45e5bc555cc7dfbeb1ad +func (a *API) AdjustPromotionTimeSingle(infoId int64, outInfoId string, endDate time.Time, traceId string) (err error) { + return a.adjustPromotionTime(PromotionTypeDirectDown, infoId, outInfoId, endDate, traceId) +} + +// 单品实时促销商品促销数量调整接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=178&apiid=82964e5e0f9c448db072a54ed20e00c4 +func (a *API) AdjustPromotionSkuSingle(infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + return a.adjustPromotionSku(PromotionTypeDirectDown, infoId, outInfoId, skus, traceId) +} + +// 以下为限时抢 + // 限时抢添加活动主信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=184&apiid=7d8b7ff86c9e457bb8a46963cb575769 -func (a *API) CreatePromotionInfosLimitTime(name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) { - return a.createPromotionInfos(PromotionTypeLimitedTime, name, beginDate, endDate, outInfoId, advertising) +func (a *API) CreatePromotionInfosLimitTime(name string, beginDate, endDate time.Time, outInfoId, advertising, traceId string) (infoId int64, err error) { + return a.createPromotionInfos(PromotionTypeLimitedTime, name, beginDate, endDate, outInfoId, advertising, traceId) } -// 限时抢添加活动规则信息接口(直降也是调用此接口) +// 限时抢添加活动规则信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=184&apiid=e80674791d3542f0a87502753c0d0592 -func (a *API) CreatePromotionRules(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int) (err error) { - jdParams := map[string]interface{}{ - "limitDevice": limitDevice, - "limitPin": limitPin, - "limitCount": limitCount, - "limitDaily": limitDaily, - "timeStamp": utils.GetCurTimeStr(), - } - if infoId != 0 { - jdParams[KeyInfoId] = infoId - } else { - jdParams[KeyOutInfoId] = outInfoId - } - _, err = a.AccessAPINoPage("limitTime/createPromotionRules", jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) - return err +func (a *API) CreatePromotionRulesLimitTime(infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int, traceId string) (err error) { + return a.createPromotionRules(PromotionTypeLimitedTime, infoId, outInfoId, limitDevice, limitPin, limitCount, limitDaily, traceId) } // 限时抢添加活动商品信息接口 // 最多200条 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=184&apiid=65fecef4883c40c6b23bbdb6123f5d80 -func (a *API) CreatePromotionSkuLimitTime(infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { - return a.createPromotionSku(PromotionTypeLimitedTime, infoId, outInfoId, skus) +func (a *API) CreatePromotionSkuLimitTime(infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + return a.createPromotionSku(PromotionTypeLimitedTime, infoId, outInfoId, skus, traceId) } // 限时抢活动提交保存接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=184&apiid=3991063e2f0d435094e9fe44754f3490 -func (a *API) ConfirmPromotionLimitTime(infoId int64, outInfoId string) (err error) { - return a.confirmPromotion(PromotionTypeLimitedTime, infoId, outInfoId) +func (a *API) ConfirmPromotionLimitTime(infoId int64, outInfoId, traceId string) (err error) { + return a.confirmPromotion(PromotionTypeLimitedTime, infoId, outInfoId, traceId) +} + +// 限时抢活动取消部分商品接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=184&apiid=01bd37df882749af91be4b040873ccc6 +func (a *API) CancelPromotionSkuLimitTime(infoId int64, outInfoId string, cancelSkus []*PromotionSku, traceId string) (err error) { + return a.cancelPromotionSku(PromotionTypeLimitedTime, infoId, outInfoId, cancelSkus, traceId) } // 限时抢整个活动取消接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=184&apiid=97b6678d30624f73bc13cb68987e6a6d -func (a *API) CancelPromotionLimitTime(infoId int64, outInfoId string) (err error) { - return a.cancelPromotion(PromotionTypeLimitedTime, infoId, outInfoId) +func (a *API) CancelPromotionLimitTime(infoId int64, outInfoId, traceId string) (err error) { + return a.cancelPromotion(PromotionTypeLimitedTime, infoId, outInfoId, traceId) } -func (a *API) createPromotionInfos(promotionType int, name string, beginDate, endDate time.Time, outInfoId, advertising string) (infoId int64, err error) { +// 限时抢活动结束时间调整接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=184&apiid=f91035295cd54a9bbd2db9dfc800484c +func (a *API) AdjustPromotionTimeLimitTime(infoId int64, outInfoId string, endDate time.Time, traceId string) (err error) { + return a.adjustPromotionTime(PromotionTypeLimitedTime, infoId, outInfoId, endDate, traceId) +} + +// 限时抢商品促销数量调整接口 +// https://openo2o.jddj.com/staticnew/widgets/resources.html?groupid=184&apiid=aa878a000dfb4a248634ee755af1f1d3 +func (a *API) AdjustPromotionSkuLimitTime(infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + return a.adjustPromotionSku(PromotionTypeLimitedTime, infoId, outInfoId, skus, traceId) +} + +func (a *API) createPromotionInfos(promotionType int, name string, beginDate, endDate time.Time, outInfoId, advertising, traceId string) (infoId int64, err error) { if outInfoId == "" { outInfoId = fmt.Sprintf("%X", md5.Sum([]byte(name))) } - jdParams := map[string]interface{}{ - "promotionName": name, - "beginDate": utils.Time2Str(beginDate), - "endDate": utils.Time2Str(endDate), - "advertising": advertising, - "promotionType": promotionType, - "timeStamp": utils.GetCurTimeStr(), - KeyOutInfoId: outInfoId, - } + jdParams := getCommonSkuPromotionParams(0, outInfoId, traceId) + jdParams["promotionName"] = name + jdParams["beginDate"] = utils.Time2Str(beginDate) + jdParams["endDate"] = utils.Time2Str(endDate) + jdParams["advertising"] = advertising + jdParams["promotionType"] = promotionType + jdParams["advertising"] = advertising result, err := a.AccessAPINoPage(getPromotionCmd("createPromotionInfos", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "data", "0")) if err == nil { return utils.MustInterface2Int64(result), nil @@ -150,35 +173,64 @@ func (a *API) createPromotionInfos(promotionType int, name string, beginDate, en return 0, err } -// todo skusResult 返回值没有意义,为了兼容暂时保留 -func (a *API) createPromotionSku(promotionType int, infoId int64, outInfoId string, skus []map[string]interface{}) (skusResult []map[string]interface{}, err error) { - jdParams := map[string]interface{}{ - "skus": skus, - "timeStamp": utils.GetCurTimeStr(), - } - if infoId != 0 { - jdParams[KeyInfoId] = infoId - } else { - jdParams[KeyOutInfoId] = outInfoId - } - _, err = a.AccessAPINoPage(getPromotionCmd("createPromotionSku", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "data", "", "0")) - return nil, err +func (a *API) createPromotionRules(promotionType int, infoId int64, outInfoId string, limitDevice, limitPin, limitCount, limitDaily int, traceId string) (err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + jdParams["limitDevice"] = limitDevice + jdParams["limitPin"] = limitPin + jdParams["limitCount"] = limitCount + jdParams["limitDaily"] = limitDaily + _, err = a.AccessAPINoPage(getPromotionCmd("createPromotionRules", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) + return err } -func (a *API) confirmPromotion(promotionType int, infoId int64, outInfoId string) (err error) { - jdParams := map[string]interface{}{ - "timeStamp": utils.GetCurTimeStr(), - } - if infoId != 0 { - jdParams[KeyInfoId] = infoId - } else { - jdParams[KeyOutInfoId] = outInfoId +// todo skusResult 返回值没有意义,为了兼容暂时保留 +func (a *API) createPromotionSku(promotionType int, infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + jdParams["skus"] = skus + // todo 当前是在出错时,把data中的数据当成错误信息处理的 + result, err := a.AccessAPINoPage(getPromotionCmd("createPromotionSku", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "data", "", "0")) + if err == nil && result != nil { + err = utils.Map2StructByJson(result, &skusResult, false) } + return skusResult, err +} + +func (a *API) confirmPromotion(promotionType int, infoId int64, outInfoId, traceId string) (err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) _, err = a.AccessAPINoPage(getPromotionCmd("confirmPromotion", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) return err } -func (a *API) cancelPromotion(promotionType int, infoId int64, outInfoId string) (err error) { +func (a *API) cancelPromotionSku(promotionType int, infoId int64, outInfoId string, cancelSkus []*PromotionSku, traceId string) (err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + jdParams["cancelSkus"] = cancelSkus + _, err = a.AccessAPINoPage(getPromotionCmd("cancelPromotionSku", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) + return err +} + +func (a *API) cancelPromotion(promotionType int, infoId int64, outInfoId, traceId string) (err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + _, err = a.AccessAPINoPage(getPromotionCmd("cancelPromotion", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) + return err +} + +func (a *API) adjustPromotionTime(promotionType int, infoId int64, outInfoId string, endDate time.Time, traceId string) (err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + _, err = a.AccessAPINoPage(getPromotionCmd("adjustPromotionTime", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) + return err +} + +func (a *API) adjustPromotionSku(promotionType int, infoId int64, outInfoId string, skus []*PromotionSku, traceId string) (skusResult []*PromotionSku, err error) { + jdParams := getCommonSkuPromotionParams(infoId, outInfoId, traceId) + jdParams["skus"] = skus + result, err := a.AccessAPINoPage(getPromotionCmd("adjustPromotionSku", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "data", "", "0")) + if err == nil && result != nil { + err = utils.Map2StructByJson(result, &skusResult, false) + } + return skusResult, err +} + +func getCommonSkuPromotionParams(infoId int64, outInfoId, traceId string) map[string]interface{} { jdParams := map[string]interface{}{ "timeStamp": utils.GetCurTimeStr(), } @@ -187,42 +239,8 @@ func (a *API) cancelPromotion(promotionType int, infoId int64, outInfoId string) } else { jdParams[KeyOutInfoId] = outInfoId } - _, err = a.AccessAPINoPage(getPromotionCmd("cancelPromotion", promotionType), jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "", "0")) - return err -} - -func (a *API) QueryPromotionInfo(promotionInfoId int64) (promotionInfo *PromotionInfo, err error) { - jdParams := map[string]interface{}{ - "promotionInfoId": promotionInfoId, + if traceId != "" { + jdParams["traceId"] = traceId } - result, err := a.AccessAPINoPage("singlePromote/queryPromotionInfo", jdParams, nil, nil, genNoPageResultParser("errorCode", "errorInfos", "data", "0")) - if err == nil { - data := result.(map[string]interface{}) - // baseapi.SugarLogger.Debug(utils.Format4Output(data, false)) - promotionInfo = &PromotionInfo{ - BeginTime: utils.Timestamp2Time(utils.MustInterface2Int64(data["beginTime"].(map[string]interface{})["time"]) / 1000), - EndTime: utils.Timestamp2Time(utils.MustInterface2Int64(data["endTime"].(map[string]interface{})["time"]) / 1000), - PromotionInfoId: utils.MustInterface2Int64(data["promotionInfoId"]), - PromotionState: int(utils.MustInterface2Int64(data["promotionState"])), - PromotionType: int(utils.MustInterface2Int64(data["promotionType"])), - Source: utils.Interface2String(data["source"]), - } - skuResultList := data["skuResultList"].([]interface{}) - promotionInfo.SkuResultList = make([]*PromotionSkuResult, len(skuResultList)) - for k, v := range skuResultList { - skuResult := v.(map[string]interface{}) - promotionInfo.SkuResultList[k] = &PromotionSkuResult{ - LimitDaily: int(utils.MustInterface2Int64(skuResult["limitDaily"])), - LimitDevice: int(utils.MustInterface2Int64(skuResult["limitDevice"])), - LimitPin: int(utils.MustInterface2Int64(skuResult["limitPin"])), - PlatformRatio: int(utils.MustInterface2Int64(skuResult["platformRatio"])), - PromotionPrice: int(utils.MustInterface2Int64(skuResult["promotionPrice"])), - SkuId: utils.MustInterface2Int64(skuResult["skuId"]), - StationNo: utils.MustInterface2Int64(skuResult["stationNo"]), - StoreRatio: int(utils.MustInterface2Int64(skuResult["storeRatio"])), - } - } - return promotionInfo, nil - } - return nil, err + return jdParams } diff --git a/platformapi/jdapi/promotion_sku_test.go b/platformapi/jdapi/promotion_sku_test.go index a71be83a..8a8a4975 100644 --- a/platformapi/jdapi/promotion_sku_test.go +++ b/platformapi/jdapi/promotion_sku_test.go @@ -6,67 +6,57 @@ import ( ) func TestCreatePromotionSingle(t *testing.T) { - infoId, err := api.CreatePromotionInfosSingle("测试1", time.Now(), time.Now().Add(24*time.Hour), "", "") + infoId, err := api.CreatePromotionInfosSingle("测试1", time.Now(), time.Now().Add(24*time.Hour), "", "", "") if err != nil { t.Fatal(err) } t.Log(infoId) - err = api.CreatePromotionRules(infoId, "", 1, 1, 1, 1) + err = api.CreatePromotionRulesSingle(infoId, "", 1, 1, 1, 1, "") if err != nil { t.Fatal(err) } - skuInfos, err := api.CreatePromotionSkuSingle(infoId, "", []map[string]interface{}{ - { - KeyOutSkuId: "2216", - KeyStationNo: 11682042, - KeyPromotionPrice: 500, - KeyLimitSkuCount: 2, + skuInfos, err := api.CreatePromotionSkuSingle(infoId, "", []*PromotionSku{ + &PromotionSku{ + OutSkuID: "2216", + StationNo: 11682042, + PromotionPrice: 500, + LimitSkuCount: 2, }, - }) + }, "") if err != nil { t.Fatal(err) } t.Log(skuInfos) - err = api.ConfirmPromotionSingle(infoId, "") + err = api.ConfirmPromotionSingle(infoId, "", "") if err != nil { t.Fatal(err) } } func TestCreatePromotionLimitTime(t *testing.T) { - infoId, err := api.CreatePromotionInfosLimitTime("测试1", time.Now(), time.Now().Add(24*time.Hour), "", "") + infoId, err := api.CreatePromotionInfosLimitTime("测试1", time.Now(), time.Now().Add(24*time.Hour), "", "", "") if err != nil { t.Fatal(err) } t.Log(infoId) - err = api.CreatePromotionRules(infoId, "", 1, 1, 5, 1) + err = api.CreatePromotionRulesLimitTime(infoId, "", 1, 1, 5, 1, "") if err != nil { t.Fatal(err) } - skuInfos, err := api.CreatePromotionSkuLimitTime(infoId, "", []map[string]interface{}{ - { - KeyOutSkuId: "2216", - KeyStationNo: 11682042, - KeyPromotionPrice: 300, - KeyLimitSkuCount: 2, + skuInfos, err := api.CreatePromotionSkuLimitTime(infoId, "", []*PromotionSku{ + &PromotionSku{ + OutSkuID: "2216", + StationNo: 11682042, + PromotionPrice: 300, + LimitSkuCount: 2, }, - }) + }, "") if err != nil { t.Fatal(err) } t.Log(skuInfos) - err = api.ConfirmPromotionLimitTime(infoId, "") + err = api.ConfirmPromotionLimitTime(infoId, "", "") if err != nil { t.Fatal(err) } } - -func TestQueryPromotionInfo(t *testing.T) { - result, err := api.QueryPromotionInfo(14885272) - if err != nil { - t.Fatal(err) - } - if len(result.SkuResultList) == 0 { - t.Fatal("should have SkuResultList") - } -} diff --git a/platformapi/jdapi/sku.go b/platformapi/jdapi/sku.go index 22214dab..9c5239db 100644 --- a/platformapi/jdapi/sku.go +++ b/platformapi/jdapi/sku.go @@ -80,7 +80,7 @@ const ( ) const ( - MaxSkuNameLen = 45 // skuname最长字符数 + MaxSkuNameCharCount = 45 // skuname最长字符数 SaleAttrIDBase = 1001 SaleAttrValueIDBase = 10 ) @@ -399,13 +399,7 @@ func (a *API) QueryKeyWordDicInfo(pageNo, pageSize int, keyValue string) (values // 商家商品状态同步接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=180&apiid=5e29d6c9317847e58b8cbcc70702fd52 func (a *API) SyncProduct(storeId, skuId string) (retVal bool, err error) { - result, err := a.AccessAPINoPage("search/syncProduct", utils.Params2Map(KeyStoreId, storeId, KeySkuId, skuId), nil, nil, func(data map[string]interface{}) (interface{}, error) { - status := utils.MustInterface2Int64(data["status"]) - if status == 200 { - return data["synchronized"].(bool), nil - } - return nil, utils.NewErrorIntCode(data["message"].(string), int(status)) - }) + result, err := a.AccessAPINoPage("search/syncProduct", utils.Params2Map(KeyStoreId, storeId, KeySkuId, skuId), nil, nil, genNoPageResultParser("status", "message", "synchronized", "200")) if err == nil { return result.(bool), nil } diff --git a/platformapi/jdapi/sku_test.go b/platformapi/jdapi/sku_test.go index 797744f9..478daed2 100644 --- a/platformapi/jdapi/sku_test.go +++ b/platformapi/jdapi/sku_test.go @@ -58,12 +58,12 @@ func TestBatchUpdateOutSkuId(t *testing.T) { func TestQuerySkuInfos(t *testing.T) { pageSize := 20 - result, totalCount, err := api.QuerySkuInfos("", 0, 0, pageSize, true) + result, totalCount, err := api.QuerySkuInfos("一个高级商品", 0, 0, pageSize, true) if err != nil { t.Fatal(err) } if len(result) != pageSize || totalCount == 0 { - baseapi.SugarLogger.Debug(result) + baseapi.SugarLogger.Debug(utils.Format4Output(result, false)) t.Fatalf("QuerySkuInfos result size is not same as requested:%d", pageSize) } } @@ -95,32 +95,32 @@ func TestQueryKeyWordDicInfo(t *testing.T) { } func TestSyncProduct(t *testing.T) { - result, err := api.SyncProduct("11732425", "2015717812") + result, err := api.SyncProduct(mustExistStoreID, "2022250244") if err != nil { t.Fatal(err) } baseapi.SugarLogger.Debug(result) - result, err = api.SyncProduct("wrongstoreid", "2015717812") + result, err = api.SyncProduct("wrongstoreid", "2022250244") if err == nil { t.Fatal("SyncProduct should return error") } - result, err = api.SyncProduct("11732425", "wrongskuid") + result, err = api.SyncProduct(mustExistStoreID, "wrongskuid") if err == nil { t.Fatal("SyncProduct should return error") } } func TestGetProductStatust(t *testing.T) { - result, err := api.GetProductStatus("11732425", "2015717812") + result, err := api.GetProductStatus(mustExistStoreID, "2022250244") if err != nil || result == nil { t.Fatal(err) } // baseapi.SugarLogger.Debug(result) - result, err = api.GetProductStatus("wrongstoreid", "2015717812") + result, err = api.GetProductStatus("wrongstoreid", "2022250244") if err == nil { t.Fatal("GetProductStatus should return error") } - result, err = api.GetProductStatus("11732425", "wrongskuid") + result, err = api.GetProductStatus(mustExistStoreID, "wrongskuid") if err == nil { t.Fatal("GetProductStatus should return error") } diff --git a/platformapi/jdapi/store.go b/platformapi/jdapi/store.go index 25c4638b..71cb7629 100644 --- a/platformapi/jdapi/store.go +++ b/platformapi/jdapi/store.go @@ -35,6 +35,23 @@ type CreateShopResult struct { StationNo string `json:"stationNo"` } +type CityInfo struct { + AreaCode int `json:"areaCode"` + AreaLevel int `json:"areaLevel"` + AreaName string `json:"areaName"` + ParentAreaID int `json:"parentAreaId"` + TencentAddressCode int `json:"tencentAddressCode"` + Yn int `json:"yn"` +} + +func (a *API) GetAllCities() (cities []*CityInfo, err error) { + result, err := a.AccessAPINoPage("address/allcities", nil, nil, nil, genNoPageResultParser("code", "msg", "result", "0")) + if err == nil { + err = utils.Map2StructByJson(result, &cities, false) + } + return cities, err +} + // 获取门店编码列表接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=194&apiid=138426aa19b54c48ae8464af1ca3b681 func (a *API) GetStationsByVenderId() ([]string, error) { @@ -125,7 +142,7 @@ func (a *API) OrgReplyComment(orderID int64, storeID, content, replayPin string) "orderId": orderID, "storeId": storeID, "content": content, - "replyPin": replayPin, + "replyPin": utils.GetAPIOperator(replayPin), } _, err := a.AccessAPINoPage("commentOutApi/orgReplyComment", jdParams, nil, nil, genNoPageResultParser("code", "msg", "result", "200")) return err diff --git a/platformapi/jdapi/store_page.go b/platformapi/jdapi/store_page.go index 6865b3e4..bdf4c388 100644 --- a/platformapi/jdapi/store_page.go +++ b/platformapi/jdapi/store_page.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/platformapi" @@ -11,8 +12,9 @@ import ( ) const ( - accessStorePageCookieName = "shop.o2o.jd.com1" - getStoreURL = "https://daojia.jd.com" + getStoreURL = "https://daojia.jd.com" + accessStorePageCookieName = "shop.o2o.jd.com1" + accessStorePageCookieName2 = "lsp-store1.jddj.com" ) type SkuPageImg struct { @@ -21,6 +23,48 @@ type SkuPageImg struct { Small string `json:"small"` } +type CorporationInfo struct { + Scope string `json:"scope"` + OperName string `json:"oper_name"` + Status string `json:"status"` + BelongOrg string `json:"belong_org"` + CreditNo string `json:"credit_no"` + RegNo string `json:"reg_no"` + ID string `json:"id"` + OrgNo string `json:"org_no"` + EconKind string `json:"econ_kind"` + EndDate string `json:"end_date"` + TermEnd string `json:"term_end"` + NeedID bool `json:"needID"` + Address string `json:"address"` + Partners []struct { + IdentifyType string `json:"identify_type"` + ShouldCapiItems []interface{} `json:"should_capi_items"` + StockType string `json:"stock_type"` + IdentifyNo string `json:"identify_no"` + RealCapiItems []interface{} `json:"real_capi_items"` + Name string `json:"name"` + } `json:"partners"` + Name string `json:"name"` + Province string `json:"province"` + TermStart string `json:"term_start"` + AbnormalItems []interface{} `json:"abnormal_items"` + CheckDate string `json:"check_date"` + RegistCapi string `json:"regist_capi"` + StartDate string `json:"start_date"` + Changerecords []struct { + BeforeContent string `json:"before_content"` + ChangeDate string `json:"change_date"` + ChangeItem string `json:"change_item"` + AfterContent string `json:"after_content"` + } `json:"changerecords"` + Branches []interface{} `json:"branches"` + Employees []struct { + JobTitle string `json:"job_title"` + Name string `json:"name"` + } `json:"employees"` +} + func (a *API) SetStoreCookie(storeCookie string) { a.locker.Lock() defer a.locker.Unlock() @@ -33,14 +77,21 @@ func (a *API) GetStoreCookie() string { return a.storeCookie } -func (a *API) AccessStorePage(fullURL string) (retVal map[string]interface{}, err error) { +func (a *API) AccessStorePage(fullURL string, formData map[string]interface{}) (retVal map[string]interface{}, err error) { storeCookie := a.GetStoreCookie() if storeCookie == "" { return nil, fmt.Errorf("需要设置Store Cookie才能使用此方法") } err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { - request, _ := http.NewRequest(http.MethodGet, fullURL, nil) + var request *http.Request + if formData == nil { + request, _ = http.NewRequest(http.MethodGet, fullURL, nil) + } else { + request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(formData).Encode())) + request.Header.Set("charset", "UTF-8") + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } if err != nil { return nil } @@ -48,10 +99,14 @@ func (a *API) AccessStorePage(fullURL string) (retVal map[string]interface{}, er Name: accessStorePageCookieName, Value: storeCookie, }) + request.AddCookie(&http.Cookie{ + Name: accessStorePageCookieName2, + Value: storeCookie, + }) return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { retVal = jsonResult1 code := jsonResult1["code"].(string) if code == ResponseCodeSuccess { @@ -84,7 +139,7 @@ func (a *API) GetStoreOrderInfo(orderId, stationNo string) (storeOrderInfo map[s if stationNo != "" { urlStr += "&stationNo=" + stationNo } - retVal, err := a.AccessStorePage(urlStr) + retVal, err := a.AccessStorePage(urlStr, nil) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err == nil { newOrderinfoMains := retVal["newOrderinfoMains"].(map[string]interface{}) @@ -102,7 +157,7 @@ func (a *API) GetStoreOrderInfoList(fromTime, toTime string) (storeOrderList []m pageNo := 1 urlTemplate := "http://store.jddj.com/order/newManager/tabQuery/all?o2oOrderType=10000&pageNo=%d&pageSize=%d&orderBy=&desc=true&startTimeQuery=%s&endTimeQuery=%s&stationNo=" for { - retVal, err := a.AccessStorePage(fmt.Sprintf(urlTemplate, pageNo, pageSize, url.QueryEscape(fromTime), url.QueryEscape(toTime))) + retVal, err := a.AccessStorePage(fmt.Sprintf(urlTemplate, pageNo, pageSize, url.QueryEscape(fromTime), url.QueryEscape(toTime)), nil) // baseapi.SugarLogger.Debug(utils.Format4Output(retVal, false)) if err == nil { newOrderinfoMains := retVal["newOrderinfoMains"].(map[string]interface{}) @@ -124,10 +179,7 @@ func (a *API) GetSkuPageInfo(skuId int64) (skuPageInfo map[string]interface{}, e "skuId": utils.Int64ToStr(skuId), "storeId": "0", } - skuPageInfo, err = a.AccessStorePage(fmt.Sprintf("https://daojia.jd.com/client?platCode=H5&functionId=product/detailV6_0&body=%s", utils.Format4Output(skuIDMap, true))) - if err == nil { - baseapi.SugarLogger.Debug(utils.Format4Output(skuPageInfo, false)) - } + skuPageInfo, err = a.AccessStorePage(fmt.Sprintf("https://daojia.jd.com/client?platCode=H5&functionId=product/detailV6_0&body=%s", utils.Format4Output(skuIDMap, true)), nil) return skuPageInfo, err } @@ -140,34 +192,22 @@ func (a *API) GetSkuPageImageInfo(skuId int64) (imgList []*SkuPageImg, err error } func (a *API) GetStoreInfo(storeId string) (storeInfo map[string]interface{}, err error) { - retVal, err := a.AccessStorePage2(fmt.Sprintf("client?functionId=store/storeDetailV220&body={\"storeId\":\"%s\"}&appVersion=6.1.0", storeId)) - return retVal, err -} - -func (a *API) AccessStorePage2(subURL string) (retVal map[string]interface{}, err error) { - err = platformapi.AccessPlatformAPIWithRetry(a.client, - func() *http.Request { - fullURL := utils.GenerateGetURL(getStoreURL, subURL, nil) - request, _ := http.NewRequest(http.MethodGet, fullURL, nil) - if err != nil { - return nil - } - return request - }, - a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { - code := jsonResult1["code"].(string) - if code == ResponseCodeSuccess && jsonResult1["result"] != nil { - retVal = jsonResult1["result"].(map[string]interface{}) - return platformapi.ErrLevelSuccess, nil - } - newErr := utils.NewErrorCode(jsonResult1["msg"].(string), code) - return platformapi.ErrLevelCodeIsNotOK, newErr - }) + retVal, err := a.AccessStorePage(fmt.Sprintf("https://daojia.jd.com/client?functionId=store/storeDetailV220&body={\"storeId\":\"%s\"}&appVersion=6.1.0", storeId), nil) return retVal, err } func (a *API) GetStoreList(lng string, lat string) (retVal map[string]interface{}, err error) { - retVal, err = a.AccessStorePage2(fmt.Sprintf("client?platCode=h5&appVersion=6.5.0&functionId=zone/recommendStoreList&body={\"channelId\":\"3997\",\"currentPage\":1,\"pageSize\":999,\"coordType\":\"2\",\"platform\":\"1\"}&signKey=b63f63fa9e27123b84a0c80ef5cd210d&lng=%s&lat=%s", lng, lat)) + retVal, err = a.AccessStorePage(fmt.Sprintf("https://daojia.jd.com/client?platCode=h5&appVersion=6.5.0&functionId=zone/recommendStoreList&body={\"channelId\":\"3997\",\"currentPage\":1,\"pageSize\":999,\"coordType\":\"2\",\"platform\":\"1\"}&signKey=b63f63fa9e27123b84a0c80ef5cd210d&lng=%s&lat=%s", lng, lat), nil) return retVal, err } + +func (a *API) GetCorporationInfo(stationNo, qualifyNumber string) (corporatonInfo *CorporationInfo, err error) { + result, err := a.AccessStorePage("https://sta-store.jddj.com/store/requestQualify.o2o", map[string]interface{}{ + "stationNo": stationNo, + "qualifyNumber": qualifyNumber, + }) + if err == nil { + err = utils.Map2StructByJson(result, &corporatonInfo, false) + } + return corporatonInfo, err +} diff --git a/platformapi/jdapi/store_page_test.go b/platformapi/jdapi/store_page_test.go index 55c881b4..91b820cf 100644 --- a/platformapi/jdapi/store_page_test.go +++ b/platformapi/jdapi/store_page_test.go @@ -54,3 +54,27 @@ func TestGetSkuPageImageInfo(t *testing.T) { } baseapi.SugarLogger.Debug(utils.Format4Output(imgList, false)) } + +func TestGetCorporationInfo(t *testing.T) { + imgList, err := api.GetCorporationInfo("", "915101003431062533") + if err != nil { + t.Fatal(err) + } + baseapi.SugarLogger.Debug(utils.Format4Output(imgList, false)) +} + +func TestGetStoreList(t *testing.T) { + result, err := api.GetStoreList("104.057218", "30.6949") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestGetStoreInfo(t *testing.T) { + result, err := api.GetStoreInfo("11750116") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/jdapi/store_sku.go b/platformapi/jdapi/store_sku.go index af6342ea..98a269ca 100644 --- a/platformapi/jdapi/store_sku.go +++ b/platformapi/jdapi/store_sku.go @@ -41,6 +41,38 @@ type SkuIdEntity struct { OutSkuId string `json:"outSkuId"` } +type StorePriceInfo struct { + MarketPrice int64 `json:"marketPrice"` + Pin string `json:"pin"` + Price int64 `json:"price"` + PromoteVipPrice int64 `json:"promoteVipPrice"` + SkuID int64 `json:"skuId"` + StationNo string `json:"stationNo"` + VenderID string `json:"venderId"` + VipPrice int64 `json:"vipPrice"` +} + +type QueryStockResponse struct { + SkuID int64 `json:"skuId"` + StationNo string `json:"stationNo"` + UsableQty int `json:"usableQty"` + LockQty int `json:"lockQty"` + OrderQty int `json:"orderQty"` + Vendibility int `json:"vendibility"` +} + +type UpdateVendibilityResponse struct { + Code int `json:"code"` + CurrentQty int `json:"currentQty"` + LockQty int `json:"lockQty"` + Msg string `json:"msg"` + OrderQty int `json:"orderQty"` + OutSkuID string `json:"outSkuId"` + SkuID int64 `json:"skuId"` + UsableQty int `json:"usableQty"` + Vendibility int `json:"vendibility"` +} + // 传入为数组的,最多一次为50个 // 有好些功能有两个类似的函数,一个为到家ID,一个为商家ID,建议都只用商家ID的那个,因为: // 1,这类函数一般可以批量操作 @@ -85,16 +117,16 @@ func (a *API) UpdateStationPrice(skuId int64, stationNo string, price int) (stri // 根据到家商品编码和到家门店编码批量查询商品门店价格信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=205&apiid=21ccd5a00d3a4582b4c9a8ef0ae238fc -func (a *API) GetStationInfoList(stationNo string, skuIds []int64) ([]map[string]interface{}, error) { +func (a *API) GetStationInfoList(stationNo string, skuIds []int64) (priceInfo []*StorePriceInfo, err error) { jdParams := map[string]interface{}{ "skuIds": skuIds, "stationNo": stationNo, } - result, err := a.AccessAPINoPage("price/getStationInfoList", jdParams, nil, nil, nil) + result, err := a.AccessAPINoPage("price/getStationInfoList", jdParams, nil, nil, genNoPageResultParser("code", "detail", "result", "0")) if err == nil && result != nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &priceInfo, false) } - return nil, err + return priceInfo, err } // 根据商家商品编码和商家门店编码批量修改现货库存接口 @@ -105,7 +137,7 @@ func (a *API) BatchUpdateCurrentQtys(outStationNo, stationNo string, skuStockLis } jdParams := map[string]interface{}{ "skuStockList": skuStockList, - "userPin": userPin, + "userPin": utils.GetAPIOperator(userPin), } if outStationNo != "" { jdParams["outStationNo"] = outStationNo @@ -157,7 +189,7 @@ func (a *API) BatchUpdateVendibility(outStationNo, stationNo string, stockVendib } jdParams := map[string]interface{}{ "stockVendibilityList": stockVendibilityList, - "userPin": userPin, + "userPin": utils.GetAPIOperator(userPin), } if outStationNo != "" { jdParams["outStationNo"] = outStationNo @@ -174,28 +206,28 @@ func (a *API) BatchUpdateVendibility(outStationNo, stationNo string, stockVendib // 根据到家商品编码和门店编码批量查询商品库存及可售状态信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=200&apiid=bc6ad75e8fd34580856e06b5eb149aad // 尽量不用这个接口,用下面那个 -func (a *API) QueryOpenUseable(listBaseStockCenterRequest []*BaseStockCenterRequest) ([]map[string]interface{}, error) { +func (a *API) QueryOpenUseable(listBaseStockCenterRequest []*BaseStockCenterRequest) (stockResponse []*QueryStockResponse, err error) { jdParams := map[string]interface{}{ "listBaseStockCenterRequest": listBaseStockCenterRequest, } result, err := a.AccessAPINoPage("stock/queryOpenUseable", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0")) if err == nil && result != nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &stockResponse, false) } - return nil, err + return stockResponse, err } // 根据商家商品编码和门店编码批量查询商品库存及可售状态信息接口 // https://opendj.jd.com/staticnew/widgets/resources.html?groupid=200&apiid=ba70316bb84f425f8c088d3c19b2570d -func (a *API) QueryStockCenter(outStationNo string, skuIds []*SkuIdEntity, userPin string) ([]map[string]interface{}, error) { +func (a *API) QueryStockCenter(outStationNo string, skuIds []*SkuIdEntity, userPin string) (vendibilityResponse []*UpdateVendibilityResponse, err error) { jdParams := map[string]interface{}{ "outStationNo": outStationNo, "skuIds": skuIds, - "userPin": userPin, + "userPin": utils.GetAPIOperator(userPin), } result, err := a.AccessAPINoPage("stock/queryStockCenter", jdParams, nil, nil, genNoPageResultParser("retCode", "retMsg", "data", "0")) if err == nil && result != nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &vendibilityResponse, false) } - return nil, err + return vendibilityResponse, err } diff --git a/platformapi/jdapi/store_sku_test.go b/platformapi/jdapi/store_sku_test.go index 2e7b0985..0c0d6d60 100644 --- a/platformapi/jdapi/store_sku_test.go +++ b/platformapi/jdapi/store_sku_test.go @@ -8,8 +8,8 @@ import ( ) const ( - mustExistSkuID = 2017194325 - mustExistSkuJXID = "21206" + mustExistSkuID = 2023747677 + mustExistSkuJXID = "5246" ) func TestGetStationInfoList(t *testing.T) { @@ -17,9 +17,7 @@ func TestGetStationInfoList(t *testing.T) { if err != nil { t.Fatal(err) } - for _, v := range result { - baseapi.SugarLogger.Debug(v) - } + t.Log(utils.Format4Output(result, false)) } func TestQueryOpenUseable(t *testing.T) { @@ -32,9 +30,7 @@ func TestQueryOpenUseable(t *testing.T) { if err != nil { t.Fatal(err) } - for _, v := range result { - baseapi.SugarLogger.Debug(v) - } + t.Log(utils.Format4Output(result, false)) } func TestQueryStockCenter(t *testing.T) { @@ -46,9 +42,7 @@ func TestQueryStockCenter(t *testing.T) { if err != nil { t.Fatal(err) } - for _, v := range result { - baseapi.SugarLogger.Debug(v) - } + t.Log(utils.Format4Output(result, false)) } func TestBatchUpdateVendibility(t *testing.T) { diff --git a/platformapi/jdapi/store_test.go b/platformapi/jdapi/store_test.go index f8dc41f5..2b62eeab 100644 --- a/platformapi/jdapi/store_test.go +++ b/platformapi/jdapi/store_test.go @@ -9,10 +9,18 @@ import ( ) const ( - mustExistStoreID = "11738324" - mustExistStoreJXID = "100285" + mustExistStoreID = "11053496" + mustExistStoreJXID = "2" ) +func TestGetAllCities(t *testing.T) { + result, err := api.GetAllCities() + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + func TestGetStationsByVenderId(t *testing.T) { result, err := api.GetStationsByVenderId() if err != nil { diff --git a/platformapi/mtpsapi/mtpsapi.go b/platformapi/mtpsapi/mtpsapi.go index cb6dd4d0..1b0f7389 100644 --- a/platformapi/mtpsapi/mtpsapi.go +++ b/platformapi/mtpsapi/mtpsapi.go @@ -215,7 +215,7 @@ func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *R return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { code := int(utils.MustInterface2Int64(jsonResult1["code"])) retVal = &ResponseResult{ Code: code, diff --git a/platformapi/mtwmapi/act.go b/platformapi/mtwmapi/act.go index 9eeff362..b834f154 100644 --- a/platformapi/mtwmapi/act.go +++ b/platformapi/mtwmapi/act.go @@ -42,7 +42,7 @@ const ( type FullDiscountActInfo struct { ActIDs string `json:"act_ids"` ActName string `json:"act_name"` - StartTime int64 `json:"start_time"` // 活动开始时间,单s位秒 + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 ActType int `json:"act_type"` Status int `json:"-"` @@ -79,52 +79,105 @@ type ShippingFeeActDetail struct { LimitPrice float64 `json:"limit_price"` DiscountPrice float64 `json:"discount_price"` - PoiCharge float64 `json:"-"` - MtCharge float64 `json:"-"` + // list时用 + PoiCharge float64 `json:"poi_charge,omitempty"` + MtCharge float64 `json:"mt_charge,omitempty"` } type ShippingFeeActData struct { - StartTime int64 `json:"start_time"` // 活动开始时间,单s位秒 + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 WeeksTime string `json:"weeks_time,omitempty"` Period string `json:"period,omitempty"` ActDetail []*ShippingFeeActDetail `json:"act_detail"` MaxPrice float64 `json:"max_price,omitempty"` - AppPoiCode string `json:"-"` - ActID string `json:"-"` - ActStatus string `json:"-"` + // list时用 + AppPoiCode string `json:"app_poi_code,omitempty"` + ActID int64 `json:"act_id,omitempty"` + ActStatus string `json:"actStatus,omitempty"` } -type RetailActData struct { +type RetailDiscountActData struct { AppFoodCode string `json:"app_food_code"` UserType int `json:"user_type"` - StartTime int64 `json:"start_time"` // 活动开始时间,单s位秒 + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 OrderLimit int `json:"order_limit"` DayLimit int `json:"day_limit"` - WeeksTime string `json:"weeks_time"` Period string `json:"period"` + WeeksTime string `json:"weeks_time"` SettingType int `json:"setting_type"` ActPrice float64 `json:"act_price"` // 折扣价格(单位元):必须为大于0的数字,且不能超过2位小数。 DiscountCoefficient float64 `json:"discount_coefficient"` // 折扣系数:必须大于0小于9.8,最多支持两位小数。如输入3,即为3折 Sequence int `json:"sequence,omitempty"` ItemID int64 `json:"item_id,omitempty"` // 活动ID,为什么这里又是int64 - // 以下参数只有查询时用到 - OriginalPrice float64 `json:"-"` // 商品原价,单位元 - Stock int `json:"-"` // 当日剩余活动商品数量。只有当发起查询时间处于活动生效时段内时(start_time、end_time、period、weeks_time均需满足),该字段才代表实际剩余活动商品数量,否则显示的是创建活动时规定的当日活动库存 - Status int `json:"-"` // 活动状态,0:已过期,1:已生效,2:待生效。 - Name string `json:"-"` + // list时用 + OriginalPrice float64 `json:"origin_price,omitempty"` // 商品原价,单位元 + Stock int `json:"stock,omitempty"` // 当日剩余活动商品数量。只有当发起查询时间处于活动生效时段内时(start_time、end_time、period、weeks_time均需满足),该字段才代表实际剩余活动商品数量,否则显示的是创建活动时规定的当日活动库存 + Status int `json:"status,omitempty"` // 活动状态,0:已过期,1:已生效,2:待生效。 + Name string `json:"name,omitempty"` } -type RetailActDataLimit struct { +type RetailDiscountActResult struct { + AppFoodCode string `json:"app_food_code"` + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 + EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 + ActID int64 `json:"act_id,omitempty"` + Period string `json:"period"` + WeeksTime string `json:"weeks_time"` +} + +type RetailDiscountActDataLimit struct { ItemID int64 `json:"item_id"` // 活动ID,为什么这里又是int64 DayLimit int `json:"day_limit,omitempty"` // 当日活动库存,只能为正整数或-1,-1表示无限库存 OrderLimit int `json:"order_limit,omitempty"` // 每单可购买的折扣商品数量 } -func (a *API) FullDiscountBatchSave(poiCode string, actInfo *FullDiscountActInfo, actList []*FullDiscountActDetail, actSkuList []*FullDiscountSku) (actID int64, err error) { +type LimitTime struct { + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 + EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 +} + +type CouponInfo struct { + LimitPrice float64 `json:"limit_price"` //使用门槛,单位元 + CouponPrice float64 `json:"coupon_price"` // 优惠券金额,单位元 + UserType int `json:"user_type"` // 可领用户类型,新客、老客、全部 + ValidityDays int `json:"validity_days"` //使用有效期,用户领用了此商家券后在领用日起多久内可有效使用 + Stock int `json:"stock"` +} + +type CouponResult struct { + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 + EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 + ActID int64 `json:"act_id,omitempty"` +} + +type CouponData struct { + LimitPrice float64 `json:"limit_price"` //使用门槛,单位元 + CouponPrice float64 `json:"coupon_price"` // 优惠券金额,单位元 + UserType int `json:"user_type"` // 可领用户类型,新客、老客、全部 + ValidityDays int `json:"validity_days"` //使用有效期,用户领用了此商家券后在领用日起多久内可有效使用 + Stock int `json:"stock"` + + UseCount int `json:"use_count"` // 劵已使用数量 + SentCount int `json:"sent_count"` // 劵已发数量 +} + +type CouponActInfo struct { + AppPoiCode string `json:"app_poi_code"` + StartTime int64 `json:"start_time"` // 活动开始时间,单位秒 + EndTime int64 `json:"end_time"` // 活动结束时间,单位秒 + ActData []*CouponData `json:"act_data"` // 活动数据 + ActID int64 `json:"actId"` + RemainDays int `json:"remain_days"` // 活动剩余天数 + Status int `json:"status"` // 活动状态 +} + +// 批量创建指定商品满减活动或创建店内满减活动 +// http://developer.waimai.meituan.com/home/docDetail/255 +func (a *API) FullDiscountBatchSave(poiCode string, actInfo *FullDiscountActInfo, actList []*FullDiscountActDetail, actSkuList []*FullDiscountSku) (actIDList []int64, err error) { params := map[string]interface{}{ KeyAppPoiCode: poiCode, "act_info": string(utils.MustMarshal(actInfo)), @@ -135,11 +188,13 @@ func (a *API) FullDiscountBatchSave(poiCode string, actInfo *FullDiscountActInfo } result, err := a.AccessAPI2("act/full/discount/batchsave", false, params, resultKeyMsg) if err == nil { - return utils.MustInterface2Int64(result.([]interface{})[0].(map[string]interface{})["act_id"]), nil + err = utils.UnmarshalUseNumber([]byte(result.(string)), &actIDList) } - return 0, err + return actIDList, err } +// 批量查询指定商品满减活动或店内满减活动 +// http://developer.waimai.meituan.com/home/docDetail/256 func (a *API) FullDiscountList(poiCode string, actType int) (actInfoList []*FullDiscountActData, err error) { result, err := a.AccessAPI("act/full/discount/list", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -151,6 +206,8 @@ func (a *API) FullDiscountList(poiCode string, actType int) (actInfoList []*Full return actInfoList, err } +// 批量删除指定商品满减活动或店内满减活动 +// http://developer.waimai.meituan.com/home/docDetail/257 func (a *API) FullDiscountDelete(poiCode string, actIDList []string, actType int) (err error) { _, err = a.AccessAPI("act/full/discount/delete", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -160,6 +217,8 @@ func (a *API) FullDiscountDelete(poiCode string, actIDList []string, actType int return err } +// 批量添加活动商品至指定商品满减活动 +// http://developer.waimai.meituan.com/home/docDetail/258 func (a *API) FullDiscountFoodsBatchSave(poiCode, actID string, appFoodList []*FullDiscountSku) (err error) { _, err = a.AccessAPI("act/full/discount/foods/batchsave", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -169,6 +228,8 @@ func (a *API) FullDiscountFoodsBatchSave(poiCode, actID string, appFoodList []*F return err } +// 批量查询指定商品满减活动中的活动商品 +// http://developer.waimai.meituan.com/home/docDetail/259 func (a *API) FullDiscountFoodsList(poiCode, actID string) (actFoodsInfo *FullDiscountFoodsInfo, err error) { limit := 200 for { @@ -201,6 +262,7 @@ func (a *API) FullDiscountFoodsList(poiCode, actID string) (actFoodsInfo *FullDi // 批量删除活动商品至指定商品满减活动 // appFoodCodeList 删除商品数量上限为100,如果删除门店多个活动商品,用英文逗号隔开 +// http://developer.waimai.meituan.com/home/docDetail/260 func (a *API) FullDiscountFoodsDelete(poiCode, actID string, appFoodCodeList []string) (err error) { _, err = a.AccessAPI("act/full/discount/foods/delete", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -211,6 +273,7 @@ func (a *API) FullDiscountFoodsDelete(poiCode, actID string, appFoodCodeList []s } // 批量修改指定商品满减活动中商品的当日活动库存 +// http://developer.waimai.meituan.com/home/docDetail/261 func (a *API) FullDiscountFoodsDayLimit(poiCode, actID string, appFoodList []*FullDiscountSku) (err error) { _, err = a.AccessAPI("act/full/discount/foods/daylimit", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -222,6 +285,7 @@ func (a *API) FullDiscountFoodsDayLimit(poiCode, actID string, appFoodList []*Fu // 阶梯满减配送费活动一个门店只可能有一个活动,所以不涉及actID的概念 // 批量创建阶梯满减配送费活动 +// http://developer.waimai.meituan.com/home/docDetail/281 func (a *API) FulllDiscountShippingFeeBatchSave(poiCode string, actData []*ShippingFeeActData) (err error) { _, err = a.AccessAPI("act/full/discount/shippingfee/batchsave", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -231,6 +295,7 @@ func (a *API) FulllDiscountShippingFeeBatchSave(poiCode string, actData []*Shipp } // 查询阶梯满减配送费活动 +// http://developer.waimai.meituan.com/home/docDetail/282 func (a *API) FulllDiscountShippingFeeList(poiCode string) (actList []*ShippingFeeActData, err error) { result, err := a.AccessAPI("act/full/discount/shippingfee/list", true, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -241,20 +306,32 @@ func (a *API) FulllDiscountShippingFeeList(poiCode string) (actList []*ShippingF return actList, err } +// 批量删除阶梯满减配送费活动 +// http://developer.waimai.meituan.com/home/docDetail/283 +func (a *API) FulllDiscountShippingFeeBatchDelete(poiCode string, actIDList []string) (err error) { + _, err = a.AccessAPI("act/full/discount/shippingfee/batchdelete", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "act_ids": strings.Join(actIDList, ","), + }) + return err +} + // 批量创建或更新零售折扣商品 -func (a *API) RetailDiscountBatchSave(poiCode string, actData []*RetailActData) (actID int64, err error) { +// http://developer.waimai.meituan.com/home/docDetail/287 +func (a *API) RetailDiscountBatchSave(poiCode string, actData []*RetailDiscountActData) (actResult []*RetailDiscountActResult, err error) { result, err := a.AccessAPI2("act/retail/discount/batchsave", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "act_data": string(utils.MustMarshal(actData)), }, resultKeyMsg) if err == nil { - return utils.MustInterface2Int64(result.([]interface{})[0].(map[string]interface{})["act_id"]), nil + err = utils.UnmarshalUseNumber([]byte(result.(string)), &actResult) } - return 0, err + return actResult, err } // 查询门店零售折扣商品 -func (a *API) RetailDiscountList(poiCode string) (actList []*RetailActData, err error) { +// http://developer.waimai.meituan.com/home/docDetail/288 +func (a *API) RetailDiscountList(poiCode string) (actList []*RetailDiscountActData, err error) { limit := 200 offset := 0 for { @@ -264,7 +341,7 @@ func (a *API) RetailDiscountList(poiCode string) (actList []*RetailActData, err "offset": offset, }) if err == nil { - var tmpActList []*RetailActData + var tmpActList []*RetailDiscountActData if err = utils.Map2StructByJson(result, &tmpActList, false); err != nil { return nil, err } @@ -281,6 +358,7 @@ func (a *API) RetailDiscountList(poiCode string) (actList []*RetailActData, err } // 批量删除零售折扣商品 +// http://developer.waimai.meituan.com/home/docDetail/289 func (a *API) RetailDiscountDelete(poiCode string, actIDList []string) (err error) { _, err = a.AccessAPI("act/retail/discount/batchdelete", false, map[string]interface{}{ KeyAppPoiCode: poiCode, @@ -290,7 +368,8 @@ func (a *API) RetailDiscountDelete(poiCode string, actIDList []string) (err erro } // 批量修改零售折扣商品当日活动库存 -func (a *API) RetailDiscountBatchStock(poiCode, actDataList []*RetailActDataLimit) (err error) { +// http://developer.waimai.meituan.com/home/docDetail/290 +func (a *API) RetailDiscountBatchStock(poiCode, actDataList []*RetailDiscountActDataLimit) (err error) { _, err = a.AccessAPI("act/retail/discount/batchstock", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "act_data": string(utils.MustMarshal(actDataList)), @@ -299,10 +378,50 @@ func (a *API) RetailDiscountBatchStock(poiCode, actDataList []*RetailActDataLimi } // 批量修改零售折扣商品每单限购数量 -func (a *API) RetailDiscountBatchLimit(poiCode, actDataList []*RetailActDataLimit) (err error) { +// http://developer.waimai.meituan.com/home/docDetail/291 +func (a *API) RetailDiscountBatchLimit(poiCode, actDataList []*RetailDiscountActDataLimit) (err error) { _, err = a.AccessAPI("act/retail/discount/batchlimit", false, map[string]interface{}{ KeyAppPoiCode: poiCode, "act_data": string(utils.MustMarshal(actDataList)), }) return err } + +// 批量新增商家券(店内发券)活动 +// http://developer.waimai.meituan.com/home/docDetail/312 +func (a *API) InStoreCouponBatchSave(poiCode string, limitTime *LimitTime, couponInfoList []*CouponInfo) (couponResultList []*CouponResult, err error) { + result, err := a.AccessAPI2("act/instore/coupon/batchsave", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "limit_time": string(utils.MustMarshal(limitTime)), + "act_data": string(utils.MustMarshal(couponInfoList)), + }, resultKeyMsg) + if err == nil { + err = utils.UnmarshalUseNumber([]byte(result.(string)), &couponResultList) + } + return couponResultList, err +} + +// 批量删除商家券(店内发券)活动 +// http://developer.waimai.meituan.com/home/docDetail/313 +func (a *API) InStoreCouponDelete(poiCode string, actIDList []string) (err error) { + _, err = a.AccessAPI("act/instore/coupon/delete", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "act_ids": strings.Join(actIDList, ","), + }) + return err +} + +// 查询商家券(店内发券)活动 +// http://developer.waimai.meituan.com/home/docDetail/314 +// 页码从1开始 +func (a *API) InStoreCouponList(poiCode string, pageNum, pageSize int) (couponActList []*CouponActInfo, err error) { + result, err := a.AccessAPI("act/instore/coupon/list", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "page_num": pageNum, + "page_size": pageSize, + }) + if err == nil { + err = utils.Map2StructByJson(result, &couponActList, false) + } + return couponActList, err +} diff --git a/platformapi/mtwmapi/act_test.go b/platformapi/mtwmapi/act_test.go index 9e0177ee..cd280ed2 100644 --- a/platformapi/mtwmapi/act_test.go +++ b/platformapi/mtwmapi/act_test.go @@ -8,7 +8,7 @@ import ( ) func TestFullDiscountBatchSave(t *testing.T) { - result, err := api.FullDiscountBatchSave("6693359", &FullDiscountActInfo{ + result, err := api.FullDiscountBatchSave(testPoiCode, &FullDiscountActInfo{ // ActIDs: "12345678", ActName: "测试活动0402", StartTime: time.Now().Unix(), @@ -33,7 +33,7 @@ func TestFullDiscountBatchSave(t *testing.T) { } func TestFullDiscountList(t *testing.T) { - result, err := api.FullDiscountList("6737142", ActTypeStoreFullDiscount) + result, err := api.FullDiscountList(testPoiCode, ActTypeStoreFullDiscount) if err != nil { t.Fatal(err) } @@ -41,7 +41,7 @@ func TestFullDiscountList(t *testing.T) { } func TestFullDiscountFoodsList(t *testing.T) { - result, err := api.FullDiscountFoodsList("6737142", "497726932") + result, err := api.FullDiscountFoodsList(testPoiCode, "497726932") if err != nil { t.Fatal(err) } @@ -49,14 +49,14 @@ func TestFullDiscountFoodsList(t *testing.T) { } func TestFulllDiscountShippingFeeBatchSave(t *testing.T) { - err := api.FulllDiscountShippingFeeBatchSave("6737142", []*ShippingFeeActData{&ShippingFeeActData{}}) + err := api.FulllDiscountShippingFeeBatchSave(testPoiCode, []*ShippingFeeActData{&ShippingFeeActData{}}) if err != nil { t.Fatal(err) } } func TestFulllDiscountShippingFeeList(t *testing.T) { - result, err := api.FulllDiscountShippingFeeList("6737142") + result, err := api.FulllDiscountShippingFeeList(testPoiCode) if err != nil { t.Fatal(err) } @@ -64,8 +64,8 @@ func TestFulllDiscountShippingFeeList(t *testing.T) { } func TestRetailDiscountBatchSave(t *testing.T) { - result, err := api.RetailDiscountBatchSave("6737142", []*RetailActData{&RetailActData{ - AppFoodCode: "24785", + result, err := api.RetailDiscountBatchSave(testPoiCode, []*RetailDiscountActData{&RetailDiscountActData{ + AppFoodCode: "5246", UserType: UserTypeAll, StartTime: time.Now().Unix(), EndTime: time.Now().Add(24 * time.Hour).Unix(), @@ -81,7 +81,7 @@ func TestRetailDiscountBatchSave(t *testing.T) { } func TestRetailDiscountList(t *testing.T) { - result, err := api.RetailDiscountList("6737142") + result, err := api.RetailDiscountList(testPoiCode) if err != nil { t.Fatal(err) } @@ -89,8 +89,16 @@ func TestRetailDiscountList(t *testing.T) { } func TestRetailDiscountDelete(t *testing.T) { - err := api.RetailDiscountDelete("6737142", []string{"329187452"}) + err := api.RetailDiscountDelete(testPoiCode, []string{"329187452"}) if err != nil { t.Fatal(err) } } + +func TestInStoreCouponList(t *testing.T) { + result, err := api.InStoreCouponList(testPoiCode, 1, 100) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/mtwmapi/callback.go b/platformapi/mtwmapi/callback.go index e37b8b90..9353678e 100644 --- a/platformapi/mtwmapi/callback.go +++ b/platformapi/mtwmapi/callback.go @@ -38,10 +38,31 @@ type CallbackResponse struct { Data string `json:"data"` } -// !!!特别注意,Data中的数据 +// 全额退款没有Food, FoodList, Money项 +type CallbackRefundInfo struct { + Timestamp int64 `json:"timestamp"` + AppID string `json:"app_id"` + Sig string `json:"sig"` + + OrderID int64 `json:"order_id"` + NotifyType string `json:"notify_type"` + RefundID int64 `json:"refund_id"` + Ctime int64 `json:"ctime"` + Reason string `json:"reason"` + ResType int `json:"res_type"` + IsAppeal int `json:"is_appeal"` + Pictures string `json:"pictures"` + PictureList []string `json:"pictureList"` + + Food string `json:"food"` + FoodList []*RefundSkuDetail `json:"foodList"` + Money float32 `json:"money"` +} + type CallbackMsg struct { - Data url.Values - Cmd string + Cmd string + FormData url.Values + Data interface{} } var ( @@ -77,16 +98,29 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR msg = &CallbackMsg{ Cmd: cmd, } - msg.Data = make(url.Values) + msg.FormData = make(url.Values) for k, v := range data { // 美团外卖的用户催单信息的timestamp是毫秒(其它的事件是秒),统一归一化为秒 if cmd == MsgTypeUserUrgeOrder && k == "timestamp" { intV := utils.Str2Int64(v.(string)) if intV > time.Now().Unix()*2 { v = utils.Int64ToStr(intV / 1000) + data[k] = v } } - msg.Data.Set(k, v.(string)) + msg.FormData.Set(k, v.(string)) + } + if msg.Cmd == MsgTypeOrderRefund || msg.Cmd == MsgTypeOrderPartialRefund { + var refundData *CallbackRefundInfo + if err = utils.Map2StructByJson(data, &refundData, true); err == nil { + if refundData.Food != "" { + utils.UnmarshalUseNumber([]byte(refundData.Food), &refundData.FoodList) + } + if refundData.Pictures != "" { + utils.UnmarshalUseNumber([]byte(refundData.Pictures), &refundData.PictureList) + } + msg.Data = refundData + } } } } else { @@ -98,3 +132,8 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR } return msg, callbackResponse } + +func (a *API) GetRefundSkuDetailFromMsg(msg *CallbackMsg) (refundSkuDetail []*RefundSkuDetail) { + utils.UnmarshalUseNumber([]byte(msg.FormData.Get("food")), &refundSkuDetail) + return refundSkuDetail +} diff --git a/platformapi/mtwmapi/callback_test.go b/platformapi/mtwmapi/callback_test.go new file mode 100644 index 00000000..cd0eed57 --- /dev/null +++ b/platformapi/mtwmapi/callback_test.go @@ -0,0 +1,19 @@ +package mtwmapi + +import ( + "net/url" + "testing" + + "git.rosy.net.cn/baseapi/utils" +) + +func TestGetRefundSkuDetailFromMsg(t *testing.T) { + data, _ := url.ParseQuery(` + /mtwm/orderPartialRefund?timestamp=1556595759&reason=%E5%BA%97%E9%93%BA%E5%A4%AA%E5%BF%99%E4%BA%86%21%E5%AE%A2%E5%AE%98%E6%88%91%E6%80%95%E6%82%A8%E7%AD%89%E7%9A%84%E5%A4%AA%E4%B9%85&food=%5B%7B%22app_food_code%22%3A%2231458%22%2C%22box_num%22%3A0%2C%22box_price%22%3A0%2C%22count%22%3A1%2C%22food_name%22%3A%22%E9%B2%9C%E7%AB%B9%E7%AC%8B%EF%BC%88%E9%B8%A1%E5%A9%86%E7%AC%8B%EF%BC%89%5B%E6%9C%AA%E6%B3%A1%E6%B0%B4%5D%E7%BA%A6250g%2F%E4%BB%BD%22%2C%22food_price%22%3A7.97%2C%22origin_food_price%22%3A7.97%2C%22refund_price%22%3A7.97%2C%22sku_id%22%3A%2231458%22%2C%22spec%22%3A%22250g%22%7D%5D&money=7.97&is_appeal=0&pictures=¬ify_type=agree&app_id=589&order_id=67413510345111009&res_type=2&sig=0cef69f37b4a0e924ac4119c3d75117b + `) + msg := &CallbackMsg{ + FormData: data, + } + result := api.GetRefundSkuDetailFromMsg(msg) + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/mtwmapi/comment.go b/platformapi/mtwmapi/comment.go new file mode 100644 index 00000000..0695e54b --- /dev/null +++ b/platformapi/mtwmapi/comment.go @@ -0,0 +1,95 @@ +package mtwmapi + +import ( + "math" + "strings" + + "git.rosy.net.cn/baseapi/utils" +) + +const ( + CommentReplyStatusAll = -1 //全部 + CommentReplyStatusNotReplied = 0 // 未回复 + CommentReplyStatusReplied = 1 // 已回复 + + MaxCommentQueryPageSize = 20 +) + +type OrderComment struct { + AddComment string `json:"add_comment"` + AddCommentTime string `json:"add_comment_time"` + CommentContent string `json:"comment_content"` + CommentID int64 `json:"comment_id"` + CommentLables string `json:"comment_lables"` + CommentLableList []string `json:"comment_lable_list"` + CommentPictures string `json:"comment_pictures"` + CommentTime string `json:"comment_time"` + CriticFoodList string `json:"critic_food_list"` + DeliveryCommentScore int `json:"delivery_comment_score"` + FoodCommentScore int `json:"food_comment_score"` + OrderCommentScore int `json:"order_comment_score"` + PackingScore int `json:"packing_score"` + PraiseFoodList string `json:"praise_food_list"` + ReplyStatus int `json:"reply_status"` + Result string `json:"result"` + ShipTime int `json:"ship_time"` +} + +// 根据门店id批量查询评价信息(新版) +// http://developer.waimai.meituan.com/home/docDetail/191 +func (a *API) CommentQuery(poiCode string, startDateStr, endDateStr string, offset, limit int, replyStatus int) (commentList []*OrderComment, err error) { + params := map[string]interface{}{ + KeyAppPoiCode: poiCode, + "start_time": startDateStr, + "end_time": endDateStr, + "pageoffset": offset, + "pagesize": limit, + } + if limit <= 0 { + limit = math.MaxInt32 + } + originalOffset := offset + originalLimit := limit + for { + if limit <= 0 { + break + } + if limit > MaxCommentQueryPageSize { + limit = MaxCommentQueryPageSize + } + params["pageoffset"] = offset + params["pagesize"] = limit + result, err := a.AccessAPI("comment/query", true, params) + if err != nil { + return nil, err + } + var batchCommentList []*OrderComment + err = utils.Map2StructByJson(result, &batchCommentList, false) + if err != nil { + return nil, err + } + for _, comment := range batchCommentList { + if comment.CommentLables != "" { + comment.CommentLableList = strings.Split(comment.CommentLables, ",") + } + } + commentList = append(commentList, batchCommentList...) + if len(batchCommentList) < limit { + break + } + offset += limit + limit = originalLimit + originalOffset - offset + } + return commentList, nil +} + +// 根据评价id添加商家回复 +// http://developer.waimai.meituan.com/home/docDetail/194 +func (a *API) CommentAddReply(poiCode string, commentID int64, reply string) (err error) { + _, err = a.AccessAPI("poi/comment/add_reply", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "comment_id": commentID, + "reply": reply, + }) + return err +} diff --git a/platformapi/mtwmapi/comment_test.go b/platformapi/mtwmapi/comment_test.go new file mode 100644 index 00000000..155e56ba --- /dev/null +++ b/platformapi/mtwmapi/comment_test.go @@ -0,0 +1,15 @@ +package mtwmapi + +import ( + "testing" + + "git.rosy.net.cn/baseapi/utils" +) + +func TestCommentQuery(t *testing.T) { + result, err := api.CommentQuery("6783778", "20190501", "20190515", 0, 0, CommentReplyStatusAll) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} diff --git a/platformapi/mtwmapi/mtwmapi.go b/platformapi/mtwmapi/mtwmapi.go index 41ed8b81..e8f5704f 100644 --- a/platformapi/mtwmapi/mtwmapi.go +++ b/platformapi/mtwmapi/mtwmapi.go @@ -39,6 +39,12 @@ const ( GeneralMaxLimit = 200 // 大多数的API的批处理最大条数 ) +const ( + ErrCodeSysErr = 700 // 系统错误,按美团外卖技术支持的说法,可当成需重试的错误 + ErrCodeAccessLimited = 711 // 接口调用过于频繁,触发流控,请降低调用频率 + ErrCodeNoAppFood = 805 // 不存在此菜品 +) + type API struct { appID string secret string @@ -47,6 +53,13 @@ type API struct { config *platformapi.APIConfig } +var ( + canRetryCodes = map[int]int{ + ErrCodeSysErr: 1, + ErrCodeAccessLimited: 1, + } +) + func New(appID, secret, callbackURL string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { @@ -133,11 +146,14 @@ func (a *API) AccessAPI2(cmd string, isGet bool, bizParams map[string]interface{ return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { - if _, ok := jsonResult1["error"]; ok { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if errObj, ok := jsonResult1["error"]; ok { baseapi.SugarLogger.Debugf("mtwm AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) - errorInfo := jsonResult1["error"].(map[string]interface{}) + errorInfo := errObj.(map[string]interface{}) newErr := utils.NewErrorIntCode(errorInfo["msg"].(string), int(utils.MustInterface2Int64(errorInfo["code"]))) + if canRetryCodes[newErr.IntCode()] == 1 { + return platformapi.ErrLevelExceedLimit, newErr + } return platformapi.ErrLevelCodeIsNotOK, newErr } retVal = jsonResult1[resultKey] diff --git a/platformapi/mtwmapi/order.go b/platformapi/mtwmapi/order.go index ed6ae628..530426cf 100644 --- a/platformapi/mtwmapi/order.go +++ b/platformapi/mtwmapi/order.go @@ -22,6 +22,7 @@ const ( const ( WaybillStatusWait4Delivery = "0" // 配送单发往配送 + WaybillStatusPending = "5" // 配送压单 WaybillStatusAccepted = "10" // 配送单已确认 WaybillStatusCourierArrived = "15" // 骑手已到店 WaybillStatusPickedup = "20" // 骑手已取餐 @@ -29,6 +30,7 @@ const ( WaybillStatusCanceled = "100" // 配送单已取消 ) +// APP方URL 推送用户或客服取消订单(必接) const ( CancelReasonAcceptTimeout = 1001 // 系统取消,超时未确认 CancelReasonPayTimeout = 1002 // 系统取消,在线支付订单15分钟未支付 @@ -42,6 +44,18 @@ const ( CancelReasonOther = 1301 // 其他原因 ) +const ( + ResTypePending = 0 // 未处理 + ResTypeMerchantRefused = 1 // 商家驳回退款请求 + ResTypeMerchantAgreed = 2 // 商家同意退款 + ResTypeCSRefused = 3 // 客服驳回退款请求 + ResTypeCSAgreed = 4 // 客服帮商家同意退款 + ResTypeTimeoutAutoAgreed = 5 // 超过3小时自动同意 + ResTypeAutoAgreed = 6 // 系统自动确认 + ResTypeUserCancelApply = 7 // 用户取消退款申请 + ResTypeUserCancelComplain = 8 // 用户取消退款申诉 +) + const ( NotifyTypeApply = "apply" // 发起全款退款 NotifyTypePartyApply = "part" // 发起部分退款 @@ -55,12 +69,234 @@ const ( UserApplyCancelWaitMinute = 30 // 用户申请退款后,商家未在30分钟内(大连锁商家为3小时内)处理退款请求,系统将自动同意退款 ) +const ( + RefundTypeAll = 1 + RefundTypePart = 2 +) + +const ( + ExtrasPromotionTypeTaoCanZeng = 4 // 套餐赠 + ExtrasPromotionTypeManZeng = 5 // 满赠 + + ExtrasPromotionTypeTeJiaCai = 7 // 特价菜 + ExtrasPromotionTypeZheKouCai = 17 // 折扣菜 + ExtrasPromotionTypeSecondHalfPrice = 20 // 第2份半价活动 + ExtrasPromotionTypeShanGouBaoPin = 56 // 闪购爆品 +) + type RefundSku struct { AppFoodCode string `json:"app_food_code"` SkuID string `json:"sku_id,omitempty"` Count int `json:"count"` } +type RefundSkuDetail struct { + AppFoodCode string `json:"app_food_code"` + BoxNum float32 `json:"box_num"` + BoxPrice float64 `json:"box_price"` + Count int `json:"count"` + FoodName string `json:"food_name"` + FoodPrice float64 `json:"food_price"` + OriginFoodPrice float64 `json:"origin_food_price"` + RefundPrice float64 `json:"refund_price"` + SkuID string `json:"sku_id"` + Spec string `json:"spec"` +} + +type RefundOrderDetail struct { + ApplyReason string `json:"apply_reason"` + ApplyType int `json:"apply_type"` + CTime int64 `json:"ctime"` + Money float64 `json:"money"` + OrderID int64 `json:"order_id"` + Pictures string `json:"pictures"` + PictureList []string `json:"pictureList"` + RefundType int `json:"refund_type"` + ResReason string `json:"res_reason"` + ResType int `json:"res_type"` + UTime int64 `json:"utime"` + WmAppRetailForOrderPartRefundList []*RefundSkuDetail `json:"wmAppRetailForOrderPartRefundList"` + WmOrderIDView int64 `json:"wm_order_id_view"` +} + +type OrderExtraInfo struct { + ActDetailID int `json:"act_detail_id,omitempty"` + MtCharge float64 `json:"mt_charge,omitempty"` + PoiCharge float64 `json:"poi_charge,omitempty"` + ReduceFee float64 `json:"reduce_fee,omitempty"` + Remark string `json:"remark,omitempty"` + Type int `json:"type,omitempty"` + + RiderFee float64 `json:"rider_fee,omitempty"` +} + +type FoodInfo struct { + AppFoodCode string `json:"app_food_code"` + BoxNum int `json:"box_num"` + BoxPrice int `json:"box_price"` + CartID int `json:"cart_id"` + FoodDiscount int `json:"food_discount"` + FoodName string `json:"food_name"` + FoodProperty string `json:"food_property"` + Price float64 `json:"price"` + Quantity int `json:"quantity"` + SkuID string `json:"sku_id"` + Spec string `json:"spec"` + Unit string `json:"unit"` +} + +type CanRefundFoodInfo struct { + AppFoodCode string `json:"app_food_code"` + BoxNum float64 `json:"box_num"` + BoxPrice float64 `json:"box_price"` + Count int `json:"count"` + FoodName string `json:"food_name"` + FoodPrice float64 `json:"food_price"` + IsRefundDifference int `json:"is_refund_difference"` + OriginFoodPrice float64 `json:"origin_food_price"` + RefundPrice float64 `json:"refund_price"` + SkuID string `json:"sku_id"` +} + +type WmAppOrderActDetailInfo struct { + ActID int64 `json:"act_id"` + Count int `json:"count"` + MtCharge float64 `json:"mtCharge"` + PoiCharge float64 `json:"poiCharge"` + Remark string `json:"remark"` + Type int `json:"type"` +} + +type SkuBenefitDetailInfo struct { + ActivityPrice float64 `json:"activityPrice"` + AppFoodCode string `json:"app_food_code"` + BoxNumber float64 `json:"boxNumber"` + BoxPrice float64 `json:"boxPrice"` + Count int `json:"count"` + Name string `json:"name"` + OriginPrice float64 `json:"originPrice"` + SkuID string `json:"sku_id"` + TotalActivityPrice float64 `json:"totalActivityPrice"` + TotalBoxPrice float64 `json:"totalBoxPrice"` + TotalMtCharge float64 `json:"totalMtCharge"` + TotalOriginPrice float64 `json:"totalOriginPrice"` + TotalPoiCharge float64 `json:"totalPoiCharge"` + TotalReducePrice float64 `json:"totalReducePrice"` + WmAppOrderActDetails []*WmAppOrderActDetailInfo `json:"wmAppOrderActDetails"` +} + +type ActOrderChargeInfo struct { + Comment string `json:"comment"` + FeeTypeDesc string `json:"feeTypeDesc"` + FeeTypeID int `json:"feeTypeId"` + MoneyCent int64 `json:"moneyCent"` +} + +type PoiReceiveDetailInfo struct { + ActOrderChargeByMt []*ActOrderChargeInfo `json:"actOrderChargeByMt"` + ActOrderChargeByPoi []*ActOrderChargeInfo `json:"actOrderChargeByPoi"` + FoodShareFeeChargeByPoi int64 `json:"foodShareFeeChargeByPoi"` + LogisticsFee int64 `json:"logisticsFee"` + OnlinePayment int64 `json:"onlinePayment"` + WmPoiReceiveCent int64 `json:"wmPoiReceiveCent"` +} + +type OrderInfo struct { + AppOrderCode string `json:"app_order_code"` + AppPoiCode string `json:"app_poi_code"` + AvgSendTime int `json:"avg_send_time"` + BackupRecipientPhone string `json:"backup_recipient_phone"` + Caution string `json:"caution"` + CityID int `json:"city_id"` + Ctime int64 `json:"ctime"` + DaySeq int `json:"day_seq"` + DeliveryTime int `json:"delivery_time"` + Detail string `json:"detail"` + DetailList []*FoodInfo `json:"detailList"` + DinnersNumber int `json:"dinners_number"` + ExpectDeliverTime int `json:"expect_deliver_time"` + Extras string `json:"extras"` + ExtraList []*OrderExtraInfo `json:"extraList"` + HasInvoiced int `json:"has_invoiced"` + InvoiceTitle string `json:"invoice_title"` + IsFavorites bool `json:"is_favorites"` + IsPoiFirstOrder bool `json:"is_poi_first_order"` + IsPre int `json:"is_pre"` + IsThirdShipping int `json:"is_third_shipping"` + Latitude float64 `json:"latitude"` + LogisticsCode string `json:"logistics_code"` + Longitude float64 `json:"longitude"` + OrderCompletedTime int `json:"order_completed_time"` + OrderConfirmTime int `json:"order_confirm_time"` + OrderID int64 `json:"order_id"` + OrderSendTime int `json:"order_send_time"` + OriginalPrice float64 `json:"original_price"` + PackageBagMoney int `json:"package_bag_money"` + PayType int `json:"pay_type"` + PickType int `json:"pick_type"` + PoiReceiveDetail string `json:"poi_receive_detail"` + PoiReceiveDetailPtr *PoiReceiveDetailInfo `json:"poi_receive_detail_ptr"` + RecipientAddress string `json:"recipient_address"` + RecipientName string `json:"recipient_name"` + RecipientPhone string `json:"recipient_phone"` + Remark string `json:"remark"` + Result string `json:"result"` + ShipperPhone string `json:"shipper_phone"` + ShippingFee float64 `json:"shipping_fee"` + ShippingType int `json:"shipping_type"` + SkuBenefitDetail string `json:"sku_benefit_detail"` + SkuBenefitDetailList []*SkuBenefitDetailInfo `json:"sku_benefit_detail_list"` + SourceID int `json:"source_id"` + Status int `json:"status"` + TaxpayerID string `json:"taxpayer_id"` + Total float64 `json:"total"` + Utime int64 `json:"utime"` + WmOrderIDView int64 `json:"wm_order_id_view"` + WmPoiAddress string `json:"wm_poi_address"` + WmPoiID int `json:"wm_poi_id"` + WmPoiName string `json:"wm_poi_name"` + WmPoiPhone string `json:"wm_poi_phone"` +} + +type GetOrderActDetailParamAct struct { + Type int `json:"type,omitempty"` + ActID int64 `json:"act_id,omitempty"` +} + +type GetOrderActDetailParam struct { + OrderID int64 `json:"order_id_view"` + ActParam []*GetOrderActDetailParamAct `json:"act_param,omitempty"` +} + +type OrderActInfo struct { + OrderID int64 `json:"order_id_view"` + ActDetailList []*struct { + ActID int64 `json:"act_id"` + CouponDetail interface{} `json:"coupon_detail"` + EndTime int64 `json:"end_time"` + GiftsDetail interface{} `json:"gifts_detail"` + PoiDetail interface{} `json:"poi_detail"` + ProductDetail struct { + AppFoodCode string `json:"app_food_code"` + CategorySeqence int `json:"category_seqence"` + Charge string `json:"charge"` + DayLimit int `json:"day_limit"` + Name string `json:"name"` + OrderLimit int `json:"order_limit"` + Period string `json:"period"` + Policy string `json:"policy"` + SettingType int `json:"setting_type"` + UserType int `json:"user_type"` + WeeksTime string `json:"weeks_time"` + } `json:"product_detail"` + Remark string `json:"remark"` + StartTime int64 `json:"start_time"` + Status int `json:"status"` + Type int `json:"type"` + TypeName string `json:"type_name"` + } `json:"act_detail_list"` +} + func (a *API) OrderReceived(orderID int64) (err error) { _, err = a.AccessAPI("order/poi_received", true, map[string]interface{}{ KeyOrderID: orderID, @@ -115,10 +351,10 @@ func (a *API) OrderRefundReject(orderID int64, reason string) (err error) { } func (a *API) OrderApplyPartRefund(orderID int64, reason string, removeSkuList []*RefundSku) (err error) { - _, err = a.AccessAPI("order/applyPartRefund", true, map[string]interface{}{ + _, err = a.AccessAPI("order/applyPartRefund", false, map[string]interface{}{ KeyOrderID: orderID, "reason": reason, - "food_data": removeSkuList, + "food_data": string(utils.MustMarshal(removeSkuList)), }) return err } @@ -128,7 +364,6 @@ func (a *API) OrderViewStatus(orderID int64) (status int, err error) { KeyOrderID: orderID, }) if err == nil { - // baseapi.SugarLogger.Debug(result) return int(utils.MustInterface2Int64(result.(map[string]interface{})["status"])), nil } return 0, err @@ -148,6 +383,50 @@ func (a *API) OrderGetOrderDetail(orderID int64, isMTLogistics bool) (orderInfo return nil, err } +func (a *API) OrderGetOrderDetail2(orderID int64, isMTLogistics bool) (orderInfo *OrderInfo, err error) { + orderMap, err := a.OrderGetOrderDetail(orderID, isMTLogistics) + if err == nil { + if err = utils.Map2StructByJson(orderMap, &orderInfo, false); err == nil { + if orderInfo.Detail != "" { + err = utils.UnmarshalUseNumber([]byte(orderInfo.Detail), &orderInfo.DetailList) + } + if err != nil { + return nil, err + } + + if orderInfo.Extras != "" { + err = utils.UnmarshalUseNumber([]byte(orderInfo.Extras), &orderInfo.ExtraList) + } + if err != nil { + return nil, err + } + + if orderInfo.SkuBenefitDetail != "" { + err = utils.UnmarshalUseNumber([]byte(orderInfo.SkuBenefitDetail), &orderInfo.SkuBenefitDetailList) + } + if err != nil { + return nil, err + } + + if orderInfo.PoiReceiveDetail != "" { + err = utils.UnmarshalUseNumber([]byte(orderInfo.PoiReceiveDetail), &orderInfo.PoiReceiveDetailPtr) + } + } + } + return orderInfo, err +} + +func (a *API) OrderGetPartRefundFoods(orderID int64) (canRefundFoodList []*CanRefundFoodInfo, err error) { + params := map[string]interface{}{ + KeyOrderID: orderID, + } + result, err := a.AccessAPI("order/getPartRefundFoods", true, params) + if err == nil { + err = utils.Map2StructByJson(result, &canRefundFoodList, false) + } + return canRefundFoodList, err +} + func (a *API) OrderLogisticsPush(orderID int64, reason string) (err error) { _, err = a.AccessAPI("order/logistics/push", true, map[string]interface{}{ KeyOrderID: orderID, @@ -195,3 +474,37 @@ func (a *API) OrderLogisticsChange2Self(orderID int64) (err error) { }) return err } + +// refundOrderDetailList不能定义为[]*RefundOrderDetail,否则会有解析错误,原因不明 +func (a *API) GetOrderRefundDetail(orderID int64, refundType int) (refundOrderDetailList []*RefundOrderDetail, err error) { + params := map[string]interface{}{ + "wm_order_id_view": orderID, + } + if refundType > 0 { + params["refund_type"] = refundType + } + result, err := a.AccessAPI("ecommerce/order/getOrderRefundDetail", true, params) + if err == nil { + if err = utils.Map2StructByJson(result, &refundOrderDetailList, false); err == nil { + for _, v := range refundOrderDetailList { + if v.Pictures != "" { + if err = utils.UnmarshalUseNumber([]byte(v.Pictures), &v.PictureList); err != nil { + return nil, err + } + } + } + } + } + return refundOrderDetailList, err +} + +func (a *API) GetOrderActDetail(queryData []*GetOrderActDetailParam) (orderActList []*OrderActInfo, err error) { + params := map[string]interface{}{ + "query_data": string(utils.MustMarshal(queryData)), + } + result, err := a.AccessAPI("ecommerce/order/getOrderActDetail", true, params) + if err == nil { + err = utils.Map2StructByJson(result, &orderActList, false) + } + return orderActList, err +} diff --git a/platformapi/mtwmapi/order_test.go b/platformapi/mtwmapi/order_test.go index 5ebb06bf..fda51cf5 100644 --- a/platformapi/mtwmapi/order_test.go +++ b/platformapi/mtwmapi/order_test.go @@ -2,6 +2,8 @@ package mtwmapi import ( "testing" + + "git.rosy.net.cn/baseapi/utils" ) func TestOrderViewStatus(t *testing.T) { @@ -13,14 +15,56 @@ func TestOrderViewStatus(t *testing.T) { } func TestOrderGetOrderDetail(t *testing.T) { - result, err := api.OrderGetOrderDetail(33762863658107006, false) + result, err := api.OrderGetOrderDetail(69760842061320598, false) if err != nil { t.Fatal(err) } if len(result) == 0 { t.Fatal("result should have value") } - // t.Log(utils.Format4Output(result, false)) + t.Log(utils.Format4Output(result, false)) +} + +func TestOrderGetOrderDetail2(t *testing.T) { + result, err := api.OrderGetOrderDetail2(69760842061320598, false) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestOrderGetPartRefundFoods(t *testing.T) { + result, err := api.OrderGetPartRefundFoods(25236870562694625) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) +} + +func TestOrderReceived(t *testing.T) { + err := api.OrderReceived(25236872740350976) + if err != nil { + t.Fatal(err) + } +} + +func TestOrderConfirm(t *testing.T) { + err := api.OrderConfirm(25236872740350976) + if err != nil { + t.Fatal(err) + } +} + +func TestOrderApplyPartRefund(t *testing.T) { + err := api.OrderApplyPartRefund(25236872740350976, "缺货", []*RefundSku{ + &RefundSku{ + AppFoodCode: "30831", + Count: 1, + }, + }) + if err != nil { + t.Fatal(err) + } } func TestOrderLogisticsStatus(t *testing.T) { @@ -45,9 +89,27 @@ func TestOrderBatchPullPhoneNumber(t *testing.T) { // t.Log(utils.Format4Output(result, false)) } -func TestOrderConfirm(t *testing.T) { - err := api.OrderConfirm(24617230733961993) +func TestGetOrderRefundDetail(t *testing.T) { + result, err := api.GetOrderRefundDetail(25236870166465610, RefundTypePart) if err != nil { t.Fatal(err) } + t.Log(utils.Format4Output(result, false)) +} + +func TestGetOrderActDetaill(t *testing.T) { + result, err := api.GetOrderActDetail([]*GetOrderActDetailParam{ + &GetOrderActDetailParam{ + OrderID: 69760842061320598, + // ActParam: []*GetOrderActDetailParamAct{ + // &GetOrderActDetailParamAct{ + // Type: 1, + // }, + // }, + }, + }) + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) } diff --git a/platformapi/mtwmapi/poi.go b/platformapi/mtwmapi/poi.go index 392c361e..bc407d74 100644 --- a/platformapi/mtwmapi/poi.go +++ b/platformapi/mtwmapi/poi.go @@ -15,6 +15,38 @@ type PoiCategoryInfo struct { Name string `json:"name"` } +type PoiInfo struct { + Address string `json:"address,omitempty"` + AppID int `json:"app_id,omitempt"` + AppPoiCode string `json:"app_poi_code,omitempt"` + CityID int `json:"city_id,omitempt"` + Ctime int64 `json:"ctime,omitempt"` + InvoiceDescription string `json:"invoice_description,omitempt"` + InvoiceMinPrice int `json:"invoice_min_price,omitempt"` + InvoiceSupport int `json:"invoice_support,omitempt"` + IsOnline int `json:"is_online,omitempt"` + Latitude float64 `json:"latitude,omitempt"` + LocationID int `json:"location_id,omitempt"` + Longitude float64 `json:"longitude,omitempt"` + Name string `json:"name,omitempt"` + OpenLevel int `json:"open_level,omitempt"` + Phone string `json:"phone,omitempt"` + PicURL string `json:"pic_url,omitempt"` + PicURLLarge string `json:"pic_url_large,omitempt"` + PreBook int `json:"pre_book,omitempt"` + PreBookMaxDays int `json:"pre_book_max_days,omitempt"` + PreBookMinDays int `json:"pre_book_min_days,omitempt"` + PromotionInfo string `json:"promotion_info,omitempt"` + Remark string `json:"remark,omitempt"` + ShippingFee float64 `json:"shipping_fee,omitempt"` + ShippingTime string `json:"shipping_time,omitempt"` + StandbyTel string `json:"standby_tel,omitempt"` + TagName string `json:"tag_name,omitempt"` + ThirdTagName string `json:"third_tag_name,omitempt"` + TimeSelect int `json:"time_select,omitempt"` + Utime int64 `json:"utime,omitempt"` +} + func (a *API) PoiSave(poiCode string, poiParams map[string]interface{}) (err error) { _, err = a.AccessAPI("poi/save", false, utils.MergeMaps(utils.Params2Map(KeyAppPoiCode, poiCode), poiParams)) return err @@ -33,17 +65,17 @@ func (a *API) PoiGetIDs() (ids []string, err error) { return nil, err } -func (a *API) PoiMGet(poiCodes []string) (pois []map[string]interface{}, err error) { +func (a *API) PoiMGet(poiCodes []string) (pois []*PoiInfo, err error) { result, err := a.AccessAPI("poi/mget", true, map[string]interface{}{ KeyAppPoiCodes: strings.Join(poiCodes, ","), }) if err == nil { - return utils.Slice2MapSlice(result.([]interface{})), nil + err = utils.Map2StructByJson(result, &pois, false) } - return nil, err + return pois, err } -func (a *API) PoiGet(poiCode string) (poi map[string]interface{}, err error) { +func (a *API) PoiGet(poiCode string) (poi *PoiInfo, err error) { result, err := a.PoiMGet([]string{poiCode}) if err == nil { if len(result) == 0 { diff --git a/platformapi/mtwmapi/poi_test.go b/platformapi/mtwmapi/poi_test.go index 151ded8e..e8874199 100644 --- a/platformapi/mtwmapi/poi_test.go +++ b/platformapi/mtwmapi/poi_test.go @@ -7,7 +7,7 @@ import ( ) const ( - testPoiCode = "100077" + testPoiCode = "2523687" ) func TestPoiGetIDs(t *testing.T) { @@ -29,7 +29,7 @@ func TestPoiMGet(t *testing.T) { if len(result) != 1 { t.Fatal("result len is not ok") } - if result[0][KeyAppPoiCode] != testPoiCode { + if result[0].AppPoiCode != testPoiCode { t.Fatal("test_poi_01 is not equal") } t.Log(utils.Format4Output(result, false)) @@ -40,7 +40,7 @@ func TestPoiSave(t *testing.T) { if err != nil { t.Fatal(err) } - poiParams := utils.FilterMapNilMembers(result[0]) + poiParams := utils.FilterMapNilMembers(utils.Struct2FlatMap(result[0])) poiParams["name"] = "中国" err = api.PoiSave(testPoiCode, poiParams) if err != nil { diff --git a/platformapi/mtwmapi/retail.go b/platformapi/mtwmapi/retail.go index dd500381..549a5411 100644 --- a/platformapi/mtwmapi/retail.go +++ b/platformapi/mtwmapi/retail.go @@ -6,6 +6,10 @@ import ( "git.rosy.net.cn/baseapi/utils" ) +const ( + MaxSkuNameCharCount = 30 // SkuName的最大字符数 +) + type RetailCategoryInfo struct { Name string `json:"name"` Sequence int `json:"sequence"` @@ -20,6 +24,68 @@ type RetailTag struct { NamePath string `json:"namePath"` } +type BareStoreSkuInfo struct { + SkuID string `json:"sku_id"` + Price string `json:"price,omitempty"` + Stock string `json:"stock,omitempty"` +} + +type BareStoreFoodInfo struct { + AppFoodCode string `json:"app_food_code"` + Skus []*BareStoreSkuInfo `json:"skus"` +} + +type AvailableTimesInfo struct { + Friday string `json:"friday"` + Monday string `json:"monday"` + Saturday string `json:"saturday"` + Sunday string `json:"sunday"` + Thursday string `json:"thursday"` + Tuesday string `json:"tuesday"` + Wednesday string `json:"wednesday"` +} + +type SkuInfo struct { + AvailableTimes *AvailableTimesInfo `json:"available_times"` + BoxNum string `json:"box_num"` + BoxPrice string `json:"box_price"` + LocationCode string `json:"location_code"` + Price string `json:"price"` + SkuID string `json:"sku_id"` + Spec string `json:"spec"` + Stock string `json:"stock"` + Upc string `json:"upc"` + Weight int `json:"weight"` +} + +type AppFood struct { + AppFoodCode string `json:"app_food_code"` + AppPoiCode string `json:"app_poi_code"` + BoxNum float64 `json:"box_num"` + BoxPrice float64 `json:"box_price"` + CategoryCode string `json:"category_code"` + CategoryName string `json:"category_name"` + Ctime int `json:"ctime"` + Description string `json:"description"` + IsSp int `json:"isSp"` + IsSoldOut int `json:"is_sold_out"` + IsSpecialty int `json:"is_specialty"` + MinOrderCount int `json:"min_order_count"` + Name string `json:"name"` + Picture string `json:"picture"` + PictureContents string `json:"picture_contents"` + Price float64 `json:"price"` + SecondaryCategoryCode string `json:"secondary_category_code"` + SecondaryCategoryName string `json:"secondary_category_name"` + Sequence int `json:"sequence"` + Skus string `json:"skus"` + SkuList []*SkuInfo `json:"skuList"` + TagID int `json:"tag_id"` + Unit string `json:"unit"` + Utime int `json:"utime"` + ZhName string `json:"zh_name"` +} + // 美团分类没有ID,就以名字为唯一标识,不论级别都必须不能重名 // name(和originName)的长度不能超过10个字符(字符,不是字节) // 创建一级分类,originName为空,name为新分类名,secondaryName为空 @@ -100,48 +166,59 @@ func (a *API) RetailBatchInitData(poiCode string, foodDataList []map[string]inte return err } -func (a *API) RetailSkuPrice(poiCode string, foodData []map[string]interface{}) (err error) { +func (a *API) RetailSkuPrice(poiCode string, foodData []*BareStoreFoodInfo) (err error) { _, err = a.AccessAPI("retail/sku/price", false, map[string]interface{}{ KeyAppPoiCode: poiCode, - "food_data": foodData, + "food_data": string(utils.MustMarshal(foodData)), }) return err } -func (a *API) RetailSkuStock(poiCode string, foodData []map[string]interface{}) (err error) { +func (a *API) RetailSkuStock(poiCode string, foodData []*BareStoreFoodInfo) (err error) { _, err = a.AccessAPI("retail/sku/stock", false, map[string]interface{}{ KeyAppPoiCode: poiCode, - "food_data": foodData, + "food_data": string(utils.MustMarshal(foodData)), }) return err } -func (a *API) RetailGet(poiCode, foodCode string) (food map[string]interface{}, err error) { +func (a *API) RetailGet(poiCode, foodCode string) (food *AppFood, err error) { result, err := a.AccessAPI("retail/get", true, map[string]interface{}{ KeyAppPoiCode: poiCode, KeyAppFoodCode: foodCode, }) if err == nil { - return result.(map[string]interface{}), nil + if err = utils.Map2StructByJson(result, &food, true); err == nil && food.Skus != "" { + err = utils.UnmarshalUseNumber([]byte(food.Skus), &food.SkuList) + } } - return nil, err + return food, err } func (a *API) RetailSkuSave(poiCode, foodCode string, standardSkus, unstandardSkus []map[string]interface{}) (err error) { _, err = a.AccessAPI("retail/sku/save", false, map[string]interface{}{ KeyAppPoiCode: poiCode, KeyAppFoodCode: foodCode, - "standard_skus": standardSkus, - "unstandard_skus": unstandardSkus, + "standard_skus": string(utils.MustMarshal(standardSkus)), + "unstandard_skus": string(utils.MustMarshal(unstandardSkus)), }) return err } -func (a *API) RetailSkuSellStatus(poiCode string, foodData []map[string]interface{}, status int) (err error) { +func (a *API) RetailSkuSellStatus(poiCode string, foodData []map[string]interface{}, sellStatus int) (err error) { _, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{ KeyAppPoiCode: poiCode, - "food_data": foodData, - "status": status, + "food_data": string(utils.MustMarshal(foodData)), + "sell_status": sellStatus, + }) + return err +} + +func (a *API) RetailSkuSellStatus2(poiCode string, foodData []*BareStoreFoodInfo, sellStatus int) (err error) { + _, err = a.AccessAPI("retail/sku/sellStatus", false, map[string]interface{}{ + KeyAppPoiCode: poiCode, + "food_data": string(utils.MustMarshal(foodData)), + "sell_status": sellStatus, }) return err } diff --git a/platformapi/mtwmapi/retail_test.go b/platformapi/mtwmapi/retail_test.go index a81c8cdc..09704e74 100644 --- a/platformapi/mtwmapi/retail_test.go +++ b/platformapi/mtwmapi/retail_test.go @@ -47,7 +47,15 @@ func TestRetailList(t *testing.T) { if len(result) == 0 { t.Fatal("should have items") } - // t.Log(utils.Format4Output(result, false)) + t.Log(utils.Format4Output(result, false)) +} + +func TestRetailGet(t *testing.T) { + result, err := api.RetailGet(testPoiCode, "5246") + if err != nil { + t.Fatal(err) + } + t.Log(utils.Format4Output(result, false)) } func TestRetailGetSpTagIds(t *testing.T) { @@ -69,7 +77,7 @@ func TestRetailCatSkuBatchDelete(t *testing.T) { } func TestRetailDelete(t *testing.T) { - err := api.RetailDelete(testPoiCode, "") + err := api.RetailDelete(testPoiCode, "614") if err != nil { t.Fatal(err) } diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go index 5815cf37..505c667f 100644 --- a/platformapi/platformapi.go +++ b/platformapi/platformapi.go @@ -17,10 +17,9 @@ import ( ) const ( - DefClientTimeout = 10 * time.Second + DefClientTimeout = 5 * time.Second DefSleepSecondWhenExceedLimit = 3 * time.Second - DefRandSlice = 10 - DefMaxRecoverableRetryCount = 3 + DefMaxRecoverableRetryCount = 1 DefMaxExceedLimitRetryCount = 25 ) @@ -89,7 +88,7 @@ func getClonedData(requestURL *url.URL, r *bytes.Buffer) string { return retVal } -func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(bodyMap map[string]interface{}) (string, error)) error { +func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http.Request, config *APIConfig, handleResponse func(response *http.Response, bodyMap map[string]interface{}) (string, error)) error { exceedLimitRetryCount := 0 recoverableErrorRetryCount := 0 for { @@ -106,8 +105,8 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. if err != nil { baseapi.SugarLogger.Debugf("AccessPlatformAPIWithRetry:%s client.Get return err:%v", trackID, err) err, ok := err.(net.Error) + recoverableErrorRetryCount++ if ok /*&& err.Timeout()*/ && recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { // 只要是网络错误都重试 - recoverableErrorRetryCount++ continue } else { baseapi.SugarLogger.Errorf("AccessPlatformAPIWithRetry:%s access api url:%v, request:%v, error:%v", trackID, request.URL, getClonedData(request.URL, savedBuf), err) @@ -155,7 +154,7 @@ func AccessPlatformAPIWithRetry(client *http.Client, handleRequest func() *http. if err != nil { errLevel = ErrLevelRecoverableErr // 读取数据错误,或数据格式错误认为是偶发情况,重试 } else { - errLevel, err = handleResponse(bodyMap) + errLevel, err = handleResponse(response, bodyMap) } if err == nil { return nil diff --git a/platformapi/weimobapi/goods.go b/platformapi/weimobapi/goods.go index 98616472..aafd0f51 100644 --- a/platformapi/weimobapi/goods.go +++ b/platformapi/weimobapi/goods.go @@ -32,14 +32,29 @@ type PendingSaveB2CSkuVo struct { type PendingSaveSkuVo struct { OuterSkuCode string `json:"outerSkuCode"` - ImageURL string `json:"imageUrl"` - SalePrice float32 `json:"salePrice"` - OriginalPrice float32 `json:"originalPrice"` - CostPrice float32 `json:"costPrice"` + ImageURL string `json:"imageUrl,omitempty"` + SalePrice float64 `json:"salePrice"` + OriginalPrice float64 `json:"originalPrice,omitempty"` + CostPrice float64 `json:"costPrice"` EditStockNum int `json:"editStockNum"` B2cSku *PendingSaveB2CSkuVo `json:"b2cSku"` } +type PendingSaveGoodsVo struct { + Title string `json:"title"` + OuterGoodsCode string `json:"outerGoodsCode,omitempty"` + IsMultiSku int `json:"isMultiSku"` + GoodsImageURL []string `json:"goodsImageUrl"` + GoodsDesc string `json:"goodsDesc"` + InitialSales int `json:"initialSales,omitempty"` + DeductStockType int `json:"deductStockType"` + IsPutAway int `json:"isPutAway"` + Sort int `json:"Sort,omitempty"` + CategoryID int64 `json:"categoryId"` + B2cGoods []*PendingSaveB2CGoodsVo `json:"b2cGoods"` + SkuList []*PendingSaveSkuVo `json:"skuList"` +} + type Category struct { CategoryID int64 `json:"categoryId"` Title string `json:"title"` @@ -198,6 +213,16 @@ func (a *API) UpdateClassify(classifyID int64, title string, imageURL string) (e return err } +func getDataFromCUGoodsResult(result interface{}) (goodsId int64, skuMap map[string]int64, err error) { + skuMap = make(map[string]int64) + skuList := result.(map[string]interface{})["skuList"].([]interface{}) + for _, v := range skuList { + sku := v.(map[string]interface{}) + skuMap[utils.Interface2String(sku[KeyOuterSkuCode])] = utils.MustInterface2Int64(sku[KeySkuID]) + } + return utils.MustInterface2Int64(result.(map[string]interface{})["goodsId"]), skuMap, nil +} + func (a *API) AddGoods(outerGoodsCode, title string, isMultiSku bool, goodsImageUrl []string, goodsDesc string, isPutAway bool, sort int, categoryId int64, classifyIdList []int64, b2cGoods *PendingSaveB2CGoodsVo, skuList []map[string]interface{}, addParams map[string]interface{}) (goodsId int64, skuMap map[string]int64, err error) { goodsInfo := map[string]interface{}{ "outerGoodsCode": outerGoodsCode, @@ -221,13 +246,20 @@ func (a *API) AddGoods(outerGoodsCode, title string, isMultiSku bool, goodsImage "goods": mergedMap, }) if err == nil { - skuMap := make(map[string]int64) - skuList := result.(map[string]interface{})["skuList"].([]interface{}) - for _, v := range skuList { - sku := v.(map[string]interface{}) - skuMap[utils.Interface2String(sku[KeyOuterSkuCode])] = utils.MustInterface2Int64(sku[KeySkuID]) - } - return utils.MustInterface2Int64(result.(map[string]interface{})["goodsId"]), skuMap, nil + return getDataFromCUGoodsResult(result) + } + return 0, nil, err +} + +func (a *API) AddGoods2(goodsInfo *PendingSaveGoodsVo) (goodsId int64, skuMap map[string]int64, err error) { + if goodsInfo.DeductStockType == 0 { + goodsInfo.DeductStockType = DeductStockTypePay + } + result, err := a.AccessAPI("goods/addGoods", map[string]interface{}{ + "goods": goodsInfo, + }) + if err == nil { + return getDataFromCUGoodsResult(result) } return 0, nil, err } @@ -254,13 +286,20 @@ func (a *API) UpdateGoods(goodsID int64, title string, isMultiSku bool, goodsIma "goods": mergedMap, }) if err == nil { - skuMap := make(map[string]int64) - skuList := result.(map[string]interface{})["skuList"].([]interface{}) - for _, v := range skuList { - sku := v.(map[string]interface{}) - skuMap[utils.Interface2String(sku[KeyOuterSkuCode])] = utils.MustInterface2Int64(sku[KeySkuID]) - } - return utils.MustInterface2Int64(result.(map[string]interface{})["goodsId"]), skuMap, nil + return getDataFromCUGoodsResult(result) + } + return 0, nil, err +} + +func (a *API) UpdateGoods2(goodsInfo *PendingSaveGoodsVo) (goodsId int64, skuMap map[string]int64, err error) { + if goodsInfo.DeductStockType == 0 { + goodsInfo.DeductStockType = DeductStockTypePay + } + result, err := a.AccessAPI("goods/updateGoods", map[string]interface{}{ + "goods": goodsInfo, + }) + if err == nil { + return getDataFromCUGoodsResult(result) } return 0, nil, err } diff --git a/platformapi/weimobapi/weimobapi.go b/platformapi/weimobapi/weimobapi.go index d0580e4b..ae4ae685 100644 --- a/platformapi/weimobapi/weimobapi.go +++ b/platformapi/weimobapi/weimobapi.go @@ -162,7 +162,7 @@ func (a *API) AccessAPI(apiStr string, apiParams map[string]interface{}) (retVal return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { // baseapi.SugarLogger.Debug(utils.Format4Output(jsonResult1, false)) code := ResponseCodeSuccess errMsg := "" diff --git a/platformapi/weixinapi/weixinapi.go b/platformapi/weixinapi/weixinapi.go index 1aea54e9..efb9b9eb 100644 --- a/platformapi/weixinapi/weixinapi.go +++ b/platformapi/weixinapi/weixinapi.go @@ -100,7 +100,7 @@ func (a *API) AccessAPI(action string, params map[string]interface{}, body strin return request }, a.config, - func(jsonResult1 map[string]interface{}) (result string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (result string, err error) { var errInfo *ErrorInfo // 微信的返回值,在错误与正常情况下,结构是完全不一样的 if errCode, ok := jsonResult1["errcode"]; ok { diff --git a/platformapi/xiaowmapi/xiaowmapi.go b/platformapi/xiaowmapi/xiaowmapi.go index cdf82210..11c0383d 100644 --- a/platformapi/xiaowmapi/xiaowmapi.go +++ b/platformapi/xiaowmapi/xiaowmapi.go @@ -80,7 +80,7 @@ func (a *API) AccessAPI(apiName string, apiParams map[string]interface{}) (retVa return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := int(utils.Interface2Int64WithDefault(jsonResult1["errno"], ResponseCodeSuccess)) if code == ResponseCodeSuccess { retVal = jsonResult1 diff --git a/platformapi/yilianyunapi/yilianyunapi.go b/platformapi/yilianyunapi/yilianyunapi.go index 2df43d0d..ea67be88 100644 --- a/platformapi/yilianyunapi/yilianyunapi.go +++ b/platformapi/yilianyunapi/yilianyunapi.go @@ -91,7 +91,7 @@ func (a *API) AccessAPI(apiName string, apiParams map[string]interface{}) (retVa return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := utils.Interface2String(jsonResult1["error"]) if code == ResponseCodeSuccess { retVal, _ = jsonResult1["body"].(map[string]interface{}) diff --git a/platformapi/zhongwuapi/zhongwuapi.go b/platformapi/zhongwuapi/zhongwuapi.go index 6d071b85..e5f1b590 100644 --- a/platformapi/zhongwuapi/zhongwuapi.go +++ b/platformapi/zhongwuapi/zhongwuapi.go @@ -88,7 +88,7 @@ func (a *API) AccessAPI(apiName string, apiParams map[string]interface{}, isGet return request }, a.config, - func(jsonResult1 map[string]interface{}) (errLevel string, err error) { + func(response *http.Response, jsonResult1 map[string]interface{}) (errLevel string, err error) { code := int(utils.MustInterface2Int64(jsonResult1["errNum"])) if code == ResponseCodeSuccess { retVal, _ = jsonResult1["retData"].(map[string]interface{}) diff --git a/utils/java_date.go b/utils/java_date.go new file mode 100644 index 00000000..696f65ab --- /dev/null +++ b/utils/java_date.go @@ -0,0 +1,47 @@ +package utils + +import ( + "time" +) + +type JavaDate struct { + Day time.Weekday `json:"day"` // day of the week + + Hours int `json:"hours"` + Minutes int `json:"minutes"` + Seconds int `json:"seconds"` + Time int64 `json:"time"` + TimezoneOffset int `json:"timezoneOffset"` + + Date int `json:"date"` + Month time.Month `json:"month"` + Year int `json:"year"` +} + +func NewJavaDate() *JavaDate { + return NewJavaDateFromTime(time.Now()) +} + +func NewJavaDateFromTime(tm time.Time) (jd *JavaDate) { + jd = &JavaDate{ + Time: tm.UnixNano() / 1000000, + Day: tm.Weekday(), + Hours: tm.Hour(), + Minutes: tm.Minute(), + Seconds: tm.Second(), + } + jd.Year, jd.Month, jd.Date = tm.Date() + jd.Year -= 1900 + jd.Month-- + _, jd.TimezoneOffset = tm.Zone() + jd.TimezoneOffset /= 60 + jd.TimezoneOffset = -jd.TimezoneOffset + return jd +} + +func (j *JavaDate) GoTime() time.Time { + if j == nil { + return DefaultTimeValue + } + return Timestamp2Time(j.Time / 1000) +} diff --git a/utils/java_date_test.go b/utils/java_date_test.go new file mode 100644 index 00000000..11773b03 --- /dev/null +++ b/utils/java_date_test.go @@ -0,0 +1,38 @@ +package utils + +import ( + "testing" +) + +func TestJavaDate(t *testing.T) { + var ( + date interface{} + jd *JavaDate + ) + err := UnmarshalUseNumber([]byte(` + { + "date":6, + "day":1, + "hours":9, + "minutes":45, + "month":4, + "seconds":46, + "time":1557107146000, + "timezoneOffset":-480, + "year":119 + } + `), &date) + if err != nil { + t.Fatal(err) + } + err = Map2StructByJson(date, &jd, false) + if err != nil { + t.Fatal(err) + } + t.Log(jd.GoTime()) + t.Log(Format4Output(jd, false)) + + jd = NewJavaDateFromTime(jd.GoTime()) + t.Log(jd.GoTime()) + t.Log(Format4Output(jd, false)) +} diff --git a/utils/routinepool/routinepool.go b/utils/routinepool/routinepool.go index 17115757..4ebcc111 100644 --- a/utils/routinepool/routinepool.go +++ b/utils/routinepool/routinepool.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "fmt" "runtime/debug" + + "git.rosy.net.cn/baseapi/utils" ) const ( @@ -65,23 +67,27 @@ func taskFun(taskChan chan *TaskParam, index int) { } } -func (p *Pool) callFun(func4Call func(), primaryID string, isAsync bool) chan interface{} { +func (p *Pool) callFun(func4Call func(), primaryID string, isAsync bool) (r interface{}) { result := md5.Sum([]byte(primaryID)) resultInt64 := int64(binary.LittleEndian.Uint32(result[8:])) chanIndex := int(resultInt64 % int64(p.curRoutineCount)) chanParam := &TaskParam{ handler: func4Call, } - if isAsync { + if !isAsync { chanParam.resultChan = make(chan interface{}) } p.taskChans[chanIndex] <- chanParam - return chanParam.resultChan + if !isAsync { + r = <-chanParam.resultChan + close(chanParam.resultChan) + } + return r } func (p *Pool) CallFun(func4Call func(), primaryID string) { if p.curRoutineCount > 0 { - r := <-p.callFun(func4Call, primaryID, true) + r := p.callFun(func4Call, primaryID, false) if r != nil { panic(r) } @@ -94,5 +100,7 @@ func (p *Pool) CallFunAsync(func4Call func(), primaryID string) { if p.curRoutineCount == 0 { panic("CallFunAsync can not run when p.curRoutineCount == 0") } - p.callFun(func4Call, primaryID, false) + utils.CallFuncAsync(func() { + p.callFun(func4Call, primaryID, true) + }) } diff --git a/utils/typeconv.go b/utils/typeconv.go index 527302cd..9aacf1ab 100644 --- a/utils/typeconv.go +++ b/utils/typeconv.go @@ -7,7 +7,6 @@ import ( "fmt" "math" "net/url" - "reflect" "strconv" "strings" "time" @@ -22,17 +21,6 @@ var ( ZeroTimeValue = time.Time{} ) -func GetConcretValue(value reflect.Value) reflect.Value { - for { - if value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr { - value = value.Elem() - } else { - break - } - } - return value -} - func UnmarshalUseNumber(data []byte, result interface{}) error { err := TryUnmarshalUseNumber(data, result) if err != nil { @@ -184,21 +172,6 @@ func Interface2StringList(data interface{}) []string { return retVal } -func Interface2Slice(value interface{}) (retVal []interface{}) { - typeInfo := reflect.TypeOf(value) - if typeInfo.Kind() != reflect.Slice { - panic("list must be slice type!") - } - if value != nil { - valueInfo := reflect.ValueOf(value) - retVal = make([]interface{}, valueInfo.Len()) - for i := 0; i < valueInfo.Len(); i++ { - retVal[i] = valueInfo.Index(i).Interface() - } - } - return retVal -} - func Slice2MapSlice(data []interface{}) []map[string]interface{} { retVal := make([]map[string]interface{}, len(data)) for k, v := range data { @@ -486,6 +459,7 @@ func Struct2FlatMap(obj interface{}) map[string]interface{} { return FlatMap(m) } +// !!! 此函数好像不支持struct是内嵌结构的 func Map2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) { decoder, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ TagName: "json", diff --git a/utils/utils.go b/utils/utils.go index d265207b..445c7c42 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -14,6 +14,10 @@ import ( uuid "github.com/satori/go.uuid" ) +const ( + APIPin = "jxadmin" +) + func DictKeysMan(data interface{}, keysToRemove []string, keysToKeep []string) interface{} { if data == nil || (keysToKeep == nil && keysToRemove == nil) { return data @@ -101,10 +105,11 @@ func GetCurTimestamp() int64 { } func GetAPIOperator(userName string) string { - retVal := "jxc4-admin" + retVal := APIPin if userName != "" { retVal += "-" + userName } + retVal = LimitUTF8StringLen(retVal, 50) return retVal } @@ -182,14 +187,18 @@ func GenerateGetURL(baseURL, apiStr string, params map[string]interface{}) strin return baseURL + queryString } -func SendFakeRequest(method, url, body, contentType string) (*http.Response, error) { - request, _ := http.NewRequest(method, url, strings.NewReader(body)) +func BuildRequest(method, url, body, contentType string) (request *http.Request) { + request, _ = http.NewRequest(method, url, strings.NewReader(body)) if contentType == "" { contentType = "application/x-www-form-urlencoded" } request.Header.Set("Content-Type", contentType) + return request +} + +func SendFakeRequest(method, url, body, contentType string) (*http.Response, error) { client := &http.Client{} - return client.Do(request) + return client.Do(BuildRequest(method, url, body, contentType)) } // 过滤 utf8mb4(比如emoji表情) @@ -238,7 +247,7 @@ func Base64DecodeMultiString(strs ...string) (decodedData [][]byte, err error) { return decodedData, nil } -// 只适合与纯英文的情况 +// 限制的是字节数,只适合纯英文的情况,推荐使用LimitMixedStringLen,除非确定是纯英文,且对性能相当敏感 func LimitStringLen(str string, maxByteCount int) (limitedStr string) { if maxByteCount > 0 { if strLen := len(str); strLen > maxByteCount { diff --git a/utils/utils_reflect.go b/utils/utils_reflect.go new file mode 100644 index 00000000..16aea491 --- /dev/null +++ b/utils/utils_reflect.go @@ -0,0 +1,85 @@ +package utils + +import ( + "reflect" + "strings" +) + +// 得到一个结构体(或指向结构体的指针)中其成员(tag指定,缺省是字段名)的路径(嵌入结构体或指向结构指针会被展开) +func GetStructObjNameIndex(obj interface{}, tag string) map[string][]int { + objType := reflect.TypeOf(obj) + return GetStructNameIndex(objType, tag) +} + +func GetStructNameIndex(objType reflect.Type, tag string) (nameIndex map[string][]int) { + if objType.Kind() == reflect.Ptr { + objType = objType.Elem() + } + if objType.Kind() != reflect.Struct { + panic("obj must be struct obj or pointer to struct of obj!") + } + nameIndex = make(map[string][]int) + for i := 0; i < objType.NumField(); i++ { + structField := objType.Field(i) + fieldType := structField.Type + + if IndirectType(fieldType).Kind() == reflect.Struct && IsFieldEmbedded(&structField) { // 嵌入 + subNameIndex := GetStructNameIndex(fieldType, tag) + for k, v := range subNameIndex { + nameIndex[k] = append([]int{i}, v...) + } + } else { + tagName := structField.Name + if tag != "" { + tagName2 := strings.Split(structField.Tag.Get(tag), ",")[0] + if tagName2 == "-" { + tagName = "" // 忽略 + } else if tagName2 != "" { + tagName = tagName2 + } + } + if tagName != "" { + nameIndex[tagName] = []int{i} + } + } + } + return nameIndex +} + +// 判断成员是否是嵌入的,依据是成员名与类型名相同 +func IsFieldEmbedded(structField *reflect.StructField) bool { + return structField.Name == IndirectType(structField.Type).Name() +} + +func IndirectType(objType reflect.Type) reflect.Type { + if objType.Kind() == reflect.Ptr { + objType = objType.Elem() + } + return objType +} + +func GetConcretValue(value reflect.Value) reflect.Value { + for { + if value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr { + value = value.Elem() + } else { + break + } + } + return value +} + +func Interface2Slice(value interface{}) (retVal []interface{}) { + typeInfo := reflect.TypeOf(value) + if typeInfo.Kind() != reflect.Slice { + panic("list must be slice type!") + } + if value != nil { + valueInfo := reflect.ValueOf(value) + retVal = make([]interface{}, valueInfo.Len()) + for i := 0; i < valueInfo.Len(); i++ { + retVal[i] = valueInfo.Index(i).Interface() + } + } + return retVal +} diff --git a/utils/utils_reflect_test.go b/utils/utils_reflect_test.go new file mode 100644 index 00000000..9398f43a --- /dev/null +++ b/utils/utils_reflect_test.go @@ -0,0 +1,24 @@ +package utils + +import ( + "reflect" + "testing" +) + +func TestReflect(t *testing.T) { + type SubXXXX struct { + IntData int + StrData string + } + type XXXX struct { + SubXXXX + A int + B string `json:"b"` + } + + x := XXXX{} + typeX := reflect.TypeOf(x) + field := typeX.Field(0) + t.Log(IsFieldEmbedded(&field)) + t.Log(Format4Output(GetStructObjNameIndex(&x, ""), false)) +}