data 透传闭环:发货数据回显、回调带回,并限制 2KB
- 「查询发货数据」响应新增 data(下单透传原样回显,供自建发货页预填/展示) - 回调 payload 带回 data(下单传 → 回调收,商户对接免回查) - 创建订单 data 限制最长 2048 字节 - 文档更新:data 用途(自建页预填/回调/上游查单约定字段)、发货数据与回调示例 - 单测覆盖 data 回显
This commit is contained in:
@@ -58,6 +58,11 @@ func (h *OpenV1Handler) CreateOrder(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
var data interface{}
|
var data interface{}
|
||||||
if len(req.Data) > 0 {
|
if len(req.Data) > 0 {
|
||||||
|
if len(req.Data) > 2048 {
|
||||||
|
openlog.Warn(c, "create_order data_too_large size=%d", len(req.Data))
|
||||||
|
response.BadRequest(c, "data 最长 2048 字节")
|
||||||
|
return
|
||||||
|
}
|
||||||
var decoded interface{}
|
var decoded interface{}
|
||||||
if err := json.Unmarshal(req.Data, &decoded); err != nil {
|
if err := json.Unmarshal(req.Data, &decoded); err != nil {
|
||||||
openlog.Warn(c, "create_order bad_data err=%v", err)
|
openlog.Warn(c, "create_order bad_data err=%v", err)
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ type DeliveryOrderInfo struct {
|
|||||||
GameUID string `json:"game_uid,omitempty"`
|
GameUID string `json:"game_uid,omitempty"`
|
||||||
RoleName string `json:"role_name,omitempty"`
|
RoleName string `json:"role_name,omitempty"`
|
||||||
PayScore int `json:"pay_score,omitempty"`
|
PayScore int `json:"pay_score,omitempty"`
|
||||||
|
Data map[string]interface{} `json:"data,omitempty"`
|
||||||
Good map[string]interface{} `json:"good,omitempty"`
|
Good map[string]interface{} `json:"good,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,6 +559,7 @@ func (s *DeliveryService) buildDeliveryState(openOrder *OpenOrderQuery, order *m
|
|||||||
GameUID: openOrder.GameUID,
|
GameUID: openOrder.GameUID,
|
||||||
RoleName: openOrder.RoleName,
|
RoleName: openOrder.RoleName,
|
||||||
PayScore: openOrder.PayScore,
|
PayScore: openOrder.PayScore,
|
||||||
|
Data: requestDataMap(order.RequestData),
|
||||||
Good: good,
|
Good: good,
|
||||||
}
|
}
|
||||||
return info, order, goodID, nil
|
return info, order, goodID, nil
|
||||||
@@ -767,6 +769,7 @@ func (s *DeliveryService) prepareOrder(orderNo string, requireCanShip bool, auth
|
|||||||
GameUID: openOrder.GameUID,
|
GameUID: openOrder.GameUID,
|
||||||
RoleName: openOrder.RoleName,
|
RoleName: openOrder.RoleName,
|
||||||
PayScore: openOrder.PayScore,
|
PayScore: openOrder.PayScore,
|
||||||
|
Data: requestDataMap(order.RequestData),
|
||||||
Good: good,
|
Good: good,
|
||||||
}
|
}
|
||||||
return info, order, goodID, nil
|
return info, order, goodID, nil
|
||||||
|
|||||||
@@ -221,6 +221,10 @@ func TestDeliveryMerchantApiBindAndSubmit(t *testing.T) {
|
|||||||
APIClientID: 1,
|
APIClientID: 1,
|
||||||
ClientOrderNo: "delivery-api-001",
|
ClientOrderNo: "delivery-api-001",
|
||||||
SKU: "suit_alan_walker",
|
SKU: "suit_alan_walker",
|
||||||
|
RequestData: map[string]interface{}{
|
||||||
|
"game_account": "4808146277",
|
||||||
|
"game_channel": "ios-wechat",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("create order: %v", err)
|
t.Fatalf("create order: %v", err)
|
||||||
@@ -234,6 +238,9 @@ func TestDeliveryMerchantApiBindAndSubmit(t *testing.T) {
|
|||||||
if !info.CanShip || info.Product == nil || info.Product.Image == "" {
|
if !info.CanShip || info.Product == nil || info.Product.Image == "" {
|
||||||
t.Fatalf("merchant order should include delivery data: %+v", info)
|
t.Fatalf("merchant order should include delivery data: %+v", info)
|
||||||
}
|
}
|
||||||
|
if info.Data["game_account"] != "4808146277" || info.Data["game_channel"] != "ios-wechat" {
|
||||||
|
t.Fatalf("delivery order should echo request data: %+v", info.Data)
|
||||||
|
}
|
||||||
|
|
||||||
bind, err := deliverySvc.BindForMerchant(merchantID, created.Order.OrderNo, "4808146277")
|
bind, err := deliverySvc.BindForMerchant(merchantID, created.Order.OrderNo, "4808146277")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -772,7 +772,7 @@ func CanFulfill(order *model.FulfillmentOrder) (bool, string) {
|
|||||||
|
|
||||||
func orderCallbackData(order *model.FulfillmentOrder) map[string]interface{} {
|
func orderCallbackData(order *model.FulfillmentOrder) map[string]interface{} {
|
||||||
canShip, cannotShipReason := CanFulfill(order)
|
canShip, cannotShipReason := CanFulfill(order)
|
||||||
return map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"order_no": order.OrderNo,
|
"order_no": order.OrderNo,
|
||||||
"client_order_no": order.ClientOrderNo,
|
"client_order_no": order.ClientOrderNo,
|
||||||
"product_sku": order.ProductSKU,
|
"product_sku": order.ProductSKU,
|
||||||
@@ -788,6 +788,10 @@ func orderCallbackData(order *model.FulfillmentOrder) map[string]interface{} {
|
|||||||
"provider_order_no": order.ProviderOrderNo,
|
"provider_order_no": order.ProviderOrderNo,
|
||||||
"failure_reason": order.FailureReason,
|
"failure_reason": order.FailureReason,
|
||||||
}
|
}
|
||||||
|
if requestData := requestDataMap(order.RequestData); len(requestData) > 0 {
|
||||||
|
data["data"] = requestData
|
||||||
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- 仪表盘统计 -----
|
// ----- 仪表盘统计 -----
|
||||||
@@ -1309,6 +1313,15 @@ func resultDataMap(raw string) map[string]interface{} {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requestDataMap 解析下单透传的 data(RequestData),非法或为空时返回空 map。
|
||||||
|
func requestDataMap(raw string) map[string]interface{} {
|
||||||
|
m := map[string]interface{}{}
|
||||||
|
if raw != "" && json.Valid([]byte(raw)) {
|
||||||
|
_ = json.Unmarshal([]byte(raw), &m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
func resultDataString(raw, key string) string {
|
func resultDataString(raw, key string) string {
|
||||||
if v, ok := resultDataMap(raw)[key].(string); ok {
|
if v, ok := resultDataMap(raw)[key].(string); ok {
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ const deliveryOrderResponseExample = `{
|
|||||||
"game_uid": "4808146277",
|
"game_uid": "4808146277",
|
||||||
"role_name": "巫师哈丁12",
|
"role_name": "巫师哈丁12",
|
||||||
"pay_score": 360,
|
"pay_score": 360,
|
||||||
|
"data": { "game_account": "4808146277", "game_channel": "ios-wechat", "role_name": "玩家名" },
|
||||||
"good": { "title": "套装-糯粉咩咩", "image": "https://..." }
|
"good": { "title": "套装-糯粉咩咩", "image": "https://..." }
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@@ -196,14 +197,14 @@ export const endpoints: EndpointSpec[] = [
|
|||||||
{ name: 'sku', type: 'string', required: true, desc: '商品标识,取自商品列表', example: 'suit_pink_sheep' },
|
{ name: 'sku', type: 'string', required: true, desc: '商品标识,取自商品列表', example: 'suit_pink_sheep' },
|
||||||
{ name: 'quantity', type: 'int', desc: '数量,默认 1', example: '1' },
|
{ name: 'quantity', type: 'int', desc: '数量,默认 1', example: '1' },
|
||||||
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注', example: 'buyer-001' },
|
{ name: 'buyer_reference', type: 'string', desc: '买家标识/备注', example: 'buyer-001' },
|
||||||
{ name: 'data', type: 'object', desc: '透传给发货流程的业务数据(区服、账号等),原样存储并回显' },
|
{ name: 'data', type: 'object', desc: '透传业务数据(最长 2048 字节)。自建发货页模式会原样返回给「查询发货数据」用于预填/展示;回调 payload 也会带回;约定字段 game_account/game_channel/role_name 会同步给上游查单' },
|
||||||
],
|
],
|
||||||
requestExample: `{
|
requestExample: `{
|
||||||
"client_order_no": "shop-10001",
|
"client_order_no": "shop-10001",
|
||||||
"sku": "suit_pink_sheep",
|
"sku": "suit_pink_sheep",
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"buyer_reference": "buyer-001",
|
"buyer_reference": "buyer-001",
|
||||||
"data": { "server": "ios-wechat", "uid": "player-id" }
|
"data": { "game_account": "4808146277", "game_channel": "ios-wechat", "role_name": "玩家名" }
|
||||||
}`,
|
}`,
|
||||||
responseExample: `{
|
responseExample: `{
|
||||||
"code": 0,
|
"code": 0,
|
||||||
@@ -317,6 +318,7 @@ export const endpoints: EndpointSpec[] = [
|
|||||||
{ name: 'game_uid', type: 'string', desc: 'UID / 角色编号' },
|
{ name: 'game_uid', type: 'string', desc: 'UID / 角色编号' },
|
||||||
{ name: 'role_name', type: 'string', desc: '角色名' },
|
{ name: 'role_name', type: 'string', desc: '角色名' },
|
||||||
{ name: 'pay_score', type: 'int', desc: '消耗积分' },
|
{ name: 'pay_score', type: 'int', desc: '消耗积分' },
|
||||||
|
{ name: 'data', type: 'object', desc: '下单时透传的业务数据(原样返回,可用于预填玩家编号/展示区服)' },
|
||||||
{ name: 'good', type: 'object', desc: '上游商品详情' },
|
{ name: 'good', type: 'object', desc: '上游商品详情' },
|
||||||
],
|
],
|
||||||
notes: [
|
notes: [
|
||||||
|
|||||||
@@ -490,14 +490,15 @@ const callbackPayloadExample = `{
|
|||||||
"client_order_no": "shop-10001",
|
"client_order_no": "shop-10001",
|
||||||
"order_status": "delivered",
|
"order_status": "delivered",
|
||||||
"can_ship": false,
|
"can_ship": false,
|
||||||
"product": { "sku": "suit_pink_sheep", "name": "套装-糯粉咩咩" },
|
"product_sku": "suit_pink_sheep",
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
"amount": 105,
|
"amount": 105,
|
||||||
"currency": "POINT",
|
"currency": "POINT",
|
||||||
"provider_order_no": "PROV-xxxx",
|
"provider_order_no": "PROV-xxxx",
|
||||||
"failure_reason": "",
|
"failure_reason": "",
|
||||||
"created_at": "2026-08-03T10:00:00+08:00",
|
"created_at": "2026-08-03T10:00:00+08:00",
|
||||||
"delivered_at": "2026-08-03T10:11:20+08:00"
|
"delivered_at": "2026-08-03T10:11:20+08:00",
|
||||||
|
"data": { "game_account": "4808146277", "game_channel": "ios-wechat", "role_name": "玩家名" }
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user