amazon_spapi/client_apis/
pricing_v0.rs

1use anyhow::{anyhow, Ok, Result};
2use std::vec;
3
4use crate::{
5    client::{ApiEndpoint, ApiMethod, SpapiClient},
6    models::product_pricing_v0::{
7        CustomerType, GetItemOffersBatchRequest, GetItemOffersBatchResponse,
8        GetListingOffersBatchRequest, GetListingOffersBatchResponse, GetOffersResponse,
9        GetPricingResponse, HttpMethod, ItemCondition, ItemOffersRequest, ListingOffersRequest,
10    },
11};
12
13impl SpapiClient {
14    /// Returns pricing information for a seller's offer listings based on seller SKU or ASIN.
15    ///
16    /// # Arguments
17    /// * `marketplace_id` - A marketplace identifier. Specifies the marketplace for which prices are returned.
18    /// * `item_type` - Indicates whether ASIN values or seller SKU values are used to identify items. Possible values: "Asin", "Sku".
19    /// * `asins` - A list of up to twenty Amazon Standard Identification Number (ASIN) values used to identify items in the given marketplace.
20    /// * `skus` - A list of up to twenty seller SKU values used to identify items in the given marketplace.
21    /// * `item_condition` - Filters the offer listings based on item condition. Possible values: "New", "Used", "Collectible", "Refurbished", "Club".
22    /// * `offer_type` - Indicates whether to request pricing information for the seller's B2C or B2B offers. Default is "B2C".
23    ///
24    /// # Rate Limits
25    /// - Rate: 0.5 requests per second
26    /// - Burst: 1
27    ///
28    /// # Examples
29    /// ```no_run
30    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
31    /// # let client = SpApiClient::new(config);
32    /// let response = client.get_pricing(
33    ///     "ATVPDKIKX0DER",
34    ///     "Asin",
35    ///     Some(vec!["B08N5WRWNW".to_string(), "B07XJ8C8F7".to_string()]),
36    ///     None,
37    ///     Some("New".to_string()),
38    ///     Some("B2C".to_string()),
39    /// ).await?;
40    ///
41    /// println!("Pricing information: {:?}", response);
42    /// # Ok(())
43    /// # }
44    /// ```
45    pub async fn get_pricing(
46        &self,
47        marketplace_id: &str,
48        item_type: &str,
49        asins: Option<Vec<String>>,
50        skus: Option<Vec<String>>,
51        item_condition: Option<String>,
52        offer_type: Option<String>,
53    ) -> Result<GetPricingResponse> {
54        let mut query_params = vec![];
55        query_params.push(("MarketplaceId".to_string(), marketplace_id.to_string()));
56        query_params.push(("ItemType".to_string(), item_type.to_string()));
57
58        if let Some(asins_list) = asins {
59            for (i, asin) in asins_list.iter().enumerate() {
60                query_params.push((format!("Asins.Asin.{}", i + 1), asin.clone()));
61            }
62        }
63
64        if let Some(skus_list) = skus {
65            for (i, sku) in skus_list.iter().enumerate() {
66                query_params.push((format!("Skus.SellerSKU.{}", i + 1), sku.clone()));
67            }
68        }
69
70        if let Some(condition) = item_condition {
71            query_params.push(("ItemCondition".to_string(), condition));
72        }
73
74        if let Some(offer_type_val) = offer_type {
75            query_params.push(("OfferType".to_string(), offer_type_val));
76        }
77
78        let endpoint = ApiEndpoint {
79            version: "pricing/v0",
80            path: "/products/pricing/v0/price",
81            path_params: None,
82            method: ApiMethod::Get,
83            rate: 0.5,
84            burst: 1,
85        };
86
87        let res = self
88            .request(&endpoint, Some(query_params), None, None)
89            .await?;
90        Self::from_json(&res)
91    }
92
93    /// Returns competitive pricing information for a seller's offer listings based on seller SKU or ASIN.
94    ///
95    /// # Arguments
96    /// * `marketplace_id` - A marketplace identifier. Specifies the marketplace for which prices are returned.
97    /// * `item_type` - Indicates whether ASIN values or seller SKU values are used to identify items. Possible values: "Asin", "Sku".
98    /// * `asins` - A list of up to twenty Amazon Standard Identification Number (ASIN) values used to identify items in the given marketplace.
99    /// * `skus` - A list of up to twenty seller SKU values used to identify items in the given marketplace.
100    /// * `customer_type` - Indicates whether to request Consumer or Business offers. Default is "Consumer".
101    ///
102    /// # Rate Limits
103    /// - Rate: 0.5 requests per second
104    /// - Burst: 1
105    ///
106    /// # Examples
107    /// ```no_run
108    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
109    /// # let client = SpApiClient::new(config);
110    /// let response = client.get_competitive_pricing(
111    ///     "ATVPDKIKX0DER",
112    ///     "Asin",
113    ///     Some(vec!["B08N5WRWNW".to_string(), "B07XJ8C8F7".to_string()]),
114    ///     None,
115    ///     Some("Consumer".to_string()),
116    /// ).await?;
117    ///
118    /// println!("Competitive pricing: {:?}", response);
119    /// # Ok(())
120    /// # }
121    /// ```
122    pub async fn get_competitive_pricing(
123        &self,
124        marketplace_id: &str,
125        item_type: &str,
126        asins: Option<Vec<String>>,
127        skus: Option<Vec<String>>,
128        customer_type: Option<String>,
129    ) -> Result<GetPricingResponse> {
130        let mut query_params = vec![];
131        query_params.push(("MarketplaceId".to_string(), marketplace_id.to_string()));
132        query_params.push(("ItemType".to_string(), item_type.to_string()));
133
134        if let Some(asins_list) = asins {
135            for (i, asin) in asins_list.iter().enumerate() {
136                query_params.push((format!("Asins.Asin.{}", i + 1), asin.clone()));
137            }
138        }
139
140        if let Some(skus_list) = skus {
141            for (i, sku) in skus_list.iter().enumerate() {
142                query_params.push((format!("Skus.SellerSKU.{}", i + 1), sku.clone()));
143            }
144        }
145
146        if let Some(customer_type_val) = customer_type {
147            query_params.push(("CustomerType".to_string(), customer_type_val));
148        }
149
150        let endpoint = ApiEndpoint {
151            version: "pricing/v0",
152            path: "/products/pricing/v0/competitivePrice",
153            path_params: None,
154            method: ApiMethod::Get,
155            rate: 0.5,
156            burst: 1,
157        };
158
159        let res = self
160            .request(&endpoint, Some(query_params), None, None)
161            .await?;
162        Self::from_json(&res)
163    }
164
165    /// Returns the lowest priced offers for a single item based on ASIN.
166    ///
167    /// # Arguments
168    /// * `marketplace_id` - A marketplace identifier. Specifies the marketplace for which prices are returned.
169    /// * `asin` - The Amazon Standard Identification Number (ASIN) of the item for which you want to retrieve offer information.
170    /// * `item_condition` - Filters the offer listings to be considered based on item condition. Possible values: "New", "Used", "Collectible", "Refurbished", "Club".
171    /// * `customer_type` - Indicates whether to request Consumer or Business offers. Default is "Consumer".
172    ///
173    /// # Rate Limits
174    /// - Rate: 0.5 requests per second
175    /// - Burst: 1
176    ///
177    /// # Examples
178    /// ```no_run
179    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
180    /// # let client = SpApiClient::new(config);
181    /// let response = client.get_item_offers(
182    ///     "ATVPDKIKX0DER",
183    ///     "B08N5WRWNW",
184    ///     "New",
185    ///     Some("Consumer".to_string()),
186    /// ).await?;
187    ///
188    /// println!("Item offers: {:?}", response);
189    /// # Ok(())
190    /// # }
191    /// ```
192    pub async fn get_item_offers(
193        &self,
194        marketplace_id: &str,
195        asin: &str,
196        item_condition: &str,
197        customer_type: Option<String>,
198    ) -> Result<GetOffersResponse> {
199        let mut query_params = vec![];
200        query_params.push(("MarketplaceId".to_string(), marketplace_id.to_string()));
201        query_params.push(("ItemCondition".to_string(), item_condition.to_string()));
202
203        if let Some(customer_type_val) = customer_type {
204            query_params.push(("CustomerType".to_string(), customer_type_val));
205        }
206
207        let endpoint = ApiEndpoint {
208            version: "pricing/v0",
209            path: "/products/pricing/v0/items/{asin}/offers",
210            path_params: Some(vec![("asin", asin.to_string())]),
211            method: ApiMethod::Get,
212            rate: 0.5,
213            burst: 1,
214        };
215
216        let res = self
217            .request(&endpoint, Some(query_params), None, None)
218            .await?;
219        Self::from_json(&res)
220    }
221
222    /// Returns the lowest priced offers for a single SKU listing.
223    ///
224    /// # Arguments
225    /// * `marketplace_id` - A marketplace identifier. Specifies the marketplace for which prices are returned.
226    /// * `seller_sku` - A seller identifier that you want to retrieve offer information for.
227    /// * `item_condition` - Filters the offer listings to be considered based on item condition. Possible values: "New", "Used", "Collectible", "Refurbished", "Club".
228    /// * `customer_type` - Indicates whether to request Consumer or Business offers. Default is "Consumer".
229    ///
230    /// # Rate Limits
231    /// - Rate: 1 request per second
232    /// - Burst: 2
233    ///
234    /// # Examples
235    /// ```no_run
236    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
237    /// # let client = SpApiClient::new(config);
238    /// let response = client.get_listing_offers(
239    ///     "ATVPDKIKX0DER",
240    ///     "MY-SKU-123",
241    ///     "New",
242    ///     Some("Consumer".to_string()),
243    /// ).await?;
244    ///
245    /// println!("Listing offers: {:?}", response);
246    /// # Ok(())
247    /// # }
248    /// ```
249    pub async fn get_listing_offers(
250        &self,
251        marketplace_id: &str,
252        seller_sku: &str,
253        item_condition: &str,
254        customer_type: Option<String>,
255    ) -> Result<GetOffersResponse> {
256        let mut query_params = vec![];
257        query_params.push(("MarketplaceId".to_string(), marketplace_id.to_string()));
258        query_params.push(("ItemCondition".to_string(), item_condition.to_string()));
259
260        if let Some(customer_type_val) = customer_type {
261            query_params.push(("CustomerType".to_string(), customer_type_val));
262        }
263
264        let endpoint = ApiEndpoint {
265            version: "pricing/v0",
266            path: "/products/pricing/v0/listings/{seller_sku}/offers",
267            path_params: Some(vec![("seller_sku", seller_sku.to_string())]),
268            method: ApiMethod::Get,
269            rate: 1.0,
270            burst: 2,
271        };
272
273        let res = self
274            .request(&endpoint, Some(query_params), None, None)
275            .await?;
276        Self::from_json(&res)
277    }
278
279    /// Returns the set of responses that correspond to the batched list of up to 20 requests defined in the request body.
280    /// The response for each successful (HTTP status code 200) request in the set includes the item offers for a given ASIN.
281    ///
282    /// # Arguments
283    /// * `request` - The batch of `getItemOffers` requests.
284    ///
285    /// # Rate Limits
286    /// - Rate: 0.1 requests per second
287    /// - Burst: 1
288    ///
289    /// # Examples
290    /// ```no_run
291    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
292    /// # let client = SpApiClient::new(config);
293    /// use spapi::models::pricing_v0::*;
294    ///
295    /// let batch_request = GetItemOffersBatchRequest {
296    ///     requests: Some(vec![
297    ///         ItemOffersRequest {
298    ///             uri: "/products/pricing/v0/items/B123456789/offers".to_string(),
299    ///             method: HttpMethod::Get,
300    ///             marketplace_id: "ATVPDKIKX0DER".to_string(),
301    ///             item_condition: "New".to_string(),
302    ///             customer_type: Some("Consumer".to_string()),
303    ///             headers: None,
304    ///         },
305    ///     ]),
306    /// };
307    ///
308    /// let response = client.get_item_offers_batch(batch_request).await?;
309    /// println!("Batch item offers: {:?}", response);
310    /// # Ok(())
311    /// # }
312    /// ```
313    pub async fn get_item_offers_batch(
314        &self,
315        request: GetItemOffersBatchRequest,
316    ) -> Result<GetItemOffersBatchResponse> {
317        let endpoint = ApiEndpoint {
318            version: "pricing/v0",
319            path: "/batches/products/pricing/v0/itemOffers",
320            path_params: None,
321            method: ApiMethod::Post,
322            rate: 0.1,
323            burst: 1,
324        };
325
326        let body = serde_json::to_string(&request)?;
327        let res = self.request(&endpoint, None, None, Some(&body)).await?;
328        Self::from_json(&res)
329    }
330
331    /// Returns the set of responses that correspond to the batched list of up to 20 requests defined in the request body.
332    /// The response for each successful (HTTP status code 200) request in the set includes the listing offers for a given SKU.
333    ///
334    /// # Arguments
335    /// * `request` - The batch of `getListingOffers` requests.
336    ///
337    /// # Rate Limits
338    /// - Rate: 0.5 requests per second
339    /// - Burst: 1
340    ///
341    /// # Examples
342    /// ```no_run
343    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
344    /// # let client = SpApiClient::new(config);
345    /// use spapi::models::pricing_v0::*;
346    ///
347    /// let batch_request = GetListingOffersBatchRequest {
348    ///     requests: Some(vec![
349    ///         ListingOffersRequest {
350    ///             uri: "/products/pricing/v0/listings/MY-SKU-123/offers".to_string(),
351    ///             method: HttpMethod::Get,
352    ///             marketplace_id: "ATVPDKIKX0DER".to_string(),
353    ///             item_condition: "New".to_string(),
354    ///             customer_type: Some("Consumer".to_string()),
355    ///             headers: None,
356    ///         },
357    ///     ]),
358    /// };
359    ///
360    /// let response = client.get_listing_offers_batch(batch_request).await?;
361    /// println!("Batch listing offers: {:?}", response);
362    /// # Ok(())
363    /// # }
364    /// ```
365    pub async fn get_listing_offers_batch(
366        &self,
367        request: GetListingOffersBatchRequest,
368    ) -> Result<GetListingOffersBatchResponse> {
369        let endpoint = ApiEndpoint {
370            version: "pricing/v0",
371            path: "/batches/products/pricing/v0/listingOffers",
372            path_params: None,
373            method: ApiMethod::Post,
374            // 测试0.5时quotaExceeded, 所以降低访问频率
375            rate: 0.1, //0.5,
376            burst: 1,
377        };
378
379        let body = serde_json::to_string(&request)?;
380
381        log::debug!("Request body: {}", body);
382
383        let res = self.request(&endpoint, None, None, Some(&body)).await?;
384        Self::from_json(&res)
385    }
386
387    /// Convenience method to get item offers for multiple ASINs in a single batch request.
388    /// - asins: length between 1 and 20
389    pub async fn get_item_offers_batch_by_asins(
390        &self,
391        asins: Vec<&str>,
392    ) -> Result<GetItemOffersBatchResponse> {
393        let marketplace_id = self.get_marketplace_id();
394        let item_offer_requests = asins
395            .iter()
396            .map(|&asin| {
397                ItemOffersRequest {
398                    uri: format!("/products/pricing/v0/items/{}/offers", asin),
399                    method: HttpMethod::Get,
400                    headers: None,
401                    marketplace_id: marketplace_id.to_string(),
402                    item_condition: ItemCondition::New,
403                    customer_type: None, // Default to CustomerType::Consumer if None
404                }
405            })
406            .collect();
407        let request = GetItemOffersBatchRequest {
408            requests: Some(item_offer_requests),
409        };
410        self.get_item_offers_batch(request).await
411    }
412
413    /// Convenience method to get listing offers for multiple SKUs in a single batch request.
414    /// - skus: length between 1 and 20
415    pub async fn get_listing_offers_batch_by_skus(
416        &self,
417        skus: Vec<&str>,
418    ) -> Result<GetListingOffersBatchResponse> {
419        let marketplace_id = self.get_marketplace_id();
420        let listing_offer_requests = skus
421            .iter()
422            .map(|&sku| {
423                ListingOffersRequest {
424                    uri: format!("/products/pricing/v0/listings/{}/offers", sku),
425                    method: HttpMethod::Get,
426                    headers: None,
427                    marketplace_id: marketplace_id.to_string(),
428                    item_condition: ItemCondition::New,
429                    customer_type: None, // Default to CustomerType::Consumer if None
430                }
431            })
432            .collect();
433        self.get_listing_offers_batch(GetListingOffersBatchRequest {
434            requests: Some(listing_offer_requests),
435        })
436        .await
437    }
438}
439
440// Helper functions for batch requests
441
442/// Helper function to create a batch request for getting item offers
443pub fn create_item_offers_batch_request(
444    marketplace_id: &str,
445    asins: &[&str],
446    item_condition: ItemCondition,
447    customer_type: Option<CustomerType>,
448) -> GetItemOffersBatchRequest {
449    let requests = asins
450        .iter()
451        .map(|asin| ItemOffersRequest {
452            uri: format!("/products/pricing/v0/items/{}/offers", asin),
453            method: HttpMethod::Get,
454            marketplace_id: marketplace_id.to_string(),
455            item_condition: item_condition.clone(),
456            customer_type: customer_type.clone(),
457            headers: None,
458        })
459        .collect();
460
461    GetItemOffersBatchRequest {
462        requests: Some(requests),
463    }
464}
465
466/// Helper function to create a batch request for getting listing offers
467pub fn create_listing_offers_batch_request(
468    marketplace_id: &str,
469    skus: &[&str],
470    item_condition: ItemCondition,
471    customer_type: Option<CustomerType>,
472) -> GetListingOffersBatchRequest {
473    let requests = skus
474        .iter()
475        .map(|sku| ListingOffersRequest {
476            uri: format!("/products/pricing/v0/listings/{}/offers", sku),
477            method: HttpMethod::Get,
478            marketplace_id: marketplace_id.to_string(),
479            item_condition: item_condition.clone(),
480            customer_type: customer_type.clone(),
481            headers: None,
482        })
483        .collect();
484
485    GetListingOffersBatchRequest {
486        requests: Some(requests),
487    }
488}
489
490#[cfg(test)]
491mod tests {
492    use super::*;
493
494    #[test]
495    fn test_create_item_offers_batch_request() {
496        let asins = vec!["B123456789", "B987654321"];
497        let batch_request = create_item_offers_batch_request(
498            "ATVPDKIKX0DER",
499            &asins,
500            ItemCondition::New,
501            Some(CustomerType::Consumer),
502        );
503
504        assert!(batch_request.requests.is_some());
505        let requests = batch_request.requests.unwrap();
506        assert_eq!(requests.len(), 2);
507        assert_eq!(requests[0].marketplace_id, "ATVPDKIKX0DER");
508        assert_eq!(requests[0].item_condition, ItemCondition::New);
509        assert_eq!(requests[0].customer_type, Some(CustomerType::Consumer));
510    }
511
512    #[test]
513    fn test_create_listing_offers_batch_request() {
514        let skus = vec!["MY-SKU-123", "MY-SKU-456"];
515        let batch_request = create_listing_offers_batch_request(
516            "ATVPDKIKX0DER",
517            &skus,
518            ItemCondition::New,
519            Some(CustomerType::Consumer),
520        );
521
522        assert!(batch_request.requests.is_some());
523        let requests = batch_request.requests.unwrap();
524        assert_eq!(requests.len(), 2);
525        assert_eq!(requests[0].marketplace_id, "ATVPDKIKX0DER");
526        assert_eq!(requests[0].item_condition, ItemCondition::New);
527        assert_eq!(requests[0].customer_type, Some(CustomerType::Consumer));
528    }
529}