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