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}