Skip to main content

paddle_rust_sdk/
prices.rs

1//! Request builders for working with price entities in Paddle API.
2//!
3//! See the [Paddle API](https://developer.paddle.com/api-reference/prices/overview) documentation for more information.
4
5use std::collections::HashMap;
6use std::ops::Range;
7
8use reqwest::Method;
9use serde::Serialize;
10use serde_with::skip_serializing_none;
11
12use crate::entities::{Duration, Money, Price, PriceQuantity, UnitPriceOverride};
13use crate::enums::{CatalogType, CountryCodeSupported, CurrencyCode, Interval, Status, TaxMode};
14use crate::ids::{PriceID, ProductID};
15use crate::paginated::Paginated;
16use crate::nullable::Nullable;
17use crate::{Paddle, Result};
18
19/// Request builder for fetching prices from Paddle API.
20#[skip_serializing_none]
21#[derive(Serialize)]
22pub struct PricesList<'a> {
23    #[serde(skip)]
24    client: &'a Paddle,
25    after: Option<PriceID>,
26    #[serde(serialize_with = "crate::comma_separated")]
27    id: Option<Vec<PriceID>>,
28    #[serde(serialize_with = "crate::comma_separated")]
29    include: Option<Vec<String>>,
30    order_by: Option<String>,
31    per_page: Option<usize>,
32    #[serde(serialize_with = "crate::comma_separated")]
33    product_id: Option<Vec<ProductID>>,
34    status: Option<Status>,
35    recurring: Option<bool>,
36    r#type: Option<CatalogType>,
37}
38
39impl<'a> PricesList<'a> {
40    pub fn new(client: &'a Paddle) -> Self {
41        Self {
42            client,
43            after: None,
44            id: None,
45            include: None,
46            order_by: None,
47            per_page: None,
48            product_id: None,
49            status: None,
50            recurring: None,
51            r#type: None,
52        }
53    }
54
55    /// Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations.
56    pub fn after(&mut self, price_id: impl Into<PriceID>) -> &mut Self {
57        self.after = Some(price_id.into());
58        self
59    }
60
61    /// Return only the IDs specified.
62    pub fn ids(&mut self, price_ids: impl IntoIterator<Item = impl Into<PriceID>>) -> &mut Self {
63        self.id = Some(price_ids.into_iter().map(Into::into).collect());
64        self
65    }
66
67    /// Include related entities in the response. Valid values are: "product".
68    pub fn include(&mut self, includes: impl IntoIterator<Item = impl Into<String>>) -> &mut Self {
69        self.include = Some(includes.into_iter().map(Into::into).collect());
70        self
71    }
72
73    /// Order returned entities by the specified field. Valid fields for ordering: billing_cycle.frequency, billing_cycle.interval, id, product_id, quantity.maximum, quantity.minimum, status, tax_mode, unit_price.amount, and unit_price.currency_code
74    pub fn order_by_asc(&mut self, field: &str) -> &mut Self {
75        self.order_by = Some(format!("{}[ASC]", field));
76        self
77    }
78
79    /// Order returned entities by the specified field. Valid fields for ordering: billing_cycle.frequency, billing_cycle.interval, id, product_id, quantity.maximum, quantity.minimum, status, tax_mode, unit_price.amount, and unit_price.currency_code
80    pub fn order_by_desc(&mut self, field: &str) -> &mut Self {
81        self.order_by = Some(format!("{}[DESC]", field));
82        self
83    }
84
85    /// Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested.
86    /// Check `meta.pagination.per_page` in the response to see how many were returned.
87    ///
88    /// Default: `50`; Maximum: `200`.
89    pub fn per_page(&mut self, entities_per_page: usize) -> &mut Self {
90        self.per_page = Some(entities_per_page);
91        self
92    }
93
94    /// Return only prices for the specified product IDs.
95    pub fn product_ids(
96        &mut self,
97        product_ids: impl IntoIterator<Item = impl Into<ProductID>>,
98    ) -> &mut Self {
99        self.product_id = Some(product_ids.into_iter().map(Into::into).collect());
100        self
101    }
102
103    /// Return only prices with the specified status.
104    pub fn status(&mut self, status: Status) -> &mut Self {
105        self.status = Some(status);
106        self
107    }
108
109    /// Determine whether returned entities are for recurring prices (true) or one-time prices (false)
110    pub fn recurring(&mut self, value: bool) -> &mut Self {
111        self.recurring = Some(value);
112        self
113    }
114
115    /// Return only prices with the specified type.
116    pub fn r#type(&mut self, catalog_type: CatalogType) -> &mut Self {
117        self.r#type = Some(catalog_type);
118        self
119    }
120
121    /// Returns a paginator for fetching pages of entities from Paddle
122    pub fn send(&self) -> Paginated<'_, Vec<Price>> {
123        Paginated::new(self.client, "/prices", self)
124    }
125}
126
127/// Request builder for creating a new price in Paddle API.
128#[skip_serializing_none]
129#[derive(Serialize)]
130pub struct PricesCreate<'a> {
131    #[serde(skip)]
132    client: &'a Paddle,
133    description: String,
134    product_id: ProductID,
135    unit_price: Money,
136    r#type: Option<CatalogType>,
137    name: Option<String>,
138    billing_cycle: Option<Duration>,
139    trial_period: Option<Duration>,
140    tax_mode: TaxMode,
141    unit_price_overrides: Option<Vec<UnitPriceOverride>>,
142    quantity: Option<PriceQuantity>,
143    custom_data: Option<HashMap<String, String>>,
144}
145
146impl<'a> PricesCreate<'a> {
147    pub fn new(
148        client: &'a Paddle,
149        product_id: impl Into<ProductID>,
150        description: impl Into<String>,
151        amount: u64,
152        currency: CurrencyCode,
153    ) -> Self {
154        Self {
155            client,
156            description: description.into(),
157            product_id: product_id.into(),
158            unit_price: Money {
159                amount: amount.to_string(),
160                currency_code: currency,
161            },
162            r#type: None,
163            name: None,
164            billing_cycle: None,
165            trial_period: None,
166            tax_mode: TaxMode::AccountSetting,
167            unit_price_overrides: None,
168            quantity: None,
169            custom_data: None,
170        }
171    }
172
173    /// Set the price type.
174    pub fn catalog_type(&mut self, catalog_type: CatalogType) -> &mut Self {
175        self.r#type = Some(catalog_type);
176        self
177    }
178
179    /// Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills.
180    pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
181        self.name = Some(name.into());
182        self
183    }
184
185    /// How often this price should be charged.
186    pub fn billing_cycle(&mut self, frequency: u64, interval: Interval) -> &mut Self {
187        self.billing_cycle = Some(Duration {
188            interval,
189            frequency,
190        });
191
192        self
193    }
194
195    /// Trial period for the product related to this price. The billing cycle begins once the trial period is over. Requires billing_cycle.
196    pub fn trial_period(&mut self, frequency: u64, interval: Interval) -> &mut Self {
197        self.trial_period = Some(Duration {
198            interval,
199            frequency,
200        });
201
202        self
203    }
204
205    /// How tax is calculated for this price. If omitted, defaults to TaxMode::AccountSetting.
206    /// See [TaxMode] for more information.
207    pub fn tax_mode(&mut self, tax_mode: TaxMode) -> &mut Self {
208        self.tax_mode = tax_mode;
209        self
210    }
211
212    /// Use to override the base price with a custom price and currency for a country or group of countries.
213    /// See [UnitPriceOverride] for more information.
214    /// See [CountryCodeSupported] for more information.
215    /// See [Money] for more information.
216    /// See [CurrencyCode] for more information.
217    pub fn add_unit_price_override(
218        &mut self,
219        country_codes: impl IntoIterator<Item = CountryCodeSupported>,
220        amount: u64,
221        currency: CurrencyCode,
222    ) -> &mut Self {
223        if self.unit_price_overrides.is_none() {
224            self.unit_price_overrides = Some(vec![]);
225        }
226
227        self.unit_price_overrides
228            .as_mut()
229            .unwrap()
230            .push(UnitPriceOverride {
231                country_codes: country_codes.into_iter().collect(),
232                unit_price: Money {
233                    amount: amount.to_string(),
234                    currency_code: currency,
235                },
236            });
237
238        self
239    }
240
241    /// Use to override the base price with a custom price and currency for a country or group of countries.
242    /// This will replace any existing overrides.
243    /// Use `add_unit_price_override` to add additional overrides.
244    /// See [UnitPriceOverride] for more information.
245    /// See [CountryCodeSupported] for more information.
246    /// See [Money] for more information.
247    /// See [CurrencyCode] for more information.
248    pub fn set_unit_price_overrides(&mut self, overrides: Vec<UnitPriceOverride>) -> &mut Self {
249        self.unit_price_overrides = Some(overrides);
250        self
251    }
252
253    /// Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. If omitted, defaults to 1..100.
254    pub fn quantity(&mut self, range: Range<u64>) -> &mut Self {
255        self.quantity = Some(PriceQuantity {
256            minimum: range.start,
257            maximum: range.end,
258        });
259        self
260    }
261
262    /// Set custom data for this price.
263    pub fn custom_data(&mut self, custom_data: HashMap<String, String>) -> &mut Self {
264        self.custom_data = Some(custom_data);
265        self
266    }
267
268    /// Send the request to Paddle and return the response.
269    pub async fn send(&self) -> Result<Price> {
270        self.client.send(self, Method::POST, "/prices").await
271    }
272}
273
274/// Request builder for fetching a specific price from Paddle API.
275#[skip_serializing_none]
276#[derive(Serialize)]
277pub struct PriceGet<'a> {
278    #[serde(skip)]
279    client: &'a Paddle,
280    #[serde(skip)]
281    price_id: PriceID,
282    #[serde(serialize_with = "crate::comma_separated")]
283    include: Option<Vec<String>>,
284}
285
286impl<'a> PriceGet<'a> {
287    pub fn new(client: &'a Paddle, price_id: impl Into<PriceID>) -> Self {
288        Self {
289            client,
290            price_id: price_id.into(),
291            include: None,
292        }
293    }
294
295    /// Include related entities in the response. Allowed values: "product".
296    pub fn include(&mut self, entities: impl IntoIterator<Item = impl AsRef<str>>) -> &mut Self {
297        self.include = Some(
298            entities
299                .into_iter()
300                .map(|s| s.as_ref().to_string())
301                .collect(),
302        );
303        self
304    }
305
306    /// Send the request to Paddle and return the response.
307    pub async fn send(&self) -> Result<Price> {
308        self.client
309            .send(
310                self,
311                Method::GET,
312                &format!("/prices/{}", self.price_id.as_ref()),
313            )
314            .await
315    }
316}
317
318/// Request builder for updating a price in Paddle API.
319#[derive(Serialize)]
320pub struct PriceUpdate<'a> {
321    #[serde(skip)]
322    client: &'a Paddle,
323    #[serde(skip)]
324    price_id: PriceID,
325    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
326    description: Nullable<String>,
327    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
328    r#type: Nullable<CatalogType>,
329    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
330    name: Nullable<String>,
331    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
332    billing_cycle: Nullable<Duration>,
333    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
334    trial_period: Nullable<Duration>,
335    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
336    tax_mode: Nullable<TaxMode>,
337    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
338    unit_price: Nullable<Money>,
339    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
340    unit_price_overrides: Nullable<Vec<UnitPriceOverride>>,
341    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
342    quantity: Nullable<PriceQuantity>,
343    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
344    status: Nullable<Status>,
345    #[serde(skip_serializing_if = "Nullable::is_unchanged")]
346    custom_data: Nullable<HashMap<String, String>>,
347}
348
349impl<'a> PriceUpdate<'a> {
350    pub fn new(client: &'a Paddle, price_id: impl Into<PriceID>) -> Self {
351        Self {
352            client,
353            price_id: price_id.into(),
354            description: Nullable::Unchanged,
355            r#type: Nullable::Unchanged,
356            name: Nullable::Unchanged,
357            billing_cycle: Nullable::Unchanged,
358            trial_period: Nullable::Unchanged,
359            tax_mode: Nullable::Unchanged,
360            unit_price: Nullable::Unchanged,
361            unit_price_overrides: Nullable::Unchanged,
362            quantity: Nullable::Unchanged,
363            status: Nullable::Unchanged,
364            custom_data: Nullable::Unchanged,
365        }
366    }
367
368    /// Update the price description.
369    pub fn description(&mut self, description: impl Into<Nullable<String>>) -> &mut Self {
370        self.description = description.into();
371        self
372    }
373
374    /// Update the price type.
375    pub fn catalog_type(&mut self, catalog_type: impl Into<Nullable<CatalogType>>) -> &mut Self {
376        self.r#type = catalog_type.into();
377        self
378    }
379
380    /// Update the price name. Name is shown to customers at checkout and on invoices. Typically describes how often the related product bills.
381    pub fn name(&mut self, name: impl Into<Nullable<String>>) -> &mut Self {
382        self.name = name.into();
383        self
384    }
385
386    /// Update how often this price should be charged.
387    pub fn billing_cycle(
388        &mut self,
389        billing_cycle: impl Into<Nullable<Duration>>,
390    ) -> &mut Self {
391        self.billing_cycle = billing_cycle.into();
392        self
393    }
394
395    /// Update the trial period for the product related to this price.
396    pub fn trial_period(
397        &mut self,
398        trial_period: impl Into<Nullable<Duration>>,
399    ) -> &mut Self {
400        self.trial_period = trial_period.into();
401        self
402    }
403
404    /// Update how tax is calculated for this price.
405    pub fn tax_mode(&mut self, tax_mode: impl Into<Nullable<TaxMode>>) -> &mut Self {
406        self.tax_mode = tax_mode.into();
407        self
408    }
409
410    /// Update the base price. This price applies to all customers, except for customers located in countries where you have unit_price_overrides.
411    pub fn unit_price(&mut self, unit_price: impl Into<Nullable<Money>>) -> &mut Self {
412        self.unit_price = unit_price.into();
413        self
414    }
415
416    /// Use to override the base price with a custom price and currency for a country or group of countries.
417    pub fn add_unit_price_override(
418        &mut self,
419        country_codes: impl IntoIterator<Item = CountryCodeSupported>,
420        amount: u64,
421        currency: CurrencyCode,
422    ) -> &mut Self {
423        if !matches!(self.unit_price_overrides, Nullable::Value(_)) {
424            self.unit_price_overrides = Nullable::Value(vec![]);
425        }
426
427        if let Nullable::Value(ref mut v) = self.unit_price_overrides {
428            v.push(UnitPriceOverride {
429                country_codes: country_codes.into_iter().collect(),
430                unit_price: Money {
431                    amount: amount.to_string(),
432                    currency_code: currency,
433                },
434            });
435        }
436
437        self
438    }
439
440    /// Use to override the base price with a custom price and currency for a country or group of countries.
441    pub fn set_unit_price_overrides(
442        &mut self,
443        overrides: impl Into<Nullable<Vec<UnitPriceOverride>>>,
444    ) -> &mut Self {
445        self.unit_price_overrides = overrides.into();
446        self
447    }
448
449    /// Update how many times the related product can be purchased at this price.
450    pub fn quantity(&mut self, quantity: impl Into<Nullable<PriceQuantity>>) -> &mut Self {
451        self.quantity = quantity.into();
452        self
453    }
454
455    /// Update whether this entity can be used in Paddle.
456    pub fn status(&mut self, status: impl Into<Nullable<Status>>) -> &mut Self {
457        self.status = status.into();
458        self
459    }
460
461    /// Set custom data for the price.
462    pub fn custom_data(
463        &mut self,
464        custom_data: impl Into<Nullable<HashMap<String, String>>>,
465    ) -> &mut Self {
466        self.custom_data = custom_data.into();
467        self
468    }
469
470    /// Send the request to Paddle and return the response.
471    pub async fn send(&self) -> Result<Price> {
472        self.client
473            .send(
474                self,
475                Method::PATCH,
476                &format!("/prices/{}", self.price_id.as_ref()),
477            )
478            .await
479    }
480}