From 13757d515d2f75589c215035a993070d8e68f33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E5=AE=97=E6=A5=A0?= Date: Thu, 14 Apr 2022 17:02:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8A=96=E9=9F=B3=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformapi/fnpsapi/fn_test.go | 4 + platformapi/mtpsapi/mtpsapi.go | 5 - platformapi/mtpsapi/riderInfo.go | 19 ++ platformapi/mtwmapi/order.go | 7 + platformapi/sfps/callback.go | 198 +++++++++++++++++++++ platformapi/sfps/order.go | 274 +++++++++++++++++++++++++++++ platformapi/sfps/sf_base.go | 159 +++++++++++++++++ platformapi/sfps/sf_test.go | 208 ++++++++++++++++++++++ platformapi/sfps/sfpsapi.go | 141 +++++++++++++++ platformapi/sfps/store.go | 119 +++++++++++++ platformapi/tiktok/tiktok.go | 73 ++++++++ platformapi/tiktok/tiktok_const.go | 24 +++ platformapi/tiktok/tiktok_token.go | 81 +++++++++ 13 files changed, 1307 insertions(+), 5 deletions(-) create mode 100644 platformapi/mtpsapi/riderInfo.go create mode 100644 platformapi/sfps/callback.go create mode 100644 platformapi/sfps/order.go create mode 100644 platformapi/sfps/sf_base.go create mode 100644 platformapi/sfps/sf_test.go create mode 100644 platformapi/sfps/sfpsapi.go create mode 100644 platformapi/sfps/store.go create mode 100644 platformapi/tiktok/tiktok.go create mode 100644 platformapi/tiktok/tiktok_const.go create mode 100644 platformapi/tiktok/tiktok_token.go diff --git a/platformapi/fnpsapi/fn_test.go b/platformapi/fnpsapi/fn_test.go index 1728eddb..8dd4f8f5 100644 --- a/platformapi/fnpsapi/fn_test.go +++ b/platformapi/fnpsapi/fn_test.go @@ -204,4 +204,8 @@ func TestDaDa(t *testing.T) { fmt.Println("美团", 1&0) fmt.Println("达达", 2&0) fmt.Println("蜂鸟", 4&0) + fmt.Println("蜂鸟", 19&16 != 0) + fmt.Println("蜂鸟", 23&16 != 0) + fmt.Println("蜂鸟", 21&16 != 0) + fmt.Println("蜂鸟", 11&16 != 0) } diff --git a/platformapi/mtpsapi/mtpsapi.go b/platformapi/mtpsapi/mtpsapi.go index 5a03ca0b..ff3d2f7c 100644 --- a/platformapi/mtpsapi/mtpsapi.go +++ b/platformapi/mtpsapi/mtpsapi.go @@ -228,10 +228,6 @@ func (a *API) signParams(params url.Values) string { } func (a *API) AccessAPI2(baseURL, action string, params map[string]interface{}) (retVal *ResponseResult, err error) { - // if params == nil { - // panic("params is nil!") - // } - params2 := utils.Map2URLValues(params) if baseURL == mtpsAPIURL { params2.Set("appkey", a.appKey) @@ -241,7 +237,6 @@ func (a *API) AccessAPI2(baseURL, action string, params map[string]interface{}) } else if a.GetCookieCount() == 0 { return nil, fmt.Errorf("需要设置Store Cookie才能使用此方法") } - // baseapi.SugarLogger.Debug(params2.Encode()) err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { diff --git a/platformapi/mtpsapi/riderInfo.go b/platformapi/mtpsapi/riderInfo.go new file mode 100644 index 00000000..cb697205 --- /dev/null +++ b/platformapi/mtpsapi/riderInfo.go @@ -0,0 +1,19 @@ +package mtpsapi + +const ( + DaDaCode = 10002 // 达达配送 + FnPsCode = 10004 // 蜂鸟配送 + MTPsCode = 10032 // 美团配送 + MyselfPsCode = 10015 // 自送 +) + +type RiderInfo struct { + OrderId string `json:"order_id"` // 发单平台订单id(美团,京西,京,京东) + ThirdCarrierOrderId string `json:"third_carrier_order_id"` // 京西平台id + CourierName string `json:"courier_name"` // 骑手名称 + CourierPhone string `json:"courier_phone"` // 骑手电话 + LogisticsProviderCode string `json:"logistics_provider_code"` // 配送平台code 10001-顺丰, 10002-达达, 10003-闪送, 10004-蜂鸟, 10005 UU跑腿,10006 快跑者, 10007 极客快送,10008-点我达,10009 同达, 10010-生活半径,10011 邻趣,10012 趣送, 10013 快服务 10014 菜鸟新配盟 10015 商家自建配送 10016 风先生,10017-其他,10032-美团跑腿 + LogisticsStatus int `json:"logistics_status"` // 配送状态 + Latitude string `json:"latitude"` // 骑手当前的纬度,美团使用的是高德坐标系。 + Longitude string `json:"longitude"` // 骑手当前的经度,美团使用的是高德坐标系。 +} diff --git a/platformapi/mtwmapi/order.go b/platformapi/mtwmapi/order.go index ee5214fc..33d66ec2 100644 --- a/platformapi/mtwmapi/order.go +++ b/platformapi/mtwmapi/order.go @@ -659,3 +659,10 @@ func (a *API) OrderUpdateTip(orderID int64, tipAmount float64) (err error) { _, err = a.AccessAPI("order/zhongbao/update/tip", true, params) return err } + +// 美团外卖自配送商家同步发货状态和配送信息(推荐) +// https://waimaiopen.meituan.com/api/v1/ecommerce/order/logistics/sync +func (a *API) OrderStatusAndPsInfo(params map[string]interface{}) (err error) { + _, err = a.AccessAPI("ecommerce/order/logistics/sync", false, params) + return err +} diff --git a/platformapi/sfps/callback.go b/platformapi/sfps/callback.go new file mode 100644 index 00000000..c4023d71 --- /dev/null +++ b/platformapi/sfps/callback.go @@ -0,0 +1,198 @@ +package fnpsapi + +import ( + "encoding/json" + "git.rosy.net.cn/baseapi" + "io/ioutil" + "net/http" +) + +const ( + OrderStatus = "orderStatusNotify" // 订单状态回调 + AbnormalStatus = "abnormalReportNotify" // 异常报备回调 + CookingFinishStatus = "cookingFinishNotify" // 商户出餐回调 + ChainstoreStatus = "chainstoreStatusNotify" // 门店状态变更回调 + ChainstoreServiceStatus = "chainstoreServiceStatusNotify" // 门店采购服务变更回调 + NoServiceStatus = "noServiceNotify" // 城市屏蔽区域调整回调通知 +) + +var ( + SuccessResponse = &CallbackResponse{Code: 200} + SignatureIsNotOk = &CallbackResponse{Code: -1} +) + +type CallbackResponse struct { + Code int `json:"code"` +} + +func Err2CallbackResponse(err error, data string) *CallbackResponse { + if err == nil { + return SuccessResponse + } + return &CallbackResponse{ + Code: -1, + } +} + +type ShortStatus struct { + AppId string `json:"app_id"` + Signature string `json:"signature"` + Timestamp string `json:"timestamp"` + BusinessData string `json:"business_data"` +} + +//#region 订单 + +// 订单状态 +type OrderStatusNottify struct { + CallbackBusinessType string `json:"callback_business_type"` + Param *OrderCallbackParam `json:"param"` +} + +// 订单状态回调paramter +type OrderCallbackParam struct { + OrderId int64 `json:"order_id"` // 订单号 + AppId string `json:"app_id"` // 应用id + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + OrderStatus int `json:"order_status"` // 订单状态 订单生成0,运单生成成功1,20:骑手接单,80:骑手到 店,2:配送中,3:已完成,4:已取消,5:配送异常 + CarrierDriverId int64 `json:"carrier_driver_id"` // 骑手id + CarrierLat string `json:"carrier_lat"` // 坐标高德(不要使用) + CarrierLng string `json:"carrier_lng"` // 坐标高德(不要使用) + CarrierDriverName string `json:"carrier_driver_name"` // 骑手姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 骑手电话 + Description string `json:"description"` // 描述 + ErrorCode string `json:"error_code"` // 异常code + ErrorScene string `json:"error_scene"` // 异常描述 + DetailDescription string `json:"detail_description"` // 详情描述 + PushTime int64 `json:"push_time"` // 状态推送时间 (毫秒) + Transfer int `json:"transfer"` // 转单标识 转单标识 1 是转单 0非转单 +} + +//#endregion +// 门店状态回调 +type ChainstoreStatusNotify struct { + CallbackBusinessType string `json:"callback_business_type"` + Param *ChainstoreParam `json:"param"` +} + +// 门店状态回调paramter +type ChainstoreParam struct { + MerchantId string `json:"merchant_id"` // 商户id + ChainStoreId string `json:"chain_store_id"` // 蜂鸟门店id + OutShopCode string `json:"out_shop_code"` // 外部门店编码 + Status string `json:"status"` // 门店认证状态 + ModifyStatus string `json:"modify_status"` // 门店修改状态 + Remark string `json:"remark"` // 门店认证、修改等驳回时返回原因 +} + +// 出餐回调 +type CookingFinishNotify struct { + OrderId int64 `json:"order_id"` // 订单号 + AppId string `json:"app_id"` // 应用id + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + ChainStoreId string `json:"chain_store_id"` // 蜂鸟门店id + MerchantId string `json:"merchant_id"` // 商户id + CookingFinishTime int64 `json:"cooking_finish_time"` // 状态推送时间 (毫秒) +} + +// 异常状态回掉 +type AbnormalStatusNotify struct { + CallbackBusinessType string `json:"callback_business_type"` + Param *AbnormalReportNotify `json:"param"` +} + +// 异常状态回调 +type AbnormalReportNotify struct { + OrderId int64 `json:"order_id"` // 订单号 + AppId string `json:"app_id"` // 应用id + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + CarrierDriverId int64 `json:"carrier_driver_id"` // 骑手id + Longitude string `json:"longitude"` // 纬度 + Latitude string `json:"latitude"` // 门店纬度 + AbnormalCode string `json:"abnormal_code"` // 异常报备code + AbnormalDesc string `json:"abnormal_desc"` // 异常报备描述 + AbnormalReportTime int64 `json:"abnormal_report_time"` // 异常报备时间 毫秒 + +} + +// 获取门店状态回调消息 +func (a *API) GetChainstoreStatusNotify(request *http.Request) (shopStatusMsg *ChainstoreStatusNotify, callbackResponse *CallbackResponse) { + data, err := ioutil.ReadAll(request.Body) + if err != nil { + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + storeNotify := &ShortStatus{} + if err := json.Unmarshal(data, &storeNotify); err != nil { + baseapi.SugarLogger.Debugf("FN GetChainstoreStatusNotify failed with err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + fnNotify := &ChainstoreStatusNotify{} + if err := json.Unmarshal([]byte(storeNotify.BusinessData), fnNotify); err != nil { + baseapi.SugarLogger.Debugf("FN callback string to GetChainstoreStatusNotify failed with err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + return fnNotify, SuccessResponse +} + +// 获取订单状态回调消息 +func (a *API) GetChainOrderStatusNotify(request *http.Request) (shopStatusMsg *OrderStatusNottify, callbackResponse *CallbackResponse) { + data, err := ioutil.ReadAll(request.Body) + if err != nil { + baseapi.SugarLogger.Debugf("FN GetChainOrderStatusNotify failed with No result msg err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + result := &ShortStatus{} + if err := json.Unmarshal(data, &result); err != nil { + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + orderResult := &OrderStatusNottify{} + if err := json.Unmarshal([]byte(result.BusinessData), orderResult); err != nil { + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + return orderResult, SuccessResponse +} + +// 异常配送 +func (a *API) GetChainAbnormaltatusNotify(request *http.Request) (shopStatusMsg *AbnormalStatusNotify, callbackResponse *CallbackResponse) { + data, err := ioutil.ReadAll(request.Body) + if err != nil { + baseapi.SugarLogger.Debugf("FN GetChainOrderStatusNotify failed with No result msg err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + storeNotify := &ShortStatus{} + if err := json.Unmarshal(data, &storeNotify); err != nil { + baseapi.SugarLogger.Debugf("FN GetShopStatusCallbackMsg failed with err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + fnNotify := &AbnormalStatusNotify{} + if err := json.Unmarshal([]byte(storeNotify.BusinessData), fnNotify); err != nil { + baseapi.SugarLogger.Debugf("FN callback string to ChainstoreStatusNotify failed with err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + return fnNotify, SuccessResponse +} + +func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse *CallbackResponse) { + err := request.ParseForm() + if err != nil { + callbackResponse = Err2CallbackResponse(err, "") + } + return callbackResponse +} diff --git a/platformapi/sfps/order.go b/platformapi/sfps/order.go new file mode 100644 index 00000000..a94dea36 --- /dev/null +++ b/platformapi/sfps/order.go @@ -0,0 +1,274 @@ +package fnpsapi + +// +//import ( +// "encoding/json" +// "errors" +// "git.rosy.net.cn/baseapi/utils" +// "time" +//) +// +//const ( +// OrderCancelReson0 = 0 // 0:其它(必填原因)必须填写原因 +// OrderCancelReson1 = 1 // 1:物流原因:订单长时间未分配骑手 +// OrderCancelReson4 = 4 // 4:商品缺货/无法出货/已售完, +// OrderCancelReson6 = 6 // 6:商户发错单, +// OrderCancelReson7 = 7 // 7:商户/顾客自身定位错误, +// OrderCancelReson8 = 8 // 8:商户改其他第三方配送, +// OrderCancelReson9 = 9 // 9:顾客下错单/临时不想要了, +// OrderCancelReson10 = 10 // 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) +// OrderCancelReson32 = 32 // 32:订单信息填写错误 +// OrderCancelReson36 = 36 // 36:重复下单了 +// OrderCancelReson2 = 2 // 2:物流原因:分配骑手后,骑手长时间未取件(类型已下线) +// OrderCancelReson3 = 3 // 3:物流原因:骑手告知不配送,让取消订单(类型已下线) +// OrderCancelReson5 = 5 // 5:商户联系不上门店/门店关门了(类型已下线) +// +// OrderStatusAcceptCreate = 0 //订单生成 +// OrderStatusAcceptCacle = 4 //订单取消 +// OrderStatusAccept = 1 //系统已接单 +// OrderStatusAssigned = 20 //已分配骑手 +// OrderStatusArrived = 80 //已到店 +// OrderStatusDelivering = 2 //配送中 +// OrderStatusDelivered = 3 //已送达 +// OrderStatusException = 5 //异常 +//) +// +//// 蜂鸟预下单 +//func (a *API) PreCreateByShopFn(basicParams *PreCreateOrder) (deliveryFee, baseDeliveryFee int64, err error) { +// preOrder := a.MakeFnRequestHead() +// bytes, err := json.Marshal(basicParams) +// if err != nil { +// return 0, 0, err +// } +// preOrder["business_data"] = string(bytes) +// result, err := a.AccessAPI(ApiURL, "preCreateOrder", RequestPost, preOrder) +// if err != nil { +// return 0, 0, err +// } +// +// preOrderResult := PreCreateOrderResp{} +// if err := json.Unmarshal([]byte(result["business_data"].(string)), &preOrderResult); err != nil { +// return 0, 0, err +// } +// +// // 返回所有可选的商品列表,每项包含当前使用该商品下单时对应的价格等信息,(其中不可用的商品会返回不可用原因) 可挑选其中一个可用的商品进行正式下单。 +// var deliveryFeeFn int64 +// var baseDeliveryFeeFn int64 +// for _, v := range preOrderResult.GoodsInfos { +// if v.IsValid != 1 { +// continue +// } +// deliveryFeeFn = v.ActualDeliveryAmountCent +// baseDeliveryFeeFn = v.TotalDeliveryAmountCent +// } +// +// // 异常检测 +// if deliveryFeeFn == 0 && baseDeliveryFeeFn == 0 { +// return 0, 0, errors.New(preOrderResult.GoodsInfos[0].DisableReason) +// } +// return deliveryFeeFn, baseDeliveryFeeFn, nil +//} +// +//// 蜂鸟正式下单 +//func (a *API) CreateOrder(createOrderParam *CreateOrderReqParam) (result string, err error) { +// orderHead := a.MakeFnRequestHead() +// bytes, err := json.Marshal(createOrderParam) +// if err != nil { +// return "", err +// } +// orderHead["business_data"] = string(bytes) +// resultData, err := a.AccessAPI(ApiURL, "createOrder", RequestPost, orderHead) +// if orderId, ok := resultData["business_data"]; ok { +// res := &CreateOrderRes{} +// if err := json.Unmarshal([]byte(utils.Interface2String(orderId)), res); err != nil { +// return "", err +// } +// return utils.Int64ToStr(res.OrderId), nil +// } +// return "", err +//} +// +//// addTip 加小费接口 +//func (a *API) AddTip(req *AddTipRes) (err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return errors.New("内部订单号或者外部订单号比填写一个") +// } +// +// orderHead := a.MakeFnRequestHead() +// addTipString, err := json.Marshal(req) +// if err != nil { +// return err +// } +// +// orderHead["businessData"] = string(addTipString) +// if _, err = a.AccessAPI(ApiURL, "addTip", RequestPost, orderHead); err != nil { +// return err +// } +// return nil +//} +// +//// getCancelReasonList 获取可用取消原因列表(暂油前端写死) +//func (a *API) GetCancelReasonList(req *GetOrderDetailReq) (result *GetCancelReasonListRes, err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return nil, errors.New("内部订单号或者外部订单号比填写一个") +// } +// +// a.timestamp = time.Now().Unix() * 1000 +// orderHead := a.MakeFnRequestHead() +// business, err := json.Marshal(req) +// if err != nil { +// return nil, err +// } +// orderHead["businessData"] = string(business) +// data, err := a.AccessAPI(ApiURL, "getCancelReasonList", RequestPost, orderHead) +// if err != nil { +// return nil, err +// } +// +// cancel := make(map[string]*GetCancelReasonListRes, 0) +// if k, ok := data["business_data"]; ok { +// if err := json.Unmarshal([]byte(utils.Interface2String(k)), cancel); err != nil { +// return nil, err +// } +// } +// return cancel["cancel_reason_list"], nil +//} +// +//// preCancelOrder 预取消订单接口,获取取消订单需要扣除的金额 +//func (a *API) PreCancelOrder(req *PreCancelOrderReq) (actualCancelCostCent int64, err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return 0, errors.New("内部订单号或者外部订单号比填写一个") +// } +// orderHead := a.MakeFnRequestHead() +// business, err := json.Marshal(req) +// if err != nil { +// return 0, err +// } +// orderHead["businessData"] = string(business) +// +// data, err := a.AccessAPI(ApiURL, "preCancelOrder", RequestPost, orderHead) +// if err != nil { +// return 0, err +// } +// +// if k, ok := data["businessData"]; ok { +// return utils.MustInterface2Int64(k), nil +// } +// return +//} +// +//// cancelOrder 取消订单 +//func (a *API) CancelOrder(req *CancelOrderReq) (err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return errors.New("内部订单号或者外部订单号比填写一个") +// } +// if req.OrderCancelCode == 0 && req.OrderCancelOtherReason == "" { +// req.OrderCancelOtherReason = "暂时不想要了" +// } +// +// orderHead := a.MakeFnRequestHead() +// business, err := json.Marshal(req) +// if err != nil { +// return err +// } +// orderHead["business_data"] = string(business) +// +// if _, err := a.AccessAPI(ApiURL, "cancelOrder", RequestPost, orderHead); err != nil { +// return err +// } +// +// return +//} +// +//// 查询订单详情接口 +//func (a *API) QueryOrder(partnerOrderCode string) (result *GetOrderDetailRes, err error) { +// orderHead := a.MakeFnRequestHead() +// bytes, err := json.Marshal(&GetOrderDetailReq{PartnerOrderCode: partnerOrderCode}) +// if err != nil { +// return nil, err +// } +// +// orderHead["business_data"] = string(bytes) +// data, err := a.AccessAPI(ApiURL, "getOrderDetail", RequestPost, orderHead) +// if err != nil { +// return nil, err +// } +// +// fnResult := &GetOrderDetailRes{} +// if err := json.Unmarshal([]byte(data["business_data"].(string)), &fnResult); err != nil { +// return nil, err +// } +// return fnResult, nil +//} +// +//// 查询骑手信息 +//func (a *API) GetKnightInfo(req *GetOrderDetailReq) (result *GetKnightInfoRes, err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return nil, errors.New("内部订单号或者外部订单号比填写一个") +// } +// +// orderHead := a.MakeFnRequestHead() +// bytes, err := json.Marshal(req) +// if err != nil { +// return nil, err +// } +// orderHead["business_data"] = string(bytes) +// +// data, err := a.AccessAPI(ApiURL, "getKnightInfo", RequestPost, orderHead) +// if err != nil { +// return nil, err +// } +// +// fnResult := &GetKnightInfoRes{} +// if err := json.Unmarshal([]byte(utils.Interface2String(data["business_data"])), &fnResult); err != nil { +// return nil, err +// } +// return fnResult, nil +//} +// +//// 余额查询 +//func (a *API) GetAmount() (outAmount map[string]interface{}, err error) { +// orderHead := a.MakeFnRequestHead() +// result, err := a.AccessAPI(ApiURL, "getAmount", RequestPost, orderHead) +// if err != nil { +// return nil, err +// } +// return result, err +//} +// +////order_cancel_reason_code 订单取消原因代码(1:用户取消,2:商家取消) +//// order_cancel_code 订单取消编码( +//// 1:物流原因:订单长时间未分配骑手, +//// 2:物流原因:分配骑手后,骑手长时间未取件 , +//// 3:物流原因:骑手告知不配送,让取消订单, +//// 4:商品缺货/无法出货/已售完, 5:商户联系不上门店/门店关门了, 6:商户发错单, +//// 7:商户/顾客自身定位错误, 8:商户改其他第三方配送, 9:顾客下错单/临时不想要了, +//// 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) +//func (a *API) ComplaintOrder(req *ComplaintOrderReq) (err error) { +// if req.PartnerOrderCode == "" && req.OrderId == "" { +// return errors.New("内部订单号或者外部订单号比填写一个") +// } +// orderHead := a.MakeFnRequestHead() +// bytes, err := json.Marshal(req) +// if err != nil { +// return err +// } +// orderHead["businessData"] = string(bytes) +// _, err = a.AccessAPI(ApiURL, "cancelOrder", RequestPost, orderHead) +// return err +//} +// +//func (a *API) ComplaintRider(partnerOrderCode string, orderComplaintCode int) (err error) { +// orderHead := a.MakeFnRequestHead() +// +// bytes, err := json.Marshal(map[string]interface{}{ +// "partner_order_code": partnerOrderCode, +// "order_complaint_code": orderComplaintCode, +// }) +// if err != nil { +// return err +// } +// orderHead["businessData"] = string(bytes) +// _, err = a.AccessAPI(ApiURL, "cancelOrder", RequestPost, orderHead) +// return err +//} diff --git a/platformapi/sfps/sf_base.go b/platformapi/sfps/sf_base.go new file mode 100644 index 00000000..bf5bab69 --- /dev/null +++ b/platformapi/sfps/sf_base.go @@ -0,0 +1,159 @@ +package fnpsapi + +import ( + "git.rosy.net.cn/baseapi/platformapi" + "net/http" + "sync" +) + +const ( + ApiURL = "https://openic.sf-express.com" // 正式环境 + RequestPost = "POST" +) + +// 物品类型product_type枚举值: +const ( + FastFood = 1 // 快餐 + Drugs = 2 // 药品 + DepartmentStore = 3 // 百货 + OldClothes = 4 // 脏衣服收 + NewClothes = 5 // 干净衣服派 + Fresh = 6 // 生鲜 + HighDrinks = 8 // 高端饮品 + SiteInspection = 9 // 现场勘验 + Express = 10 // 快递 + File = 12 // 文件 + Cake = 13 // 蛋糕 + Flower = 14 // 鲜花 + Digital = 15 // 数码 + Clothing = 16 // 服装 + Car = 17 // 汽配 + Jewellery = 18 // 珠宝 + Pizza = 20 // 披萨 + ChineseFood = 21 // 中餐 + FreshwaterFresh = 22 // 水产 + DirectDelivery = 27 // 专人直送 + MidRangeDrinks = 32 // 中端饮品 + ConvenienceStore = 33 // 便利店 + Bakeries = 34 // 面包糕点 + HotPot = 35 // 火锅 + licence = 36 // 证照 + Crayfish = 40 // 烧烤小龙虾 + OtherInfo = 41 // 外部落地配 + AlcoholAndTobacco = 47 // 烟酒行 + AdultEroticaProducts = 48 // 成人用品 + Other = 99 // 其他 +) + +// 注册请求api +type API struct { + devId string `json:"dev_id"` + devKey string `json:"dev_key"` + signature string `json:"sign"` + pushTime int64 `json:"push_time"` + locker sync.RWMutex + client *http.Client + config *platformapi.APIConfig +} + +/************************************************订单*****************************************************/ +//#region 获取蜂鸟门店信息 + +// 创建订单 +type CreateOrder struct { + // 必填 + DevId int64 `json:"dev_id"` // 同城开发者ID + ShopId string `json:"shop_id"` // 店铺ID + ShopOrderId string `json:"shop_order_id"` // 商家订单号不允许重复 + OrderSource string `json:"order_source"` // 订单接入来源 1:美团;2:饿了么;3:百度;4:口碑;其他请直接填写中文字符串值 + LbsType int `json:"lbs_type"` // 坐标类型 1:百度坐标,2:高德坐标 + PayType int64 `json:"pay_type"` // 用户支付方式 1:已付款 0:货到付款 + OrderTime int64 `json:"order_time"` // 用户下单时间 秒级时间戳 + IsAppoint int `json:"is_appoint"` // 是否是预约单 0:非预约单;1:预约单 + IsInsured int64 `json:"is_insured"` // 是否保价,0:非保价;1:保价 + IsPriorityAssign int64 `json:"is_priority_assign"` // 是否优先派单,0:否 1:是 + IsPersonDirect int64 `json:"is_person_direct"` // 是否是专人直送订单,0:否;1:是 + PushTime int64 `json:"push_time"` // 推单时间 秒级时间戳 + Version int64 `json:"version"` // 版本号 参照文档主版本号填写 如:文档版本号1.7,version=17 + Receive *ReceiveAddress `json:"receive"` // 收货人信息 + Shop *SfShopInfo `json:"shop"` // 发货店铺信息 Obj,详见shop结构, 平台级开发者(如饿了么)需传入如无特殊说明此字段可忽略 + OrderDetail *OrderDetails `json:"order_detail"` // 订单详情 + MultiPickupInfo []*MultiPickupDetails `json:"multi_pickup_info"` // 多点取货信息 + // 非必填 + ShopType int64 `json:"shop_type"` // 店铺ID类型 1:顺丰店铺ID ;2:接入方店铺ID + ShopPreparationTime int64 `json:"shop_preparation_time"` // 商家预计备餐时长(分10) + OrderSequence string `json:"order_sequence"` // 取货序号 与order_source配合使用 如:饿了么10号单,表示如下:order_source=2;order_sequence=10。用于骑士快速寻找配送物 + AppointType int `json:"appoint_type"` // 预约单类型 预约单的时候传入,1:预约单送达单;2:预约单上门单 + ExpectTime int64 `json:"expect_time"` // 用户期望送达时间 若传入自此段且时间大于配送时效,则按照预约送达单处理,时间小于配送时效按照立即单处理;appoint_type=1时需必传,秒级时间戳; + ExpectPickupTime int64 `json:"expect_pickup_time"` // 用户期望上门时间 appoint_type=2时需必传,秒级时间戳 + ShopExpectTime int64 `json:"shop_expect_time"` // 商家期望送达时间 只展示给骑士,不参与时效考核;秒级时间戳 + Vehicle int `json:"vehicle"` // 配送交通工具,0:否;1:电动车;2:小轿车 + DeclaredValue int64 `json:"declared_value"` // 保价金额(单位:分) + GratuityFee int64 `json:"gratuity_fee"` // 订单小费,不传或者传0为不加小费 单位分,加小费最低不能少于100分 + Remark string `json:"remark"` // 订单备注 + RiderPickMethod int64 `json:"rider_pick_method"` // 物流流向 1:从门店取件送至用户; 2:从用户取件送至门店 + ReturnFlag int `json:"return_flag"` // 返回字段控制标志位(二进制) 1:商品总价格,2:配送距离,4:物品重量,8:起送时间,16:期望送达时间,32:支付费用,64:实际支付金额,128:优惠券总金额,256:结算方式 例如全部返回为填入511 + +} + +// 发货店铺信息,必填 +type SfShopInfo struct { + ShopName string `json:"shop_name"` // 店铺名称 + ShopPhone string `json:"shop_phone"` // 店铺电话 + ShopAddress string `json:"shop_address"` // 店铺地址 + ShopLng string `json:"shop_lng"` // 店铺经度 + ShopLat string `json:"shop_lat"` // 店铺纬度 +} + +// 收货人信息 +type ReceiveAddress struct { + // 必填 + UserName string `json:"user_name"` // 用户姓名 + UserPhone string `json:"user_phone"` // 用户电话 + UserAddress string `json:"user_address"` // 用户地址 + UserLng string `json:"user_lng"` // 用户经度 + UserLat string `json:"user_lat"` // 用户纬度 + // 非必填 + CityName string `json:"city_name"` // 发单城市 +} + +// 订单详情 +type OrderDetails struct { + // 必填 + TotalPrice int64 `json:"total_price"` // 订单金额 分 + ProductType int64 `json:"product_type"` // 物品类型 枚举值见下面定义 + WeightGram int64 `json:"weight_gram"` // 物品重量(单位:克) + ProductNum int64 `json:"product_num"` // 物品个数 + ProductTypeNum int64 `json:"product_type_num"` // 物品种类个数 + ProductDetail []*ProductDetail `json:"product_detail"` // 物品详情;数组结构,详见product_detail结构 + // 非必填 + UserMoney int64 `json:"user_money"` // 用户实付商家金额(单位:分) + ShopMoney int `json:"shop_money"` // 商家实收用户金额(单位:分) + VolumeLitre int64 `json:"volume_litre"` // 物品体积(单位:升) + DeliveryMoney int64 `json:"delivery_money"` // 商家收取用户的配送费(单位:分) +} + +// 购买物品详情清单 +type ProductDetail struct { + // 必填 + ProductName string `json:"product_name"` // 物品名称 + ProductNum int64 `json:"product_num"` // 物品数量 + // 非必填 + ProductId int64 `json:"product_id"` // 物品ID + ProductPrice int64 `json:"product_price"` // 物品价格 + ProductUnit string `json:"product_unit"` // 物品单位 + ProductRemark string `json:"product_remark"` // 备注 + ItemDetail string `json:"item_detail"` // 详情 +} + +// 取货地址详情 +type MultiPickupDetails struct { + PickupShopAddress string `json:"pickup_shop_address"` // 取货点地址 + PickupShopPhone string `json:"pickup_shop_phone"` // 取货点店铺手机号 + PickupShopName string `json:"pickup_shop_name"` // 取货点店铺名称 + PickupLng string `json:"pickup_lng"` // 取货点经度 + PickupLat string `json:"pickup_lat"` // 取货点纬度 + PickupProducts string `json:"pickup_products"` // 取货点店铺物品信息 +} + +//#endregion diff --git a/platformapi/sfps/sf_test.go b/platformapi/sfps/sf_test.go new file mode 100644 index 00000000..99a9d32d --- /dev/null +++ b/platformapi/sfps/sf_test.go @@ -0,0 +1,208 @@ +package fnpsapi + +// +//import ( +// "encoding/json" +// "fmt" +// "git.rosy.net.cn/baseapi" +// "go.uber.org/zap" +// "testing" +//) +// +//var ( +// api *API +// sugarLogger *zap.SugaredLogger +//) +// +//func init() { +// logger, _ := zap.NewDevelopment() +// sugarLogger = logger.Sugar() +// baseapi.Init(sugarLogger) +// +// //api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") +// //token, _ := api.GetAccessToken() +// //api.accessToken = token.BusinessDataObj.AccessToken +//} +// +//// 查询单个门店 +//func TestQueryOneStore(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "") +// token, err := api.GetAccessToken() +// api.accessToken = token.BusinessDataObj.AccessToken +// data, err := api.GetStore("209476483") +// fmt.Println(data) +// fmt.Println(err) +//} +// +//// 正式下单 +//func TestCreateOrder(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "") +// token, err := api.GetAccessToken() +// fmt.Println("22222222222", err) +// api.accessToken = token.BusinessDataObj.AccessToken +// +// aa := []*GoodsItemsList{ +// {"白菜", 1, 100, 100, "30011", 1, "备注:大白菜"}, +// //{"白菜2", 1, 100, 100, "30012", 1, "备注:大白菜2"}, +// // {"白菜3", 1, 100, 100, "1004", 1, "备注:大白菜2"}, +// } +// _, err = api.CreateOrder(&CreateOrderReqParam{ +// PartnerOrderCode: "232232992229uue21", +// OrderType: 1, +// PositionSource: 3, +// ReceiverAddress: "四川成都", +// ReceiverLongitude: 116.307892, +// ReceiverLatitude: 40.039115, +// GoodsTotalAmountCent: 100, +// GoodsActualAmountCent: 100, +// GoodsWeight: 0.25, +// GoodsCount: 1, +// GoodsItemList: aa, +// ReceiverName: "刘磊", +// ReceiverPrimaryPhone: "18981810340", +// OutShopCode: "", +// ChainStoreId: "209476483", +// }) +// fmt.Println(err) +//} +// +//// 创建门店, +//func TestCreateStore(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") +// token, _ := api.GetAccessToken() +// api.accessToken = token.BusinessDataObj.AccessToken +// err, _ := api.CreateStore(&CreateStoreBaseInfo{ +// HeadShopName: "刘磊测试门店", +// ContactPhone: "18981810340", +// Address: "四川成都", +// Longitude: 104.094555, +// Latitude: 30.661382, +// PositionSource: 3, +// OutShopCode: "637910", +// CategoryID: "12", +// OwnerName: "刘磊", +// OwnerIDNum: "511324199308263974", +// HandheldLicencePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// OwnerIDPicFrontHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// OwnerIDPicBackHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// CreditCode: "12345", +// BusinessLicencePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// +// BranchShopName: "", +// ChainstoreType: 2, +// SettlementModel: "1", +// SettlementAccountID: "", +// FoodLicensePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// SecondMedicalEquipmentLicensePicHash: "", +// MedicalInstitutionLicensePicHash: "", +// MedicalEquipmentLicensePicHash: "", +// MedicineLicensePicHash: "", +// TabacooLicensePicHash: "", +// }) +// fmt.Println(err) +//} +// +//func TestUpdataStore(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") +// token, _ := api.GetAccessToken() +// api.accessToken = token.BusinessDataObj.AccessToken +// err := api.UpdateStore(&UpdateStoreParam{ +// ChainStoreID: 209636747, +// HeadShopName: "刘磊测试门店", +// ContactPhone: "18981810340", +// Address: "四川成都", +// Longitude: 104.094555, +// Latitude: 30.661382, +// PositionSource: 3, +// OutShopCode: "637910", +// CategoryID: "12", +// OwnerName: "刘磊", +// OwnerIDNum: "511324199308263974", +// HandheldLicencePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// OwnerIDPicFrontHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// OwnerIDPicBackHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// CreditCode: "12345", +// BusinessLicencePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// +// BranchShopName: "", +// ChainstoreType: 2, +// FoodLicensePicHash: "d7c64022f6458f9aa76968e01f5686c5.jpeg", +// SecondMedicalEquipmentLicensePicHash: "", +// MedicalInstitutionLicensePicHash: "", +// MedicalEquipmentLicensePicHash: "", +// MedicineLicensePicHash: "", +// TabacooLicensePicHash: "", +// }) +// fmt.Println(err) +//} +// +//func TestClient(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "51658", "4W4hqacKND6NOct5gCyjbT") +// token, err := api.GetAccessToken() +// api.accessToken = token.BusinessDataObj.AccessToken +// fmt.Println("token===", token.BusinessDataObj.AccessToken) +// fmt.Println("err=====", err) +//} +// +//// 预下单 +//func TestGetOrder(t *testing.T) { +// api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "") +// token, _ := api.GetAccessToken() +// api.accessToken = token.BusinessDataObj.AccessToken +// +// aa := []*GoodsItemsList{ +// {"白菜", 1, 100, 100, "30011", 1, "备注:大白菜"}, +// {"白菜2", 1, 100, 100, "30012", 1, "备注:大白菜2"}, +// // {"白菜3", 1, 100, 100, "1004", 1, "备注:大白菜2"}, +// } +// deliveryFee, baseDeliveryFee, err := api.PreCreateByShopFn(&PreCreateOrder{ +// PartnerOrderCode: "817102016000041", +// OrderType: 1, +// PositionSource: 3, +// ReceiverAddress: "四川成都", +// ReceiverLongitude: 104.093445, +// ReceiverLatitude: 30.661585, +// GoodsTotalAmountCent: 100, +// GoodsActualAmountCent: 100, +// GoodsWeight: 0.25, +// GoodsCount: 1, +// GoodsItemList: aa, +// //ServiceGoodsId: nil, +// //BaseGoodsId: nil, +// //OutShopCode: "637910", +// ChainStoreID: "209476483", +// }) +// fmt.Println(err) +// fmt.Println(deliveryFee) +// fmt.Println(baseDeliveryFee) +//} +// +//func TestDataUnmas(t *testing.T) { +// data := `{"signature":"5a4270c8fb3c4a3a641d2f5f66990c503c7248ecd51248b020e0b44db75a6761","app_id":"6705486294797503379","timestamp":"1648861445686","business_data":"{\"callback_business_type\":\"orderStatusNotify\",\"param\":{\"app_id\":\"6705486294797503379\",\"carrier_driver_name\":\"\",\"carrier_driver_phone\":\"\",\"order_id\":300000388333833530,\"order_status\":1,\"partner_order_code\":\"88386523916938\",\"push_time\":1648861445686,\"transfer\":0}}"}` +// fnNotify := &ShortStatus{} +// if err := json.Unmarshal([]byte(data), fnNotify); err != nil { +// baseapi.SugarLogger.Debugf("FN callback string to ChainstoreStatusNotify failed with err:%v", err) +// } +// +// fmt.Println("data", fnNotify.BusinessData) +// fmt.Println("data", fnNotify.AppId) +// fmt.Println("data", fnNotify.Signature) +// +// retsult := &OrderStatusNottify{} +// err := json.Unmarshal([]byte(fnNotify.BusinessData), retsult) +// fmt.Println("err1======", err) +// fmt.Println("retsult", retsult) +// fmt.Println("retsult", retsult.Param) +// fmt.Println("retsult", retsult.CallbackBusinessType) +// fmt.Println("retsult", retsult.Param.PartnerOrderCode) +//} +// +//func TestDaDa(t *testing.T) { +// fmt.Println("美团", 1&1) +// fmt.Println("达达", 2&1) +// fmt.Println("蜂鸟", 4&1) +// +// fmt.Println("美团", 1&0) +// fmt.Println("达达", 2&0) +// fmt.Println("蜂鸟", 4&0) +//} diff --git a/platformapi/sfps/sfpsapi.go b/platformapi/sfps/sfpsapi.go new file mode 100644 index 00000000..9d1d43da --- /dev/null +++ b/platformapi/sfps/sfpsapi.go @@ -0,0 +1,141 @@ +package fnpsapi + +import ( + "crypto/md5" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +//func (a *API) SetToken(token string) { +// a.locker.Lock() +// defer a.locker.Unlock() +// a.accessToken = token +//} +// +//func (a *API) SetRefreshToken(token string) { +// a.locker.Lock() +// defer a.locker.Unlock() +// a.refreshToken = token +//} +// +//func (a *API) MakeFnRequestHead() map[string]interface{} { +// requestParam := make(map[string]interface{}, 6) +// requestParam["access_token"] = a.accessToken +// requestParam["signature"] = a.signature +// requestParam["merchant_id"] = a.merchantId +// requestParam["version"] = a.version +// requestParam["app_id"] = a.appID +// requestParam["timestamp"] = a.timestamp +// return requestParam +//} +func New(devId, devKey string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + + // 查询蜂鸟refeshToken + return &API{ + devId: devId, + devKey: devKey, + locker: sync.RWMutex{}, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) signParam(params []byte) (sig string) { + sign := fmt.Sprintf("%s&%s&%s", string(params), a.devId, a.devKey) + md2sign := md5.Sum([]byte(sign)) + return base64.StdEncoding.EncodeToString(md2sign[:]) +} + +func (a *API) AccessAPI(baseUrl, actionApi, method string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { + bizParams["push_time"] = utils.Int64ToStr(time.Now().Unix()) + signParam, err := json.Marshal(bizParams) + if err != nil { + return nil, err + } + + //bizParams["sign"] = a.signParam(signParam) + //data, err := json.Marshal(bizParams) + //if err != nil { + // return nil, err + //} + + // 全路径请求参数 + fullURL := utils.GenerateGetURL(baseUrl, actionApi, map[string]interface{}{"sign": a.signParam(signParam)}) + + // 发送请求 + sendUrl := func() *http.Request { + var request *http.Request + request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(signParam))) + request.Header.Set("Content-Type", "application/json") + return request + } + + // 数据解析 + dataMarshal := func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + if err != nil { + return "", err + } + if utils.MustInterface2Int64(jsonResult1["code"]) != 200 { + errLevel = platformapi.ErrLevelGeneralFail + err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"]))) + baseapi.SugarLogger.Debugf("sfps AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) + } + retVal = jsonResult1 + return errLevel, err + } + + err = platformapi.AccessPlatformAPIWithRetry(a.client, sendUrl, a.config, dataMarshal) + return retVal, err +} + +// +//// 获取access_token +//func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) { +// parameter := make(map[string]interface{}, 6) +// parameter["grant_type"] = "refresh_token" +// parameter["app_id"] = a.appID +// parameter["merchant_id"] = a.merchantId +// +// // 先去刷新token,没有的话再去获取token(code只能使用一次,生成的token管一年) +// var result map[string]interface{} +// switch { +// case a.accessToken != "" && a.refreshToken != "": +// parameter["refresh_token"] = a.refreshToken +// result, err = a.AccessAPI(RefreshTokenUrl, "", RequestPost, parameter) +// case a.accessToken == "" && a.refreshToken == "": +// parameter["grant_type"] = "authorization_code" +// parameter["code"] = a.code +// result, err = a.AccessAPI(TokenURL, "", RequestPost, parameter) +// default: +// return nil, errors.New("更换code,请在配置表中删除原蜂鸟token") +// } +// if err != nil { +// return nil, err +// } +// if err := utils.Map2StructByJson(result, &tokenInfo, false); err != nil { +// return nil, err +// } +// +// businessData := &BusinessData{} +// if err := json.Unmarshal([]byte(utils.Interface2String(result["business_data"])), businessData); err != nil { +// return nil, err +// } +// tokenInfo.BusinessDataObj = businessData +// return tokenInfo, err +//} diff --git a/platformapi/sfps/store.go b/platformapi/sfps/store.go new file mode 100644 index 00000000..e94ea406 --- /dev/null +++ b/platformapi/sfps/store.go @@ -0,0 +1,119 @@ +package fnpsapi + +// +//import ( +// "encoding/json" +// "fmt" +// "strings" +// +// "git.rosy.net.cn/baseapi/utils" +//) +// +//const ( +// StoreNotExist = "门店信息不存在" +// StoreExist = "该门店已存在" +//) +// +//const ( +// // 创建状态 +// ShopCreateStatusAuditRejected = 10 // 上架审核中 +// ShopCreateStatusAuditPassed = 20 // 正常(已上架) +// ShopCreateStatusAuditCreated = 30 // 上架审核失败 +// ShopCreateStatusAuditOnline = 40 // 已冻结 +// ShopCreateStatusAuditUpdateRejected = 50 // 已下架 +// // 修改状态 +// ShopUpdateStatusAuditRejected = 0 // 无修改 +// ShopUpdateStatusAuditPassed = 10 // 资料修改审核中 +// ShopUpdateStatusAuditCreated = 20 // 审核通过 +// ShopUpdateStatusAuditOnline = 30 // 审核驳回 +//) +// +//func (a *API) CreateStore(createStoreParam *CreateStoreBaseInfo) (result1 string, err error) { +// requestHead := a.MakeFnRequestHead() +// storeByte, err := json.Marshal(createStoreParam) +// if err != nil { +// return "", err +// } +// requestHead["business_data"] = string(storeByte) +// result, err := a.AccessAPI(ApiURL, "chainstoreCreate", RequestPost, requestHead) +// if err != nil { +// return "", err +// } +// +// createShop := struct { +// ChainStoreId string `json:"chain_store_id"` +// }{} +// if err := json.Unmarshal([]byte(utils.Interface2String(result["business_data"])), &createShop); err != nil { +// return "", err +// } +// return createShop.ChainStoreId, nil +//} +// +//func (a *API) UpdateStore(updateStore *UpdateStoreParam) (err error) { +// requestHead := a.MakeFnRequestHead() +// storeByte, err := json.Marshal(updateStore) +// if err != nil { +// return err +// } +// requestHead["business_data"] = string(storeByte) +// _, err = a.AccessAPI(ApiURL, "chainstoreUpdate", RequestPost, requestHead) +// return +//} +// +//func (a *API) GetStore(storeID string) (getStoreResult *GetOneStoreRespData, err error) { +// params := GetOneStoreParam{ +// BaseInfo: BaseInfo{ +// AccessToken: a.accessToken, +// Signature: a.signature, +// MerchantID: a.merchantId, +// Version: a.version, +// AppID: a.appID, +// Timestamp: a.timestamp, +// }, +// BusinessData: "", +// } +// // 序列化请求参数 +// data, err := json.Marshal(GetOneStore{MerchantID: a.merchantId, OutShopCode: storeID}) +// if err != nil { +// return nil, err +// } +// params.BusinessData = string(data) +// paramsMap := utils.Struct2FlatMap(params) +// +// result, err := a.AccessAPI(ApiURL, "chainstoreQuery", RequestPost, paramsMap) +// if err != nil { +// return nil, err +// } +// if result["code"].(string) != "200" { +// return nil, fmt.Errorf("%s", result["msg"]) +// } +// +// fnResult := &GetOneStoreRespData{} +// if storeData, ok := result["business_data"]; ok { +// if err := json.Unmarshal([]byte(utils.Interface2String(storeData)), fnResult); err != nil { +// return nil, err +// } +// } else { +// return nil, fmt.Errorf("暂无数据") +// } +// +// return fnResult, err +//} +// +//func IsErrShopNotExist(err error) bool { +// if err != nil { +// if strings.Contains(err.Error(), StoreNotExist) { +// return true +// } +// } +// return false +//} +// +//func IsErrShopExist(err error) bool { +// if err != nil { +// if strings.Contains(err.Error(), StoreExist) { +// return true +// } +// } +// return false +//} diff --git a/platformapi/tiktok/tiktok.go b/platformapi/tiktok/tiktok.go new file mode 100644 index 00000000..f0d10e75 --- /dev/null +++ b/platformapi/tiktok/tiktok.go @@ -0,0 +1,73 @@ +package tiktok + +import ( + "encoding/json" + "fmt" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" + "net/http" + "strings" +) + +func (a *API) GetAppID() string { + return a.clientKey +} + +func (a *API) GetSecret() string { + return a.clientSecret +} + +func New(clientSecret, clientKey string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ + clientSecret: clientSecret, + clientKey: clientKey, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) AccessAPI(baseUrl, actionApi, method string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { + // 序列化 + data, err := json.Marshal(bizParams) + if err != nil { + return nil, err + } + + // 全路径请求参数 + fullURL := utils.GenerateGetURL(baseUrl, actionApi, nil) + + // 发送请求 + sendUrl := func() *http.Request { + var request *http.Request + if http.MethodPost == method { + request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data))) + } else { + request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(baseUrl, actionApi, bizParams), nil) + } + switch actionApi { + case TiktokTokenUrl: + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + case TiktokRefreshTokenUrl: + request.Header.Set("Content-Type", "multipart/form-data") + } + + return request + } + + // 数据解析 + dataMarshal := func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + + retVal = jsonResult1 + return errLevel, err + } + + err = platformapi.AccessPlatformAPIWithRetry(a.client, sendUrl, a.config, dataMarshal) + return retVal, err +} diff --git a/platformapi/tiktok/tiktok_const.go b/platformapi/tiktok/tiktok_const.go new file mode 100644 index 00000000..1f1085e8 --- /dev/null +++ b/platformapi/tiktok/tiktok_const.go @@ -0,0 +1,24 @@ +package tiktok + +import ( + "git.rosy.net.cn/baseapi/platformapi" + "net/http" + "sync" +) + +const ( + BaseURL = "https://open.douyin.com" + TiktokTokenUrl = "/oauth/access_token/" + TiktokRefreshTokenUrl = "/oauth/renew_refresh_token/" +) + +type API struct { + clientSecret string // 应用唯一标识对应的密钥 + clientKey string // 应用唯一标识 + client *http.Client + config *platformapi.APIConfig + locker sync.RWMutex + + msgToken string + msgKey string +} diff --git a/platformapi/tiktok/tiktok_token.go b/platformapi/tiktok/tiktok_token.go new file mode 100644 index 00000000..a576f2d1 --- /dev/null +++ b/platformapi/tiktok/tiktok_token.go @@ -0,0 +1,81 @@ +package tiktok + +import ( + "git.rosy.net.cn/baseapi/utils" + "net/http" +) + +// OauthAccessTokenResData access_token +type OauthAccessTokenResData struct { + AccessToken string `json:"access_token"` // 接口调用凭证 + UnionId string `json:"union_id"` // 当且仅当该网站应用已获得该用户的userinfo授权时,才会出现该字段。 + Scope string `json:"scope"` // 用户授权的作用域(Scope),使用逗号(,)分隔,开放平台几乎几乎每个接口都需要特定的Scope。 + ExpiresIn uint64 `json:"expires_in"` // access_token接口调用凭证超时时间,单位(秒) + OpenId string `json:"open_id"` // 授权用户唯一标识 + RefreshToken string `json:"refresh_token"` // 用户刷新access_token + DYError +} + +// DYError 错误结构体 +type DYError struct { + ErrorCode int64 `json:"error_code,omitempty"` // 错误码 + Description string `json:"description,omitempty"` // 错误码描述 +} + +// OauthAccessTokenRes access_token +type OauthAccessTokenRes struct { + Data OauthAccessTokenResData `json:"data"` + Message string `json:"message"` +} + +// 获取抖音token +func (a *API) GetTiktokToken(code string) (*OauthAccessTokenRes, error) { + tokenReq := make(map[string]interface{}, 4) + tokenReq["client_secret"] = a.clientSecret + tokenReq["code"] = code + tokenReq["grant_type"] = "authorization_code" + tokenReq["client_key"] = a.clientKey + result, err := a.AccessAPI(BaseURL, TiktokTokenUrl, http.MethodPost, tokenReq) + if err != nil { + return nil, err + } + + oauthAccessToken := &OauthAccessTokenRes{} + if err := utils.Map2StructByJson(result, oauthAccessToken, false); err != nil { + return nil, err + } + return oauthAccessToken, nil +} + +// OauthRefreshTokenResData 刷新access_token +type OauthRefreshTokenResData struct { + AccessToken string `json:"access_token"` // 接口调用凭证 + Scope string `json:"scope"` // 用户授权的作用域 + ExpiresIn uint64 `json:"expires_in"` // 过期时间,单位(秒) + OpenId string `json:"open_id"` // 当前应用下,授权用户唯一标识 + RefreshToken string `json:"refresh_token"` // 用户刷新 + DYError +} + +// OauthRefreshTokenRes 刷新access_token +type OauthRefreshTokenRes struct { + Data OauthRefreshTokenResData `json:"data"` + Message string `json:"message"` +} + +// 刷新用户Refresh token +func (a *API) RefreshToken(refreshToken string) (*OauthRefreshTokenRes, error) { + tokenReq := make(map[string]interface{}, 2) + tokenReq["client_key"] = a.clientKey + tokenReq["refresh_token"] = refreshToken + // 通过旧的refresh_token获取新的refresh_token,调用后旧refresh_token会失效,新refresh_token有30天有效期。最多只能获取5次新的refresh_token,5次过后需要用户重新授权。 + result, err := a.AccessAPI(BaseURL, TiktokRefreshTokenUrl, http.MethodPost, tokenReq) + if err != nil { + return nil, err + } + oauthRefreshToken := &OauthRefreshTokenRes{} + if err := utils.Map2StructByJson(result, oauthRefreshToken, false); err != nil { + return nil, err + } + return oauthRefreshToken, nil +}