Skip to content

[FEAT] 정산 - 정산 완료 처리(Pending → Succeed) 동기화 흐름 구현 #482

@minij02

Description

@minij02

✨ 기능 설명

현재 코드는 결제 완료 시 Settlement.status='Pending'으로 row 생성만 하고, Succeed로 업데이트하는 로직이 코드 전체에 없음(grep 검증 완료). 결과적으로 GET /pending-amount, /sales/monthly, /yearly 등 모든 정산 통계가 영구히 Pending에 머무름. #479/#480에서 추가한 utils/payple-settlement.ts(파트너 인증 + 정산내역 조회 + 페이지네이션)를 활용해 정산 완료 동기화를 구현.

3개 에이전트(architect / critic / security) 교차 검증 후 도출된 최적안.

✨ 핵심 결정 사항 (확정)

  • 방식 A 단독 — KST 08:00 인앱 node-cron (기존 src/stats/jobs/prompt-stat-snapshot.job.ts 패턴 재사용). webhook은 명세 미확정이라 분리
  • APPROVAL만 처리, CANCEL은 skip + WARN 로그 (환불은 enum 확장과 함께 별도 이슈)
  • 금액 검증 필수: PCD_SETTLE_AMOUNT == Settlement.amount일 때만 Succeed. 불일치는 alert + skip (Discrepancy 분류)
  • 멱등 가드: where: { payment_id, status: 'Pending' } updateMany — affected=0이면 skip
  • Redis 분산 락 (payple:settlement:sync:lock, NX EX 600)으로 중복 실행 차단
  • lastKey Redis 영속화 + 페이지당 ≥1.1초 sleep + 잡당 페이지 한도. rate-limit 미완료 시 다음 cron이 이어받기
  • 로그 redactor 적용 (redactPaypleLog 재사용 + PCD_PAYER_NAME 등 PII 필드 추가)
  • Auth 캐시 보안 강화: TTL 25분 → 15분 단축 + cstId/custKey는 캐시에서 제외하고 매번 env에서 로드 (#479의 utils/payple-settlement.ts 보강)

✨ 개발 목록

동기화 잡 (src/settlements/jobs/settlement-sync.job.ts)

  • node-cron으로 KST 08:00 트리거 (timezone Asia/Seoul)
  • src/index.tsstartSettlementSyncJob() 부트스트랩
  • Redis 분산 락 획득 → 실행 → 해제 (SET NX EX 600)
  • 어제 KST 날짜로 PCD_START_DATE = PCD_END_DATE로 호출
  • 페이지네이션 루프 — PCD_HAS_MORE=true 동안 PCD_LASTKEY 사용
    • lastKey를 Redis(payple:settlement:sync:lastkey:{yyyyMMdd})에 영속화
    • 페이지 사이 ≥1.1초 sleep
    • 잡당 최대 페이지 한도(예: 100) 도달 시 종료 → 다음 cron이 lastKey로 이어받기
  • 페이지 단위가 아닌 건별 트랜잭션 (prisma.$transaction 1건씩)
  • 결과 카운트 로그: { date, processed, updated, skipped, missing, cancel_skipped, discrepancy, failed }

매칭/업데이트 로직

  • PCD_PAY_OIDPayment.pcd_pay_oid lookup
    • Payment 없음 → missing 카운트 + WARN
  • txnType === 'CANCEL'cancel_skipped 카운트 + WARN (본 PR 범위 밖)
  • txnType === 'APPROVAL' 처리:
    • Settlement.payment_id 조회 → 없으면 missing
    • PCD_SETTLE_AMOUNT === Settlement.amount 검증 → 불일치 시 discrepancy + ERROR 로그 (자동 업데이트 금지)
    • prisma.settlement.updateMany({ where: { payment_id, status: 'Pending' }, data: { status: 'Succeed' } })
    • affected = 1 → updated, 0 → skipped

보안/로그

  • utils/payple-settlement.ts Auth 캐시 보강:
    • AUTH_CACHE_TTL_SECONDS를 25분 → 15분으로 단축
    • 캐시 payload에서 cstId/custKey 제거 → 매 호출 시 env에서 직접 로드
  • redactPaypleLog를 settlement util import → 요청/응답 로그 마스킹
  • redactor의 REDACTED_FIELDSPCD_PAYER_NAME, PCD_PAY_OID 등 추가 검토

환경변수

  • PAYPLE_SETTLEMENT_SYNC_ENABLED (기본값 true, dev 비활성화 가능)
  • PAYPLE_SETTLEMENT_SYNC_MAX_PAGES_PER_RUN (기본 100)

검증

  • pnpm build / pnpm tsc --noEmit
  • Payple sandbox: 페이지네이션 2+ 페이지 케이스
  • 멱등성: 같은 날짜 재실행 시 affected=0 검증
  • 분산 락: 동시 실행 시 두 번째 잡이 즉시 종료
  • CANCEL 케이스: cancel_skipped 카운트 + 업데이트 안 됨 확인
  • 금액 불일치 케이스: discrepancy 카운트 + alert

✨ 별도 이슈로 분리 (본 PR 범위 밖)

  • 환불(CANCEL) 흐름 + Status.Refunded enum 확장 + Refund 모델 검토
  • 6개월 백필 스크립트 (31일 슬라이딩 윈도우)
  • Payple Webhook 도입 (실시간 보강)
  • PAYPLE_CUST_KEY 분리 / AWS Secrets Manager 이전

✨ 기타 설명 / 질문

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions