Files
Qris-Soundbox/src/shared/events/transactionEvents.ts
2026-05-25 08:22:12 +07:00

88 lines
2.7 KiB
TypeScript

export type TransactionPaidPayload = {
transaction_id: string;
merchant_id: string;
outlet_id: string;
terminal_id: string;
device_id: string | null | undefined;
amount: number;
currency: string;
partner_reference: string;
paid_at?: string;
};
type TransactionPaidInternalEvent = {
id: string;
event_type: "transaction.paid";
transaction_id: string;
payload_json: TransactionPaidPayload;
created_at: string;
};
export type TransactionPaidEvent = TransactionPaidInternalEvent;
import { randomUUID } from "node:crypto";
const transactionPaidEvents = new Map<string, TransactionPaidInternalEvent[]>();
const transactionPaidIndex = new Map<string, string>();
const transactionPaidSubscribers = new Set<(event: TransactionPaidEvent) => void>();
function nowIso() {
return new Date().toISOString();
}
function cloneInternalEvent(event: TransactionPaidInternalEvent): TransactionPaidInternalEvent {
return {
...event,
payload_json: { ...event.payload_json }
};
}
export function subscribeTransactionPaid(handler: (event: TransactionPaidEvent) => void) {
transactionPaidSubscribers.add(handler);
return () => transactionPaidSubscribers.delete(handler);
}
export function emitTransactionPaid(payload: TransactionPaidPayload): TransactionPaidEvent {
const existingId = transactionPaidIndex.get(payload.transaction_id);
if (existingId) {
const events = transactionPaidEvents.get(payload.transaction_id) || [];
const existing = events.find((event) => event.id === existingId);
if (existing) {
return cloneInternalEvent(existing);
}
}
const event: TransactionPaidInternalEvent = {
id: randomUUID(),
event_type: "transaction.paid",
transaction_id: payload.transaction_id,
payload_json: payload,
created_at: nowIso()
};
const bucket = transactionPaidEvents.get(payload.transaction_id) || [];
bucket.push(event);
transactionPaidEvents.set(payload.transaction_id, bucket);
transactionPaidIndex.set(payload.transaction_id, event.id);
const frozen = cloneInternalEvent(event);
for (const listener of Array.from(transactionPaidSubscribers)) {
listener(frozen);
}
return frozen;
}
export function getTransactionPaidEvents(transactionId?: string): TransactionPaidEvent[] {
if (transactionId) {
return (transactionPaidEvents.get(transactionId) || []).map(cloneInternalEvent);
}
return Array.from(transactionPaidEvents.values()).flatMap((bucket) => bucket.map(cloneInternalEvent));
}
export function getTransactionPaidEventByTransactionId(transactionId: string): TransactionPaidEvent | null {
const events = transactionPaidEvents.get(transactionId) || [];
return events.length > 0 ? cloneInternalEvent(events[events.length - 1]) : null;
}