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