paddle_rust_sdk/lib.rs
1//! # Paddle API Client
2//!
3//! This is a Rust client for the Paddle API, which allows you to interact with Paddle's services.
4
5use std::fmt::Display;
6
7use entities::CustomerAuthenticationToken;
8use reqwest::{header::CONTENT_TYPE, IntoUrl, Method, Url};
9use serde::{de::DeserializeOwned, Serialize};
10
11pub mod entities;
12pub mod enums;
13pub mod error;
14pub mod ids;
15
16pub mod addresses;
17pub mod customers;
18pub mod discounts;
19pub mod prices;
20pub mod products;
21
22pub mod response;
23
24use enums::{CountryCodeSupported, CurrencyCode, DiscountType, TaxCategory};
25use ids::{AddressID, CustomerID, DiscountID, PriceID, ProductID};
26
27use response::{ErrorResponse, Response, SuccessResponse};
28
29use error::{Error, PaddleError};
30
31type Result<T> = std::result::Result<SuccessResponse<T>, Error>;
32
33/// Paddle API client
34///
35/// This struct is used to create a new Paddle client instance.
36#[derive(Clone, Debug)]
37pub struct Paddle {
38 base_url: Url,
39 api_key: String,
40}
41
42impl Paddle {
43 pub const PRODUCTION: &'static str = "https://api.paddle.com";
44 pub const SANDBOX: &'static str = "https://sandbox-api.paddle.com";
45
46 /// Creates a new Paddle client instance.
47 ///
48 /// Example:
49 /// ```
50 /// use paddle::Paddle;
51 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
52 /// ```
53 #[allow(clippy::result_large_err)]
54 pub fn new(
55 api_key: impl Into<String>,
56 base_url: impl IntoUrl,
57 ) -> std::result::Result<Self, Error> {
58 Ok(Self {
59 base_url: base_url.into_url()?,
60 api_key: api_key.into(),
61 })
62 }
63
64 /// Returns a request builder for fetching products. Use the after method to page through results.
65 ///
66 /// By default, Paddle returns products that are active. Use the status method to return products that are archived.
67 /// Use the include method to include related price entities in the response.
68 ///
69 /// # Example:
70 /// ```
71 /// use paddle::Paddle;
72 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
73 /// let products = client.products_list().send().await.unwrap();
74 /// ```
75 pub fn products_list(&self) -> products::ProductsList {
76 products::ProductsList::new(self)
77 }
78
79 /// Returns a request builder for creating a new product.
80 ///
81 /// # Example:
82 /// ```
83 /// use paddle::Paddle;
84 /// use paddle::enums::TaxCategory;
85 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
86 /// let product = client.products_create("My Product", TaxCategory::Standard).send().await.unwrap();
87 /// ```
88 pub fn product_create(
89 &self,
90 name: impl Into<String>,
91 tax_category: TaxCategory,
92 ) -> products::ProductCreate {
93 products::ProductCreate::new(self, name, tax_category)
94 }
95
96 /// Returns a request builder for fetching a specific product.
97 ///
98 /// # Example:
99 /// ```
100 /// use paddle::Paddle;
101 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
102 /// let product = client.product_get("pro_01jqx9rd...").send().await.unwrap();
103 /// ```
104 pub fn product_get(&self, product_id: impl Into<ProductID>) -> products::ProductGet {
105 products::ProductGet::new(self, product_id)
106 }
107
108 /// Returns a request builder for updating a specific product.
109 ///
110 /// # Example:
111 /// ```
112 /// use paddle::Paddle;
113 /// use paddle::enums::TaxCategory;
114 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
115 /// let product = client.product_update("pro_01jqx9rd...").name("My New Name").send().await.unwrap();
116 /// ```
117 pub fn product_update(&self, product_id: impl Into<ProductID>) -> products::ProductUpdate {
118 products::ProductUpdate::new(self, product_id)
119 }
120
121 /// Returns a request builder listing prices
122 ///
123 /// # Example:
124 /// ```
125 /// use paddle::Paddle;
126 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
127 /// let prices = client.prices_list().send().await.unwrap();
128 /// ```
129 pub fn prices_list(&self) -> prices::PricesList {
130 prices::PricesList::new(self)
131 }
132
133 /// Returns a request builder for creating a new price.
134 ///
135 /// * `product_id` - Paddle ID for the product that this price is for.
136 /// * `description` - Internal description for this price, not shown to customers. Typically notes for your team.
137 /// * `amount` - Amount of the price in the smallest unit of the currency (e.g. 1000 cents for 10 USD).
138 /// * `currency` - Currency code for the price. Use the [CurrencyCode] enum to specify the currency.
139 ///
140 /// # Example:
141 /// ```
142 /// use paddle::Paddle;
143 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
144 /// let price = client.price_create("pro_01jqx9rd...", "Low price", 19.99, CurrencyCode::USD).send().await.unwrap();
145 /// ```
146 pub fn price_create(
147 &self,
148 product_id: impl Into<ProductID>,
149 description: impl Into<String>,
150 amount: u64,
151 currency: CurrencyCode,
152 ) -> prices::PricesCreate {
153 prices::PricesCreate::new(self, product_id, description, amount, currency)
154 }
155
156 /// Returns a request builder for fetching a specific price by id.
157 ///
158 /// # Example:
159 /// ```
160 /// use paddle::Paddle;
161 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
162 /// let price = client.price_get("price_01jqx9rd...").send().await.unwrap();
163 /// ```
164 pub fn price_get(&self, price_id: impl Into<PriceID>) -> prices::PriceGet {
165 prices::PriceGet::new(self, price_id)
166 }
167
168 /// Returns a request builder for updating a specific price.
169 ///
170 /// # Example:
171 /// ```
172 /// use paddle::Paddle;
173 /// use paddle::enums::TaxCategory;
174 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
175 /// let price = client.price_update("pri_01jqxv...").name("Updated Name").send().await.unwrap();
176 /// ```
177 pub fn price_update(&self, price_id: impl Into<PriceID>) -> prices::PriceUpdate {
178 prices::PriceUpdate::new(self, price_id)
179 }
180
181 /// Returns a request builder for fetching discounts.
182 ///
183 /// # Example:
184 /// ```
185 /// use paddle::Paddle;
186 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
187 /// let discounts = client.discounts_list().send().await.unwrap();
188 /// ```
189 pub fn discounts_list(&self) -> discounts::DiscountsList {
190 discounts::DiscountsList::new(self)
191 }
192
193 /// Returns a request builder for creating discounts.
194 ///
195 /// # Example:
196 /// ```
197 /// use paddle::Paddle;
198 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
199 /// let discount = client.discount_create("15", "Winter Holidays", DiscountType::Percentage).send().await.unwrap();
200 /// ```
201 pub fn discount_create(
202 &self,
203 amount: impl Into<String>,
204 description: impl Into<String>,
205 discount_type: DiscountType,
206 ) -> discounts::DiscountCreate {
207 discounts::DiscountCreate::new(self, amount, description, discount_type)
208 }
209
210 /// Returns a request builder for fetching a specific discount by id.
211 ///
212 /// # Example:
213 /// ```
214 /// use paddle::Paddle;
215 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
216 /// let discount = client.discount_get("dsc_01jqzpbmnq...").send().await.unwrap();
217 /// ```
218 pub fn discount_get(&self, discount_id: impl Into<DiscountID>) -> discounts::DiscountGet {
219 discounts::DiscountGet::new(self, discount_id)
220 }
221
222 /// Returns a request builder for creating discounts.
223 ///
224 /// # Example:
225 /// ```
226 /// use paddle::Paddle;
227 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
228 /// let discount = client.discount_update("dsc_01jqzpbmnq...").amount("18").send().await.unwrap();
229 /// ```
230 pub fn discount_update(&self, discount_id: impl Into<DiscountID>) -> discounts::DiscountUpdate {
231 discounts::DiscountUpdate::new(self, discount_id)
232 }
233
234 /// Returns a request builder for fetching customers. Use the after method to page through results.
235 ///
236 /// By default, Paddle returns customers that are `active`. Use the status query parameter to return customers that are archived.
237 ///
238 /// # Example:
239 /// ```
240 /// use paddle::Paddle;
241 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
242 /// let customers = client.customers_list().send().await.unwrap();
243 /// ```
244 pub fn customers_list(&self) -> customers::CustomersList {
245 customers::CustomersList::new(self)
246 }
247
248 /// Returns a request builder for creating a new customer.
249 ///
250 /// # Example:
251 /// ```
252 /// use paddle::Paddle;
253 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
254 /// let customers = client.customer_create("test@example.com").send().await.unwrap();
255 /// ```
256 pub fn customer_create(&self, email: impl Into<String>) -> customers::CustomerCreate {
257 customers::CustomerCreate::new(self, email.into())
258 }
259
260 /// Returns a request builder for fetching a specific customer by id.
261 ///
262 /// # Example:
263 /// ```
264 /// use paddle::Paddle;
265 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
266 /// let discount = client.customer_get("ctm_01jqztc78e1xfdgwhcgjzdrvgd").send().await.unwrap();
267 /// ```
268 pub fn customer_get(&self, customer_id: impl Into<CustomerID>) -> customers::CustomerGet {
269 customers::CustomerGet::new(self, customer_id)
270 }
271
272 /// Returns a request builder for updating customer data.
273 ///
274 /// # Example:
275 /// ```
276 /// use paddle::Paddle;
277 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
278 /// let discount = client.customer_update("ctm_01jqztc78e1xfdgwhcgjzdrvgd").email("new_email@example.com").send().await.unwrap();
279 /// ```
280 pub fn customer_update(&self, customer_id: impl Into<CustomerID>) -> customers::CustomerUpdate {
281 customers::CustomerUpdate::new(self, customer_id)
282 }
283
284 /// Returns a request builder for fetching a list of credit balances for each currency for a customer.
285 ///
286 /// Each balance has three totals:
287 ///
288 /// * `available` - total available to use.
289 /// * `reserved` - total temporarily reserved for billed transactions.
290 /// * `used` - total amount of credit used.
291 ///
292 /// Credit is added to the available total initially. When used, it moves to the used total.
293 ///
294 /// The reserved total is used when a credit balance is applied to a transaction that's marked as billed, like when working with an issued invoice. It's not available for other transactions at this point, but isn't considered used until the transaction is completed. If a billed transaction is canceled, any reserved credit moves back to available.
295 ///
296 /// Credit balances are created automatically by Paddle when you take an action that results in Paddle creating a credit for a customer, like making prorated changes to a subscription. An empty data array is returned where a customer has no credit balances.
297 ///
298 /// The response is not paginated.
299 ///
300 /// # Example:
301 /// ```
302 /// use paddle::Paddle;
303 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
304 /// let discount = client.customer_credit_balances("ctm_01jqztc78e1xfdgwhcgjzdrvgd").send().await.unwrap();
305 /// ```
306 pub fn customer_credit_balances(
307 &self,
308 customer_id: impl Into<CustomerID>,
309 ) -> customers::CustomerCreditBalances {
310 customers::CustomerCreditBalances::new(self, customer_id)
311 }
312
313 /// Generates an authentication token for a customer.
314 ///
315 /// You can pass a generated authentication token to Paddle.js when opening a checkout to let customers work with saved payment methods.
316 ///
317 /// Authentication tokens are temporary and shouldn't be cached. They're valid until the expires_at date returned in the response.
318 pub async fn generate_auth_token(
319 &self,
320 customer_id: impl Display,
321 ) -> Result<CustomerAuthenticationToken> {
322 let client = reqwest::Client::new();
323
324 let url = format!("{}customers/{}/auth-token", self.base_url, customer_id);
325
326 let res: Response<_> = client
327 .post(url)
328 .bearer_auth(self.api_key.clone())
329 .send()
330 .await?
331 .json()
332 .await?;
333
334 match res {
335 Response::Success(success) => Ok(success),
336 Response::Error(error) => Err(Error::Paddle(error)),
337 }
338 }
339
340 /// Returns a request builder for fetching customers addresses.
341 ///
342 /// By default, Paddle returns addresses that are `active`. Use the status query parameter to return addresses that are archived.
343 ///
344 /// # Example:
345 /// ```
346 /// use paddle::Paddle;
347 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
348 /// let customers = client.addresses_list("ctm_01jqztc78e1xfdgwhcgjzdrvgd").send().await.unwrap();
349 /// ```
350 pub fn addresses_list(&self, customer_id: impl Into<CustomerID>) -> addresses::AddressesList {
351 addresses::AddressesList::new(self, customer_id)
352 }
353
354 /// Returns a request builder for creating a new customer address.
355 ///
356 /// # Example:
357 /// ```
358 /// use paddle::Paddle;
359 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
360 /// let customers = client.address_create("ctm_01jqztc78e1xfdgwhcgjzdrvgd", CountryCodeSupported::US).send().await.unwrap();
361 /// ```
362 pub fn address_create(
363 &self,
364 customer_id: impl Into<CustomerID>,
365 country_code: CountryCodeSupported,
366 ) -> addresses::AddressCreate {
367 addresses::AddressCreate::new(self, customer_id, country_code)
368 }
369
370 /// Returns a request builder for getting an address for a customer using its ID and related customer ID.
371 ///
372 /// # Example:
373 /// ```
374 /// use paddle::Paddle;
375 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
376 /// let customers = client.address_get("ctm_01jqztc78e1xfdgwhcgjzdrvgd", "add_01hv8gwdfkw5z6d1yy6pa3xyrz").send().await.unwrap();
377 /// ```
378 pub fn address_get(
379 &self,
380 customer_id: impl Into<CustomerID>,
381 address_id: impl Into<AddressID>,
382 ) -> addresses::AddressGet {
383 addresses::AddressGet::new(self, customer_id, address_id)
384 }
385
386 /// Returns a request builder for updating an address for a customer using its ID and related customer ID.
387 ///
388 /// # Example:
389 /// ```
390 /// use paddle::Paddle;
391 /// let client = Paddle::new("your_api_key", Paddle::SANDBOX).unwrap();
392 /// let customers = client.address_update("add_01hv8gwdfkw5z6d1yy6pa3xyrz", "add_01hv8gwdfkw5z6d1yy6pa3xyrz").first_line("Test").send().await.unwrap();
393 /// ```
394 pub fn address_update(
395 &self,
396 customer_id: impl Into<CustomerID>,
397 address_id: impl Into<AddressID>,
398 ) -> addresses::AddressUpdate {
399 addresses::AddressUpdate::new(self, customer_id, address_id)
400 }
401
402 async fn send<T: DeserializeOwned>(
403 &self,
404 req: impl Serialize,
405 method: Method,
406 path: &str,
407 ) -> Result<T> {
408 let url = self.base_url.join(path)?;
409 let client = reqwest::Client::new();
410
411 let mut builder = client
412 .request(method.clone(), url)
413 .bearer_auth(self.api_key.clone())
414 .header(CONTENT_TYPE, "application/json; charset=utf-8");
415
416 builder = match method {
417 reqwest::Method::GET => builder.query(&req),
418 reqwest::Method::POST | reqwest::Method::PUT | reqwest::Method::PATCH => {
419 builder.json(&req)
420 }
421 _ => builder,
422 };
423
424 // let text = builder.send().await?.text().await?;
425 // println!("{}", text);
426 // todo!();
427
428 let res: Response<_> = builder.send().await?.json().await?;
429
430 match res {
431 Response::Success(success) => Ok(success),
432 Response::Error(error) => Err(Error::Paddle(error)),
433 }
434 }
435}
436
437fn comma_separated<S, T>(
438 values: &Option<Vec<T>>,
439 serializer: S,
440) -> std::result::Result<S::Ok, S::Error>
441where
442 S: serde::Serializer,
443 T: AsRef<str>,
444{
445 match values {
446 Some(values) => {
447 let values = values
448 .iter()
449 .map(|v| v.as_ref())
450 .collect::<Vec<_>>()
451 .join(",");
452
453 serializer.serialize_str(&values)
454 }
455 None => serializer.serialize_none(),
456 }
457}
458
459// fn comma_separated_enum<S, T>(
460// values: &Vec<T>,
461// serializer: S,
462// ) -> std::result::Result<S::Ok, S::Error>
463// where
464// S: serde::Serializer,
465// T: Serialize,
466// {
467// let mut serialized = vec![];
468
469// for val in values {
470// let serialized_value = serde_json::to_string(val).map_err(serde::ser::Error::custom)?;
471// serialized.push(serialized_value);
472// }
473
474// serializer.serialize_str(serialized.join(",").as_str())
475// }