//! Stripe Connect, Checkout, and webhook routes. //! //! See also: `/docs/guide/03-selling` mod checkout; mod connect; pub(crate) mod webhook; mod webhook_v2; pub(crate) use checkout::grant_bundle_items; pub(crate) use webhook::process_webhook_event; pub(crate) use webhook_v2::process_v2_thin_event; use axum::routing::get; use crate::{ AppState, csrf::{CsrfRouter, post_csrf, post_csrf_manual, post_csrf_skip}, }; /// Reason string for routes that only construct a Stripe Checkout Session URL. /// State mutation happens server-side via the Stripe webhook, not in these /// handlers. The `tip` route is the documented exception, it inserts a /// `pending_tip` row BEFORE the Stripe call, so it uses Manual posture. const STRIPE_SESSION_SKIP: &str = "Stripe Checkout Session constructor, no mutation until webhook"; /// Register Stripe Connect, Checkout, and webhook routes. pub fn stripe_routes() -> CsrfRouter { CsrfRouter::new() // Creator onboarding (Account Links flow) .route_get("/stripe/connect", get(connect::stripe_connect_disclaimer)) .route( "/stripe/connect/proceed", post_csrf(connect::stripe_connect_proceed), ) .route_get( "/stripe/connect/return", get(connect::stripe_connect_return), ) .route_get( "/stripe/connect/refresh", get(connect::stripe_connect_refresh), ) // Checkout flow (idempotency handled by global middleware in metrics.rs) .route( "/stripe/fan-plus", post_csrf(checkout::create_fan_plus_checkout), ) .route( "/stripe/fan-plus/cancel", post_csrf(checkout::cancel_fan_plus), ) .route( "/stripe/fan-plus/resume", post_csrf(checkout::resume_fan_plus), ) .route( "/stripe/billing-portal", post_csrf(checkout::open_billing_portal), ) .route( "/stripe/creator-tier", post_csrf(checkout::create_creator_tier_checkout), ) .route( "/stripe/checkout/{item_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_checkout), ) .route( "/stripe/checkout/{item_id}/cancel-pending", post_csrf(checkout::cancel_pending_item_checkout), ) .route( "/stripe/checkout/project/{project_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_project_checkout), ) .route( "/stripe/subscribe/{tier_id}", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_subscription_checkout), ) .route( "/stripe/checkout/tip/{recipient_id}", post_csrf_manual( "inserts pending_tip row before Stripe call, handler validates _csrf", checkout::create_tip_checkout, ), ) .route( "/stripe/checkout/cart", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_cart_checkout), ) .route( "/stripe/checkout/cart/all", post_csrf_skip(STRIPE_SESSION_SKIP, checkout::create_cart_checkout_all), ) .route_get("/stripe/success", get(checkout::checkout_success)) .route_get("/stripe/cancel", get(checkout::checkout_cancel)) // Webhooks .route( "/stripe/webhook", post_csrf_skip( "webhook: stripe signature verified in handler", webhook::webhook, ), ) .route( "/stripe/webhook/v2", post_csrf_skip( "webhook: stripe signature verified in handler", webhook_v2::webhook_v2, ), ) }