model Refund {
refund_id Int @id @default(autoincrement())
purchase_id Int @unique
payment_id Int @unique
user_id Int
amount Int
reason String? @db.VarChar(200)
initiator String @db.VarChar(20) // USER | ADMIN
payple_pay_code String? @db.VarChar(20)
payple_card_trade_num String? @db.VarChar(64) // PCD_PAY_CARDTRADENUM (감사 추적)
refunded_at DateTime @default(now())
purchase Purchase @relation(...)
payment Payment @relation(...)
user User @relation(...)
}
✨ 기능 설명
구매한 프롬프트에 대한 사용자 자동 환불 흐름 구현. 기획 정책상 환불 가능 조건은 7일 이내 + 미열람(미다운로드). 정책 충족 시 사용자가 직접 [환불하기]를 누르면 즉시 Payple 결제 취소 API를 호출하고 Settlement/Purchase까지 정합화.
#480 / #482 후속.
Status.Refundedenum 신규로 환불 거래를 회계적으로 구분.✨ 핵심 결정 사항 (확정)
Purchase.downloaded_at기록. 이후 환불 불가Status.Refundedenum 추가 +Refund별도 모델 신규 (양쪽 모두 활용)Settlement.status='Succeed'여도 환불 가능 (회계 환수는 별도 처리)✨ 개발 목록
Prisma 스키마
Statusenum에Refunded추가Purchase.downloaded_at DateTime?컬럼 추가Refund모델 신규다운로드 이력 기록
Prompt.downloads카운터와 별개로Purchase.downloaded_at을 첫 다운로드 시점에 setdownloaded_at미값이면 NOW()로 update환불 가능 여부 조회
GET /api/purchases/:purchaseId/refund-eligibility{ eligible: boolean, reason?: 'EXPIRED_7DAYS' | 'ALREADY_DOWNLOADED' | 'ALREADY_REFUNDED', remaining_seconds?: number }환불 실행
POST /api/purchases/:purchaseId/refunddownloaded_atnull +Refund미존재 +Payment.status='Succeed'Payple 결제 취소 유틸 (
utils/payple-refund.ts신규)fetchPaypleRefundAuth()—PCD_PAYCANCEL_FLAG=Y파트너 인증, Auth 15분 Redis 캐시 (custKey 캐시 제외)requestPaypleRefund({ payOid, payDate, amount })PCD_REFUND_KEY포함)PCD_PAY_RST !== 'success'처리, 응답PCD_PAY_CARDTRADENUM저장PCD_REFUND_KEY등 신규 마스킹 필드 추가)DB 정합화 (트랜잭션)
Refundrow 생성 (initiator='USER')Payment.status = 'Failed'(또는Refundedenum 사용 시 그쪽)Settlement.status = 'Refunded'(settlement row 존재 시)Purchase는 그대로 유지 (환불 이력은 Refund row로 추적). 또는is_refunded별도 컬럼 검토접근 차단
Refund존재 시 403getDownloadedPromptsByUser등 구매 목록 조회 시 환불 표시 (UI에서 비활성화)환경변수
PAYPLE_REFUND_KEY— Payple 파트너 관리자에서 발급받은 환불 전용 키 (필수)PAYPLE_REFUND_AUTH_PATH— 환불용 파트너 인증 endpoint (기본값/php/auth.php, sandbox 검증 후 정정)Swagger / 검증
pnpm build관련 후속 흐름
CANCEL거래가 들어왔을 때 처리:Refund모델이 추가되면 sync 잡이 CANCEL을 받아Refundrow 매칭/생성도 가능✨ 별도 이슈로 분리 (본 PR 범위 밖)
PromptDownload테이블로 정규화 (현재는 단일 시점만)✨ 기타 설명 / 질문
PCD_REFUND_KEY발급 필요 — 계약 완료 후 파트너 관리자에서 확인. 미발급 시 sandbox에서 테스트 불가Purchase.created_at기준 KST? UTC? — KST 가정downloaded_at처리: 다운로드 응답 200 직전에 set (실패는 미기록)onDelete: Cascade)