ztk_rust_sdk/taobao/
request.rs

1//! 淘宝平台请求参数结构体
2//!
3//! 定义淘宝平台 API 的请求参数结构体
4
5use serde::Serialize;
6
7use super::enums::{TaobaoOrderQueryType, TaobaoOrderStatus, TaobaoSignUrlType};
8
9/// 高佣转链请求 (商品ID)
10///
11/// 将商品 ID 转换为带有高佣金的推广链接
12///
13/// # Example
14///
15/// ```rust,ignore
16/// let request = ConvertByItemIdRequest {
17///     sid: "your_sid".to_string(),
18///     pid: "mm_xxx_xxx_xxx".to_string(),
19///     num_iid: "123456789".to_string(),
20///     relation_id: Some("your_relation_id".to_string()),
21///     special_id: None,
22///     signurl: Some(TaobaoSignUrlType::WithFullDetail),
23/// };
24/// ```
25#[derive(Debug, Clone, Serialize)]
26pub struct ConvertByItemIdRequest {
27    /// 淘客账号授权 ID
28    pub sid: String,
29    /// 淘客 PID,格式: mm_xxx_xxx_xxx
30    pub pid: String,
31    /// 商品 ID
32    pub num_iid: String,
33    /// 渠道关系 ID (可选,用于返利场景)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub relation_id: Option<String>,
36    /// 会员运营 ID (可选)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub special_id: Option<String>,
39    /// 返回结果类型
40    /// - 0/1/2: 官方结果
41    /// - 3: 整合高佣转链+解析商品编号
42    /// - 4: 整合+简版详情
43    /// - 5: 整合+全网详情+淘口令
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub signurl: Option<TaobaoSignUrlType>,
46}
47
48impl ConvertByItemIdRequest {
49    /// 创建新的高佣转链请求
50    ///
51    /// # Arguments
52    ///
53    /// * `sid` - 淘客账号授权 ID
54    /// * `pid` - 淘客 PID
55    /// * `num_iid` - 商品 ID
56    pub fn new(sid: impl Into<String>, pid: impl Into<String>, num_iid: impl Into<String>) -> Self {
57        Self {
58            sid: sid.into(),
59            pid: pid.into(),
60            num_iid: num_iid.into(),
61            relation_id: None,
62            special_id: None,
63            signurl: None,
64        }
65    }
66
67    /// 设置渠道关系 ID
68    pub fn relation_id(mut self, relation_id: impl Into<String>) -> Self {
69        self.relation_id = Some(relation_id.into());
70        self
71    }
72
73    /// 设置会员运营 ID
74    pub fn special_id(mut self, special_id: impl Into<String>) -> Self {
75        self.special_id = Some(special_id.into());
76        self
77    }
78
79    /// 设置返回结果类型
80    pub fn signurl(mut self, signurl: TaobaoSignUrlType) -> Self {
81        self.signurl = Some(signurl);
82        self
83    }
84}
85
86/// 高佣转链请求 (淘口令)
87///
88/// 将淘口令转换为自己的推广淘口令
89///
90/// # Example
91///
92/// ```rust,ignore
93/// let request = ConvertByTklRequest {
94///     sid: "your_sid".to_string(),
95///     pid: "mm_xxx_xxx_xxx".to_string(),
96///     tkl: "淘口令内容".to_string(),
97///     relation_id: None,
98///     union_id: None,
99///     position_id: None,
100///     custom_parameters: None,
101///     chan_tag: None,
102///     customer_id: None,
103/// };
104/// ```
105#[derive(Debug, Clone, Serialize)]
106pub struct ConvertByTklRequest {
107    /// 淘客账号授权 ID
108    pub sid: String,
109    /// 淘客 PID
110    pub pid: String,
111    /// 淘口令文案 (会自动 URL 编码)
112    pub tkl: String,
113    /// 淘宝渠道关系 ID (可选)
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub relation_id: Option<String>,
116    /// 京东联盟 ID (可选)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub union_id: Option<String>,
119    /// 京东渠道关系 ID (可选)
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub position_id: Option<String>,
122    /// 拼多多自定义参数 (可选,JSON 格式)
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub custom_parameters: Option<String>,
125    /// 唯品会渠道标识 (可选)
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub chan_tag: Option<String>,
128    /// 美团渠道关系 ID (可选)
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub customer_id: Option<String>,
131}
132
133impl ConvertByTklRequest {
134    /// 创建新的淘口令转链请求
135    ///
136    /// # Arguments
137    ///
138    /// * `sid` - 淘客账号授权 ID
139    /// * `pid` - 淘客 PID
140    /// * `tkl` - 淘口令文案
141    pub fn new(sid: impl Into<String>, pid: impl Into<String>, tkl: impl Into<String>) -> Self {
142        Self {
143            sid: sid.into(),
144            pid: pid.into(),
145            tkl: tkl.into(),
146            relation_id: None,
147            union_id: None,
148            position_id: None,
149            custom_parameters: None,
150            chan_tag: None,
151            customer_id: None,
152        }
153    }
154
155    /// 设置淘宝渠道关系 ID
156    pub fn relation_id(mut self, relation_id: impl Into<String>) -> Self {
157        self.relation_id = Some(relation_id.into());
158        self
159    }
160
161    /// 设置京东联盟 ID
162    pub fn union_id(mut self, union_id: impl Into<String>) -> Self {
163        self.union_id = Some(union_id.into());
164        self
165    }
166
167    /// 设置京东渠道关系 ID
168    pub fn position_id(mut self, position_id: impl Into<String>) -> Self {
169        self.position_id = Some(position_id.into());
170        self
171    }
172
173    /// 设置拼多多自定义参数
174    pub fn custom_parameters(mut self, custom_parameters: impl Into<String>) -> Self {
175        self.custom_parameters = Some(custom_parameters.into());
176        self
177    }
178
179    /// 设置唯品会渠道标识
180    pub fn chan_tag(mut self, chan_tag: impl Into<String>) -> Self {
181        self.chan_tag = Some(chan_tag.into());
182        self
183    }
184
185    /// 设置美团渠道关系 ID
186    pub fn customer_id(mut self, customer_id: impl Into<String>) -> Self {
187        self.customer_id = Some(customer_id.into());
188        self
189    }
190}
191
192/// 批量高佣转链请求
193///
194/// 同时获取多个渠道 ID 的转链结果
195#[derive(Debug, Clone, Serialize)]
196pub struct BatchConvertRequest {
197    /// 淘客账号授权 ID
198    pub sid: String,
199    /// 淘客 PID
200    pub pid: String,
201    /// 商品 ID
202    pub num_iid: String,
203    /// 渠道关系 ID 列表 (用逗号分隔)
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub relation_ids: Option<String>,
206    /// 返回结果类型
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub signurl: Option<TaobaoSignUrlType>,
209}
210
211impl BatchConvertRequest {
212    /// 创建新的批量转链请求
213    ///
214    /// # Arguments
215    ///
216    /// * `sid` - 淘客账号授权 ID
217    /// * `pid` - 淘客 PID
218    /// * `num_iid` - 商品 ID
219    pub fn new(sid: impl Into<String>, pid: impl Into<String>, num_iid: impl Into<String>) -> Self {
220        Self {
221            sid: sid.into(),
222            pid: pid.into(),
223            num_iid: num_iid.into(),
224            relation_ids: None,
225            signurl: None,
226        }
227    }
228
229    /// 设置渠道关系 ID 列表
230    pub fn relation_ids(mut self, relation_ids: impl Into<String>) -> Self {
231        self.relation_ids = Some(relation_ids.into());
232        self
233    }
234
235    /// 设置返回结果类型
236    pub fn signurl(mut self, signurl: TaobaoSignUrlType) -> Self {
237        self.signurl = Some(signurl);
238        self
239    }
240}
241
242/// 订单查询请求
243///
244/// 查询淘宝联盟订单
245#[derive(Debug, Clone, Serialize)]
246pub struct QueryOrdersRequest {
247    /// 淘客账号授权 ID
248    pub sid: String,
249    /// 查询开始时间 (格式: yyyy-MM-dd HH:mm:ss)
250    pub start_time: String,
251    /// 查询结束时间 (格式: yyyy-MM-dd HH:mm:ss)
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub end_time: Option<String>,
254    /// 查询类型
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub query_type: Option<TaobaoOrderQueryType>,
257    /// 订单状态
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub tk_status: Option<TaobaoOrderStatus>,
260    /// 页码 (从 1 开始)
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub page_no: Option<u32>,
263    /// 每页数量 (最大 100)
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub page_size: Option<u32>,
266    /// 渠道关系 ID (可选)
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub relation_id: Option<String>,
269    /// 会员运营 ID (可选)
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub special_id: Option<String>,
272}
273
274impl QueryOrdersRequest {
275    /// 创建新的订单查询请求
276    ///
277    /// # Arguments
278    ///
279    /// * `sid` - 淘客账号授权 ID
280    /// * `start_time` - 查询开始时间
281    pub fn new(sid: impl Into<String>, start_time: impl Into<String>) -> Self {
282        Self {
283            sid: sid.into(),
284            start_time: start_time.into(),
285            end_time: None,
286            query_type: None,
287            tk_status: None,
288            page_no: None,
289            page_size: None,
290            relation_id: None,
291            special_id: None,
292        }
293    }
294
295    /// 设置查询结束时间
296    pub fn end_time(mut self, end_time: impl Into<String>) -> Self {
297        self.end_time = Some(end_time.into());
298        self
299    }
300
301    /// 设置查询类型
302    pub fn query_type(mut self, query_type: TaobaoOrderQueryType) -> Self {
303        self.query_type = Some(query_type);
304        self
305    }
306
307    /// 设置订单状态
308    pub fn tk_status(mut self, tk_status: TaobaoOrderStatus) -> Self {
309        self.tk_status = Some(tk_status);
310        self
311    }
312
313    /// 设置页码
314    pub fn page_no(mut self, page_no: u32) -> Self {
315        self.page_no = Some(page_no);
316        self
317    }
318
319    /// 设置每页数量
320    pub fn page_size(mut self, page_size: u32) -> Self {
321        self.page_size = Some(page_size);
322        self
323    }
324
325    /// 设置渠道关系 ID
326    pub fn relation_id(mut self, relation_id: impl Into<String>) -> Self {
327        self.relation_id = Some(relation_id.into());
328        self
329    }
330
331    /// 设置会员运营 ID
332    pub fn special_id(mut self, special_id: impl Into<String>) -> Self {
333        self.special_id = Some(special_id.into());
334        self
335    }
336}
337
338/// 创建淘口令请求
339///
340/// 生成淘口令
341#[derive(Debug, Clone, Serialize)]
342pub struct CreateTklRequest {
343    /// 淘客账号授权 ID
344    pub sid: String,
345    /// 淘客 PID
346    pub pid: String,
347    /// 商品 ID
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub num_iid: Option<String>,
350    /// 推广链接 URL
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub url: Option<String>,
353    /// 淘口令文案
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub text: Option<String>,
356    /// 淘口令 logo 图片 URL
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub logo: Option<String>,
359    /// 渠道关系 ID (可选)
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub relation_id: Option<String>,
362}
363
364impl CreateTklRequest {
365    /// 创建新的淘口令生成请求
366    ///
367    /// # Arguments
368    ///
369    /// * `sid` - 淘客账号授权 ID
370    /// * `pid` - 淘客 PID
371    pub fn new(sid: impl Into<String>, pid: impl Into<String>) -> Self {
372        Self {
373            sid: sid.into(),
374            pid: pid.into(),
375            num_iid: None,
376            url: None,
377            text: None,
378            logo: None,
379            relation_id: None,
380        }
381    }
382
383    /// 设置商品 ID
384    pub fn num_iid(mut self, num_iid: impl Into<String>) -> Self {
385        self.num_iid = Some(num_iid.into());
386        self
387    }
388
389    /// 设置推广链接 URL
390    pub fn url(mut self, url: impl Into<String>) -> Self {
391        self.url = Some(url.into());
392        self
393    }
394
395    /// 设置淘口令文案
396    pub fn text(mut self, text: impl Into<String>) -> Self {
397        self.text = Some(text.into());
398        self
399    }
400
401    /// 设置淘口令 logo 图片 URL
402    pub fn logo(mut self, logo: impl Into<String>) -> Self {
403        self.logo = Some(logo.into());
404        self
405    }
406
407    /// 设置渠道关系 ID
408    pub fn relation_id(mut self, relation_id: impl Into<String>) -> Self {
409        self.relation_id = Some(relation_id.into());
410        self
411    }
412}
413
414/// 解析商品编号请求
415///
416/// 从淘口令或链接中提取商品 ID
417#[derive(Debug, Clone, Serialize)]
418pub struct ParseItemIdRequest {
419    /// 淘口令或商品链接
420    pub content: String,
421}
422
423impl ParseItemIdRequest {
424    /// 创建新的解析商品编号请求
425    ///
426    /// # Arguments
427    ///
428    /// * `content` - 淘口令或商品链接
429    pub fn new(content: impl Into<String>) -> Self {
430        Self {
431            content: content.into(),
432        }
433    }
434}
435
436/// 全网搜索商品请求
437///
438/// 根据关键字搜索商品,返回动态描述分≥4.6的商品列表
439///
440/// # Example
441///
442/// ```rust,ignore
443/// let request = SearchGoodsRequest::new("sid123", "mm_xxx_xxx_xxx")
444///     .keyword("内存条")
445///     .page(1)
446///     .page_size(20)
447///     .sort("sale_num_desc");
448/// ```
449#[derive(Debug, Clone, Serialize)]
450pub struct SearchGoodsRequest {
451    /// 淘客账号授权 ID
452    pub sid: String,
453    /// 淘客 PID,格式: mm_xxx_xxx_xxx
454    pub pid: String,
455    /// 搜索关键词
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub q: Option<String>,
458    /// 页码 (从 1 开始)
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub page: Option<u32>,
461    /// 每页数量 (1-50,默认 20)
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub page_size: Option<u32>,
464    /// 排序方式
465    /// - new: 综合排序
466    /// - total_sale_num_desc: 总销量从大到小
467    /// - sale_num_desc: 年销量从大到小
468    /// - commission_rate_desc: 佣金比例从大到小
469    /// - price_asc: 价格从小到大
470    /// - price_desc: 价格从大到小
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub sort: Option<String>,
473    /// 是否有券,1 为有券
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub youquan: Option<String>,
476    /// 是否天猫商品,tmall 为天猫
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub tj: Option<String>,
479    /// 是否海外商品,1 为海外
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub haiwai: Option<String>,
482    /// 商品所在地 (城市名称,如:北京、上海、广州)
483    #[serde(skip_serializing_if = "Option::is_none")]
484    pub itemloc: Option<String>,
485    /// 淘客佣金比率下限 (如:20 表示 >=20%)
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub start_tk_rate: Option<String>,
488    /// 淘客佣金比率上限 (如:50 表示 <=50%)
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub end_tk_rate: Option<String>,
491    /// 折扣价格下限 (如:100 表示 >=100 元)
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub start_price: Option<String>,
494    /// 折扣价格上限 (如:200 表示 <=200 元)
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub end_price: Option<String>,
497    /// 过滤值: 0-不过滤, 1-轻度过滤, 2-中度过滤 (推荐)
498    #[serde(skip_serializing_if = "Option::is_none")]
499    #[serde(rename = "type")]
500    pub filter_type: Option<String>,
501    /// 后台类目 ID
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub cat: Option<String>,
504}
505
506impl SearchGoodsRequest {
507    /// 创建新的全网搜索商品请求
508    ///
509    /// # Arguments
510    ///
511    /// * `sid` - 淘客账号授权 ID
512    /// * `pid` - 淘客 PID
513    pub fn new(sid: impl Into<String>, pid: impl Into<String>) -> Self {
514        Self {
515            sid: sid.into(),
516            pid: pid.into(),
517            q: None,
518            page: None,
519            page_size: None,
520            sort: None,
521            youquan: None,
522            tj: None,
523            haiwai: None,
524            itemloc: None,
525            start_tk_rate: None,
526            end_tk_rate: None,
527            start_price: None,
528            end_price: None,
529            filter_type: None,
530            cat: None,
531        }
532    }
533
534    /// 设置搜索关键词
535    pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
536        self.q = Some(keyword.into());
537        self
538    }
539
540    /// 设置页码
541    pub fn page(mut self, page: u32) -> Self {
542        self.page = Some(page);
543        self
544    }
545
546    /// 设置每页数量
547    pub fn page_size(mut self, page_size: u32) -> Self {
548        self.page_size = Some(page_size);
549        self
550    }
551
552    /// 设置排序方式
553    pub fn sort(mut self, sort: impl Into<String>) -> Self {
554        self.sort = Some(sort.into());
555        self
556    }
557
558    /// 只返回有券商品
559    pub fn with_coupon(mut self) -> Self {
560        self.youquan = Some("1".to_string());
561        self
562    }
563
564    /// 只返回天猫商品
565    pub fn tmall_only(mut self) -> Self {
566        self.tj = Some("tmall".to_string());
567        self
568    }
569
570    /// 只返回海外商品
571    pub fn overseas_only(mut self) -> Self {
572        self.haiwai = Some("1".to_string());
573        self
574    }
575
576    /// 设置商品所在地
577    pub fn location(mut self, location: impl Into<String>) -> Self {
578        self.itemloc = Some(location.into());
579        self
580    }
581
582    /// 设置佣金比率范围
583    pub fn commission_rate_range(mut self, start: u32, end: u32) -> Self {
584        self.start_tk_rate = Some(start.to_string());
585        self.end_tk_rate = Some(end.to_string());
586        self
587    }
588
589    /// 设置价格范围
590    pub fn price_range(mut self, start: f64, end: f64) -> Self {
591        self.start_price = Some(start.to_string());
592        self.end_price = Some(end.to_string());
593        self
594    }
595
596    /// 设置过滤级别 (推荐使用 2)
597    pub fn filter(mut self, level: u32) -> Self {
598        self.filter_type = Some(level.to_string());
599        self
600    }
601
602    /// 设置后台类目 ID
603    pub fn category(mut self, cat: impl Into<String>) -> Self {
604        self.cat = Some(cat.into());
605        self
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn test_convert_by_item_id_request_serialize() {
615        let request = ConvertByItemIdRequest::new("sid123", "mm_123_456_789", "987654321");
616        let json = serde_json::to_value(&request).unwrap();
617
618        assert_eq!(json["sid"], "sid123");
619        assert_eq!(json["pid"], "mm_123_456_789");
620        assert_eq!(json["num_iid"], "987654321");
621        assert!(json.get("relation_id").is_none());
622    }
623
624    #[test]
625    fn test_convert_by_item_id_request_with_options() {
626        let request = ConvertByItemIdRequest::new("sid123", "mm_123_456_789", "987654321")
627            .relation_id("rel123")
628            .signurl(TaobaoSignUrlType::WithFullDetail);
629
630        let json = serde_json::to_value(&request).unwrap();
631
632        assert_eq!(json["relation_id"], "rel123");
633        assert_eq!(json["signurl"], 5);
634    }
635
636    #[test]
637    fn test_convert_by_tkl_request_serialize() {
638        let request = ConvertByTklRequest::new("sid123", "mm_123_456_789", "淘口令内容");
639        let json = serde_json::to_value(&request).unwrap();
640
641        assert_eq!(json["sid"], "sid123");
642        assert_eq!(json["pid"], "mm_123_456_789");
643        assert_eq!(json["tkl"], "淘口令内容");
644    }
645
646    #[test]
647    fn test_batch_convert_request_serialize() {
648        let request = BatchConvertRequest::new("sid123", "mm_123_456_789", "987654321")
649            .relation_ids("rel1,rel2,rel3");
650
651        let json = serde_json::to_value(&request).unwrap();
652
653        assert_eq!(json["relation_ids"], "rel1,rel2,rel3");
654    }
655
656    #[test]
657    fn test_query_orders_request_serialize() {
658        let request = QueryOrdersRequest::new("sid123", "2024-01-01 00:00:00")
659            .end_time("2024-01-31 23:59:59")
660            .query_type(TaobaoOrderQueryType::CreateTime)
661            .page_no(1)
662            .page_size(20);
663
664        let json = serde_json::to_value(&request).unwrap();
665
666        assert_eq!(json["start_time"], "2024-01-01 00:00:00");
667        assert_eq!(json["end_time"], "2024-01-31 23:59:59");
668        assert_eq!(json["query_type"], 1);
669        assert_eq!(json["page_no"], 1);
670        assert_eq!(json["page_size"], 20);
671    }
672
673    #[test]
674    fn test_create_tkl_request_serialize() {
675        let request = CreateTklRequest::new("sid123", "mm_123_456_789")
676            .num_iid("987654321")
677            .text("推荐好物");
678
679        let json = serde_json::to_value(&request).unwrap();
680
681        assert_eq!(json["num_iid"], "987654321");
682        assert_eq!(json["text"], "推荐好物");
683    }
684
685    #[test]
686    fn test_parse_item_id_request_serialize() {
687        let request = ParseItemIdRequest::new("https://item.taobao.com/item.htm?id=123456");
688        let json = serde_json::to_value(&request).unwrap();
689
690        assert_eq!(
691            json["content"],
692            "https://item.taobao.com/item.htm?id=123456"
693        );
694    }
695
696    #[test]
697    fn test_optional_fields_not_serialized() {
698        let request = ConvertByItemIdRequest::new("sid", "pid", "num_iid");
699        let json_str = serde_json::to_string(&request).unwrap();
700
701        assert!(!json_str.contains("relation_id"));
702        assert!(!json_str.contains("special_id"));
703        assert!(!json_str.contains("signurl"));
704    }
705}