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    CheckoutSession, CheckoutSessionMode, CheckoutSessionPaymentMethodCollection,
  9    CreateCheckoutSession, CreateCheckoutSessionLineItems, CreateCheckoutSessionSubscriptionData,
 10    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, StripeCheckoutSession, StripeCheckoutSessionMode,
 21    StripeCheckoutSessionPaymentMethodCollection, StripeClient,
 22    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 create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
 61        let customer = Customer::create(
 62            &self.client,
 63            CreateCustomer {
 64                email: params.email,
 65                ..Default::default()
 66            },
 67        )
 68        .await?;
 69
 70        Ok(StripeCustomer::from(customer))
 71    }
 72
 73    async fn list_subscriptions_for_customer(
 74        &self,
 75        customer_id: &StripeCustomerId,
 76    ) -> Result<Vec<StripeSubscription>> {
 77        let customer_id = customer_id.try_into()?;
 78
 79        let subscriptions = stripe::Subscription::list(
 80            &self.client,
 81            &stripe::ListSubscriptions {
 82                customer: Some(customer_id),
 83                status: None,
 84                ..Default::default()
 85            },
 86        )
 87        .await?;
 88
 89        Ok(subscriptions
 90            .data
 91            .into_iter()
 92            .map(StripeSubscription::from)
 93            .collect())
 94    }
 95
 96    async fn get_subscription(
 97        &self,
 98        subscription_id: &StripeSubscriptionId,
 99    ) -> Result<StripeSubscription> {
100        let subscription_id = subscription_id.try_into()?;
101
102        let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
103
104        Ok(StripeSubscription::from(subscription))
105    }
106
107    async fn create_subscription(
108        &self,
109        params: StripeCreateSubscriptionParams,
110    ) -> Result<StripeSubscription> {
111        let customer_id = params.customer.try_into()?;
112
113        let mut create_subscription = stripe::CreateSubscription::new(customer_id);
114        create_subscription.items = Some(
115            params
116                .items
117                .into_iter()
118                .map(|item| stripe::CreateSubscriptionItems {
119                    price: item.price.map(|price| price.to_string()),
120                    quantity: item.quantity,
121                    ..Default::default()
122                })
123                .collect(),
124        );
125
126        let subscription = Subscription::create(&self.client, create_subscription).await?;
127
128        Ok(StripeSubscription::from(subscription))
129    }
130
131    async fn update_subscription(
132        &self,
133        subscription_id: &StripeSubscriptionId,
134        params: UpdateSubscriptionParams,
135    ) -> Result<()> {
136        let subscription_id = subscription_id.try_into()?;
137
138        stripe::Subscription::update(
139            &self.client,
140            &subscription_id,
141            stripe::UpdateSubscription {
142                items: params.items.map(|items| {
143                    items
144                        .into_iter()
145                        .map(|item| UpdateSubscriptionItems {
146                            price: item.price.map(|price| price.to_string()),
147                            ..Default::default()
148                        })
149                        .collect()
150                }),
151                trial_settings: params.trial_settings.map(Into::into),
152                ..Default::default()
153            },
154        )
155        .await?;
156
157        Ok(())
158    }
159
160    async fn list_prices(&self) -> Result<Vec<StripePrice>> {
161        let response = stripe::Price::list(
162            &self.client,
163            &stripe::ListPrices {
164                limit: Some(100),
165                ..Default::default()
166            },
167        )
168        .await?;
169
170        Ok(response.data.into_iter().map(StripePrice::from).collect())
171    }
172
173    async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
174        #[derive(Serialize)]
175        struct Params {
176            #[serde(skip_serializing_if = "Option::is_none")]
177            limit: Option<u64>,
178        }
179
180        let response = self
181            .client
182            .get_query::<stripe::List<StripeMeter>, _>(
183                "/billing/meters",
184                Params { limit: Some(100) },
185            )
186            .await?;
187
188        Ok(response.data)
189    }
190
191    async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
192        let identifier = params.identifier;
193        match self.client.post_form("/billing/meter_events", params).await {
194            Ok(event) => Ok(event),
195            Err(stripe::StripeError::Stripe(error)) => {
196                if error.http_status == 400
197                    && error
198                        .message
199                        .as_ref()
200                        .map_or(false, |message| message.contains(identifier))
201                {
202                    Ok(())
203                } else {
204                    Err(anyhow!(stripe::StripeError::Stripe(error)))
205                }
206            }
207            Err(error) => Err(anyhow!(error)),
208        }
209    }
210
211    async fn create_checkout_session(
212        &self,
213        params: StripeCreateCheckoutSessionParams<'_>,
214    ) -> Result<StripeCheckoutSession> {
215        let params = params.try_into()?;
216        let session = CheckoutSession::create(&self.client, params).await?;
217
218        Ok(session.into())
219    }
220}
221
222impl From<CustomerId> for StripeCustomerId {
223    fn from(value: CustomerId) -> Self {
224        Self(value.as_str().into())
225    }
226}
227
228impl TryFrom<StripeCustomerId> for CustomerId {
229    type Error = anyhow::Error;
230
231    fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
232        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
233    }
234}
235
236impl TryFrom<&StripeCustomerId> for CustomerId {
237    type Error = anyhow::Error;
238
239    fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
240        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
241    }
242}
243
244impl From<Customer> for StripeCustomer {
245    fn from(value: Customer) -> Self {
246        StripeCustomer {
247            id: value.id.into(),
248            email: value.email,
249        }
250    }
251}
252
253impl From<SubscriptionId> for StripeSubscriptionId {
254    fn from(value: SubscriptionId) -> Self {
255        Self(value.as_str().into())
256    }
257}
258
259impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
260    type Error = anyhow::Error;
261
262    fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
263        Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
264    }
265}
266
267impl From<Subscription> for StripeSubscription {
268    fn from(value: Subscription) -> Self {
269        Self {
270            id: value.id.into(),
271            customer: value.customer.id().into(),
272            status: value.status,
273            current_period_start: value.current_period_start,
274            current_period_end: value.current_period_end,
275            items: value.items.data.into_iter().map(Into::into).collect(),
276        }
277    }
278}
279
280impl From<SubscriptionItemId> for StripeSubscriptionItemId {
281    fn from(value: SubscriptionItemId) -> Self {
282        Self(value.as_str().into())
283    }
284}
285
286impl From<SubscriptionItem> for StripeSubscriptionItem {
287    fn from(value: SubscriptionItem) -> Self {
288        Self {
289            id: value.id.into(),
290            price: value.price.map(Into::into),
291        }
292    }
293}
294
295impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
296    fn from(value: StripeSubscriptionTrialSettings) -> Self {
297        Self {
298            end_behavior: value.end_behavior.into(),
299        }
300    }
301}
302
303impl From<StripeSubscriptionTrialSettingsEndBehavior>
304    for UpdateSubscriptionTrialSettingsEndBehavior
305{
306    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
307        Self {
308            missing_payment_method: value.missing_payment_method.into(),
309        }
310    }
311}
312
313impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
314    for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
315{
316    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
317        match value {
318            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
319            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
320                Self::CreateInvoice
321            }
322            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
323        }
324    }
325}
326
327impl From<PriceId> for StripePriceId {
328    fn from(value: PriceId) -> Self {
329        Self(value.as_str().into())
330    }
331}
332
333impl TryFrom<StripePriceId> for PriceId {
334    type Error = anyhow::Error;
335
336    fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
337        Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
338    }
339}
340
341impl From<Price> for StripePrice {
342    fn from(value: Price) -> Self {
343        Self {
344            id: value.id.into(),
345            unit_amount: value.unit_amount,
346            lookup_key: value.lookup_key,
347            recurring: value.recurring.map(StripePriceRecurring::from),
348        }
349    }
350}
351
352impl From<Recurring> for StripePriceRecurring {
353    fn from(value: Recurring) -> Self {
354        Self { meter: value.meter }
355    }
356}
357
358impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
359    type Error = anyhow::Error;
360
361    fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
362        Ok(Self {
363            customer: value
364                .customer
365                .map(|customer_id| customer_id.try_into())
366                .transpose()?,
367            client_reference_id: value.client_reference_id,
368            mode: value.mode.map(Into::into),
369            line_items: value
370                .line_items
371                .map(|line_items| line_items.into_iter().map(Into::into).collect()),
372            payment_method_collection: value.payment_method_collection.map(Into::into),
373            subscription_data: value.subscription_data.map(Into::into),
374            success_url: value.success_url,
375            ..Default::default()
376        })
377    }
378}
379
380impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
381    fn from(value: StripeCheckoutSessionMode) -> Self {
382        match value {
383            StripeCheckoutSessionMode::Payment => Self::Payment,
384            StripeCheckoutSessionMode::Setup => Self::Setup,
385            StripeCheckoutSessionMode::Subscription => Self::Subscription,
386        }
387    }
388}
389
390impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
391    fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
392        Self {
393            price: value.price,
394            quantity: value.quantity,
395            ..Default::default()
396        }
397    }
398}
399
400impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
401    fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
402        match value {
403            StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
404            StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
405        }
406    }
407}
408
409impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
410    fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
411        Self {
412            trial_period_days: value.trial_period_days,
413            trial_settings: value.trial_settings.map(Into::into),
414            metadata: value.metadata,
415            ..Default::default()
416        }
417    }
418}
419
420impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
421    fn from(value: StripeSubscriptionTrialSettings) -> Self {
422        Self {
423            end_behavior: value.end_behavior.into(),
424        }
425    }
426}
427
428impl From<StripeSubscriptionTrialSettingsEndBehavior>
429    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
430{
431    fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
432        Self {
433            missing_payment_method: value.missing_payment_method.into(),
434        }
435    }
436}
437
438impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
439    for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
440{
441    fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
442        match value {
443            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
444            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
445                Self::CreateInvoice
446            }
447            StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
448        }
449    }
450}
451
452impl From<CheckoutSession> for StripeCheckoutSession {
453    fn from(value: CheckoutSession) -> Self {
454        Self { url: value.url }
455    }
456}