paddle_rust_sdk/
discounts.rs

1//! Builders for making requests to the Paddle API for discounts.
2//!
3//! See the [Paddle API](https://developer.paddle.com/api-reference/discounts/overview) documentation for more information.
4
5use std::collections::HashMap;
6
7use chrono::{DateTime, Utc};
8use reqwest::Method;
9use serde::Serialize;
10use serde_with::skip_serializing_none;
11
12use crate::entities::Discount;
13use crate::enums::{CurrencyCode, DiscountType, Status};
14use crate::ids::DiscountID;
15use crate::paginated::Paginated;
16use crate::{Paddle, Result};
17
18/// Request builder for fetching discounts from Paddle API.
19#[skip_serializing_none]
20#[derive(Serialize)]
21pub struct DiscountsList<'a> {
22    #[serde(skip)]
23    client: &'a Paddle,
24    after: Option<DiscountID>,
25    #[serde(serialize_with = "crate::comma_separated")]
26    code: Option<Vec<String>>,
27    #[serde(serialize_with = "crate::comma_separated")]
28    id: Option<Vec<DiscountID>>,
29    order_by: Option<String>,
30    per_page: Option<usize>,
31    status: Option<Status>,
32}
33
34impl<'a> DiscountsList<'a> {
35    pub fn new(client: &'a Paddle) -> Self {
36        Self {
37            client,
38            after: None,
39            code: None,
40            id: None,
41            order_by: None,
42            per_page: None,
43            status: None,
44        }
45    }
46
47    /// Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations.
48    pub fn after(&mut self, discount_id: impl Into<DiscountID>) -> &mut Self {
49        self.after = Some(discount_id.into());
50        self
51    }
52
53    /// Return only entities that match the discount codes provided
54    pub fn codes(&mut self, codes: impl IntoIterator<Item = impl AsRef<str>>) -> &mut Self {
55        self.code = Some(codes.into_iter().map(|s| s.as_ref().to_string()).collect());
56        self
57    }
58
59    /// Return only the IDs specified.
60    pub fn ids(
61        &mut self,
62        discount_ids: impl IntoIterator<Item = impl Into<DiscountID>>,
63    ) -> &mut Self {
64        self.id = Some(discount_ids.into_iter().map(Into::into).collect());
65        self
66    }
67
68    /// Order returned entities by the specified field. Valid fields for ordering: created_at and id
69    pub fn order_by_asc(&mut self, field: &str) -> &mut Self {
70        self.order_by = Some(format!("{}[ASC]", field));
71        self
72    }
73
74    /// Order returned entities by the specified field. Valid fields for ordering: created_at and id
75    pub fn order_by_desc(&mut self, field: &str) -> &mut Self {
76        self.order_by = Some(format!("{}[DESC]", field));
77        self
78    }
79
80    /// Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested.
81    /// Check `meta.pagination.per_page` in the response to see how many were returned.
82    ///
83    /// Default: `50`; Maximum: `200`.
84    pub fn per_page(&mut self, entities_per_page: usize) -> &mut Self {
85        self.per_page = Some(entities_per_page);
86        self
87    }
88
89    /// Return only prices with the specified status.
90    pub fn status(&mut self, status: Status) -> &mut Self {
91        self.status = Some(status);
92        self
93    }
94
95    /// Returns a paginator for fetching pages of entities from Paddle
96    pub fn send(&self) -> Paginated<'_, Vec<Discount>> {
97        Paginated::new(self.client, "/discounts", self)
98    }
99}
100
101/// Request builder for creating a discount in Paddle API.
102#[skip_serializing_none]
103#[derive(Serialize)]
104pub struct DiscountCreate<'a> {
105    #[serde(skip)]
106    client: &'a Paddle,
107    amount: String,
108    description: String,
109    r#type: DiscountType,
110    enabled_for_checkout: bool,
111    code: Option<String>,
112    currency_code: Option<CurrencyCode>,
113    recur: bool,
114    maximum_recurring_intervals: Option<u64>,
115    usage_limit: Option<u64>,
116    restrict_to: Option<Vec<String>>,
117    expires_at: Option<DateTime<Utc>>,
118    custom_data: Option<HashMap<String, String>>,
119}
120
121impl<'a> DiscountCreate<'a> {
122    pub fn new(
123        client: &'a Paddle,
124        amount: impl Into<String>,
125        description: impl Into<String>,
126        discount_type: DiscountType,
127    ) -> Self {
128        Self {
129            client,
130            amount: amount.into(),
131            description: description.into(),
132            r#type: discount_type,
133            enabled_for_checkout: false,
134            code: None,
135            currency_code: None,
136            recur: false,
137            maximum_recurring_intervals: None,
138            usage_limit: None,
139            restrict_to: None,
140            expires_at: None,
141            custom_data: None,
142        }
143    }
144
145    /// Whether this discount can be redeemed by customers at checkout (true) or not (false).
146    pub fn enabled_for_checkout(&mut self, enabled: bool) -> &mut Self {
147        self.enabled_for_checkout = enabled;
148        self
149    }
150
151    /// Unique code that customers can use to redeem this discount at checkout. Use letters and numbers only, up to 32 characters. Not case-sensitive.
152    ///
153    /// If omitted and enabled_for_checkout is true, Paddle generates a random 10-character code.
154    pub fn code(&mut self, code: impl Into<String>) -> &mut Self {
155        self.code = Some(code.into());
156        self
157    }
158
159    /// Supported three-letter ISO 4217 currency code. Required where discount type is [DiscountType::Flat] or [DiscountType::FlatPerSeat].
160    pub fn currency_code(&mut self, currency_code: CurrencyCode) -> &mut Self {
161        self.currency_code = Some(currency_code);
162        self
163    }
164
165    /// Whether this discount applies for multiple subscription billing periods (`true`) or not (`false`). If omitted, defaults to `false`.
166    pub fn recur(&mut self, recur: bool) -> &mut Self {
167        self.recur = recur;
168        self
169    }
170
171    /// Number of subscription billing periods that this discount recurs for. Requires recur. `null` if this discount recurs forever.
172    pub fn maximum_recurring_intervals(&mut self, maximum_recurring_intervals: u64) -> &mut Self {
173        self.maximum_recurring_intervals = Some(maximum_recurring_intervals);
174        self
175    }
176
177    /// Maximum number of times this discount can be redeemed. This is an overall limit for this discount, rather than a per-customer limit. `null` if this discount can be redeemed an unlimited amount of times.
178    ///
179    /// Paddle counts a usage as a redemption on a checkout, transaction, or the initial application against a subscription. Transactions created for subscription renewals, midcycle changes, and one-time charges aren't considered a redemption.
180    pub fn usage_limit(&mut self, usage_limit: u64) -> &mut Self {
181        self.usage_limit = Some(usage_limit);
182        self
183    }
184
185    /// Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices.
186    pub fn restrict_to(
187        &mut self,
188        restrict_to: impl IntoIterator<Item = impl AsRef<str>>,
189    ) -> &mut Self {
190        self.restrict_to = Some(
191            restrict_to
192                .into_iter()
193                .map(|s| s.as_ref().to_string())
194                .collect(),
195        );
196        self
197    }
198
199    /// Datetime when this discount expires. Discount can no longer be redeemed after this date has elapsed. `null` if this discount can be redeemed forever.
200    ///
201    /// Expired discounts can't be redeemed against transactions or checkouts, but can be applied when updating subscriptions.
202    pub fn expires_at(&mut self, expires_at: DateTime<Utc>) -> &mut Self {
203        self.expires_at = Some(expires_at);
204        self
205    }
206
207    /// Set custom data for this discount.
208    pub fn custom_data(&mut self, custom_data: HashMap<String, String>) -> &mut Self {
209        self.custom_data = Some(custom_data);
210        self
211    }
212
213    /// Send the request to Paddle and return the response.
214    pub async fn send(&self) -> Result<Discount> {
215        self.client.send(self, Method::POST, "/discounts").await
216    }
217}
218
219/// Request builder for fetching a single discount from Paddle API.
220#[derive(Serialize)]
221pub struct DiscountGet<'a> {
222    #[serde(skip)]
223    client: &'a Paddle,
224    #[serde(skip)]
225    discount_id: DiscountID,
226}
227
228impl<'a> DiscountGet<'a> {
229    pub fn new(client: &'a Paddle, discount_id: impl Into<DiscountID>) -> Self {
230        Self {
231            client,
232            discount_id: discount_id.into(),
233        }
234    }
235
236    /// Send the request to Paddle and return the response.
237    pub async fn send(&self) -> Result<Discount> {
238        self.client
239            .send(
240                self,
241                Method::GET,
242                &format!("/discounts/{}", self.discount_id.as_ref()),
243            )
244            .await
245    }
246}
247
248/// Request builder for updating discounts in Paddle API.
249#[skip_serializing_none]
250#[derive(Serialize)]
251pub struct DiscountUpdate<'a> {
252    #[serde(skip)]
253    client: &'a Paddle,
254    #[serde(skip)]
255    discount_id: DiscountID,
256    status: Option<Status>,
257    description: Option<String>,
258    enabled_for_checkout: Option<bool>,
259    code: Option<String>,
260    r#type: Option<DiscountType>,
261    amount: Option<String>,
262    currency_code: Option<CurrencyCode>,
263    recur: Option<bool>,
264    maximum_recurring_intervals: Option<u64>,
265    usage_limit: Option<u64>,
266    restrict_to: Option<Vec<String>>,
267    expires_at: Option<DateTime<Utc>>,
268    custom_data: Option<HashMap<String, String>>,
269}
270
271impl<'a> DiscountUpdate<'a> {
272    pub fn new(client: &'a Paddle, discount_id: impl Into<DiscountID>) -> Self {
273        Self {
274            client,
275            discount_id: discount_id.into(),
276            status: None,
277            description: None,
278            enabled_for_checkout: None,
279            code: None,
280            r#type: None,
281            amount: None,
282            currency_code: None,
283            recur: None,
284            maximum_recurring_intervals: None,
285            usage_limit: None,
286            restrict_to: None,
287            expires_at: None,
288            custom_data: None,
289        }
290    }
291
292    /// Whether this entity can be used in Paddle.
293    pub fn status(&mut self, status: Status) -> &mut Self {
294        self.status = Some(status);
295        self
296    }
297
298    /// Short description for this discount for your reference. Not shown to customers.
299    pub fn description(&mut self, description: impl Into<String>) -> &mut Self {
300        self.description = Some(description.into());
301        self
302    }
303
304    /// Whether this discount can be redeemed by customers at checkout (true) or not (false).
305    pub fn enabled_for_checkout(&mut self, enabled: bool) -> &mut Self {
306        self.enabled_for_checkout = Some(enabled);
307        self
308    }
309
310    /// Unique code that customers can use to redeem this discount at checkout. Not case-sensitive.
311    pub fn code(&mut self, code: impl Into<String>) -> &mut Self {
312        self.code = Some(code.into());
313        self
314    }
315
316    /// Type of discount. Determines how this discount impacts the checkout or transaction total.
317    pub fn discount_type(&mut self, discount_type: DiscountType) -> &mut Self {
318        self.r#type = Some(discount_type);
319        self
320    }
321
322    /// Amount to discount by. For percentage discounts, must be an amount between 0.01 and 100. For flat and flat_per_seat discounts, amount in the lowest denomination for a currency.
323    pub fn amount(&mut self, amount: impl Into<String>) -> &mut Self {
324        self.amount = Some(amount.into());
325        self
326    }
327
328    /// Supported three-letter ISO 4217 currency code. Required where discount type is [DiscountType::Flat] or [DiscountType::FlatPerSeat].
329    pub fn currency_code(&mut self, currency_code: CurrencyCode) -> &mut Self {
330        self.currency_code = Some(currency_code);
331        self
332    }
333
334    /// Whether this discount applies for multiple subscription billing periods (`true`) or not (`false`). If omitted, defaults to `false`.
335    pub fn recur(&mut self, recur: bool) -> &mut Self {
336        self.recur = Some(recur);
337        self
338    }
339
340    /// Number of subscription billing periods that this discount recurs for. Requires `recur`. `null` if this discount recurs forever.
341    ///
342    /// Subscription renewals, midcycle changes, and one-time charges billed to a subscription aren't considered a redemption. `times_used` is not incremented in these cases.
343    pub fn maximum_recurring_intervals(&mut self, maximum_recurring_intervals: u64) -> &mut Self {
344        self.maximum_recurring_intervals = Some(maximum_recurring_intervals);
345        self
346    }
347
348    /// Maximum number of times this discount can be redeemed. This is an overall limit for this discount, rather than a per-customer limit. `null` if this discount can be redeemed an unlimited amount of times.
349    pub fn usage_limit(&mut self, usage_limit: u64) -> &mut Self {
350        self.usage_limit = Some(usage_limit);
351        self
352    }
353
354    /// Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices.
355    pub fn restrict_to(
356        &mut self,
357        restrict_to: impl IntoIterator<Item = impl AsRef<str>>,
358    ) -> &mut Self {
359        self.restrict_to = Some(
360            restrict_to
361                .into_iter()
362                .map(|s| s.as_ref().to_string())
363                .collect(),
364        );
365        self
366    }
367
368    /// Datetime when this discount expires. Discount can no longer be redeemed after this date has elapsed. `null` if this discount can be redeemed forever.
369    ///
370    /// Expired discounts can't be redeemed against transactions or checkouts, but can be applied when updating subscriptions.
371    pub fn expires_at(&mut self, expires_at: DateTime<Utc>) -> &mut Self {
372        self.expires_at = Some(expires_at);
373        self
374    }
375
376    /// Set custom data for this discount.
377    pub fn custom_data(&mut self, custom_data: HashMap<String, String>) -> &mut Self {
378        self.custom_data = Some(custom_data);
379        self
380    }
381
382    /// Send the request to Paddle and return the response.
383    pub async fn send(&self) -> Result<Discount> {
384        self.client
385            .send(
386                self,
387                Method::PATCH,
388                &format!("/discounts/{}", self.discount_id.as_ref()),
389            )
390            .await
391    }
392}