2026-08-13 07:15:59 +04:00
|
|
|
import { Injectable, inject } from '@angular/core';
|
2026-07-15 10:57:29 +04:00
|
|
|
import { Observable, of } from 'rxjs';
|
|
|
|
|
import { delay } from 'rxjs/operators';
|
|
|
|
|
import { AdminTransaction, AdminTransactionListFilters, AdminTransactionsListResult } from '../models/admin-transaction.model';
|
|
|
|
|
import { AdminTransactionsGateway } from './admin-transactions-gateway.interface';
|
|
|
|
|
import { AdminOrdersLocalGateway } from '../../orders/services/admin-orders-local.gateway';
|
2026-08-18 01:05:16 +04:00
|
|
|
import { AdminAuthService } from '@marketplaces/auth';
|
2026-07-15 10:57:29 +04:00
|
|
|
|
|
|
|
|
const METHODS = ['card', 'qr', 'cash_on_delivery'];
|
|
|
|
|
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
|
|
export class AdminTransactionsLocalGateway implements AdminTransactionsGateway {
|
2026-08-13 07:15:59 +04:00
|
|
|
private readonly adminAuth = inject(AdminAuthService);
|
2026-07-15 10:57:29 +04:00
|
|
|
private cache: AdminTransaction[] | null = null;
|
|
|
|
|
|
|
|
|
|
constructor(private readonly ordersGateway: AdminOrdersLocalGateway) {}
|
|
|
|
|
|
2026-08-13 07:15:59 +04:00
|
|
|
private get currentActor(): string {
|
|
|
|
|
return this.adminAuth.displayName() ?? 'admin';
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-15 10:57:29 +04:00
|
|
|
loadTransactions(filters: AdminTransactionListFilters): Observable<AdminTransactionsListResult> {
|
|
|
|
|
return new Observable<AdminTransactionsListResult>(subscriber => {
|
|
|
|
|
this.ensureData().then(() => {
|
|
|
|
|
const filtered = (this.cache ?? [])
|
|
|
|
|
.filter(tx => !filters.search || `${tx.orderNumber} ${tx.method}`.toLowerCase().includes(filters.search.toLowerCase()))
|
|
|
|
|
.filter(tx => filters.status === 'all' || tx.status === filters.status)
|
|
|
|
|
.filter(tx => filters.type === 'all' || tx.type === filters.type)
|
|
|
|
|
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
|
|
|
const start = (filters.page - 1) * filters.pageSize;
|
|
|
|
|
subscriber.next({
|
|
|
|
|
items: filtered.slice(start, start + filters.pageSize),
|
|
|
|
|
total: filtered.length,
|
|
|
|
|
page: filters.page,
|
|
|
|
|
pageSize: filters.pageSize,
|
|
|
|
|
});
|
|
|
|
|
subscriber.complete();
|
|
|
|
|
});
|
|
|
|
|
}).pipe(delay(50));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retryFailed(id: string): Observable<AdminTransaction | null> {
|
|
|
|
|
return this.mutate(id, tx => ({
|
|
|
|
|
...tx,
|
|
|
|
|
status: 'retried',
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
2026-08-13 07:15:59 +04:00
|
|
|
audit: [...tx.audit, { action: 'Retried failed transaction', actor: this.currentActor, timestamp: new Date().toISOString() }],
|
2026-07-15 10:57:29 +04:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setFraudFlag(id: string, flagged: boolean): Observable<AdminTransaction | null> {
|
|
|
|
|
return this.mutate(id, tx => ({
|
|
|
|
|
...tx,
|
|
|
|
|
fraudFlag: flagged,
|
|
|
|
|
updatedAt: new Date().toISOString(),
|
2026-08-13 07:15:59 +04:00
|
|
|
audit: [...tx.audit, { action: flagged ? 'Flagged as fraud' : 'Fraud flag cleared', actor: this.currentActor, timestamp: new Date().toISOString() }],
|
2026-07-15 10:57:29 +04:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private mutate(id: string, update: (tx: AdminTransaction) => AdminTransaction): Observable<AdminTransaction | null> {
|
|
|
|
|
const existing = (this.cache ?? []).find(tx => tx.id === id);
|
|
|
|
|
if (!existing) {
|
|
|
|
|
return of(null);
|
|
|
|
|
}
|
|
|
|
|
const updated = update(existing);
|
|
|
|
|
this.cache = (this.cache ?? []).map(tx => tx.id === id ? updated : tx);
|
|
|
|
|
return of(updated).pipe(delay(50));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async ensureData(): Promise<void> {
|
|
|
|
|
if (this.cache) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const orders = await new Promise<{ id: string; orderNumber: string; total: number; currency: string; status: string; createdAt: string }[]>(resolve =>
|
|
|
|
|
this.ordersGateway.loadOrders({ search: '', status: 'all', page: 1, pageSize: 1000 }).subscribe(result => resolve(result.items))
|
|
|
|
|
);
|
|
|
|
|
this.cache = orders.map((order, index) => this.seedTransaction(order, index));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private seedTransaction(order: { id: string; orderNumber: string; total: number; currency: string; status: string; createdAt: string }, index: number): AdminTransaction {
|
|
|
|
|
const status = order.status === 'cancelled' ? 'failed' : order.status === 'refunded' ? 'success' : index % 7 === 0 ? 'pending' : 'success';
|
|
|
|
|
return {
|
|
|
|
|
id: `tx-${index + 1}`,
|
|
|
|
|
orderId: order.id,
|
|
|
|
|
orderNumber: order.orderNumber,
|
|
|
|
|
type: order.status === 'refunded' ? 'refund' : index % 3 === 0 ? 'qr_payment' : 'payment',
|
|
|
|
|
method: METHODS[index % METHODS.length],
|
|
|
|
|
status,
|
|
|
|
|
amount: order.total,
|
|
|
|
|
currency: order.currency,
|
|
|
|
|
fraudFlag: index % 11 === 0,
|
|
|
|
|
audit: [{ action: 'Transaction created', actor: 'system', timestamp: order.createdAt }],
|
|
|
|
|
createdAt: order.createdAt,
|
|
|
|
|
updatedAt: order.createdAt,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|