Files
sam-react-prod/src/components/settings/PaymentHistoryManagement/actions.ts
byeongcheolryu d38b1242d7 feat: fetchWrapper 마이그레이션 및 토큰 리프레시 캐싱 구현
- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션
- 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts)
- ApiErrorContext 추가로 전역 에러 처리 개선
- HR EmployeeForm 컴포넌트 개선
- 참조함(ReferenceBox) 기능 수정
- juil 테스트 URL 페이지 추가
- claudedocs 문서 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 17:00:18 +09:00

243 lines
6.6 KiB
TypeScript

'use server';
import { serverFetch } from '@/lib/api/fetch-wrapper';
import type { PaymentApiData, PaymentHistory } from './types';
import { transformApiToFrontend } from './utils';
// ===== 결제 목록 조회 =====
export async function getPayments(params?: {
page?: number;
perPage?: number;
status?: string;
startDate?: string;
endDate?: string;
search?: string;
}): Promise<{
success: boolean;
data: PaymentHistory[];
pagination: {
currentPage: number;
lastPage: number;
perPage: number;
total: number;
};
error?: string;
__authError?: boolean;
}> {
try {
// 쿼리 파라미터 생성
const searchParams = new URLSearchParams();
if (params?.page) searchParams.append('page', String(params.page));
if (params?.perPage) searchParams.append('per_page', String(params.perPage));
if (params?.status) searchParams.append('status', params.status);
if (params?.startDate) searchParams.append('start_date', params.startDate);
if (params?.endDate) searchParams.append('end_date', params.endDate);
if (params?.search) searchParams.append('search', params.search);
const queryString = searchParams.toString();
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/payments${queryString ? `?${queryString}` : ''}`;
const { response, error } = await serverFetch(url, {
method: 'GET',
cache: 'no-store',
});
if (error) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: error.message,
__authError: error.code === 'UNAUTHORIZED',
};
}
if (!response) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: '결제 내역을 불러오는데 실패했습니다.',
};
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: result.message || '결제 내역을 불러오는데 실패했습니다.',
};
}
const payments = result.data.data.map(transformApiToFrontend);
return {
success: true,
data: payments,
pagination: {
currentPage: result.data.current_page,
lastPage: result.data.last_page,
perPage: result.data.per_page,
total: result.data.total,
},
};
} catch (error) {
console.error('[PaymentActions] getPayments error:', error);
return {
success: false,
data: [],
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
error: '서버 오류가 발생했습니다.',
};
}
}
// ===== 결제 명세서 조회 =====
export async function getPaymentStatement(id: string): Promise<{
success: boolean;
data?: {
statementNo: string;
issuedAt: string;
payment: {
id: number;
amount: number;
formattedAmount: string;
paymentMethod: string;
paymentMethodLabel: string;
transactionId: string | null;
status: string;
statusLabel: string;
paidAt: string | null;
memo: string | null;
};
subscription: {
id: number;
startedAt: string | null;
endedAt: string | null;
status: string;
statusLabel: string;
};
plan: {
id: number;
name: string;
code: string;
price: number;
billingCycle: string;
billingCycleLabel: string;
} | null;
customer: {
tenantId: number;
companyName: string;
businessNumber: string | null;
representative: string | null;
address: string | null;
email: string | null;
phone: string | null;
};
items: Array<{
description: string;
quantity: number;
unitPrice: number;
amount: number;
}>;
subtotal: number;
tax: number;
total: number;
};
error?: string;
__authError?: boolean;
}> {
try {
const { response, error } = await serverFetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/payments/${id}/statement`,
{
method: 'GET',
cache: 'no-store',
}
);
if (error) {
return {
success: false,
error: error.message,
__authError: error.code === 'UNAUTHORIZED',
};
}
if (!response) {
return {
success: false,
error: '명세서를 불러오는데 실패했습니다.',
};
}
const result = await response.json();
if (!response.ok || !result.success) {
return {
success: false,
error: result.message || '명세서를 불러오는데 실패했습니다.',
};
}
// snake_case → camelCase 변환
const data = result.data;
return {
success: true,
data: {
statementNo: data.statement_no,
issuedAt: data.issued_at,
payment: {
id: data.payment.id,
amount: data.payment.amount,
formattedAmount: data.payment.formatted_amount,
paymentMethod: data.payment.payment_method,
paymentMethodLabel: data.payment.payment_method_label,
transactionId: data.payment.transaction_id,
status: data.payment.status,
statusLabel: data.payment.status_label,
paidAt: data.payment.paid_at,
memo: data.payment.memo,
},
subscription: {
id: data.subscription.id,
startedAt: data.subscription.started_at,
endedAt: data.subscription.ended_at,
status: data.subscription.status,
statusLabel: data.subscription.status_label,
},
plan: data.plan ? {
id: data.plan.id,
name: data.plan.name,
code: data.plan.code,
price: data.plan.price,
billingCycle: data.plan.billing_cycle,
billingCycleLabel: data.plan.billing_cycle_label,
} : null,
customer: {
tenantId: data.customer.tenant_id,
companyName: data.customer.company_name,
businessNumber: data.customer.business_number,
representative: data.customer.representative,
address: data.customer.address,
email: data.customer.email,
phone: data.customer.phone,
},
items: data.items,
subtotal: data.subtotal,
tax: data.tax,
total: data.total,
},
};
} catch (error) {
console.error('[PaymentActions] getPaymentStatement error:', error);
return {
success: false,
error: '서버 오류가 발생했습니다.',
};
}
}