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