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::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        let identifier = params.identifier;
217        match self.client.post_form("/billing/meter_events", params).await {
218            Ok(event) => Ok(event),
219            Err(stripe::StripeError::Stripe(error)) => {
220                if error.http_status == 400
221                    && error
222                        .message
223                        .as_ref()
224                        .map_or(false, |message| message.contains(identifier))
225                {
226                    Ok(())
227                } else {
228                    Err(anyhow!(stripe::StripeError::Stripe(error)))
229                }
230            }
231            Err(error) => Err(anyhow!(error)),
232        }
233    }
234
235    async fn create_checkout_session(
236        &self,
237        params: StripeCreateCheckoutSessionParams<'_>,
238    ) -> Result<StripeCheckoutSession> {
239        let params = params.try_into()?;
240        let session = CheckoutSession::create(&self.client, params).await?;
241
242        Ok(session.into())
243    }
244}
245
246impl From<CustomerId> for StripeCustomerId {
247    fn from(value: CustomerId) -> Self {
248        Self(value.as_str().into())
249    }
250}
251
252impl TryFrom<StripeCustomerId> for CustomerId {
253    type Error = anyhow::Error;
254
255    fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
256        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
257    }
258}
259
260impl TryFrom<&StripeCustomerId> for CustomerId {
261    type Error = anyhow::Error;
262
263    fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
264        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
265    }
266}
267
268impl From<Customer> for StripeCustomer {
269    fn from(value: Customer) -> Self {
270        StripeCustomer {
271            id: value.id.into(),
272            email: value.email,
273        }
274    }
275}
276
277impl From<SubscriptionId> for StripeSubscriptionId {
278    fn from(value: SubscriptionId) -> Self {
279        Self(value.as_str().into())
280    }
281}
282
283impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
284    type Error = anyhow::Error;
285
286    fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
287        Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
288    }
289}
290
291impl From<Subscription> for StripeSubscription {
292    fn from(value: Subscription) -> Self {
293        Self {
294            id: value.id.into(),
295            customer: value.customer.id().into(),
296            status: value.status,
297            current_period_start: value.current_period_start,
298            current_period_end: value.current_period_end,
299            items: value.items.data.into_iter().map(Into::into).collect(),
300            cancel_at: value.cancel_at,
301            cancellation_details: value.cancellation_details.map(Into::into),
302        }
303    }
304}
305
306impl From<CancellationDetails> for StripeCancellationDetails {
307    fn from(value: CancellationDetails) -> Self {
308        Self {
309            reason: value.reason.map(Into::into),
310        }
311    }
312}
313
314impl From<CancellationDetailsReason> for StripeCancellationDetailsReason {
315    fn from(value: CancellationDetailsReason) -> Self {
316        match value {
317            CancellationDetailsReason::CancellationRequested => Self::CancellationRequested,
318            CancellationDetailsReason::PaymentDisputed => Self::PaymentDisputed,
319            CancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
320        }
321    }
322}
323
324impl From<SubscriptionItemId> for StripeSubscriptionItemId {
325    fn from(value: SubscriptionItemId) -> Self {
326        Self(value.as_str().into())
327    }
328}
329
330impl From<SubscriptionItem> for StripeSubscriptionItem {
331    fn from(value: SubscriptionItem) -> Self {
332        Self {
333            id: value.id.into(),
334            price: value.price.map(Into::into),
335        }
336    }
337}
338
339impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
340    fn from(value: StripeSubscriptionTrialSettings) -> Self {
341        Self {
342            end_behavior: value.end_behavior.into(),
343        }
344    }
345}
346
347impl From<StripeSubscriptionTrialSettingsEndBehavior>
348    for UpdateSubscriptionTrialSettingsEndBehavior
349{
350    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
351        Self {
352            missing_payment_method: value.missing_payment_method.into(),
353        }
354    }
355}
356
357impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
358    for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
359{
360    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
361        match value {
362            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
363            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
364                Self::CreateInvoice
365            }
366            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
367        }
368    }
369}
370
371impl From<PriceId> for StripePriceId {
372    fn from(value: PriceId) -> Self {
373        Self(value.as_str().into())
374    }
375}
376
377impl TryFrom<StripePriceId> for PriceId {
378    type Error = anyhow::Error;
379
380    fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
381        Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
382    }
383}
384
385impl From<Price> for StripePrice {
386    fn from(value: Price) -> Self {
387        Self {
388            id: value.id.into(),
389            unit_amount: value.unit_amount,
390            lookup_key: value.lookup_key,
391            recurring: value.recurring.map(StripePriceRecurring::from),
392        }
393    }
394}
395
396impl From<Recurring> for StripePriceRecurring {
397    fn from(value: Recurring) -> Self {
398        Self { meter: value.meter }
399    }
400}
401
402impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
403    type Error = anyhow::Error;
404
405    fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
406        Ok(Self {
407            customer: value
408                .customer
409                .map(|customer_id| customer_id.try_into())
410                .transpose()?,
411            client_reference_id: value.client_reference_id,
412            mode: value.mode.map(Into::into),
413            line_items: value
414                .line_items
415                .map(|line_items| line_items.into_iter().map(Into::into).collect()),
416            payment_method_collection: value.payment_method_collection.map(Into::into),
417            subscription_data: value.subscription_data.map(Into::into),
418            success_url: value.success_url,
419            ..Default::default()
420        })
421    }
422}
423
424impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
425    fn from(value: StripeCheckoutSessionMode) -> Self {
426        match value {
427            StripeCheckoutSessionMode::Payment => Self::Payment,
428            StripeCheckoutSessionMode::Setup => Self::Setup,
429            StripeCheckoutSessionMode::Subscription => Self::Subscription,
430        }
431    }
432}
433
434impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
435    fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
436        Self {
437            price: value.price,
438            quantity: value.quantity,
439            ..Default::default()
440        }
441    }
442}
443
444impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
445    fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
446        match value {
447            StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
448            StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
449        }
450    }
451}
452
453impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
454    fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
455        Self {
456            trial_period_days: value.trial_period_days,
457            trial_settings: value.trial_settings.map(Into::into),
458            metadata: value.metadata,
459            ..Default::default()
460        }
461    }
462}
463
464impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
465    fn from(value: StripeSubscriptionTrialSettings) -> Self {
466        Self {
467            end_behavior: value.end_behavior.into(),
468        }
469    }
470}
471
472impl From<StripeSubscriptionTrialSettingsEndBehavior>
473    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
474{
475    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
476        Self {
477            missing_payment_method: value.missing_payment_method.into(),
478        }
479    }
480}
481
482impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
483    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
484{
485    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
486        match value {
487            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
488            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
489                Self::CreateInvoice
490            }
491            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
492        }
493    }
494}
495
496impl From<CheckoutSession> for StripeCheckoutSession {
497    fn from(value: CheckoutSession) -> Self {
498        Self { url: value.url }
499    }
500}