Billing and account fixed - final

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-05 12:56:24 +00:00
parent ee4fa53987
commit 4a16a6a402
10 changed files with 176 additions and 246 deletions

View File

@@ -67,23 +67,23 @@ const AccountSettingsPage = lazy(() => import("./pages/account/AccountSettingsPa
const TeamManagementPage = lazy(() => import("./pages/account/TeamManagementPage"));
const UsageAnalyticsPage = lazy(() => import("./pages/account/UsageAnalyticsPage"));
// Admin Module - Lazy loaded
// Admin Module - Lazy loaded (mixed folder casing in repo, match actual file paths)
const AdminBilling = lazy(() => import("./pages/Admin/AdminBilling"));
const PaymentApprovalPage = lazy(() => import("./pages/Admin/PaymentApprovalPage.tsx"));
const AdminSystemDashboard = lazy(() => import("./pages/Admin/AdminSystemDashboard"));
const AdminAllAccountsPage = lazy(() => import("./pages/Admin/AdminAllAccountsPage"));
const AdminSubscriptionsPage = lazy(() => import("./pages/Admin/AdminSubscriptionsPage"));
const AdminAccountLimitsPage = lazy(() => import("./pages/Admin/AdminAccountLimitsPage"));
const AdminAllInvoicesPage = lazy(() => import("./pages/Admin/AdminAllInvoicesPage"));
const AdminAllPaymentsPage = lazy(() => import("./pages/Admin/AdminAllPaymentsPage"));
const AdminCreditPackagesPage = lazy(() => import("./pages/Admin/AdminCreditPackagesPage"));
const PaymentApprovalPage = lazy(() => import("./pages/admin/PaymentApprovalPage"));
const AdminSystemDashboard = lazy(() => import("./pages/admin/AdminSystemDashboard"));
const AdminAllAccountsPage = lazy(() => import("./pages/admin/AdminAllAccountsPage"));
const AdminSubscriptionsPage = lazy(() => import("./pages/admin/AdminSubscriptionsPage"));
const AdminAccountLimitsPage = lazy(() => import("./pages/admin/AdminAccountLimitsPage"));
const AdminAllInvoicesPage = lazy(() => import("./pages/admin/AdminAllInvoicesPage"));
const AdminAllPaymentsPage = lazy(() => import("./pages/admin/AdminAllPaymentsPage"));
const AdminCreditPackagesPage = lazy(() => import("./pages/admin/AdminCreditPackagesPage"));
const AdminCreditCostsPage = lazy(() => import("./pages/Admin/AdminCreditCostsPage"));
const AdminAllUsersPage = lazy(() => import("./pages/Admin/AdminAllUsersPage"));
const AdminRolesPermissionsPage = lazy(() => import("./pages/Admin/AdminRolesPermissionsPage"));
const AdminActivityLogsPage = lazy(() => import("./pages/Admin/AdminActivityLogsPage"));
const AdminSystemSettingsPage = lazy(() => import("./pages/Admin/AdminSystemSettingsPage"));
const AdminSystemHealthPage = lazy(() => import("./pages/Admin/AdminSystemHealthPage"));
const AdminAPIMonitorPage = lazy(() => import("./pages/Admin/AdminAPIMonitorPage"));
const AdminAllUsersPage = lazy(() => import("./pages/admin/AdminAllUsersPage"));
const AdminRolesPermissionsPage = lazy(() => import("./pages/admin/AdminRolesPermissionsPage"));
const AdminActivityLogsPage = lazy(() => import("./pages/admin/AdminActivityLogsPage"));
const AdminSystemSettingsPage = lazy(() => import("./pages/admin/AdminSystemSettingsPage"));
const AdminSystemHealthPage = lazy(() => import("./pages/admin/AdminSystemHealthPage"));
const AdminAPIMonitorPage = lazy(() => import("./pages/admin/AdminAPIMonitorPage"));
// Reference Data - Lazy loaded
const SeedKeywords = lazy(() => import("./pages/Reference/SeedKeywords"));

View File

@@ -112,7 +112,7 @@ const endpointGroups = [
{ path: "/v1/system/strategies/", method: "GET" },
{ path: "/v1/system/settings/integrations/openai/test/", method: "POST" },
{ path: "/v1/system/settings/account/", method: "GET" },
{ path: "/v1/billing/credits/balance/balance/", method: "GET" },
{ path: "/v1/billing/credits/balance/", method: "GET" },
{ path: "/v1/billing/credits/usage/", method: "GET" },
{ path: "/v1/billing/credits/usage/summary/", method: "GET" },
{ path: "/v1/billing/credits/transactions/", method: "GET" },

View File

@@ -21,23 +21,39 @@ import {
interface UserAccount {
id: number;
username: string;
email: string;
username?: string;
account_name?: string;
credits: number;
subscription_plan: string;
subscription_plan?: string;
is_active: boolean;
date_joined: string;
}
interface CreditCostConfig {
id: number;
model_name: string;
operation_type: string;
cost: number;
display_name: string;
credits_cost: number;
unit?: string;
description?: string;
is_active: boolean;
created_at: string;
}
interface CreditPackageItem {
id: number;
name: string;
slug: string;
credits: number;
price: string;
discount_percentage: number;
is_featured: boolean;
description?: string;
is_active?: boolean;
sort_order?: number;
}
interface SystemStats {
total_users: number;
active_users: number;
@@ -50,8 +66,9 @@ const AdminBilling: React.FC = () => {
const [stats, setStats] = useState<SystemStats | null>(null);
const [users, setUsers] = useState<UserAccount[]>([]);
const [creditConfigs, setCreditConfigs] = useState<CreditCostConfig[]>([]);
const [creditPackages, setCreditPackages] = useState<CreditPackageItem[]>([]);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'pricing'>('overview');
const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'pricing' | 'packages'>('overview');
const [searchTerm, setSearchTerm] = useState('');
const [selectedUser, setSelectedUser] = useState<UserAccount | null>(null);
const [creditAmount, setCreditAmount] = useState('');
@@ -65,17 +82,19 @@ const AdminBilling: React.FC = () => {
try {
setLoading(true);
const [statsData, usersData, configsData] = await Promise.all([
// Admin billing stats (credits, activity, revenue)
fetchAPI('/v1/billing/admin/stats/'),
// Admin billing users list (with credits)
fetchAPI('/v1/admin/billing/users/?limit=100'),
// Admin billing credit costs
fetchAPI('/v1/admin/billing/credit-costs/'),
// Admin billing stats (modules admin endpoints)
fetchAPI('/v1/admin/billing/stats/'),
// Admin users with credits
fetchAPI('/v1/admin/users/'),
// Admin credit costs (modules billing)
fetchAPI('/v1/admin/credit-costs/'),
]);
const packagesData = await fetchAPI('/v1/billing/credit-packages/');
setStats(statsData);
setUsers(usersData.results || []);
setCreditConfigs(configsData.results || []);
setCreditPackages(packagesData.results || []);
} catch (error: any) {
toast?.error(error?.message || 'Failed to load admin data');
} finally {
@@ -90,7 +109,7 @@ const AdminBilling: React.FC = () => {
}
try {
await fetchAPI(`/v1/admin/billing/users/${selectedUser.id}/adjust-credits/`, {
await fetchAPI(`/v1/admin/users/${selectedUser.id}/adjust-credits/`, {
method: 'POST',
body: JSON.stringify({
amount: parseInt(creditAmount),
@@ -110,9 +129,9 @@ const AdminBilling: React.FC = () => {
const handleUpdateCreditCost = async (configId: number, newCost: number) => {
try {
await fetchAPI(`/v1/admin/billing/credit-costs/${configId}/`, {
method: 'PATCH',
body: JSON.stringify({ cost: newCost }),
await fetchAPI('/v1/admin/credit-costs/', {
method: 'POST',
body: JSON.stringify({ updates: [{ id: configId, cost: newCost }] }),
});
toast?.success('Credit cost updated successfully');
@@ -123,10 +142,26 @@ const AdminBilling: React.FC = () => {
};
const filteredUsers = users.filter(user =>
user.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase())
(user.email || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
(user.username || '').toLowerCase().includes(searchTerm.toLowerCase()) ||
(user.account_name || '').toLowerCase().includes(searchTerm.toLowerCase())
);
const formatLabel = (value?: string) =>
(value || '')
.split('_')
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : ''))
.join(' ')
.trim();
const updateLocalCost = (id: number, value: string) => {
setCreditConfigs((prev) =>
prev.map((c) =>
c.id === id ? { ...c, credits_cost: value === '' ? ('' as any) : Number(value) } : c
)
);
};
if (loading) {
return (
<div className="p-6">
@@ -220,6 +255,16 @@ const AdminBilling: React.FC = () => {
>
Credit Pricing ({creditConfigs.length})
</button>
<button
onClick={() => setActiveTab('packages')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'packages'
? 'border-primary-500 text-primary-600 dark:text-primary-400'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300'
}`}
>
Credit Packages ({creditPackages.length})
</button>
</nav>
</div>
@@ -399,19 +444,22 @@ const AdminBilling: React.FC = () => {
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50 dark:bg-gray-800">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Model
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Operation
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Display Name
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Credits Cost
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Unit
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Description
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Cost (Credits)
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Status
</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">
Actions
</th>
</tr>
@@ -419,27 +467,34 @@ const AdminBilling: React.FC = () => {
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
{creditConfigs.map((config) => (
<tr key={config.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{config.model_name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-400">
<td className="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-700 dark:text-gray-300">
{config.operation_type}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-bold text-amber-600 dark:text-amber-400">
{config.cost}
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 dark:text-white">
{config.display_name || formatLabel(config.operation_type)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Badge tone={config.is_active ? 'success' : 'warning'}>
{config.is_active ? 'Active' : 'Inactive'}
</Badge>
<td className="px-6 py-4">
<input
type="number"
value={config.credits_cost as any}
onChange={(e) => updateLocalCost(config.id, e.target.value)}
className="w-24 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
/>
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
{formatLabel(config.unit)}
</td>
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400 max-w-md">
{config.description || '—'}
</td>
<td className="px-6 py-4 text-right">
<Button
variant="outline"
size="sm"
onClick={() => window.open(`/admin/igny8_core/creditcostconfig/${config.id}/change/`, '_blank')}
variant="primary"
tone="brand"
onClick={() => handleUpdateCreditCost(config.id, Number(config.credits_cost) || 0)}
>
Edit
Save
</Button>
</td>
</tr>
@@ -447,16 +502,37 @@ const AdminBilling: React.FC = () => {
</tbody>
</table>
</div>
<div className="mt-4 text-sm text-gray-600 dark:text-gray-400">
To add new credit costs or modify these settings, use the{' '}
<a
href="/admin/igny8_core/creditcostconfig/"
target="_blank"
rel="noopener noreferrer"
className="text-primary-600 dark:text-primary-400 hover:underline"
>
Django Admin Panel
</a>
</ComponentCard>
)}
{activeTab === 'packages' && (
<ComponentCard title="Credit Packages">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{creditPackages.map((pkg) => (
<ComponentCard key={pkg.id} title={pkg.name} desc={pkg.description || ''}>
<div className="space-y-3">
<div className="text-3xl font-bold text-blue-600">{pkg.credits.toLocaleString()}</div>
<div className="text-sm text-gray-500">credits</div>
<div className="text-2xl font-semibold text-gray-900 dark:text-white">${pkg.price}</div>
{pkg.discount_percentage > 0 && (
<div className="text-sm text-green-600">Save {pkg.discount_percentage}%</div>
)}
<div className="flex items-center gap-2">
<Badge variant="light" color={pkg.is_active ? 'success' : 'warning'}>
{pkg.is_active ? 'Active' : 'Inactive'}
</Badge>
{pkg.is_featured && (
<Badge variant="light" color="primary">
Featured
</Badge>
)}
</div>
</div>
</ComponentCard>
))}
{creditPackages.length === 0 && (
<div className="col-span-3 text-center py-8 text-gray-500">No credit packages found.</div>
)}
</div>
</ComponentCard>
)}

View File

@@ -158,164 +158,4 @@ export default function AdminCreditCostsPage() {
</div>
);
}
/**
* Admin Credit Costs Page
* Manage credit pricing per billable action
*/
import { useEffect, useState } from 'react';
import { AlertCircle, Loader2, Check } from 'lucide-react';
import PageMeta from '../../components/common/PageMeta';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import {
getCreditCosts,
updateCreditCosts,
type CreditCostConfig,
} from '../../services/billing.api';
export default function AdminCreditCostsPage() {
const [costs, setCosts] = useState<CreditCostConfig[]>([]);
const [loading, setLoading] = useState(true);
const [savingId, setSavingId] = useState<number | null>(null);
const [error, setError] = useState<string>('');
useEffect(() => {
loadCosts();
}, []);
const loadCosts = async () => {
try {
setLoading(true);
const data = await getCreditCosts();
setCosts(data.results || []);
} catch (err: any) {
setError(err.message || 'Failed to load credit costs');
} finally {
setLoading(false);
}
};
const handleSave = async (cost: CreditCostConfig) => {
try {
setSavingId(cost.id);
await updateCreditCosts([
{ operation_type: cost.operation_type, credits_cost: Number(cost.credits_cost) || 0 },
]);
await loadCosts();
} catch (err: any) {
setError(err.message || 'Failed to update credit cost');
} finally {
setSavingId(null);
}
};
const updateLocalCost = (id: number, value: string) => {
setCosts((prev) =>
prev.map((c) => (c.id === id ? { ...c, credits_cost: value as any } : c)),
);
};
if (loading) {
return (
<div className="p-6 flex items-center justify-center min-h-screen">
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Admin - Credit Costs" description="Manage credit pricing per action" />
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Credit Costs</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Configure credits required for each billable operation.
</p>
</div>
{error && (
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg flex items-center gap-3">
<AlertCircle className="w-5 h-5 text-red-600" />
<p className="text-red-800 dark:text-red-200">{error}</p>
</div>
)}
<Card className="overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Operation
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Display Name
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Credits Cost
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Unit
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Description
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{costs.length === 0 ? (
<tr>
<td colSpan={6} className="px-6 py-8 text-center text-gray-500">
No credit costs configured
</td>
</tr>
) : (
costs.map((cost) => (
<tr key={cost.id} className="hover:bg-gray-50 dark:hover:bg-gray-800">
<td className="px-6 py-4 text-sm font-mono text-gray-700 dark:text-gray-300">
{cost.operation_type}
</td>
<td className="px-6 py-4 text-sm text-gray-900 dark:text-white">
{cost.display_name || '-'}
</td>
<td className="px-6 py-4">
<input
type="number"
value={cost.credits_cost as any}
onChange={(e) => updateLocalCost(cost.id, e.target.value)}
className="w-24 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white"
/>
</td>
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">
{cost.unit}
</td>
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400 max-w-md">
{cost.description}
</td>
<td className="px-6 py-4 text-right">
<Button
size="sm"
variant="primary"
tone="brand"
startIcon={savingId === cost.id ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
disabled={savingId === cost.id}
onClick={() => handleSave(cost)}
>
{savingId === cost.id ? 'Saving...' : 'Save'}
</Button>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</Card>
</div>
);
}

View File

@@ -107,7 +107,7 @@ const endpointGroups: EndpointGroup[] = [
{ path: "/v1/system/strategies/", method: "GET", description: "List strategies" },
{ path: "/v1/system/settings/integrations/openai/test/", method: "POST", description: "Test integration (OpenAI)" },
{ path: "/v1/system/settings/account/", method: "GET", description: "Account settings" },
{ path: "/v1/billing/credits/balance/balance/", method: "GET", description: "Credit balance" },
{ path: "/v1/billing/credits/balance/", method: "GET", description: "Credit balance" },
{ path: "/v1/billing/credits/usage/", method: "GET", description: "Usage logs" },
{ path: "/v1/billing/credits/usage/summary/", method: "GET", description: "Usage summary" },
{

View File

@@ -7,7 +7,7 @@ import { useState, useEffect } from 'react';
import { Plus, Loader2, AlertCircle, Edit, Trash } from 'lucide-react';
import { Card } from '../../components/ui/card';
import Badge from '../../components/ui/badge/Badge';
import { getCreditPackages, type CreditPackage } from '../../services/billing.api';
import { getAdminCreditPackages, type CreditPackage } from '../../services/billing.api';
export default function AdminCreditPackagesPage() {
const [packages, setPackages] = useState<CreditPackage[]>([]);
@@ -21,7 +21,7 @@ export default function AdminCreditPackagesPage() {
const loadPackages = async () => {
try {
setLoading(true);
const data = await getCreditPackages();
const data = await getAdminCreditPackages();
setPackages(data.results || []);
} catch (err: any) {
setError(err.message || 'Failed to load credit packages');

View File

@@ -1743,12 +1743,12 @@ export interface UsageSummary {
export async function fetchCreditBalance(): Promise<CreditBalance> {
try {
const response = await fetchAPI('/v1/billing/credits/balance/balance/');
// fetchAPI automatically extracts data field from unified format
// Canonical balance endpoint (business billing CreditTransactionViewSet.balance)
const response = await fetchAPI('/v1/billing/transactions/balance/');
if (response && typeof response === 'object' && 'credits' in response) {
return response as CreditBalance;
}
// Return default if response is invalid
// Default if response is invalid
return {
credits: 0,
plan_credits_per_month: 0,
@@ -1756,7 +1756,7 @@ export async function fetchCreditBalance(): Promise<CreditBalance> {
credits_remaining: 0,
};
} catch (error: any) {
console.warn('Failed to fetch credit balance, using defaults:', error.message);
console.debug('Failed to fetch credit balance, using defaults:', error?.message || error);
// Return default balance on error so UI can still render
return {
credits: 0,

View File

@@ -1,6 +1,6 @@
/**
* Billing API Service
* Uses STANDARD existing billing endpoints from /v1/billing/, /v1/system/, and /v1/admin/
* Uses STANDARD billing endpoints from /api/v1/billing and /api/v1/admin/billing
*/
import { fetchAPI } from './api';
@@ -142,6 +142,9 @@ export interface CreditPackage {
is_featured: boolean;
description: string;
display_order: number;
is_active?: boolean;
sort_order?: number;
stripe_price_id?: string | null;
}
export interface TeamMember {
@@ -188,6 +191,7 @@ export interface PendingPayment extends Payment {
// ============================================================================
export async function getCreditBalance(): Promise<CreditBalance> {
// Use business billing CreditTransactionViewSet.balance
return fetchAPI('/v1/billing/transactions/balance/');
}
@@ -196,7 +200,7 @@ export async function getCreditTransactions(): Promise<{
count: number;
current_balance?: number;
}> {
return fetchAPI('/v1/billing/transactions/');
return fetchAPI('/api/v1/billing/transactions/');
}
// ============================================================================
@@ -216,7 +220,7 @@ export async function getCreditUsage(params?: {
if (params?.start_date) queryParams.append('start_date', params.start_date);
if (params?.end_date) queryParams.append('end_date', params.end_date);
const url = `/v1/billing/credits/usage/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
const url = `/api/v1/billing/credits/usage/${queryParams.toString() ? '?' + queryParams.toString() : ''}`;
return fetchAPI(url);
}
@@ -261,8 +265,8 @@ export async function getCreditUsageLimits(): Promise<{
// ============================================================================
export async function getAdminBillingStats(): Promise<AdminBillingStats> {
// Use business billing admin stats endpoint (returns all dashboard metrics)
return fetchAPI('/v1/billing/admin/stats/');
// Admin billing dashboard metrics
return fetchAPI('/v1/admin/billing/stats/');
}
export async function getAdminInvoices(params?: { status?: string; account_id?: number; search?: string }): Promise<{
@@ -332,7 +336,8 @@ export async function getCreditCosts(): Promise<{
results: CreditCostConfig[];
count: number;
}> {
return fetchAPI('/v1/admin/billing/credit-costs/');
// credit costs are served from the admin namespace (modules billing)
return fetchAPI('/v1/admin/credit-costs/');
}
export async function updateCreditCosts(
@@ -344,7 +349,7 @@ export async function updateCreditCosts(
message: string;
updated_count: number;
}> {
return fetchAPI('/v1/admin/billing/credit-costs/', {
return fetchAPI('/v1/admin/credit-costs/', {
method: 'POST',
body: JSON.stringify({ costs }),
});
@@ -434,6 +439,15 @@ export async function getCreditPackages(): Promise<{
return fetchAPI('/v1/billing/credit-packages/');
}
// Admin credit packages (CRUD capable backend; currently read-only here)
export async function getAdminCreditPackages(): Promise<{
results: CreditPackage[];
count: number;
}> {
// Backend does not expose a dedicated admin credit-packages list; fall back to the shared packages list
return fetchAPI('/v1/billing/credit-packages/');
}
export async function purchaseCreditPackage(data: {
package_id: number;
payment_method: 'stripe' | 'paypal' | 'bank_transfer' | 'local_wallet';
@@ -447,7 +461,7 @@ export async function purchaseCreditPackage(data: {
stripe_client_secret?: string;
paypal_order_id?: string;
}> {
return fetchAPI(`/v1/billing/credit-packages/${data.package_id}/purchase/`, {
return fetchAPI(`/api/v1/billing/credit-packages/${data.package_id}/purchase/`, {
method: 'POST',
body: JSON.stringify({ payment_method: data.payment_method }),
});
@@ -495,7 +509,7 @@ export async function getAvailablePaymentMethods(): Promise<{
results: PaymentMethod[];
count: number;
}> {
return fetchAPI('/v1/billing/payment-methods/');
return fetchAPI('/api/v1/billing/payment-methods/');
}
export async function createManualPayment(data: {
@@ -509,7 +523,7 @@ export async function createManualPayment(data: {
status: string;
message: string;
}> {
return fetchAPI('/v1/billing/payments/manual/', {
return fetchAPI('/api/v1/billing/payments/manual/', {
method: 'POST',
body: JSON.stringify(data),
});
@@ -523,7 +537,7 @@ export async function getPendingPayments(): Promise<{
results: PendingPayment[];
count: number;
}> {
return fetchAPI('/v1/billing/admin/pending_payments/');
return fetchAPI('/api/v1/billing/admin/pending_payments/');
}
export async function approvePayment(paymentId: number, data?: {
@@ -532,7 +546,7 @@ export async function approvePayment(paymentId: number, data?: {
message: string;
payment: Payment;
}> {
return fetchAPI(`/v1/billing/admin/${paymentId}/approve_payment/`, {
return fetchAPI(`/api/v1/billing/admin/${paymentId}/approve_payment/`, {
method: 'POST',
body: JSON.stringify(data || {}),
});
@@ -545,7 +559,7 @@ export async function rejectPayment(paymentId: number, data: {
message: string;
payment: Payment;
}> {
return fetchAPI(`/v1/billing/admin/${paymentId}/reject_payment/`, {
return fetchAPI(`/api/v1/billing/admin/${paymentId}/reject_payment/`, {
method: 'POST',
body: JSON.stringify(data),
});