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