Point billing clients at /monetization/ instead of /finance/.
Unit Tests / test (pull_request) Failing after 11s
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:
@@ -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) {
|
||||||
|
|||||||
@@ -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',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user