Skip to content

fix: 클래스 결제관리 연동 보강#687

Merged
Hyeonjun0527 merged 1 commit into
developfrom
fix/admin-course-payment-management
May 23, 2026
Merged

fix: 클래스 결제관리 연동 보강#687
Hyeonjun0527 merged 1 commit into
developfrom
fix/admin-course-payment-management

Conversation

@Hyeonjun0527

@Hyeonjun0527 Hyeonjun0527 commented May 23, 2026

Copy link
Copy Markdown
Member

요약

  • 관리자 매출/환불 관리 화면에서 그룹스터디/클래스 결제 대상을 분리 조회할 수 있게 했습니다.
  • 클래스 결제관리 조회/상세 API 훅과 응답 타입을 추가했습니다.
  • 드롭다운 값 검증, 프로젝트 spacing token 사용, 날짜/영수증 처리 안전성을 보강했습니다.

백엔드 계약 확인

  • GET /api/v5/admin/course-payments
  • GET /api/v5/admin/course-payments/{paymentId}
  • 확인 파일: study-platform-mvp AdminCoursePaymentController 및 course payment response DTO

검증

  • yarn prettier:fix
  • yarn typecheck
  • yarn lint:fix (0 errors, existing warnings only)

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 결제 환불 관리에 클래스 결제 지원 추가
    • 클래스 결제 상세 정보 조회 기능 추가
    • 결제 상세 정보 모달에 결제 수단, 요청/결제/취소 일시, 처리 이력 타임라인 표시
  • Improvements

    • 결제 대상(그룹스터디/클래스)에 따른 필터 및 표시 정보 최적화

Review Change Stack

@vercel

vercel Bot commented May 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
study-platform-client-dev Ready Ready Preview, Comment May 23, 2026 3:40am

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

PaymentRefundPage가 결제 관리 기능을 그룹스터디와 클래스 결제로 이분화하여 확장됩니다. 각 결제 유형은 서로 다른 API 훅, 필터 옵션, 행 컴포넌트를 통해 조건부로 처리되며, 클래스 결제는 상세 조회 모달을 추가로 제공합니다.

Changes

코스 결제 관리 기능 추가

Layer / File(s) Summary
코스 결제 타입 계약
src/types/api/course.types.ts
관리자 코스 결제 목록 아이템, 타임라인 이벤트, 상세 응답, 검색 파라미터 타입과 페이지네이션 래퍼 제네릭을 정의한다.
코스 결제 API 훅 및 기존 훅 확장
src/hooks/queries/admin/admin-course-payment-api.ts, src/hooks/queries/admin/admin-payment-api.ts
코스 결제 목록/상세 조회 훅을 신규 추가하고, 기존 거래 조회 훅에 enabled 옵션을 추가하여 조건부 쿼리 실행을 가능하게 한다.
페이지 상태 및 필터 구성
src/app/(admin)/admin/sales-management/payment-refund/page.tsx (lines 5–160)
페이지 컴포넌트가 결제 대상 선택 상태, 필터 옵션 배열, 날짜 범위, 검색 조건을 초기화하고 변경 시 페이지를 리셋한다.
이중 데이터 소스 통합
src/app/(admin)/admin/sales-management/payment-refund/page.tsx (lines 176–259)
그룹스터디는 기존 거래 훅을, 클래스는 코스 결제 훅을 enabled 플래그로 조건부 실행하며, 필터 UI가 결제 대상에 따라 동작한다.
결제 행 컴포넌트
src/app/(admin)/admin/sales-management/payment-refund/page.tsx (lines 339–470)
그룹스터디 행은 거래 정보를, 클래스 행은 코스 결제 정보를 렌더링하고, 클래스 행에는 상세 보기 버튼과 모달을 연결한다.
상세 모달 및 지원 컴포넌트
src/app/(admin)/admin/sales-management/payment-refund/page.tsx (lines 496–728)
클래스 결제 상세를 모달로 표시하며, 상태/필드/가상계좌/타임라인 이력과 날짜 포맷 유틸을 통합한다.
테이블 렌더링 및 페이지네이션
src/app/(admin)/admin/sales-management/payment-refund/page.tsx (lines 276–336)
테이블 헤더와 행 목록이 결제 대상에 따라 조건부로 렌더링되며, 페이지네이션이 동기화된다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 두 가지 결제의 길을 나누어,
각각 제 데이터 소스를 따르고,
모달 속에 상세가 담겨,
타임라인은 역사를 그리고—
필터와 쿼리가 춤을 추네!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 '클래스 결제관리 연동 보강'으로 주요 변경사항인 클래스 결제관리 기능 확장을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/admin-course-payment-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Hyeonjun0527 Hyeonjun0527 force-pushed the fix/admin-course-payment-management branch from 120542d to da42206 Compare May 23, 2026 03:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(admin)/admin/sales-management/payment-refund/page.tsx:
- Around line 358-364: transaction.groupStudyStatus is nullable but
STUDY_STATUS_MAP expects a non-null key, so the current lookup can render
"undefined" in the UI; change the rendering to perform a null-safe lookup and
provide a fallback label. Specifically, compute a safe key from
transaction.groupStudyStatus (e.g., use nullish coalescing or a guard) before
indexing STUDY_STATUS_MAP and then fallback to a default string like "Unknown"
if the map lookup returns undefined; update the JSX that references
transaction.groupStudyStatus and STUDY_STATUS_MAP to use this safe value.
- Around line 296-301: The map rendering uses an optional ID field for the React
key (paymentCode) without an index fallback which can cause duplicate keys when
paymentCode is undefined; update the map callbacks for list.map that render
GroupStudyPaymentRow (and similarly CoursePaymentRow) to include the index in
the map signature and use a fallback like paymentCode ?? index (or include both
values) for the key prop so keys are always unique (e.g., change the map to
accept (transaction, index) and set key using transaction.paymentCode ?? index).

