Enhance billing and subscription management: Added payment method checks in ProtectedRoute, improved error handling in billing components, and optimized API calls to reduce throttling. Updated user account handling in various components to ensure accurate plan and subscription data display.
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
|
||||
import { fetchAPI } from './api';
|
||||
|
||||
// Coalesce concurrent credit balance requests to avoid hitting throttle for normal users
|
||||
let creditBalanceInFlight: Promise<CreditBalance> | null = null;
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
// ============================================================================
|
||||
@@ -193,8 +196,17 @@ export interface PendingPayment extends Payment {
|
||||
// ============================================================================
|
||||
|
||||
export async function getCreditBalance(): Promise<CreditBalance> {
|
||||
// Use business billing CreditTransactionViewSet.balance
|
||||
return fetchAPI('/v1/billing/credits/balance/');
|
||||
// Use canonical balance endpoint (transactions/balance) and coalesce concurrent calls
|
||||
if (creditBalanceInFlight) {
|
||||
return creditBalanceInFlight;
|
||||
}
|
||||
|
||||
creditBalanceInFlight = fetchAPI('/v1/billing/transactions/balance/');
|
||||
try {
|
||||
return await creditBalanceInFlight;
|
||||
} finally {
|
||||
creditBalanceInFlight = null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCreditTransactions(): Promise<{
|
||||
@@ -640,7 +652,11 @@ export async function getAvailablePaymentMethods(): Promise<{
|
||||
results: PaymentMethod[];
|
||||
count: number;
|
||||
}> {
|
||||
return fetchAPI('/v1/billing/payment-methods/');
|
||||
const response = await fetchAPI('/v1/billing/payment-methods/');
|
||||
// Frontend guard: only allow the simplified set we currently support
|
||||
const allowed = new Set(['bank_transfer', 'manual']);
|
||||
const filtered = (response.results || []).filter((m: PaymentMethod) => allowed.has(m.type));
|
||||
return { results: filtered, count: filtered.length };
|
||||
}
|
||||
|
||||
export async function createPaymentMethod(data: {
|
||||
@@ -830,11 +846,53 @@ export interface Subscription {
|
||||
}
|
||||
|
||||
export async function getPlans(): Promise<{ results: Plan[] }> {
|
||||
return fetchAPI('/v1/auth/plans/');
|
||||
// Coalesce concurrent plan fetches to avoid 429s on first load
|
||||
if (!(getPlans as any)._inFlight) {
|
||||
(getPlans as any)._inFlight = fetchAPI('/v1/auth/plans/').finally(() => {
|
||||
(getPlans as any)._inFlight = null;
|
||||
});
|
||||
}
|
||||
return (getPlans as any)._inFlight;
|
||||
}
|
||||
|
||||
export async function getSubscriptions(): Promise<{ results: Subscription[] }> {
|
||||
return fetchAPI('/v1/auth/subscriptions/');
|
||||
const now = Date.now();
|
||||
const self: any = getSubscriptions as any;
|
||||
|
||||
// Return cached result if fetched within 5s to avoid hitting throttle
|
||||
if (self._lastResult && self._lastFetched && now - self._lastFetched < 5000) {
|
||||
return self._lastResult;
|
||||
}
|
||||
|
||||
// Respect cooldown if previous call was throttled
|
||||
if (self._cooldownUntil && now < self._cooldownUntil) {
|
||||
if (self._lastResult) return self._lastResult;
|
||||
return { results: [] };
|
||||
}
|
||||
|
||||
// Coalesce concurrent subscription fetches
|
||||
if (!self._inFlight) {
|
||||
self._inFlight = fetchAPI('/v1/auth/subscriptions/')
|
||||
.then((res) => {
|
||||
self._lastResult = res;
|
||||
self._lastFetched = Date.now();
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err?.status === 429) {
|
||||
// Set a short cooldown to prevent immediate re-hits
|
||||
self._cooldownUntil = Date.now() + 5000;
|
||||
// Return cached or empty to avoid surfacing the 429 upstream
|
||||
return self._lastResult || { results: [] };
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
.finally(() => {
|
||||
self._inFlight = null;
|
||||
});
|
||||
}
|
||||
|
||||
return self._inFlight;
|
||||
}
|
||||
|
||||
export async function createSubscription(data: {
|
||||
|
||||
Reference in New Issue
Block a user