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