In `@src/hooks/queries/admin/admin-course-payment-api.ts`:
- Around line 23-52: Both useGetAdminCoursePayments and
useGetAdminCoursePaymentDetail are missing an onError handler on their useQuery
calls; add an onError option to each useQuery that forwards the caught error to
the shared error handler (from utils/error-handler.ts) and triggers the toast
via useToastStore so the common error flow and user notification are executed on
query failures. Locate the useQuery invocations in the functions
useGetAdminCoursePayments and useGetAdminCoursePaymentDetail and add an onError:
(error) => { /* call shared error handler with error and invoke toast store to
show message */ } implemented using the existing utilities.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 804eb398-4823-44df-8b11-0b42f6ba3971

📥 Commits

Reviewing files that changed from the base of the PR and between 8b137ad and da42206.

📒 Files selected for processing (4)
  • src/app/(admin)/admin/sales-management/payment-refund/page.tsx
  • src/hooks/queries/admin/admin-course-payment-api.ts
  • src/hooks/queries/admin/admin-payment-api.ts
  • src/types/api/course.types.ts

Comment on lines +296 to +301
list.map((transaction) => (
<GroupStudyPaymentRow
key={`${transaction.paymentCode}`}
className="border-b-border-default border-b"
>
{/* 거래 ID */}
<td className="py-200 pl-250">
<span className="font-designer-14r text-text-default">
{transaction.paymentCode || '-'}
</span>
</td>

{/* 스터디명 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{`${transaction.groupStudyName} (${
STUDY_STATUS_MAP[transaction.groupStudyStatus]
})`}
</span>
</td>

{/* 결제자 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{transaction.paymentMemberName || '-'}(
{transaction.paymentMemberId || '-'})
</span>
</td>

{/* 결제 내역 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{transaction.transactionAmount?.toLocaleString() || 0}
원(
{transaction.paymentMethod || '-'})
</span>
</td>

{/* 상태 */}
<td className="py-200 pl-125">
<Badge color={statusConfig.color} shape="rectangle">
{statusConfig.label}
</Badge>
</td>

{/* 일시 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{formatToKST(transaction.transactionedAt)
? format(
formatToKST(transaction.transactionedAt)!,
'yyyy.MM.dd HH:mm',
)
: '-'}
</span>
</td>

{/* 액션 버튼 */}
<td className="py-200 pr-250">
<SalesActionButtons
paymentId={transaction.paymentId}
paymentReceiptUrl={transaction.paymentReceiptUrl}
paymentHistoryType={transaction.paymentHistoryType}
groupStudyName={transaction.groupStudyName}
paymentMemberName={transaction.paymentMemberName}
paymentMemberId={transaction.paymentMemberId}
transactionAmount={transaction.transactionAmount}
/>
</td>
</tr>
);
})
) : (
<tr>
<td
colSpan={7}
className="border-b-border-default border-b py-[200px] text-center"
>
<p className="font-designer-16r text-text-subtlest">
매출 내역이 없습니다.
</p>
</td>
</tr>
)}
</tbody>
transaction={transaction}
/>
))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

