diff --git a/src/sentry/organizations/absolute_url.py b/src/sentry/organizations/absolute_url.py
index a4e2c62a7154ba..854c613b321452 100644
--- a/src/sentry/organizations/absolute_url.py
+++ b/src/sentry/organizations/absolute_url.py
@@ -27,6 +27,7 @@
re.compile(r"^\/?(?!settings)[^/]+\/([^/]+)\/getting-started\/(.*)"),
r"/getting-started/\1/\2",
),
+ (re.compile(r"^\/?checkout\/[^/]+\/?.*"), r"/checkout/"),
]
diff --git a/static/app/utils/url/normalizeUrl.spec.tsx b/static/app/utils/url/normalizeUrl.spec.tsx
index 0122580ef2335d..4618b1e9ed8edb 100644
--- a/static/app/utils/url/normalizeUrl.spec.tsx
+++ b/static/app/utils/url/normalizeUrl.spec.tsx
@@ -79,6 +79,9 @@ describe('normalizeUrl', () => {
['/onboarding/acme/', '/onboarding/'],
['/onboarding/acme/project/', '/onboarding/project/'],
+ ['/checkout/', '/checkout/'],
+ ['/checkout/acme/', '/checkout/'],
+
['/organizations/new/', '/organizations/new/'],
['/organizations/albertos-organizations/issues/', '/issues/'],
[
diff --git a/static/app/utils/url/normalizeUrl.tsx b/static/app/utils/url/normalizeUrl.tsx
index 8429cab1329af4..137eed67ba7a35 100644
--- a/static/app/utils/url/normalizeUrl.tsx
+++ b/static/app/utils/url/normalizeUrl.tsx
@@ -22,6 +22,7 @@ const NORMALIZE_PATTERNS: Array<[pattern: RegExp, replacement: string]> = [
// Handles /org-slug/project-slug/getting-started/platform/ -> /getting-started/project-slug/platform/
[/^\/?(?!settings)[^/]+\/([^/]+)\/getting-started\/(.*)/, '/getting-started/$1/$2'],
[/^\/?accept-terms\/[^/]*\/?$/, '/accept-terms/'],
+ [/^\/?checkout\/[^/]+\/?.*$/, '/checkout/'],
];
type NormalizeUrlOptions = {
diff --git a/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx b/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx
index 7cf5c9e2de11ee..fd41b3415571bf 100644
--- a/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx
+++ b/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx
@@ -174,7 +174,7 @@ function PerformanceQuotaExceededWarning(props: ErrorOnlyWarningsProps) {
props.tree.shape
);
browserHistory.push({
- pathname: `/settings/billing/checkout/?referrer=trace-view`,
+ pathname: `/checkout/?referrer=trace-view`,
query: {
skipBundles: true,
},
diff --git a/static/gsApp/components/addEventsCTA.tsx b/static/gsApp/components/addEventsCTA.tsx
index a08acfd2d4d207..c9ec499a6076b2 100644
--- a/static/gsApp/components/addEventsCTA.tsx
+++ b/static/gsApp/components/addEventsCTA.tsx
@@ -108,7 +108,7 @@ function AddEventsCTA(props: Props) {
}, 0);
};
- const checkoutUrl = `/settings/${organization.slug}/billing/checkout/?referrer=${referrer}`;
+ const checkoutUrl = `/checkout/?referrer=${referrer}`;
const subscriptionUrl = `/settings/${organization.slug}/billing/overview/`;
switch (action) {
diff --git a/static/gsApp/components/ai/AiSetupDataConsent.tsx b/static/gsApp/components/ai/AiSetupDataConsent.tsx
index 8a08595ea65307..3a68c34ffff525 100644
--- a/static/gsApp/components/ai/AiSetupDataConsent.tsx
+++ b/static/gsApp/components/ai/AiSetupDataConsent.tsx
@@ -84,7 +84,7 @@ function AiSetupDataConsent({groupId}: AiSetupDataConsentProps) {
const autofixAcknowledgeMutation = useSeerAcknowledgeMutation();
function handlePurchaseSeer() {
- navigate(`/settings/billing/checkout/?referrer=ai_setup_data_consent`);
+ navigate(`/checkout/?referrer=ai_setup_data_consent`);
}
function handleAddBudget() {
@@ -93,7 +93,7 @@ function AiSetupDataConsent({groupId}: AiSetupDataConsentProps) {
}
if (isPerCategoryOnDemand) {
// Seer does not support per category on demand budgets, so we need to redirect to the checkout page to prompt the user to switch
- navigate(`/settings/billing/checkout/?referrer=ai_setup_data_consent#step3`);
+ navigate('/checkout/?referrer=ai_setup_data_consent#step3');
return;
}
openOnDemandBudgetEditModal({
diff --git a/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx b/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx
index 6d1226c69bb392..af2a6da0739391 100644
--- a/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx
+++ b/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx
@@ -21,7 +21,7 @@ export function CronsBannerUpgradeCTA({hasBillingAccess}: UpgradeCTAProps) {
if (hasBillingAccess) {
return (
diff --git a/static/gsApp/components/gsBanner.tsx b/static/gsApp/components/gsBanner.tsx
index 488e667247563b..634b4f8a35f845 100644
--- a/static/gsApp/components/gsBanner.tsx
+++ b/static/gsApp/components/gsBanner.tsx
@@ -1014,7 +1014,7 @@ class GSBanner extends Component {
// if there are deactivated members, than anyone who doesn't have org:billing will be
// prevented from accessing this view anyways cause they will be deactivated
if (isOverMemberLimit && !deactivatedMemberDismissed && this.hasBillingPerms) {
- const checkoutUrl = `/settings/${organization.slug}/billing/checkout/?referrer=deactivated_member_header`;
+ const checkoutUrl = '/checkout/?referrer=deactivated_member_header';
const wrappedNumber = {membersDeactivatedFromLimit};
// only disabling members if the plan allows exactly one member
return (
diff --git a/static/gsApp/components/partnerPlanEndingBanner.tsx b/static/gsApp/components/partnerPlanEndingBanner.tsx
index 945e81276a1f6e..307d7a13a45813 100644
--- a/static/gsApp/components/partnerPlanEndingBanner.tsx
+++ b/static/gsApp/components/partnerPlanEndingBanner.tsx
@@ -81,7 +81,7 @@ function PartnerPlanEndingBanner({
analyticsEventName="Partner Plan Ending Banner: Manage Subscription"
size="md"
onClick={() => handleAnalytics()}
- to={`/settings/${organization.slug}/billing/checkout/?referrer=partner_plan_ending_banner`}
+ to="/checkout/?referrer=partner_plan_ending_banner"
>
{t('Upgrade to %s', planToUpgradeTo)}
diff --git a/static/gsApp/components/partnerPlanEndingModal.tsx b/static/gsApp/components/partnerPlanEndingModal.tsx
index 5ad0148b66ea43..d18a759a40d841 100644
--- a/static/gsApp/components/partnerPlanEndingModal.tsx
+++ b/static/gsApp/components/partnerPlanEndingModal.tsx
@@ -157,7 +157,7 @@ function PartnerPlanEndingModal({organization, subscription, closeModal}: Props)
{hasBillingAccess ? (
diff --git a/static/gsApp/components/performance/quotaExceededAlert.spec.tsx b/static/gsApp/components/performance/quotaExceededAlert.spec.tsx
index d484d8dbebe246..e17b6941613df6 100644
--- a/static/gsApp/components/performance/quotaExceededAlert.spec.tsx
+++ b/static/gsApp/components/performance/quotaExceededAlert.spec.tsx
@@ -121,10 +121,7 @@ describe('Renders QuotaExceededAlert correctly for spans', () => {
expect(
screen.getByRole('link', {name: /increase your on-demand budget/})
- ).toHaveAttribute(
- 'href',
- '/settings/billing/checkout/?referrer=trace-view&skipBundles=true'
- );
+ ).toHaveAttribute('href', '/checkout/?referrer=trace-view&skipBundles=true');
});
it('renders alert when quota is exceeded for logs', async () => {
@@ -176,9 +173,6 @@ describe('Renders QuotaExceededAlert correctly for spans', () => {
expect(
screen.getByRole('link', {name: /increase your on-demand budget/})
- ).toHaveAttribute(
- 'href',
- '/settings/billing/checkout/?referrer=trace-view&skipBundles=true'
- );
+ ).toHaveAttribute('href', '/checkout/?referrer=trace-view&skipBundles=true');
});
});
diff --git a/static/gsApp/components/performance/quotaExceededAlert.tsx b/static/gsApp/components/performance/quotaExceededAlert.tsx
index f8b47da50133ac..3870c20566a0c5 100644
--- a/static/gsApp/components/performance/quotaExceededAlert.tsx
+++ b/static/gsApp/components/performance/quotaExceededAlert.tsx
@@ -79,7 +79,7 @@ function useQuotaExceededAlertMessage(
const billingPageLink = (
{
browserHistory.push(
- normalizeUrl(`/settings/${organization.slug}/billing/checkout/`)
+ normalizeUrl('/checkout/?referrer=product-trial-alert')
);
}}
>
@@ -212,9 +212,7 @@ function ProductTrialAlert(props: ProductTrialAlertProps) {
{t('Manage Subscription')}
diff --git a/static/gsApp/components/replayOnboardingCTA.tsx b/static/gsApp/components/replayOnboardingCTA.tsx
index 4da04d3ef2cb01..cbaa382505c838 100644
--- a/static/gsApp/components/replayOnboardingCTA.tsx
+++ b/static/gsApp/components/replayOnboardingCTA.tsx
@@ -95,7 +95,7 @@ function ReplayOnboardingCTAUpsell({
if (hasBillingAccess) {
// Redirect the user to the subscriptions page, where they will find important information.
// If they wish to update their plan, we ask them to contact our sales/support team.
- redirectToManage(organization);
+ redirectToManage();
}
return;
}
diff --git a/static/gsApp/components/upgradeNowModal/actionButtons.tsx b/static/gsApp/components/upgradeNowModal/actionButtons.tsx
index 2ab4047b4750b0..8129a37581ba0f 100644
--- a/static/gsApp/components/upgradeNowModal/actionButtons.tsx
+++ b/static/gsApp/components/upgradeNowModal/actionButtons.tsx
@@ -80,7 +80,7 @@ function ActionButtons({
});
} catch (err) {
Sentry.captureException(err);
- redirectToManage(organization);
+ redirectToManage();
}
}, [
api,
@@ -114,7 +114,7 @@ function ActionButtons({
});
},
onError: () => {
- redirectToManage(organization);
+ redirectToManage();
},
});
}, [api, organization, subscription, surface, onComplete]);
@@ -142,7 +142,7 @@ function ActionButtons({
{t('Update Now')}
{t('Manage Subscription')}
diff --git a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx
index 0787c94139478b..6df7532db2374d 100644
--- a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx
+++ b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx
@@ -75,7 +75,7 @@ function UpgradeNowModal({
});
} catch (err) {
Sentry.captureException(err);
- redirectToManage(organization);
+ redirectToManage();
addErrorMessage(
t(
'Oops! Unable to update Subscription automatically. Click through to update manually.'
diff --git a/static/gsApp/components/upgradeNowModal/utils.tsx b/static/gsApp/components/upgradeNowModal/utils.tsx
index 6d370d333614e1..90cea27a10d981 100644
--- a/static/gsApp/components/upgradeNowModal/utils.tsx
+++ b/static/gsApp/components/upgradeNowModal/utils.tsx
@@ -1,11 +1,10 @@
-import type {Organization} from 'sentry/types/organization';
import {browserHistory} from 'sentry/utils/browserHistory';
import normalizeUrl from 'sentry/utils/url/normalizeUrl';
-export function redirectToManage(organization: Organization) {
+export function redirectToManage() {
browserHistory.replace(
normalizeUrl({
- pathname: `/settings/${organization.slug}/billing/checkout/`,
+ pathname: '/checkout/',
query: {
referrer: 'replay_onboard-error-redirect',
},
diff --git a/static/gsApp/components/upgradeOrTrialButton.tsx b/static/gsApp/components/upgradeOrTrialButton.tsx
index 6b47e8a7ff2f75..d9c17c703f9e31 100644
--- a/static/gsApp/components/upgradeOrTrialButton.tsx
+++ b/static/gsApp/components/upgradeOrTrialButton.tsx
@@ -153,7 +153,7 @@ function UpgradeOrTrialButton({
if (hasAccess) {
// send self-serve directly to checkout
const baseUrl = subscription.canSelfServe
- ? `/settings/${slug}/billing/checkout/`
+ ? '/checkout/'
: `/settings/${slug}/billing/overview/`;
return (
diff --git a/static/gsApp/components/upsellModal/footer.spec.tsx b/static/gsApp/components/upsellModal/footer.spec.tsx
index 8d673de636d71a..c8a51243c774f7 100644
--- a/static/gsApp/components/upsellModal/footer.spec.tsx
+++ b/static/gsApp/components/upsellModal/footer.spec.tsx
@@ -9,7 +9,7 @@ describe('Business Landing Footer', () => {
const organization = OrganizationFixture({access: ['org:billing']});
const subscription = SubscriptionFixture({organization});
- const checkoutPage = `/settings/${organization.slug}/billing/checkout/?referrer=upgrade-business-landing.unknown`;
+ const checkoutPage = '/checkout/?referrer=upgrade-business-landing.unknown';
beforeEach(() => {
MockApiClient.clearMockResponses();
diff --git a/static/gsApp/components/upsellProvider.spec.tsx b/static/gsApp/components/upsellProvider.spec.tsx
index f269a7cbc4543c..23e02f07f12486 100644
--- a/static/gsApp/components/upsellProvider.spec.tsx
+++ b/static/gsApp/components/upsellProvider.spec.tsx
@@ -120,7 +120,7 @@ describe('UpsellProvider', () => {
expect(router.location).toEqual(
expect.objectContaining({
- pathname: `/settings/${org.slug}/billing/checkout/`,
+ pathname: '/checkout/',
query: {
referrer: 'upsell-test-abc',
},
diff --git a/static/gsApp/components/upsellProvider.tsx b/static/gsApp/components/upsellProvider.tsx
index 0aa522ec7d990f..db722937e72ad2 100644
--- a/static/gsApp/components/upsellProvider.tsx
+++ b/static/gsApp/components/upsellProvider.tsx
@@ -213,7 +213,7 @@ function UpsellProvider({
} else {
// for self-serve can send them to checkout
const baseUrl = subscription.canSelfServe
- ? `/settings/${organization.slug}/billing/checkout/`
+ ? '/checkout/'
: `/settings/${organization.slug}/billing/overview/`;
browserHistory.push(`${normalizeUrl(baseUrl)}?referrer=upsell-${source}`);
}
diff --git a/static/gsApp/hooks/dashboardsLimit.tsx b/static/gsApp/hooks/dashboardsLimit.tsx
index 18e0facaecd1ee..6afae13a3c2a3c 100644
--- a/static/gsApp/hooks/dashboardsLimit.tsx
+++ b/static/gsApp/hooks/dashboardsLimit.tsx
@@ -54,9 +54,7 @@ export function useDashboardsLimit(): UseDashboardsLimitResult {
? tct(
'You have reached the maximum number of Dashboards available on your plan. To add more, [link:upgrade your plan]',
{
- link: (
-
- ),
+ link: ,
}
)
: null;
diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx
index 9e13c9b649f6ce..ab19c28ff7f737 100644
--- a/static/gsApp/hooks/rootRoutes.tsx
+++ b/static/gsApp/hooks/rootRoutes.tsx
@@ -1,21 +1,37 @@
import {makeLazyloadComponent as make} from 'sentry/makeLazyloadComponent';
import type {SentryRouteObject} from 'sentry/router/types';
import errorHandler from 'sentry/utils/errorHandler';
+import withDomainRedirect from 'sentry/utils/withDomainRedirect';
+import withDomainRequired from 'sentry/utils/withDomainRequired';
import OrganizationSubscriptionContext from 'getsentry/components/organizationSubscriptionContext';
const rootRoutes = (): SentryRouteObject => ({
children: [
{
- // TODO(checkout v3): rename this to /checkout/ when the legacy checkout route is removed
- path: '/checkout-v3/',
+ path: '/checkout/',
component: errorHandler(OrganizationSubscriptionContext),
- deprecatedRouteProps: true,
customerDomainOnlyRoute: true,
+ deprecatedRouteProps: true,
+ children: [
+ {
+ index: true,
+ component: withDomainRequired(
+ make(() => import('getsentry/views/decideCheckout'))
+ ),
+ },
+ ],
+ },
+ {
+ path: '/checkout/:orgId/',
+ component: errorHandler(OrganizationSubscriptionContext),
+ deprecatedRouteProps: true,
children: [
{
index: true,
- component: make(() => import('getsentry/views/decideCheckout')),
+ component: withDomainRedirect(
+ make(() => import('getsentry/views/decideCheckout'))
+ ),
},
],
},
diff --git a/static/gsApp/hooks/settingsRoutes.tsx b/static/gsApp/hooks/settingsRoutes.tsx
index 77f8871a2fb077..56bda6722a0471 100644
--- a/static/gsApp/hooks/settingsRoutes.tsx
+++ b/static/gsApp/hooks/settingsRoutes.tsx
@@ -23,18 +23,10 @@ const settingsRoutes = (): SentryRouteObject => ({
redirectTo: 'overview/',
},
{
- // TODO(checkout v3): This should be removed when checkout v3 is GA'd
+ // NOTE: This route is retained for legacy linking
path: 'checkout/',
name: 'Change',
- component: errorHandler(SubscriptionContext),
- deprecatedRouteProps: true,
- children: [
- {
- index: true,
- component: make(() => import('../views/decideCheckout')),
- deprecatedRouteProps: true,
- },
- ],
+ redirectTo: '/checkout/',
},
{
path: 'cancel/',
diff --git a/static/gsApp/hooks/targetedOnboardingHeader.tsx b/static/gsApp/hooks/targetedOnboardingHeader.tsx
index 708fb148e7a1a6..71eb9b79d9d0b8 100644
--- a/static/gsApp/hooks/targetedOnboardingHeader.tsx
+++ b/static/gsApp/hooks/targetedOnboardingHeader.tsx
@@ -55,9 +55,7 @@ function TargetedOnboardingHeader({source, subscription}: Props) {
{cta}
}
diff --git a/static/gsApp/views/amCheckout/checkoutSuccess.tsx b/static/gsApp/views/amCheckout/checkoutSuccess.tsx
index a3ae4d71211d73..5b536af054c71c 100644
--- a/static/gsApp/views/amCheckout/checkoutSuccess.tsx
+++ b/static/gsApp/views/amCheckout/checkoutSuccess.tsx
@@ -606,7 +606,7 @@ function CheckoutSuccess({
{t('Edit plan')}
diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx
index 2992a18449d49a..ef7a2cfd1f43ed 100644
--- a/static/gsApp/views/amCheckout/index.tsx
+++ b/static/gsApp/views/amCheckout/index.tsx
@@ -6,7 +6,6 @@ import {loadStripe} from '@stripe/stripe-js';
import type {Location} from 'history';
import isEqual from 'lodash/isEqual';
import moment from 'moment-timezone';
-import * as qs from 'query-string';
import type {Client} from 'sentry/api';
import {Alert} from 'sentry/components/core/alert';
@@ -134,21 +133,6 @@ class AMCheckout extends Component {
) {
props.onToggleLegacy(props.subscription.planTier);
}
- const query = props.location?.query;
- const queryString =
- query && Object.keys(query).length > 0 ? `?${qs.stringify(query)}` : '';
-
- // TODO(checkout v3): remove these checks once checkout v3 is GA'd and we've remove the legacy checkout route
- if (props.location?.pathname.includes('checkout-v3') && !props.isNewCheckout) {
- props.navigate(
- `/settings/${props.organization.slug}/billing/checkout/${queryString}`,
- {
- replace: true,
- }
- );
- } else if (!props.location?.pathname.includes('checkout-v3') && props.isNewCheckout) {
- props.navigate(`/checkout-v3/${queryString}`, {replace: true});
- }
let step = 1;
if (props.location?.hash) {
const stepMatch = /^#step(\d)$/.exec(props.location.hash);
diff --git a/static/gsApp/views/spendAllocations/index.tsx b/static/gsApp/views/spendAllocations/index.tsx
index 4b07b6bdfb4e2d..50d1ff5eda6de6 100644
--- a/static/gsApp/views/spendAllocations/index.tsx
+++ b/static/gsApp/views/spendAllocations/index.tsx
@@ -371,7 +371,7 @@ export function SpendAllocationsRoot({organization, subscription}: Props) {
aria-label={t('Manage Subscription')}
size="sm"
style={{marginRight: space(1)}}
- to={`/settings/${organization.slug}/billing/checkout/?referrer=spend_allocations`}
+ to="/checkout/?referrer=spend_allocations"
>
{t('Manage Subscription')}
diff --git a/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx b/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx
index 12c2ec45539851..185e9c2eb086fc 100644
--- a/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx
+++ b/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx
@@ -87,7 +87,7 @@ function openPerformanceReservedTransactionsDiscountModal({
onAccept: () => {
navigate(
normalizeUrl({
- pathname: `/settings/${organization.slug}/billing/checkout/`,
+ pathname: '/checkout/',
query: {
skipBundles: true,
},
diff --git a/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx b/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx
index 3fecb67e9b2d39..9386c06ef4df27 100644
--- a/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx
+++ b/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx
@@ -146,7 +146,7 @@ function SubscriptionHeader(props: Props) {
{subscription.canSelfServe && hasBillingPerms && (
{t('Manage Subscription')}
@@ -210,7 +210,7 @@ function SubscriptionHeader(props: Props) {
{subscription.canSelfServe && hasBillingPerms && (
diff --git a/static/gsApp/views/subscriptionPage/usageTotals.tsx b/static/gsApp/views/subscriptionPage/usageTotals.tsx
index 7e94c15e75e040..ae9a8c0b676006 100644
--- a/static/gsApp/views/subscriptionPage/usageTotals.tsx
+++ b/static/gsApp/views/subscriptionPage/usageTotals.tsx
@@ -1081,7 +1081,7 @@ export function CombinedUsageTotals({
}
>
{tct('Enable [productName]', {