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