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}