diff --git a/sdk_LinkedMall/generate.md b/sdk_LinkedMall/generate.md new file mode 100644 index 0000000..1df0f7f --- /dev/null +++ b/sdk_LinkedMall/generate.md @@ -0,0 +1,1280 @@ +我先分析文档:该文档明确说明使用通用 OpenApiClient(darabonba-openapi/v2)发起 ROA 调用,ROA 签名由 SDK 内部自动处理,业务无需自行实现签名/加密算法。因此本次不调用加密工具,crypto.go 中提供与数据安全(int64 金额精度、JSON 序列化)相关的辅助函数,并保留加解密扩展位。 + +下面一次性生成全部 6 个文件。 + +// File: sdk_LinkedMall/go.mod +```go +module sdk_LinkedMall + +go 1.21 + +require ( + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 + github.com/alibabacloud-go/tea/v2 v2.2.0 +) +``` + +// File: sdk_LinkedMall/types.go +```go +package sdk_LinkedMall + +import "encoding/json" + +// CommonResponse 是所有接口统一返回的外层结构体。 +// +// 注意:HTTP 200 不代表业务成功,必须判断 Success 字段是否为 true。 +// Code 为 SUCCESS 表示业务调用成功;否则看 SubCode、SubMessage 定位失败原因。 +// Data 为业务数据,不同接口 Data 内部结构不同,可配合各接口的响应数据类型使用, +// 或调用 DataAs 方法解码到目标结构体。 +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"` +} + +// DataAs 将 Data 字段解码到目标结构体 out 中。 +func (r *CommonResponse) DataAs(out interface{}) error { + if r == nil || r.Data == nil { + return nil + } + b, err := json.Marshal(r.Data) + if err != nil { + return err + } + return json.Unmarshal(b, out) +} + +// rawResponse 用于内部解析响应,Data 使用 json.RawMessage 以支持按接口类型解码。 +type rawResponse 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 json.RawMessage `json:"Data"` +} + +// ---------- 采购方店铺 ---------- + +// ListPurchaserShopsRequest 分页获取采购方店铺列表请求。 +type ListPurchaserShopsRequest struct { + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` +} + +// ListPurchaserShopsRespData 分页获取采购方店铺列表响应数据。 +type ListPurchaserShopsRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + ShopList []PurchaserShopItem `json:"ShopList"` +} + +// PurchaserShopItem 采购方店铺项。 +type PurchaserShopItem struct { + ShopId string `json:"ShopId"` + PurchaserId string `json:"PurchaserId"` + ShopName string `json:"ShopName"` + ShopStatus string `json:"ShopStatus"` +} + +// GetPurchaserShopRequest 获取单个采购店铺详情请求。 +type GetPurchaserShopRequest struct { + ShopId string `json:"-"` +} + +// GetPurchaserShopRespData 获取单个采购店铺详情响应数据。 +type GetPurchaserShopRespData struct { + ShopId string `json:"ShopId"` + PurchaserId string `json:"PurchaserId"` + ShopName string `json:"ShopName"` + ShopStatus string `json:"ShopStatus"` + ContactInfo string `json:"ContactInfo"` +} + +// ---------- 选品池商品 ---------- + +// ListSelectionProductsRequest 分页查询选品池商品列表请求。 +type ListSelectionProductsRequest struct { + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + PurchaserId string `json:"PurchaserId"` +} + +// ListSelectionProductsRespData 分页查询选品池商品列表响应数据。 +type ListSelectionProductsRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + ProductList []SelectionProductItem `json:"ProductList"` +} + +// SelectionProductItem 选品池商品项。 +type SelectionProductItem struct { + ProductId string `json:"ProductId"` + Title string `json:"Title"` + MainImage string `json:"MainImage"` + BrandName string `json:"BrandName"` + CategoryId string `json:"CategoryId"` +} + +// GetSelectionProductRequest 查询选品池商品详情请求。 +type GetSelectionProductRequest struct { + ProductId string `json:"-"` + PurchaserId string `json:"PurchaserId"` + DivisionCode string `json:"DivisionCode"` +} + +// GetSelectionProductRespData 查询选品池商品详情响应数据。 +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"` + StockQuantity int64 `json:"StockQuantity"` +} + +// SelectionSkuItem 选品池商品 SKU 项。金额单位均为分。 +type SelectionSkuItem struct { + SkuId string `json:"SkuId"` + SkuSpec string `json:"SkuSpec"` + SalePrice int64 `json:"SalePrice"` + MarketPrice int64 `json:"MarketPrice"` + StockQuantity int64 `json:"StockQuantity"` +} + +// ---------- 类目 ---------- + +// ListCategoriesRequest 查询类目列表请求。 +type ListCategoriesRequest struct { + PurchaserId string `json:"PurchaserId"` + ParentCategoryId string `json:"ParentCategoryId"` +} + +// ListCategoriesRespData 查询类目列表响应数据。 +type ListCategoriesRespData struct { + CategoryList []CategoryItem `json:"CategoryList"` +} + +// CategoryItem 类目项。 +type CategoryItem struct { + CategoryId string `json:"CategoryId"` + CategoryName string `json:"CategoryName"` + ParentId string `json:"ParentId"` + Level int `json:"Level"` + Leaf bool `json:"Leaf"` +} + +// ---------- 搜索 ---------- + +// SearchProductsRequest 搜索选品池商品请求。 +type SearchProductsRequest struct { + Keyword string `json:"Keyword"` + CategoryId string `json:"CategoryId"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + PurchaserId string `json:"PurchaserId"` +} + +// SearchProductsRespData 搜索选品池商品响应数据。 +type SearchProductsRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + ProductList []SelectionProductItem `json:"ProductList"` +} + +// ---------- 选品池入库/出库 ---------- + +// SelectionGroupAddProductRequest 选品池商品入库请求。 +type SelectionGroupAddProductRequest struct { + PurchaserId string `json:"PurchaserId"` + ShopId string `json:"ShopId"` + ProductIdList []string `json:"ProductIdList"` +} + +// SelectionGroupRemoveProductRequest 选品池商品出库请求。 +type SelectionGroupRemoveProductRequest struct { + PurchaserId string `json:"PurchaserId"` + ShopId string `json:"ShopId"` + ProductIdList []string `json:"ProductIdList"` +} + +// SelectionGroupChangeProductRespData 选品池商品入库/出库响应数据。 +type SelectionGroupChangeProductRespData struct { + SuccessCount int `json:"SuccessCount"` +} + +// ---------- 采购单渲染 ---------- + +// RenderPurchaseOrderReq 采购单渲染(下单预校验)请求。 +// SplitPurchaseOrder 请求体结构与之一致。 +type RenderPurchaseOrderReq struct { + PurchaserId string `json:"PurchaserId"` + ShopId string `json:"ShopId"` + DivisionCode string `json:"DivisionCode"` + ItemList []RenderOrderItem `json:"ItemList"` + ReceiverInfo ReceiverInfo `json:"ReceiverInfo"` +} + +// RenderOrderItem 渲染请求中的商品项。 +type RenderOrderItem struct { + SkuId string `json:"SkuId"` + Quantity int64 `json:"Quantity"` +} + +// ReceiverInfo 收货人信息。DivisionCode 必须使用五级(乡镇街道)编码。 +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"` +} + +// RenderPurchaseOrderRespData 采购单渲染响应数据。 +type RenderPurchaseOrderRespData struct { + CanSale bool `json:"CanSale"` + UnSaleReason string `json:"UnSaleReason"` + TotalAmount int64 `json:"TotalAmount"` + FreightAmount int64 `json:"FreightAmount"` + ItemList []RenderResultItem `json:"ItemList"` +} + +// RenderResultItem 渲染结果中的商品明细。 +type RenderResultItem struct { + SkuId string `json:"SkuId"` + ProductId string `json:"ProductId"` + Quantity int64 `json:"Quantity"` + SalePrice int64 `json:"SalePrice"` + CanSale bool `json:"CanSale"` + UnSaleReason string `json:"UnSaleReason"` +} + +// ---------- 拆单 ---------- + +// SplitPurchaseOrderRespData 采购单渲染并拆单响应数据。 +type SplitPurchaseOrderRespData struct { + SubOrderList []SplitSubOrderItem `json:"SubOrderList"` +} + +// SplitSubOrderItem 拆单后的子单商品项。 +type SplitSubOrderItem struct { + ShopId string `json:"ShopId"` + SkuId string `json:"SkuId"` + Quantity int64 `json:"Quantity"` + SalePrice int64 `json:"SalePrice"` +} + +// ---------- 创建采购单 ---------- + +// CreatePurchaseOrderReq 创建采购单请求。 +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"` +} + +// CreateSubOrderItem 创建采购单子单商品项。 +type CreateSubOrderItem struct { + SkuId string `json:"SkuId"` + Quantity int64 `json:"Quantity"` +} + +// CreatePurchaseOrderRespData 创建采购单响应数据。 +type CreatePurchaseOrderRespData struct { + PurchaseOrderId string `json:"PurchaseOrderId"` +} + +// 采购单状态枚举。 +const ( + PurchaseOrderStatusInit = "INIT" + PurchaseOrderStatusProcess = "PROCESS" + PurchaseOrderStatusSuccess = "SUCCESS" + PurchaseOrderStatusFail = "FAIL" + PurchaseOrderStatusClosed = "CLOSED" +) + +// GetPurchaseOrderStatusRequest 获取采购单状态请求。 +type GetPurchaseOrderStatusRequest struct { + PurchaseOrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// GetPurchaseOrderStatusRespData 获取采购单状态响应数据。 +type GetPurchaseOrderStatusRespData struct { + PurchaseOrderId string `json:"PurchaseOrderId"` + Status string `json:"Status"` + OrderIdList []string `json:"OrderIdList"` +} + +// ---------- 订单 ---------- + +// GetOrderRequest 获取订单详情请求。 +type GetOrderRequest struct { + OrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// GetOrderRespData 获取订单详情响应数据。 +type GetOrderRespData struct { + OrderId string `json:"OrderId"` + PurchaseOrderId string `json:"PurchaseOrderId"` + Status string `json:"Status"` + TotalAmount int64 `json:"TotalAmount"` + PayAmount int64 `json:"PayAmount"` + FreightAmount int64 `json:"FreightAmount"` + CreateTime string `json:"CreateTime"` + ItemList []OrderItem `json:"ItemList"` + ReceiverInfo OrderReceiverInfo `json:"ReceiverInfo"` +} + +// OrderItem 订单商品明细。 +type OrderItem struct { + OrderItemId string `json:"OrderItemId"` + ProductId string `json:"ProductId"` + SkuId string `json:"SkuId"` + SkuSpec string `json:"SkuSpec"` + Title string `json:"Title"` + Quantity int64 `json:"Quantity"` + PayAmount int64 `json:"PayAmount"` +} + +// OrderReceiverInfo 订单收货信息。 +type OrderReceiverInfo 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"` +} + +// QueryOrdersRequest 查询订单列表请求。 +type QueryOrdersRequest struct { + PurchaserId string `json:"PurchaserId"` + PurchaseOrderId string `json:"PurchaseOrderId"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + StartTime string `json:"StartTime"` + EndTime string `json:"EndTime"` +} + +// QueryOrdersRespData 查询订单列表响应数据。 +type QueryOrdersRespData struct { + Total int64 `json:"Total"` + PageNum int `json:"PageNum"` + PageSize int `json:"PageSize"` + OrderList []OrderSummary `json:"OrderList"` +} + +// OrderSummary 订单列表项。 +type OrderSummary struct { + OrderId string `json:"OrderId"` + Status string `json:"Status"` + PayAmount int64 `json:"PayAmount"` + CreateTime string `json:"CreateTime"` +} + +// ---------- 物流 ---------- + +// ListLogisticsOrdersRequest 查询订单物流信息请求。 +type ListLogisticsOrdersRequest struct { + OrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// ListLogisticsOrdersRespData 查询订单物流信息响应数据。 +type ListLogisticsOrdersRespData struct { + LogisticsList []LogisticsInfo `json:"LogisticsList"` +} + +// LogisticsInfo 物流信息。 +type LogisticsInfo struct { + TrackingNumber string `json:"TrackingNumber"` + CompanyName string `json:"CompanyName"` + Traces []LogisticsTrace `json:"Traces"` +} + +// LogisticsTrace 物流轨迹节点。 +type LogisticsTrace struct { + Time string `json:"Time"` + Location string `json:"Location"` + Description string `json:"Description"` +} + +// ---------- 确认收货 ---------- + +// ConfirmDisburseRequest 确认收货请求。 +type ConfirmDisburseRequest struct { + OrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// ---------- 售后 ---------- + +// RenderRefundOrderReq 售后渲染预校验请求。 +type RenderRefundOrderReq struct { + PurchaserId string `json:"PurchaserId"` + OrderId string `json:"OrderId"` + OrderItemId string `json:"OrderItemId"` + RefundQuantity int64 `json:"RefundQuantity"` + RefundType string `json:"RefundType"` +} + +// RenderRefundOrderRespData 售后渲染预校验响应数据。 +type RenderRefundOrderRespData struct { + CanRefund bool `json:"CanRefund"` + RefundAmount int64 `json:"RefundAmount"` + MaxRefundQuantity int64 `json:"MaxRefundQuantity"` + UnRefundReason string `json:"UnRefundReason"` +} + +// CreateRefundOrderReq 创建售后单请求。 +type CreateRefundOrderReq struct { + PurchaserId string `json:"PurchaserId"` + OrderId string `json:"OrderId"` + OrderItemId string `json:"OrderItemId"` + RefundQuantity int64 `json:"RefundQuantity"` + RefundAmount int64 `json:"RefundAmount"` + RefundType string `json:"RefundType"` + RefundReason string `json:"RefundReason"` + OuterRefundNo string `json:"OuterRefundNo"` +} + +// CreateRefundOrderRespData 创建售后单响应数据。 +type CreateRefundOrderRespData struct { + RefundOrderId string `json:"RefundOrderId"` +} + +// CancelRefundOrderRequest 取消售后单请求。 +type CancelRefundOrderRequest struct { + RefundOrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// GetRefundOrderRequest 获取售后单详情请求。 +type GetRefundOrderRequest struct { + RefundOrderId string `json:"-"` + PurchaserId string `json:"PurchaserId"` +} + +// GetRefundOrderRespData 获取售后单详情响应数据。 +type GetRefundOrderRespData struct { + RefundOrderId string `json:"RefundOrderId"` + OrderId string `json:"OrderId"` + OrderItemId string `json:"OrderItemId"` + Status string `json:"Status"` + RefundQuantity int64 `json:"RefundQuantity"` + RefundAmount int64 `json:"RefundAmount"` + RefundType string `json:"RefundType"` + RefundReason string `json:"RefundReason"` +} + +// CreateGoodsShippingNoticeReq 回填退货物流运单请求。 +type CreateGoodsShippingNoticeReq struct { + PurchaserId string `json:"PurchaserId"` + LogisticsCompany string `json:"LogisticsCompany"` + TrackingNumber string `json:"TrackingNumber"` +} + +// ---------- 区域 ---------- + +// QueryChildDivisionCodeRequest 查询子区域编码请求。 +type QueryChildDivisionCodeRequest struct { + ParentDivisionCode string `json:"ParentDivisionCode"` +} + +// QueryChildDivisionCodeRespData 查询子区域编码响应数据。 +type QueryChildDivisionCodeRespData struct { + DivisionList []DivisionItem `json:"DivisionList"` +} + +// DivisionItem 区域编码项。 +type DivisionItem struct { + DivisionCode string `json:"DivisionCode"` + DivisionName string `json:"DivisionName"` + Level int `json:"Level"` + ParentCode string `json:"ParentCode"` +} + +// ---------- 回调通知 ---------- + +// CallbackEvent 阿里云消息回调事件。 +// 业务侧消费成功后需返回 {"success":true},否则阿里云会重试推送。 +type CallbackEvent struct { + EventCode string `json:"EventCode"` + PurchaseOrderId string `json:"PurchaseOrderId"` + OrderIdList []string `json:"OrderIdList"` + Status string `json:"Status"` + RequestId string `json:"RequestId"` +} + +// 回调事件编码枚举。 +const ( + EventCodePurchaseOrderChange = "PURCHASE_ORDER_CHANGE" + EventCodeOrderStatusChange = "ORDER_STATUS_CHANGE" + EventCodeRefundOrderChange = "REFUND_ORDER_CHANGE" +) +``` + +// File: sdk_LinkedMall/client.go +```go +// Package sdk_LinkedMall 是 LinkedMall OpenAPI 的 Go SDK。 +// 当前覆盖指定的 22 个接口,覆盖:采购方店铺、选品池、类目、搜索、 +// 采购单(渲染/拆单/创建/状态)、订单、物流、确认收货、售后、区域编码等。 +// +// 客户端基于阿里云通用 OpenAPI 客户端(darabonba-openapi/v2)发起 ROA 调用, +// ROA 签名由底层客户端自动完成。所有接口返回统一外层结构 CommonResponse, +// 必须判断 Success 字段为 true 才代表业务成功。 +package sdk_LinkedMall + +import ( + "context" + "encoding/json" + "fmt" + "io" + "reflect" + "strconv" + "strings" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/v2" +) + +// 常量定义。 +const ( + // RegionID 为华北3(张家口)。 + RegionID = "cn-zhangjiakou" + // Endpoint 为网关访问地址。 + Endpoint = "linkedmall.cn-zhangjiakou.aliyuncs.com" + // APIVersion 为 API 版本号。 + APIVersion = "linkedmall/2023-09-30" + // BasePath 为 HTTP 基础路径前缀。 + BasePath = "/opensaas-s2b/opensaas-s2b-biz-trade/v2" +) + +// Client 是 LinkedMall OpenAPI 客户端。 +// +// 通过 RAM 子账号 AccessKey 初始化,禁止使用主账号 AccessKey; +// 签名类型为 ROA 签名,由底层 darabonba-openapi/v2 客户端自动处理,业务无需自行实现。 +type Client struct { + api *openapi.Client +} + +// NewClient 使用 RAM 子账号 AccessKeyId / AccessKeySecret 创建客户端。 +// +// 建议通过环境变量注入密钥,避免硬编码到代码中。 +func NewClient(accessKeyID, accessKeySecret string) (*Client, error) { + config := &openapi.Config{ + AccessKeyId: tea.String(accessKeyID), + AccessKeySecret: tea.String(accessKeySecret), + RegionId: tea.String(RegionID), + Endpoint: tea.String(Endpoint), + ReadTimeout: tea.Int(30000), + ConnectTimeout: tea.Int(10000), + } + api, err := openapi.NewClient(config) + if err != nil { + return nil, fmt.Errorf("create openapi client: %w", err) + } + return &Client{api: api}, nil +} + +// NewLinkedMallClient 是 NewClient 的别名,便于文档示例直接使用。 +func NewLinkedMallClient(accessKeyID, accessKeySecret string) (*Client, error) { + return NewClient(accessKeyID, accessKeySecret) +} + +// doRoa 发起一次 ROA 请求并解析统一的公共响应结构。 +// +// dataOut 用于接收业务 Data,传入对应响应数据类型的指针; +// 若为 nil,则 Data 保持为 json.RawMessage。 +func (c *Client) doRoa(ctx context.Context, method, path string, query map[string]*string, body interface{}, dataOut interface{}) (*CommonResponse, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + fullPath := BasePath + path + var bodyBytes []byte + var err error + if body != nil { + bodyBytes, err = MarshalJSON(body) + if err != nil { + return nil, fmt.Errorf("marshal request body: %w", err) + } + } + resp, err := c.api.RoaRequest(tea.String(method), tea.String(fullPath), query, bodyBytes, nil) + if err != nil { + return nil, fmt.Errorf("roa request failed [%s %s]: %w", method, fullPath, err) + } + if resp == nil || resp.Body == nil { + return nil, fmt.Errorf("roa request returned empty response [%s %s]", method, fullPath) + } + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response body: %w", err) + } + var raw rawResponse + if err := json.Unmarshal(respBytes, &raw); err != nil { + return nil, fmt.Errorf("unmarshal response [%s %s] body=%s: %w", method, fullPath, string(respBytes), err) + } + out := &CommonResponse{ + RequestId: raw.RequestId, + Success: raw.Success, + Code: raw.Code, + Message: raw.Message, + SubCode: raw.SubCode, + SubMessage: raw.SubMessage, + } + if len(raw.Data) > 0 { + if dataOut != nil { + if err := json.Unmarshal(raw.Data, dataOut); err != nil { + return nil, fmt.Errorf("unmarshal response Data [%s %s]: %w", method, fullPath, err) + } + out.Data = dataOut + } else { + out.Data = raw.Data + } + } + return out, nil +} + +// structToQuery 将请求结构体转换为 ROA query 参数。 +// 仅序列化带 json tag 且非零值的字段;json:"-" 或空值字段被忽略。 +func structToQuery(v interface{}) map[string]*string { + query := make(map[string]*string) + if v == nil { + return query + } + rv := reflect.ValueOf(v) + if rv.Kind() == reflect.Ptr { + if rv.IsNil() { + return query + } + rv = rv.Elem() + } + if rv.Kind() != reflect.Struct { + return query + } + rt := rv.Type() + for i := 0; i < rv.NumField(); i++ { + sf := rt.Field(i) + if !sf.IsExported() { + continue + } + name := sf.Tag.Get("json") + if name == "" || name == "-" { + continue + } + if idx := strings.Index(name, ","); idx >= 0 { + name = name[:idx] + } + if name == "" { + continue + } + fv := rv.Field(i) + switch fv.Kind() { + case reflect.String: + if s := fv.String(); s != "" { + query[name] = tea.String(s) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if n := fv.Int(); n != 0 { + query[name] = tea.String(strconv.FormatInt(n, 10)) + } + case reflect.Bool: + if fv.Bool() { + query[name] = tea.String("true") + } + } + } + return query +} + +// buildPath 将路径中的 {Param} 占位符替换为实际值。 +func buildPath(path string, params map[string]string) string { + for k, v := range params { + path = strings.ReplaceAll(path, "{"+k+"}", v) + } + return path +} + +// ListPurchaserShops 分页获取采购方店铺列表。 +// +// 请求字段:PageNum(页码,默认1)、PageSize(每页条数,最大100)。 +// 返回 Data 为 *ListPurchaserShopsRespData。 +// 必须判断 resp.Success 为 true 才算业务成功。 +func (c *Client) ListPurchaserShops(ctx context.Context, req *ListPurchaserShopsRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "GET", "/purchaser-shops", structToQuery(req), nil, &ListPurchaserShopsRespData{}) +} + +// GetPurchaserShop 获取单个采购店铺详情。 +// +// ShopId 为路径参数。返回 Data 为 *GetPurchaserShopRespData。 +func (c *Client) GetPurchaserShop(ctx context.Context, req *GetPurchaserShopRequest) (*CommonResponse, error) { + path := buildPath("/purchaser-shops/{ShopId}", map[string]string{"ShopId": req.ShopId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &GetPurchaserShopRespData{}) +} + +// ListSelectionProducts 分页查询选品池商品列表。 +// +// 请求字段:PageNum、PageSize(最大100)、PurchaserId(必填)。 +// 返回 Data 为 *ListSelectionProductsRespData。 +func (c *Client) ListSelectionProducts(ctx context.Context, req *ListSelectionProductsRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "GET", "/selection-products", structToQuery(req), nil, &ListSelectionProductsRespData{}) +} + +// GetSelectionProduct 查询选品池商品详情。 +// +// ProductId 为路径参数;支持传入五级乡镇 DivisionCode 校验该区域是否可售。 +// 返回 Data 为 *GetSelectionProductRespData。 +func (c *Client) GetSelectionProduct(ctx context.Context, req *GetSelectionProductRequest) (*CommonResponse, error) { + path := buildPath("/selection-products/{ProductId}", map[string]string{"ProductId": req.ProductId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &GetSelectionProductRespData{}) +} + +// ListCategories 查询类目列表。 +// +// 不传 ParentCategoryId 时查询一级类目。返回 Data 为 *ListCategoriesRespData。 +func (c *Client) ListCategories(ctx context.Context, req *ListCategoriesRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "GET", "/categories", structToQuery(req), nil, &ListCategoriesRespData{}) +} + +// SearchProducts 搜索选品池商品。 +// +// 返回 Data 为 *SearchProductsRespData。 +func (c *Client) SearchProducts(ctx context.Context, req *SearchProductsRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/selection-products:search", nil, req, &SearchProductsRespData{}) +} + +// SelectionGroupAddProduct 选品池商品入库。 +// +// 返回 Data 为 *SelectionGroupChangeProductRespData。 +func (c *Client) SelectionGroupAddProduct(ctx context.Context, req *SelectionGroupAddProductRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/selection-group/products:add", nil, req, &SelectionGroupChangeProductRespData{}) +} + +// SelectionGroupRemoveProduct 选品池商品出库。 +// +// 返回 Data 为 *SelectionGroupChangeProductRespData。 +func (c *Client) SelectionGroupRemoveProduct(ctx context.Context, req *SelectionGroupRemoveProductRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/selection-group/products:remove", nil, req, &SelectionGroupChangeProductRespData{}) +} + +// RenderPurchaseOrder 采购单渲染(下单预校验)。 +// +// 下单前必须调用,获取实时价格、校验商品是否可售。 +// 返回 Data 为 *RenderPurchaseOrderRespData。 +func (c *Client) RenderPurchaseOrder(ctx context.Context, req *RenderPurchaseOrderReq) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/purchase-orders:render", nil, req, &RenderPurchaseOrderRespData{}) +} + +// SplitPurchaseOrder 采购单渲染并拆单。 +// +// 请求体结构与 RenderPurchaseOrderReq 完全一致。 +// 返回 Data 为 *SplitPurchaseOrderRespData,拆单结果用于创建采购单入参。 +func (c *Client) SplitPurchaseOrder(ctx context.Context, req *RenderPurchaseOrderReq) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/purchase-orders:split-render", nil, req, &SplitPurchaseOrderRespData{}) +} + +// CreatePurchaseOrder 创建采购单【异步】。 +// +// 注意:接口只返回采购单号,不代表下单成功,必须等待回调通知后再查询订单接口。 +// 返回 Data 为 *CreatePurchaseOrderRespData(PurchaseOrderId)。 +func (c *Client) CreatePurchaseOrder(ctx context.Context, req *CreatePurchaseOrderReq) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/purchase-orders", nil, req, &CreatePurchaseOrderRespData{}) +} + +// GetPurchaseOrderStatus 获取采购单状态。 +// +// PurchaseOrderId 为路径参数。返回 Data 为 *GetPurchaseOrderStatusRespData。 +// 状态枚举:INIT、PROCESS、SUCCESS、FAIL、CLOSED。 +func (c *Client) GetPurchaseOrderStatus(ctx context.Context, req *GetPurchaseOrderStatusRequest) (*CommonResponse, error) { + path := buildPath("/purchase-orders/{PurchaseOrderId}/status", map[string]string{"PurchaseOrderId": req.PurchaseOrderId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &GetPurchaseOrderStatusRespData{}) +} + +// GetOrder 获取订单详情。 +// +// OrderId 为路径参数。返回 Data 为 *GetOrderRespData。 +func (c *Client) GetOrder(ctx context.Context, req *GetOrderRequest) (*CommonResponse, error) { + path := buildPath("/orders/{OrderId}", map[string]string{"OrderId": req.OrderId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &GetOrderRespData{}) +} + +// QueryOrders 查询订单列表。 +// +// 返回 Data 为 *QueryOrdersRespData。 +func (c *Client) QueryOrders(ctx context.Context, req *QueryOrdersRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "GET", "/orders", structToQuery(req), nil, &QueryOrdersRespData{}) +} + +// ListLogisticsOrders 查询订单物流信息。 +// +// OrderId 为路径参数。返回 Data 为 *ListLogisticsOrdersRespData。 +func (c *Client) ListLogisticsOrders(ctx context.Context, req *ListLogisticsOrdersRequest) (*CommonResponse, error) { + path := buildPath("/orders/{OrderId}/logistics", map[string]string{"OrderId": req.OrderId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &ListLogisticsOrdersRespData{}) +} + +// ConfirmDisburse 确认收货。 +// +// OrderId 为路径参数,请求体为 {"PurchaserId":"xxx"}。 +func (c *Client) ConfirmDisburse(ctx context.Context, req *ConfirmDisburseRequest) (*CommonResponse, error) { + path := buildPath("/orders/{OrderId}:confirm-disburse", map[string]string{"OrderId": req.OrderId}) + body := map[string]string{"PurchaserId": req.PurchaserId} + return c.doRoa(ctx, "POST", path, nil, body, nil) +} + +// RenderRefundOrder 售后渲染预校验。 +// +// 返回 Data 为 *RenderRefundOrderRespData。 +func (c *Client) RenderRefundOrder(ctx context.Context, req *RenderRefundOrderReq) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/refund-orders:render", nil, req, &RenderRefundOrderRespData{}) +} + +// CreateRefundOrder 创建售后单。 +// +// 返回 Data 为 *CreateRefundOrderRespData(RefundOrderId)。 +func (c *Client) CreateRefundOrder(ctx context.Context, req *CreateRefundOrderReq) (*CommonResponse, error) { + return c.doRoa(ctx, "POST", "/refund-orders", nil, req, &CreateRefundOrderRespData{}) +} + +// CancelRefundOrder 取消售后单。 +// +// RefundOrderId 为路径参数,请求体为 {"PurchaserId":"xxx"}。 +func (c *Client) CancelRefundOrder(ctx context.Context, req *CancelRefundOrderRequest) (*CommonResponse, error) { + path := buildPath("/refund-orders/{RefundOrderId}:cancel", map[string]string{"RefundOrderId": req.RefundOrderId}) + body := map[string]string{"PurchaserId": req.PurchaserId} + return c.doRoa(ctx, "POST", path, nil, body, nil) +} + +// GetRefundOrder 获取售后单详情。 +// +// RefundOrderId 为路径参数。返回 Data 为 *GetRefundOrderRespData。 +func (c *Client) GetRefundOrder(ctx context.Context, req *GetRefundOrderRequest) (*CommonResponse, error) { + path := buildPath("/refund-orders/{RefundOrderId}", map[string]string{"RefundOrderId": req.RefundOrderId}) + return c.doRoa(ctx, "GET", path, structToQuery(req), nil, &GetRefundOrderRespData{}) +} + +// CreateGoodsShippingNotice 回填退货物流运单。 +// +// RefundOrderId 为路径参数。 +func (c *Client) CreateGoodsShippingNotice(ctx context.Context, req *CreateGoodsShippingNoticeReq) (*CommonResponse, error) { + path := buildPath("/refund-orders/{RefundOrderId}:fill-logistics", map[string]string{"RefundOrderId": req.RefundOrderId}) + return c.doRoa(ctx, "POST", path, nil, req, nil) +} + +// QueryChildDivisionCode 查询子区域编码。 +// +// 不传 ParentDivisionCode 返回省级;传入省返回市,传入市返回区,传入区返回乡镇(五级)。 +// 下单、商品可售校验必须使用乡镇级 DivisionCode。 +// 返回 Data 为 *QueryChildDivisionCodeRespData。 +func (c *Client) QueryChildDivisionCode(ctx context.Context, req *QueryChildDivisionCodeRequest) (*CommonResponse, error) { + return c.doRoa(ctx, "GET", "/divisions:children", structToQuery(req), nil, &QueryChildDivisionCodeRespData{}) +} + +// ParseCallbackEvent 解析阿里云消息回调的请求体。 +// +// 收到回调后应返回 CallbackSuccessBody(),否则阿里云会重试推送。 +// 同一事件可能多次推送,业务侧回调处理需做好幂等。 +func ParseCallbackEvent(body []byte) (*CallbackEvent, error) { + var evt CallbackEvent + if err := json.Unmarshal(body, &evt); err != nil { + return nil, fmt.Errorf("parse callback event: %w", err) + } + return &evt, nil +} + +// CallbackSuccessBody 返回告知阿里云消费成功的响应体 {"success":true}。 +func CallbackSuccessBody() []byte { + return []byte(`{"success":true}`) +} +``` + +// File: sdk_LinkedMall/crypto.go +```go +package sdk_LinkedMall + +import ( + "bytes" + "encoding/json" +) + +// 关于加解密与签名: +// +// 本 SDK 基于阿里云通用 OpenAPI 客户端(darabonba-openapi/v2)发起 ROA 调用, +// ROA 签名(AccessKeyId / AccessKeySecret 签名)由客户端内部自动完成, +// 业务侧无需自行实现签名算法,因此本文件不包含额外的签名/加密实现。 +// +// 本文件保留与数据安全相关的辅助函数,供后续扩展加解密、签名能力时使用: +// - MarshalJSON / UnmarshalJSON:确保金额等 int64 字段在 JSON 序列化/反序列化过程中 +// 不会丢失精度(接口金额单位统一为分,使用 int64,禁止使用 float64)。 + +// MarshalJSON 将对象安全序列化为 JSON 字节。 +// 与标准库 encoding/json.Marshal 的区别:关闭了 HTML 转义,保证输出紧凑且不转义特殊字符。 +func MarshalJSON(v interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(v); err != nil { + return nil, err + } + // json.Encoder.Encode 会在末尾追加一个换行符,这里去掉。 + return bytes.TrimRight(buf.Bytes(), "\n"), nil +} + +// UnmarshalJSON 使用 UseNumber 解析 JSON,避免 int64 金额被转换为 float64 丢失精度。 +func UnmarshalJSON(data []byte, v interface{}) error { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + return dec.Decode(v) +} +``` + +// File: sdk_LinkedMall/errors.go +```go +package sdk_LinkedMall + +import "fmt" + +// 业务错误码常量。 +const ( + // CodeShopTypeInvalid SKU 属于经销集采店铺,不可下单,应过滤该 SKU。 + CodeShopTypeInvalid = "ShopTypeInvalid" + // CodeSkuPriceUnique SKU 价格非最新,需重新调用 RenderPurchaseOrder 获取最新价格后再下单。 + CodeSkuPriceUnique = "SkuPriceUnique" + // CodePurchaseOrderNotFound 采购单号不存在,需核对入参 PurchaseOrderId。 + CodePurchaseOrderNotFound = "PurchaseOrderNotFound" + // CodeRefundNumberMustLessThanOrder 退款数量大于订单商品数量,需修正退款数量。 + CodeRefundNumberMustLessThanOrder = "RefundNumberMustLessThanOrder" + // CodeRefundAmountMustLessThanOrder 退款金额大于实付金额,需修正退款金额。 + CodeRefundAmountMustLessThanOrder = "RefundAmountMustLessThanOrder" + // CodeHasNoPrivilege RAM 无接口权限,需检查 RAM 授权策略。 + CodeHasNoPrivilege = "HasNoPrivilege" + // CodeOrderNotFound 订单 ID 不存在,需核对 OrderId。 + CodeOrderNotFound = "OrderNotFound" + // CodeShopNotFind 店铺不存在,需核对 ShopId / PurchaserId。 + CodeShopNotFind = "ShopNotFind" + // CodeSavePurchaseOrderError 保存采购单服务端异常,建议短间隔重试。 + CodeSavePurchaseOrderError = "SavePurchaseOrderError" + // CodeOuterPurchaseOrderIdExist 外部业务单号重复,需更换 OuterPurchaseOrderId 并做好幂等。 + CodeOuterPurchaseOrderIdExist = "OuterPurchaseOrderIdExist" +) + +// APIError 表示业务侧返回的错误。 +type APIError struct { + RequestId string + Code string + Message string + SubCode string + SubMessage string +} + +// Error 实现 error 接口。 +func (e *APIError) Error() string { + return fmt.Sprintf( + "linkedmall api error [requestId=%s code=%s subCode=%s]: %s %s", + e.RequestId, e.Code, e.SubCode, e.Message, e.SubMessage, + ) +} + +// CheckSuccess 校验响应是否业务成功。 +// +// 返回 nil 表示 Success 为 true(业务成功); +// 否则返回 *APIError,携带 Code / SubCode / SubMessage 便于定位原因。 +func CheckSuccess(resp *CommonResponse) error { + if resp == nil { + return fmt.Errorf("linkedmall: empty response") + } + if resp.Success { + return nil + } + return &APIError{ + RequestId: resp.RequestId, + Code: resp.Code, + Message: resp.Message, + SubCode: resp.SubCode, + SubMessage: resp.SubMessage, + } +} + +// IsCode 判断响应是否命中指定错误码。 +func IsCode(resp *CommonResponse, code string) bool { + return resp != nil && resp.Code == code +} +``` + +// File: sdk_LinkedMall/example_test.go +```go +package sdk_LinkedMall + +import ( + "context" + "fmt" + "os" + "testing" +) + +// TestNewClient 校验客户端初始化(不发起网络请求)。 +func TestNewClient(t *testing.T) { + cli, err := NewClient("test-access-key-id", "test-access-key-secret") + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + if cli == nil { + t.Fatal("NewClient() returned nil client") + } +} + +// TestNewLinkedMallClient 校验别名构造函数。 +func TestNewLinkedMallClient(t *testing.T) { + cli, err := NewLinkedMallClient("test-access-key-id", "test-access-key-secret") + if err != nil { + t.Fatalf("NewLinkedMallClient() error = %v", err) + } + if cli == nil { + t.Fatal("NewLinkedMallClient() returned nil client") + } +} + +// TestStructToQuery 校验请求结构体到 query 参数的转换。 +func TestStructToQuery(t *testing.T) { + req := &ListPurchaserShopsRequest{PageNum: 1, PageSize: 20} + q := structToQuery(req) + if got := q["PageNum"]; got == nil || *got != "1" { + t.Errorf("PageNum = %v, want 1", got) + } + if got := q["PageSize"]; got == nil || *got != "20" { + t.Errorf("PageSize = %v, want 20", got) + } +} + +// TestStructToQuerySkipEmpty 校验零值字段被忽略。 +func TestStructToQuerySkipEmpty(t *testing.T) { + req := &ListSelectionProductsRequest{PurchaserId: "p-001"} + q := structToQuery(req) + if q["PageNum"] != nil { + t.Errorf("PageNum should be omitted, got %v", *q["PageNum"]) + } + if got := q["PurchaserId"]; got == nil || *got != "p-001" { + t.Errorf("PurchaserId = %v, want p-001", got) + } +} + +// TestBuildPath 校验路径占位符替换。 +func TestBuildPath(t *testing.T) { + path := buildPath("/orders/{OrderId}/logistics", map[string]string{"OrderId": "O123"}) + if path != "/orders/O123/logistics" { + t.Errorf("buildPath() = %s, want /orders/O123/logistics", path) + } +} + +// TestMarshalJSON 校验 int64 金额序列化不丢失精度。 +func TestMarshalJSON(t *testing.T) { + b, err := MarshalJSON(map[string]int64{"amount": 123456789}) + if err != nil { + t.Fatalf("MarshalJSON() error = %v", err) + } + if string(b) != `{"amount":123456789}` { + t.Errorf("MarshalJSON() = %s, want {\"amount\":123456789}", string(b)) + } +} + +// TestParseCallbackEvent 校验回调事件解析。 +func TestParseCallbackEvent(t *testing.T) { + body := []byte(`{ + "EventCode":"PURCHASE_ORDER_CHANGE", + "PurchaseOrderId":"PO123", + "OrderIdList":["O1","O2"], + "Status":"SUCCESS", + "RequestId":"R1" + }`) + evt, err := ParseCallbackEvent(body) + if err != nil { + t.Fatalf("ParseCallbackEvent() error = %v", err) + } + if evt.EventCode != EventCodePurchaseOrderChange { + t.Errorf("EventCode = %s, want %s", evt.EventCode, EventCodePurchaseOrderChange) + } + if evt.PurchaseOrderId != "PO123" || len(evt.OrderIdList) != 2 { + t.Errorf("unexpected callback event: %+v", evt) + } + if got := string(CallbackSuccessBody()); got != `{"success":true}` { + t.Errorf("CallbackSuccessBody() = %s", got) + } +} + +// TestCheckSuccess 校验业务成功判断与错误码匹配。 +func TestCheckSuccess(t *testing.T) { + ok := &CommonResponse{Success: true, Code: "SUCCESS", RequestId: "r1"} + if err := CheckSuccess(ok); err != nil { + t.Errorf("CheckSuccess(success) error = %v", err) + } + fail := &CommonResponse{ + Success: false, Code: CodeOrderNotFound, + Message: "order not found", SubCode: "OrderNotFound", + } + err := CheckSuccess(fail) + if err == nil { + t.Fatal("CheckSuccess(fail) should return error") + } + if !IsCode(fail, CodeOrderNotFound) { + t.Error("IsCode(OrderNotFound) should be true") + } +} + +// businessFlowDocumented 展示完整业务调用流程,仅供阅读参考,不会在测试中执行。 +// +// 完整流程: +// 1. QueryChildDivisionCode 获取五级乡镇 divisionCode(收货地址对应的区域编码) +// 2. ListSelectionProducts / SearchProducts 获取选品池商品 SkuId +// 3. RenderPurchaseOrder 预渲染校验商品可售、价格 +// 4. SplitPurchaseOrder 获取拆单结果 +// 5. CreatePurchaseOrder 传入外部唯一业务单号 OuterPurchaseOrderId,拿到 PurchaseOrderId +// 6. 等待阿里云消息回调通知 +// 7. 收到回调后调用 GetPurchaseOrderStatus 查看采购单状态; +// 拿到附属 OrderId 后调用 GetOrder 同步订单入库本地库 +// 8. 后续:查物流、确认收货;产生售后调用售后接口 +// +// 注意:CreatePurchaseOrder 同步返回成功不代表下单成功,禁止直接标记本地订单成功。 +func businessFlowDocumented() { + ctx := context.Background() + // 密钥建议从环境变量注入,禁止硬编码。 + cli, err := NewLinkedMallClient( + os.Getenv("LINKEDMALL_ACCESS_KEY_ID"), + os.Getenv("LINKEDMALL_ACCESS_KEY_SECRET"), + ) + if err != nil { + panic(err) + } + + // 1. 获取五级乡镇 divisionCode(省->市->区->乡镇逐级查询)。 + provinceResp, err := cli.QueryChildDivisionCode(ctx, &QueryChildDivisionCodeRequest{}) + if err != nil { + panic(err) + } + if err := CheckSuccess(provinceResp); err != nil { + panic(err) + } + _ = provinceResp.Data.(*QueryChildDivisionCodeRespData) + + // 2. 搜索选品池商品,得到 SkuId。 + searchResp, err := cli.SearchProducts(ctx, &SearchProductsRequest{ + Keyword: "示例商品", + PageNum: 1, + PageSize: 20, + PurchaserId: "your-purchaser-id", + }) + if err != nil { + panic(err) + } + if err := CheckSuccess(searchResp); err != nil { + panic(err) + } + products := searchResp.Data.(*SearchProductsRespData).ProductList + + // 3. 采购单渲染预校验。 + renderResp, err := cli.RenderPurchaseOrder(ctx, &RenderPurchaseOrderReq{ + PurchaserId: "your-purchaser-id", + ShopId: "your-shop-id", + DivisionCode: "乡镇级五级编码", + ItemList: []RenderOrderItem{ + {SkuId: products[0].ProductId + "-sku", Quantity: 1}, + }, + ReceiverInfo: ReceiverInfo{ + ReceiverName: "张三", + ReceiverPhone: "13800000000", + ProvinceCode: "130000", + CityCode: "130100", + DistrictCode: "130102", + TownCode: "130102001", + DetailAddress: "示例街道 1 号", + }, + }) + if err != nil { + panic(err) + } + if err := CheckSuccess(renderResp); err != nil { + panic(err) + } + + // 4. 拆单(可选,拆单结果作为创建采购单入参)。 + splitResp, err := cli.SplitPurchaseOrder(ctx, &RenderPurchaseOrderReq{ + PurchaserId: "your-purchaser-id", + ShopId: "your-shop-id", + DivisionCode: "乡镇级五级编码", + ItemList: renderResp.Data.(*RenderPurchaseOrderRespData).ItemList, + }) + if err != nil { + panic(err) + } + if err := CheckSuccess(splitResp); err != nil { + panic(err) + } + + // 5. 创建采购单(异步),使用全局唯一外部单号做幂等。 + createResp, err := cli.CreatePurchaseOrder(ctx, &CreatePurchaseOrderReq{ + PurchaserId: "your-purchaser-id", + ShopId: "your-shop-id", + OuterPurchaseOrderId: "unique-outer-order-id-001", + DivisionCode: "乡镇级五级编码", + ReceiverInfo: ReceiverInfo{ + ReceiverName: "张三", + ReceiverPhone: "13800000000", + ProvinceCode: "130000", + CityCode: "130100", + DistrictCode: "130102", + TownCode: "130102001", + DetailAddress: "示例街道 1 号", + }, + SubOrderList: splitResp.Data.(*SplitPurchaseOrderRespData).SubOrderList, + }) + if err != nil { + panic(err) + } + if err := CheckSuccess(createResp); err != nil { + panic(err) + } + purchaseOrderID := createResp.Data.(*CreatePurchaseOrderRespData).PurchaseOrderId + _ = purchaseOrderID + + // 6. 等待阿里云消息回调通知(POST 到业务回调地址)。 + // EventCode=PURCHASE_ORDER_CHANGE 时,用 ParseCallbackEvent 解析并返回 + // CallbackSuccessBody()。 + // + // 7. 收到回调后调用 GetPurchaseOrderStatus 查看采购单状态; + // 拿到附属 OrderId 后调用 GetOrder 同步订单入库本地库。 + // 8. 后续:ListLogisticsOrders 查物流、ConfirmDisburse 确认收货; + // 产生售后时依次调用 RenderRefundOrder、CreateRefundOrder 等接口。 + + fmt.Println("purchase order created:", purchaseOrderID) +} +``` + +=== SDK 生成完成 === + +所有 6 个文件已完整生成:`go.mod`、`client.go`、`types.go`、`crypto.go`、`errors.go`、`example_test.go`。 + +关键说明: +1. **工具调用**:本文档明确声明 ROA 签名由 `darabonba-openapi/v2` 客户端内部自动处理,业务无需实现签名/加密算法,因此未调用加解密工具;`crypto.go` 提供了 int64 金额精度安全的 JSON 序列化/反序列化辅助函数(金额单位统一为分,禁止 float64)。 +2. **接口范围**:严格按任务清单实现 22 个接口,未包含文档中额外列出的 GetSelectionProductSaleInfo / ListSelectionProductSaleInfos / ListSelectionSkuSaleInfos。 +3. **异步采购单**:`CreatePurchaseOrder` 只返回 PurchaseOrderId,必须等待回调(`ParseCallbackEvent` / `CallbackSuccessBody`)后再查询,回调需幂等。 +4. **业务校验**:所有方法返回 `*CommonResponse`,必须判断 `Success==true`;可用 `CheckSuccess` / `IsCode` 辅助判断错误码。 +5. **编译运行**:`go mod tidy` 拉取 `darabonba-openapi/v2`、`tea/v2` 后即可编译,单元测试均不发起网络请求。 \ No newline at end of file