diff --git a/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx b/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx index 52e20a6d09..c5d1ff6588 100644 --- a/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx +++ b/frontend/src/proprietary/components/shared/config/configSections/AdminPlanSection.tsx @@ -31,9 +31,9 @@ const AdminPlanSection: React.FC = () => { // Check if we should use static version useEffect(() => { - // Check if Stripe is configured - const stripeKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; - if (!stripeKey || !isSupabaseConfigured || error) { + // Only use static version if Supabase is not configured or there's an error + // Stripe key is not required - hosted checkout works without it + if (!isSupabaseConfigured || error) { setUseStaticVersion(true); } }, [error]); diff --git a/frontend/src/proprietary/components/shared/stripeCheckout/StripeCheckout.tsx b/frontend/src/proprietary/components/shared/stripeCheckout/StripeCheckout.tsx index e2aa7f540d..2743a49061 100644 --- a/frontend/src/proprietary/components/shared/stripeCheckout/StripeCheckout.tsx +++ b/frontend/src/proprietary/components/shared/stripeCheckout/StripeCheckout.tsx @@ -1,8 +1,7 @@ import React, { useEffect } from 'react'; -import { Modal, Text, Alert, Stack, Button, Group, ActionIcon } from '@mantine/core'; +import { Modal, Text, Group, ActionIcon } from '@mantine/core'; import { useTranslation } from 'react-i18next'; import LocalIcon from '@app/components/shared/LocalIcon'; -import { loadStripe } from '@stripe/stripe-js'; import licenseService from '@app/services/licenseService'; import { useIsMobile } from '@app/hooks/useIsMobile'; import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex'; @@ -37,8 +36,6 @@ if (STRIPE_KEY && !STRIPE_KEY.startsWith('pk_')) { ); } -const stripePromise = STRIPE_KEY ? loadStripe(STRIPE_KEY) : null; - const StripeCheckout: React.FC = ({ opened, onClose, @@ -192,25 +189,8 @@ const StripeCheckout: React.FC = ({ // Render stage content const renderContent = () => { - // Check if Stripe is configured - if (!stripePromise) { - return ( - - - - {t( - 'payment.stripeNotConfiguredMessage', - 'Stripe payment integration is not configured. Please contact your administrator.' - )} - - - - - ); - } - + // Don't block checkout - hosted mode works without publishable key + // The checkout will automatically redirect to Stripe hosted page if key is missing switch (checkoutState.state.currentStage) { case 'email': return ( diff --git a/frontend/src/proprietary/components/shared/stripeCheckout/stages/PaymentStage.tsx b/frontend/src/proprietary/components/shared/stripeCheckout/stages/PaymentStage.tsx index 9cee1983cd..84664f873f 100644 --- a/frontend/src/proprietary/components/shared/stripeCheckout/stages/PaymentStage.tsx +++ b/frontend/src/proprietary/components/shared/stripeCheckout/stages/PaymentStage.tsx @@ -35,10 +35,15 @@ export const PaymentStage: React.FC = ({ } if (!stripePromise) { + // This should only happen if embedded mode was attempted without key + // Hosted checkout should have redirected before reaching this component return ( - - Stripe is not configured properly. - + + + + {t('payment.redirecting', 'Redirecting to secure checkout...')} + + ); } diff --git a/frontend/src/proprietary/utils/protocolDetection.ts b/frontend/src/proprietary/utils/protocolDetection.ts index eb5d0a29a6..4f7e328c55 100644 --- a/frontend/src/proprietary/utils/protocolDetection.ts +++ b/frontend/src/proprietary/utils/protocolDetection.ts @@ -3,6 +3,16 @@ * Used to decide between Embedded Checkout (HTTPS) and Hosted Checkout (HTTP) */ +/** + * Check if Stripe publishable key is configured + * Similar to isSupabaseConfigured pattern - checks availability at decision points + * @returns true if key exists and has valid format + */ +export function isStripeConfigured(): boolean { + const stripeKey = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; + return !!stripeKey && stripeKey.startsWith('pk_'); +} + /** * Check if the current context is secure (HTTPS or localhost) * @returns true if HTTPS or localhost, false if HTTP @@ -28,16 +38,24 @@ export function isSecureContext(): boolean { /** * Get the appropriate Stripe checkout UI mode based on current context - * @returns 'embedded' for HTTPS/localhost, 'hosted' for HTTP + * @returns 'embedded' for HTTPS with key, 'hosted' for HTTP or missing key */ export function getCheckoutMode(): 'embedded' | 'hosted' { + // Force hosted checkout if no publishable key (regardless of protocol) + // Hosted checkout works without the key - it just redirects to Stripe + if (!isStripeConfigured()) { + return 'hosted'; + } + + // Normal protocol-based detection if key is available return isSecureContext() ? 'embedded' : 'hosted'; } /** * Check if Embedded Checkout can be used in current context - * @returns true if secure context (HTTPS/localhost) + * Requires both HTTPS and Stripe publishable key + * @returns true if secure context AND key is configured */ export function canUseEmbeddedCheckout(): boolean { - return isSecureContext(); + return isSecureContext() && isStripeConfigured(); }