package sdk_LinkedMall import ( "encoding/json" "fmt" "io" "net/http" "strconv" "strings" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/alibabacloud-go/tea/v2" ) const ( defaultRegionId = "cn-zhangjiakou" defaultEndpoint = "linkedmall.cn-zhangjiakou.aliyuncs.com" basePath = "/opensaas-s2b/opensaas-s2b-biz-trade/v2" readTimeout = 30000 connectTimeout = 10000 ) // Client LinkedMall SDK 客户端 type Client struct { inner *openapi.Client } // NewClient 创建一个新的LinkedMall SDK客户端 // // 使用RAM子账号的AccessKey初始化,禁止使用主账号AK。 // 示例: // // cli, err := NewClient("your-access-key-id", "your-access-key-secret") // if err != nil { // panic(err) // } func NewClient(accessKeyId, accessKeySecret string) (*Client, error) { config := &openapi.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), RegionId: tea.String(defaultRegionId), Endpoint: tea.String(defaultEndpoint), ReadTimeout: tea.Int(readTimeout), ConnectTimeout: tea.Int(connectTimeout), } c, err := openapi.NewClient(config) if err != nil { return nil, fmt.Errorf("failed to create openapi client: %w", err) } return &Client{inner: c}, nil } // buildPath 构建完整的请求路径 func buildPath(suffix string) string { return basePath + suffix } // toQueryMap 将结构体转为查询参数map func toQueryMap(params interface{}) map[string]*string { result := make(map[string]*string) if params == nil { return result } // 使用json标签序列化后转为map data, _ := json.Marshal(params) var rawMap map[string]interface{} if err := json.Unmarshal(data, &rawMap); err != nil { return result } for k, v := range rawMap { switch val := v.(type) { case string: if val != "" { result[k] = tea.String(val) } case float64: if val != 0 { result[k] = tea.String(strconv.FormatInt(int64(val), 10)) } case bool: result[k] = tea.String(strconv.FormatBool(val)) default: // 忽略复杂类型 } } return result } // doRequest 执行HTTP请求并返回响应体字节 func (c *Client) doRequest(method, path string, query map[string]*string, body interface{}) ([]byte, error) { resp, err := c.inner.RoaRequest( tea.String(method), tea.String(path), query, body, nil, ) if err != nil { return nil, fmt.Errorf("roa request failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected http status %d, body: %s", resp.StatusCode, string(bodyBytes)) } return io.ReadAll(resp.Body) } // parseResponse 解析通用响应,如果业务失败返回 *APIError func (c *Client) parseResponse(data []byte, target interface{}) error { var commonResp CommonResponse if err := json.Unmarshal(data, &commonResp); err != nil { return fmt.Errorf("failed to parse common response: %w, body: %s", err, string(data)) } if !commonResp.Success { return &APIError{ Code: commonResp.Code, Message: commonResp.Message, SubCode: commonResp.SubCode, SubMessage: commonResp.SubMessage, RequestId: commonResp.RequestId, } } if target != nil && len(commonResp.Data) > 0 { if err := json.Unmarshal(commonResp.Data, target); err != nil { return fmt.Errorf("failed to parse data field: %w, data: %s", err, string(commonResp.Data)) } } return nil } // ============================================================ // 采购方店铺 (Purchaser Shop) // ============================================================ // ListPurchaserShops 分页获取采购方店铺列表 func (c *Client) ListPurchaserShops(req *ListPurchaserShopsReq) (*ListPurchaserShopsRespData, error) { path := buildPath("/purchaser-shops") query := toQueryMap(req) data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result ListPurchaserShopsRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // GetPurchaserShop 获取单个采购店铺详情 func (c *Client) GetPurchaserShop(shopId string) (*GetPurchaserShopRespData, error) { path := buildPath("/purchaser-shops/" + shopId) data, err := c.doRequest("GET", path, nil, nil) if err != nil { return nil, err } var result GetPurchaserShopRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 选品池商品 (Selection Product) // ============================================================ // ListSelectionProducts 分页查询选品池商品列表 func (c *Client) ListSelectionProducts(req *ListSelectionProductsReq) (*ListSelectionProductsRespData, error) { path := buildPath("/selection-products") query := toQueryMap(req) data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result ListSelectionProductsRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // GetSelectionProduct 查询选品池商品详情 // // divisionCode 为可选参数,传入五级乡镇区域编码后可返回该区域可售状态和库存。 func (c *Client) GetSelectionProduct(productId, purchaserId, divisionCode string) (*GetSelectionProductRespData, error) { path := buildPath("/selection-products/" + productId) query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } if divisionCode != "" { query["DivisionCode"] = tea.String(divisionCode) } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result GetSelectionProductRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // GetSelectionProductSaleInfo 查询选品池商品销售信息 func (c *Client) GetSelectionProductSaleInfo(productId, purchaserId, divisionCode string) (*GetSelectionProductSaleInfoRespData, error) { path := buildPath("/selection-products/" + productId + "/sale-info") query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } if divisionCode != "" { query["DivisionCode"] = tea.String(divisionCode) } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result GetSelectionProductSaleInfoRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ListSelectionProductSaleInfos 批量查询商品销售信息 func (c *Client) ListSelectionProductSaleInfos(req *ListSelectionProductSaleInfosReq) (*ListSelectionProductSaleInfosRespData, error) { path := buildPath("/selection-products:batch-sale-info") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result ListSelectionProductSaleInfosRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ListSelectionSkuSaleInfos 批量查询SKU销售信息 func (c *Client) ListSelectionSkuSaleInfos(req *ListSelectionSkuSaleInfosReq) (*ListSelectionSkuSaleInfosRespData, error) { path := buildPath("/selection-skus:batch-sale-info") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result ListSelectionSkuSaleInfosRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ListCategories 查询类目列表 func (c *Client) ListCategories(req *ListCategoriesReq) (*ListCategoriesRespData, error) { path := buildPath("/categories") query := toQueryMap(req) data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result ListCategoriesRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // SearchProducts 搜索选品池商品 func (c *Client) SearchProducts(req *SearchProductsReq) (*SearchProductsRespData, error) { path := buildPath("/selection-products:search") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result SearchProductsRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 选品池入库/出库 // ============================================================ // SelectionGroupAddProduct 选品池商品入库 func (c *Client) SelectionGroupAddProduct(req *SelectionGroupAddProductReq) (*OperationRespData, error) { path := buildPath("/selection-group/products:add") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result OperationRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // SelectionGroupRemoveProduct 选品池商品出库 func (c *Client) SelectionGroupRemoveProduct(req *SelectionGroupRemoveProductReq) (*OperationRespData, error) { path := buildPath("/selection-group/products:remove") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result OperationRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 采购单 (Purchase Order) // ============================================================ // RenderPurchaseOrder 采购单渲染(下单预校验) // // 下单前必须调用,获取实时价格、校验商品是否可售。 func (c *Client) RenderPurchaseOrder(req *RenderPurchaseOrderReq) (*RenderPurchaseOrderRespData, error) { path := buildPath("/purchase-orders:render") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result RenderPurchaseOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // SplitPurchaseOrder 采购单渲染并拆单 // // 返回拆成多份子单结构,用于前端展示,拆单结果用于创建采购单入参。 func (c *Client) SplitPurchaseOrder(req *SplitPurchaseOrderReq) (*SplitPurchaseOrderRespData, error) { path := buildPath("/purchase-orders:split-render") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result SplitPurchaseOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // CreatePurchaseOrder 创建采购单【异步】 // // 重要:接口只返回采购单号,不代表下单成功,必须等待回调通知,再查询订单接口。 // 禁止在同步返回后直接标记本地订单成功。 func (c *Client) CreatePurchaseOrder(req *CreatePurchaseOrderReq) (*CreatePurchaseOrderRespData, error) { path := buildPath("/purchase-orders") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result CreatePurchaseOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // GetPurchaseOrderStatus 获取采购单状态 // // 状态枚举:INIT(初始化)、PROCESS(处理中)、SUCCESS(成功)、FAIL(失败)、CLOSED(已关闭) func (c *Client) GetPurchaseOrderStatus(purchaseOrderId, purchaserId string) (*GetPurchaseOrderStatusRespData, error) { path := buildPath("/purchase-orders/" + purchaseOrderId + "/status") query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result GetPurchaseOrderStatusRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 订单 (Order) // ============================================================ // GetOrder 获取订单详情 func (c *Client) GetOrder(orderId, purchaserId string) (*GetOrderRespData, error) { path := buildPath("/orders/" + orderId) query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result GetOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // QueryOrders 查询订单列表 func (c *Client) QueryOrders(req *QueryOrdersReq) (*QueryOrdersRespData, error) { path := buildPath("/orders") query := toQueryMap(req) data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result QueryOrdersRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ListLogisticsOrders 查询订单物流信息 func (c *Client) ListLogisticsOrders(orderId, purchaserId string) (*ListLogisticsOrdersRespData, error) { path := buildPath("/orders/" + orderId + "/logistics") query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result ListLogisticsOrdersRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ConfirmDisburse 确认收货 func (c *Client) ConfirmDisburse(orderId, purchaserId string) (*OperationRespData, error) { path := buildPath("/orders/" + orderId + ":confirm-disburse") body := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("POST", path, nil, body) if err != nil { return nil, err } var result OperationRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 售后单 (Refund Order) // ============================================================ // RenderRefundOrder 售后渲染预校验 func (c *Client) RenderRefundOrder(req *RenderRefundOrderReq) (*RenderRefundOrderRespData, error) { path := buildPath("/refund-orders:render") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result RenderRefundOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // CreateRefundOrder 创建售后单 func (c *Client) CreateRefundOrder(req *CreateRefundOrderReq) (*CreateRefundOrderRespData, error) { path := buildPath("/refund-orders") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result CreateRefundOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // CancelRefundOrder 取消售后单 func (c *Client) CancelRefundOrder(refundOrderId, purchaserId string) (*CancelRefundOrderRespData, error) { path := buildPath("/refund-orders/" + refundOrderId + ":cancel") body := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("POST", path, nil, body) if err != nil { return nil, err } var result CancelRefundOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // GetRefundOrder 获取售后单详情 func (c *Client) GetRefundOrder(refundOrderId, purchaserId string) (*GetRefundOrderRespData, error) { path := buildPath("/refund-orders/" + refundOrderId) query := map[string]*string{ "PurchaserId": tea.String(purchaserId), } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result GetRefundOrderRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // CreateGoodsShippingNotice 回填退货物流运单 func (c *Client) CreateGoodsShippingNotice(refundOrderId string, req *CreateGoodsShippingNoticeReq) (*CreateGoodsShippingNoticeRespData, error) { path := buildPath("/refund-orders/" + refundOrderId + ":fill-logistics") data, err := c.doRequest("POST", path, nil, req) if err != nil { return nil, err } var result CreateGoodsShippingNoticeRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 区域编码 (Division Code) // ============================================================ // QueryChildDivisionCode 查询子区域编码 // // 不传ParentDivisionCode返回省级编码; // 传入省返回市,传入市返回区,传入区返回乡镇(五级)。 // 下单、商品可售校验必须拿到乡镇级DivisionCode。 func (c *Client) QueryChildDivisionCode(parentDivisionCode string) (*QueryChildDivisionCodeRespData, error) { path := buildPath("/divisions:children") var query map[string]*string if parentDivisionCode != "" { query = map[string]*string{ "ParentDivisionCode": tea.String(parentDivisionCode), } } data, err := c.doRequest("GET", path, query, nil) if err != nil { return nil, err } var result QueryChildDivisionCodeRespData if err := c.parseResponse(data, &result); err != nil { return nil, err } return &result, nil } // ============================================================ // 回调解析工具 // ============================================================ // CallbackEvent 回调通知事件结构体 type CallbackEvent struct { EventCode string `json:"EventCode"` // 事件类型 PurchaseOrderId string `json:"PurchaseOrderId"` // 采购单ID OrderIdList []string `json:"OrderIdList"` // 附属真实订单ID数组 Status string `json:"Status"` // 状态 RequestId string `json:"RequestId"` // 请求ID } // ParseCallbackEvent 解析回调通知的JSON Body // // 业务侧在回调接口中接收阿里云POST请求,解析事件后做本地状态同步。 // 处理完成后需返回 {"success":true} 告知阿里云消费成功。 func ParseCallbackEvent(body []byte) (*CallbackEvent, error) { var event CallbackEvent if err := json.Unmarshal(body, &event); err != nil { return nil, fmt.Errorf("failed to parse callback event: %w", err) } return &event, nil } // CallbackSuccessResponse 回调成功响应 // // 业务处理完回调事件后,返回此结构告知阿里云消费成功。 func CallbackSuccessResponse() string { return `{"success":true}` } // IsAPIError 判断错误是否为指定错误码 func IsAPIError(err error, code string) bool { if apiErr, ok := err.(*APIError); ok { return apiErr.Code == code } return false } // ============================================================ // 辅助函数 // ============================================================ // ToQueryString 将结构体转为URL查询字符串(仅用于调试) func ToQueryString(params interface{}) string { query := toQueryMap(params) if len(query) == 0 { return "" } var parts []string for k, v := range query { if v != nil { parts = append(parts, k+"="+*v) } } return strings.Join(parts, "&") }