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