Skip to main content

rustauth_stripe/
errors.rs

1use rustauth_core::error::RustAuthError;
2use rustauth_core::error_codes::ErrorCode;
3use rustauth_core::plugin::PluginErrorCode;
4
5/// Invalid Stripe plugin configuration detected at build time.
6#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
7pub enum StripeConfigError {
8    #[error("stripe_webhook_secret must not be empty")]
9    EmptyWebhookSecret,
10    #[error(
11        "seat-based billing requires organization: {{ enabled: true }} in stripe plugin options"
12    )]
13    SeatPricingWithoutOrganization,
14}
15
16impl From<StripeConfigError> for RustAuthError {
17    fn from(error: StripeConfigError) -> Self {
18        Self::InvalidConfig(error.to_string())
19    }
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum StripeErrorCode {
24    Unauthorized,
25    InvalidRequestBody,
26    SubscriptionNotFound,
27    SubscriptionPlanNotFound,
28    AlreadySubscribedPlan,
29    ReferenceIdNotAllowed,
30    CustomerNotFound,
31    UnableToCreateCustomer,
32    UnableToCreateBillingPortal,
33    StripeSignatureNotFound,
34    StripeWebhookSecretNotFound,
35    StripeWebhookError,
36    FailedToConstructStripeEvent,
37    FailedToFetchPlans,
38    EmailVerificationRequired,
39    SubscriptionNotActive,
40    SubscriptionNotPendingChange,
41    OrganizationNotFound,
42    OrganizationSubscriptionNotEnabled,
43    AuthorizeReferenceRequired,
44    OrganizationHasActiveSubscription,
45    OrganizationReferenceIdRequired,
46}
47
48impl std::fmt::Display for StripeErrorCode {
49    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        formatter.write_str(self.message())
51    }
52}
53
54impl StripeErrorCode {
55    pub fn code(self) -> &'static str {
56        match self {
57            Self::Unauthorized => "UNAUTHORIZED",
58            Self::InvalidRequestBody => "INVALID_REQUEST_BODY",
59            Self::SubscriptionNotFound => "SUBSCRIPTION_NOT_FOUND",
60            Self::SubscriptionPlanNotFound => "SUBSCRIPTION_PLAN_NOT_FOUND",
61            Self::AlreadySubscribedPlan => "ALREADY_SUBSCRIBED_PLAN",
62            Self::ReferenceIdNotAllowed => "REFERENCE_ID_NOT_ALLOWED",
63            Self::CustomerNotFound => "CUSTOMER_NOT_FOUND",
64            Self::UnableToCreateCustomer => "UNABLE_TO_CREATE_CUSTOMER",
65            Self::UnableToCreateBillingPortal => "UNABLE_TO_CREATE_BILLING_PORTAL",
66            Self::StripeSignatureNotFound => "STRIPE_SIGNATURE_NOT_FOUND",
67            Self::StripeWebhookSecretNotFound => "STRIPE_WEBHOOK_SECRET_NOT_FOUND",
68            Self::StripeWebhookError => "STRIPE_WEBHOOK_ERROR",
69            Self::FailedToConstructStripeEvent => "FAILED_TO_CONSTRUCT_STRIPE_EVENT",
70            Self::FailedToFetchPlans => "FAILED_TO_FETCH_PLANS",
71            Self::EmailVerificationRequired => "EMAIL_VERIFICATION_REQUIRED",
72            Self::SubscriptionNotActive => "SUBSCRIPTION_NOT_ACTIVE",
73            Self::SubscriptionNotPendingChange => "SUBSCRIPTION_NOT_PENDING_CHANGE",
74            Self::OrganizationNotFound => "ORGANIZATION_NOT_FOUND",
75            Self::OrganizationSubscriptionNotEnabled => "ORGANIZATION_SUBSCRIPTION_NOT_ENABLED",
76            Self::AuthorizeReferenceRequired => "AUTHORIZE_REFERENCE_REQUIRED",
77            Self::OrganizationHasActiveSubscription => "ORGANIZATION_HAS_ACTIVE_SUBSCRIPTION",
78            Self::OrganizationReferenceIdRequired => "ORGANIZATION_REFERENCE_ID_REQUIRED",
79        }
80    }
81
82    pub fn message(self) -> &'static str {
83        match self {
84            Self::Unauthorized => "Unauthorized access",
85            Self::InvalidRequestBody => "Invalid request body",
86            Self::SubscriptionNotFound => "Subscription not found",
87            Self::SubscriptionPlanNotFound => "Subscription plan not found",
88            Self::AlreadySubscribedPlan => "You're already subscribed to this plan",
89            Self::ReferenceIdNotAllowed => "Reference id is not allowed",
90            Self::CustomerNotFound => "Stripe customer not found for this user",
91            Self::UnableToCreateCustomer => "Unable to create customer",
92            Self::UnableToCreateBillingPortal => "Unable to create billing portal session",
93            Self::StripeSignatureNotFound => "Stripe signature not found",
94            Self::StripeWebhookSecretNotFound => "Stripe webhook secret not found",
95            Self::StripeWebhookError => "Stripe webhook error",
96            Self::FailedToConstructStripeEvent => "Failed to construct Stripe event",
97            Self::FailedToFetchPlans => "Failed to fetch plans",
98            Self::EmailVerificationRequired => {
99                "Email verification is required before you can subscribe to a plan"
100            }
101            Self::SubscriptionNotActive => "Subscription is not active",
102            Self::SubscriptionNotPendingChange => {
103                "Subscription has no pending cancellation or scheduled plan change"
104            }
105            Self::OrganizationNotFound => "Organization not found",
106            Self::OrganizationSubscriptionNotEnabled => "Organization subscription is not enabled",
107            Self::AuthorizeReferenceRequired => {
108                "Organization subscriptions require authorizeReference callback to be configured"
109            }
110            Self::OrganizationHasActiveSubscription => {
111                "Cannot delete organization with active subscription"
112            }
113            Self::OrganizationReferenceIdRequired => {
114                "Reference ID is required. Provide referenceId or set activeOrganizationId in session"
115            }
116        }
117    }
118}
119
120impl ErrorCode for StripeErrorCode {
121    fn as_str(&self) -> &str {
122        (*self).code()
123    }
124
125    fn message(&self) -> &str {
126        (*self).message()
127    }
128}
129
130pub fn error_codes() -> Vec<PluginErrorCode> {
131    [
132        StripeErrorCode::Unauthorized,
133        StripeErrorCode::InvalidRequestBody,
134        StripeErrorCode::SubscriptionNotFound,
135        StripeErrorCode::SubscriptionPlanNotFound,
136        StripeErrorCode::AlreadySubscribedPlan,
137        StripeErrorCode::ReferenceIdNotAllowed,
138        StripeErrorCode::CustomerNotFound,
139        StripeErrorCode::UnableToCreateCustomer,
140        StripeErrorCode::UnableToCreateBillingPortal,
141        StripeErrorCode::StripeSignatureNotFound,
142        StripeErrorCode::StripeWebhookSecretNotFound,
143        StripeErrorCode::StripeWebhookError,
144        StripeErrorCode::FailedToConstructStripeEvent,
145        StripeErrorCode::FailedToFetchPlans,
146        StripeErrorCode::EmailVerificationRequired,
147        StripeErrorCode::SubscriptionNotActive,
148        StripeErrorCode::SubscriptionNotPendingChange,
149        StripeErrorCode::OrganizationNotFound,
150        StripeErrorCode::OrganizationSubscriptionNotEnabled,
151        StripeErrorCode::AuthorizeReferenceRequired,
152        StripeErrorCode::OrganizationHasActiveSubscription,
153        StripeErrorCode::OrganizationReferenceIdRequired,
154    ]
155    .into_iter()
156    .map(|code| PluginErrorCode::new(code.code(), code.message()))
157    .collect()
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use rustauth_core::error_codes::ErrorCode;
164
165    fn assert_error_code(code: impl ErrorCode, expected_code: &str, expected_message: &str) {
166        assert_eq!(code.as_str(), expected_code);
167        assert_eq!(code.message(), expected_message);
168    }
169
170    #[test]
171    fn stripe_error_code_implements_error_code_trait() {
172        assert_error_code(
173            StripeErrorCode::SubscriptionNotFound,
174            "SUBSCRIPTION_NOT_FOUND",
175            "Subscription not found",
176        );
177    }
178}