From 930cafc1e2257b4439034785bb60a95fc1b8586a Mon Sep 17 00:00:00 2001 From: kanade Date: Mon, 5 Dec 2022 10:16:45 +0800 Subject: [PATCH] init --- address/address.go | 9 ++ api.go | 16 +++ config/config.go | 38 ++++++ go.mod | 5 + go.sum | 2 + mq/mq.go | 283 +++++++++++++++++++++++++++++++++++++++ order/afterSale.go | 234 ++++++++++++++++++++++++++++++++ order/order.go | 289 ++++++++++++++++++++++++++++++++++++++++ request/client.go | 324 +++++++++++++++++++++++++++++++++++++++++++++ request/request.go | 37 ++++++ sku/sku.go | 297 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1534 insertions(+) create mode 100644 address/address.go create mode 100644 api.go create mode 100644 config/config.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 mq/mq.go create mode 100644 order/afterSale.go create mode 100644 order/order.go create mode 100644 request/client.go create mode 100644 request/request.go create mode 100644 sku/sku.go diff --git a/address/address.go b/address/address.go new file mode 100644 index 0000000..21553b8 --- /dev/null +++ b/address/address.go @@ -0,0 +1,9 @@ +package address + +type Address struct { + ProvinceId uint `json:"provinceId"` + CityId uint `json:"cityId"` + CountyId uint `json:"countyId"` + TownId uint `json:"townId"` + FullAddress string `json:"fullAddress"` +} diff --git a/api.go b/api.go new file mode 100644 index 0000000..4c0aafd --- /dev/null +++ b/api.go @@ -0,0 +1,16 @@ +package jdsdk + +import ( + "git.oa00.com/go/jdsdk/mq" + "git.oa00.com/go/jdsdk/order" + "git.oa00.com/go/jdsdk/sku" +) + +var Api = &api{} + +type api struct { + Sku sku.Sku + Order order.Order + Mq mq.Mq + AfterSale order.AfterSale +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..c397051 --- /dev/null +++ b/config/config.go @@ -0,0 +1,38 @@ +package config + +var SdkConfig = Config{} + +// ConfigInit @Title 初始化配置 +func ConfigInit(config Config) { + SdkConfig = config +} + +type Config struct { + Url string // api地址 + TokenUrl string // api + MqUrl string // mq地址 + AppKey string // appKey + AppSecret string // appSecret + AccessToken string // accessToken + RefreshToken string // refreshToken + ChannelId uint // channelId + CustomerId uint // customerId + OpName string // 账号 + Pin string // 下单账号 + AccountId uint // 账号id + AccessKey string // accessKey + SecretKey string // secretKey + RefreshTokenCallback func(token *Bearer) +} + +type Bearer struct { + AccessToken string `json:"access_token"` + ExpiresIn uint `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + UID string `json:"uid"` + Time int64 `json:"time"` + TokenType string `json:"token_type"` + Code uint `json:"code"` + Xid string `json:"xid"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..582d96a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.oa00.com/go/jdsdk + +go 1.17 + +require github.com/mitchellh/mapstructure v1.5.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..59f4b8e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= diff --git a/mq/mq.go b/mq/mq.go new file mode 100644 index 0000000..3aa7032 --- /dev/null +++ b/mq/mq.go @@ -0,0 +1,283 @@ +package mq + +import ( + "git.oa00.com/go/jdsdk/request" + "strconv" +) + +const ( + skuChange = "sku_change" + skuPriceChange = "sku_price_change" + orderCreate = "order_create" + orderPay = "order_pay" + orderStockOut = "order_stockout" + orderDelivered = "order_delivered" + orderFinish = "order_finish" + orderCancel = "order_cancel" + orderRefund = "order_refund" + afsCreate = "afs_create" + afsStepResult = "afs_step_result" + balanceEarlyWarning = "order_balance_not_enough" + + AckSuccess = "SUCCESS" + AckFailed = "CONSUME_FAILED" + AckRESEND = "RESEND" + AckDISCARD = "DISCARD" + + SkuTypeDelete = 0 // 删除 + SkuTypeAdd = 1 // 新增 + SkuTypeEdit = 2 // 修改 + SkuTypeRecovery = 3 // 删除恢复 + + OrderModelNormal = 1 // 正常订单 + OrderModelAfter = 2 // 售后订单 + + OrderCancelStatusSuccess = 1 // 取消成功 + OrderCancelStatusFail = 2 // 取消失败 + OrderCancelStatusApply = 3 // 申请取消 + OrderCancelStatusReject = 4 // 申请拒收 +) + +type Mq struct { +} + +type MessageSku struct { + Type int `json:"type"` // 0=删除 1=新增 2=修改 3=删除 + SkuId uint64 `json:"skuId"` + Timestamp int64 `json:"timestamp"` +} + +// SkuChange @Title sku变动 +func (m *Mq) SkuChange(handler func(message request.Message)) { + run(skuChange, handler) +} + +// SkuPriceChange @Title sku价格变动 +func (m *Mq) SkuPriceChange(handler func(message request.Message)) { + run(skuPriceChange, handler) +} + +type OrderCreate struct { + OrderId uint64 `json:"orderId"` // 订单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + AutoCancelSeconds string `json:"autoCancelSeconds"` // 订单自动取消时间 秒数 + Yn uint `json:"yn"` // 是否有效 1=有效 0=无效 + CreateTime string `json:"createTime"` // 订单创建时间 Y/m/d H:i + TotalFee float64 `json:"totalFee"` // 总金额 包含运费 + FreightFee string `json:"freightFee"` // 运费 + Timestamp int64 `json:"timestamp"` // 请求时间 + SkuList []OrderSkuItem `json:"skuList"` // sku列表 + ChannelOrderId string `json:"channelOrderId"` // 渠道订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 + OrderModel int `json:"orderModel"` // 订单模式 1=正常订单 2=售后订单 + DiscountFee float64 `json:"discountFee"` // 优惠金额 + AfsOrderInfo OrderAfsItem `json:"afsOrderInfo"` // 售后单信息 +} +type OrderAfsItem struct { + OrderId uint64 `json:"orderId"` // 售后订单号 + ParentOrderId uint64 `json:"parentOrderId"` // 售后父订单号 + ServiceId uint64 `json:"serviceId"` // 售后单号 +} +type OrderSkuItem struct { + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 商品名称 + Quantity int `json:"quantity"` // 数量 + Price float64 `json:"price"` // 单价 + SkuType int `json:"skuType"` // 商品类型 1=主品 2=赠品 + MainSkuId uint64 `json:"mainSkuId"` // 赠品对应主品skuId +} + +// OrderCreate @Title 订单创建 +func (m *Mq) OrderCreate(handler func(message request.Message)) { + run(orderCreate, handler) +} + +type OrderPay struct { + OrderId uint64 `json:"orderId"` // 订单号 + PayTime string `json:"payTime"` // 支付时间 + RealPayFee float64 `json:"realPayFee"` // 实际支付金额 + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 +} + +// OrderPay @Title 订单支付 +func (m *Mq) OrderPay(handler func(message request.Message)) { + run(orderPay, handler) +} + +type OrderStockOut struct { + OrderId uint64 `json:"orderId"` // 订单号 + Packages []Package `json:"packages"` // 包裹 + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 +} + +type Package struct { + OutboundTime string `json:"outboundTime"` // 出库时间 + LogisticsCode string `json:"logisticsCode"` // 物流编码 + LogisticsName string `json:"logisticsName"` // 物流名称 + WaybillCode string `json:"waybillCode"` // 运单号 + OutLogisticsCode string `json:"outLogisticsCode"` // 物流外部编码 + SkuList []PackageSku `json:"skuList"` // sku列表 +} + +type PackageSku struct { + SkuId string `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 名称 + Quantity uint `json:"quantity"` // 数量 +} + +// GetSkuIdUint64 @Title 获取uint64 +func (p *PackageSku) GetSkuIdUint64() uint64 { + skuId, _ := strconv.ParseUint(p.SkuId, 10, 64) + return skuId +} + +// OrderStockOut @Title 订单出库 +func (m *Mq) OrderStockOut(handler func(message request.Message)) { + run(orderStockOut, handler) +} + +type OrderDelivered struct { + OrderId uint64 `json:"orderId"` // 订单号 + OpeTime string `json:"opeTime"` // 妥投时间 Y/m/d H:i + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 +} + +// OrderDelivered @Title 订单妥投 +func (m *Mq) OrderDelivered(handler func(message request.Message)) { + run(orderDelivered, handler) +} + +type OrderFinish struct { + OrderId uint64 `json:"orderId"` // 订单号 + FinishedTime string `json:"finishedTime"` // 完成时间 Y/m/d H:i + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 +} + +// OrderFinish @Title 订单完成 +func (m *Mq) OrderFinish(handler func(message request.Message)) { + run(orderFinish, handler) +} + +type OrderCancel struct { + OrderId uint64 `json:"orderId"` // 订单号 + CreateTime string `json:"createTime"` // 取消成功时间 Y/m/d H:i + CancelStatus uint `json:"cancelStatus"` // 取消状态 1=取消成功 2=取消失败 3=申请取消 4=申请拒收 + ExtendInfo string `json:"extendInfo"` // 拓展字段 + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 + RootOrderStatus uint `json:"rootOrderStatus"` // 订单取消前的状态 0=已代扣 1=未代扣 +} + +// OrderCancel @Title 订单取消 +func (m *Mq) OrderCancel(handler func(message request.Message)) { + run(orderCancel, handler) +} + +type OrderRefund struct { + OrderId uint64 `json:"orderId"` // 订单号 + RefundTime string `json:"refundTime"` // 退款时间 无效 + ServiceNumber string `json:"service_number"` // 京东服务单号 + SkuList []PackageSku `json:"skuList"` // sku列表 + RefundType uint `json:"refundType"` // 退款类型 1=售后退款 2=取消退款 3=多支付退款 4=余额提现 5=整单二次退款 + RefundFee float64 `json:"refundFee"` // 退款金额 + RefundId string `json:"refundId"` // 退款编码 + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 + DParentOrderId uint64 `json:"dParentOrderId"` // 父订单号 + RootOrderId uint64 `json:"rootOrderId"` // 顶级订单号 +} + +// OrderRefund @Title 退款成功 +func (m *Mq) OrderRefund(handler func(message request.Message)) { + run(orderRefund, handler) +} + +type AfsCreate struct { + OrderId uint64 `json:"orderId"` // 订单号 + AfsApplyTime int64 `json:"afsApplyTime"` // 售后申请时间 Y/m/d H:i + ChannelAfsApplyId string `json:"channelAfsApplyId"` // 渠道售后单号 + AfsServiceId uint64 `json:"afsServiceId"` // 京东收好单号 + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 商品名称 + AfsType uint `json:"afsType"` // 售后类型 10=退货 20=换货 + AfsApplyId uint64 `json:"afsApplyId"` // 售后申请单号 + Timestamp int64 `json:"timestamp"` // 时间戳 + ChannelOrderId string `json:"channelOrderId"` // 渠道单号 +} + +// AfsCreate @Title 售后创建 +func (m *Mq) AfsCreate(handler func(message request.Message)) { + run(afsCreate, handler) +} + +type AfsStepResult struct { + OrderId uint64 `json:"orderId"` // 订单号 + AfsServiceId uint64 `json:"afsServiceId"` // 京东售后单号 + AfsResultType string `json:"afsResultType"` // 京东售后单号 + // 售后状态 APPLY=创建售后 AUDIT_FAIL=审核驳回 CANCEL=审核取消 COMPENSATE_BALANCE=直赔余额 OA_COMPENSATION=直赔商品 PICKWARE_SEND=客户发货 + // POP_REISSUE=POP补发商品 REFUND_NO_RETURN=退款不退货 OA_COMPENSATION_NEWORDER=直赔商品生成新订单 + // AFS_RECV_PRODUCT=收到商品 OA_REWORK=返修换新 RETURN=原反 RETURN_CANCEL=原反取消 FORCECOMPLETE=强制关单 + // OA_REWORK_NEWORDER=换修换新生成新订单 POP_UNDERLINENEWORDER=pop线下换新 REFUND_APPLY=退款申请 CUSTOMER_CONFIRM=待用户确认 + // REFUND=退款 CUSTOMER_COMPLETE=用户已解决 + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 商品名称 + AfsType uint `json:"afsType"` // 售后类型 10=退货 20=换货 + StepType string `json:"stepType"` // 处理环节 APPLY=申请 AUDIT=审核 RECEIVED=收货 PROCESS=处理 CONFIRM=确认 COMPLETE=用户已解决 + OperationDate string `json:"operationDate"` // 操作时间 Y-m-d H:i:s + Timestamp int64 `json:"timestamp"` // 时间戳 +} + +// AfsStepResult @Title 售后处理 +func (m *Mq) AfsStepResult(handler func(message request.Message)) { + run(afsStepResult, handler) +} + +type BalanceEarlyWarning struct { + CustomerNum string `json:"customerNum"` // 钱包号 + Msg string `json:"msg"` // 不足提示 +} + +// BalanceEarlyWarning @Title 余额预警 +func (m *Mq) BalanceEarlyWarning(handler func(message request.Message)) { + run(balanceEarlyWarning, handler) +} + +type ack struct { + action string +} + +func (a *ack) Ack() { +} + +// @Title 调用请求 +func run(action string, handler func(message request.Message)) { + defer func() { + if err := recover(); err != nil { + run(action, handler) + } + }() + for true { + mq, err := request.ExecMq(action) + if err != nil { + continue + } + if len(mq.Messages) == 0 { + continue + } + handler(mq) + } +} diff --git a/order/afterSale.go b/order/afterSale.go new file mode 100644 index 0000000..0374b74 --- /dev/null +++ b/order/afterSale.go @@ -0,0 +1,234 @@ +package order + +import ( + "git.oa00.com/go/jdsdk/address" + "git.oa00.com/go/jdsdk/config" + "git.oa00.com/go/jdsdk/request" +) + +type AfterSale struct { +} + +const ( + afterSaleIsCan = "jingdong.ctp.afs.operate.apply.getIsCanApplyInfo" + afterSaleReason = "jingdong.ctp.afs.operate.apply.getApplyReason" + afterSaleApply = "jingdong.ctp.afs.operate.apply.createAfsApply" + afterSaleLogisticsAddress = "jingdong.ctp.afs.logistics.getLogisticsAddress" + afterSaleDelivery = "jingdong.ctp.afs.logistics.postBackLogisticsBillParam" + afterSaleInfo = "jingdong.ctp.afs.servicenbill.getAfsServiceDetail" + afterSaleCancel = "jingdong.ctp.afs.servicenbill.cancelAfsService" + + AfterServiceCanApplyFalse = 0 // 不可申请 + AfterServiceCanApplyTrue = 1 // 可申请 + + AfterServiceAfsDetailTypeMaster = 10 // 主品 + AfterServiceAfsDetailTypeSlave = 20 // 赠品 +) + +type canApplyInfoParam struct { + Pin string `json:"pin"` // 下单账号 + SkuId uint64 `json:"skuId"` // skuId + OrderId uint64 `json:"orderId"` // 订单号 + //AfsDetailType uint `json:"afsDetailType"` // 商品类型 10=主品 20=赠品 +} + +type resIsCanInfo struct { + CanApply uint `json:"canApply"` // 是否可申请售后 0=不可申请 1=可申请 + SkuId uint64 `json:"skuId"` // skuId + AppliedNum uint `json:"appliedNum"` // 已申请售后商品数量 + CannotApplyTip string `json:"cannotApplyTip"` // 不可申请原因 + OrderId uint64 `json:"orderId"` // 订单号 + AfsSupportedTypes []AfterSaleTypeItem `json:"afsSupportedTypes"` // 可售后类型列表 +} + +type AfterSaleTypeItem struct { + AfsTypeName string `json:"afsTypeName"` // 售后名称 + AfsType uint `json:"afsType"` // 售后类型 +} + +// IsCan @Title 是否能售后 +func (a *AfterSale) IsCan(skuId, orderId uint64) (result resIsCanInfo, err error) { + err = request.ExecCtlProtocol(afterSaleIsCan, canApplyInfoParam{ + Pin: config.SdkConfig.Pin, + SkuId: skuId, + OrderId: orderId, + }, &result) + return +} + +type applyReasonParam struct { + Pin string `json:"pin"` // 账号 + SkuId uint64 `json:"skuId"` // skuId + AfsType uint `json:"afsType"` // 售后类型 10=退货 20=换货 + OrderId uint64 `json:"orderId"` // 订单号 + //AfsDetailType uint `json:"afsDetailType"` // 商品类型 10=主品 20=赠品 +} +type reasonItem struct { + ApplyReasonName string `json:"applyReasonName"` // 售后原因 + ApplyReasonId uint `json:"applyReasonId"` // 售后Id + NeedUploadPic bool `json:"needUploadPic"` // 是否必须传图 +} + +// Reason @Title 售后原因 +func (a *AfterSale) Reason(skuId, orderId uint64, AfsType uint) (result []reasonItem, err error) { + err = request.ExecCtlProtocol(afterSaleReason, applyReasonParam{ + Pin: config.SdkConfig.Pin, + SkuId: skuId, + OrderId: orderId, + AfsType: AfsType, + }, &result) + return +} + +type afsApplyParam struct { + Pin string `json:"pin"` // 下单账号 + PickWareType uint `json:"pickWareType"` // 取件方式 40=客户发货 固定 + PickWareAddress address.Address `json:"pickWareAddress"` // 取件地址 + ApplyParam +} + +type ApplyParam struct { + ApplyReasonName string `json:"applyReasonName"` // 售后原因 + ApplyReasonId uint `json:"applyReasonId"` // 售后原因id + ChannelAfsApplyId string `json:"channelAfsApplyId"` // 渠道售后申请单号 + AfsType uint `json:"afsType"` // 期望售后类型 10=退货 20=换货 + QuestionPic string `json:"questionPic"` // 问题图片 逗号分隔 + OrderId uint `json:"orderId"` // 订单号 + SkuQuantity AfterSaleSkuItem `json:"skuQuantity"` // 申请商品信息 +} + +type AfterSaleSkuItem struct { + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // skuName + Quantity uint `json:"quantity"` // 数量 + AfsDetailType uint `json:"afsDetailType"` // 商品类型 10=主品 20=赠品 +} +type resApply struct { + ChannelAfsApplyId string `json:"channelAfsApplyId"` // 渠道申请单号 + AfsApplyId uint64 `json:"afsApplyId"` // 京东申请单号 +} + +// Apply @Title 售后申请 +func (a *AfterSale) Apply(data ApplyParam) (result resApply, err error) { + err = request.ExecCtlProtocol(afterSaleApply, afsApplyParam{ + Pin: config.SdkConfig.Pin, + PickWareType: 40, + ApplyParam: data, + }, &result) + return +} + +type logisticsAddressParam struct { + AfsServiceId uint64 `json:"afsServiceId"` // 售后id + Pin string `json:"pin"` // 下单账号 +} + +type resLogisticsAddress struct { + ContactsMobile string `json:"contactsMobile"` // 回寄手机号 + ContactsZipCode string `json:"contactsZipCode"` // 回寄邮编 + Address string `json:"address"` // 回寄详细地址 + ContactsName string `json:"contactsName"` // 回寄联系人 +} + +// LogisticsAddress @Title 回寄地址 +func (a *AfterSale) LogisticsAddress(afsServiceId uint64) (result resLogisticsAddress, err error) { + err = request.ExecCtlProtocol(afterSaleLogisticsAddress, logisticsAddressParam{ + AfsServiceId: afsServiceId, + Pin: config.SdkConfig.Pin, + }, &result) + return +} + +type logisticsBillParam struct { + Pin string `json:"pin"` // 下单账号 + AfsServiceId uint64 `json:"afsServiceId"` // 售后id + LogisticsCompany string `json:"logisticsCompany"` // 物流公司 + WaybillCode string `json:"waybillCode"` // 运单号 + SendGoodsDate string `json:"sendGoodsDate"` // 运单发货日期 Y-m-d +} +type resDelivery struct { + Message string `json:"message"` // 回传结果描述 + PostBackResult bool `json:"postBackResult"` // 回传结果 +} + +// Delivery @Title 回传客户发货信息 +func (a *AfterSale) Delivery(afsServiceId uint64, logisticsCompany, waybillCode, sendGoodsDate string) (result resDelivery, err error) { + err = request.ExecCtlProtocol(afterSaleDelivery, logisticsBillParam{ + Pin: config.SdkConfig.Pin, + AfsServiceId: afsServiceId, + LogisticsCompany: logisticsCompany, + WaybillCode: waybillCode, + SendGoodsDate: sendGoodsDate, + }, &result) + return +} + +type afsServiceDetailParam struct { + AfsServiceId uint64 `json:"afsServiceId"` // 售后id + Pin string `json:"pin"` // 下单账号 +} +type resInfo struct { + ProcessResult uint `json:"processResult"` // 处理结果 + CustomerName string `json:"customerName"` // 客户名称 + ApplyReasonId uint `json:"applyReasonId"` // 售后原因id + ApplyReasonName string `json:"applyReasonName"` // 售后原因 + ApproveResult uint `json:"approveResult"` // 审核结果 + AfsApplyTime int64 `json:"afsApplyTime"` // 发起售后申请时间 Y-m-d H:i:s + ApproveResultName string `json:"approveResultName"` // 审核结果 + ProcessResultName string `json:"processResultName"` // 处理结果 + AfsType uint `json:"afsType"` // 售后服务类型id退货(10),换货(20) + ReturnWareType uint `json:"returnWareType"` // 售后返件类型 1:客户发货 + CustomerMobile string `json:"customerMobile"` // 售后联系人联系方式 + QuestionPic string `json:"questionPic"` // 售后描述图片 + ApproveNotes string `json:"approveNotes"` // 售后服务单审核意见 + ApprovedDate int64 `json:"approvedDate"` // 售后服务单审核时间 Y-m-d H:i:s + CustomerEmail string `json:"customerEmail"` // 售后联系人邮箱 + ProcessedDate string `json:"processedDate"` // 售后服务单处理时间 Y-m-d H:i:s + AfsApplyId uint64 `json:"afsApplyId"` // 售后申请单号 + AfsTypeName string `json:"afsTypeName"` // 用户期望的售后服务类型 1:退货 2:换货 + AfsServiceState uint `json:"afsServiceState"` // 服务单状态 + AfsServiceId uint64 `json:"afsServiceId"` // 京东售后服务单号 + NewOrderId uint64 `json:"newOrderId"` // 售后换新订单号 + AfsServiceStep uint `json:"afsServiceStep"` // 处理环节 + AfsServiceStateName string `json:"afsServiceStateName"` // 售后处理状态 + ProcessNotes string `json:"processNotes"` // 售后处理意见 + AfsServiceStepName string `json:"afsServiceStepName"` // 售后当前处理环节 + OrderId string `json:"orderId"` // 订单号 + SkuQuantity skuItem `json:"skuQuantity"` // sku信息 + DesenCustomerMobile string `json:"desen_customerMobile"` // 售后联系人联系方式 +} + +type skuItem struct { + SkuId string `json:"skuId"` // skuId + SkuName string `json:"skuName"` // skuName + Quantity uint `json:"quantity"` // 数量 + ValidNumFlag uint `json:"validNumFlag"` // 赠品申请标识 1代表申请了,0代表没申请或释放了,后续可以继续申请 + SkuType uint `json:"skuType"` // 标识商品属性 1单品、2买赠:赠品套装中的主商品、3买赠:赠品套装中的赠品 +} + +// Info @Title 售后详情 +func (a *AfterSale) Info(afsServiceId uint64) (result resInfo, err error) { + err = request.ExecCtlProtocol(afterSaleInfo, afsServiceDetailParam{ + AfsServiceId: afsServiceId, + Pin: config.SdkConfig.Pin, + }, &result) + return +} + +type cancelAfsServiceParam struct { + AfsServiceId uint64 `json:"afsServiceId"` // 售后id + Pin string `json:"pin"` // 下单账号 +} + +type resCancel struct { + CancelState uint `json:"cancelState"` // 1=不可取消 2=取消成功 3=取消失败 +} + +// Cancel @Title 取消售后服务单 +func (a *AfterSale) Cancel(afsServiceId uint64) (result resCancel, err error) { + err = request.ExecCtlProtocol(afterSaleCancel, cancelAfsServiceParam{ + AfsServiceId: afsServiceId, + Pin: config.SdkConfig.Pin, + }, &result) + return +} diff --git a/order/order.go b/order/order.go new file mode 100644 index 0000000..12df5cc --- /dev/null +++ b/order/order.go @@ -0,0 +1,289 @@ +package order + +import ( + "git.oa00.com/go/jdsdk/address" + "git.oa00.com/go/jdsdk/config" + "git.oa00.com/go/jdsdk/request" +) + +const ( + freightFee = "jingdong.ctp.order.getFreightFee" // 运费 + shipmentType = "jingdong.ctp.order.getShipmentType" // 配送方式 + createOrder = "jingdong.ctp.order.submitOrder" // 创建订单 + queryOrder = "jingdong.ctp.order.querySubmitOrder" // 查询订单 + orderDetail = "jingdong.ctp.order.getOrderDetail" // 订单详情 + orderCancel = "jingdong.ctp.order.cancelOrder" // 订单取消 + orderTrajectory = "jingdong.ctp.order.getLogistics" // 订单轨迹 + orderConfirm = "jingdong.ctp.order.confirmDelivery" // 确认收货 + orderPush = "jingdong.ctp.order.pushOrder" // 订单推送 + orderPayInfo = "jingdong.ctp.order.getOrderPayInfo" // 订单支付信息 + + NotSupport = 9 // 商品不支持当前地址配送 + + paymentType = 2 // 在线支付 +) + +type Order struct { +} +type SkuItem struct { + SkuPrice float64 `json:"skuPrice"` // 单价 + SkuId string `json:"skuId"` // skuId + SkuName string `json:"skuName"` // skuName + Quantity uint `json:"quantity"` // 数量 +} +type ApiFreightFeeParam struct { + SkuList []SkuItem `json:"skuList"` // 商品列表 + OrderFee float64 `json:"orderFee"` // 商品总金额 + Address address.Address `json:"address"` // 收货地址 +} +type apiFreightFeeParam struct { + ApiFreightFeeParam + PaymentType uint `json:"paymentType"` // 支付方式。固定传2:在线支付 + Pin string `json:"pin"` // 下单账号 +} +type resFreightFee struct { + FreightFee float64 `json:"freightFee"` // 运费 +} + +// GetFreightFee @Title 获取运费 +func (o *Order) GetFreightFee(data ApiFreightFeeParam) (result resFreightFee, err error) { + err = request.ExecProtocol(freightFee, apiFreightFeeParam{ + ApiFreightFeeParam: data, + PaymentType: paymentType, + Pin: config.SdkConfig.Pin, + }, &result) + return +} + +type apiShipmentTypeParam struct { + apiFreightFeeParam +} +type skuShipmentType struct { + SkuId string `json:"skuId"` // skuId + ShipmentType uint `json:"shipmentType"` // 配送方式 +} +type shipmentDetail struct { + GiftList []skuShipmentType `json:"giftList"` // 赠品sku列表 + AttachmentList []skuShipmentType `json:"attachmentList"` // 附件sku信息 + ShipmentType uint `json:"shipmentType"` // 配送方式 +} +type shipmentItem struct { + ShipmentDetail shipmentDetail `json:"shipmentDetail"` // 配送明细 + SkuId string `json:"skuId"` // skuId +} +type resShipmentType struct { + ShipmentType uint `json:"shipmentType"` // 配送方式。1:京东配送 2:京配转三方配送 3:第三方配送 4:普通快递配送 9:不支持配送 + ShipmentInfoList []shipmentItem `json:"shipmentInfoList"` // 配送明细 +} + +// GetShipmentType @Title 获取配送方式 +func (o *Order) GetShipmentType(data ApiFreightFeeParam) (result resShipmentType, err error) { + err = request.ExecProtocol(shipmentType, apiShipmentTypeParam{ + apiFreightFeeParam{ + ApiFreightFeeParam: data, + PaymentType: paymentType, + Pin: config.SdkConfig.Pin, + }, + }, &result) + return +} + +type MainSku struct { + MainSku SkuItem `json:"mainSku"` +} +type Receiver struct { + ReceiverName string `json:"receiverName"` // 姓名 + ReceiverMobile string `json:"receiverMobile"` // 手机号 + ReceiverEmail string `json:"receiverEmail"` // 邮箱 + ZipCode string `json:"zipCode"` // 邮编 +} + +type OrderParam struct { + ChannelOrderId string `json:"channelOrderId"` // 渠道订单号 + ProductList []MainSku `json:"productList"` // 商品信息列表 + OrderFee float64 `json:"orderFee"` // 总金额-不含运费 + FreightFee float64 `json:"freightFee"` // 运费 + Address address.Address `json:"address"` // 收货地址 + Receiver Receiver `json:"receiver"` // 收货人 + Invoice Invoice `json:"invoice"` // 发票信息 + UserIp string `json:"userIp"` // 用户ip + ShipmentType uint `json:"shipmentType"` // 配送方式 + AutoCancelTime uint `json:"autoCancelTime"` // 自动取消时间 秒 +} +type Invoice struct { + InvoiceType uint `json:"invoiceType"` // 发票类型 2=增票 3=电子票 + VatInvoice VatInvoice `json:"vatInvoice"` // 增值税发票信息 +} + +type VatInvoiceAddress struct { + VatProvinceId uint `json:"vatProvinceId"` // 省 + VatCityId uint `json:"vatCityId"` // 市 + VatCountyId uint `json:"vatCountyId"` // 区 + VatTownId uint `json:"vatTownId"` // 街道 + VatFullAddress string `json:"vatFullAddress"` // 全地址 +} +type VatInvoice struct { + CompanyName string `json:"companyName"` // 公司名 + Code string `json:"code"` // 营业执照号 + RegAddr string `json:"regAddr"` // 注册地址 + RegPhone string `json:"regPhone"` // 注册手机 + RegBank string `json:"regBank"` // 开户行 + RegBankAccount string `json:"regBankAccount"` // 开户账号 + ConsigneeName string `json:"consigneeName"` // 收件人 + ConsigneeMobile string `json:"consigneeMobile"` // 收件电话 + VatAddress VatInvoiceAddress `json:"vatAddress"` // 邮寄地址 +} + +type param struct { + OrderParam + Pin string `json:"pin"` // 下单账号 + PaymentType uint `json:"paymentType"` // 支付方式 2=在线支付 + ChannelOrderSource string `json:"channelOrderSource"` // 订单来源 OTHER=其他 + SendGoods uint `json:"sendGoods"` // 物品类型 固定1 +} +type resOrder struct { + ChannelOrderId string `json:"channelOrderId"` // 渠道订单id + OrderId uint `json:"orderId"` // 京东订单id + ExtendInfo extendInfo `json:"extendInfo"` // 拓展属性 +} + +type extendInfo struct { + Code string `json:"code"` // 返回码 0001=重复下单 +} + +// Create @Title 下单 +func (o *Order) Create(data OrderParam) (result resOrder, err error) { + err = request.ExecProtocol(createOrder, param{ + OrderParam: data, + Pin: config.SdkConfig.Pin, + PaymentType: paymentType, + ChannelOrderSource: "OTHER", + SendGoods: 1, + }, &result) + return +} + +// Query @Title 订单查询 +func (o *Order) Query(channelOrderId string) (result resOrder, err error) { + err = request.ExecParam(queryOrder, map[string]interface{}{ + "channelOrderId": channelOrderId, + }, &result) + return +} + +type OrderInfo struct { + SkuList []orderSkuItem `json:"skuList"` // 商品列表 + OrderId uint64 `json:"orderId"` // 订单号 + BaseOrderInfo baseOrderInfo `json:"baseOrderInfo"` // 订单基本信息 + OrderRelationFee orderRelationFee `json:"orderRelationFee"` // 订单费用信息 +} + +type orderRelationFee struct { + ShouldPaymentFee float64 `json:"shouldPaymentFee"` // 订单总金额 + DiscountFee float64 `json:"discountFee"` // 订单优惠金额 + FreightFee float64 `json:"freightFee"` // 运费金额 +} + +type baseOrderInfo struct { + RootOrderId uint64 `json:"rootOrderId"` // 父订单号 + OrderStatus int `json:"orderStatus"` // 订单状态 -100=已取消 0=提单成功 1=等待付款 4=已支付 6=等待打印 7=拣货完成 8=出库完成 9=等待发货 15=待用户确认 16=用户拒收 18=用户签收 21=订单锁定 + SubmitTime float64 `json:"submitTime"` // 订单创建时间 + CompleteTime float64 `json:"completeTime"` // 订单完成时间 + PayTime float64 `json:"payTime"` // 订单支付时间 + OutWarehouseTime float64 `json:"outWarehouseTime"` // 订单出库时间 + OrderType int `json:"orderType"` // 订单类型 0=自营/混单 1=商家自发订单 2=厂直订单 99=其他订单 + Remark string `json:"remark"` // 订单备注 +} + +type orderSkuItem struct { + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 商品名称 + Quantity uint `json:"quantity"` // 数量 + ShouldPrice float64 `json:"shouldPrice"` // 单价 + SkuGiftType uint `json:"skuGiftType"` // sku类型 1=主品 2=赠品 + MainSkuId uint64 `json:"mainSkuId"` // 赠品主skuId +} + +// Detail @Title 订单详情 京东订单id +func (o *Order) Detail(orderId string) (result OrderInfo, err error) { + err = request.ExecParam(orderDetail, map[string]interface{}{ + "orderId": orderId, + }, &result) + return +} + +// Cancel @Title 订单取消 京东订单id +func (o *Order) Cancel(orderId string) (result OrderInfo, err error) { + err = request.ExecParam(orderCancel, map[string]interface{}{ + "orderId": orderId, + "cancelReasonCode": 100, + }, &result) + return +} + +type resTrajectory struct { + WaybillCode string `json:"waybillCode"` // 运单号 + OperatorNodeList []operatorNodeItem `json:"operatorNodeList"` // 轨迹 +} +type operatorNodeItem struct { + Content string `json:"content"` // 详细信息 + GroupState string `json:"groupState"` // 阶段状态 + ScanState string `json:"scanState"` // 扫描状态 + MsgTime int64 `json:"msgTime"` // 操作时间 + SystemOperator string `json:"systemOperator"` // 操作人 + OrderId uint64 `json:"orderId"` // 订单号 +} + +// Trajectory @Title 订单轨迹 +func (o *Order) Trajectory(orderId string) (result []resTrajectory, err error) { + err = request.ExecParam(orderTrajectory, map[string]interface{}{ + "orderId": orderId, + }, &result) + return +} + +// Confirm @Title 确认收货 +func (o *Order) Confirm(orderId string) (err error) { + err = request.ExecParam(orderConfirm, map[string]interface{}{ + "orderId": orderId, + }, nil) + return +} + +// Push @Title 订单推送 +func (o *Order) Push(orderId string) (err error) { + err = request.ExecParam(orderPush, map[string]interface{}{ + "orderId": orderId, + }, nil) + return +} + +type resPayInfo struct { + RefundTotalFee float64 `json:"refundTotalFee"` // 退款总金额 + RootOrderId uint64 `json:"rootOrderId"` // 父订单号 + ChannelOrderId uint64 `json:"channelOrderId"` // 渠道订单号 + FreightFee float64 `json:"freightFee"` // 运费金额 + PaidInTotalFee float64 `json:"paidInTotalFee"` // 实际支付金额 + OrderFee float64 `json:"orderFee"` // 商品总金额 + RefundDetailList []payRefundItem `json:"refundDetailList"` // 退款列表 + PaidInDetailList []payItem `json:"paidInDetailList"` // 追夫列表 +} + +type payRefundItem struct { + RefundFee float64 `json:"refundFee"` // 退款金额 + RefundType float64 `json:"refundType"` // 退款类型 1=取消退款 2=售后退款 3=拆分取消非退款 4=多支付退款 5=其他退款类型 + RefundTime int64 `json:"refundTime"` // 退款时间 +} +type payItem struct { + PaidInFee float64 `json:"paidInFee"` // 支付金额 + PaidInType float64 `json:"paidInType"` // 支付类型 2=在线支付 + PaidInTime int64 `json:"paidInTime"` // 支付时间 +} + +// PayInfo @Title 订单支付信息 +func (o *Order) PayInfo(orderId string) (result resPayInfo, err error) { + err = request.ExecParam(orderPayInfo, map[string]interface{}{ + "orderId": orderId, + }, &result) + return +} diff --git a/request/client.go b/request/client.go new file mode 100644 index 0000000..2afdea9 --- /dev/null +++ b/request/client.go @@ -0,0 +1,324 @@ +package request + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "git.oa00.com/go/jdsdk/config" + "github.com/mitchellh/mapstructure" + "log" + "net/url" + "reflect" + "sort" + "strings" + "time" +) + +type ctpProtocol struct { + AppKey string `json:"appKey"` + ChannelId uint `json:"channelId"` + CustomerId uint `json:"customerId"` + OpName string `json:"opName"` + TraceId string `json:"traceId"` +} + +type resp struct { + Code string `json:"code"` + Result struct { + ErrMsg string `json:"errMsg"` + Data interface{} `json:"data"` + StockStateList interface{} `json:"StockStateList"` + Success bool `json:"success"` + ErrCode int `json:"errCode"` + } `json:"result"` +} + +type RespErr struct { + ErrorResponse struct { + Code string `json:"code"` + ZhDesc string `json:"zh_desc"` + EnDesc string `json:"en_desc"` + } `json:"error_response"` +} + +// @Title 调用接口 +func exec(action string, data string, result interface{}) error { + t := time.Now().Format("2006-01-02 15:04:05") + params := url.Values{ + "app_key": {config.SdkConfig.AppKey}, + "method": {action}, + "access_token": {config.SdkConfig.AccessToken}, + "v": {"2.0"}, + "format": {"json"}, + "timestamp": {t}, + "360buy_param_json": {data}, + } + params.Add("sign", Sign(params)) + bytes, err := request("GET", fmt.Sprintf("%s?%s", config.SdkConfig.Url, params.Encode()), "") + if err != nil { + return err + } + log.Println(string(bytes)) + respErr := RespErr{} + json.Unmarshal(bytes, &respErr) + if respErr.ErrorResponse.Code != "" { + if respErr.ErrorResponse.Code == "19" && config.SdkConfig.RefreshTokenCallback != nil { + token, err := RefreshToken() + if err != nil { + return err + } + config.SdkConfig.RefreshTokenCallback(token) + } + return errors.New(respErr.ErrorResponse.ZhDesc) + } + mResp := map[string]resp{} + json.Unmarshal(bytes, &mResp) + for _, val := range mResp { + if val.Result.ErrCode != 200 { + return errors.New(val.Result.ErrMsg) + } + if val.Result.Data != nil { + err = mapstructure.Decode(val.Result.Data, &result) + } + if val.Result.StockStateList != nil { + err = mapstructure.Decode(val.Result.StockStateList, &result) + } + return nil + } + return errors.New("接口请求错误") +} + +// ExecProtocol @Title 调用接口 +func ExecProtocol(action string, data interface{}, result interface{}) error { + requestData := map[string]interface{}{ + "protocol": ctpProtocol{ + AppKey: config.SdkConfig.AppKey, + ChannelId: config.SdkConfig.ChannelId, + CustomerId: config.SdkConfig.CustomerId, + OpName: config.SdkConfig.OpName, + TraceId: "", + }, + getStructName(data): data, + } + jsonData, _ := json.Marshal(&requestData) + return exec(action, string(jsonData), result) +} + +// ExecCtlProtocol @Title 调用接口 +func ExecCtlProtocol(action string, data interface{}, result interface{}) error { + requestData := map[string]interface{}{ + "ctpProtocol": ctpProtocol{ + AppKey: config.SdkConfig.AppKey, + ChannelId: config.SdkConfig.ChannelId, + CustomerId: config.SdkConfig.CustomerId, + OpName: config.SdkConfig.OpName, + TraceId: "", + }, + getStructName(data): data, + } + jsonData, _ := json.Marshal(&requestData) + return exec(action, string(jsonData), result) +} + +// ExecParam @Title 调用接口 +func ExecParam(action string, data map[string]interface{}, result interface{}) error { + requestData := map[string]interface{}{ + "appKey": config.SdkConfig.AppKey, + "channelId": config.SdkConfig.ChannelId, + "customerId": config.SdkConfig.CustomerId, + "traceId": "", + "pin": config.SdkConfig.Pin, + "clientIp": "127.0.0.1", + } + for key, item := range data { + requestData[key] = item + } + jsonData, _ := json.Marshal(&requestData) + return exec(action, string(jsonData), result) +} + +type mqResult struct { + RequestId string `json:"requestId"` + Result Message `json:"result"` + Error resError `json:"error"` +} + +type Message struct { + TopicName string `json:"topicName"` + AckIndex string `json:"ackIndex"` + Messages []MessageItem `json:"messages"` + Action string `json:"action"` +} + +type MessageItem struct { + MessageId string `json:"messageId"` + MessageBody string `json:"messageBody"` +} + +type resError struct { + Code int `json:"code"` + Message string `json:"message"` + Status string `json:"status"` +} + +const ( + AckActionSuccess = "SUCCESS" // 消费成功 + AckActionFailed = "CONSUME_FAILED" // 消费失败,服务端会进行重新推送 + AckActionResend = "RESEND" // 立即重发 + AckActionDiscard = "DISCARD" // 丢弃消息,服务端不会进行重试 +) + +// Success @Title 消费成功 +func (m *Message) Success() error { + return m.ack(AckActionSuccess) +} + +// Failed @Title 消费失败,服务端会进行重新推送 +func (m *Message) Failed() error { + return m.ack(AckActionFailed) +} + +// Resend @Title 立即重发 +func (m *Message) Resend() error { + return m.ack(AckActionResend) +} + +// Discard @Title 丢弃消息,服务端不会进行重试 +func (m *Message) Discard() error { + return m.ack(AckActionDiscard) +} + +// Ack @Title 确认消息 +func (m *Message) ack(ackAction string) error { + dateTime := time.Now().UTC().Format("2006-01-02T15:04:05Z") + data := map[string]string{ + "topic": m.TopicName, + "consumerGroupId": fmt.Sprintf("open_message_%d", config.SdkConfig.AccountId), + "ackAction": ackAction, + "ackIndex": m.AckIndex, + "accessKey": config.SdkConfig.AccessKey, + "dateTime": dateTime, + } + sign := MqSign(data) + jsonData, _ := json.Marshal(&data) + bytes, err := request(post, config.SdkConfig.MqUrl+"/ack", string(jsonData), map[string]string{ + "accessKey": config.SdkConfig.AccessKey, + "dateTime": dateTime, + "signature": sign, + "Content-Type": "application/json", + }) + if err != nil { + return err + } + res := mqResult{} + if err := json.Unmarshal(bytes, &res); err != nil { + return err + } + if res.Error.Code > 0 { + return errors.New(res.Error.Message) + } + return nil +} + +// ExecMq @Title 调用mq +func ExecMq(action string) (Message, error) { + data := map[string]string{ + "topic": fmt.Sprintf("open_message_ct_%s_%s", action, config.SdkConfig.AppKey), + "consumerGroupId": fmt.Sprintf("open_message_%d", config.SdkConfig.AccountId), + } + value := url.Values{} + for k, v := range data { + value.Set(k, v) + } + dateTime := time.Now().UTC().Format("2006-01-02T15:04:05Z") + data["accessKey"] = config.SdkConfig.AccessKey + data["dateTime"] = dateTime + + sign := MqSign(data) + bytes, err := request(get, config.SdkConfig.MqUrl+"/messages?"+value.Encode(), "", map[string]string{ + "accessKey": config.SdkConfig.AccessKey, + "dateTime": dateTime, + "signature": sign, + }) + if err != nil { + return Message{}, err + } + res := mqResult{} + if err := json.Unmarshal(bytes, &res); err != nil { + return Message{}, err + } + res.Result.Action = action + if res.Error.Code > 0 { + return res.Result, errors.New(res.Error.Message) + } + return res.Result, err +} + +func map2str(data map[string]string, sep string) string { + var temp []string + for key, _ := range data { + temp = append(temp, key) + } + sort.Strings(temp) + var tt []string + for _, v := range temp { + tt = append(tt, fmt.Sprintf("%s=%s", v, data[v])) + } + return strings.Join(tt, sep) +} + +// MqSign @Title mq签名 +func MqSign(data map[string]string) string { + h := hmac.New(sha1.New, []byte(config.SdkConfig.SecretKey)) + str := map2str(data, "&") + h.Write([]byte(str)) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +// RefreshToken @Title 刷新token +func RefreshToken() (*config.Bearer, error) { + value := url.Values{} + value.Set("app_key", config.SdkConfig.AppKey) + value.Set("app_secret", config.SdkConfig.AppSecret) + value.Set("refresh_token", config.SdkConfig.RefreshToken) + value.Set("grant_type", "refresh_token") + data, err := request(get, fmt.Sprintf("%s?%s", config.SdkConfig.TokenUrl, value.Encode()), "") + if err != nil { + return nil, err + } + var b config.Bearer + if err = json.Unmarshal(data, &b); err != nil { + return nil, err + } + return &b, nil +} + +// Sign @Title 签名 +func Sign(params url.Values) string { + h := md5.New() + var order []string + for key, _ := range params { + order = append(order, key) + } + sort.Strings(order) + h.Write([]byte(config.SdkConfig.AppSecret)) + for _, value := range order { + h.Write([]byte(fmt.Sprintf("%s%s", value, params.Get(value)))) + } + h.Write([]byte(config.SdkConfig.AppSecret)) + return strings.ToUpper(hex.EncodeToString(h.Sum(nil))) +} + +// 字符串首字母小写 +func getStructName(obj interface{}) string { + s := reflect.TypeOf(obj).Name() + if s == "" { + return "" + } + return strings.ToLower(s[:1]) + s[1:] +} diff --git a/request/request.go b/request/request.go new file mode 100644 index 0000000..5d8d4d8 --- /dev/null +++ b/request/request.go @@ -0,0 +1,37 @@ +package request + +import ( + "io" + "net/http" + "strings" +) + +const ( + post = "POST" + get = "GET" +) + +var client = &http.Client{} + +// @Title 请求 +func request(method, url, data string, headers ...map[string]string) ([]byte, error) { + reqest, err := http.NewRequest(method, url, strings.NewReader(data)) + if err != nil { + return nil, err + } + if len(headers) > 0 { + for key, value := range headers[0] { + reqest.Header.Add(key, value) + } + } + response, err := client.Do(reqest) + if err != nil { + return nil, err + } + defer response.Body.Close() + result, err := io.ReadAll(response.Body) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/sku/sku.go b/sku/sku.go new file mode 100644 index 0000000..0443384 --- /dev/null +++ b/sku/sku.go @@ -0,0 +1,297 @@ +package sku + +import ( + "git.oa00.com/go/jdsdk/address" + "git.oa00.com/go/jdsdk/request" +) + +const ( + publicSkuList = "jingdong.ctp.ware.skupool.getSkuPoolList" // 公共池商品 + publicSkuAdd = "jingdong.ctp.ware.skupool.addSkuIntoChannel" // 添加到渠道池 + brotherList = "jingdong.ctp.ware.sku.getBrotherList" // 兄弟sku + skuList = "jingdong.ctp.ware.sku.getSkuList" // sku列表 + skuPriceList = "jingdong.ctp.ware.price.getSkuPriceInfoList" // 价格列表 + skuDetail = "jingdong.ctp.ware.sku.getSkuDetail" // sku详情 + skuStock = "jingdong.ctp.ware.stock.queryAreaStockState" // sku库存 +) + +type Sku struct { +} + +type ApiSkuPoolListParam struct { + ScrollId string `json:"scrollId"` + SkuPoolType uint `json:"skuPoolType"` + PageSize uint `json:"pageSize"` +} + +type poolSku struct { + SkuPoolId uint64 `json:"skuPoolId"` + SkuId uint64 `json:"skuId"` + SkuName string `json:"skuName"` + SkuPoolType uint `json:"skuPoolType"` + SkuPoolTypeName string `json:"skuPoolTypeName"` +} + +type poolData struct { + ScrollId string `json:"scrollId"` + Total uint `json:"total"` + Entries []poolSku `json:"entries"` +} + +// GetPublicList @Title 获取公共商品列表 +func (s *Sku) GetPublicList(data ApiSkuPoolListParam) (result poolData, err error) { + err = request.ExecCtlProtocol(publicSkuList, data, &result) + return +} + +type PoolItem struct { + SkuPoolId uint64 `json:"skuPoolId"` + SkuId uint64 `json:"skuId"` +} + +type apiSkuPoolAddParam struct { + SkuPoolAddList []PoolItem `json:"skuPoolAddList"` +} +type skuPoolAdd struct { + ErrorMessage string `json:"errorMessage"` + SkuPoolId uint64 `json:"skuPoolId"` + SkuId uint64 `json:"skuId"` + IsSuccess bool `json:"isSuccess"` +} + +// PublicAdd @Title 添加公共商品到商品库 +func (s *Sku) PublicAdd(data []PoolItem) (result []skuPoolAdd, err error) { + err = request.ExecCtlProtocol(publicSkuAdd, apiSkuPoolAddParam{ + SkuPoolAddList: data, + }, &result) + return +} + +type apiBrotherListParam struct { + SkuIdSet []uint64 `json:"skuIdSet"` +} +type brotherItem struct { + BrotherSkuIds []uint64 `json:"brotherSkuIds"` // 兄弟sku集合 + ErrorMessage string `json:"errorMessage"` // 错误原因 + SkuId uint64 `json:"skuId"` // skuId + IsSuccess bool `json:"isSuccess"` // 是否成功 +} + +// GetBrother @Title 获取商品兄弟关系 最大20个 +func (s *Sku) GetBrother(skuIds []uint64) (result []brotherItem, err error) { + err = request.ExecCtlProtocol(brotherList, apiBrotherListParam{ + SkuIdSet: skuIds, + }, &result) + return +} + +type ApiSkuListParam struct { + ScrollId string `json:"scrollId"` + OrderBy string `json:"orderBy,omitempty"` // 排序方式。格式为column:asc/desc。column可选值:modified(最近修改时间) + SkuStatus uint `json:"skuStatus,omitempty"` // sku上下架状态(1 上架,2 下架) + CategoryId uint `json:"categoryId,omitempty"` // 三级分类 + StartModified string `json:"startModified,omitempty"` // 起始的修改时间,非必填 + EndModified string `json:"endModified,omitempty"` // 结束的修改时间,非必填 + PageSize uint `json:"pageSize"` // 分页大小,size小于等于20 + SkuName string `json:"skuName,omitempty"` // 搜索条件 +} +type EntryParams struct { + SkuId uint64 `json:"skuId"` // skuId + SkuName string `json:"skuName"` // 商品名称 + OuterId string `json:"outerId"` // 渠道sku编号 无意义 + ImgUrl string `json:"imgUrl"` // 商品主图 + CategoryId1 uint `json:"categoryId1"` // 一级分类 + CategoryName1 string `json:"categoryName1"` // 一级分类 + CategoryId2 uint `json:"categoryId2"` // 二级分类 + CategoryName2 string `json:"categoryName2"` // 二级分类 + CategoryId uint `json:"categoryId"` // 三级分类 + CategoryName string `json:"categoryName"` // 三级分类 + BrandId uint `json:"brandId"` // 品牌id + BrandName string `json:"brandName"` // 品牌名称 + SkuStatus uint `json:"skuStatus"` // 上下加状态 1=上架 2=下架 + Created int64 `json:"created"` // 创建时间 + Modified int64 `json:"modified"` // 修改时间 + EnBrandName string `json:"enBrandName"` // 品牌英文名 + BrandCountry string `json:"brandCountry"` // 品牌国家 + WareType uint `json:"wareType"` // 品牌类型(1-普通商品; 2-虚拟组套) +} + +type skuData struct { + ScrollId string `json:"scrollId"` + Total uint `json:"total"` + Entries []EntryParams `json:"entries"` +} + +// GetList @Title 获取商品列表 +func (s *Sku) GetList(data ApiSkuListParam) (result skuData, err error) { + err = request.ExecCtlProtocol(skuList, data, &result) + return +} + +type skuPriceInfoParam struct { + SkuIdSet []uint64 `json:"skuIdSet"` +} + +type priceDetail struct { + SkuPriceList []PriceItem `json:"skuPriceList"` + CustomerId uint `json:"customerId"` + ChannelId uint `json:"channelId"` +} + +type PriceItem struct { + IsSuccess bool `json:"isSuccess"` // 是否获取价格成功(若成功,则skuPrice字段返回该sku价格;否则,errorMessage字段返回查询失败原因) + ErrorMessage string `json:"errorMessage"` // 若sku价格查询失败(isSuccess!=true),则返回原因 + SkuPrice float64 `json:"skuPrice"` // sku价格,仅获取价格成功时(isSuccess=true)有值,单位:元 小数点后两位 + SkuId uint64 `json:"skuId"` // skuId + //PriceTypeMark string `json:"priceTypeMark"` // 商品价格类型标识(1:基础价格,2:促销价格) + ProfitRate float64 `json:"profitRate"` // 商品利润率 + BackStagePrice string `json:"backStagePrice"` // 京东后台价 + //PsriceUpdateTime string `json:"priceUpdateTime"` // 商品价格更新时间 +} + +// GetPriceList @Title 获取商品价格列表 最大100 +func (s *Sku) GetPriceList(skuIds []uint64) ([]PriceItem, error) { + detail := priceDetail{} + err := request.ExecCtlProtocol(skuPriceList, skuPriceInfoParam{ + SkuIdSet: skuIds, + }, &detail) + return detail.SkuPriceList, err +} + +type skuDetailParam struct { + SkuIdSet []uint64 `json:"skuIdSet"` // 最大20 + DetailAssemblyType uint `json:"detailAssemblyType"` // 商品详情类型。0:返回sku详情全部信息 +} + +type imageInfo struct { + Path string `json:"path"` // 图片路径 + Features string `json:"features"` // 特征 无意义 + OrderSort uint `json:"orderSort"` // 排序 1-n 可能为null + IsPrimary uint `json:"isPrimary"` // 是否主图 + Position uint `json:"position"` // 未知 无意义 + Type uint `json:"type"` // 类型 0=方图 1=长图 +} +type skuBaseBookInfo struct { + Id string `json:"id"` + ISBN string `json:"ISBN"` + ISSN string `json:"ISSN"` + BookName string `json:"bookName"` + ForeignBookName string `json:"foreignBookName"` + Language string `json:"language"` + Author string `json:"author"` + Editer string `json:"editer"` + Proofreader string `json:"proofreader"` + Remarker string `json:"remarker"` + Transfer string `json:"transfer"` + Drawer string `json:"drawer"` + Publishers string `json:"publishers"` + PublishNo string `json:"publishNo"` + Series string `json:"series"` + Brand string `json:"brand"` + Format string `json:"format"` + PackageStr string `json:"packageStr"` + Pages string `json:"pages"` + BatchNo string `json:"batchNo"` + PublishTime string `json:"publishTime"` + PrintNo string `json:"printNo"` + PrintTime string `json:"printTime"` + SizeAndHeight string `json:"sizeAndHeight"` + ChinaCatalog string `json:"chinaCatalog"` + Attachment string `json:"attachment"` + AttachmentNum string `json:"attachmentNum"` + PackNum string `json:"packNum"` + Letters string `json:"letters"` + BarCode string `json:"barCode"` + Compile string `json:"compile"` + Photography string `json:"photography"` + PicNo string `json:"picNo"` + MarketPrice string `json:"marketPrice"` +} +type skuBaseInfo struct { + SkuName string `json:"skuName"` // 商品名称 + VenderName string `json:"venderName"` // 商家名称 + ShopName string `json:"shopName"` // 店铺名称 + CategoryId1 uint `json:"categoryId1"` // 一级分类id + CategoryId2 uint `json:"categoryId2"` // 二级分类id + CategoryId uint `json:"categoryId"` // 三级分类id + Length float64 `json:"length"` // 长 mm + Width float64 `json:"width"` // 宽 mm + Height float64 `json:"height"` // 高 mm + Weight float64 `json:"weight"` // 重 kg + PackageType string `json:"packageType"` // 包装规格 + Model string `json:"model"` // 型号 + Color string `json:"color"` // 颜色 + ColorSequence string `json:"colorSequence"` // 颜色顺序 + UpcCode string `json:"upcCode"` // upc码 + Size string `json:"size"` // 尺码 + SizeSequence string `json:"sizeSequence"` // 尺码顺序 + Unit string `json:"unit"` // 单位 + Warranty string `json:"warranty"` // 质保 + ShelfLife string `json:"shelfLife"` // 保质期天数 + Delivery string `json:"delivery"` // 发货地址 + PlaceOfProduction string `json:"placeOfProduction"` // 产地 + Tax string `json:"tax"` // 税率 + ProductId uint `json:"productId"` // 商品编号 无意义 + SkuStatus uint `json:"skuStatus"` // 商家状态 1=上架 2=下架 与京东商品无关 + Yn uint `json:"yn"` // 是否有效 0=无效 1=有效 京东可能会将商品设置成无效商品 + Fare uint `json:"fare"` // 运费模版id 无意义 + CategoryName1 string `json:"categoryName1"` // 一级分类 + CategoryName2 string `json:"categoryName2"` // 二级分类 + CategoryName string `json:"categoryName"` // 三级分类 + //SkuInfoType string `json:"skuInfoType"` // sku信息类型 1-图书 2-音像 + //BookSkuBaseInfo skuBaseBookInfo `json:"bookSkuBaseInfo"` // 图书基本信息 skuInfoType=1时获取 + //WareType uint `json:"wareType"` // 商品类型 (1-普通商品; 2-虚拟组套) +} +type skuAttribute struct { + AttName string `json:"attName"` // 属性名 + ValNames []string `json:"valNames"` // 属性值列表 +} +type skuSpecification struct { + GroupName string `json:"groupName"` // 组名称 + Attributes []skuAttribute `json:"attributes"` // 属性信息列表 +} + +type skuBigFieldInfo struct { + PcWdis string `json:"pcWdis"` // pc端商品介绍 + PcHtmlContent string `json:"pcHtmlContent"` // PC HTML 可能为null + PcJsContent string `json:"pcJsContent"` // PC CSS 可能为null + PcCssContent string `json:"pcCssContent"` // PC JS 可能为null +} +type Detail struct { + ImageInfos []imageInfo `json:"imageInfos"` // 图片信息 + Specifications []skuSpecification `json:"specifications"` // 规格参数 + SkuId uint64 `json:"skuId"` // skuId + SkuBaseInfo skuBaseInfo `json:"skuBaseInfo"` // 基础信息 + SkuBigFieldInfo skuBigFieldInfo `json:"skuBigFieldInfo"` // 大字段 商品详情 + ExtAtts []skuAttribute `json:"extAtts"` + //WReadMe string `json:"wReadMe"` +} + +// GetDetail @Title 获取商品详情 sku最大20 +func (s *Sku) GetDetail(skuIds []uint64) (result []Detail, err error) { + err = request.ExecCtlProtocol(skuDetail, skuDetailParam{ + SkuIdSet: skuIds, + }, &result) + return result, err +} + +type SkuQuantityItem struct { + Quantity uint `json:"quantity"` // 数量 + SkuId uint64 `json:"skuId"` // skuId +} + +type StockStateParam struct { + SkuQuantityList []SkuQuantityItem `json:"skuQuantityList"` // 商品信息 最多20个 + Address address.Address `json:"address"` // 收货地址 +} +type stockStateList struct { + SkuQuantity SkuQuantityItem `json:"skuQuantity"` // 对应商品信息 + AreaStockState uint `json:"areaStockState"` // 库存状态。0:无货 1:有货 2:采购中 + LeadTime string `json:"leadTime"` // 采购中商品的预计到货时间 +} + +// GetStock @Title 获取商品库存 最多20个 +func (s *Sku) GetStock(data StockStateParam) (result []stockStateList, err error) { + err = request.ExecCtlProtocol(skuStock, data, &result) + return result, err +}