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, UpdateCustomer, UpdateSubscriptionItems,
15 UpdateSubscriptionTrialSettings, UpdateSubscriptionTrialSettingsEndBehavior,
16 UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod,
17};
18
19use crate::stripe_client::{
20 CreateCustomerParams, StripeBillingAddressCollection, StripeCancellationDetails,
21 StripeCancellationDetailsReason, StripeCheckoutSession, StripeCheckoutSessionMode,
22 StripeCheckoutSessionPaymentMethodCollection, StripeClient,
23 StripeCreateCheckoutSessionLineItems, StripeCreateCheckoutSessionParams,
24 StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
25 StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
26 StripeCustomerUpdateAddress, StripeCustomerUpdateName, StripeCustomerUpdateShipping,
27 StripeMeter, StripePrice, StripePriceId, StripePriceRecurring, StripeSubscription,
28 StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId,
29 StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
30 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection,
31 UpdateCustomerParams, UpdateSubscriptionParams,
32};
33
34pub struct RealStripeClient {
35 client: Arc<stripe::Client>,
36}
37
38impl RealStripeClient {
39 pub fn new(client: Arc<stripe::Client>) -> Self {
40 Self { client }
41 }
42}
43
44#[async_trait]
45impl StripeClient for RealStripeClient {
46 async fn list_customers_by_email(&self, email: &str) -> Result<Vec<StripeCustomer>> {
47 let response = Customer::list(
48 &self.client,
49 &ListCustomers {
50 email: Some(email),
51 ..Default::default()
52 },
53 )
54 .await?;
55
56 Ok(response
57 .data
58 .into_iter()
59 .map(StripeCustomer::from)
60 .collect())
61 }
62
63 async fn get_customer(&self, customer_id: &StripeCustomerId) -> Result<StripeCustomer> {
64 let customer_id = customer_id.try_into()?;
65
66 let customer = Customer::retrieve(&self.client, &customer_id, &[]).await?;
67
68 Ok(StripeCustomer::from(customer))
69 }
70
71 async fn create_customer(&self, params: CreateCustomerParams<'_>) -> Result<StripeCustomer> {
72 let customer = Customer::create(
73 &self.client,
74 CreateCustomer {
75 email: params.email,
76 ..Default::default()
77 },
78 )
79 .await?;
80
81 Ok(StripeCustomer::from(customer))
82 }
83
84 async fn update_customer(
85 &self,
86 customer_id: &StripeCustomerId,
87 params: UpdateCustomerParams<'_>,
88 ) -> Result<StripeCustomer> {
89 let customer = Customer::update(
90 &self.client,
91 &customer_id.try_into()?,
92 UpdateCustomer {
93 email: params.email,
94 ..Default::default()
95 },
96 )
97 .await?;
98
99 Ok(StripeCustomer::from(customer))
100 }
101
102 async fn list_subscriptions_for_customer(
103 &self,
104 customer_id: &StripeCustomerId,
105 ) -> Result<Vec<StripeSubscription>> {
106 let customer_id = customer_id.try_into()?;
107
108 let subscriptions = stripe::Subscription::list(
109 &self.client,
110 &stripe::ListSubscriptions {
111 customer: Some(customer_id),
112 status: None,
113 ..Default::default()
114 },
115 )
116 .await?;
117
118 Ok(subscriptions
119 .data
120 .into_iter()
121 .map(StripeSubscription::from)
122 .collect())
123 }
124
125 async fn get_subscription(
126 &self,
127 subscription_id: &StripeSubscriptionId,
128 ) -> Result<StripeSubscription> {
129 let subscription_id = subscription_id.try_into()?;
130
131 let subscription = Subscription::retrieve(&self.client, &subscription_id, &[]).await?;
132
133 Ok(StripeSubscription::from(subscription))
134 }
135
136 async fn create_subscription(
137 &self,
138 params: StripeCreateSubscriptionParams,
139 ) -> Result<StripeSubscription> {
140 let customer_id = params.customer.try_into()?;
141
142 let mut create_subscription = stripe::CreateSubscription::new(customer_id);
143 create_subscription.items = Some(
144 params
145 .items
146 .into_iter()
147 .map(|item| stripe::CreateSubscriptionItems {
148 price: item.price.map(|price| price.to_string()),
149 quantity: item.quantity,
150 ..Default::default()
151 })
152 .collect(),
153 );
154
155 let subscription = Subscription::create(&self.client, create_subscription).await?;
156
157 Ok(StripeSubscription::from(subscription))
158 }
159
160 async fn update_subscription(
161 &self,
162 subscription_id: &StripeSubscriptionId,
163 params: UpdateSubscriptionParams,
164 ) -> Result<()> {
165 let subscription_id = subscription_id.try_into()?;
166
167 stripe::Subscription::update(
168 &self.client,
169 &subscription_id,
170 stripe::UpdateSubscription {
171 items: params.items.map(|items| {
172 items
173 .into_iter()
174 .map(|item| UpdateSubscriptionItems {
175 price: item.price.map(|price| price.to_string()),
176 ..Default::default()
177 })
178 .collect()
179 }),
180 trial_settings: params.trial_settings.map(Into::into),
181 ..Default::default()
182 },
183 )
184 .await?;
185
186 Ok(())
187 }
188
189 async fn cancel_subscription(&self, subscription_id: &StripeSubscriptionId) -> Result<()> {
190 let subscription_id = subscription_id.try_into()?;
191
192 Subscription::cancel(
193 &self.client,
194 &subscription_id,
195 stripe::CancelSubscription {
196 invoice_now: None,
197 ..Default::default()
198 },
199 )
200 .await?;
201
202 Ok(())
203 }
204
205 async fn list_prices(&self) -> Result<Vec<StripePrice>> {
206 let response = stripe::Price::list(
207 &self.client,
208 &stripe::ListPrices {
209 limit: Some(100),
210 ..Default::default()
211 },
212 )
213 .await?;
214
215 Ok(response.data.into_iter().map(StripePrice::from).collect())
216 }
217
218 async fn list_meters(&self) -> Result<Vec<StripeMeter>> {
219 #[derive(Serialize)]
220 struct Params {
221 #[serde(skip_serializing_if = "Option::is_none")]
222 limit: Option<u64>,
223 }
224
225 let response = self
226 .client
227 .get_query::<stripe::List<StripeMeter>, _>(
228 "/billing/meters",
229 Params { limit: Some(100) },
230 )
231 .await?;
232
233 Ok(response.data)
234 }
235
236 async fn create_meter_event(&self, params: StripeCreateMeterEventParams<'_>) -> Result<()> {
237 #[derive(Deserialize)]
238 struct StripeMeterEvent {
239 pub identifier: String,
240 }
241
242 let identifier = params.identifier;
243 match self
244 .client
245 .post_form::<StripeMeterEvent, _>("/billing/meter_events", params)
246 .await
247 {
248 Ok(_event) => Ok(()),
249 Err(stripe::StripeError::Stripe(error)) => {
250 if error.http_status == 400
251 && error
252 .message
253 .as_ref()
254 .map_or(false, |message| message.contains(identifier))
255 {
256 Ok(())
257 } else {
258 Err(anyhow!(stripe::StripeError::Stripe(error)))
259 }
260 }
261 Err(error) => Err(anyhow!("failed to create meter event: {error:?}")),
262 }
263 }
264
265 async fn create_checkout_session(
266 &self,
267 params: StripeCreateCheckoutSessionParams<'_>,
268 ) -> Result<StripeCheckoutSession> {
269 let params = params.try_into()?;
270 let session = CheckoutSession::create(&self.client, params).await?;
271
272 Ok(session.into())
273 }
274}
275
276impl From<CustomerId> for StripeCustomerId {
277 fn from(value: CustomerId) -> Self {
278 Self(value.as_str().into())
279 }
280}
281
282impl TryFrom<StripeCustomerId> for CustomerId {
283 type Error = anyhow::Error;
284
285 fn try_from(value: StripeCustomerId) -> Result<Self, Self::Error> {
286 Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
287 }
288}
289
290impl TryFrom<&StripeCustomerId> for CustomerId {
291 type Error = anyhow::Error;
292
293 fn try_from(value: &StripeCustomerId) -> Result<Self, Self::Error> {
294 Self::from_str(value.0.as_ref()).context("failed to parse Stripe customer ID")
295 }
296}
297
298impl From<Customer> for StripeCustomer {
299 fn from(value: Customer) -> Self {
300 StripeCustomer {
301 id: value.id.into(),
302 email: value.email,
303 }
304 }
305}
306
307impl From<SubscriptionId> for StripeSubscriptionId {
308 fn from(value: SubscriptionId) -> Self {
309 Self(value.as_str().into())
310 }
311}
312
313impl TryFrom<&StripeSubscriptionId> for SubscriptionId {
314 type Error = anyhow::Error;
315
316 fn try_from(value: &StripeSubscriptionId) -> Result<Self, Self::Error> {
317 Self::from_str(value.0.as_ref()).context("failed to parse Stripe subscription ID")
318 }
319}
320
321impl From<Subscription> for StripeSubscription {
322 fn from(value: Subscription) -> Self {
323 Self {
324 id: value.id.into(),
325 customer: value.customer.id().into(),
326 status: value.status,
327 current_period_start: value.current_period_start,
328 current_period_end: value.current_period_end,
329 items: value.items.data.into_iter().map(Into::into).collect(),
330 cancel_at: value.cancel_at,
331 cancellation_details: value.cancellation_details.map(Into::into),
332 }
333 }
334}
335
336impl From<CancellationDetails> for StripeCancellationDetails {
337 fn from(value: CancellationDetails) -> Self {
338 Self {
339 reason: value.reason.map(Into::into),
340 }
341 }
342}
343
344impl From<CancellationDetailsReason> for StripeCancellationDetailsReason {
345 fn from(value: CancellationDetailsReason) -> Self {
346 match value {
347 CancellationDetailsReason::CancellationRequested => Self::CancellationRequested,
348 CancellationDetailsReason::PaymentDisputed => Self::PaymentDisputed,
349 CancellationDetailsReason::PaymentFailed => Self::PaymentFailed,
350 }
351 }
352}
353
354impl From<SubscriptionItemId> for StripeSubscriptionItemId {
355 fn from(value: SubscriptionItemId) -> Self {
356 Self(value.as_str().into())
357 }
358}
359
360impl From<SubscriptionItem> for StripeSubscriptionItem {
361 fn from(value: SubscriptionItem) -> Self {
362 Self {
363 id: value.id.into(),
364 price: value.price.map(Into::into),
365 }
366 }
367}
368
369impl From<StripeSubscriptionTrialSettings> for UpdateSubscriptionTrialSettings {
370 fn from(value: StripeSubscriptionTrialSettings) -> Self {
371 Self {
372 end_behavior: value.end_behavior.into(),
373 }
374 }
375}
376
377impl From<StripeSubscriptionTrialSettingsEndBehavior>
378 for UpdateSubscriptionTrialSettingsEndBehavior
379{
380 fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
381 Self {
382 missing_payment_method: value.missing_payment_method.into(),
383 }
384 }
385}
386
387impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
388 for UpdateSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod
389{
390 fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
391 match value {
392 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
393 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
394 Self::CreateInvoice
395 }
396 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
397 }
398 }
399}
400
401impl From<PriceId> for StripePriceId {
402 fn from(value: PriceId) -> Self {
403 Self(value.as_str().into())
404 }
405}
406
407impl TryFrom<StripePriceId> for PriceId {
408 type Error = anyhow::Error;
409
410 fn try_from(value: StripePriceId) -> Result<Self, Self::Error> {
411 Self::from_str(value.0.as_ref()).context("failed to parse Stripe price ID")
412 }
413}
414
415impl From<Price> for StripePrice {
416 fn from(value: Price) -> Self {
417 Self {
418 id: value.id.into(),
419 unit_amount: value.unit_amount,
420 lookup_key: value.lookup_key,
421 recurring: value.recurring.map(StripePriceRecurring::from),
422 }
423 }
424}
425
426impl From<Recurring> for StripePriceRecurring {
427 fn from(value: Recurring) -> Self {
428 Self { meter: value.meter }
429 }
430}
431
432impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSession<'a> {
433 type Error = anyhow::Error;
434
435 fn try_from(value: StripeCreateCheckoutSessionParams<'a>) -> Result<Self, Self::Error> {
436 Ok(Self {
437 customer: value
438 .customer
439 .map(|customer_id| customer_id.try_into())
440 .transpose()?,
441 client_reference_id: value.client_reference_id,
442 mode: value.mode.map(Into::into),
443 line_items: value
444 .line_items
445 .map(|line_items| line_items.into_iter().map(Into::into).collect()),
446 payment_method_collection: value.payment_method_collection.map(Into::into),
447 subscription_data: value.subscription_data.map(Into::into),
448 success_url: value.success_url,
449 billing_address_collection: value.billing_address_collection.map(Into::into),
450 customer_update: value.customer_update.map(Into::into),
451 tax_id_collection: value.tax_id_collection.map(Into::into),
452 ..Default::default()
453 })
454 }
455}
456
457impl From<StripeCheckoutSessionMode> for CheckoutSessionMode {
458 fn from(value: StripeCheckoutSessionMode) -> Self {
459 match value {
460 StripeCheckoutSessionMode::Payment => Self::Payment,
461 StripeCheckoutSessionMode::Setup => Self::Setup,
462 StripeCheckoutSessionMode::Subscription => Self::Subscription,
463 }
464 }
465}
466
467impl From<StripeCreateCheckoutSessionLineItems> for CreateCheckoutSessionLineItems {
468 fn from(value: StripeCreateCheckoutSessionLineItems) -> Self {
469 Self {
470 price: value.price,
471 quantity: value.quantity,
472 ..Default::default()
473 }
474 }
475}
476
477impl From<StripeCheckoutSessionPaymentMethodCollection> for CheckoutSessionPaymentMethodCollection {
478 fn from(value: StripeCheckoutSessionPaymentMethodCollection) -> Self {
479 match value {
480 StripeCheckoutSessionPaymentMethodCollection::Always => Self::Always,
481 StripeCheckoutSessionPaymentMethodCollection::IfRequired => Self::IfRequired,
482 }
483 }
484}
485
486impl From<StripeCreateCheckoutSessionSubscriptionData> for CreateCheckoutSessionSubscriptionData {
487 fn from(value: StripeCreateCheckoutSessionSubscriptionData) -> Self {
488 Self {
489 trial_period_days: value.trial_period_days,
490 trial_settings: value.trial_settings.map(Into::into),
491 metadata: value.metadata,
492 ..Default::default()
493 }
494 }
495}
496
497impl From<StripeSubscriptionTrialSettings> for CreateCheckoutSessionSubscriptionDataTrialSettings {
498 fn from(value: StripeSubscriptionTrialSettings) -> Self {
499 Self {
500 end_behavior: value.end_behavior.into(),
501 }
502 }
503}
504
505impl From<StripeSubscriptionTrialSettingsEndBehavior>
506 for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehavior
507{
508 fn from(value: StripeSubscriptionTrialSettingsEndBehavior) -> Self {
509 Self {
510 missing_payment_method: value.missing_payment_method.into(),
511 }
512 }
513}
514
515impl From<StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod>
516 for CreateCheckoutSessionSubscriptionDataTrialSettingsEndBehaviorMissingPaymentMethod
517{
518 fn from(value: StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod) -> Self {
519 match value {
520 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Cancel => Self::Cancel,
521 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::CreateInvoice => {
522 Self::CreateInvoice
523 }
524 StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod::Pause => Self::Pause,
525 }
526 }
527}
528
529impl From<CheckoutSession> for StripeCheckoutSession {
530 fn from(value: CheckoutSession) -> Self {
531 Self { url: value.url }
532 }
533}
534
535impl From<StripeBillingAddressCollection> for stripe::CheckoutSessionBillingAddressCollection {
536 fn from(value: StripeBillingAddressCollection) -> Self {
537 match value {
538 StripeBillingAddressCollection::Auto => {
539 stripe::CheckoutSessionBillingAddressCollection::Auto
540 }
541 StripeBillingAddressCollection::Required => {
542 stripe::CheckoutSessionBillingAddressCollection::Required
543 }
544 }
545 }
546}
547
548impl From<StripeCustomerUpdateAddress> for stripe::CreateCheckoutSessionCustomerUpdateAddress {
549 fn from(value: StripeCustomerUpdateAddress) -> Self {
550 match value {
551 StripeCustomerUpdateAddress::Auto => {
552 stripe::CreateCheckoutSessionCustomerUpdateAddress::Auto
553 }
554 StripeCustomerUpdateAddress::Never => {
555 stripe::CreateCheckoutSessionCustomerUpdateAddress::Never
556 }
557 }
558 }
559}
560
561impl From<StripeCustomerUpdateName> for stripe::CreateCheckoutSessionCustomerUpdateName {
562 fn from(value: StripeCustomerUpdateName) -> Self {
563 match value {
564 StripeCustomerUpdateName::Auto => stripe::CreateCheckoutSessionCustomerUpdateName::Auto,
565 StripeCustomerUpdateName::Never => {
566 stripe::CreateCheckoutSessionCustomerUpdateName::Never
567 }
568 }
569 }
570}
571
572impl From<StripeCustomerUpdateShipping> for stripe::CreateCheckoutSessionCustomerUpdateShipping {
573 fn from(value: StripeCustomerUpdateShipping) -> Self {
574 match value {
575 StripeCustomerUpdateShipping::Auto => {
576 stripe::CreateCheckoutSessionCustomerUpdateShipping::Auto
577 }
578 StripeCustomerUpdateShipping::Never => {
579 stripe::CreateCheckoutSessionCustomerUpdateShipping::Never
580 }
581 }
582 }
583}
584
585impl From<StripeCustomerUpdate> for stripe::CreateCheckoutSessionCustomerUpdate {
586 fn from(value: StripeCustomerUpdate) -> Self {
587 stripe::CreateCheckoutSessionCustomerUpdate {
588 address: value.address.map(Into::into),
589 name: value.name.map(Into::into),
590 shipping: value.shipping.map(Into::into),
591 }
592 }
593}
594
595impl From<StripeTaxIdCollection> for stripe::CreateCheckoutSessionTaxIdCollection {
596 fn from(value: StripeTaxIdCollection) -> Self {
597 stripe::CreateCheckoutSessionTaxIdCollection {
598 enabled: value.enabled,
599 }
600 }
601}