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:
IGNY8 VPS (Salman)
2025-12-07 10:07:28 +00:00
parent 46fc6dcf04
commit 508b6b4220
26 changed files with 518 additions and 69 deletions

View File

@@ -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: {