Point billing clients at /monetization/ instead of /finance/.
Unit Tests / test (pull_request) Failing after 11s

Align FE with backend finance→monetization rename (api/monetization);
backend keeps /api/finance as alias only.
This commit is contained in:
2026-08-04 05:36:11 -05:00
parent 75a0d7f637
commit bf8f6ea1d2
11 changed files with 27 additions and 27 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
| Runtime | Checkout / manage | Ledger | | Runtime | Checkout / manage | Ledger |
|---------|-------------------|--------| |---------|-------------------|--------|
| Web (`!isNativePlatform`) | Stripe Checkout + Customer Portal via `/finance/...` | Stripe webhooks | | Web (`!isNativePlatform`) | Stripe Checkout + Customer Portal via `/monetization/...` | Stripe webhooks |
| Native Capacitor | RevenueCat IAP (`@revenuecat/purchases-capacitor`) | RevenueCat webhooks → same invoice/payment tables | | Native Capacitor | RevenueCat IAP (`@revenuecat/purchases-capacitor`) | RevenueCat webhooks → same invoice/payment tables |
FE displays whatever `/finance/invoices/` (and subscription) returns after refresh. Provider badge uses `provider` + `revenuecat_store`. FE displays whatever `/monetization/invoices/` (and subscription) returns after refresh. Provider badge uses `provider` + `revenuecat_store`.
## App user id ## App user id
@@ -157,16 +157,16 @@ const mockFinanceGets = ({
plans?: unknown[]; plans?: unknown[];
} = {}) => { } = {}) => {
mockGet.mockImplementation((url: string) => { mockGet.mockImplementation((url: string) => {
if (url === '/finance/invoices/') { if (url === '/monetization/invoices/') {
return Promise.resolve({ data: invoices }); return Promise.resolve({ data: invoices });
} }
if (url === '/finance/payments/') { if (url === '/monetization/payments/') {
return Promise.resolve({ data: payments }); return Promise.resolve({ data: payments });
} }
if (url === '/finance/subscription/') { if (url === '/monetization/subscription/') {
return Promise.resolve({ data: subscription }); return Promise.resolve({ data: subscription });
} }
if (url === '/finance/plans/') { if (url === '/monetization/plans/') {
return Promise.resolve({ data: plans }); return Promise.resolve({ data: plans });
} }
return Promise.reject(new Error(`unexpected GET ${url}`)); return Promise.reject(new Error(`unexpected GET ${url}`));
@@ -246,7 +246,7 @@ describe('BillingSection', () => {
await user.click(screen.getByRole('button', { name: /Change plan/i })); await user.click(screen.getByRole('button', { name: /Change plan/i }));
await waitFor(() => { await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/finance/portal/', { expect(mockPost).toHaveBeenCalledWith('/monetization/portal/', {
return_url: 'http://localhost/account/', return_url: 'http://localhost/account/',
}); });
}); });
@@ -321,7 +321,7 @@ describe('BillingSection', () => {
await waitFor(() => { await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/finance/checkout/', '/monetization/checkout/',
expect.objectContaining({ expect.objectContaining({
success_url: expect.stringContaining('/billing/success'), success_url: expect.stringContaining('/billing/success'),
cancel_url: expect.stringContaining('/billing/cancel'), cancel_url: expect.stringContaining('/billing/cancel'),
@@ -378,7 +378,7 @@ describe('BillingSection', () => {
expect(mockPurchasePlan).toHaveBeenCalled(); expect(mockPurchasePlan).toHaveBeenCalled();
}); });
expect(mockPost).not.toHaveBeenCalledWith( expect(mockPost).not.toHaveBeenCalledWith(
'/finance/checkout/', '/monetization/checkout/',
expect.anything() expect.anything()
); );
}); });
@@ -292,10 +292,10 @@ const BillingSection = (): JSX.Element => {
try { try {
const [invoiceResponse, paymentResponse, subscriptionResponse, plansResponse] = const [invoiceResponse, paymentResponse, subscriptionResponse, plansResponse] =
await Promise.all([ await Promise.all([
axiosInstance.get<FinanceInvoice[]>('/finance/invoices/'), axiosInstance.get<FinanceInvoice[]>('/monetization/invoices/'),
axiosInstance.get<FinancePayment[]>('/finance/payments/'), axiosInstance.get<FinancePayment[]>('/monetization/payments/'),
axiosInstance.get<SubscriptionMe>('/finance/subscription/'), axiosInstance.get<SubscriptionMe>('/monetization/subscription/'),
axiosInstance.get<SubscriptionPlanInfo[]>('/finance/plans/'), axiosInstance.get<SubscriptionPlanInfo[]>('/monetization/plans/'),
]); ]);
setInvoices(Array.isArray(invoiceResponse.data) ? invoiceResponse.data : []); setInvoices(Array.isArray(invoiceResponse.data) ? invoiceResponse.data : []);
setPayments(Array.isArray(paymentResponse.data) ? paymentResponse.data : []); setPayments(Array.isArray(paymentResponse.data) ? paymentResponse.data : []);
@@ -385,7 +385,7 @@ const BillingSection = (): JSX.Element => {
try { try {
const returnUrl = `${window.location.origin}/account/`; const returnUrl = `${window.location.origin}/account/`;
const response = await axiosInstance.post<{ portal_url: string }>( const response = await axiosInstance.post<{ portal_url: string }>(
'/finance/portal/', '/monetization/portal/',
{ return_url: returnUrl } { return_url: returnUrl }
); );
const portalUrl = response.data?.portal_url; const portalUrl = response.data?.portal_url;
@@ -461,7 +461,7 @@ const BillingSection = (): JSX.Element => {
...(planSlug ? { plan_slug: planSlug } : {}), ...(planSlug ? { plan_slug: planSlug } : {}),
}); });
const response = await axiosInstance.post<{ checkout_url: string }>( const response = await axiosInstance.post<{ checkout_url: string }>(
'/finance/checkout/', '/monetization/checkout/',
{ {
success_url, success_url,
cancel_url, cancel_url,
@@ -86,7 +86,7 @@ describe('Header2 (#81 subscription-aware Documents nav link)', () => {
renderHeader(); renderHeader();
await waitFor(() => expect(mockGet).toHaveBeenCalledWith('/finance/subscription/')); await waitFor(() => expect(mockGet).toHaveBeenCalledWith('/monetization/subscription/'));
expect(screen.queryByText('Documents')).not.toBeInTheDocument(); expect(screen.queryByText('Documents')).not.toBeInTheDocument();
}); });
@@ -174,8 +174,8 @@ const UsageSummaryCard = (): JSX.Element => {
setLoading(true); setLoading(true);
try { try {
const [subscriptionResponse, invoiceResponse] = await Promise.all([ const [subscriptionResponse, invoiceResponse] = await Promise.all([
axiosInstance.get<SubscriptionMe>('/finance/subscription/'), axiosInstance.get<SubscriptionMe>('/monetization/subscription/'),
axiosInstance.get<FinanceInvoice[]>('/finance/invoices/'), axiosInstance.get<FinanceInvoice[]>('/monetization/invoices/'),
]); ]);
setSubscription(subscriptionResponse.data || null); setSubscription(subscriptionResponse.data || null);
setHasPortalAccess( setHasPortalAccess(
@@ -231,7 +231,7 @@ const UsageSummaryCard = (): JSX.Element => {
try { try {
const returnUrl = `${window.location.origin}/account/`; const returnUrl = `${window.location.origin}/account/`;
const response = await axiosInstance.post<{ portal_url: string }>( const response = await axiosInstance.post<{ portal_url: string }>(
'/finance/portal/', '/monetization/portal/',
{ return_url: returnUrl } { return_url: returnUrl }
); );
if (response.data?.portal_url) { if (response.data?.portal_url) {
+3 -3
View File
@@ -6,7 +6,7 @@ import { planAllowsRag, SubscriptionMe } from '../utils/finance';
/** /**
* Module-level cache so every component that calls useSubscription() during the * Module-level cache so every component that calls useSubscription() during the
* same session shares one /finance/subscription/ request instead of each * same session shares one /monetization/subscription/ request instead of each
* mounting its own (e.g. Header2 + DocumentStoragePage on the same page). * mounting its own (e.g. Header2 + DocumentStoragePage on the same page).
*/ */
let cachedSubscription: SubscriptionMe | null = null; let cachedSubscription: SubscriptionMe | null = null;
@@ -18,7 +18,7 @@ async function loadSubscription(): Promise<SubscriptionMe | null> {
} }
if (!inFlightRequest) { if (!inFlightRequest) {
inFlightRequest = axiosInstance inFlightRequest = axiosInstance
.get<SubscriptionMe>('/finance/subscription/') .get<SubscriptionMe>('/monetization/subscription/')
.then((response: AxiosResponse<SubscriptionMe>) => { .then((response: AxiosResponse<SubscriptionMe>) => {
cachedSubscription = response.data || null; cachedSubscription = response.data || null;
return cachedSubscription; return cachedSubscription;
@@ -44,7 +44,7 @@ export type UseSubscriptionResult = {
refresh: () => Promise<void>; refresh: () => Promise<void>;
}; };
/** Fetches /finance/subscription/ once per session (shared across callers) and exposes plan-derived flags. */ /** Fetches /monetization/subscription/ once per session (shared across callers) and exposes plan-derived flags. */
export function useSubscription(): UseSubscriptionResult { export function useSubscription(): UseSubscriptionResult {
const { authenticated } = useContext(AuthContext); const { authenticated } = useContext(AuthContext);
const [subscription, setSubscription] = useState<SubscriptionMe | null>(cachedSubscription); const [subscription, setSubscription] = useState<SubscriptionMe | null>(cachedSubscription);
@@ -133,7 +133,7 @@ describe('AuthCallback', () => {
await waitFor(() => { await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith( expect(mockPost).toHaveBeenCalledWith(
'/finance/checkout/', '/monetization/checkout/',
expect.objectContaining({ expect.objectContaining({
success_url: expect.stringContaining('/billing/success'), success_url: expect.stringContaining('/billing/success'),
cancel_url: expect.stringContaining('/billing/cancel'), cancel_url: expect.stringContaining('/billing/cancel'),
@@ -181,7 +181,7 @@ const AuthCallback = (): JSX.Element => {
setStatusText('Starting checkout…'); setStatusText('Starting checkout…');
const { success_url, cancel_url } = checkoutReturnUrls(); const { success_url, cancel_url } = checkoutReturnUrls();
trackEvent(AnalyticsEvents.CHECKOUT_STARTED, { source: 'sso_signup' }); trackEvent(AnalyticsEvents.CHECKOUT_STARTED, { source: 'sso_signup' });
const checkoutResponse = await axiosInstance.post('/finance/checkout/', { const checkoutResponse = await axiosInstance.post('/monetization/checkout/', {
success_url, success_url,
cancel_url, cancel_url,
}); });
@@ -60,7 +60,7 @@ const subscriptionResponse = (rag: boolean) => ({
const mockGetByUrl = (overrides: { rag: boolean }) => { const mockGetByUrl = (overrides: { rag: boolean }) => {
mockGet.mockImplementation((url: string) => { mockGet.mockImplementation((url: string) => {
if (url === '/finance/subscription/') { if (url === '/monetization/subscription/') {
return Promise.resolve(subscriptionResponse(overrides.rag)); return Promise.resolve(subscriptionResponse(overrides.rag));
} }
if (typeof url === 'string' && url.startsWith('/documents/')) { if (typeof url === 'string' && url.startsWith('/documents/')) {
@@ -139,7 +139,7 @@ describe('SignUp', () => {
}); });
await waitFor(() => { await waitFor(() => {
expect(mockPost).toHaveBeenCalledWith('/finance/checkout/', { expect(mockPost).toHaveBeenCalledWith('/monetization/checkout/', {
success_url: 'http://localhost/billing/success?session_id={CHECKOUT_SESSION_ID}', success_url: 'http://localhost/billing/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'http://localhost/billing/cancel', cancel_url: 'http://localhost/billing/cancel',
}); });
+1 -1
View File
@@ -271,7 +271,7 @@ const SignUp = (): JSX.Element => {
const { success_url, cancel_url } = checkoutReturnUrls(); const { success_url, cancel_url } = checkoutReturnUrls();
trackEvent(AnalyticsEvents.CHECKOUT_STARTED, { source: 'signup' }); trackEvent(AnalyticsEvents.CHECKOUT_STARTED, { source: 'signup' });
const checkoutResponse = await axiosInstance.post('/finance/checkout/', { const checkoutResponse = await axiosInstance.post('/monetization/checkout/', {
success_url, success_url,
cancel_url, cancel_url,
}); });