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    CreateCustomer, Customer, CustomerId, ListCustomers, Price, PriceId, Recurring, Subscription,
  9    SubscriptionId, SubscriptionItem, SubscriptionItemId, UpdateSubscriptionItems,
 10    UpdateSubscriptionTrialSettings, UpdateSubscriptionTrialSettingsEndBehavior,
 11    UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
 12};
 13
 14use crate::stripe_client::{
 15    CreateCustomerParams, StripeClient, StripeCreateMeterEventParams, StripeCustomer,
 16    StripeCustomerId, StripeMeter, StripePrice, StripePriceId, StripePriceRecurring,
 17    StripeSubscription, StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId,
 18    UpdateSubscriptionParams,
 19};
 20
 21pub struct RealStripeClient {
 22    client: Arc<stripe::Client>,
 23}
 24
 25impl RealStripeClient {
 26    pub fn new(client: Arc<stripe::Client>) -> Self {
 27        Self { client }
 28    }
 29}
 30
 31#[async_trait]
 32impl StripeClient for RealStripeClient {
 33    async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
 34        let response = Customer::list(
 35            &self.client,
 36            &ListCustomers {
 37                email: Some(email),
 38                ..Default::default()
 39            },
 40        )
 41        .await?;
 42
 43        Ok(response
 44            .data
 45            .into_iter()
 46            .map(StripeCustomer::from)
 47            .collect())
 48    }
 49
 50    async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
 51        let customer = Customer::create(
 52            &self.client,
 53            CreateCustomer {
 54                email: params.email,
 55                ..Default::default()
 56            },
 57        )
 58        .await?;
 59
 60        Ok(StripeCustomer::from(customer))
 61    }
 62
 63    async fn get_subscription(
 64        &self,
 65        subscription_id: &StripeSubscriptionId,
 66    ) -> Result<StripeSubscription> {
 67        let subscription_id = subscription_id.try_into()?;
 68
 69        let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
 70
 71        Ok(StripeSubscription::from(subscription))
 72    }
 73
 74    async fn update_subscription(
 75        &self,
 76        subscription_id: &StripeSubscriptionId,
 77        params: UpdateSubscriptionParams,
 78    ) -> Result<()> {
 79        let subscription_id = subscription_id.try_into()?;
 80
 81        stripe::Subscription::update(
 82            &self.client,
 83            &subscription_id,
 84            stripe::UpdateSubscription {
 85                items: params.items.map(|items| {
 86                    items
 87                        .into_iter()
 88                        .map(|item| UpdateSubscriptionItems {
 89                            price: item.price.map(|price| price.to_string()),
 90                            ..Default::default()
 91                        })
 92                        .collect()
 93                }),
 94                trial_settings: params.trial_settings.map(Into::into),
 95                ..Default::default()
 96            },
 97        )
 98        .await?;
 99
100        Ok(())
101    }
102
103    async fn list_prices(&self) -> Result<Vec<StripePrice>> {
104        let response = stripe::Price::list(
105            &self.client,
106            &stripe::ListPrices {
107                limit: Some(100),
108                ..Default::default()
109            },
110        )
111        .await?;
112
113        Ok(response.data.into_iter().map(StripePrice::from).collect())
114    }
115
116    async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
117        #[derive(Serialize)]
118        struct Params {
119            #[serde(skip_serializing_if = "Option::is_none")]
120            limit: Option<u64>,
121        }
122
123        let response = self
124            .client
125            .get_query::<stripe::List<StripeMeter>, _>(
126                "/billing/meters",
127                Params { limit: Some(100) },
128            )
129            .await?;
130
131        Ok(response.data)
132    }
133
134    async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
135        let identifier = params.identifier;
136        match self.client.post_form("/billing/meter_events", params).await {
137            Ok(event) => Ok(event),
138            Err(stripe::StripeError::Stripe(error)) => {
139                if error.http_status == 400
140                    && error
141                        .message
142                        .as_ref()
143                        .map_or(false, |message| message.contains(identifier))
144                {
145                    Ok(())
146                } else {
147                    Err(anyhow!(stripe::StripeError::Stripe(error)))
148                }
149            }
150            Err(error) => Err(anyhow!(error)),
151        }
152    }
153}
154
155impl From<CustomerId> for StripeCustomerId {
156    fn from(value: CustomerId) -> Self {
157        Self(value.as_str().into())
158    }
159}
160
161impl TryFrom<StripeCustomerId> for CustomerId {
162    type Error = anyhow::Error;
163
164    fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
165        Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
166    }
167}
168
169impl From<Customer> for StripeCustomer {
170    fn from(value: Customer) -> Self {
171        StripeCustomer {
172            id: value.id.into(),
173            email: value.email,
174        }
175    }
176}
177
178impl From<SubscriptionId> for StripeSubscriptionId {
179    fn from(value: SubscriptionId) -> Self {
180        Self(value.as_str().into())
181    }
182}
183
184impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
185    type Error = anyhow::Error;
186
187    fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
188        Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
189    }
190}
191
192impl From<Subscription> for StripeSubscription {
193    fn from(value: Subscription) -> Self {
194        Self {
195            id: value.id.into(),
196            items: value.items.data.into_iter().map(Into::into).collect(),
197        }
198    }
199}
200
201impl From<SubscriptionItemId> for StripeSubscriptionItemId {
202    fn from(value: SubscriptionItemId) -> Self {
203        Self(value.as_str().into())
204    }
205}
206
207impl From<SubscriptionItem> for StripeSubscriptionItem {
208    fn from(value: SubscriptionItem) -> Self {
209        Self {
210            id: value.id.into(),
211            price: value.price.map(Into::into),
212        }
213    }
214}
215
216impl From<crate::stripe_client::UpdateSubscriptionTrialSettings>
217    for UpdateSubscriptionTrialSettings
218{
219    fn from(value: crate::stripe_client::UpdateSubscriptionTrialSettings) -> Self {
220        Self {
221            end_behavior: value.end_behavior.into(),
222        }
223    }
224}
225
226impl From<crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehavior>
227    for UpdateSubscriptionTrialSettingsEndBehavior
228{
229    fn from(value: crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehavior) -> Self {
230        Self {
231            missing_payment_method: value.missing_payment_method.into(),
232        }
233    }
234}
235
236impl From<crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
237    for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
238{
239    fn from(
240        value: crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
241    ) -> Self {
242        match value {
243            crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
244            crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
245                Self::CreateInvoice
246            }
247            crate::stripe_client::UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
248        }
249    }
250}
251
252impl From<PriceId> for StripePriceId {
253    fn from(value: PriceId) -> Self {
254        Self(value.as_str().into())
255    }
256}
257
258impl TryFrom<StripePriceId> for PriceId {
259    type Error = anyhow::Error;
260
261    fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
262        Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
263    }
264}
265
266impl From<Price> for StripePrice {
267    fn from(value: Price) -> Self {
268        Self {
269            id: value.id.into(),
270            unit_amount: value.unit_amount,
271            lookup_key: value.lookup_key,
272            recurring: value.recurring.map(StripePriceRecurring::from),
273        }
274    }
275}
276
277impl From<Recurring> for StripePriceRecurring {
278    fn from(value: Recurring) -> Self {
279        Self { meter: value.meter }
280    }
281}