trading-platform-backend-v2/src/modules/payments/services/refund.service.ts
Adrian Flores Cortes 5e03e15916 [TASK-2026-02-03-BACKEND-ENTITIES-SYNC] feat: Create backend services for new DDL tables
Services created:
- education/instructor.service.ts: CRUD for education.instructors
- education/tag.service.ts: Course tags and assignments management
- trading/drawing.service.ts: Chart drawing tools persistence
- ml/prediction-overlay.service.ts: ML prediction overlays CRUD
- payments/refund.service.ts: Stripe refund workflow management

Types added:
- Instructor, CourseTag, CourseTagAssignment interfaces
- DrawingTool, DrawingTemplate, DrawingToolType types
- PredictionOverlay, OverlayType, OverlayStyleConfig
- Refund, RefundStatus, RefundRow interfaces

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 00:16:49 -06:00

178 lines
5.6 KiB
TypeScript

/**
* Refund Service
* Handles financial.refunds operations with approval flow
*/
import { db } from '../../../shared/database';
import type { Refund, RefundRow, RequestRefundInput, RefundStatus } from '../types/financial.types';
// ============================================================================
// Transform Functions
// ============================================================================
function transformRefund(row: RefundRow): Refund {
return {
id: row.id,
paymentId: row.payment_id,
amount: parseFloat(row.amount),
currency: row.currency,
reason: row.reason,
reasonCode: row.reason_code,
notes: row.notes,
status: row.status as RefundStatus,
stripeRefundId: row.stripe_refund_id,
stripeFailureReason: row.stripe_failure_reason,
requestedBy: row.requested_by,
approvedBy: row.approved_by,
approvedAt: row.approved_at ? new Date(row.approved_at) : null,
rejectedAt: row.rejected_at ? new Date(row.rejected_at) : null,
rejectionReason: row.rejection_reason,
createdAt: new Date(row.created_at),
processedAt: row.processed_at ? new Date(row.processed_at) : null,
completedAt: row.completed_at ? new Date(row.completed_at) : null,
failedAt: row.failed_at ? new Date(row.failed_at) : null,
};
}
// ============================================================================
// Query Functions
// ============================================================================
export async function getRefundById(id: string): Promise<Refund | null> {
const result = await db.query<RefundRow>(
'SELECT * FROM financial.refunds WHERE id = $1',
[id]
);
return result.rows[0] ? transformRefund(result.rows[0]) : null;
}
export async function getRefundsForPayment(paymentId: string): Promise<Refund[]> {
const result = await db.query<RefundRow>(
'SELECT * FROM financial.refunds WHERE payment_id = $1 ORDER BY created_at DESC',
[paymentId]
);
return result.rows.map(transformRefund);
}
export async function getPendingRefunds(): Promise<Refund[]> {
const result = await db.query<RefundRow>(
`SELECT * FROM financial.refunds WHERE status = 'pending' ORDER BY created_at ASC`
);
return result.rows.map(transformRefund);
}
export async function getRefundsByStatus(status: RefundStatus): Promise<Refund[]> {
const result = await db.query<RefundRow>(
'SELECT * FROM financial.refunds WHERE status = $1 ORDER BY created_at DESC',
[status]
);
return result.rows.map(transformRefund);
}
export async function getRefundsByUser(userId: string): Promise<Refund[]> {
const result = await db.query<RefundRow>(
'SELECT * FROM financial.refunds WHERE requested_by = $1 ORDER BY created_at DESC',
[userId]
);
return result.rows.map(transformRefund);
}
// ============================================================================
// Mutation Functions
// ============================================================================
export async function requestRefund(
requestedBy: string,
input: RequestRefundInput
): Promise<Refund> {
const result = await db.query<RefundRow>(
`INSERT INTO financial.refunds
(payment_id, amount, reason, reason_code, notes, requested_by, status)
VALUES ($1, $2, $3, $4, $5, $6, 'pending')
RETURNING *`,
[
input.paymentId,
input.amount,
input.reason || null,
input.reasonCode || null,
input.notes || null,
requestedBy,
]
);
return transformRefund(result.rows[0]);
}
export async function approveRefund(
id: string,
approvedBy: string
): Promise<Refund | null> {
const result = await db.query<RefundRow>(
`UPDATE financial.refunds
SET status = 'processing', approved_by = $2, approved_at = NOW()
WHERE id = $1 AND status = 'pending'
RETURNING *`,
[id, approvedBy]
);
return result.rows[0] ? transformRefund(result.rows[0]) : null;
}
export async function rejectRefund(
id: string,
rejectedBy: string,
reason: string
): Promise<Refund | null> {
const result = await db.query<RefundRow>(
`UPDATE financial.refunds
SET status = 'cancelled', rejected_at = NOW(), rejection_reason = $3
WHERE id = $1 AND status = 'pending'
RETURNING *`,
[id, rejectedBy, reason]
);
return result.rows[0] ? transformRefund(result.rows[0]) : null;
}
export async function completeRefund(
id: string,
stripeRefundId: string
): Promise<Refund | null> {
const result = await db.query<RefundRow>(
`UPDATE financial.refunds
SET status = 'succeeded', stripe_refund_id = $2, completed_at = NOW(), processed_at = NOW()
WHERE id = $1
RETURNING *`,
[id, stripeRefundId]
);
return result.rows[0] ? transformRefund(result.rows[0]) : null;
}
export async function failRefund(
id: string,
failureReason: string
): Promise<Refund | null> {
const result = await db.query<RefundRow>(
`UPDATE financial.refunds
SET status = 'failed', stripe_failure_reason = $2, failed_at = NOW()
WHERE id = $1
RETURNING *`,
[id, failureReason]
);
return result.rows[0] ? transformRefund(result.rows[0]) : null;
}
// ============================================================================
// Refund Service Object (Alternative Export)
// ============================================================================
export const refundService = {
getById: getRefundById,
getForPayment: getRefundsForPayment,
getPending: getPendingRefunds,
getByStatus: getRefundsByStatus,
getByUser: getRefundsByUser,
request: requestRefund,
approve: approveRefund,
reject: rejectRefund,
complete: completeRefund,
fail: failRefund,
};