1use futures_core::Stream;
17use futures_util::stream::TryStreamExt;
18use ordered_float::OrderedFloat;
19use reqwest::Method;
20use serde::{Deserialize, Serialize};
21use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
22use time::OffsetDateTime;
23
24use crate::client::customers::{Customer, CustomerId, CustomerResponse};
25use crate::client::marketplaces::ExternalMarketplace;
26use crate::client::plans::{Plan, PlanId};
27use crate::client::Client;
28use crate::config::ListParams;
29use crate::error::Error;
30use crate::util::StrIteratorExt;
31
32const SUBSCRIPTIONS_PATH: [&str; 1] = ["subscriptions"];
33
34#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
36pub struct CreateSubscriptionRequest<'a> {
37 #[serde(flatten)]
40 pub customer_id: CustomerId<'a>,
41 #[serde(flatten)]
45 pub plan_id: PlanId<'a>,
46 #[serde(skip_serializing_if = "Option::is_none")]
51 #[serde(with = "time::serde::rfc3339::option")]
52 pub start_date: Option<OffsetDateTime>,
53 #[serde(skip_serializing_if = "Option::is_none")]
56 pub external_marketplace: Option<SubscriptionExternalMarketplaceRequest<'a>>,
57 #[serde(skip_serializing_if = "Option::is_none")]
61 pub align_billing_with_subscription_start_date: Option<bool>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub minimum_amount: Option<&'a str>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub net_terms: Option<i64>,
68 #[serde(skip_serializing_if = "Option::is_none")]
73 pub auto_collection: Option<bool>,
74 #[serde(skip_serializing_if = "Option::is_none")]
78 pub default_invoice_memo: Option<&'a str>,
79 #[serde(skip_serializing)]
83 pub idempotency_key: Option<&'a str>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
87pub struct SubscriptionExternalMarketplaceRequest<'a> {
88 #[serde(rename = "external_marketplace")]
90 pub kind: ExternalMarketplace,
91 #[serde(rename = "external_marketplace_reporting_id")]
93 pub reporting_id: &'a str,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
98pub struct Subscription<C = Customer> {
99 pub id: String,
101 pub customer: C,
103 pub plan: Plan,
105 #[serde(with = "time::serde::rfc3339")]
107 pub start_date: OffsetDateTime,
108 #[serde(with = "time::serde::rfc3339::option")]
110 pub end_date: Option<OffsetDateTime>,
111 pub status: Option<SubscriptionStatus>,
113 #[serde(with = "time::serde::rfc3339::option")]
116 pub current_billing_period_start_date: Option<OffsetDateTime>,
117 #[serde(with = "time::serde::rfc3339::option")]
120 pub current_billing_period_end_date: Option<OffsetDateTime>,
121 pub active_plan_phase_order: Option<i64>,
124 pub fixed_fee_quantity_schedule: Vec<SubscriptionFixedFee>,
126 pub net_terms: i64,
132 pub auto_collection: bool,
137 pub default_invoice_memo: String,
141 #[serde(with = "time::serde::rfc3339")]
143 pub created_at: OffsetDateTime,
144}
145
146#[non_exhaustive]
148#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize_enum_str, Serialize_enum_str)]
149#[serde(rename_all = "snake_case")]
150pub enum SubscriptionStatus {
151 Active,
153 Ended,
155 Upcoming,
157 #[serde(other)]
159 Other(String),
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
164pub struct SubscriptionFixedFee {
165 #[serde(with = "time::serde::rfc3339")]
167 pub start_date: OffsetDateTime,
168 #[serde(with = "time::serde::rfc3339::option")]
170 pub end_date: Option<OffsetDateTime>,
171 pub price_id: String,
173 pub quantity: OrderedFloat<f64>,
175}
176
177#[derive(Debug, Clone)]
179pub struct SubscriptionListParams<'a> {
180 inner: ListParams,
181 filter: Option<CustomerId<'a>>,
182}
183
184impl<'a> Default for SubscriptionListParams<'a> {
185 fn default() -> SubscriptionListParams<'a> {
186 SubscriptionListParams::DEFAULT
187 }
188}
189
190impl<'a> SubscriptionListParams<'a> {
191 pub const DEFAULT: SubscriptionListParams<'static> = SubscriptionListParams {
195 inner: ListParams::DEFAULT,
196 filter: None,
197 };
198
199 pub const fn page_size(mut self, page_size: u64) -> Self {
203 self.inner = self.inner.page_size(page_size);
204 self
205 }
206
207 pub const fn customer_id(mut self, filter: CustomerId<'a>) -> Self {
209 self.filter = Some(filter);
210 self
211 }
212}
213
214impl Client {
215 pub fn list_subscriptions(
220 &self,
221 params: &SubscriptionListParams,
222 ) -> impl Stream<Item = Result<Subscription, Error>> + '_ {
223 let req = self.build_request(Method::GET, SUBSCRIPTIONS_PATH);
224 let req = match params.filter {
225 None => req,
226 Some(CustomerId::Orb(id)) => req.query(&[("customer_id", id)]),
227 Some(CustomerId::External(id)) => req.query(&[("external_customer_id", id)]),
228 };
229 self.stream_paginated_request(¶ms.inner, req)
230 .try_filter_map(|subscription: Subscription<CustomerResponse>| async move {
231 match subscription.customer {
232 CustomerResponse::Normal(customer) => Ok(Some(Subscription {
233 id: subscription.id,
234 customer,
235 plan: subscription.plan,
236 start_date: subscription.start_date,
237 end_date: subscription.end_date,
238 status: subscription.status,
239 current_billing_period_start_date: subscription
240 .current_billing_period_start_date,
241 current_billing_period_end_date: subscription
242 .current_billing_period_end_date,
243 active_plan_phase_order: subscription.active_plan_phase_order,
244 fixed_fee_quantity_schedule: subscription.fixed_fee_quantity_schedule,
245 net_terms: subscription.net_terms,
246 auto_collection: subscription.auto_collection,
247 default_invoice_memo: subscription.default_invoice_memo,
248 created_at: subscription.created_at,
249 })),
250 CustomerResponse::Deleted {
251 id: _,
252 deleted: true,
253 } => Ok(None),
254 CustomerResponse::Deleted { id, deleted: false } => {
255 Err(Error::UnexpectedResponse {
256 detail: format!(
257 "customer {id} used deleted response shape \
258 but deleted field was `false`"
259 ),
260 })
261 }
262 }
263 })
264 }
265
266 pub async fn create_subscription(
268 &self,
269 subscription: &CreateSubscriptionRequest<'_>,
270 ) -> Result<Subscription, Error> {
271 let mut req = self.build_request(Method::POST, SUBSCRIPTIONS_PATH);
272 if let Some(key) = subscription.idempotency_key {
273 req = req.header("Idempotency-Key", key);
274 }
275
276 let req = req.json(subscription);
277 let res = self.send_request(req).await?;
278 Ok(res)
279 }
280
281 pub async fn get_subscription(&self, id: &str) -> Result<Subscription, Error> {
283 let req = self.build_request(Method::GET, SUBSCRIPTIONS_PATH.chain_one(id));
284 let res = self.send_request(req).await?;
285 Ok(res)
286 }
287
288 }