코딩 가이드라인 위반: 선택적 ID 필드에 index fallback 누락

paymentCode가 optional 필드인 경우(354번 라인에서 || '-' fallback 사용), key prop에 index fallback이 필요합니다. 여러 항목의 paymentCode가 undefined면 중복 key 문제가 발생할 수 있습니다.

🔧 수정 제안
-                list.map((transaction) => (
+                list.map((transaction, index) => (
                   <GroupStudyPaymentRow
-                    key={`${transaction.paymentCode}`}
+                    key={transaction.paymentCode ?? index}
                     transaction={transaction}
                   />
                 ))

309-314번 라인의 CoursePaymentRow에도 동일하게 적용:

-                coursePaymentList.map((payment) => (
+                coursePaymentList.map((payment, index) => (
                   <CoursePaymentRow
-                    key={`${payment.paymentCode}`}
+                    key={payment.paymentCode ?? index}
                     payment={payment}
                   />
                 ))

As per coding guidelines: "Optional ID fields in React key props need ?? index fallback"

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
list.map((transaction) => (
<GroupStudyPaymentRow
key={`${transaction.paymentCode}`}
className="border-b-border-default border-b"
>
{/* 거래 ID */}
<td className="py-200 pl-250">
<span className="font-designer-14r text-text-default">
{transaction.paymentCode || '-'}
</span>
</td>
{/* 스터디명 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{`${transaction.groupStudyName} (${
STUDY_STATUS_MAP[transaction.groupStudyStatus]
})`}
</span>
</td>
{/* 결제자 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{transaction.paymentMemberName || '-'}(
{transaction.paymentMemberId || '-'})
</span>
</td>
{/* 결제 내역 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{transaction.transactionAmount?.toLocaleString() || 0}
(
{transaction.paymentMethod || '-'})
</span>
</td>
{/* 상태 */}
<td className="py-200 pl-125">
<Badge color={statusConfig.color} shape="rectangle">
{statusConfig.label}
</Badge>
</td>
{/* 일시 */}
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{formatToKST(transaction.transactionedAt)
? format(
formatToKST(transaction.transactionedAt)!,
'yyyy.MM.dd HH:mm',
)
: '-'}
</span>
</td>
{/* 액션 버튼 */}
<td className="py-200 pr-250">
<SalesActionButtons
paymentId={transaction.paymentId}
paymentReceiptUrl={transaction.paymentReceiptUrl}
paymentHistoryType={transaction.paymentHistoryType}
groupStudyName={transaction.groupStudyName}
paymentMemberName={transaction.paymentMemberName}
paymentMemberId={transaction.paymentMemberId}
transactionAmount={transaction.transactionAmount}
/>
</td>
</tr>
);
})
) : (
<tr>
<td
colSpan={7}
className="border-b-border-default border-b py-[200px] text-center"
>
<p className="font-designer-16r text-text-subtlest">
매출 내역이 없습니다.
</p>
</td>
</tr>
)}
</tbody>
transaction={transaction}
/>
))
list.map((transaction, index) => (
<GroupStudyPaymentRow
key={transaction.paymentCode ?? index}
transaction={transaction}
/>
))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(admin)/admin/sales-management/payment-refund/page.tsx around lines
296 - 301, The map rendering uses an optional ID field for the React key
(paymentCode) without an index fallback which can cause duplicate keys when
paymentCode is undefined; update the map callbacks for list.map that render
GroupStudyPaymentRow (and similarly CoursePaymentRow) to include the index in
the map signature and use a fallback like paymentCode ?? index (or include both
values) for the key prop so keys are always unique (e.g., change the map to
accept (transaction, index) and set key using transaction.paymentCode ?? index).

Comment on lines +358 to +364
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{`${transaction.groupStudyName} (${
STUDY_STATUS_MAP[transaction.groupStudyStatus]
})`}
</span>
</td>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

groupStudyStatus 접근 시 null 안전성 보장 필요

STUDY_STATUS_MAP의 타입 정의가 NonNullable<AdminTransactionListResponse['groupStudyStatus']>를 사용하므로 원본 타입은 nullable입니다. transaction.groupStudyStatus가 undefined인 경우 map lookup이 undefined를 반환하여 UI에 "undefined"가 표시될 수 있습니다.

