ztk_rust_sdk/jd/
request.rs

1//! 京东平台请求参数结构体
2//!
3//! 定义京东平台 API 的请求参数结构体
4
5use serde::Serialize;
6
7use super::enums::{JdChainType, JdEliteId, JdOrderQueryType, JdSortField};
8use crate::common::types::SortDirection;
9
10/// 京东转链请求
11///
12/// 将商品链接或 SKU ID 转换为推广链接
13///
14/// # Example
15///
16/// ```rust,ignore
17/// let request = JdConvertRequest::new("https://item.jd.com/123456.html", "your_union_id")
18///     .position_id("12345")
19///     .chain_type(JdChainType::Short);
20/// ```
21#[derive(Debug, Clone, Serialize)]
22pub struct JdConvertRequest {
23    /// 推广物料 URL 或 SKU ID (需要 URL 编码)
24    #[serde(rename = "materialId")]
25    pub material_id: String,
26    /// 京东联盟 ID,为一串数字
27    #[serde(rename = "unionId")]
28    pub union_id: String,
29    /// 自定义推广位 ID (可选,用于返利场景)
30    /// 可以自定义数字,自己在本地跟用户做好关联
31    #[serde(skip_serializing_if = "Option::is_none")]
32    #[serde(rename = "positionId")]
33    pub position_id: Option<String>,
34    /// 联盟子推客身份标识 (可选)
35    /// 格式: 联盟id_应用id_推广位id,三段式
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub pid: Option<String>,
38    /// 优惠券领取链接 (可选)
39    #[serde(skip_serializing_if = "Option::is_none")]
40    #[serde(rename = "couponUrl")]
41    pub coupon_url: Option<String>,
42    /// 子渠道标识 (可选)
43    #[serde(skip_serializing_if = "Option::is_none")]
44    #[serde(rename = "subUnionId")]
45    pub sub_union_id: Option<String>,
46    /// 转链类型: 1-长链, 2-短链, 3-长链+短链
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[serde(rename = "chainType")]
49    pub chain_type: Option<JdChainType>,
50    /// 礼金批次号 (可选)
51    #[serde(skip_serializing_if = "Option::is_none")]
52    #[serde(rename = "giftCouponKey")]
53    pub gift_coupon_key: Option<String>,
54}
55
56impl JdConvertRequest {
57    /// 创建新的京东转链请求
58    ///
59    /// # Arguments
60    ///
61    /// * `material_id` - 推广物料 URL 或 SKU ID
62    /// * `union_id` - 京东联盟 ID
63    pub fn new(material_id: impl Into<String>, union_id: impl Into<String>) -> Self {
64        Self {
65            material_id: material_id.into(),
66            union_id: union_id.into(),
67            position_id: None,
68            pid: None,
69            coupon_url: None,
70            sub_union_id: None,
71            chain_type: None,
72            gift_coupon_key: None,
73        }
74    }
75
76    /// 设置自定义推广位 ID
77    pub fn position_id(mut self, position_id: impl Into<String>) -> Self {
78        self.position_id = Some(position_id.into());
79        self
80    }
81
82    /// 设置联盟子推客身份标识
83    pub fn pid(mut self, pid: impl Into<String>) -> Self {
84        self.pid = Some(pid.into());
85        self
86    }
87
88    /// 设置优惠券领取链接
89    pub fn coupon_url(mut self, coupon_url: impl Into<String>) -> Self {
90        self.coupon_url = Some(coupon_url.into());
91        self
92    }
93
94    /// 设置子渠道标识
95    pub fn sub_union_id(mut self, sub_union_id: impl Into<String>) -> Self {
96        self.sub_union_id = Some(sub_union_id.into());
97        self
98    }
99
100    /// 设置转链类型
101    pub fn chain_type(mut self, chain_type: JdChainType) -> Self {
102        self.chain_type = Some(chain_type);
103        self
104    }
105
106    /// 设置礼金批次号
107    pub fn gift_coupon_key(mut self, gift_coupon_key: impl Into<String>) -> Self {
108        self.gift_coupon_key = Some(gift_coupon_key.into());
109        self
110    }
111}
112
113/// 京粉精选商品请求
114///
115/// 获取京粉精选商品列表
116///
117/// # Example
118///
119/// ```rust,ignore
120/// let request = JingfenGoodsRequest::new(JdEliteId::HotSale)
121///     .page_index(1)
122///     .page_size(20);
123/// ```
124#[derive(Debug, Clone, Serialize)]
125pub struct JingfenGoodsRequest {
126    /// 频道 ID
127    #[serde(rename = "eliteId")]
128    pub elite_id: JdEliteId,
129    /// 页码 (从 1 开始)
130    #[serde(skip_serializing_if = "Option::is_none")]
131    #[serde(rename = "pageIndex")]
132    pub page_index: Option<u32>,
133    /// 每页数量 (最大 20)
134    #[serde(skip_serializing_if = "Option::is_none")]
135    #[serde(rename = "pageSize")]
136    pub page_size: Option<u32>,
137    /// 排序字段
138    #[serde(skip_serializing_if = "Option::is_none")]
139    #[serde(rename = "sortName")]
140    pub sort_name: Option<JdSortField>,
141    /// 排序方向
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub sort: Option<SortDirection>,
144    /// 联盟子推客身份标识 (可选)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub pid: Option<String>,
147    /// 支持出参数据筛选 (可选)
148    /// 逗号分隔,可用: videoInfo, hotWords, similar, documentInfo, skuLabelInfo, promotionLabelInfo
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub fields: Option<String>,
151    /// 禁售类型 (可选)
152    /// 10-微信京东购物小程序禁售, 11-微信京喜小程序禁售
153    #[serde(skip_serializing_if = "Option::is_none")]
154    #[serde(rename = "forbidTypes")]
155    pub forbid_types: Option<String>,
156}
157
158impl JingfenGoodsRequest {
159    /// 创建新的京粉精选商品请求
160    ///
161    /// # Arguments
162    ///
163    /// * `elite_id` - 频道 ID
164    pub fn new(elite_id: JdEliteId) -> Self {
165        Self {
166            elite_id,
167            page_index: None,
168            page_size: None,
169            sort_name: None,
170            sort: None,
171            pid: None,
172            fields: None,
173            forbid_types: None,
174        }
175    }
176
177    /// 设置页码
178    pub fn page_index(mut self, page_index: u32) -> Self {
179        self.page_index = Some(page_index);
180        self
181    }
182
183    /// 设置每页数量
184    pub fn page_size(mut self, page_size: u32) -> Self {
185        self.page_size = Some(page_size);
186        self
187    }
188
189    /// 设置排序字段
190    pub fn sort_name(mut self, sort_name: JdSortField) -> Self {
191        self.sort_name = Some(sort_name);
192        self
193    }
194
195    /// 设置排序方向
196    pub fn sort(mut self, sort: SortDirection) -> Self {
197        self.sort = Some(sort);
198        self
199    }
200
201    /// 设置联盟子推客身份标识
202    pub fn pid(mut self, pid: impl Into<String>) -> Self {
203        self.pid = Some(pid.into());
204        self
205    }
206
207    /// 设置出参数据筛选
208    pub fn fields(mut self, fields: impl Into<String>) -> Self {
209        self.fields = Some(fields.into());
210        self
211    }
212
213    /// 设置禁售类型
214    pub fn forbid_types(mut self, forbid_types: impl Into<String>) -> Self {
215        self.forbid_types = Some(forbid_types.into());
216        self
217    }
218}
219
220/// 京东订单查询请求
221///
222/// 查询京东联盟订单
223///
224/// # Example
225///
226/// ```rust,ignore
227/// let request = JdOrderQueryRequest::new("2024-01-01 00:00:00")
228///     .end_time("2024-01-31 23:59:59")
229///     .query_type(JdOrderQueryType::OrderTime)
230///     .page_no(1);
231/// ```
232#[derive(Debug, Clone, Serialize)]
233pub struct JdOrderQueryRequest {
234    /// 查询开始时间 (格式: yyyy-MM-dd HH:mm:ss)
235    #[serde(rename = "startTime")]
236    pub start_time: String,
237    /// 查询结束时间 (格式: yyyy-MM-dd HH:mm:ss)
238    #[serde(skip_serializing_if = "Option::is_none")]
239    #[serde(rename = "endTime")]
240    pub end_time: Option<String>,
241    /// 查询类型: 1-按下单时间, 2-按完成时间, 3-按更新时间
242    #[serde(skip_serializing_if = "Option::is_none")]
243    #[serde(rename = "type")]
244    pub query_type: Option<JdOrderQueryType>,
245    /// 页码 (从 1 开始)
246    #[serde(skip_serializing_if = "Option::is_none")]
247    #[serde(rename = "pageNo")]
248    pub page_no: Option<u32>,
249    /// 每页数量 (最大 500)
250    #[serde(skip_serializing_if = "Option::is_none")]
251    #[serde(rename = "pageSize")]
252    pub page_size: Option<u32>,
253    /// 子渠道标识 (可选)
254    #[serde(skip_serializing_if = "Option::is_none")]
255    #[serde(rename = "childUnionId")]
256    pub child_union_id: Option<String>,
257}
258
259impl JdOrderQueryRequest {
260    /// 创建新的京东订单查询请求
261    ///
262    /// # Arguments
263    ///
264    /// * `start_time` - 查询开始时间
265    pub fn new(start_time: impl Into<String>) -> Self {
266        Self {
267            start_time: start_time.into(),
268            end_time: None,
269            query_type: None,
270            page_no: None,
271            page_size: None,
272            child_union_id: None,
273        }
274    }
275
276    /// 设置查询结束时间
277    pub fn end_time(mut self, end_time: impl Into<String>) -> Self {
278        self.end_time = Some(end_time.into());
279        self
280    }
281
282    /// 设置查询类型
283    pub fn query_type(mut self, query_type: JdOrderQueryType) -> Self {
284        self.query_type = Some(query_type);
285        self
286    }
287
288    /// 设置页码
289    pub fn page_no(mut self, page_no: u32) -> Self {
290        self.page_no = Some(page_no);
291        self
292    }
293
294    /// 设置每页数量
295    pub fn page_size(mut self, page_size: u32) -> Self {
296        self.page_size = Some(page_size);
297        self
298    }
299
300    /// 设置子渠道标识
301    pub fn child_union_id(mut self, child_union_id: impl Into<String>) -> Self {
302        self.child_union_id = Some(child_union_id.into());
303        self
304    }
305}
306
307/// 京东商品详情请求
308///
309/// 获取京东商品详情信息
310///
311/// # Example
312///
313/// ```rust,ignore
314/// let request = JdGoodsDetailRequest::new("10025768652616");
315/// ```
316#[derive(Debug, Clone, Serialize)]
317pub struct JdGoodsDetailRequest {
318    /// 商品 SKU ID 列表 (多个用逗号分隔,最多 100 个)
319    #[serde(rename = "skuIds")]
320    pub sku_ids: String,
321    /// 联盟子推客身份标识 (可选)
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub pid: Option<String>,
324    /// 支持出参数据筛选 (可选)
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub fields: Option<String>,
327}
328
329impl JdGoodsDetailRequest {
330    /// 创建新的京东商品详情请求
331    ///
332    /// # Arguments
333    ///
334    /// * `sku_ids` - 商品 SKU ID (多个用逗号分隔)
335    pub fn new(sku_ids: impl Into<String>) -> Self {
336        Self {
337            sku_ids: sku_ids.into(),
338            pid: None,
339            fields: None,
340        }
341    }
342
343    /// 设置联盟子推客身份标识
344    pub fn pid(mut self, pid: impl Into<String>) -> Self {
345        self.pid = Some(pid.into());
346        self
347    }
348
349    /// 设置出参数据筛选
350    pub fn fields(mut self, fields: impl Into<String>) -> Self {
351        self.fields = Some(fields.into());
352        self
353    }
354}
355
356/// 京东朋友圈火爆商品请求
357///
358/// 获取京东朋友圈火爆商品列表
359///
360/// # Example
361///
362/// ```rust,ignore
363/// let request = JdHotGoodsRequest::new()
364///     .page_index(1)
365///     .page_size(20)
366///     .keyword("手机");
367/// ```
368#[derive(Debug, Clone, Serialize, Default)]
369pub struct JdHotGoodsRequest {
370    /// 页码 (从 1 开始)
371    #[serde(skip_serializing_if = "Option::is_none")]
372    #[serde(rename = "pageIndex")]
373    pub page_index: Option<u32>,
374    /// 每页数量 (最大 20)
375    #[serde(skip_serializing_if = "Option::is_none")]
376    #[serde(rename = "pageSize")]
377    pub page_size: Option<u32>,
378    /// 商品 ID (可选)
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub goods_id: Option<String>,
381    /// 关键词 (可选)
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub keyword: Option<String>,
384    /// 一级类目 (可选)
385    /// 0-全部, 1-居家日用, 2-食品, 3-生鲜, 4-图书, 5-美妆个护,
386    /// 6-母婴, 7-数码家电, 8-内衣, 9-配饰, 10-女装, 11-男装,
387    /// 12-鞋品, 13-家装家纺, 14-文娱车品, 15-箱包, 16-户外运动
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub goods_type: Option<u32>,
390    /// 佣金比例起始值 (可选)
391    /// 空-全部, 20-佣金比例>=20%, 50-佣金比例>=50%
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub commission_rate_start: Option<u32>,
394}
395
396impl JdHotGoodsRequest {
397    /// 创建新的京东朋友圈火爆商品请求
398    pub fn new() -> Self {
399        Self::default()
400    }
401
402    /// 设置页码
403    pub fn page_index(mut self, page_index: u32) -> Self {
404        self.page_index = Some(page_index);
405        self
406    }
407
408    /// 设置每页数量
409    pub fn page_size(mut self, page_size: u32) -> Self {
410        self.page_size = Some(page_size);
411        self
412    }
413
414    /// 设置商品 ID
415    pub fn goods_id(mut self, goods_id: impl Into<String>) -> Self {
416        self.goods_id = Some(goods_id.into());
417        self
418    }
419
420    /// 设置关键词
421    pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
422        self.keyword = Some(keyword.into());
423        self
424    }
425
426    /// 设置一级类目
427    pub fn goods_type(mut self, goods_type: u32) -> Self {
428        self.goods_type = Some(goods_type);
429        self
430    }
431
432    /// 设置佣金比例起始值
433    pub fn commission_rate_start(mut self, commission_rate_start: u32) -> Self {
434        self.commission_rate_start = Some(commission_rate_start);
435        self
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_jd_convert_request_serialize() {
445        let request = JdConvertRequest::new("https://item.jd.com/123456.html", "1000000001");
446        let json = serde_json::to_value(&request).unwrap();
447
448        assert_eq!(json["materialId"], "https://item.jd.com/123456.html");
449        assert_eq!(json["unionId"], "1000000001");
450        assert!(json.get("positionId").is_none());
451    }
452
453    #[test]
454    fn test_jd_convert_request_with_options() {
455        let request = JdConvertRequest::new("https://item.jd.com/123456.html", "1000000001")
456            .position_id("12345")
457            .chain_type(JdChainType::Short);
458
459        let json = serde_json::to_value(&request).unwrap();
460
461        assert_eq!(json["positionId"], "12345");
462        assert_eq!(json["chainType"], 2);
463    }
464
465    #[test]
466    fn test_jingfen_goods_request_serialize() {
467        let request = JingfenGoodsRequest::new(JdEliteId::HotSale)
468            .page_index(1)
469            .page_size(20);
470
471        let json = serde_json::to_value(&request).unwrap();
472
473        assert_eq!(json["eliteId"], 22);
474        assert_eq!(json["pageIndex"], 1);
475        assert_eq!(json["pageSize"], 20);
476    }
477
478    #[test]
479    fn test_jingfen_goods_request_with_sort() {
480        let request = JingfenGoodsRequest::new(JdEliteId::HighCommission)
481            .sort_name(JdSortField::CommissionShare)
482            .sort(SortDirection::Desc);
483
484        let json = serde_json::to_value(&request).unwrap();
485
486        assert_eq!(json["eliteId"], 129);
487        assert_eq!(json["sortName"], "commissionShare");
488        assert_eq!(json["sort"], "desc");
489    }
490
491    #[test]
492    fn test_jd_order_query_request_serialize() {
493        let request = JdOrderQueryRequest::new("2024-01-01 00:00:00")
494            .end_time("2024-01-31 23:59:59")
495            .query_type(JdOrderQueryType::OrderTime)
496            .page_no(1)
497            .page_size(100);
498
499        let json = serde_json::to_value(&request).unwrap();
500
501        assert_eq!(json["startTime"], "2024-01-01 00:00:00");
502        assert_eq!(json["endTime"], "2024-01-31 23:59:59");
503        assert_eq!(json["type"], 1);
504        assert_eq!(json["pageNo"], 1);
505        assert_eq!(json["pageSize"], 100);
506    }
507
508    #[test]
509    fn test_jd_goods_detail_request_serialize() {
510        let request = JdGoodsDetailRequest::new("10025768652616,10025768652617");
511        let json = serde_json::to_value(&request).unwrap();
512
513        assert_eq!(json["skuIds"], "10025768652616,10025768652617");
514    }
515
516    #[test]
517    fn test_jd_hot_goods_request_serialize() {
518        let request = JdHotGoodsRequest::new()
519            .page_index(1)
520            .page_size(20)
521            .keyword("手机")
522            .goods_type(7);
523
524        let json = serde_json::to_value(&request).unwrap();
525
526        assert_eq!(json["pageIndex"], 1);
527        assert_eq!(json["pageSize"], 20);
528        assert_eq!(json["keyword"], "手机");
529        assert_eq!(json["goods_type"], 7);
530    }
531
532    #[test]
533    fn test_optional_fields_not_serialized() {
534        let request = JdConvertRequest::new("material", "union");
535        let json_str = serde_json::to_string(&request).unwrap();
536
537        assert!(!json_str.contains("positionId"));
538        assert!(!json_str.contains("couponUrl"));
539        assert!(!json_str.contains("chainType"));
540    }
541}