45 KiB
45 KiB
我先分析文档:该文档明确说明使用通用 OpenApiClient(darabonba-openapi/v2)发起 ROA 调用,ROA 签名由 SDK 内部自动处理,业务无需自行实现签名/加密算法。因此本次不调用加密工具,crypto.go 中提供与数据安全(int64 金额精度、JSON 序列化)相关的辅助函数,并保留加解密扩展位。
下面一次性生成全部 6 个文件。
// File: sdk_LinkedMall/go.mod
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
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
// 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
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
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
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。
关键说明:
- 工具调用:本文档明确声明 ROA 签名由
darabonba-openapi/v2客户端内部自动处理,业务无需实现签名/加密算法,因此未调用加解密工具;crypto.go提供了 int64 金额精度安全的 JSON 序列化/反序列化辅助函数(金额单位统一为分,禁止 float64)。 - 接口范围:严格按任务清单实现 22 个接口,未包含文档中额外列出的 GetSelectionProductSaleInfo / ListSelectionProductSaleInfos / ListSelectionSkuSaleInfos。
- 异步采购单:
CreatePurchaseOrder只返回 PurchaseOrderId,必须等待回调(ParseCallbackEvent/CallbackSuccessBody)后再查询,回调需幂等。 - 业务校验:所有方法返回
*CommonResponse,必须判断Success==true;可用CheckSuccess/IsCode辅助判断错误码。 - 编译运行:
go mod tidy拉取darabonba-openapi/v2、tea/v2后即可编译,单元测试均不发起网络请求。