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>
178 lines
5.6 KiB
TypeScript
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,
|
|
};
|