daShangDao_psiServer/service/wangdian.go

809 lines
24 KiB
Go

package service
import (
"encoding/json"
"fmt"
"log"
"psi/config"
"psi/database"
"psi/models"
"psi/utils"
"strconv"
"time"
"gorm.io/datatypes"
"gorm.io/gorm"
)
type WangdianPurchaseInfo struct {
ProviderNo string `json:"provider_no"`
WarehouseNo string `json:"warehouse_no"`
OuterNo string `json:"outer_no"`
IsUseOuterNo int8 `json:"is_use_outer_no,omitempty"`
IsCheck int8 `json:"is_check,omitempty"`
Contact string `json:"contact,omitempty"`
PurchaseName string `json:"purchase_name,omitempty"`
Telno string `json:"telno,omitempty"`
ReceiveAddress string `json:"receive_address,omitempty"`
LogisticsType int8 `json:"logistics_type,omitempty"` // 暂未使用 运货方式(物流公司)
ExpectArriveTime string `json:"expect_arrive_time,omitempty"`
Remark string `json:"remark,omitempty"`
OtherFee float64 `json:"other_fee,omitempty"` // 暂未使用 其他费用
PostFee float64 `json:"post_fee,omitempty"` // 暂未使用 邮资
Prop1 string `json:"prop1,omitempty"` // 暂未使用 自定义属性1
Prop2 string `json:"prop2,omitempty"` // 暂未使用 自定义属性2
DetailsList []WangdianPurchaseDetailItem `json:"details_list"`
}
type WangdianPurchaseDetailItem struct {
SpecNo string `json:"spec_no"`
Num float64 `json:"num"`
Price float64 `json:"price"`
Discount float64 `json:"discount,omitempty"`
Tax float64 `json:"tax,omitempty"` //暂未使用 税率
TaxPrice float64 `json:"tax_price,omitempty"` //暂未使用 税后单价
TaxAmount float64 `json:"tax_amount,omitempty"` //暂未使用 税后金额
Remark string `json:"remark,omitempty"`
Prop1 string `json:"prop1,omitempty"` // 暂未使用 自定义属性1
Prop2 string `json:"prop2,omitempty"` // 暂未使用 自定义属性2
}
type WangdianPurchasePushResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
type WangdianProviderQueryResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
ProviderList []map[string]interface{} `json:"provider_list"`
}
type WangdianWarehouseQueryResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
Warehouses []map[string]interface{} `json:"warehouses"`
}
const (
wangdianPrefix = "【旺店通】"
apiPurchasePush = "purchase_order_push.php"
apiProviderQuery = "purchase_provider_query.php"
apiWarehouseQuery = "warehouse_query.php"
apiGoodsQuery = "goods_query.php"
apiGoodsPush = "goods_push.php"
)
type WangdianGoodsPushResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
// QueryProvider 查询旺店通供应商
func QueryProvider(column, providerNo, providerName string, pageSize, pageNo int) (*WangdianProviderQueryResponse, error) {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
}
if column != "" {
params["column"] = column
}
if providerNo != "" {
params["provider_no"] = providerNo
}
if providerName != "" {
params["provider_name"] = providerName
}
if pageSize > 0 {
params["page_size"] = strconv.Itoa(pageSize)
}
if pageNo > 0 {
params["page_no"] = strconv.Itoa(pageNo)
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiProviderQuery)
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
log.Printf("[旺店通供应商查询] 请求URL: %s (sandbox=%v), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
if err != nil {
return nil, fmt.Errorf("查询请求失败: %v", err)
}
log.Printf("[旺店通供应商查询] 返回信息: %s", string(respBody))
var resp WangdianProviderQueryResponse
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
}
return &resp, nil
}
// SyncWarehouse 从旺店通同步仓库数据到本地
func SyncWarehouse(db *gorm.DB, taskID int64) error {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
pageSize := 100
pageNo := 0
requestCount := 0
totalCount := 0
totalPages := 0
totalSynced := 0
for {
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"page_size": strconv.Itoa(pageSize),
"page_no": strconv.Itoa(pageNo),
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiWarehouseQuery)
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
requestCount++
if err != nil {
log.Printf("[旺店通仓库同步] 查询第%d页失败(网络): %v, 已请求%d次", pageNo, err, requestCount)
break
}
var pageResp struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
Warehouses []map[string]interface{} `json:"warehouses"`
}
if err := json.Unmarshal([]byte(respBody), &pageResp); err != nil {
log.Printf("[旺店通仓库同步] 解析第%d页失败: %v", pageNo, err)
break
}
if pageResp.Code != 0 {
log.Printf("[旺店通仓库同步] 第%d页返回错误(code=%d): %s, 已请求%d次", pageNo, pageResp.Code, pageResp.Message, requestCount)
break
}
if pageNo == 0 {
totalCount = pageResp.TotalCount
totalPages = (totalCount + pageSize - 1) / pageSize
}
n := upsertWarehouses(db, pageResp.Warehouses)
totalSynced += n
UpdateSyncTaskProgress(db, taskID, totalSynced, totalCount)
log.Printf("[旺店通仓库同步] 第%d/%d页完成, 本页写入%d个仓库, 累计%d/%d, 已请求%d次", pageNo+1, totalPages, n, totalSynced, totalCount, requestCount)
if len(pageResp.Warehouses) < pageSize {
break
}
pageNo++
time.Sleep(500 * time.Millisecond)
}
log.Printf("[旺店通仓库同步] 同步结束, 共同步 %d/%d 个仓库", totalSynced, totalCount)
CompleteSyncTask(db, taskID)
return nil
}
func upsertWarehouses(db *gorm.DB, warehouses []map[string]interface{}) int {
now := time.Now().Unix()
synced := 0
for _, wh := range warehouses {
code := toString(wh["warehouse_no"])
if code == "" {
continue
}
var existing models.Warehouse
err := db.Where("code = ? AND is_del = 0", code).First(&existing).Error
if err == nil {
continue
}
status := int8(1)
if toString(wh["is_disabled"]) == "1" {
status = 0
}
warehouse := models.Warehouse{
Code: code,
Name: wangdianPrefix + toString(wh["name"]),
ContactPerson: toString(wh["contact"]),
ContactPhone: toString(wh["telno"]),
Province: toString(wh["province"]),
City: toString(wh["city"]),
District: toString(wh["district"]),
Address: toString(wh["address"]),
Status: status,
CreatedAt: now,
UpdatedAt: now,
Type: 1,
}
if err := db.Create(&warehouse).Error; err != nil {
log.Printf("[旺店通仓库同步] 创建仓库 %s 失败: %v", code, err)
continue
}
synced++
}
return synced
}
func toString(v interface{}) string {
if v == nil {
return ""
}
switch val := v.(type) {
case string:
return val
default:
return fmt.Sprintf("%v", val)
}
}
// SyncProvider 从旺店通同步供应商数据到本地
func SyncProvider(db *gorm.DB, taskID int64) error {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
pageSize := 100
pageNo := 0
requestCount := 0
totalCount := 0
totalPages := 0
totalSynced := 0
for {
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"page_size": strconv.Itoa(pageSize),
"page_no": strconv.Itoa(pageNo),
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiProviderQuery)
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
requestCount++
if err != nil {
log.Printf("[旺店通供应商同步] 查询第%d页失败(网络): %v, 已请求%d次", pageNo, err, requestCount)
break
}
var pageResp struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
ProviderList []map[string]interface{} `json:"provider_list"`
}
if err := json.Unmarshal([]byte(respBody), &pageResp); err != nil {
log.Printf("[旺店通供应商同步] 解析第%d页失败: %v", pageNo, err)
break
}
if pageResp.Code != 0 {
log.Printf("[旺店通供应商同步] 第%d页返回错误(code=%d): %s, 已请求%d次", pageNo, pageResp.Code, pageResp.Message, requestCount)
break
}
if pageNo == 0 {
totalCount = pageResp.TotalCount
totalPages = (totalCount + pageSize - 1) / pageSize
}
n := upsertProviders(db, pageResp.ProviderList)
totalSynced += n
UpdateSyncTaskProgress(db, taskID, totalSynced, totalCount)
log.Printf("[旺店通供应商同步] 第%d/%d页完成, 本页写入%d个供应商, 累计%d/%d, 已请求%d次", pageNo+1, totalPages, n, totalSynced, totalCount, requestCount)
if len(pageResp.ProviderList) < pageSize {
break
}
pageNo++
time.Sleep(500 * time.Millisecond)
}
log.Printf("[旺店通供应商同步] 同步结束, 共同步 %d/%d 个供应商", totalSynced, totalCount)
CompleteSyncTask(db, taskID)
return nil
}
func upsertProviders(db *gorm.DB, providers []map[string]interface{}) int {
now := time.Now().Unix()
synced := 0
for _, p := range providers {
code := toString(p["provider_no"])
if code == "" {
continue
}
var existing models.Supplier
err := db.Where("code = ? AND is_del = 0", code).First(&existing).Error
if err == nil {
continue
}
status := int8(1)
if toString(p["is_disabled"]) == "1" {
status = 0
}
supplier := models.Supplier{
Code: code,
Name: wangdianPrefix + toString(p["provider_name"]),
ContactPerson: toString(p["contact"]),
ContactPhone: toString(p["telno"]),
Address: toString(p["address"]),
Status: status,
CreatedAt: now,
UpdatedAt: now,
}
if err := db.Create(&supplier).Error; err != nil {
log.Printf("[旺店通供应商同步] 创建供应商 %s 失败: %v", code, err)
continue
}
synced++
}
return synced
}
type WangdianGoodsQueryResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
GoodsList []map[string]interface{} `json:"goods_list"`
}
// QueryGoods 查询旺店通商品
func QueryGoods(specNo, goodsNo, brandNo, className, barcode, startTime, endTime string, deleted int, pageSize, pageNo int) (*WangdianGoodsQueryResponse, error) {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
}
if specNo != "" {
params["spec_no"] = specNo
}
if goodsNo != "" {
params["goods_no"] = goodsNo
}
if brandNo != "" {
params["brand_no"] = brandNo
}
if className != "" {
params["class_name"] = className
}
if barcode != "" {
params["barcode"] = barcode
}
if startTime != "" {
params["start_time"] = startTime
}
if endTime != "" {
params["end_time"] = endTime
}
params["page_size"] = strconv.Itoa(pageSize)
params["page_no"] = strconv.Itoa(pageNo)
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiGoodsQuery)
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
log.Printf("[旺店通商品查询] 请求URL: %s (sandbox=%v), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
if err != nil {
return nil, fmt.Errorf("查询请求失败: %v", err)
}
log.Printf("[旺店通商品查询] 返回信息: %s", string(respBody))
var resp WangdianGoodsQueryResponse
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
}
return &resp, nil
}
// SyncGoods 从旺店通同步商品数据到本地
func SyncGoods(db *gorm.DB, taskID int64, startTime, endTime string, aboutID int64) error {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
if startTime == "" || endTime == "" {
return fmt.Errorf("start_time 和 end_time 不能为空")
}
startT, err := time.Parse("2006-01-02 15:04:05", startTime)
if err != nil {
return fmt.Errorf("start_time 格式无效, 正确格式: 2006-01-02 15:04:05")
}
endT, err := time.Parse("2006-01-02 15:04:05", endTime)
if err != nil {
return fmt.Errorf("end_time 格式无效, 正确格式: 2006-01-02 15:04:05")
}
if endT.Sub(startT) > 30*24*time.Hour {
return fmt.Errorf("时间跨度不能超过30天")
}
pageSize := 100
pageNo := 0
requestCount := 0
totalCount := 0
totalPages := 0
totalSynced := 0
for {
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"page_size": strconv.Itoa(pageSize),
"page_no": strconv.Itoa(pageNo),
"start_time": startTime,
"end_time": endTime,
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiGoodsQuery)
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
requestCount++
if err != nil {
log.Printf("[旺店通商品同步] 查询第%d页失败(网络): %v, 已请求%d次", pageNo, err, requestCount)
break
}
var pageResp struct {
Code int `json:"code"`
Message string `json:"message"`
TotalCount int `json:"total_count"`
GoodsList []map[string]interface{} `json:"goods_list"`
}
if err := json.Unmarshal([]byte(respBody), &pageResp); err != nil {
log.Printf("[旺店通商品同步] 解析第%d页失败: %v", pageNo, err)
break
}
if pageResp.Code != 0 {
log.Printf("[旺店通商品同步] 第%d页返回错误(code=%d): %s, 已请求%d次", pageNo, pageResp.Code, pageResp.Message, requestCount)
break
}
if pageNo == 0 {
totalCount = pageResp.TotalCount
totalPages = (totalCount + pageSize - 1) / pageSize
}
n := upsertGoods(db, pageResp.GoodsList, aboutID)
totalSynced += n
UpdateSyncTaskProgress(db, taskID, totalSynced, totalCount)
log.Printf("[旺店通商品同步] 第%d/%d页完成, 本页写入%d个商品, 累计%d/%d, 已请求%d次", pageNo+1, totalPages, n, totalSynced, totalCount, requestCount)
if len(pageResp.GoodsList) < pageSize {
break
}
pageNo++
time.Sleep(500 * time.Millisecond)
}
log.Printf("[旺店通商品同步] 同步结束, 共同步 %d/%d 个商品", totalSynced, totalCount)
CompleteSyncTask(db, taskID)
return nil
}
func upsertGoods(db *gorm.DB, goodsList []map[string]interface{}, aboutID int64) int {
now := time.Now().Unix()
synced := 0
for _, g := range goodsList {
goodsName := toString(g["goods_name"])
if goodsName == "" {
continue
}
goodsPic := toString(g["pic_url"])
specList, ok := g["spec_list"].([]interface{})
if !ok || len(specList) == 0 {
barcode := toString(g["barcode"])
if barcode == "" {
continue
}
var existing models.Product
if err := db.Where("barcode = ? AND is_del = 0", barcode).First(&existing).Error; err == nil {
continue
}
product := models.Product{
AboutId: aboutID,
Name: wangdianPrefix + goodsName,
Barcode: barcode,
Status: 1,
LiveImage: goodsPicToJSON(goodsPic),
Price: parsePrice(g["lowest_price"]),
SalePrice: parsePrice(g["retail_price"]),
CreatedAt: now,
UpdatedAt: now,
}
if err := db.Create(&product).Error; err != nil {
log.Printf("[旺店通商品同步] 创建商品 %s 失败: %v", barcode, err)
continue
}
synced++
continue
}
for _, spec := range specList {
specMap, ok := spec.(map[string]interface{})
if !ok {
continue
}
barcode := toString(specMap["barcode"])
if barcode == "" {
barcode = toString(specMap["spec_no"])
if barcode == "" {
continue
}
}
var existing models.Product
if err := db.Where("barcode = ? AND is_del = 0", barcode).First(&existing).Error; err == nil {
continue
}
specPic := toString(specMap["pic_url"])
if specPic == "" {
specPic = goodsPic
}
product := models.Product{
AboutId: aboutID,
Name: wangdianPrefix + goodsName,
Barcode: barcode,
Status: 1,
LiveImage: goodsPicToJSON(specPic),
Price: parsePrice(specMap["lowest_price"]),
SalePrice: parsePrice(specMap["retail_price"]),
CreatedAt: now,
UpdatedAt: now,
}
if err := db.Create(&product).Error; err != nil {
log.Printf("[旺店通商品同步] 创建商品 %s 失败: %v", barcode, err)
continue
}
synced++
}
}
return synced
}
func goodsPicToJSON(picURL string) datatypes.JSON {
if picURL == "" {
return datatypes.JSON([]byte("[]"))
}
return datatypes.JSON([]byte(`["` + picURL + `"]`))
}
func parsePrice(v interface{}) int64 {
s := toString(v)
if s == "" {
return 0
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0
}
return int64(f * 100)
}
// PushGoods 推送货品档案到旺店通
func PushGoods(goodsListJSON string) (*WangdianGoodsPushResponse, error) {
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
timestamp := time.Now().Unix()
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(timestamp, 10),
"goods_list": goodsListJSON,
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
url := cfg.GetURL(apiGoodsPush)
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
log.Printf("[旺店通货品推送] 请求URL: %s (sandbox=%v)", url, cfg.Sandbox)
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
if err != nil {
return nil, fmt.Errorf("推送请求失败: %v", err)
}
log.Printf("[旺店通货品推送] 返回信息: %s", string(respBody))
var resp WangdianGoodsPushResponse
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
}
return &resp, nil
}
func PushPurchaseOrder(purchaseOrderID int64, db ...*gorm.DB) (*WangdianPurchasePushResponse, error) {
databaseConn := database.OptionalDB(db...)
dbName := databaseConn.Migrator().CurrentDatabase()
log.Printf("[旺店通推送] 使用数据库: %s", dbName)
// 1. 查询采购单
var po models.PurchaseOrder
if err := databaseConn.Where("id = ? AND is_del = 0", purchaseOrderID).First(&po).Error; err != nil {
return nil, fmt.Errorf("采购单不存在: %v", err)
}
// 2. 查询供应商编号(必须)
var supplier models.Supplier
if err := databaseConn.Select("code, contact_person, contact_phone, address").
Where("id = ? AND is_del = 0", po.SupplierID).First(&supplier).Error; err != nil {
return nil, fmt.Errorf("获取供应商信息失败: %v", err)
}
if supplier.Code == "" {
return nil, fmt.Errorf("供应商编码为空,请先维护供应商档案")
}
// 3. 查询仓库编号(必须)
var warehouse models.Warehouse
if err := databaseConn.Select("code, contact_person, contact_phone, address").
Where("id = ? AND is_del = 0", po.WarehouseID).First(&warehouse).Error; err != nil {
return nil, fmt.Errorf("获取仓库信息失败: %v", err)
}
if warehouse.Code == "" {
return nil, fmt.Errorf("仓库编码为空,请先维护仓库档案")
}
// 4. 查询采购明细,关联商品条码
type itemRow struct {
models.PurchaseOrderItem
Barcode string `gorm:"column:barcode"`
}
var items []itemRow
if err := databaseConn.Table("purchase_order_item").
Select("purchase_order_item.*, product.barcode").
Joins("LEFT JOIN product ON purchase_order_item.product_id = product.id AND product.is_del = 0").
Where("purchase_order_item.purchase_order_id = ? AND purchase_order_item.is_del = 0", purchaseOrderID).
Debug().Scan(&items).Error; err != nil {
return nil, fmt.Errorf("获取采购明细失败: %v", err)
}
if len(items) == 0 {
return nil, fmt.Errorf("采购明细为空,无法推送")
}
// 5. 组装 details_list
detailList := make([]WangdianPurchaseDetailItem, 0, len(items))
for _, it := range items {
specNo := it.Barcode
if specNo == "" {
specNo = strconv.FormatInt(it.ProductID, 10)
}
detailList = append(detailList, WangdianPurchaseDetailItem{
SpecNo: specNo,
Num: float64(it.Quantity),
Price: float64(it.UnitPrice) / 100.0,
Discount: 1.0,
})
}
// 6. 组装 purchase_info
info := WangdianPurchaseInfo{
ProviderNo: supplier.Code,
WarehouseNo: warehouse.Code,
OuterNo: po.PoNo,
IsUseOuterNo: 0,
IsCheck: 0,
Contact: warehouse.ContactPerson,
PurchaseName: warehouse.ContactPerson,
Telno: warehouse.ContactPhone,
ReceiveAddress: warehouse.Address,
Remark: po.Remark,
DetailsList: detailList,
}
if po.ExpectedArrivalDate > 0 {
info.ExpectArriveTime = time.Unix(po.ExpectedArrivalDate, 0).Format("2006-01-02 15:04:05")
}
infoBytes, err := json.Marshal(info)
if err != nil {
return nil, fmt.Errorf("序列化采购信息失败: %v", err)
}
// 7. 校验配置
cfg := config.AppConfig.Wangdian
if cfg.Sid == "" || cfg.AppKey == "" || cfg.AppSecret == "" {
return nil, fmt.Errorf("旺店通接口配置不完整(sid/appkey/appsecret)")
}
// 8. 组装公共请求参数 + 计算签名
timestamp := time.Now().Unix()
params := map[string]string{
"sid": cfg.Sid,
"appkey": cfg.AppKey,
"timestamp": strconv.FormatInt(timestamp, 10),
"purchase_info": string(infoBytes),
}
params["sign"] = utils.WangdianSign(params, cfg.AppSecret)
// 9. 提交请求
url := cfg.GetURL(apiPurchasePush)
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 30
}
log.Printf("[旺店通推送] 请求URL: %s (sandbox=%v, form-urlencoded), 参数: sid=%s appkey=%s timestamp=%s sign=%s", url, cfg.Sandbox, cfg.Sid, cfg.AppKey, params["timestamp"], params["sign"])
log.Printf("[旺店通推送] purchase_info: %s", string(infoBytes))
respBody, err := utils.SubmitFormDataWithTimeout(url, params, timeout)
if err != nil {
return nil, fmt.Errorf("推送请求失败: %v", err)
}
log.Printf("[旺店通推送] 返回信息: %s", string(respBody))
var resp WangdianPurchasePushResponse
if err := json.Unmarshal([]byte(respBody), &resp); err != nil {
return nil, fmt.Errorf("解析响应失败(raw=%s): %v", respBody, err)
}
return &resp, nil
}