From c112e5e282c700400611650742f45757885355bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E5=AE=97=E6=A5=A0?= Date: Fri, 25 Mar 2022 09:33:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=9C=82=E9=B8=9F=E9=85=8D?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- platformapi/fnpsapi/callback.go | 154 +++++- platformapi/fnpsapi/fnps_v3.go | 451 +++++++++++++++++ platformapi/fnpsapi/fnpsapi.go | 182 ++++--- platformapi/fnpsapi/order.go | 464 +++++++++++++----- platformapi/fnpsapi/store.go | 108 ++-- platformapi/fnpsapi_old/callback.go | 54 ++ platformapi/fnpsapi_old/fnpsapi.go | 150 ++++++ .../{fnpsapi => fnpsapi_old}/fnpsapi_test.go | 0 .../{fnpsapi => fnpsapi_old}/fnpsapiv3.go | 0 .../fnpsapiv3_test.go | 0 platformapi/fnpsapi_old/order.go | 175 +++++++ .../{fnpsapi => fnpsapi_old}/order_test.go | 0 platformapi/fnpsapi_old/store.go | 84 ++++ .../{fnpsapi => fnpsapi_old}/store_test.go | 0 platformapi/fnpsapi_v3/fnClient_test.go | 102 ++++ platformapi/fnpsapi_v3/fnps_v3.go | 151 +++--- platformapi/fnpsapi_v3/fnpsapi.go | 72 ++- platformapi/fnpsapi_v3/order.go | 304 ++++++++---- platformapi/fnpsapi_v3/order_test.go | 98 ++++ platformapi/fnpsapi_v3/store.go | 66 +-- platformapi/mtpsapi/mtpsapi.go | 2 +- 21 files changed, 2117 insertions(+), 500 deletions(-) create mode 100644 platformapi/fnpsapi/fnps_v3.go create mode 100644 platformapi/fnpsapi_old/callback.go create mode 100644 platformapi/fnpsapi_old/fnpsapi.go rename platformapi/{fnpsapi => fnpsapi_old}/fnpsapi_test.go (100%) rename platformapi/{fnpsapi => fnpsapi_old}/fnpsapiv3.go (100%) rename platformapi/{fnpsapi => fnpsapi_old}/fnpsapiv3_test.go (100%) create mode 100644 platformapi/fnpsapi_old/order.go rename platformapi/{fnpsapi => fnpsapi_old}/order_test.go (100%) create mode 100644 platformapi/fnpsapi_old/store.go rename platformapi/{fnpsapi => fnpsapi_old}/store_test.go (100%) create mode 100644 platformapi/fnpsapi_v3/fnClient_test.go create mode 100644 platformapi/fnpsapi_v3/order_test.go diff --git a/platformapi/fnpsapi/callback.go b/platformapi/fnpsapi/callback.go index 173887f4..efe5c3a5 100644 --- a/platformapi/fnpsapi/callback.go +++ b/platformapi/fnpsapi/callback.go @@ -1,13 +1,163 @@ package fnpsapi import ( - "net/url" - + "encoding/json" + "errors" + "git.rosy.net.cn/baseapi" "git.rosy.net.cn/jx-callback/globals" + "net/http" + "net/url" "git.rosy.net.cn/baseapi/utils" ) +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"` +} + +// 门店状态回调 +type ChainstoreStatusNotify struct { + CallbackBusinessType string `json:"callback_business_type"` + Param string `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 "` // 门店认证、修改等驳回时返回原因 +} + +// 订单状态回调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非转单 +} + +// 出餐回调 +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"` // 状态推送时间 (毫秒) +} + +// 获取门店状态回调消息 +func (a *API) GetChainstoreStatusNotify(request *http.Request) (shopStatusMsg map[string]interface{}, callbackResponse *CallbackResponse) { + storeNotify := ShortStatus{} + err := utils.Map2StructByJson(utils.URLValues2Map(request.PostForm), &storeNotify, true) + if err != nil { + baseapi.SugarLogger.Debugf("FN GetShopStatusCallbackMsg 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 ChainstoreStatusNotify failed with err:%v", err) + callbackResponse = &CallbackResponse{Code: -1} + return nil, callbackResponse + } + + // 解析蜂鸟返回值 + shopStatusMsg, err = FnCallbackAnalysis(fnNotify) + if err != nil { + return nil, &CallbackResponse{Code: -1} + } + + return shopStatusMsg, SuccessResponse +} + +// 蜂鸟返回值解析 +func FnCallbackAnalysis(notify *ChainstoreStatusNotify) (result map[string]interface{}, err error) { + switch notify.CallbackBusinessType { + case OrderStatus: // 订单回调 + orderRes := &OrderCallbackParam{} + if err := json.Unmarshal([]byte(notify.Param), orderRes); err != nil { + return nil, err + } + notifyObj := utils.Struct2FlatMap(orderRes) + notifyObj["orderStatusNotify"] = ChainstoreStatus + return notifyObj, nil + case AbnormalStatus: // 异常报备回调 + return nil, nil + case CookingFinishStatus: // 商户出餐回调 + //cokking := &CookingFinishNotify{} + //if err := json.Unmarshal([]byte(notify.Param), cokking); err != nil { + // return nil, err + //} + //notifyObj := utils.Struct2FlatMap(cokking) + //notifyObj["cookingFinishNotify"] = ChainstoreStatus + //return notifyObj, nil + return nil, nil + case ChainstoreStatus: // 门店状态变更回调 + storeRes := &ChainstoreParam{} + if err := json.Unmarshal([]byte(notify.Param), storeRes); err != nil { + return nil, err + } + notifyObj := utils.Struct2FlatMap(storeRes) + notifyObj["notifyUel"] = ChainstoreStatus + return notifyObj, nil + case ChainstoreServiceStatus: // 门店采购服务变更回调 + return nil, nil + case NoServiceStatus: // 城市屏蔽区域调整回调通知 + return nil, nil + default: + return nil, errors.New("回调函数,回调路径错误") + } + + globals.SugarLogger.Warnf("Fn callback url func err:=[%s],dont's exits", "notify.CallbackBusinessType") + return nil, errors.New("回调函数,回调路径错误") +} + type CallBackInfo struct { AppID string `json:"app_id"` Data string `json:"data"` diff --git a/platformapi/fnpsapi/fnps_v3.go b/platformapi/fnpsapi/fnps_v3.go new file mode 100644 index 00000000..33b62881 --- /dev/null +++ b/platformapi/fnpsapi/fnps_v3.go @@ -0,0 +1,451 @@ +package fnpsapi + +import ( + "git.rosy.net.cn/baseapi/platformapi" + "net/http" + "sync" +) + +const ( + //TokenURL = "https://open-anubis.ele.me/anubis-webapi/openapi/token" // 正式环境 + //ApiURL = "https://open-anubis.ele.me/anubis-webapi/v3/invoke"// 正式环境 + TokenURL = "https://exam-anubis.ele.me/anubis-webapi/openapi/token" // 沙箱环境 + ApiURL = "https://exam-anubis.ele.me/anubis-webapi/v3/invoke" // 沙箱环境 + RequestPost = "POST" + RequestGet = "GET" +) + +// 注册请求api +type API struct { + grantType string `json:"grant_type"` + code string `json:"code"` + appID string `json:"app_id"` + merchantId string `json:"merchant_id"` + signature string `json:"signature"` + timestamp int64 `json:"timestamp"` + accessToken string `json:"access_token"` + appSecret string `json:"app_secret"` + version string `json:"version"` + locker sync.RWMutex + client *http.Client + config *platformapi.APIConfig +} + +// 请求基础结构体 +type BaseInfo struct { + AccessToken string `json:"access_token"` //凭证token + Signature string `json:"signature"` + MerchantID string `json:"merchant_id"` //商户id + Version string `json:"version"` // 版本固定1.0 + AppID string `json:"app_id"` //应用id + Timestamp int64 `json:"timestamp"` // 当前时间戳 +} + +//#region 获取授权token + +// 获取token +type TokenInfo struct { + Sign string `json:"sign"` //返回值签名,详见开放平台侧返回值签名算法 + Code string `json:"code"` //错误码,详见开放平台侧错误码映射表 + Msg string `json:"msg"` //错误信息 + BusinessData string `json:"business_data"` // string + BusinessDataObj *BusinessData // BusinessData 对象 +} + +type BusinessData struct { + AppID string `json:"app_id"` //应用id + MerchantID string `json:"merchant_id"` //商户id + AccessToken string `json:"access_token"` //凭证token + RefreshToken string `json:"refresh_token"` //刷新token + ExpireIn int64 `json:"expire_in"` //access_token剩余有效时间,单位:秒,默认有效期是一年 + ReExpireIn int64 `json:"re_expire_in"` //refresh_token剩余有效时间 +} + +//#endregion + +//<---------------------------------------------------门店---------------------------------------------------------------------------------------> +//#region 获取蜂鸟门店信息 + +// 获取单个门店查询 +type GetOneStoreParam struct { + BaseInfo + BusinessData string `json:"business_data"` // 门店基础数据 +} + +// 获取单个门店基础参数(请求) +type GetOneStore struct { + MerchantID string `json:"merchant_id"` // 商户id + OutShopCode string `json:"out_shop_code"` // 外部门店编码 +} + +// 获取单个门店(返回) +type GetOneStoreRespData struct { + Address string `json:"address"` // 门店地址 + BranchName string `json:"branch_name"` // 门店分店名 + CategoryID int `json:"category_id"` // 门店类目 + ChainStoreID int `json:"chain_store_id"` // 蜂鸟门店id,创建接口返回的id + ChainstoreType int `json:"chainstore_type"` // 门店类型 1-正式门店;2-测试门店 + ChainstoreTypeDesc string `json:"chainstore_type_desc"` // 门店类型描述 + ContactPhone string `json:"contact_phone"` // 门店联系方式 + CreditCode string `json:"credit_code"` // 统一社会信用代码 + Latitude float64 `json:"latitude"` // 门店纬度[0,90] + Longitude float64 `json:"longitude"` // 门店经度[0,180] + MerchantID int `json:"merchant_id"` // 所属商户id + ModifyStatus int `json:"modify_status"` // 0-无修改,10-资料修改审核中,20-审核通过,30-a审核驳回 + ModifyStatusDesc string `json:"modify_status_desc"` // 门店修改状态描述 + Name string `json:"name"` // 门店主店名 + OutShopCode string `json:"out_shop_code"` // 外部门店编码 + OwnerIDNum string `json:"owner_id_num"` // 门店拥有人身份证号 + OwnerName string `json:"owner_name"` // 门店拥有人姓名 + PositionSource int `json:"position_source"` // 经纬度来源 坐标属性(1:腾讯地图, 2:百度地图, 3:高德地图),蜂鸟建议使用高德地图 + PositionSourceDesc string `json:"position_source_desc"` // 经纬度来源说明 + SettlementAccountID int `json:"settlement_account_id"` // 门店结算账号id + SettlementMode int `json:"settlement_mode"` // 门店结算方式 1:实时结算; 2:账期结算; + SettlementModeDesc string `json:"settlement_mode_desc"` // 结算说明? + Status int `json:"status"` // 0-上架审核中,20-正常(已上架),30-上架审核失败,40-已冻结,50-已下架 + StatusDesc string `json:"status_desc"` // 门店认证状态描述 +} + +// 更新蜂鸟门店信息 +type UpdateStoreParam struct { + // 必填 + ChainStoreID string `json:"chain_store_id"` // 蜂鸟门店id,创建接口返回的id + HeadShopName string `json:"head_shop_name"` // 门店主店名 + ContactPhone string `json:"contact_phone"` // 门店联系方式 + Address string `json:"address"` // 门店地址 + Longitude float64 `json:"longitude"` // 纬度 + Latitude float64 `json:"latitude"` // 门店纬度 + PositionSource int `json:"position_source"` // 经纬度来源 坐标属性:高德地图:3;目前只支持高德地图坐标 + OutShopCode string `json:"out_shop_code"` // 外部门店编码 + CategoryID string `json:"category_id"` // 门店类目 + OwnerName string `json:"owner_name"` // 门店主店名 + OwnerIDNum string `json:"owner_id_num"` // 门店拥有人身份证号 + HandheldLicencePicHash string `json:"handheld_licence_pic_hash"` // 门店拥有人手持身份证、营业执照图片 + OwnerIDPicFrontHash string `json:"owner_id_pic_front_hash"` // 身份证正面 + OwnerIDPicBackHash string `json:"owner_id_pic_back_hash"` // 身份证反面 + CreditCode string `json:"credit_code"` // 统一社会信用代码 + BusinessLicencePicHash string `json:"business_licence_pic_hash"` // 营业执照图片 + + // 选填 + BranchShopName string `json:"branch_shop_name"` // 门店主店名 + ChainstoreType int `json:"chainstore_type"` + FoodLicensePicHash string `json:"food_license_pic_hash"` // 食品安全执照图片 + SecondMedicalEquipmentLicensePicHash string `json:"second_medical_equipment_license_pic_hash"` // 第二类医疗器械类目必传 + MedicalInstitutionLicensePicHash string `json:"medical_institution_license_pic_hash"` // 医疗机构必传 + MedicalEquipmentLicensePicHash string `json:"medical_equipment_license_pic_hash"` // 经营医疗器械必传 + MedicineLicensePicHash string `json:"medicine_license_pic_hash"` // 药品经营许可证 + TabacooLicensePicHash string `json:"tabacoo_license_pic_hash"` // 烟草经营许可证图片 +} + +// 创建门店基础数据 +type CreateStoreBaseInfo struct { + // 必填 + HeadShopName string `json:"head_shop_name"` // 门店主店名 + ContactPhone string `json:"contact_phone"` // 门店联系方式 + Address string `json:"address"` // 门店地址 + Longitude float64 `json:"longitude"` // 门店经度(0-18) + Latitude float64 `json:"latitude"` // 门店纬度(0-90) + PositionSource int `json:"position_source"` // 经纬度来源只支持高德(默认值3) + OutShopCode string `json:"out_shop_code"` // 外部门店编码 + CategoryID string `json:"category_id"` // 门店类目 + OwnerName string `json:"owner_name"` // 门店拥有人姓名 + OwnerIDNum string `json:"owner_id_num"` // 门店拥有人身份证号 + HandheldLicencePicHash string `json:"handheld_licence_pic_hash"` // 门店拥有人手持身份证、营业执照图片 + OwnerIDPicFrontHash string `json:"owner_id_pic_front_hash"` // 身份证正面 + OwnerIDPicBackHash string `json:"owner_id_pic_back_hash"` // 身份证反面 + CreditCode string `json:"credit_code"` // 统一社会信用代码 + BusinessLicencePicHash string `json:"business_licence_pic_hash"` // 营业执照图片 + + // 非必填 + BranchShopName string `json:"branch_shop_name"` // 门店分店名 + ChainstoreType int `json:"chainstore_type"` // 门店类型 1:正式门店;2:测试门店;默认为1 + SettlementModel string `json:"settlement_model"` // 1:实时结算; 2:账期结算;默认为1,若为账期结算,需额外传入结算账号id + SettlementAccountID string `json:"settlement_account_id"` // 门店结算账号id + + // 经营类目包含食品必传 + FoodLicensePicHash string `json:"food_license_pic_hash"` // 食品安全执照图片(食品经营必传) + SecondMedicalEquipmentLicensePicHash string `json:"second_medical_equipment_license_pic_hash"` // 第二类医疗器械类目必传 + MedicalInstitutionLicensePicHash string `json:"medical_institution_license_pic_hash"` // 医疗机构必传 + MedicalEquipmentLicensePicHash string `json:"medical_equipment_license_pic_hash"` // 经营医疗器械必传 + MedicineLicensePicHash string `json:"medicine_license_pic_hash"` // 药品经营许可证 + TabacooLicensePicHash string `json:"tabacoo_license_pic_hash"` // 烟草经营许可证图片 + +} + +//#endregion + +// <--------------------------------------------------订单-------------------------------------------------------------------> +//#region 预请求获取订单 +type PreCreateOrder struct { + // 必填参数 + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + OrderType int `json:"order_type"` // 订单类型(1:即时单,3:预约单) + PositionSource int `json:"position_source"` // 经纬度来源 + ReceiverAddress string `json:"receiver_address"` // 收货人地址 文字描述 + ReceiverLongitude float64 `json:"receiver_longitude"` // 收货人经度 + ReceiverLatitude float64 `json:"receiver_latitude"` // 收货人纬度 + GoodsTotalAmountCent int64 `json:"goods_total_amount_cent"` // 订单商品总金额(分) + GoodsActualAmountCent int64 `json:"goods_actual_amount_cent"` // 订单商品客户实际支付金额 + GoodsWeight float64 `json:"goods_weight"` // 订单总重量(kg) + GoodsCount int `json:"goods_count"` // 货物件数 + GoodsItemList []*GoodsItemsList // 货物列表 + + // 非必填参数 + TransportLongitude float64 `json:"transport_longitude,omitempty"` // 取货经度 + TransportLatitude float64 `json:"transport_latitude,omitempty"` // 取货纬度 + TransportTel string `json:"transport_tel,omitempty"` // 取货点联系人电话 + OutShopCode string `json:"out_shop_code,omitempty"` // 外部门店id 当使用门店发单 out_shop_code和chain_store_id必填1个 + ChainStoreID string `json:"chain_store_id"` // 门店id + OrderSource string `json:"order_source,omitempty"` // 饿百订单传109 商户订单来源(如饿了么、美团等)手发单/未知来源: 0 或不传 美团: 2 口碑: 4 饿了么: 6支付宝: 7 饿百: 8 + ServiceGoodsId int64 `json:"service_goods_id"` // 服务商品id + BaseGoodsId int64 `json:"base_goods_id"` // 基础商品id + RequireReceiveTime string `json:"require_receive_time"` // 需要送达时间 预约单如(需要送达时间 – 推单时间) < 60min + OrderAddTime int64 `json:"order_add_time,omitempty"` // 下单时间毫秒 + UseCoupon int `json:"use_coupon,omitempty"` // 是否使用优惠券 0:不使用, 1:使用 默认使用 + ExpectFetchTime int64 `json:"expect_fetch_time,omitempty"` // 预计出餐时间(毫秒) (预约单该字段必填)建议和商务合同里约定的承诺出货时长保持一致,否则影响考核结算 + TransportAddress string `json:"transport_address"` // 取货点地址描述 + OrderTipAmountCent int64 `json:"order_tip_amount_cent,omitempty"` // 取货点地址描述 + //OrderRemark string `json:"order_remark"` + //FetchCode string `json:"fetch_code"` + //WriteOffCode string `json:"write_off_code"` + //CustomerExtTel string `json:"customer_ext_tel"` +} + +// 货物明细 +type GoodsItemsList struct { + ItemName string `json:"item_name"` // 商品名字 + ItemQuantity int `json:"item_quantity"` // 商品数量 + ItemAmountCent int64 `json:"item_amount_cent"` // 商品原价分 + ItemActualAmountCent int64 `json:"item_actual_amount_cent"` // 商品实际支付金额,必须是乘以数量后的金额,否则影响售后环节的赔付标准 + + // 非必填 + ItemId string `json:"item_id"` // 商品编号 + ItemSize int `json:"item_size"` // 商品尺寸 1:小, 2:中, 3:大 + ItemRemark string `json:"item_remark"` // 品备注(不超过255个字符) +} + +// 蜂鸟请求预下单返回值 +type PreCreateOrderResp struct { + Distance int64 `json:"distance"` //配送距离 + CityId int64 `json:"city_id"` // 城市id + Time int64 `json:"time"` // 预询时间戳 + GoodsInfos []*GoodsInfos `json:"goods_infos"` // 服务商品明细 + //GoodsInfos string `json:"goods_infos"` // 服务商品明细 +} + +// 预下单商品服务明细 +type GoodsInfos struct { + ServiceGoodsId int64 `json:"service_goods_id"` // 服务商品id + BaseGoodsId int64 `json:"base_goods_id"` // 基础商品id + IsValid int `json:"is_valid"` // 是否可用0不可,1可 + DisableReason string `json:"disable_reason"` // 不可用原因描述 + PredictDeliveryTime int64 `json:"predict_delivery_time"` // 预计送达时间戳 + PredictDeliveryMinutes int `json:"predict_delivery_minutes"` // 预计送达时长分钟 + CanAddTip int `json:"can_add_tip"` // 服务商品是否支持加小费0否1是 + Slogan string `json:"slogan"` // 商品介绍 + TotalDeliveryAmountCent int64 `json:"total_delivery_amount_cent"` // 原始配送费总价格(含入参小费金额) 分 + ActualDeliveryAmountCent int64 `json:"actual_delivery_amount_cent"` // 优惠后配送费总价格(含入参小费金额) 入单实际价格 关注这一个字段就行!!! + PriceDetail *PriceOpenapiDetail `json:"price_detail"` // 加价说明 + WarehouseId int64 `json:"warehouse_id"` // 优惠券记录id + TIndexid string `json:"t_indexid"` // 预询标识 入单时需传入,标识本次预询 +} + +// 订单加价说明 +type PriceOpenapiDetail struct { + StartPriceCent int `json:"start_price_cent"` // 起送价 分 + DistancePriceCent int `json:"distance_price_cent"` // 距离加价 + WeightPriceCent int `json:"weight_price_cent"` // 重量加价 + TimePeriodSurchargeCent int `json:"time_period_surcharge_cent"` // 时段加价 + TemporarySurchargeCent int `json:"temporary_surcharge_cent"` // 临时加价 + CategorySurchargeCent int `json:"category_surcharge_cent"` // 品类加价 + OrderPriceSurchargeCent int `json:"order_price_surcharge_cent"` // 客单价加价 + PressureSurchargeCent int `json:"pressure_surcharge_cent"` // 运力紧张加价 + RiverCrossingSurchargeCent int `json:"river_crossing_surcharge_cent"` // 跨江单加价 +} + +//#endregion + +//#region 正式下单(订单查询,骑手查询,账户查询) + +// 创建订单 +type CreateOrderReqParam struct { + // 必传参数 + PartnerOrderCode string `json:"partner_order_code,omitempty"` // 外部订单号 + OrderType int `json:"order_type,omitempty"` // 订单类型(1:即时单,3:预约单) + PositionSource int `json:"position_source,omitempty"` // 坐标经纬度来源(1:腾讯地 图, 2:百度地图, 3:高德地图)蜂鸟建议使用高德地图 + ReceiverAddress string `json:"receiver_address,omitempty"` // 收货人地址 文字描述 + ReceiverLongitude float64 `json:"receiver_longitude,omitempty"` // 收货人经度 + ReceiverLatitude float64 `json:"receiver_latitude,omitempty"` // 收货人纬度 + GoodsTotalAmountCent int64 `json:"goods_total_amount_cent,omitempty"` // 订单商品总金额(分) + GoodsActualAmountCent int64 `json:"goods_actual_amount_cent,omitempty"` // 订单商品客户实际支付金额 + GoodsWeight float64 `json:"goods_weight,omitempty"` // 订单总重量(kg) + GoodsCount int `json:"goods_count,omitempty"` // 货物件数 + GoodsItemList []*GoodsItemsList `json:"goods_item_list,omitempty"` // 货物明细 + ReceiverName string `json:"receiver_name,omitempty"` // 收货人姓名 + ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` // 只支持手机号,400开头电话,座机号码以及95013开头、长度13位的虚拟电话 + OutShopCode string `json:"out_shop_code,omitempty"` // 外部门店id 当使用门店发单 out_shop_code和chain_store_id必填1个 + ChainStoreId string `json:"chain_store_id,omitempty"` // 门店id + + // 选传参数 + TransportLongitude float64 `json:"transport_longitude,omitempty"` // 取货经度 + TransportLatitude float64 `json:"transport_latitude,omitempty"` // 取货纬度 + TransportTel string `json:"transport_tel,omitempty"` // 取货点联系人电话 + OrderSource string `json:"order_source,omitempty"` // 饿百订单传109 商户订单来源(如饿了么、美团等)手发单/未知来源: 0 或不传 美团: 2 口碑: 4 饿了么: 6支付宝: 7 饿百: 8 + ServiceGoodsId int64 `json:"service_goods_id,omitempty"` // 服务商品id 校验预询配送费价格时 必传 + BaseGoodsId int64 `json:"base_goods_id,omitempty"` // 基础商品id 校验预询配送费价格时 必传 + RequireReceiveTime int64 `json:"require_receive_time,omitempty"` // 需要送达时间 需要送达时间(毫秒); 订单类型为预约单时必传,如(需要送达时间 – 推单时间)< 60min,则蜂鸟配送开放平台自动将订单类型置为即时单 ,反之则置为预约单 + OrderAddTime int64 `json:"order_add_time,omitempty"` // 下单时间毫秒 + UseCoupon int `json:"use_coupon,omitempty"` // 是否使用优惠券 0:不使用, 1:使用 默认使用 + ExpectFetchTime int64 `json:"expect_fetch_time,omitempty"` // 预计出餐时间(毫秒) (预约单该字段必填)建议和商务合同里约定的承诺出货时长保持一致,否则影响考核结算 + TransportAddress string `json:"transport_address,omitempty"` // 取货点地址描述 + OrderTipAmountCent int64 `json:"order_tip_amount_cent,omitempty"` // 取货点地址描述 + SerialNumber string `json:"serial_number,omitempty"` // 商家订单流水号, 方便配送骑手 到店取货, 支持数字,字母及#等常见字符. 建议填写。长度<=6。 + OrderSourceOrderId string `json:"order_source_order_id,omitempty"` // 订单来源的 单号 + ReceiverSecondPhone string `json:"receiver_second_phone,omitempty"` // 收货人备用联系方式 + CustomerExtTel string `json:"customer_ext_tel,omitempty"` // 分机号 + OrderRemark string `json:"order_remark,omitempty"` // 用户备注 + PreCreateOrderTIndexId string `json:"pre_create_order_t_index_id,omitempty"` // 预询后下单标识 为null不校验配送费价格直接 入单 不为null强校验配送费价格 与入参价格不一致拒绝入单 + WarehouseId int64 `json:"warehouse_id,omitempty"` // 优惠券id + ActualDeliveryAmountCent int64 `json:"actual_delivery_amount_cent,omitempty"` // 优惠后配送费总价格(含入参小费金额) 入单实际价格 取自预下单接口出参同名字段!!!不为null则校验配送费价格,不一致拒绝入单 (含入参小费)校验预询配送费价格时 必传 + WriteOffCode string `json:"write_off_code,omitempty"` // 销码(贵品服务必填)骑手和商户交互 签贵品履约才生效 + CancelCode string `json:"cancel_code,omitempty"` // 取消码(贵品服务必填) + FetchCode string `json:"fetch_code,omitempty"` // 取餐码 骑手和用户交互 +} + +// 查询余额getAmount +type GetBalanceAmountRes struct { + BalanceAmountCent int64 `json:"balance_amount_cent"` // 账户余额 分 + FrozenBalanceAmountCent int64 `json:"frozen_balance_amount_cent"` // 账户冻结余额 分 + BalanceStatus int `json:"balance_status"` // 账户状态 1 正常 2 冻结 +} + +// 查询订单详情接口 getOrderDetail +type GetOrderDetailReq struct { + OrderId string `json:"order_id"` // 订单号 订单号和外部订单号必填1个 + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 +} + +// 查询订单详情接口返回值 +type GetOrderDetailRes struct { + OrderId int64 `json:"order_id"` // 订单id + TrackingId int64 `json:"tracking_id"` // 运单id + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + SerialNumber string `json:"serial_number"` // 商家订单流水号, 方便配送骑手 到店取货, 支持数字,字母及#等常见字符. 建议填写。长度<=6。 + OrderStatus int `json:"order_status"` // 订单生成0,运单生成成功1,20:骑手接单,80:骑手到店,2:配送中,3:已完成,4:已取消,5:配送异常 + CarrierDriverId string `json:"carrier_driver_id"` // 配送员id + CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送员电话 + EstimateArriveTime int64 `json:"estimate_arrive_time"` // 预计送达时间(毫秒) + OvertimeCompensationCostCent int64 `json:"overtime_compensation_cost_cent"` // 时效赔付 + IfCanAddTip int `json:"if_can_add_tip"` // 是否支持添加调度费 1可以,0不可以 + OrderTipAmountCent int64 `json:"order_tip_amount_cent"` // 订单当前小费总金额 分 + DeliveryFetchPhotos []interface{} `json:"delivery_fetch_photos"` // 骑手取货照片地址 + OrderTotalAmountCent int64 `json:"order_total_amount_cent"` // 原始配送费金额(分) + OrderActualAmountCent int64 `json:"order_actual_amount_cent"` // 订单实际配送支付总金额关注这个字段即可(分) + PriceDetail []*PriceOpenapiDetail `json:"price_detail"` // 配送费价格明细 + AbnormalCode string `json:"abnormal_code"` // 运单异常原因code + AbnormalDesc string `json:"abnormal_desc"` // 运单异常原因描述 + EventLogDetails []*OrderNodeInfo `json:"event_log_details"` // 运单事件节点信息 + ComplaintId int64 `json:"complaint_id"` // 投诉编号 + ComplaintReasonDesc string `json:"complaint_reason_desc"` // 投诉原因描述 + ComplaintStatus int `json:"complaint_status"` // 投诉状态 1待处理, 2成功,3失败 + ClaimId int64 `json:"claim_id"` // 索赔id + ClaimReasonDesc string `json:"claim_reason_desc"` // 索赔原因描述 + ClaimStatus int `json:"claim_status"` // 索赔状态 1待处理, 2成功,3失败 + Temperature string `json:"temperature"` // 骑手体温 + OrderDistance float64 `json:"order_distance"` // 配送距离(米) +} + +// 点单详情信息,订单结点信息 +type OrderNodeInfo struct { + OrderStatus int `json:"order_status"` // 订单状态 + OccurTime int64 `json:"occur_time"` // 时间结点 + CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送电话 +} + +// getKnightInfo 查询骑手信息接口,参数 GetOrderDetailReq +type GetKnightInfoRes struct { + CarrierDriverId string `json:"carrier_driver_id"` // 配送员id + CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送员电话 + CarrierDriverLongitude string `json:"carrier_driver_longitude"` // 骑手坐标 + CarrierDriverLatitude string `json:"carrier_driver_latitude"` // 骑手坐标 +} + +//#endregion + +//#region 加小费接口 + +//追加小费,订单小费总金额为每次累加 +//注:不是所有订单都支持加小费,受合同服务 & 接单运力 & 当时订单状态等多个因素影响。 订单详情接口有返回是否可加小费字段。 +//另:联调环境使用加小费接口 需先使用联调工具->订单状态回调:把订单状态改为1,否则订单状态会不支持加小费 +// 添加小费 +type AddTipRes struct { + OrderId string `json:"order_id"` // 订单号 订单号和外部订单号必填1个 + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + AddTipAmountCent int64 `json:"add_tip_amount_cent"` // 小费金额分(必填) + ThirdIndexId int64 `json:"third_index_id"` // 本次加小费唯一标识 每个订单内 不可重复, 会用来做幂等,相同third_index_id的后续请求会被忽略 +} + +// 业务出参 "business_data": "{\"result\":true}" +//#endregion + +//#region 取消接口 + +// getCancelReasonList 获取可用取消原因列表接口 参数:GetOrderDetailReq + +// getCancelReasonListRes 取消接口返回值 +type GetCancelReasonListRes struct { + CancelReasonList []*struct { + OrderCancelCode int `json:"order_cancel_code"` // 取消原因code + OrderCancelDesc int `json:"order_cancel_desc"` // 取消原因描述 + } +} + +// preCancelOrder 预取消订单接口 +type PreCancelOrderReq struct { + GetOrderDetailReq + OrderCancelCode int64 `json:"order_cancel_code"` // 从可用取消原因列表接口返回结果选择(必填) +} + +// cancelOrder 正式取消订单接口 +type CancelOrderReq struct { + GetOrderDetailReq + OrderCancelCode int64 `json:"order_cancel_code"` // 从可用取消原因列表接口返回结果选择(必填) + ActualCancelCostCent int64 `json:"actual_cancel_cost_cent"` // 单位分 取消实际需扣金额,从预取消接口拿(非必填) + OrderCancelOtherReason string `json:"order_cancel_other_reason"` // 取消原因补充 20字以内(非必填) + OrderCancelRole int `json:"order_cancel_role"` // 1商户取消, 2 用户取消(必填) +} + +//#endregion + +//#region 投诉订单 + +// complaintOrder投诉订单 +type ComplaintOrderReq struct { + GetOrderDetailReq + OrderComplaintCode int `json:"order_complaint_code"` // 投诉编码 230:其他, 150:未保持餐品完整, 160:服务态度恶劣, 190:额外索取费用,170:诱导收货人或商户退单, 140:提前点击送达,210:虚假标记异常, 220:少餐错餐,200:虚假配送, 130:未进行配送(必填) + OrderComplaintDesc string `json:"order_complaint_desc"` // 投诉原因描述(否) +} + +// claimOrder 索赔订单 +type ClaimOrderRes struct { + GetOrderDetailReq + OrderClaimCode int `json:"order_claim_code"` // 索赔编码150: 未保持餐品完整,160: 服务态度恶劣, 170: 诱导收货人或商户退单,210:虚假标记异常, 130: 骑手接单后未完成配送,320:少餐错餐, 190:额外索取费用,140:提前点击送达, 200:骑手点击配送成功,实际未配送 + OrderClaimDesc string `json:"order_claim_desc"` // 投诉原因描述 + OrderClaimPriceCent int64 `json:"order_claim_price_cent"` // 索赔金额 分 + OrderClaimSku []*OrderClaimSkuDto `json:"order_claim_sku"` // 索赔明细 +} + +// 索赔明细 +type OrderClaimSkuDto struct { + ClaimSkuName string `json:"claim_sku_name"` // 索赔商品名称 + ClaimSkuGoodsCount string `json:"claim_sku_goods_count"` // 索赔商品数量 + ClaimSkuPriceCent string `json:"claim_sku_price_cent"` // 索赔商品实际单价 分 + ClaimSkuTotalPriceCent string `json:"claim_sku_total_price_cent"` // 索赔商品实际总价 分 +} + +//#endregion + +//#endregion diff --git a/platformapi/fnpsapi/fnpsapi.go b/platformapi/fnpsapi/fnpsapi.go index d9ea0f46..0e04877d 100644 --- a/platformapi/fnpsapi/fnpsapi.go +++ b/platformapi/fnpsapi/fnpsapi.go @@ -1,12 +1,11 @@ package fnpsapi import ( - "crypto/md5" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" - "math/rand" "net/http" - "net/url" "sort" "strings" "sync" @@ -25,126 +24,125 @@ const ( tokenAction = "get_access_token" ) -type API struct { - accessToken string - appID string - appSecret string - locker sync.RWMutex - client *http.Client - config *platformapi.APIConfig -} - func (a *API) SetToken(token string) { a.locker.Lock() defer a.locker.Unlock() a.accessToken = token } -func New(appID, appSecret string, config ...*platformapi.APIConfig) *API { +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(appID, appSecret, merchantId, code string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ - appID: appID, - appSecret: appSecret, - client: &http.Client{Timeout: curConfig.ClientTimeout}, - config: &curConfig, + grantType: "authorization_code", // 授权模式,填固定值authorization_code + code: code, + appID: appID, + merchantId: merchantId, + signature: "", + timestamp: time.Now().UnixNano(), + accessToken: "", + version: "1.0", + appSecret: appSecret, + locker: sync.RWMutex{}, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, } } func (a *API) signParam(params map[string]interface{}) (sig string) { var valueList []string for k, v := range params { - if k != sigKey { + if k != "signature" { if str := fmt.Sprint(v); str != "" { valueList = append(valueList, fmt.Sprintf("%s=%s", k, str)) } } } sort.Sort(sort.StringSlice(valueList)) - valueList = append(valueList, fmt.Sprintf("secret_key=%s", a.appSecret)) sig = strings.Join(valueList, "&") - binSig := md5.Sum([]byte(url.QueryEscape(sig))) - sig = fmt.Sprintf("%x", binSig) - return sig + sig = a.appSecret + sig + signature := sha256.Sum256([]byte(sig)) + return hex.EncodeToString(signature[:]) } -func (a *API) signParam2(params map[string]interface{}) (sig string) { - sb := new(strings.Builder) - sb.WriteString("app_id=") - sb.WriteString(a.appID) - sb.WriteString("&access_token=") - sb.WriteString(a.accessToken) - sb.WriteString("&data=") - sb.WriteString(params["data"].(string)) - sb.WriteString("&salt=") - sb.WriteString(utils.Int64ToStr(utils.MustInterface2Int64(params["salt"]))) - sig = sb.String() - binSig := md5.Sum([]byte(sig)) - sig = fmt.Sprintf("%x", binSig) - return sig -} - -func (a *API) AccessAPI(action string, url string, bizParams map[string]interface{}, isPost bool) (retVal map[string]interface{}, err error) { - params := make(map[string]interface{}) - params["salt"] = GetSalt() - params["app_id"] = a.appID - if action != tokenAction { - data, _ := json.Marshal(bizParams) - params["data"] = string(data) - signStr := a.signParam2(params) - params[sigKey] = signStr - } else { - signStr := a.signParam(params) - params[sigKey] = signStr +func (a *API) AccessAPI(baseUrl, actionApi, method string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { + a.signature = a.signParam(bizParams) + bizParams["signature"] = a.signature + // 序列化 + data, err := json.Marshal(bizParams) + if err != nil { + return nil, err } - data, _ := json.Marshal(params) - fullURL := utils.GenerateGetURL(url, action, nil) - err = platformapi.AccessPlatformAPIWithRetry(a.client, - func() *http.Request { - var request *http.Request - if isPost { - request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data))) - } else { - request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(url, action, params), nil) - } - request.Header.Set("Content-Type", "application/json") - return request - }, - a.config, - func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { - if jsonResult1 == nil { - return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") - } - if err == nil { - if utils.MustInterface2Int64(jsonResult1["code"]) != 200 { - errLevel = platformapi.ErrLevelGeneralFail - err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"]))) - baseapi.SugarLogger.Debugf("fnps AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) - } - retVal = jsonResult1 - } - return errLevel, err - }) + + // 全路径请求参数 + fullURL := utils.GenerateGetURL(baseUrl, actionApi, nil) + + // 发送请求 + sendUrl := func() *http.Request { + var request *http.Request + if RequestPost == method { + request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data))) + } else { + request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(baseUrl, actionApi, bizParams), nil) + } + 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("fnps 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 } -func GetSalt() (salt int) { - rand.Seed(time.Now().UnixNano()) - return rand.Intn(8999) + 1000 -} - -type TokenInfo struct { - AccessToken string `json:"access_token"` - AppID string `json:"app_id"` - ExpireTime int64 `json:"expire_time"` -} - +// 获取access_token func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) { - result, err := a.AccessAPI(tokenAction, URL, nil, false) - if err == nil { - utils.Map2StructByJson(result["data"], &tokenInfo, false) + parameter := make(map[string]interface{}, 6) + parameter["grant_type"] = a.grantType + parameter["code"] = a.code + parameter["app_id"] = a.appID + parameter["merchant_id"] = a.merchantId + parameter["timestamp"] = utils.Int64ToStr(a.timestamp) + + result, err := a.AccessAPI(TokenURL, "", RequestPost, parameter) + 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/fnpsapi/order.go b/platformapi/fnpsapi/order.go index bc01b1b2..e67f625a 100644 --- a/platformapi/fnpsapi/order.go +++ b/platformapi/fnpsapi/order.go @@ -1,102 +1,299 @@ package fnpsapi import ( + "encoding/json" + "errors" "time" "git.rosy.net.cn/baseapi/utils" ) const ( - OrderCancelReson1 = 1 // 1:物流原因:订单长时间未分配骑手, - OrderCancelReson2 = 2 // 2:物流原因:分配骑手后,骑手长时间未取件 , - OrderCancelReson3 = 3 // 3:物流原因:骑手告知不配送,让取消订单, + OrderCancelReson0 = 0 // 0:其它(必填原因)必须填写原因 + OrderCancelReson1 = 1 // 1:物流原因:订单长时间未分配骑手 OrderCancelReson4 = 4 // 4:商品缺货/无法出货/已售完, - OrderCancelReson5 = 5 // 5:商户联系不上门店/门店关门了, 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:商户联系不上门店/门店关门了(类型已下线) - OrderStatusAccept = 1 //系统已接单 - OrderStatusAssigned = 20 //已分配骑手 - OrderStatusArrived = 80 //已到店 - OrderStatusDelivering = 2 //配送中 - OrderStatusDelivered = 3 //已送达 - OrderStatusException = 5 //异常 + OrderStatusAcceptCreate = 0 //订单生成 + OrderStatusAcceptCacle = 4 //订单取消 + OrderStatusAccept = 1 //系统已接单 + OrderStatusAssigned = 20 //已分配骑手 + OrderStatusArrived = 80 //已到店 + OrderStatusDelivering = 2 //配送中 + OrderStatusDelivered = 3 //已送达 + OrderStatusException = 5 //异常 ) -type CreateOrderParam struct { - PartnerRemark string `json:"partner_remark,omitempty"` - PartnerOrderCode string `json:"partner_order_code,omitempty"` - NotifyURL string `json:"notify_url,omitempty"` - OrderType int `json:"order_type,omitempty"` - ChainStoreCode string `json:"chain_store_code,omitempty"` - TransportInfo *TransportInfo `json:"transport_info,omitempty"` - OrderAddTime int64 `json:"order_add_time,omitempty"` - OrderTotalAmount float64 `json:"order_total_amount,omitempty"` - OrderActualAmount float64 `json:"order_actual_amount,omitempty"` - OrderWeight float64 `json:"order_weight,omitempty"` - OrderRemark string `json:"order_remark,omitempty"` - IsInvoiced int `json:"is_invoiced"` - Invoice string `json:"invoice,omitempty"` - OrderPaymentStatus int `json:"order_payment_status,omitempty"` - OrderPaymentMethod int `json:"order_payment_method,omitempty"` - IsAgentPayment int `json:"is_agent_payment"` - RequirePaymentPay float64 `json:"require_payment_pay,omitempty"` - GoodsCount int `json:"goods_count,omitempty"` - RequireReceiveTime int64 `json:"require_receive_time,omitempty"` - SerialNumber string `json:"serial_number,omitempty"` - ReceiverInfo *ReceiverInfo `json:"receiver_info,omitempty"` - ItemsJSON []*ItemsJSON `json:"items_json,omitempty"` - OrderSource string `json:"order_source,omitempty"` //饿百订单传109 - ChannelOrderCode string `json:"channel_order_code,omitempty"` - CookingTime int64 `json:"cooking_time,omitempty"` - PlatformPaidTime int64 `json:"platform_paid_time,omitempty"` - PlatformCreatedTime int64 `json:"platform_created_time,omitempty"` - MerchantCode string `json:"merchant_code,omitempty"` +// +//type CreateOrderParam struct { +// PartnerRemark string `json:"partner_remark,omitempty"` +// PartnerOrderCode string `json:"partner_order_code,omitempty"` +// NotifyURL string `json:"notify_url,omitempty"` +// OrderType int `json:"order_type,omitempty"` +// ChainStoreCode string `json:"chain_store_code,omitempty"` +// TransportInfo *TransportInfo `json:"transport_info,omitempty"` +// OrderAddTime int64 `json:"order_add_time,omitempty"` +// OrderTotalAmount float64 `json:"order_total_amount,omitempty"` +// OrderActualAmount float64 `json:"order_actual_amount,omitempty"` +// OrderWeight float64 `json:"order_weight,omitempty"` +// OrderRemark string `json:"order_remark,omitempty"` +// IsInvoiced int `json:"is_invoiced"` +// Invoice string `json:"invoice,omitempty"` +// OrderPaymentStatus int `json:"order_payment_status,omitempty"` +// OrderPaymentMethod int `json:"order_payment_method,omitempty"` +// IsAgentPayment int `json:"is_agent_payment"` +// RequirePaymentPay float64 `json:"require_payment_pay,omitempty"` +// GoodsCount int `json:"goods_count,omitempty"` +// RequireReceiveTime int64 `json:"require_receive_time,omitempty"` +// SerialNumber string `json:"serial_number,omitempty"` +// ReceiverInfo *ReceiverInfo `json:"receiver_info,omitempty"` +// ItemsJSON []*ItemsJSON `json:"items_json,omitempty"` +// OrderSource string `json:"order_source,omitempty"` //饿百订单传109 +// ChannelOrderCode string `json:"channel_order_code,omitempty"` +// CookingTime int64 `json:"cooking_time,omitempty"` +// PlatformPaidTime int64 `json:"platform_paid_time,omitempty"` +// PlatformCreatedTime int64 `json:"platform_created_time,omitempty"` +// MerchantCode string `json:"merchant_code,omitempty"` +//} +// +//type ReceiverInfo struct { +// ReceiverName string `json:"receiver_name,omitempty"` +// ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` +// ReceiverSecondPhone string `json:"receiver_second_phone,omitempty"` +// ReceiverAddress string `json:"receiver_address,omitempty"` +// ReceiverLongitude float64 `json:"receiver_longitude,omitempty"` +// ReceiverLatitude float64 `json:"receiver_latitude,omitempty"` +// PositionSource int `json:"position_source,omitempty"` +//} +// +//type TransportInfo struct { +// TransportName string `json:"transport_name,omitempty"` +// TransportAddress string `json:"transport_address,omitempty"` +// TransportLongitude float64 `json:"transport_longitude,omitempty"` +// TransportLatitude float64 `json:"transport_latitude,omitempty"` +// PositionSource int `json:"position_source,omitempty"` +// TransportTel string `json:"transport_tel,omitempty"` +// TransportRemark string `json:"transport_remark,omitempty"` +//} +// +//type ItemsJSON struct { +// ItemID string `json:"item_id,omitempty"` +// ItemName string `json:"item_name,omitempty"` +// ItemQuantity int `json:"item_quantity,omitempty"` +// ItemPrice float64 `json:"item_price"` +// ItemActualPrice float64 `json:"item_actual_price"` +// ItemSize int `json:"item_size,omitempty"` +// ItemRemark string `json:"item_remark,omitempty"` +// IsNeedPackage int `json:"is_need_package"` +// IsAgentPurchase int `json:"is_agent_purchase"` +// AgentPurchasePrice float64 `json:"agent_purchase_price,omitempty"` +//} + +// 蜂鸟预下单 +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 + } + + // 返回所有可选的商品列表,每项包含当前使用该商品下单时对应的价格等信息,(其中不可用的商品会返回不可用原因) 可挑选其中一个可用的商品进行正式下单。 + for _, v := range preOrderResult.GoodsInfos { + if v.IsValid != 1 { + continue + } + deliveryFee = v.ActualDeliveryAmountCent + baseDeliveryFee = v.TotalDeliveryAmountCent + } + + // 异常检测 + if deliveryFee == 0 && baseDeliveryFee == 0 { + err = errors.New("门店数据异常") + } + return deliveryFee, baseDeliveryFee, nil } -type ReceiverInfo struct { - ReceiverName string `json:"receiver_name,omitempty"` - ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` - ReceiverSecondPhone string `json:"receiver_second_phone,omitempty"` - ReceiverAddress string `json:"receiver_address,omitempty"` - ReceiverLongitude float64 `json:"receiver_longitude,omitempty"` - ReceiverLatitude float64 `json:"receiver_latitude,omitempty"` - PositionSource int `json:"position_source,omitempty"` -} - -type TransportInfo struct { - TransportName string `json:"transport_name,omitempty"` - TransportAddress string `json:"transport_address,omitempty"` - TransportLongitude float64 `json:"transport_longitude,omitempty"` - TransportLatitude float64 `json:"transport_latitude,omitempty"` - PositionSource int `json:"position_source,omitempty"` - TransportTel string `json:"transport_tel,omitempty"` - TransportRemark string `json:"transport_remark,omitempty"` -} - -type ItemsJSON struct { - ItemID string `json:"item_id,omitempty"` - ItemName string `json:"item_name,omitempty"` - ItemQuantity int `json:"item_quantity,omitempty"` - ItemPrice float64 `json:"item_price"` - ItemActualPrice float64 `json:"item_actual_price"` - ItemSize int `json:"item_size,omitempty"` - ItemRemark string `json:"item_remark,omitempty"` - IsNeedPackage int `json:"is_need_package"` - IsAgentPurchase int `json:"is_agent_purchase"` - AgentPurchasePrice float64 `json:"agent_purchase_price,omitempty"` -} - -//https://open.ele.me/documents/%E5%88%9B%E5%BB%BA%E8%9C%82%E9%B8%9F%E8%AE%A2%E5%8D%95 -func (a *API) CreateOrder(createOrderParam *CreateOrderParam) (err error) { - params := utils.Struct2FlatMap(createOrderParam) - _, err = a.AccessAPI("v2/order", URL, params, true) +// 蜂鸟正式下单 +func (a *API) CreateOrder(createOrderParam *CreateOrderReqParam) (err error) { + orderHead := a.MakeFnRequestHead() + bytes, err := json.Marshal(createOrderParam) + if err != nil { + return err + } + orderHead["business_data"] = string(bytes) + _, err = a.AccessAPI(ApiURL, "createOrder", RequestPost, orderHead) 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 { + respParam := struct { + ActualCancelCostCent int64 `json:"actual_cancel_cost_cent"` + }{} + if err := json.Unmarshal([]byte(utils.Interface2String(k)), respParam); err != nil { + return 0, err + } + return respParam.ActualCancelCostCent, 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 == "" { + return errors.New("请补充退单原因") + } + + 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 +} + +// 查询订单详情接口 todo +func (a *API) QueryOrder(partnerOrderCode string) (result *GetOrderDetailRes, err error) { + orderHead := a.MakeFnRequestHead() + orderHead["business_data"] = partnerOrderCode + + data, err := a.AccessAPI(ApiURL, "getOrderDetail", RequestPost, orderHead) + if err != nil { + return nil, err + } + + if err := json.Unmarshal([]byte(data["business_data"].(string)), &result); err != nil { + return nil, err + } + + return +} + +// 查询骑手信息 +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 + } + + if err := json.Unmarshal([]byte(utils.Interface2String(data["business_data"])), &result); err != nil { + return nil, err + } + return +} + +// 余额查询 +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:物流原因:订单长时间未分配骑手, @@ -106,70 +303,69 @@ func (a *API) CreateOrder(createOrderParam *CreateOrderParam) (err error) { // 7:商户/顾客自身定位错误, 8:商户改其他第三方配送, 9:顾客下错单/临时不想要了, // 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) -type CancelOrderParam struct { - PartnerOrderCode string `json:"partner_order_code,omitempty"` - OrderCancelReasonCode int `json:"order_cancel_reason_code,omitempty"` - OrderCancelCode int `json:"order_cancel_code,omitempty"` - OrderCancelDescription string `json:"order_cancel_description,omitempty"` - OrderCancelTime int64 `json:"order_cancel_time,omitempty"` -} +//type CancelOrderParam struct { +// PartnerOrderCode string `json:"partner_order_code,omitempty"` +// OrderCancelReasonCode int `json:"order_cancel_reason_code,omitempty"` +// OrderCancelCode int `json:"order_cancel_code,omitempty"` +// OrderCancelDescription string `json:"order_cancel_description,omitempty"` +// OrderCancelTime int64 `json:"order_cancel_time,omitempty"` +//} -func (a *API) CancelOrder(cancelOrderParam *CancelOrderParam) (err error) { - params := utils.Struct2FlatMap(cancelOrderParam) - _, err = a.AccessAPI("v2/order/cancel", URL, params, true) - return err -} - -func (a *API) ComplaintOrder(cancelOrderParam *CancelOrderParam) (err error) { - params := utils.Struct2FlatMap(cancelOrderParam) - _, err = a.AccessAPI("v2/order/complaint", URL, params, true) - return err -} - -type QueryOrderResult struct { - AbnormalCode string `json:"abnormal_code"` - AbnormalDesc string `json:"abnormal_desc"` - CarrierDriverID int `json:"carrier_driver_id"` - CarrierDriverName string `json:"carrier_driver_name"` - CarrierDriverPhone string `json:"carrier_driver_phone"` - ComplaintDescription interface{} `json:"complaintDescription"` - ComplaintStatus interface{} `json:"complaintStatus"` - EstimateArriveTime int64 `json:"estimate_arrive_time"` - EventLogDetails []struct { - CarrierDriverName string `json:"carrier_driver_name"` - CarrierDriverPhone string `json:"carrier_driver_phone"` - OccurTime int64 `json:"occur_time"` - OrderStatus int `json:"order_status"` - } `json:"event_log_details"` - HandleID interface{} `json:"handleId"` - OrderID interface{} `json:"orderId"` - OrderStatus int `json:"order_status"` - OrderTotalDeliveryCost float64 `json:"order_total_delivery_cost"` - OrderTotalDeliveryDiscount interface{} `json:"order_total_delivery_discount"` - OvertimeCompensationCost float64 `json:"overtime_compensation_cost"` - Reason string `json:"reason"` - SupportClaims bool `json:"support_claims"` - TrackingID int64 `json:"trackingId"` - TrackingID2 int64 `json:"tracking_id"` - TransportStationID string `json:"transport_station_id"` - TransportStationTel string `json:"transport_station_tel"` -} - -func (a *API) QueryOrder(partnerOrderCode string) (queryOrderResult *QueryOrderResult, err error) { - result, err := a.AccessAPI("v2/order/query", URL, map[string]interface{}{ - "partner_order_code": partnerOrderCode, - }, true) - if err == nil { - utils.Map2StructByJson(result["data"], &queryOrderResult, false) +func (a *API) ComplaintOrder(req *ComplaintOrderReq) (err error) { + if req.PartnerOrderCode == "" && req.OrderId == "" { + return errors.New("内部订单号或者外部订单号比填写一个") } - return queryOrderResult, err + 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 } +// +//type QueryOrderResult struct { +// AbnormalCode string `json:"abnormal_code"` +// AbnormalDesc string `json:"abnormal_desc"` +// CarrierDriverID int `json:"carrier_driver_id"` +// CarrierDriverName string `json:"carrier_driver_name"` +// CarrierDriverPhone string `json:"carrier_driver_phone"` +// ComplaintDescription interface{} `json:"complaintDescription"` +// ComplaintStatus interface{} `json:"complaintStatus"` +// EstimateArriveTime int64 `json:"estimate_arrive_time"` +// EventLogDetails []struct { +// CarrierDriverName string `json:"carrier_driver_name"` +// CarrierDriverPhone string `json:"carrier_driver_phone"` +// OccurTime int64 `json:"occur_time"` +// OrderStatus int `json:"order_status"` +// } `json:"event_log_details"` +// HandleID interface{} `json:"handleId"` +// OrderID interface{} `json:"orderId"` +// OrderStatus int `json:"order_status"` +// OrderTotalDeliveryCost float64 `json:"order_total_delivery_cost"` +// OrderTotalDeliveryDiscount interface{} `json:"order_total_delivery_discount"` +// OvertimeCompensationCost float64 `json:"overtime_compensation_cost"` +// Reason string `json:"reason"` +// SupportClaims bool `json:"support_claims"` +// TrackingID int64 `json:"trackingId"` +// TrackingID2 int64 `json:"tracking_id"` +// TransportStationID string `json:"transport_station_id"` +// TransportStationTel string `json:"transport_station_tel"` +//} + func (a *API) ComplaintRider(partnerOrderCode string, orderComplaintCode int) (err error) { - _, err = a.AccessAPI("v2/order/complaint", URL, map[string]interface{}{ + orderHead := a.MakeFnRequestHead() + + bytes, err := json.Marshal(map[string]interface{}{ "partner_order_code": partnerOrderCode, "order_complaint_code": orderComplaintCode, - "order_complaint_time": time.Now().UnixNano() / 1e6, - }, true) + }) + if err != nil { + return err + } + orderHead["businessData"] = string(bytes) + _, err = a.AccessAPI(ApiURL, "cancelOrder", RequestPost, orderHead) return err } diff --git a/platformapi/fnpsapi/store.go b/platformapi/fnpsapi/store.go index d70c6cb7..f722573d 100644 --- a/platformapi/fnpsapi/store.go +++ b/platformapi/fnpsapi/store.go @@ -1,6 +1,7 @@ package fnpsapi import ( + "encoding/json" "fmt" "strings" @@ -12,24 +13,51 @@ const ( StoreExist = "该门店已存在" ) -type CreateStoreParam struct { - ChainStoreCode string `json:"chain_store_code,omitempty"` - ChainStoreName string `json:"chain_store_name,omitempty"` - ChainStoreType int `json:"chain_store_type,omitempty"` - MerchantCode string `json:"merchant_code,omitempty"` - ContactPhone string `json:"contact_phone,omitempty"` - Address string `json:"address,omitempty"` - PositionSource int `json:"position_source,omitempty"` - Longitude string `json:"longitude,omitempty"` - Latitude string `json:"latitude,omitempty"` - ServiceCode string `json:"service_code,omitempty"` +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) CreateStore(createStoreParam *CreateStoreParam) (err error) { - createStoreParam.ServiceCode = "1" - params := utils.Struct2FlatMap(createStoreParam) - _, err = a.AccessAPI("v2/chain_store", URL, params, true) - return err +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 } type GetStoreResult struct { @@ -46,15 +74,41 @@ type GetStoreResult struct { } func (a *API) GetStore(storeID string) (getStoreResult *GetStoreResult, err error) { - result, err := a.AccessAPI("v2/chain_store/query", URL, map[string]interface{}{ - "chain_store_code": []string{storeID}, - }, true) - if err == nil { - if data, ok := result["data"].([]interface{}); ok { - utils.Map2StructByJson(data[0], &getStoreResult, false) - } else { - err = fmt.Errorf(result["msg"].(string)) + 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"] != "200" { + return nil, fmt.Errorf("%s", result["msg"]) + } + + if storeData, ok := result["business_data"]; ok { + if err := json.Unmarshal([]byte(utils.Interface2String(storeData)), &getStoreResult); err != nil { + return nil, err } + } else { + err = fmt.Errorf(result["msg"].(string)) } return getStoreResult, err } @@ -76,9 +130,3 @@ func IsErrShopExist(err error) bool { } return false } - -func (a *API) UpdateStore(createStoreParam *CreateStoreParam) (err error) { - params := utils.Struct2FlatMap(createStoreParam) - _, err = a.AccessAPI("v2/chain_store/update", URL, params, true) - return err -} diff --git a/platformapi/fnpsapi_old/callback.go b/platformapi/fnpsapi_old/callback.go new file mode 100644 index 00000000..173887f4 --- /dev/null +++ b/platformapi/fnpsapi_old/callback.go @@ -0,0 +1,54 @@ +package fnpsapi + +import ( + "net/url" + + "git.rosy.net.cn/jx-callback/globals" + + "git.rosy.net.cn/baseapi/utils" +) + +type CallBackInfo struct { + AppID string `json:"app_id"` + Data string `json:"data"` + Salt int `json:"salt"` + Signature string `json:"signature"` +} + +type WayBillInfo struct { + PartnerOrderCode string `json:"partner_order_code"` + OrderStatus int `json:"order_status"` + PushTime int64 `json:"push_time"` + CarrierDriverName string `json:"carrier_driver_name"` + CarrierDriverPhone string `json:"carrier_driver_phone"` + OpenOrderCode int64 `json:"open_order_code"` + PlatformCode string `json:"platform_code"` + ErrorScene string `json:"error_scene"` + Description string `json:"description"` + ErrorCode string `json:"error_code"` + DetailDescription string `json:"detail_description"` +} + +func (a *API) GetOrderCallbackMsg(data []byte) (orderMsg *WayBillInfo) { + callbackInfo := &CallBackInfo{} + err := utils.UnmarshalUseNumber(data, callbackInfo) + if err != nil { + globals.SugarLogger.Debugf("fn msg faild %v, err : %v", string(data), err) + return nil + } + if err == nil { + if str, err := url.QueryUnescape(callbackInfo.Data); err == nil { + orderMsg = &WayBillInfo{} + if err := utils.UnmarshalUseNumber([]byte(str), orderMsg); err == nil { + return orderMsg + } else { + globals.SugarLogger.Debugf("fn msg faild3 %v", err) + return nil + } + } else { + globals.SugarLogger.Debugf("fn msg faild2 %v", err) + return nil + } + } + return orderMsg +} diff --git a/platformapi/fnpsapi_old/fnpsapi.go b/platformapi/fnpsapi_old/fnpsapi.go new file mode 100644 index 00000000..d9ea0f46 --- /dev/null +++ b/platformapi/fnpsapi_old/fnpsapi.go @@ -0,0 +1,150 @@ +package fnpsapi + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/url" + "sort" + "strings" + "sync" + "time" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + sigKey = "signature" + + TestURL = "https://exam-anubis.ele.me/anubis-webapi" + URL = "https://open-anubis.ele.me/anubis-webapi" + tokenAction = "get_access_token" +) + +type API struct { + accessToken string + appID string + appSecret string + locker sync.RWMutex + client *http.Client + config *platformapi.APIConfig +} + +func (a *API) SetToken(token string) { + a.locker.Lock() + defer a.locker.Unlock() + a.accessToken = token +} + +func New(appID, appSecret string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ + appID: appID, + appSecret: appSecret, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } +} + +func (a *API) signParam(params map[string]interface{}) (sig string) { + var valueList []string + for k, v := range params { + if k != sigKey { + if str := fmt.Sprint(v); str != "" { + valueList = append(valueList, fmt.Sprintf("%s=%s", k, str)) + } + } + } + sort.Sort(sort.StringSlice(valueList)) + valueList = append(valueList, fmt.Sprintf("secret_key=%s", a.appSecret)) + sig = strings.Join(valueList, "&") + binSig := md5.Sum([]byte(url.QueryEscape(sig))) + sig = fmt.Sprintf("%x", binSig) + return sig +} + +func (a *API) signParam2(params map[string]interface{}) (sig string) { + sb := new(strings.Builder) + sb.WriteString("app_id=") + sb.WriteString(a.appID) + sb.WriteString("&access_token=") + sb.WriteString(a.accessToken) + sb.WriteString("&data=") + sb.WriteString(params["data"].(string)) + sb.WriteString("&salt=") + sb.WriteString(utils.Int64ToStr(utils.MustInterface2Int64(params["salt"]))) + sig = sb.String() + binSig := md5.Sum([]byte(sig)) + sig = fmt.Sprintf("%x", binSig) + return sig +} + +func (a *API) AccessAPI(action string, url string, bizParams map[string]interface{}, isPost bool) (retVal map[string]interface{}, err error) { + params := make(map[string]interface{}) + params["salt"] = GetSalt() + params["app_id"] = a.appID + if action != tokenAction { + data, _ := json.Marshal(bizParams) + params["data"] = string(data) + signStr := a.signParam2(params) + params[sigKey] = signStr + } else { + signStr := a.signParam(params) + params[sigKey] = signStr + } + data, _ := json.Marshal(params) + fullURL := utils.GenerateGetURL(url, action, nil) + err = platformapi.AccessPlatformAPIWithRetry(a.client, + func() *http.Request { + var request *http.Request + if isPost { + request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data))) + } else { + request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(url, action, params), nil) + } + request.Header.Set("Content-Type", "application/json") + return request + }, + a.config, + func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + if err == nil { + if utils.MustInterface2Int64(jsonResult1["code"]) != 200 { + errLevel = platformapi.ErrLevelGeneralFail + err = utils.NewErrorCode(jsonResult1["msg"].(string), utils.Int64ToStr(utils.MustInterface2Int64(jsonResult1["code"]))) + baseapi.SugarLogger.Debugf("fnps AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) + } + retVal = jsonResult1 + } + return errLevel, err + }) + return retVal, err +} + +func GetSalt() (salt int) { + rand.Seed(time.Now().UnixNano()) + return rand.Intn(8999) + 1000 +} + +type TokenInfo struct { + AccessToken string `json:"access_token"` + AppID string `json:"app_id"` + ExpireTime int64 `json:"expire_time"` +} + +func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) { + result, err := a.AccessAPI(tokenAction, URL, nil, false) + if err == nil { + utils.Map2StructByJson(result["data"], &tokenInfo, false) + } + return tokenInfo, err +} diff --git a/platformapi/fnpsapi/fnpsapi_test.go b/platformapi/fnpsapi_old/fnpsapi_test.go similarity index 100% rename from platformapi/fnpsapi/fnpsapi_test.go rename to platformapi/fnpsapi_old/fnpsapi_test.go diff --git a/platformapi/fnpsapi/fnpsapiv3.go b/platformapi/fnpsapi_old/fnpsapiv3.go similarity index 100% rename from platformapi/fnpsapi/fnpsapiv3.go rename to platformapi/fnpsapi_old/fnpsapiv3.go diff --git a/platformapi/fnpsapi/fnpsapiv3_test.go b/platformapi/fnpsapi_old/fnpsapiv3_test.go similarity index 100% rename from platformapi/fnpsapi/fnpsapiv3_test.go rename to platformapi/fnpsapi_old/fnpsapiv3_test.go diff --git a/platformapi/fnpsapi_old/order.go b/platformapi/fnpsapi_old/order.go new file mode 100644 index 00000000..bc01b1b2 --- /dev/null +++ b/platformapi/fnpsapi_old/order.go @@ -0,0 +1,175 @@ +package fnpsapi + +import ( + "time" + + "git.rosy.net.cn/baseapi/utils" +) + +const ( + OrderCancelReson1 = 1 // 1:物流原因:订单长时间未分配骑手, + OrderCancelReson2 = 2 // 2:物流原因:分配骑手后,骑手长时间未取件 , + OrderCancelReson3 = 3 // 3:物流原因:骑手告知不配送,让取消订单, + OrderCancelReson4 = 4 // 4:商品缺货/无法出货/已售完, + OrderCancelReson5 = 5 // 5:商户联系不上门店/门店关门了, + OrderCancelReson6 = 6 // 6:商户发错单, + OrderCancelReson7 = 7 // 7:商户/顾客自身定位错误, + OrderCancelReson8 = 8 // 8:商户改其他第三方配送, + OrderCancelReson9 = 9 // 9:顾客下错单/临时不想要了, + OrderCancelReson10 = 10 // 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) + + OrderStatusAccept = 1 //系统已接单 + OrderStatusAssigned = 20 //已分配骑手 + OrderStatusArrived = 80 //已到店 + OrderStatusDelivering = 2 //配送中 + OrderStatusDelivered = 3 //已送达 + OrderStatusException = 5 //异常 +) + +type CreateOrderParam struct { + PartnerRemark string `json:"partner_remark,omitempty"` + PartnerOrderCode string `json:"partner_order_code,omitempty"` + NotifyURL string `json:"notify_url,omitempty"` + OrderType int `json:"order_type,omitempty"` + ChainStoreCode string `json:"chain_store_code,omitempty"` + TransportInfo *TransportInfo `json:"transport_info,omitempty"` + OrderAddTime int64 `json:"order_add_time,omitempty"` + OrderTotalAmount float64 `json:"order_total_amount,omitempty"` + OrderActualAmount float64 `json:"order_actual_amount,omitempty"` + OrderWeight float64 `json:"order_weight,omitempty"` + OrderRemark string `json:"order_remark,omitempty"` + IsInvoiced int `json:"is_invoiced"` + Invoice string `json:"invoice,omitempty"` + OrderPaymentStatus int `json:"order_payment_status,omitempty"` + OrderPaymentMethod int `json:"order_payment_method,omitempty"` + IsAgentPayment int `json:"is_agent_payment"` + RequirePaymentPay float64 `json:"require_payment_pay,omitempty"` + GoodsCount int `json:"goods_count,omitempty"` + RequireReceiveTime int64 `json:"require_receive_time,omitempty"` + SerialNumber string `json:"serial_number,omitempty"` + ReceiverInfo *ReceiverInfo `json:"receiver_info,omitempty"` + ItemsJSON []*ItemsJSON `json:"items_json,omitempty"` + OrderSource string `json:"order_source,omitempty"` //饿百订单传109 + ChannelOrderCode string `json:"channel_order_code,omitempty"` + CookingTime int64 `json:"cooking_time,omitempty"` + PlatformPaidTime int64 `json:"platform_paid_time,omitempty"` + PlatformCreatedTime int64 `json:"platform_created_time,omitempty"` + MerchantCode string `json:"merchant_code,omitempty"` +} + +type ReceiverInfo struct { + ReceiverName string `json:"receiver_name,omitempty"` + ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` + ReceiverSecondPhone string `json:"receiver_second_phone,omitempty"` + ReceiverAddress string `json:"receiver_address,omitempty"` + ReceiverLongitude float64 `json:"receiver_longitude,omitempty"` + ReceiverLatitude float64 `json:"receiver_latitude,omitempty"` + PositionSource int `json:"position_source,omitempty"` +} + +type TransportInfo struct { + TransportName string `json:"transport_name,omitempty"` + TransportAddress string `json:"transport_address,omitempty"` + TransportLongitude float64 `json:"transport_longitude,omitempty"` + TransportLatitude float64 `json:"transport_latitude,omitempty"` + PositionSource int `json:"position_source,omitempty"` + TransportTel string `json:"transport_tel,omitempty"` + TransportRemark string `json:"transport_remark,omitempty"` +} + +type ItemsJSON struct { + ItemID string `json:"item_id,omitempty"` + ItemName string `json:"item_name,omitempty"` + ItemQuantity int `json:"item_quantity,omitempty"` + ItemPrice float64 `json:"item_price"` + ItemActualPrice float64 `json:"item_actual_price"` + ItemSize int `json:"item_size,omitempty"` + ItemRemark string `json:"item_remark,omitempty"` + IsNeedPackage int `json:"is_need_package"` + IsAgentPurchase int `json:"is_agent_purchase"` + AgentPurchasePrice float64 `json:"agent_purchase_price,omitempty"` +} + +//https://open.ele.me/documents/%E5%88%9B%E5%BB%BA%E8%9C%82%E9%B8%9F%E8%AE%A2%E5%8D%95 +func (a *API) CreateOrder(createOrderParam *CreateOrderParam) (err error) { + params := utils.Struct2FlatMap(createOrderParam) + _, err = a.AccessAPI("v2/order", URL, params, true) + return err +} + +//order_cancel_reason_code 订单取消原因代码(1:用户取消,2:商家取消) +// order_cancel_code 订单取消编码( +// 1:物流原因:订单长时间未分配骑手, +// 2:物流原因:分配骑手后,骑手长时间未取件 , +// 3:物流原因:骑手告知不配送,让取消订单, +// 4:商品缺货/无法出货/已售完, 5:商户联系不上门店/门店关门了, 6:商户发错单, +// 7:商户/顾客自身定位错误, 8:商户改其他第三方配送, 9:顾客下错单/临时不想要了, +// 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) + +type CancelOrderParam struct { + PartnerOrderCode string `json:"partner_order_code,omitempty"` + OrderCancelReasonCode int `json:"order_cancel_reason_code,omitempty"` + OrderCancelCode int `json:"order_cancel_code,omitempty"` + OrderCancelDescription string `json:"order_cancel_description,omitempty"` + OrderCancelTime int64 `json:"order_cancel_time,omitempty"` +} + +func (a *API) CancelOrder(cancelOrderParam *CancelOrderParam) (err error) { + params := utils.Struct2FlatMap(cancelOrderParam) + _, err = a.AccessAPI("v2/order/cancel", URL, params, true) + return err +} + +func (a *API) ComplaintOrder(cancelOrderParam *CancelOrderParam) (err error) { + params := utils.Struct2FlatMap(cancelOrderParam) + _, err = a.AccessAPI("v2/order/complaint", URL, params, true) + return err +} + +type QueryOrderResult struct { + AbnormalCode string `json:"abnormal_code"` + AbnormalDesc string `json:"abnormal_desc"` + CarrierDriverID int `json:"carrier_driver_id"` + CarrierDriverName string `json:"carrier_driver_name"` + CarrierDriverPhone string `json:"carrier_driver_phone"` + ComplaintDescription interface{} `json:"complaintDescription"` + ComplaintStatus interface{} `json:"complaintStatus"` + EstimateArriveTime int64 `json:"estimate_arrive_time"` + EventLogDetails []struct { + CarrierDriverName string `json:"carrier_driver_name"` + CarrierDriverPhone string `json:"carrier_driver_phone"` + OccurTime int64 `json:"occur_time"` + OrderStatus int `json:"order_status"` + } `json:"event_log_details"` + HandleID interface{} `json:"handleId"` + OrderID interface{} `json:"orderId"` + OrderStatus int `json:"order_status"` + OrderTotalDeliveryCost float64 `json:"order_total_delivery_cost"` + OrderTotalDeliveryDiscount interface{} `json:"order_total_delivery_discount"` + OvertimeCompensationCost float64 `json:"overtime_compensation_cost"` + Reason string `json:"reason"` + SupportClaims bool `json:"support_claims"` + TrackingID int64 `json:"trackingId"` + TrackingID2 int64 `json:"tracking_id"` + TransportStationID string `json:"transport_station_id"` + TransportStationTel string `json:"transport_station_tel"` +} + +func (a *API) QueryOrder(partnerOrderCode string) (queryOrderResult *QueryOrderResult, err error) { + result, err := a.AccessAPI("v2/order/query", URL, map[string]interface{}{ + "partner_order_code": partnerOrderCode, + }, true) + if err == nil { + utils.Map2StructByJson(result["data"], &queryOrderResult, false) + } + return queryOrderResult, err +} + +func (a *API) ComplaintRider(partnerOrderCode string, orderComplaintCode int) (err error) { + _, err = a.AccessAPI("v2/order/complaint", URL, map[string]interface{}{ + "partner_order_code": partnerOrderCode, + "order_complaint_code": orderComplaintCode, + "order_complaint_time": time.Now().UnixNano() / 1e6, + }, true) + return err +} diff --git a/platformapi/fnpsapi/order_test.go b/platformapi/fnpsapi_old/order_test.go similarity index 100% rename from platformapi/fnpsapi/order_test.go rename to platformapi/fnpsapi_old/order_test.go diff --git a/platformapi/fnpsapi_old/store.go b/platformapi/fnpsapi_old/store.go new file mode 100644 index 00000000..d70c6cb7 --- /dev/null +++ b/platformapi/fnpsapi_old/store.go @@ -0,0 +1,84 @@ +package fnpsapi + +import ( + "fmt" + "strings" + + "git.rosy.net.cn/baseapi/utils" +) + +const ( + StoreNotExist = "门店信息不存在" + StoreExist = "该门店已存在" +) + +type CreateStoreParam struct { + ChainStoreCode string `json:"chain_store_code,omitempty"` + ChainStoreName string `json:"chain_store_name,omitempty"` + ChainStoreType int `json:"chain_store_type,omitempty"` + MerchantCode string `json:"merchant_code,omitempty"` + ContactPhone string `json:"contact_phone,omitempty"` + Address string `json:"address,omitempty"` + PositionSource int `json:"position_source,omitempty"` + Longitude string `json:"longitude,omitempty"` + Latitude string `json:"latitude,omitempty"` + ServiceCode string `json:"service_code,omitempty"` +} + +func (a *API) CreateStore(createStoreParam *CreateStoreParam) (err error) { + createStoreParam.ServiceCode = "1" + params := utils.Struct2FlatMap(createStoreParam) + _, err = a.AccessAPI("v2/chain_store", URL, params, true) + return err +} + +type GetStoreResult struct { + ChainStoreCode string `json:"chain_store_code"` + ChainStoreName string `json:"chain_store_name"` + Address string `json:"address"` + Latitude string `json:"latitude"` + Longitude string `json:"longitude"` + PositionSource int `json:"position_source"` + City string `json:"city"` + ContactPhone string `json:"contact_phone"` + ServiceCode string `json:"service_code"` + Status int `json:"status"` //1关店,2开店 +} + +func (a *API) GetStore(storeID string) (getStoreResult *GetStoreResult, err error) { + result, err := a.AccessAPI("v2/chain_store/query", URL, map[string]interface{}{ + "chain_store_code": []string{storeID}, + }, true) + if err == nil { + if data, ok := result["data"].([]interface{}); ok { + utils.Map2StructByJson(data[0], &getStoreResult, false) + } else { + err = fmt.Errorf(result["msg"].(string)) + } + } + return getStoreResult, 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 +} + +func (a *API) UpdateStore(createStoreParam *CreateStoreParam) (err error) { + params := utils.Struct2FlatMap(createStoreParam) + _, err = a.AccessAPI("v2/chain_store/update", URL, params, true) + return err +} diff --git a/platformapi/fnpsapi/store_test.go b/platformapi/fnpsapi_old/store_test.go similarity index 100% rename from platformapi/fnpsapi/store_test.go rename to platformapi/fnpsapi_old/store_test.go diff --git a/platformapi/fnpsapi_v3/fnClient_test.go b/platformapi/fnpsapi_v3/fnClient_test.go new file mode 100644 index 00000000..23f52d86 --- /dev/null +++ b/platformapi/fnpsapi_v3/fnClient_test.go @@ -0,0 +1,102 @@ +package fnpsapi + +import ( + "fmt" + "testing" +) + +var api *API + +func Init() { + api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") + token, _ := api.GetAccessToken() + api.accessToken = token.BusinessDataObj.AccessToken +} + +func TestClient(t *testing.T) { + api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") + token, err := api.GetAccessToken() + api.accessToken = token.BusinessDataObj.AccessToken + fmt.Println("token===", token.BusinessDataObj.AccessToken) + fmt.Println("err=====", 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 TestQueryOneStore(t *testing.T) { + api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691", "cabrXQf9eFMVWVYg4hNlwu") + token, _ := api.GetAccessToken() + api.accessToken = token.BusinessDataObj.AccessToken + data, err := api.GetStore("637910") + fmt.Println(data) + fmt.Println(err) +} diff --git a/platformapi/fnpsapi_v3/fnps_v3.go b/platformapi/fnpsapi_v3/fnps_v3.go index 2a2c3b39..5ca175d4 100644 --- a/platformapi/fnpsapi_v3/fnps_v3.go +++ b/platformapi/fnpsapi_v3/fnps_v3.go @@ -7,8 +7,10 @@ import ( ) const ( - TokenURL = "https://open-anubis.ele.me/anubis-webapi/openapi/token" - ApiURL = "https://open-anubis.ele.me/anubis-webapi/v3/invoke/" + //TokenURL = "https://open-anubis.ele.me/anubis-webapi/openapi/token" // 正式环境 + //ApiURL = "https://open-anubis.ele.me/anubis-webapi/v3/invoke"// 正式环境 + TokenURL = "https://exam-anubis.ele.me/anubis-webapi/openapi/token" // 沙箱环境 + ApiURL = "https://exam-anubis.ele.me/anubis-webapi/v3/invoke" // 沙箱环境 RequestPost = "POST" RequestGet = "GET" ) @@ -43,10 +45,11 @@ type BaseInfo struct { // 获取token type TokenInfo struct { - Sign string `json:"sign"` //返回值签名,详见开放平台侧返回值签名算法 - Code string `json:"code"` //错误码,详见开放平台侧错误码映射表 - Msg string `json:"msg"` //错误信息 - BusinessData string `json:"business_data"` // string + Sign string `json:"sign"` //返回值签名,详见开放平台侧返回值签名算法 + Code string `json:"code"` //错误码,详见开放平台侧错误码映射表 + Msg string `json:"msg"` //错误信息 + BusinessData string `json:"business_data"` // string + BusinessDataObj *BusinessData // BusinessData 对象 } type BusinessData struct { @@ -54,13 +57,13 @@ type BusinessData struct { MerchantID string `json:"merchant_id"` //商户id AccessToken string `json:"access_token"` //凭证token RefreshToken string `json:"refresh_token"` //刷新token - ExpireIn string `json:"expire_in"` //access_token剩余有效时间,单位:秒,默认有效期是一年 - ReExpireIn string `json:"re_expire_in"` //refresh_token剩余有效时间 + ExpireIn int64 `json:"expire_in"` //access_token剩余有效时间,单位:秒,默认有效期是一年 + ReExpireIn int64 `json:"re_expire_in"` //refresh_token剩余有效时间 } //#endregion -//<------------------------------------门店---------------------------------------------------------------------------------------> +//<---------------------------------------------------门店---------------------------------------------------------------------------------------> //#region 获取蜂鸟门店信息 // 获取单个门店查询 @@ -106,26 +109,26 @@ type GetOneStoreRespData struct { // 更新蜂鸟门店信息 type UpdateStoreParam struct { // 必填 - ChainStoreID string `json:"chain_store_id"` - HeadShopName string `json:"head_shop_name"` // 门店主店名 - ContactPhone string `json:"contact_phone"` // 门店联系方式 - Address string `json:"address"` // 门店地址 - Longitude string `json:"longitude"` // 纬度 - Latitude string `json:"latitude"` // 门店纬度 - PositionSource string `json:"position_source"` // 经纬度来源 坐标属性:高德地图:3;目前只支持高德地图坐标 - OutShopCode string `json:"out_shop_code"` // 外部门店编码 - CategoryID string `json:"category_id"` // 门店类目 - OwnerName string `json:"owner_name"` // 门店主店名 - OwnerIDNum string `json:"owner_id_num"` // 门店拥有人身份证号 - HandheldLicencePicHash string `json:"handheld_licence_pic_hash"` // 门店拥有人手持身份证、营业执照图片 - OwnerIDPicFrontHash string `json:"owner_id_pic_front_hash"` // 身份证正面 - OwnerIDPicBackHash string `json:"owner_id_pic_back_hash"` // 身份证反面 - CreditCode string `json:"credit_code"` // 统一社会信用代码 - BusinessLicencePicHash string `json:"business_licence_pic_hash"` // 营业执照图片 + ChainStoreID string `json:"chain_store_id"` + HeadShopName string `json:"head_shop_name"` // 门店主店名 + ContactPhone string `json:"contact_phone"` // 门店联系方式 + Address string `json:"address"` // 门店地址 + Longitude float64 `json:"longitude"` // 纬度 + Latitude float64 `json:"latitude"` // 门店纬度 + PositionSource int `json:"position_source"` // 经纬度来源 坐标属性:高德地图:3;目前只支持高德地图坐标 + OutShopCode string `json:"out_shop_code"` // 外部门店编码 + CategoryID string `json:"category_id"` // 门店类目 + OwnerName string `json:"owner_name"` // 门店主店名 + OwnerIDNum string `json:"owner_id_num"` // 门店拥有人身份证号 + HandheldLicencePicHash string `json:"handheld_licence_pic_hash"` // 门店拥有人手持身份证、营业执照图片 + OwnerIDPicFrontHash string `json:"owner_id_pic_front_hash"` // 身份证正面 + OwnerIDPicBackHash string `json:"owner_id_pic_back_hash"` // 身份证反面 + CreditCode string `json:"credit_code"` // 统一社会信用代码 + BusinessLicencePicHash string `json:"business_licence_pic_hash"` // 营业执照图片 // 选填 BranchShopName string `json:"branch_shop_name"` // 门店主店名 - ChainstoreType string `json:"chainstore_type"` + ChainstoreType int `json:"chainstore_type"` FoodLicensePicHash string `json:"food_license_pic_hash"` // 食品安全执照图片 SecondMedicalEquipmentLicensePicHash string `json:"second_medical_equipment_license_pic_hash"` // 第二类医疗器械类目必传 MedicalInstitutionLicensePicHash string `json:"medical_institution_license_pic_hash"` // 医疗机构必传 @@ -179,11 +182,11 @@ type PreCreateOrder struct { OrderType int `json:"order_type"` // 订单类型(1:即时单,3:预约单) PositionSource int `json:"position_source"` // 经纬度来源 ReceiverAddress string `json:"receiver_address"` // 收货人地址 文字描述 - ReceiverLongitude string `json:"receiver_longitude"` // 收货人经度 - ReceiverLatitude string `json:"receiver_latitude"` // 收货人纬度 + ReceiverLongitude float64 `json:"receiver_longitude"` // 收货人经度 + ReceiverLatitude float64 `json:"receiver_latitude"` // 收货人纬度 GoodsTotalAmountCent int `json:"goods_total_amount_cent"` // 订单商品总金额(分) GoodsActualAmountCent int64 `json:"goods_actual_amount_cent"` // 订单商品客户实际支付金额 - GoodsWeight string `json:"goods_weight"` // 订单总重量(kg) + GoodsWeight float64 `json:"goods_weight"` // 订单总重量(kg) GoodsCount int `json:"goods_count"` // 货物件数 GoodsItemList []*GoodsItemsList // 货物列表 @@ -223,10 +226,11 @@ type GoodsItemsList struct { // 蜂鸟请求预下单返回值 type PreCreateOrderResp struct { - Distance int64 `json:"distance"` //配送距离 - CityId int64 `json:"city_id"` // 城市id - Time int64 `json:"time"` // 预询时间戳 - GoodsInfos []*GoodsInfos // 服务商品明细 + Distance int64 `json:"distance"` //配送距离 + CityId int64 `json:"city_id"` // 城市id + Time int64 `json:"time"` // 预询时间戳 + GoodsInfos []*GoodsInfos `json:"goods_infos"` // 服务商品明细 + //GoodsInfos string `json:"goods_infos"` // 服务商品明细 } // 预下单商品服务明细 @@ -279,13 +283,13 @@ type CreateOrderReqParam struct { GoodsItemList []*GoodsItemsList `json:"goods_item_list,omitempty"` // 货物明细 ReceiverName string `json:"receiver_name,omitempty"` // 收货人姓名 ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` // 只支持手机号,400开头电话,座机号码以及95013开头、长度13位的虚拟电话 + OutShopCode string `json:"out_shop_code,omitempty"` // 外部门店id 当使用门店发单 out_shop_code和chain_store_id必填1个 + ChainStoreId string `json:"chain_store_id,omitempty"` // 门店id // 选传参数 TransportLongitude float64 `json:"transport_longitude,omitempty"` // 取货经度 TransportLatitude float64 `json:"transport_latitude,omitempty"` // 取货纬度 TransportTel string `json:"transport_tel,omitempty"` // 取货点联系人电话 - OutShopCode string `json:"out_shop_code,omitempty"` // 外部门店id 当使用门店发单 out_shop_code和chain_store_id必填1个 - ChainStoreId int64 `json:"chain_store_id,omitempty"` // 门店id OrderSource string `json:"order_source,omitempty"` // 饿百订单传109 商户订单来源(如饿了么、美团等)手发单/未知来源: 0 或不传 美团: 2 口碑: 4 饿了么: 6支付宝: 7 饿百: 8 ServiceGoodsId int64 `json:"service_goods_id,omitempty"` // 服务商品id 校验预询配送费价格时 必传 BaseGoodsId int64 `json:"base_goods_id,omitempty"` // 基础商品id 校验预询配送费价格时 必传 @@ -323,33 +327,41 @@ type GetOrderDetailReq struct { // 查询订单详情接口返回值 type GetOrderDetailRes struct { - OrderId int64 `json:"order_id"` // 订单id - TrackingId int64 `json:"tracking_id"` // 运单id - PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 - SerialNumber string `json:"serial_number"` // 商家订单流水号, 方便配送骑手 到店取货, 支持数字,字母及#等常见字符. 建议填写。长度<=6。 - OrderStatus int `json:"order_status"` // 订单生成0,运单生成成功1,20:骑手接单,80:骑手到店,2:配送中,3:已完成,4:已取消,5:配送异常 - CarrierDriverId string `json:"carrier_driver_id"` // 配送员id - CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 - CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送员电话 - EstimateArriveTime int64 `json:"estimate_arrive_time"` // 预计送达时间(毫秒) - OvertimeCompensationCostCent int64 `json:"overtime_compensation_cost_cent"` // 时效赔付 - IfCanAddTip int `json:"if_can_add_tip"` // 是否支持添加调度费 1可以,0不可以 - OrderTipAmountCent int64 `json:"order_tip_amount_cent"` // 订单当前小费总金额 分 - DeliveryFetchPhotos []interface{} `json:"delivery_fetch_photos"` // 骑手取货照片地址 - OrderTotalAmountCent int64 `json:"order_total_amount_cent"` // 原始配送费金额(分) - OrderActualAmountCent int64 `json:"order_actual_amount_cent"` // 订单实际配送支付总金额关注这个字段即可(分) - PriceDetail []*struct{} `json:"price_detail"` // 配送费价格明细 - AbnormalCode string `json:"abnormal_code"` // 运单异常原因code - AbnormalDesc string `json:"abnormal_desc"` // 运单异常原因描述 - EventLogDetails []*struct{} `json:"event_log_details"` // 运单事件节点信息 - ComplaintId int64 `json:"complaint_id"` // 投诉编号 - ComplaintReasonDesc string `json:"complaint_reason_desc"` // 投诉原因描述 - ComplaintStatus int `json:"complaint_status"` // 投诉状态 1待处理, 2成功,3失败 - ClaimId int64 `json:"claim_id"` // 索赔id - ClaimReasonDesc string `json:"claim_reason_desc"` // 索赔原因描述 - ClaimStatus int `json:"claim_status"` // 索赔状态 1待处理, 2成功,3失败 - Temperature string `json:"temperature"` // 骑手体温 - OrderDistance float64 `json:"order_distance"` // 配送距离(米) + OrderId int64 `json:"order_id"` // 订单id + TrackingId int64 `json:"tracking_id"` // 运单id + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + SerialNumber string `json:"serial_number"` // 商家订单流水号, 方便配送骑手 到店取货, 支持数字,字母及#等常见字符. 建议填写。长度<=6。 + OrderStatus int `json:"order_status"` // 订单生成0,运单生成成功1,20:骑手接单,80:骑手到店,2:配送中,3:已完成,4:已取消,5:配送异常 + CarrierDriverId string `json:"carrier_driver_id"` // 配送员id + CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送员电话 + EstimateArriveTime int64 `json:"estimate_arrive_time"` // 预计送达时间(毫秒) + OvertimeCompensationCostCent int64 `json:"overtime_compensation_cost_cent"` // 时效赔付 + IfCanAddTip int `json:"if_can_add_tip"` // 是否支持添加调度费 1可以,0不可以 + OrderTipAmountCent int64 `json:"order_tip_amount_cent"` // 订单当前小费总金额 分 + DeliveryFetchPhotos []interface{} `json:"delivery_fetch_photos"` // 骑手取货照片地址 + OrderTotalAmountCent int64 `json:"order_total_amount_cent"` // 原始配送费金额(分) + OrderActualAmountCent int64 `json:"order_actual_amount_cent"` // 订单实际配送支付总金额关注这个字段即可(分) + PriceDetail []*PriceOpenapiDetail `json:"price_detail"` // 配送费价格明细 + AbnormalCode string `json:"abnormal_code"` // 运单异常原因code + AbnormalDesc string `json:"abnormal_desc"` // 运单异常原因描述 + EventLogDetails []*OrderNodeInfo `json:"event_log_details"` // 运单事件节点信息 + ComplaintId int64 `json:"complaint_id"` // 投诉编号 + ComplaintReasonDesc string `json:"complaint_reason_desc"` // 投诉原因描述 + ComplaintStatus int `json:"complaint_status"` // 投诉状态 1待处理, 2成功,3失败 + ClaimId int64 `json:"claim_id"` // 索赔id + ClaimReasonDesc string `json:"claim_reason_desc"` // 索赔原因描述 + ClaimStatus int `json:"claim_status"` // 索赔状态 1待处理, 2成功,3失败 + Temperature string `json:"temperature"` // 骑手体温 + OrderDistance float64 `json:"order_distance"` // 配送距离(米) +} + +// 点单详情信息,订单结点信息 +type OrderNodeInfo struct { + OrderStatus int `json:"order_status"` // 订单状态 + OccurTime int64 `json:"occur_time"` // 时间结点 + CarrierDriverName string `json:"carrier_driver_name"` // 配送员姓名 + CarrierDriverPhone string `json:"carrier_driver_phone"` // 配送电话 } // getKnightInfo 查询骑手信息接口,参数 GetOrderDetailReq @@ -370,9 +382,10 @@ type GetKnightInfoRes struct { //另:联调环境使用加小费接口 需先使用联调工具->订单状态回调:把订单状态改为1,否则订单状态会不支持加小费 // 添加小费 type AddTipRes struct { - GetOrderDetailReq - AddTipAmountCent int64 `json:"add_tip_amount_cent"` // 小费金额分(必填) - ThirdIndexId int64 `json:"third_index_id"` // 本次加小费唯一标识 每个订单内 不可重复, 会用来做幂等,相同third_index_id的后续请求会被忽略 + OrderId string `json:"order_id"` // 订单号 订单号和外部订单号必填1个 + PartnerOrderCode string `json:"partner_order_code"` // 外部订单号 + AddTipAmountCent int64 `json:"add_tip_amount_cent"` // 小费金额分(必填) + ThirdIndexId int64 `json:"third_index_id"` // 本次加小费唯一标识 每个订单内 不可重复, 会用来做幂等,相同third_index_id的后续请求会被忽略 } // 业务出参 "business_data": "{\"result\":true}" @@ -390,6 +403,12 @@ type GetCancelReasonListRes struct { } } +// preCancelOrder 预取消订单接口 +type PreCancelOrderReq struct { + GetOrderDetailReq + OrderCancelCode int64 `json:"order_cancel_code"` // 从可用取消原因列表接口返回结果选择(必填) +} + // cancelOrder 正式取消订单接口 type CancelOrderReq struct { GetOrderDetailReq @@ -428,3 +447,5 @@ type OrderClaimSkuDto struct { } //#endregion + +//#endregion diff --git a/platformapi/fnpsapi_v3/fnpsapi.go b/platformapi/fnpsapi_v3/fnpsapi.go index b301c017..463cb3a3 100644 --- a/platformapi/fnpsapi_v3/fnpsapi.go +++ b/platformapi/fnpsapi_v3/fnpsapi.go @@ -2,17 +2,17 @@ package fnpsapi import ( "crypto/sha256" - "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" "net/http" + "sort" "strings" "sync" "time" - - "git.rosy.net.cn/baseapi/platformapi" - "git.rosy.net.cn/baseapi/utils" ) func (a *API) SetToken(token string) { @@ -32,19 +32,20 @@ func (a *API) MakeFnRequestHead() map[string]interface{} { return requestParam } -func New(appID, appSecret, merchantId string, config ...*platformapi.APIConfig) *API { +func New(appID, appSecret, merchantId, code string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ grantType: "authorization_code", // 授权模式,填固定值authorization_code - code: "cod3", + code: code, appID: appID, merchantId: merchantId, signature: "", timestamp: time.Now().UnixNano(), accessToken: "", + version: "1.0", appSecret: appSecret, locker: sync.RWMutex{}, client: &http.Client{Timeout: curConfig.ClientTimeout}, @@ -52,25 +53,50 @@ func New(appID, appSecret, merchantId string, config ...*platformapi.APIConfig) } } -// signParam2 apiV3签名算法SHA-256 -func (a *API) signParam2() (sig string) { - sb := new(strings.Builder) - sb.WriteString("grant_type=") - sb.WriteString(a.grantType) - sb.WriteString("&code=") - sb.WriteString(a.code) //todo - sb.WriteString("&app_id=") - sb.WriteString(a.appID) - sb.WriteString("&merchant_id=") - sb.WriteString(a.merchantId) - sb.WriteString("×tamp=") - sb.WriteString(utils.Int64ToStr(a.timestamp)) - sig = sb.String() - sig = base64.StdEncoding.EncodeToString(sha256.New().Sum([]byte(sig))) - return sig +func (a *API) signParam(params map[string]interface{}) (sig string) { + var valueList []string + for k, v := range params { + if k != "signature" { + if str := fmt.Sprint(v); str != "" { + valueList = append(valueList, fmt.Sprintf("%s=%s", k, str)) + } + } + } + sort.Sort(sort.StringSlice(valueList)) + sig = strings.Join(valueList, "&") + sig = a.appSecret + sig + signature := sha256.Sum256([]byte(sig)) + return hex.EncodeToString(signature[:]) +} + +// 获取access_token +func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) { + parameter := make(map[string]interface{}, 6) + parameter["grant_type"] = a.grantType + parameter["code"] = a.code + parameter["app_id"] = a.appID + parameter["merchant_id"] = a.merchantId + parameter["timestamp"] = utils.Int64ToStr(a.timestamp) + + result, err := a.AccessAPI(TokenURL, "", RequestPost, parameter) + 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 } func (a *API) AccessAPI(baseUrl, actionApi, method string, bizParams map[string]interface{}) (retVal map[string]interface{}, err error) { + a.signature = a.signParam(bizParams) + bizParams["signature"] = a.signature // 序列化 data, err := json.Marshal(bizParams) if err != nil { @@ -83,7 +109,7 @@ func (a *API) AccessAPI(baseUrl, actionApi, method string, bizParams map[string] // 发送请求 sendUrl := func() *http.Request { var request *http.Request - if method == "POST" { + if RequestPost == method { request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(string(data))) } else { request, _ = http.NewRequest(http.MethodGet, utils.GenerateGetURL(baseUrl, actionApi, bizParams), nil) diff --git a/platformapi/fnpsapi_v3/order.go b/platformapi/fnpsapi_v3/order.go index 25207e91..bdbf4595 100644 --- a/platformapi/fnpsapi_v3/order.go +++ b/platformapi/fnpsapi_v3/order.go @@ -2,7 +2,9 @@ package fnpsapi import ( "encoding/json" + "errors" "git.rosy.net.cn/baseapi/utils" + "time" ) const ( @@ -28,9 +30,13 @@ const ( // 蜂鸟预下单 func (a *API) PreCreateByShopFn(basicParams *PreCreateOrder) (deliveryFee, baseDeliveryFee int64, err error) { preOrder := a.MakeFnRequestHead() - params := utils.Struct2MapByJson(basicParams) - preOrder["business_data"] = params - result, err := a.AccessAPI(ApiURL, "preCreateOrder", RequestPost, params) + + 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 } @@ -40,8 +46,19 @@ func (a *API) PreCreateByShopFn(basicParams *PreCreateOrder) (deliveryFee, baseD return 0, 0, err } - deliveryFee = utils.Float64TwoInt64(utils.MustInterface2Float64(preOrderResult.GoodsInfos[0].ActualDeliveryAmountCent)) // 订单配送价格 - baseDeliveryFee = utils.Float64TwoInt64(utils.MustInterface2Float64(preOrderResult.GoodsInfos[0].TotalDeliveryAmountCent)) // 订单优惠价格 + // 返回所有可选的商品列表,每项包含当前使用该商品下单时对应的价格等信息,(其中不可用的商品会返回不可用原因) 可挑选其中一个可用的商品进行正式下单。 + for _, v := range preOrderResult.GoodsInfos { + if v.IsValid != 1 { + continue + } + deliveryFee = v.ActualDeliveryAmountCent + baseDeliveryFee = v.TotalDeliveryAmountCent + } + + // 异常检测 + if deliveryFee == 0 && baseDeliveryFee == 0 { + err = errors.New("门店数据异常") + } return deliveryFee, baseDeliveryFee, nil } @@ -57,114 +74,193 @@ func (a *API) CreateOrder(createOrderParam *CreateOrderReqParam) (err error) { return err } -type ReceiverInfo struct { - ReceiverName string `json:"receiver_name,omitempty"` - ReceiverPrimaryPhone string `json:"receiver_primary_phone,omitempty"` - ReceiverSecondPhone string `json:"receiver_second_phone,omitempty"` - ReceiverAddress string `json:"receiver_address,omitempty"` - ReceiverLongitude float64 `json:"receiver_longitude,omitempty"` - ReceiverLatitude float64 `json:"receiver_latitude,omitempty"` - PositionSource int `json:"position_source,omitempty"` // 经纬度来源 +// 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 } -type TransportInfo struct { - TransportName string `json:"transport_name,omitempty"` - TransportAddress string `json:"transport_address,omitempty"` - TransportLongitude float64 `json:"transport_longitude,omitempty"` - TransportLatitude float64 `json:"transport_latitude,omitempty"` - PositionSource int `json:"position_source,omitempty"` // 经纬度来源 - TransportTel string `json:"transport_tel,omitempty"` - TransportRemark string `json:"transport_remark,omitempty"` +// 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 } -type ItemsJSON struct { - ItemID string `json:"item_id,omitempty"` - ItemName string `json:"item_name,omitempty"` - ItemQuantity int `json:"item_quantity,omitempty"` - ItemPrice float64 `json:"item_price"` - ItemActualPrice float64 `json:"item_actual_price"` - ItemSize int `json:"item_size,omitempty"` - ItemRemark string `json:"item_remark,omitempty"` - IsNeedPackage int `json:"is_need_package"` - IsAgentPurchase int `json:"is_agent_purchase"` - AgentPurchasePrice float64 `json:"agent_purchase_price,omitempty"` +// 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 { + respParam := struct { + ActualCancelCostCent int64 `json:"actual_cancel_cost_cent"` + }{} + if err := json.Unmarshal([]byte(utils.Interface2String(k)), respParam); err != nil { + return 0, err + } + return respParam.ActualCancelCostCent, nil + } + return } -//order_cancel_reason_code 订单取消原因代码(1:用户取消,2:商家取消) -// order_cancel_code 订单取消编码( -// 1:物流原因:订单长时间未分配骑手, -// 2:物流原因:分配骑手后,骑手长时间未取件 , -// 3:物流原因:骑手告知不配送,让取消订单, -// 4:商品缺货/无法出货/已售完, 5:商户联系不上门店/门店关门了, 6:商户发错单, -// 7:商户/顾客自身定位错误, 8:商户改其他第三方配送, 9:顾客下错单/临时不想要了, -// 10:顾客自取/不在家/要求另改时间配送)(0类型已下线) +// cancelOrder 取消订单 +func (a *API) CancelOrder(req *CancelOrderReq) (err error) { + if req.PartnerOrderCode == "" && req.OrderId == "" { + return errors.New("内部订单号或者外部订单号比填写一个") + } + if req.OrderCancelCode == 0 && req.OrderCancelOtherReason == "" { + return errors.New("请补充退单原因") + } -type CancelOrderParam struct { - PartnerOrderCode string `json:"partner_order_code,omitempty"` - OrderCancelReasonCode int `json:"order_cancel_reason_code,omitempty"` - OrderCancelCode int `json:"order_cancel_code,omitempty"` - OrderCancelDescription string `json:"order_cancel_description,omitempty"` - OrderCancelTime int64 `json:"order_cancel_time,omitempty"` + 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) CancelOrder(cancelOrderParam *CancelOrderParam) (err error) { -// params := utils.Struct2FlatMap(cancelOrderParam) -// _, err = a.AccessAPI("v2/order/cancel", URL, params, true) -// return err -//} -// -//func (a *API) ComplaintOrder(cancelOrderParam *CancelOrderParam) (err error) { -// params := utils.Struct2FlatMap(cancelOrderParam) -// _, err = a.AccessAPI("v2/order/complaint", URL, params, true) -// return err -//} +// 查询订单详情接口 +func (a *API) QueryOrderDaile(req *GetOrderDetailReq) (result *GetOrderDetailRes, err error) { + if req.PartnerOrderCode == "" && req.OrderId == "" { + return nil, errors.New("内部订单号或者外部订单号比填写一个") + } -type QueryOrderResult struct { - AbnormalCode string `json:"abnormal_code"` - AbnormalDesc string `json:"abnormal_desc"` - CarrierDriverID int `json:"carrier_driver_id"` - CarrierDriverName string `json:"carrier_driver_name"` - CarrierDriverPhone string `json:"carrier_driver_phone"` - ComplaintDescription interface{} `json:"complaintDescription"` - ComplaintStatus interface{} `json:"complaintStatus"` - EstimateArriveTime int64 `json:"estimate_arrive_time"` - EventLogDetails []struct { - CarrierDriverName string `json:"carrier_driver_name"` - CarrierDriverPhone string `json:"carrier_driver_phone"` - OccurTime int64 `json:"occur_time"` - OrderStatus int `json:"order_status"` - } `json:"event_log_details"` - HandleID interface{} `json:"handleId"` - OrderID interface{} `json:"orderId"` - OrderStatus int `json:"order_status"` - OrderTotalDeliveryCost float64 `json:"order_total_delivery_cost"` - OrderTotalDeliveryDiscount interface{} `json:"order_total_delivery_discount"` - OvertimeCompensationCost float64 `json:"overtime_compensation_cost"` - Reason string `json:"reason"` - SupportClaims bool `json:"support_claims"` - TrackingID int64 `json:"trackingId"` - TrackingID2 int64 `json:"tracking_id"` - TransportStationID string `json:"transport_station_id"` - TransportStationTel string `json:"transport_station_tel"` + orderHead := a.MakeFnRequestHead() + bytes, err := json.Marshal(req) + 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 + } + + if err := json.Unmarshal([]byte(data["business_data"].(string)), &result); err != nil { + return nil, err + } + + return } -// -//func (a *API) QueryOrder(partnerOrderCode string) (queryOrderResult *QueryOrderResult, err error) { -// result, err := a.AccessAPI("v2/order/query", URL, map[string]interface{}{ -// "partner_order_code": partnerOrderCode, -// }, true) -// if err == nil { -// utils.Map2StructByJson(result["data"], &queryOrderResult, false) -// } -// return queryOrderResult, err -//} -// -//func (a *API) ComplaintRider(partnerOrderCode string, orderComplaintCode int) (err error) { -// _, err = a.AccessAPI("v2/order/complaint", URL, map[string]interface{}{ -// "partner_order_code": partnerOrderCode, -// "order_complaint_code": orderComplaintCode, -// "order_complaint_time": time.Now().UnixNano() / 1e6, -// }, true) -// return err -//} +// 查询骑手信息 +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 + } + + if err := json.Unmarshal([]byte(utils.Interface2String(data["business_data"])), &result); err != nil { + return nil, err + } + return +} + +// 余额查询 +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 +} + +// complaintOrder 投诉订单 +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 +} + +// claimOrder 索赔订单 +func (a *API) ClaimOrder(req *ClaimOrderRes) (err error) { + if req.PartnerOrderCode == "" && req.OrderId == "" { + return errors.New("内部订单号或者外部订单号比填写一个") + } + orderHead := a.MakeFnRequestHead() + bytes, err := json.Marshal(req) + if err != nil { + return err + } + orderHead["business_data"] = string(bytes) + _, err = a.AccessAPI(ApiURL, "claimOrder", RequestPost, orderHead) + if err != nil { + return err + } + + return nil +} diff --git a/platformapi/fnpsapi_v3/order_test.go b/platformapi/fnpsapi_v3/order_test.go new file mode 100644 index 00000000..1d743f60 --- /dev/null +++ b/platformapi/fnpsapi_v3/order_test.go @@ -0,0 +1,98 @@ +package fnpsapi + +import ( + "fmt" + "testing" + "time" +) + +// 预下单 +func TestPreOrder(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, + //OutShopCode: "637910", + ChainStoreID: "209476483", + }) + fmt.Println(err) + fmt.Println(deliveryFee) + fmt.Println(baseDeliveryFee) +} + +// 正式下单 +func TestCreateOrder(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"}, + } + err := api.CreateOrder(&CreateOrderReqParam{ + PartnerOrderCode: "232232999uue21", + OrderType: 1, + PositionSource: 3, + ReceiverAddress: "四川成都", + ReceiverLongitude: 113.546508, + ReceiverLatitude: 22.188382, + GoodsTotalAmountCent: 100, + GoodsActualAmountCent: 100, + GoodsWeight: 0.25, + GoodsCount: 1, + GoodsItemList: aa, + ReceiverName: "刘磊", + ReceiverPrimaryPhone: "18981810340", + OutShopCode: "", + ChainStoreId: "200008235", + }) + fmt.Println(err) +} + +// 添加小费 +func TestAddTip(t *testing.T) { + api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691") + token, _ := api.GetAccessToken() + api.accessToken = token.BusinessDataObj.AccessToken + + param := &AddTipRes{} + param.OrderId = "817102016000041" + param.PartnerOrderCode = "817102016000041" + param.AddTipAmountCent = 100 + param.ThirdIndexId = time.Now().Unix() + err := api.AddTip(param) + fmt.Println(err) +} + +// 获取可用取消订单原因 +func TestGetCancelReasonList(t *testing.T) { + api = New("6705486294797503379", "c1e6c280-e618-4103-9d0a-673bc54fb22e", "5375691") + token, _ := api.GetAccessToken() + api.accessToken = token.BusinessDataObj.AccessToken + + data, err := api.GetCancelReasonList(&GetOrderDetailReq{ + OrderId: "300000219758073736", + //PartnerOrderCode: "817102016000041", + }) + fmt.Println(err) + fmt.Println(data) +} diff --git a/platformapi/fnpsapi_v3/store.go b/platformapi/fnpsapi_v3/store.go index 97ced1f4..9055f952 100644 --- a/platformapi/fnpsapi_v3/store.go +++ b/platformapi/fnpsapi_v3/store.go @@ -4,43 +4,29 @@ import ( "encoding/json" "fmt" "git.rosy.net.cn/baseapi/utils" - "strings" ) -const ( - StoreNotExist = "门店信息不存在" - StoreExist = "该门店已存在" -) - -// 获取access_token -func (a *API) GetAccessToken() (tokenInfo *TokenInfo, err error) { - a.grantType = "authorization_code" - a.signature = a.signParam2() - result, err := a.AccessAPI(TokenURL, "", RequestPost, nil) - if err != nil { - return nil, err - } - if err := utils.Map2StructByJson(result["data"], &tokenInfo, false); err != nil { - return nil, err - } - - return tokenInfo, err -} - // 创建门店. func (a *API) CreateStore(createStoreParam *CreateStoreBaseInfo) (err error) { requestHead := a.MakeFnRequestHead() - params := utils.Struct2FlatMap(createStoreParam) - requestHead["business_data"] = params + storeByte, err := json.Marshal(createStoreParam) + if err != nil { + return err + } + requestHead["business_data"] = string(storeByte) _, err = a.AccessAPI(ApiURL, "chainstoreCreate", RequestPost, requestHead) + return err } -// 更新门店 +// 更新门店 209636747 func (a *API) UpdateStore(updateStore *UpdateStoreParam) (err error) { requestHead := a.MakeFnRequestHead() - params := utils.Struct2FlatMap(updateStore) - requestHead["business_data"] = params + storeByte, err := json.Marshal(updateStore) + if err != nil { + return err + } + requestHead["business_data"] = string(storeByte) _, err = a.AccessAPI(ApiURL, "chainstoreUpdate", RequestPost, requestHead) return } @@ -67,7 +53,7 @@ func (a *API) GetStore(storeID string) (getStoreResult *GetOneStoreRespData, err params.BusinessData = string(data) paramsMap := utils.Struct2FlatMap(params) - result, err := a.AccessAPI(ApiURL, "chainstoreQuery", RequestGet, paramsMap) + result, err := a.AccessAPI(ApiURL, "chainstoreQuery", RequestPost, paramsMap) if err != nil { return nil, err } @@ -76,30 +62,12 @@ func (a *API) GetStore(storeID string) (getStoreResult *GetOneStoreRespData, err return nil, fmt.Errorf("%s", result["msg"]) } - if data, ok := result["business_data"].([]interface{}); ok { - utils.Map2StructByJson(data[0], &getStoreResult, false) + if storeData, ok := result["business_data"]; ok { + if err := json.Unmarshal([]byte(utils.Interface2String(storeData)), &getStoreResult); err != nil { + return nil, err + } } else { err = fmt.Errorf(result["msg"].(string)) } return getStoreResult, 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 -} - -//todo商户门店接口,图片上传接口 diff --git a/platformapi/mtpsapi/mtpsapi.go b/platformapi/mtpsapi/mtpsapi.go index 6dae83ca..5a03ca0b 100644 --- a/platformapi/mtpsapi/mtpsapi.go +++ b/platformapi/mtpsapi/mtpsapi.go @@ -432,7 +432,7 @@ type PreCreateByShopParam struct { PayTypeCode int `json:"pay_type_code"` GoodsValue float64 `json:"goods_value"` GoodsWeight float64 `json:"goods_weight"` - ExpectedDeliveryTime int64 `json:"expected_delivery_time,omitempty"` + ExpectedDeliveryTime int64 `json:"expected_delivery_time"` // 以下为可选参数