PG 대량환불 기능을 하루 만에 끝까지 쌓아올린 날
목차
오후 5시 무렵, 슬랙에 올라온 스레드 하나를 닫으면서 생각했다. "오늘 이거 진짜 다 만들었네."
대량환불 기능은 설계 자체가 꽤 묵직한 편이다. 단건 환불이라면 그냥 PG API 한 번 찌르고 결과 저장하면 되지만, 대량환불은 다르다. 법인 적립금 회수, 소멸 처리, 암호화된 요청 직렬화, 은행코드 매핑, 미리보기 → 실행 투-스텝 UX까지 맞물려 있다. 그래서 오늘 커밋 순서도 자연스럽게 바텀업 흐름을 탔다.
1. 가장 먼저 암호화 클라이언트부터
PG사 대량환불 접수 API는 요청 본문을 AES-CBC로 암호화해서 보내야 한다. 처음 스펙 문서를 받았을 때 솔직히 "이걸 왜 이렇게 했을까" 싶었지만, 어쨌든 맞춰줘야 한다.
// 개념적 흐름 (실 코드 마스킹)
String plainJson = objectMapper.writeValueAsString(plainItems);
String encrypted = AesCbcUtil.encrypt(plainJson, pgKey, pgIv);
BulkRefundRequest req = new BulkRefundRequest(merchantId, encrypted);
DTO 구조는 크게 세 조각이다.
| 클래스 | 역할 |
|---|---|
BulkRefundPlainItem |
암호화 전 건별 환불 원문 데이터 |
BulkRefundRequest |
암호화 완료된 최종 요청 바디 |
BulkRefundResponse |
PG 응답 파싱 + 성공/실패 코드 판별 |
BulkRefundClient에서 이걸 조립해서 PG 엔드포인트에 쏘는 구조다. 설정값(키, IV, 엔드포인트)은 DB 쪽 PG_CONFIG 테이블에서 읽도록 INSERT 스크립트도 같이 붙였다. 코드에 하드코딩하는 건 논외.
2. 비즈니스 로직 — 법인 회수와 소멸까지 커버
클라이언트가 완성되고 나서 그 위에 Sweep 로직을 올렸다. "Sweep"이라는 이름을 붙인 건 단순 환불이 아니라, 법인 명의로 묶인 잔액을 회수하거나 소멸시키는 두 가지 케이스를 한 번에 처리하기 때문이다.
이 단계에서 예상 외로 시간이 걸린 건 은행코드 매퍼였다. PG사 요청 스펙에 들어가는 은행코드가 우리 내부 코드 체계랑 달라서, ZlgoonBulkRefundBankCode 열거형으로 매핑 테이블을 따로 뺐다. 나중에 은행 추가될 때도 여기 한 줄만 추가하면 된다.
- 법인 회수: 잔액을 모계정으로 이전 후 환불 접수
- 소멸: 기간 만료 또는 정책 사유로 잔액 0 처리 후 환불 접수 스킵
- 양쪽 모두 처리 이력을 동일 트랜잭션 안에서 남김
쿼리 매퍼도 이 단계에서 같이 손봤다. 대상 건 조회 → 상태 갱신 → 이력 INSERT가 세트로 움직이는 구조라, 매퍼 하나 건드리면 연쇄로 확인해야 하는 게 많았다.
3. 관리자 화면 — "미리보기 먼저, 실행은 그다음"
비즈니스 로직이 자리잡고 나서 마지막으로 JSP 관리 화면을 붙였다. 여기서 UX 원칙 하나만 지켰다: 관리자가 실수로 대량 실행하는 상황을 막자.
그래서 화면을 두 단계로 잘랐다.
- 미리보기 단계: 조건 입력 → 대상 건수·금액 합계를 테이블로 표시. 실제 PG 호출 없음.
- 실행 단계: 미리보기 확인 후 명시적 "실행" 버튼 클릭 → 그제야 Sweep 호출.
메뉴 등록 SQL(MENU_ZLGOON_BULK_REFUND_2026_06_23.sql)도 같이 끊었다. 배포 시 DBA 요청 없이 바로 반영할 수 있도록.
4. 사이드로 — 인증 스크립트 401 페일오버 수정
대량환불 작업 흐름 중간에, 자동화 스크립트에서 잡은 버그 하나를 끼워 넣었다. claude-auth-check.sh가 auth-death 상태(HTTP 401)를 감지했을 때 백업 계정 페일오버를 타지 않는 케이스가 있었다.
기존 로직은 특정 오류 패턴만 보고 페일오버를 트리거했는데, 401 응답이 오는 경우는 그 분기를 타지 않아서 그냥 죽어버리고 있었다. 조건 하나 추가해서 401도 동일하게 백업 계정 전환 흐름을 타도록 고쳤다. 작은 픽스지만, 인증 만료 타이밍에 봇이 통째로 멈추는 게 반복됐던 터라 묻어가길 잘했다.
오늘 작업의 핵심은 결국 레이어를 쌓는 순서를 지킨 것이다. 암호화 유틸 → API 클라이언트 → 비즈니스 Sweep → 관리 화면. 위에서 아래를 참조하는 구조라 역순으로 짜면 컴파일도 안 나고 테스트도 못 한다. 코드 리뷰 요청 전에 전체 흐름을 한 번 더 따라가 봤는데, 레이어 경계가 생각보다 깔끔하게 잘린 것 같아서 만족스러운 하루였다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.