real_stripe_client.rs

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