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, StripeTaxIdCollection,
 31    UpdateCustomerParams, 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            tax_id_collection: value.tax_id_collection.map(Into::into),
452            ..Default::default()
453        })
454    }
455}
456
457impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
458    fn from(value: StripeCheckoutSessionMode) -> Self {
459        match value {
460            StripeCheckoutSessionMode::Payment => Self::Payment,
461            StripeCheckoutSessionMode::Setup => Self::Setup,
462            StripeCheckoutSessionMode::Subscription => Self::Subscription,
463        }
464    }
465}
466
467impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
468    fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
469        Self {
470            price: value.price,
471            quantity: value.quantity,
472            ..Default::default()
473        }
474    }
475}
476
477impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
478    fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
479        match value {
480            StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
481            StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
482        }
483    }
484}
485
486impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
487    fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
488        Self {
489            trial_period_days: value.trial_period_days,
490            trial_settings: value.trial_settings.map(Into::into),
491            metadata: value.metadata,
492            ..Default::default()
493        }
494    }
495}
496
497impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
498    fn from(value: StripeSubscriptionTrialSettings) -> Self {
499        Self {
500            end_behavior: value.end_behavior.into(),
501        }
502    }
503}
504
505impl From<StripeSubscriptionTrialSettingsEndBehavior>
506    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
507{
508    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
509        Self {
510            missing_payment_method: value.missing_payment_method.into(),
511        }
512    }
513}
514
515impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
516    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
517{
518    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
519        match value {
520            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
521            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
522                Self::CreateInvoice
523            }
524            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
525        }
526    }
527}
528
529impl From<CheckoutSession> for StripeCheckoutSession {
530    fn from(value: CheckoutSession) -> Self {
531        Self { url: value.url }
532    }
533}
534
535impl From<StripeBillingAddressCollection> for stripe::CheckoutSessionBillingAddressCollection {
536    fn from(value: StripeBillingAddressCollection) -> Self {
537        match value {
538            StripeBillingAddressCollection::Auto => {
539                stripe::CheckoutSessionBillingAddressCollection::Auto
540            }
541            StripeBillingAddressCollection::Required => {
542                stripe::CheckoutSessionBillingAddressCollection::Required
543            }
544        }
545    }
546}
547
548impl From<StripeCustomerUpdateAddress> for stripe::CreateCheckoutSessionCustomerUpdateAddress {
549    fn from(value: StripeCustomerUpdateAddress) -> Self {
550        match value {
551            StripeCustomerUpdateAddress::Auto => {
552                stripe::CreateCheckoutSessionCustomerUpdateAddress::Auto
553            }
554            StripeCustomerUpdateAddress::Never => {
555                stripe::CreateCheckoutSessionCustomerUpdateAddress::Never
556            }
557        }
558    }
559}
560
561impl From<StripeCustomerUpdateName> for stripe::CreateCheckoutSessionCustomerUpdateName {
562    fn from(value: StripeCustomerUpdateName) -> Self {
563        match value {
564            StripeCustomerUpdateName::Auto => stripe::CreateCheckoutSessionCustomerUpdateName::Auto,
565            StripeCustomerUpdateName::Never => {
566                stripe::CreateCheckoutSessionCustomerUpdateName::Never
567            }
568        }
569    }
570}
571
572impl From<StripeCustomerUpdateShipping> for stripe::CreateCheckoutSessionCustomerUpdateShipping {
573    fn from(value: StripeCustomerUpdateShipping) -> Self {
574        match value {
575            StripeCustomerUpdateShipping::Auto => {
576                stripe::CreateCheckoutSessionCustomerUpdateShipping::Auto
577            }
578            StripeCustomerUpdateShipping::Never => {
579                stripe::CreateCheckoutSessionCustomerUpdateShipping::Never
580            }
581        }
582    }
583}
584
585impl From<StripeCustomerUpdate> for stripe::CreateCheckoutSessionCustomerUpdate {
586    fn from(value: StripeCustomerUpdate) -> Self {
587        stripe::CreateCheckoutSessionCustomerUpdate {
588            address: value.address.map(Into::into),
589            name: value.name.map(Into::into),
590            shipping: value.shipping.map(Into::into),
591        }
592    }
593}
594
595impl From<StripeTaxIdCollection> for stripe::CreateCheckoutSessionTaxIdCollection {
596    fn from(value: StripeTaxIdCollection) -> Self {
597        stripe::CreateCheckoutSessionTaxIdCollection {
598            enabled: value.enabled,
599        }
600    }
601}