🛡️ 수정 제안
       <td className="py-200 pl-125">
         <span className="font-designer-14r text-text-default">
-          {`${transaction.groupStudyName} (${
-            STUDY_STATUS_MAP[transaction.groupStudyStatus]
-          })`}
+          {`${transaction.groupStudyName || '-'} (${
+            transaction.groupStudyStatus
+              ? STUDY_STATUS_MAP[transaction.groupStudyStatus]
+              : '-'
+          })`}
         </span>
       </td>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{`${transaction.groupStudyName} (${
STUDY_STATUS_MAP[transaction.groupStudyStatus]
})`}
</span>
</td>
<td className="py-200 pl-125">
<span className="font-designer-14r text-text-default">
{`${transaction.groupStudyName || '-'} (${
transaction.groupStudyStatus
? STUDY_STATUS_MAP[transaction.groupStudyStatus]
: '-'
})`}
</span>
</td>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(admin)/admin/sales-management/payment-refund/page.tsx around lines
358 - 364, transaction.groupStudyStatus is nullable but STUDY_STATUS_MAP expects
a non-null key, so the current lookup can render "undefined" in the UI; change
the rendering to perform a null-safe lookup and provide a fallback label.
Specifically, compute a safe key from transaction.groupStudyStatus (e.g., use
nullish coalescing or a guard) before indexing STUDY_STATUS_MAP and then
fallback to a default string like "Unknown" if the map lookup returns undefined;
update the JSX that references transaction.groupStudyStatus and STUDY_STATUS_MAP
to use this safe value.

Comment on lines +23 to +52
return useQuery({
queryKey: [
'adminCoursePayments',
courseId,
memberId,
status,
paymentCode,
page,
size,
],
queryFn: async () => {
const { data } = await axiosInstanceV5.get<
ApiBaseResponse<
CoursePaymentPageResponse<AdminCoursePaymentListItemResponse>
>
>('admin/course-payments', {
params: {
courseId,
memberId,
status,
paymentCode,
page,
size,
},
});

return data.content;
},
enabled,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 목적: 변경된 admin course payment query hook에 onError/공통 에러 핸들링이 있는지 확인
rg -n "export const useGetAdminCoursePayments|export const useGetAdminCoursePaymentDetail|useQuery\\(|onError\\s*:" src/hooks/queries/admin/admin-course-payment-api.ts -C2
rg -n "utils/error-handler|useToastStore" src/hooks/queries/admin/admin-course-payment-api.ts -C2

Repository: code-zero-to-one/study-platform-client

Length of output: 589


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="src/hooks/queries/admin/admin-course-payment-api.ts"

echo "== Imports check =="
rg -n "error-handler|useToastStore|handleError|toast" "$FILE" || true

echo "== onError check =="
rg -n "onError\s*:" "$FILE" || true

echo "== useQuery blocks (small context) =="
rg -n "useQuery\s*\(" "$FILE" -C 6 || true

Repository: code-zero-to-one/study-platform-client

Length of output: 853


쿼리 실패 시 공통 에러 핸들링(onError) 추가 요청

src/hooks/queries/admin/admin-course-payment-api.tsuseGetAdminCoursePaymentsuseGetAdminCoursePaymentDetail 모두 useQueryonError가 없어, 실패 시 utils/error-handler.ts + useToastStore 기반의 토스트/공통 처리 경로가 적용되지 않습니다. 두 훅의 useQueryonError를 추가해 에러를 공통 핸들러로 보내고 사용자 알림을 노출해주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/queries/admin/admin-course-payment-api.ts` around lines 23 - 52,
Both useGetAdminCoursePayments and useGetAdminCoursePaymentDetail are missing an
onError handler on their useQuery calls; add an onError option to each useQuery
that forwards the caught error to the shared error handler (from
utils/error-handler.ts) and triggers the toast via useToastStore so the common
error flow and user notification are executed on query failures. Locate the
useQuery invocations in the functions useGetAdminCoursePayments and
useGetAdminCoursePaymentDetail and add an onError: (error) => { /* call shared
error handler with error and invoke toast store to show message */ } implemented
using the existing utilities.

@Hyeonjun0527 Hyeonjun0527 merged commit 25fb47e into develop May 23, 2026
16 checks passed
@Hyeonjun0527 Hyeonjun0527 deleted the fix/admin-course-payment-management branch May 23, 2026 03:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant