diff --git a/README.md b/README.md new file mode 100644 index 0000000..011761a --- /dev/null +++ b/README.md @@ -0,0 +1,555 @@ +## 文档概述 +- 接口总数:25个 +- API版本:linkedmall/2023-09-30 +- RegionId:cn-zhangjiakou(华北3 张家口) +- Endpoint:`linkedmall.cn-zhangjiakou.aliyuncs.com` +- HTTP基础Path前缀:`/opensaas-s2b/opensaas-s2b-biz-trade/v2` +- 签名类型:ROA签名,SDK内部自动处理 +- 金额单位:分(整数,long/int64) +- 区域编码divisionCode:必须使用五级(乡镇街道级别)编码 + +## 认证与安全 +- 使用RAM子账号AccessKey(AccessKeyId / AccessKeySecret),禁止主账号AK +- 授予LinkedMall对应OpenAPI最小权限 +- 业务前置资源:purchaserId(采购方ID)、shopId(店铺ID),由LinkedMall控制台分配 +- 签名类型为ROA签名,SDK内部自动处理,业务无需实现签名算法 +- 使用通用OpenApiClient(darabonba-openapi/v2)发起ROA调用 + +## 公共返回结构体 +所有接口返回外层统一格式: +```go +type CommonResponse struct { + RequestId string `json:"RequestId"` + Success bool `json:"Success"` + Code string `json:"Code"` + Message string `json:"Message"` + SubCode string `json:"SubCode"` + SubMessage string `json:"SubMessage"` + Data interface{} `json:"Data"` // 业务数据,不同接口Data内部结构不同 +} +``` +公共Code枚举: +- `SUCCESS`:业务调用成功 +- 其他:业务失败,看SubCode、SubMessage定位原因 +> 注意:HTTP 200 不代表业务成功,必须判断 `Success==true` + +## SDK客户端初始化 +```go +package main + +import ( + "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/v2" +) + +func NewLinkedMallClient(accessKeyId, accessKeySecret string) (*client.Client, error) { + config := &client.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + RegionId: tea.String("cn-zhangjiakou"), + Endpoint: tea.String("linkedmall.cn-zhangjiakou.aliyuncs.com"), + ReadTimeout: tea.Int(30000), + ConnectTimeout: tea.Int(10000), + } + return client.NewClient(config) +} +``` +调用示例: +```go +cli, err := NewLinkedMallClient("AKxxx", "SKxxx") +resp, err := cli.RoaRequest( + tea.String("POST"), + tea.String("/opensaas-s2b/opensaas-s2b-biz-trade/v2/xxx"), + nil, // query map[string]*string + bodyObj, // post json body + nil, // header +) +``` + +## 接口列表 + +### 接口 1:ListPurchaserShops 获取采购方店铺列表 +- 路径:`/purchaser-shops` +- 方法:GET +- 描述:分页获取采购方店铺列表 + +#### 请求参数(Query) +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PageNum | int | 否 | 页码,默认1 | +| PageSize | int | 否 | 每页条数,最大100 | + +#### 响应Data结构体 +```go +type ListPurchaserShopsRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + ShopList []PurchaserShopItem `json:"ShopList"` +} +type PurchaserShopItem struct { + ShopId string `json:"ShopId"` + PurchaserId string `json:"PurchaserId"` + ShopName string `json:"ShopName"` + ShopStatus string `json:"ShopStatus"` +} +``` + +### 接口 2:GetPurchaserShop 获取采购方店铺详情 +- 路径:`/purchaser-shops/{ShopId}` +- 方法:GET +- 描述:获取单个采购店铺详情 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| ShopId | string | Path | 是 | 店铺ID | + +#### 响应Data结构体 +```go +type GetPurchaserShopRespData struct { + ShopId string `json:"ShopId"` + PurchaserId string `json:"PurchaserId"` + ShopName string `json:"ShopName"` + ShopStatus string `json:"ShopStatus"` + ContactInfo string `json:"ContactInfo"` +} +``` + +### 接口 3:ListSelectionProducts 查询选品池商品列表 +- 路径:`/selection-products` +- 方法:GET +- 描述:分页查询选品池商品列表 + +#### 请求参数(Query) +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PageNum | int | 否 | 页码 | +| PageSize | int | 否 | 最大100 | +| PurchaserId | string | 是 | 采购方ID | + +#### 响应Data结构体 +```go +type ListSelectionProductsRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + ProductList []SelectionProductItem `json:"ProductList"` +} +type SelectionProductItem struct { + ProductId string `json:"ProductId"` + Title string `json:"Title"` + MainImage string `json:"MainImage"` + BrandName string `json:"BrandName"` + CategoryId string `json:"CategoryId"` +} +``` + +### 接口 4:GetSelectionProduct 查询选品池商品详情 +- 路径:`/selection-products/{ProductId}` +- 方法:GET +- 描述:查询单个商品详情,支持传入divisionCode校验区域可售库存 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| ProductId | string | Path | 是 | 商品ID | +| PurchaserId | string | Query | 是 | 采购方ID | +| DivisionCode | string | Query | 否 | 五级乡镇区域编码,传后返回该区域可售状态 | + +#### 响应Data结构体 +```go +type GetSelectionProductRespData struct { + ProductId string `json:"ProductId"` + Title string `json:"Title"` + MainImage string `json:"MainImage"` + Images []string `json:"Images"` + BrandName string `json:"BrandName"` + CategoryId string `json:"CategoryId"` + Desc string `json:"Desc"` + SkuList []SelectionSkuItem `json:"SkuList"` + CanSale bool `json:"CanSale"` // 传入divisionCode才有效,该区域是否可售 + StockQuantity int64 `json:"StockQuantity"` +} +type SelectionSkuItem struct { + SkuId string `json:"SkuId"` + SkuSpec string `json:"SkuSpec"` + SalePrice int64 `json:"SalePrice"` // 单位分 + MarketPrice int64 `json:"MarketPrice"` + StockQuantity int64 `json:"StockQuantity"` +} +``` + +### 接口 5:GetSelectionProductSaleInfo 查询选品池商品销售信息 +- 路径:`/selection-products/{ProductId}/sale-info` +- 方法:GET +- 描述:查询商品可售、价格库存快照 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| ProductId | string | Path | 是 | 商品ID | +| PurchaserId | string | Query | 是 | 采购方ID | +| DivisionCode | string | Query | 否 | 五级乡镇区域编码 | + +### 接口 6:ListSelectionProductSaleInfos 批量查询商品销售信息 +- 路径:`/selection-products:batch-sale-info` +- 方法:POST +- 描述:批量查询商品销售信息 + +#### 请求Body +```json +{ + "PurchaserId": "xxx", + "ProductIdList": ["p1","p2"], + "DivisionCode": "五级编码" +} +``` + +### 接口 7:ListSelectionSkuSaleInfos 批量SKU销售信息 +- 路径:`/selection-skus:batch-sale-info` +- 方法:POST +- 描述:批量查询SKU销售信息 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| SkuIdList | []string | 是 | SKU ID列表 | +| DivisionCode | string | 否 | 五级乡镇区域编码 | + +### 接口 8:ListCategories 查询类目列表 +- 路径:`/categories` +- 方法:GET +- 描述:查询类目列表 + +#### 请求参数(Query) +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| ParentCategoryId | string | 否 | 父类目ID(不传查一级类目) | + +### 接口 9:SearchProducts 搜索选品池商品 +- 路径:`/selection-products:search` +- 方法:POST +- 描述:搜索选品池商品 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| Keyword | string | 否 | 关键词 | +| CategoryId | string | 否 | 类目ID | +| PageNum | int | 否 | 页码 | +| PageSize | int | 否 | 每页条数 | +| PurchaserId | string | 是 | 采购方ID | + +### 接口 10:SelectionGroupAddProduct 选品池商品入库 +- 路径:`/selection-group/products:add` +- 方法:POST +- 描述:选品池商品入库 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| ShopId | string | 是 | 店铺ID | +| ProductIdList | []string | 是 | 商品ID列表 | + +### 接口 11:SelectionGroupRemoveProduct 选品池商品出库 +- 路径:`/selection-group/products:remove` +- 方法:POST +- 描述:选品池商品出库 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| ShopId | string | 是 | 店铺ID | +| ProductIdList | []string | 是 | 商品ID列表 | + +### 接口 12:RenderPurchaseOrder 采购单渲染(下单预校验) +- 路径:`/purchase-orders:render` +- 方法:POST +- 描述:下单前必须调用,获取实时价格、校验商品是否可售 + +#### 请求Body +```go +type RenderPurchaseOrderReq struct { + PurchaserId string `json:"PurchaserId"` + ShopId string `json:"ShopId"` + DivisionCode string `json:"DivisionCode"` // 五级乡镇编码 + ItemList []RenderOrderItem `json:"ItemList"` + ReceiverInfo ReceiverInfo `json:"ReceiverInfo"` +} +type RenderOrderItem struct { + SkuId string `json:"SkuId"` + Quantity int64 `json:"Quantity"` +} +type ReceiverInfo struct { + ReceiverName string `json:"ReceiverName"` + ReceiverPhone string `json:"ReceiverPhone"` + ProvinceCode string `json:"ProvinceCode"` + CityCode string `json:"CityCode"` + DistrictCode string `json:"DistrictCode"` + TownCode string `json:"TownCode"` + DetailAddress string `json:"DetailAddress"` +} +``` +#### 响应Data +渲染后的价格、商品校验结果、不可售原因、预估运费。 + +### 接口 13:SplitPurchaseOrder 采购单渲染并拆单 +- 路径:`/purchase-orders:split-render` +- 方法:POST +- 描述:采购单渲染并拆单,返回拆成多份子单结构 + +#### 请求Body +结构和RenderPurchaseOrderReq完全一致 + +#### 响应Data +返回拆成多份子单结构,用于前端展示,拆单结果用于创建采购单入参。 + +### 接口 14:CreatePurchaseOrder 创建采购单【异步】 +- 路径:`/purchase-orders` +- 方法:POST +- 描述:⚠️重要:接口只返回采购单号,不代表下单成功,必须等待回调通知,再查询订单接口 + +#### 请求Body +```go +type CreatePurchaseOrderReq struct { + PurchaserId string `json:"PurchaserId"` + ShopId string `json:"ShopId"` + OuterPurchaseOrderId string `json:"OuterPurchaseOrderId"` // 外部业务唯一单号,幂等 + DivisionCode string `json:"DivisionCode"` + ReceiverInfo ReceiverInfo `json:"ReceiverInfo"` + SubOrderList []CreateSubOrderItem `json:"SubOrderList"` // SplitPurchaseOrder返回的子单 +} +type CreateSubOrderItem struct { + SkuId string `json:"SkuId"` + Quantity int64 `json:"Quantity"` +} +``` +#### 响应Data结构体 +```go +type CreatePurchaseOrderRespData struct { + PurchaseOrderId string `json:"PurchaseOrderId"` // 阿里云采购单号 +} +``` + +### 接口 15:GetPurchaseOrderStatus 获取采购单状态 +- 路径:`/purchase-orders/{PurchaseOrderId}/status` +- 方法:GET +- 描述:获取采购单状态 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| PurchaseOrderId | string | Path | 是 | 采购单ID | +| PurchaserId | string | Query | 是 | 采购方ID | + +#### 响应Data +采购单状态枚举:INIT、PROCESS、SUCCESS、FAIL、CLOSED + +### 接口 16:GetOrder 获取订单详情 +- 路径:`/orders/{OrderId}` +- 方法:GET +- 描述:获取订单详情 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| OrderId | string | Path | 是 | 订单ID | +| PurchaserId | string | Query | 是 | 采购方ID | + +#### 响应Data +订单主信息、商品明细、实付金额、状态、收货信息 + +### 接口 17:QueryOrders 查询订单列表 +- 路径:`/orders` +- 方法:GET +- 描述:查询订单列表 + +#### 请求参数(Query) +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| PurchaseOrderId | string | 否 | 采购单ID | +| PageNum | int | 否 | 页码 | +| PageSize | int | 否 | 每页条数 | +| StartTime | string | 否 | 开始时间 | +| EndTime | string | 否 | 结束时间 | + +### 接口 18:ListLogisticsOrders 查询订单物流信息 +- 路径:`/orders/{OrderId}/logistics` +- 方法:GET +- 描述:查询订单物流信息 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| OrderId | string | Path | 是 | 订单ID | +| PurchaserId | string | Query | 是 | 采购方ID | + +#### 响应Data +运单号、物流公司、物流轨迹数组 + +### 接口 19:ConfirmDisburse 确认收货 +- 路径:`/orders/{OrderId}:confirm-disburse` +- 方法:POST +- 描述:确认收货 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| OrderId | string | Path | 是 | 订单ID | + +#### 请求Body +```json +{"PurchaserId":"xxx"} +``` + +### 接口 20:RenderRefundOrder 售后渲染预校验 +- 路径:`/refund-orders:render` +- 方法:POST +- 描述:售后渲染预校验 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| OrderId | string | 是 | 订单ID | +| OrderItemId | string | 是 | 订单商品明细ID | +| RefundQuantity | int64 | 是 | 退款数量 | +| RefundType | string | 是 | 退款类型(仅退款/退货退款) | + +### 接口 21:CreateRefundOrder 创建售后单 +- 路径:`/refund-orders` +- 方法:POST +- 描述:创建售后单 + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| OrderId | string | 是 | 订单ID | +| OrderItemId | string | 是 | 订单商品明细ID | +| RefundQuantity | int64 | 是 | 退款数量 | +| RefundAmount | int64 | 是 | 退款金额(单位分) | +| RefundType | string | 是 | 退款类型 | +| RefundReason | string | 是 | 退款原因 | +| OuterRefundNo | string | 是 | 外部售后单号 | + +### 接口 22:CancelRefundOrder 取消售后单 +- 路径:`/refund-orders/{RefundOrderId}:cancel` +- 方法:POST +- 描述:取消售后单 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| RefundOrderId | string | Path | 是 | 售后单ID | + +#### 请求Body +```json +{"PurchaserId":"xxx"} +``` + +### 接口 23:GetRefundOrder 获取售后单详情 +- 路径:`/refund-orders/{RefundOrderId}` +- 方法:GET +- 描述:获取售后单详情 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| RefundOrderId | string | Path | 是 | 售后单ID | +| PurchaserId | string | Query | 是 | 采购方ID | + +### 接口 24:CreateGoodsShippingNotice 回填退货物流运单 +- 路径:`/refund-orders/{RefundOrderId}:fill-logistics` +- 方法:POST +- 描述:回填退货物流运单 + +#### 请求参数 +| 参数名 | 类型 | 位置 | 必填 | 说明 | +|--------|------|------|------|------| +| RefundOrderId | string | Path | 是 | 售后单ID | + +#### 请求Body参数 +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| PurchaserId | string | 是 | 采购方ID | +| LogisticsCompany | string | 是 | 物流公司 | +| TrackingNumber | string | 是 | 运单号 | + +### 接口 25:QueryChildDivisionCode 查询子区域编码 +- 路径:`/divisions:children` +- 方法:GET +- 描述:查询子区域编码,不传返回省级;传入省返回市,传入市返回区,传入区返回乡镇(五级) + +#### 请求参数(Query) +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ParentDivisionCode | string | 否 | 父区域编码 | + +#### 响应Data +DivisionCode列表、名称、层级;下单、商品可售校验必须拿到乡镇级DivisionCode + +## 错误码 +| 错误码 | 说明 | 处理建议 | +|--------|------|----------| +| ShopTypeInvalid | SKU属于经销集采店铺,不可下单 | 过滤SKU | +| SkuPriceUnique | SKU价格非最新 | 重新调用RenderPurchaseOrder获取最新价格再下单 | +| PurchaseOrderNotFound | 采购单号不存在 | 核对入参PurchaseOrderId | +| RefundNumberMustLessThanOrder | 退款数量大于订单商品数量 | 修正退款数量 | +| RefundAmountMustLessThanOrder | 退款金额大于实付金额 | 修正退款金额 | +| HasNoPrivilege | RAM无接口权限 | 检查RAM授权策略 | +| OrderNotFound | 订单ID不存在 | 核对OrderId | +| ShopNotFind | 店铺不存在 | 核对ShopId/PurchaserId | +| SavePurchaseOrderError | 保存采购单服务端异常 | 短间隔重试 | +| OuterPurchaseOrderIdExist | 外部业务单号重复 | 更换OuterPurchaseOrderId,做好幂等 | + +## 回调通知 +### 消息回调(异步通知)规范 +CreatePurchaseOrder创建采购单后,阿里云主动POST回调业务配置的回调地址。 + +1. 请求方式:POST,Content-Type: application/json +2. 业务侧需要返回 `{"success":true}` 告知阿里云消费成功;否则阿里云会重试推送。 +3. 回调Body公共字段: +```json +{ + "EventCode": "PURCHASE_ORDER_CHANGE", + "PurchaseOrderId": "xxx", + "OrderIdList": ["附属真实订单ID数组"], + "Status": "SUCCESS/FAIL", + "RequestId": "xxx" +} +``` +EventCode枚举: +- PURCHASE_ORDER_CHANGE:采购单状态变更 +- ORDER_STATUS_CHANGE:订单状态变更 +- REFUND_ORDER_CHANGE:售后单变更 + +业务处理逻辑:收到回调后,调用对应查询接口拉取最新业务数据做本地状态同步。 + +## 完整业务调用流程 +1. 调用 `QueryChildDivisionCode` 获取五级乡镇divisionCode(收货地址对应的区域编码) +2. 调用 `ListSelectionProducts` / `SearchProducts` 获取选品池商品SkuId +3. 调用 `RenderPurchaseOrder` 预渲染校验商品可售、价格 +4. 调用 `SplitPurchaseOrder` 获取拆单结果 +5. 调用 `CreatePurchaseOrder`,传入外部唯一业务单号OuterPurchaseOrderId,拿到PurchaseOrderId +6. **等待阿里云消息回调通知** +7. 回调收到事件,调用`GetPurchaseOrderStatus`看采购单状态;拿到附属OrderId调用`GetOrder`同步订单入库本地库 +8. 后续:查物流、确认收货;产生售后调用售后接口 + +> ❗禁止:CreatePurchaseOrder同步返回成功直接标记本地订单成功。 + +## 开发注意事项 +1. 金额全部为**分(int64)**,Go使用int64,不要float64;JSON序列化不要丢失long精度 +2. DivisionCode必须五级乡镇,否则商品可售校验错误 +3. OuterPurchaseOrderId外部采购单号全局唯一,用于幂等防重复下单 +4. CreatePurchaseOrder异步,依赖回调;不能轮询频率过高 +5. RAM子账号最小权限,AK禁止硬编码代码,环境变量配置 +6. 所有接口必须判断外层返回`Success==true`,HTTP 200不等于业务成功 +7. 分页接口PageSize上限100,不要传超大值 +8. 回调接口做好幂等,同一个事件可能多次推送 \ No newline at end of file