veza/apps/web/src/services/commerceService.ts
senke 8e9431fe93 feat(commerce): replace mock purchases with real API calls
CLN-02: getPurchases() now calls GET /marketplace/orders;
requestRefund() calls POST /marketplace/orders/:id/refund.
Removed MOCK_PURCHASES constant. MSW handler updated.
2026-02-22 17:44:29 +01:00

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 };
},
};