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