1use std::str::FromStr as _;
2use std::sync::Arc;
3
4use anyhow::{Context as _, Result, anyhow};
5use async_trait::async_trait;
6use serde::Serialize;
7use stripe::{
8 CheckoutSession, CheckoutSessionMode, CheckoutSessionPaymentMethodCollection,
9 CreateCheckoutSession, CreateCheckoutSessionLineItems, CreateCheckoutSessionSubscriptionData,
10 CreateCheckoutSessionSubscriptionDataTrialSettings,
11 CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior,
12 CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod,
13 CreateCustomer, Customer, CustomerId, ListCustomers, Price, PriceId, Recurring, Subscription,
14 SubscriptionId, SubscriptionItem, SubscriptionItemId, UpdateSubscriptionItems,
15 UpdateSubscriptionTrialSettings, UpdateSubscriptionTrialSettingsEndBehavior,
16 UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
17};
18
19use crate::stripe_client::{
20 CreateCustomerParams, StripeCheckoutSession, StripeCheckoutSessionMode,
21 StripeCheckoutSessionPaymentMethodCollection, StripeClient,
22 StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
23 StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
24 StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeMeter, StripePrice,
25 StripePriceId, StripePriceRecurring, StripeSubscription, StripeSubscriptionId,
26 StripeSubscriptionItem, StripeSubscriptionItemId, StripeSubscriptionTrialSettings,
27 StripeSubscriptionTrialSettingsEndBehavior,
28 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateSubscriptionParams,
29};
30
31pub struct RealStripeClient {
32 client: Arc<stripe::Client>,
33}
34
35impl RealStripeClient {
36 pub fn new(client: Arc<stripe::Client>) -> Self {
37 Self { client }
38 }
39}
40
41#[async_trait]
42impl StripeClient for RealStripeClient {
43 async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
44 let response = Customer::list(
45 &self.client,
46 &ListCustomers {
47 email: Some(email),
48 ..Default::default()
49 },
50 )
51 .await?;
52
53 Ok(response
54 .data
55 .into_iter()
56 .map(StripeCustomer::from)
57 .collect())
58 }
59
60 async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
61 let customer = Customer::create(
62 &self.client,
63 CreateCustomer {
64 email: params.email,
65 ..Default::default()
66 },
67 )
68 .await?;
69
70 Ok(StripeCustomer::from(customer))
71 }
72
73 async fn list_subscriptions_for_customer(
74 &self,
75 customer_id: &StripeCustomerId,
76 ) -> Result<Vec<StripeSubscription>> {
77 let customer_id = customer_id.try_into()?;
78
79 let subscriptions = stripe::Subscription::list(
80 &self.client,
81 &stripe::ListSubscriptions {
82 customer: Some(customer_id),
83 status: None,
84 ..Default::default()
85 },
86 )
87 .await?;
88
89 Ok(subscriptions
90 .data
91 .into_iter()
92 .map(StripeSubscription::from)
93 .collect())
94 }
95
96 async fn get_subscription(
97 &self,
98 subscription_id: &StripeSubscriptionId,
99 ) -> Result<StripeSubscription> {
100 let subscription_id = subscription_id.try_into()?;
101
102 let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
103
104 Ok(StripeSubscription::from(subscription))
105 }
106
107 async fn create_subscription(
108 &self,
109 params: StripeCreateSubscriptionParams,
110 ) -> Result<StripeSubscription> {
111 let customer_id = params.customer.try_into()?;
112
113 let mut create_subscription = stripe::CreateSubscription::new(customer_id);
114 create_subscription.items = Some(
115 params
116 .items
117 .into_iter()
118 .map(|item| stripe::CreateSubscriptionItems {
119 price: item.price.map(|price| price.to_string()),
120 quantity: item.quantity,
121 ..Default::default()
122 })
123 .collect(),
124 );
125
126 let subscription = Subscription::create(&self.client, create_subscription).await?;
127
128 Ok(StripeSubscription::from(subscription))
129 }
130
131 async fn update_subscription(
132 &self,
133 subscription_id: &StripeSubscriptionId,
134 params: UpdateSubscriptionParams,
135 ) -> Result<()> {
136 let subscription_id = subscription_id.try_into()?;
137
138 stripe::Subscription::update(
139 &self.client,
140 &subscription_id,
141 stripe::UpdateSubscription {
142 items: params.items.map(|items| {
143 items
144 .into_iter()
145 .map(|item| UpdateSubscriptionItems {
146 price: item.price.map(|price| price.to_string()),
147 ..Default::default()
148 })
149 .collect()
150 }),
151 trial_settings: params.trial_settings.map(Into::into),
152 ..Default::default()
153 },
154 )
155 .await?;
156
157 Ok(())
158 }
159
160 async fn list_prices(&self) -> Result<Vec<StripePrice>> {
161 let response = stripe::Price::list(
162 &self.client,
163 &stripe::ListPrices {
164 limit: Some(100),
165 ..Default::default()
166 },
167 )
168 .await?;
169
170 Ok(response.data.into_iter().map(StripePrice::from).collect())
171 }
172
173 async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
174 #[derive(Serialize)]
175 struct Params {
176 #[serde(skip_serializing_if = "Option::is_none")]
177 limit: Option<u64>,
178 }
179
180 let response = self
181 .client
182 .get_query::<stripe::List<StripeMeter>, _>(
183 "/billing/meters",
184 Params { limit: Some(100) },
185 )
186 .await?;
187
188 Ok(response.data)
189 }
190
191 async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
192 let identifier = params.identifier;
193 match self.client.post_form("/billing/meter_events", params).await {
194 Ok(event) => Ok(event),
195 Err(stripe::StripeError::Stripe(error)) => {
196 if error.http_status == 400
197 && error
198 .message
199 .as_ref()
200 .map_or(false, |message| message.contains(identifier))
201 {
202 Ok(())
203 } else {
204 Err(anyhow!(stripe::StripeError::Stripe(error)))
205 }
206 }
207 Err(error) => Err(anyhow!(error)),
208 }
209 }
210
211 async fn create_checkout_session(
212 &self,
213 params: StripeCreateCheckoutSessionParams<'_>,
214 ) -> Result<StripeCheckoutSession> {
215 let params = params.try_into()?;
216 let session = CheckoutSession::create(&self.client, params).await?;
217
218 Ok(session.into())
219 }
220}
221
222impl From<CustomerId> for StripeCustomerId {
223 fn from(value: CustomerId) -> Self {
224 Self(value.as_str().into())
225 }
226}
227
228impl TryFrom<StripeCustomerId> for CustomerId {
229 type Error = anyhow::Error;
230
231 fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
232 Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
233 }
234}
235
236impl TryFrom<&StripeCustomerId> for CustomerId {
237 type Error = anyhow::Error;
238
239 fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
240 Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
241 }
242}
243
244impl From<Customer> for StripeCustomer {
245 fn from(value: Customer) -> Self {
246 StripeCustomer {
247 id: value.id.into(),
248 email: value.email,
249 }
250 }
251}
252
253impl From<SubscriptionId> for StripeSubscriptionId {
254 fn from(value: SubscriptionId) -> Self {
255 Self(value.as_str().into())
256 }
257}
258
259impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
260 type Error = anyhow::Error;
261
262 fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
263 Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
264 }
265}
266
267impl From<Subscription> for StripeSubscription {
268 fn from(value: Subscription) -> Self {
269 Self {
270 id: value.id.into(),
271 customer: value.customer.id().into(),
272 status: value.status,
273 current_period_start: value.current_period_start,
274 current_period_end: value.current_period_end,
275 items: value.items.data.into_iter().map(Into::into).collect(),
276 }
277 }
278}
279
280impl From<SubscriptionItemId> for StripeSubscriptionItemId {
281 fn from(value: SubscriptionItemId) -> Self {
282 Self(value.as_str().into())
283 }
284}
285
286impl From<SubscriptionItem> for StripeSubscriptionItem {
287 fn from(value: SubscriptionItem) -> Self {
288 Self {
289 id: value.id.into(),
290 price: value.price.map(Into::into),
291 }
292 }
293}
294
295impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
296 fn from(value: StripeSubscriptionTrialSettings) -> Self {
297 Self {
298 end_behavior: value.end_behavior.into(),
299 }
300 }
301}
302
303impl From<StripeSubscriptionTrialSettingsEndBehavior>
304 for UpdateSubscriptionTrialSettingsEndBehavior
305{
306 fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
307 Self {
308 missing_payment_method: value.missing_payment_method.into(),
309 }
310 }
311}
312
313impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
314 for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
315{
316 fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
317 match value {
318 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
319 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
320 Self::CreateInvoice
321 }
322 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
323 }
324 }
325}
326
327impl From<PriceId> for StripePriceId {
328 fn from(value: PriceId) -> Self {
329 Self(value.as_str().into())
330 }
331}
332
333impl TryFrom<StripePriceId> for PriceId {
334 type Error = anyhow::Error;
335
336 fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
337 Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
338 }
339}
340
341impl From<Price> for StripePrice {
342 fn from(value: Price) -> Self {
343 Self {
344 id: value.id.into(),
345 unit_amount: value.unit_amount,
346 lookup_key: value.lookup_key,
347 recurring: value.recurring.map(StripePriceRecurring::from),
348 }
349 }
350}
351
352impl From<Recurring> for StripePriceRecurring {
353 fn from(value: Recurring) -> Self {
354 Self { meter: value.meter }
355 }
356}
357
358impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
359 type Error = anyhow::Error;
360
361 fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
362 Ok(Self {
363 customer: value
364 .customer
365 .map(|customer_id| customer_id.try_into())
366 .transpose()?,
367 client_reference_id: value.client_reference_id,
368 mode: value.mode.map(Into::into),
369 line_items: value
370 .line_items
371 .map(|line_items| line_items.into_iter().map(Into::into).collect()),
372 payment_method_collection: value.payment_method_collection.map(Into::into),
373 subscription_data: value.subscription_data.map(Into::into),
374 success_url: value.success_url,
375 ..Default::default()
376 })
377 }
378}
379
380impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
381 fn from(value: StripeCheckoutSessionMode) -> Self {
382 match value {
383 StripeCheckoutSessionMode::Payment => Self::Payment,
384 StripeCheckoutSessionMode::Setup => Self::Setup,
385 StripeCheckoutSessionMode::Subscription => Self::Subscription,
386 }
387 }
388}
389
390impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
391 fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
392 Self {
393 price: value.price,
394 quantity: value.quantity,
395 ..Default::default()
396 }
397 }
398}
399
400impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
401 fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
402 match value {
403 StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
404 StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
405 }
406 }
407}
408
409impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
410 fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
411 Self {
412 trial_period_days: value.trial_period_days,
413 trial_settings: value.trial_settings.map(Into::into),
414 metadata: value.metadata,
415 ..Default::default()
416 }
417 }
418}
419
420impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
421 fn from(value: StripeSubscriptionTrialSettings) -> Self {
422 Self {
423 end_behavior: value.end_behavior.into(),
424 }
425 }
426}
427
428impl From<StripeSubscriptionTrialSettingsEndBehavior>
429 for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
430{
431 fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
432 Self {
433 missing_payment_method: value.missing_payment_method.into(),
434 }
435 }
436}
437
438impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
439 for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
440{
441 fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
442 match value {
443 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
444 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
445 Self::CreateInvoice
446 }
447 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
448 }
449 }
450}
451
452impl From<CheckoutSession> for StripeCheckoutSession {
453 fn from(value: CheckoutSession) -> Self {
454 Self { url: value.url }
455 }
456}