CLN-02: getPurchases() now calls GET /marketplace/orders; requestRefund() calls POST /marketplace/orders/:id/refund. Removed MOCK_PURCHASES constant. MSW handler updated.
166 lines
4.9 KiB
TypeScript
166 lines
4.9 KiB
TypeScript
import { apiClient } from '@/services/api/client';
|
|
import { Purchase, Product } from '../types';
|
|
|
|
interface OrderItemApi {
|
|
id?: string;
|
|
product_id: string;
|
|
price: number;
|
|
title?: string;
|
|
}
|
|
|
|
interface OrderApi {
|
|
id: string;
|
|
status: string;
|
|
total_amount?: number;
|
|
total?: number;
|
|
created_at: string;
|
|
items?: OrderItemApi[];
|
|
}
|
|
|
|
interface SellerSale {
|
|
order_id: string;
|
|
product_id: string;
|
|
product_title: string;
|
|
buyer_id: string;
|
|
amount: number;
|
|
date: string;
|
|
}
|
|
|
|
interface StatsEvolutionPoint {
|
|
date: string;
|
|
revenue: number;
|
|
sales_count: number;
|
|
}
|
|
|
|
interface TopProductStats {
|
|
product_id: string;
|
|
title: string;
|
|
revenue: number;
|
|
sales_count: number;
|
|
}
|
|
|
|
function formatRelativeDate(dateStr: string): string {
|
|
const d = new Date(dateStr);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - d.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
const diffDays = Math.floor(diffMs / 86400000);
|
|
if (diffMins < 60) return `${diffMins} mins ago`;
|
|
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
|
|
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
|
|
return d.toLocaleDateString();
|
|
}
|
|
|
|
function mapOrderToPurchases(order: OrderApi): Purchase[] {
|
|
const orderId = typeof order.id === 'string' ? order.id : String(order.id);
|
|
const dateStr = order.created_at
|
|
? new Date(order.created_at).toISOString().slice(0, 10)
|
|
: '';
|
|
const items = order.items ?? [];
|
|
if (items.length === 0) {
|
|
const total = order.total_amount ?? order.total ?? 0;
|
|
return [
|
|
{
|
|
id: orderId,
|
|
orderId,
|
|
date: dateStr,
|
|
price: total,
|
|
status: order.status,
|
|
downloadUrl: '#',
|
|
license: { id: 'lic-default', name: 'Standard', price: total, features: [] },
|
|
product: {
|
|
id: 'unknown',
|
|
title: 'Order',
|
|
type: 'pack',
|
|
price: total,
|
|
currency: 'USD',
|
|
coverUrl: '',
|
|
} as Partial<Product> as Product,
|
|
},
|
|
];
|
|
}
|
|
return items.map((item, idx) => {
|
|
const productId = item.product_id ?? 'unknown';
|
|
const price = item.price ?? 0;
|
|
const title = item.title ?? productId;
|
|
return {
|
|
id: `${orderId}-${item.id ?? idx}`,
|
|
orderId,
|
|
date: dateStr,
|
|
price,
|
|
status: order.status,
|
|
downloadUrl: '#',
|
|
license: { id: `lic-${productId}`, name: 'Standard', price, features: [] },
|
|
product: {
|
|
id: productId,
|
|
title,
|
|
type: 'pack',
|
|
price,
|
|
currency: 'USD',
|
|
coverUrl: '',
|
|
} as Partial<Product> as Product,
|
|
};
|
|
});
|
|
}
|
|
|
|
export const commerceService = {
|
|
getPurchases: async (): Promise<Purchase[]> => {
|
|
const response = await apiClient.get<OrderApi[]>('/marketplace/orders');
|
|
const orders = Array.isArray(response.data) ? response.data : [];
|
|
return orders.flatMap(mapOrderToPurchases);
|
|
},
|
|
|
|
// v0.401 M3: Real API call to GET /sell/sales
|
|
getSales: async () => {
|
|
const response = await apiClient.get<{ data?: { sales?: SellerSale[] }; sales?: SellerSale[] }>('/sell/sales');
|
|
const data = response.data?.data ?? response.data;
|
|
const sales = (data as { sales?: SellerSale[] })?.sales ?? [];
|
|
return sales.map((s) => ({
|
|
id: s.order_id,
|
|
product: s.product_title,
|
|
date: formatRelativeDate(s.date),
|
|
amount: s.amount,
|
|
buyer: s.buyer_id?.slice(0, 8) ?? '—',
|
|
}));
|
|
},
|
|
|
|
getSellerStats: async () => {
|
|
const response = await apiClient.get<{ data?: { revenue?: number; sales?: number; sales_count?: number } }>(
|
|
'/sell/stats'
|
|
);
|
|
const data = (response.data?.data ?? response.data) as Record<string, unknown> | undefined;
|
|
return {
|
|
revenue: (data?.revenue as number) ?? 0,
|
|
sales: (data?.sales_count ?? data?.sales) as number ?? 0,
|
|
views: 0,
|
|
conversion: null as number | null,
|
|
};
|
|
},
|
|
|
|
// v0.401 M3: Stats evolution for chart
|
|
getSellerStatsEvolution: async (period: 'day' | 'week' | 'month' = 'week') => {
|
|
const response = await apiClient.get<{ data?: { evolution?: StatsEvolutionPoint[] }; evolution?: StatsEvolutionPoint[] }>(
|
|
`/sell/stats/evolution?period=${period}`
|
|
);
|
|
const data = response.data?.data ?? response.data;
|
|
return (data as { evolution?: StatsEvolutionPoint[] })?.evolution ?? [];
|
|
},
|
|
|
|
// v0.401 M3: Top products by revenue
|
|
getSellerTopProducts: async (limit = 10) => {
|
|
const response = await apiClient.get<{ data?: { top_products?: TopProductStats[] }; top_products?: TopProductStats[] }>(
|
|
`/sell/stats/top-products?limit=${limit}`
|
|
);
|
|
const data = response.data?.data ?? response.data;
|
|
return (data as { top_products?: TopProductStats[] })?.top_products ?? [];
|
|
},
|
|
|
|
requestRefund: async (orderId: string, reason: string) => {
|
|
await apiClient.post(`/marketplace/orders/${orderId}/refund`, {
|
|
reason: reason || 'Requested by customer',
|
|
details: '',
|
|
});
|
|
return { success: true };
|
|
},
|
|
};
|