| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
mod checkout; |
| 18 |
mod checkout_metadata; |
| 19 |
mod connect; |
| 20 |
pub mod synckit_app_pricing; |
| 21 |
pub mod synckit_billing; |
| 22 |
mod webhooks; |
| 23 |
|
| 24 |
pub use checkout::*; |
| 25 |
pub use checkout_metadata::*; |
| 26 |
pub use synckit_app_pricing::{quote_price_cents, SyncBillingInterval, ANNUAL_MULTIPLIER, MAX_CAP_BYTES, MIN_CAP_BYTES, MIN_CHARGE_CENTS}; |
| 27 |
pub use synckit_billing::SynckitSubResult; |
| 28 |
pub use webhooks::*; |
| 29 |
|
| 30 |
use stripe::Client; |
| 31 |
use crate::config::StripeConfig; |
| 32 |
|
| 33 |
|
| 34 |
#[derive(Clone)] |
| 35 |
pub struct StripeClient { |
| 36 |
pub(crate) client: Client, |
| 37 |
pub(crate) config: StripeConfig, |
| 38 |
} |
| 39 |
|
| 40 |
impl StripeClient { |
| 41 |
|
| 42 |
pub fn new(config: &StripeConfig) -> Self { |
| 43 |
let client = Client::new(&config.secret_key); |
| 44 |
StripeClient { |
| 45 |
client, |
| 46 |
config: config.clone(), |
| 47 |
} |
| 48 |
} |
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
pub(crate) fn parse_account_id(account_id: &str) -> Result<stripe_shared::AccountId> { |
| 56 |
account_id.parse().map_err(|e| { |
| 57 |
AppError::Internal(anyhow::anyhow!("Invalid Stripe account ID '{}': {}", account_id, e)) |
| 58 |
}) |
| 59 |
} |
| 60 |
} |
| 61 |
|
| 62 |
use crate::error::{AppError, Result}; |
| 63 |
|
| 64 |
|
| 65 |
pub struct CheckoutResult { |
| 66 |
pub id: String, |
| 67 |
pub url: Option<String>, |
| 68 |
} |
| 69 |
|
| 70 |
|
| 71 |
pub struct BalanceSummary { |
| 72 |
pub available_cents: i64, |
| 73 |
pub pending_cents: i64, |
| 74 |
} |
| 75 |
|
| 76 |
|
| 77 |
#[async_trait::async_trait] |
| 78 |
pub trait PaymentProvider: Send + Sync { |
| 79 |
|
| 80 |
async fn create_checkout_session(&self, params: &CheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 81 |
async fn create_guest_checkout_session(&self, params: &GuestCheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 82 |
async fn create_subscription_checkout_session(&self, params: &SubscriptionCheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 83 |
async fn create_tip_checkout_session(&self, params: &TipCheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 84 |
async fn create_fan_plus_checkout_session(&self, price_id: &str, user_id: crate::db::UserId, success_url: &str, cancel_url: &str) -> crate::error::Result<CheckoutResult>; |
| 85 |
async fn create_creator_tier_checkout_session(&self, price_id: &str, user_id: crate::db::UserId, tier: &str, success_url: &str, cancel_url: &str) -> crate::error::Result<CheckoutResult>; |
| 86 |
async fn create_synckit_app_sub_checkout_session(&self, params: &SynckitAppSubCheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 87 |
async fn create_cart_checkout_session(&self, params: &CartCheckoutParams<'_>) -> crate::error::Result<CheckoutResult>; |
| 88 |
|
| 89 |
|
| 90 |
async fn create_connect_account(&self, email: &str) -> crate::error::Result<String>; |
| 91 |
async fn create_account_link(&self, account_id: &str, return_url: &str, refresh_url: &str) -> crate::error::Result<String>; |
| 92 |
async fn fetch_account(&self, account_id: &str) -> crate::error::Result<AccountUpdate>; |
| 93 |
async fn create_subscription_product_and_price(&self, connected_account_id: &str, tier_name: &str, tier_description: Option<&str>, price_cents: i64) -> crate::error::Result<(String, String)>; |
| 94 |
async fn get_balance(&self, account_id: &str) -> crate::error::Result<BalanceSummary>; |
| 95 |
|
| 96 |
|
| 97 |
async fn pause_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()>; |
| 98 |
async fn resume_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()>; |
| 99 |
async fn cancel_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()>; |
| 100 |
|
| 101 |
async fn set_cancel_at_period_end(&self, stripe_sub_id: &str, connected_account_id: &str, cancel: bool) -> crate::error::Result<()>; |
| 102 |
|
| 103 |
async fn cancel_platform_subscription(&self, stripe_sub_id: &str) -> crate::error::Result<()>; |
| 104 |
|
| 105 |
async fn set_platform_cancel_at_period_end(&self, stripe_sub_id: &str, cancel: bool) -> crate::error::Result<()>; |
| 106 |
|
| 107 |
async fn create_billing_portal_session(&self, stripe_customer_id: &str, return_url: &str) -> crate::error::Result<String>; |
| 108 |
|
| 109 |
|
| 110 |
async fn create_refund(&self, payment_intent_id: &str, connected_account_id: &str) -> crate::error::Result<()>; |
| 111 |
|
| 112 |
|
| 113 |
fn verify_webhook(&self, payload: &str, signature: &str) -> crate::error::Result<UntypedEvent>; |
| 114 |
fn verify_webhook_v2(&self, payload: &str, signature: &str) -> crate::error::Result<serde_json::Value>; |
| 115 |
|
| 116 |
|
| 117 |
|
| 118 |
|
| 119 |
async fn create_synckit_customer(&self, developer_user_id: crate::db::UserId, email: &str, app_name: &str) -> crate::error::Result<String>; |
| 120 |
async fn create_synckit_subscription(&self, customer_id: &str, app_id: crate::db::SyncAppId, app_name: &str, price_cents: i64) -> crate::error::Result<SynckitSubResult>; |
| 121 |
async fn update_synckit_subscription_price(&self, subscription_id: &str, new_price_cents: i64, app_name: &str) -> crate::error::Result<()>; |
| 122 |
|
| 123 |
|
| 124 |
async fn update_synckit_app_sub_price(&self, subscription_id: &str, new_price_cents: i64, interval: SyncBillingInterval, product_name: &str) -> crate::error::Result<()>; |
| 125 |
async fn cancel_synckit_subscription(&self, subscription_id: &str) -> crate::error::Result<()>; |
| 126 |
async fn create_synckit_billing_portal(&self, customer_id: &str, return_url: &str) -> crate::error::Result<String>; |
| 127 |
} |
| 128 |
|
| 129 |
#[async_trait::async_trait] |
| 130 |
impl PaymentProvider for StripeClient { |
| 131 |
async fn create_checkout_session(&self, params: &CheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 132 |
let session = StripeClient::create_checkout_session(self, params).await?; |
| 133 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 134 |
} |
| 135 |
|
| 136 |
async fn create_guest_checkout_session(&self, params: &GuestCheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 137 |
let session = StripeClient::create_guest_checkout_session(self, params).await?; |
| 138 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 139 |
} |
| 140 |
|
| 141 |
async fn create_subscription_checkout_session(&self, params: &SubscriptionCheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 142 |
let session = StripeClient::create_subscription_checkout_session(self, params).await?; |
| 143 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 144 |
} |
| 145 |
|
| 146 |
async fn create_tip_checkout_session(&self, params: &TipCheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 147 |
let session = StripeClient::create_tip_checkout_session(self, params).await?; |
| 148 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 149 |
} |
| 150 |
|
| 151 |
async fn create_fan_plus_checkout_session(&self, price_id: &str, user_id: crate::db::UserId, success_url: &str, cancel_url: &str) -> crate::error::Result<CheckoutResult> { |
| 152 |
let session = StripeClient::create_fan_plus_checkout_session(self, price_id, user_id, success_url, cancel_url).await?; |
| 153 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 154 |
} |
| 155 |
|
| 156 |
async fn create_creator_tier_checkout_session(&self, price_id: &str, user_id: crate::db::UserId, tier: &str, success_url: &str, cancel_url: &str) -> crate::error::Result<CheckoutResult> { |
| 157 |
let session = StripeClient::create_creator_tier_checkout_session(self, price_id, user_id, tier, success_url, cancel_url).await?; |
| 158 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 159 |
} |
| 160 |
|
| 161 |
async fn create_synckit_app_sub_checkout_session(&self, params: &SynckitAppSubCheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 162 |
let session = StripeClient::create_synckit_app_sub_checkout_session(self, params).await?; |
| 163 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 164 |
} |
| 165 |
|
| 166 |
async fn create_cart_checkout_session(&self, params: &CartCheckoutParams<'_>) -> crate::error::Result<CheckoutResult> { |
| 167 |
let session = StripeClient::create_cart_checkout_session(self, params).await?; |
| 168 |
Ok(CheckoutResult { id: session.id.to_string(), url: session.url }) |
| 169 |
} |
| 170 |
|
| 171 |
async fn create_connect_account(&self, email: &str) -> crate::error::Result<String> { |
| 172 |
StripeClient::create_connect_account(self, email).await |
| 173 |
} |
| 174 |
|
| 175 |
async fn create_account_link(&self, account_id: &str, return_url: &str, refresh_url: &str) -> crate::error::Result<String> { |
| 176 |
StripeClient::create_account_link(self, account_id, return_url, refresh_url).await |
| 177 |
} |
| 178 |
|
| 179 |
async fn fetch_account(&self, account_id: &str) -> crate::error::Result<AccountUpdate> { |
| 180 |
StripeClient::fetch_account(self, account_id).await |
| 181 |
} |
| 182 |
|
| 183 |
async fn create_subscription_product_and_price(&self, connected_account_id: &str, tier_name: &str, tier_description: Option<&str>, price_cents: i64) -> crate::error::Result<(String, String)> { |
| 184 |
StripeClient::create_subscription_product_and_price(self, connected_account_id, tier_name, tier_description, price_cents).await |
| 185 |
} |
| 186 |
|
| 187 |
async fn get_balance(&self, account_id: &str) -> crate::error::Result<BalanceSummary> { |
| 188 |
let balance = self.get_connected_account_balance(account_id).await?; |
| 189 |
let available_cents: i64 = balance |
| 190 |
.available |
| 191 |
.iter() |
| 192 |
.filter(|b| b.currency == stripe_types::Currency::USD) |
| 193 |
.map(|b| b.amount) |
| 194 |
.sum(); |
| 195 |
let pending_cents: i64 = balance |
| 196 |
.pending |
| 197 |
.iter() |
| 198 |
.filter(|b| b.currency == stripe_types::Currency::USD) |
| 199 |
.map(|b| b.amount) |
| 200 |
.sum(); |
| 201 |
Ok(BalanceSummary { available_cents, pending_cents }) |
| 202 |
} |
| 203 |
|
| 204 |
async fn pause_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()> { |
| 205 |
StripeClient::pause_subscription(self, stripe_sub_id, connected_account_id).await |
| 206 |
} |
| 207 |
|
| 208 |
async fn resume_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()> { |
| 209 |
StripeClient::resume_subscription(self, stripe_sub_id, connected_account_id).await |
| 210 |
} |
| 211 |
|
| 212 |
async fn cancel_subscription(&self, stripe_sub_id: &str, connected_account_id: &str) -> crate::error::Result<()> { |
| 213 |
StripeClient::cancel_subscription(self, stripe_sub_id, connected_account_id).await |
| 214 |
} |
| 215 |
|
| 216 |
async fn set_cancel_at_period_end(&self, stripe_sub_id: &str, connected_account_id: &str, cancel: bool) -> crate::error::Result<()> { |
| 217 |
StripeClient::set_cancel_at_period_end(self, stripe_sub_id, connected_account_id, cancel).await |
| 218 |
} |
| 219 |
|
| 220 |
async fn cancel_platform_subscription(&self, stripe_sub_id: &str) -> crate::error::Result<()> { |
| 221 |
StripeClient::cancel_platform_subscription(self, stripe_sub_id).await |
| 222 |
} |
| 223 |
|
| 224 |
async fn set_platform_cancel_at_period_end(&self, stripe_sub_id: &str, cancel: bool) -> crate::error::Result<()> { |
| 225 |
StripeClient::set_platform_cancel_at_period_end(self, stripe_sub_id, cancel).await |
| 226 |
} |
| 227 |
|
| 228 |
async fn create_billing_portal_session(&self, stripe_customer_id: &str, return_url: &str) -> crate::error::Result<String> { |
| 229 |
StripeClient::create_billing_portal_session(self, stripe_customer_id, return_url).await |
| 230 |
} |
| 231 |
|
| 232 |
async fn create_refund(&self, payment_intent_id: &str, connected_account_id: &str) -> crate::error::Result<()> { |
| 233 |
StripeClient::create_refund(self, payment_intent_id, connected_account_id).await |
| 234 |
} |
| 235 |
|
| 236 |
fn verify_webhook(&self, payload: &str, signature: &str) -> crate::error::Result<UntypedEvent> { |
| 237 |
StripeClient::verify_webhook(self, payload, signature) |
| 238 |
} |
| 239 |
|
| 240 |
fn verify_webhook_v2(&self, payload: &str, signature: &str) -> crate::error::Result<serde_json::Value> { |
| 241 |
StripeClient::verify_webhook_v2(self, payload, signature) |
| 242 |
} |
| 243 |
|
| 244 |
async fn create_synckit_customer(&self, developer_user_id: crate::db::UserId, email: &str, app_name: &str) -> crate::error::Result<String> { |
| 245 |
StripeClient::create_synckit_customer(self, developer_user_id, email, app_name).await |
| 246 |
} |
| 247 |
|
| 248 |
async fn create_synckit_subscription(&self, customer_id: &str, app_id: crate::db::SyncAppId, app_name: &str, price_cents: i64) -> crate::error::Result<SynckitSubResult> { |
| 249 |
StripeClient::create_synckit_subscription(self, customer_id, app_id, app_name, price_cents).await |
| 250 |
} |
| 251 |
|
| 252 |
async fn update_synckit_subscription_price(&self, subscription_id: &str, new_price_cents: i64, app_name: &str) -> crate::error::Result<()> { |
| 253 |
StripeClient::update_synckit_subscription_price(self, subscription_id, new_price_cents, app_name).await |
| 254 |
} |
| 255 |
|
| 256 |
async fn update_synckit_app_sub_price(&self, subscription_id: &str, new_price_cents: i64, interval: SyncBillingInterval, product_name: &str) -> crate::error::Result<()> { |
| 257 |
StripeClient::update_synckit_app_sub_price(self, subscription_id, new_price_cents, interval, product_name).await |
| 258 |
} |
| 259 |
|
| 260 |
async fn cancel_synckit_subscription(&self, subscription_id: &str) -> crate::error::Result<()> { |
| 261 |
StripeClient::cancel_synckit_subscription(self, subscription_id).await |
| 262 |
} |
| 263 |
|
| 264 |
async fn create_synckit_billing_portal(&self, customer_id: &str, return_url: &str) -> crate::error::Result<String> { |
| 265 |
StripeClient::create_synckit_billing_portal(self, customer_id, return_url).await |
| 266 |
} |
| 267 |
} |
| 268 |
|