sdk_LinkedMall-20260828093422/sdk_LinkedMall/client.go

678 lines
19 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, "&")
}