가입자 전원 대상 쿠폰
한 상점당 최대 2장, 전체 최대 10장이라는 정책 아래 참여 상점의 방문을 유도하는 기본 프로그램입니다.
약 3억 원의 상권 활성화 예산을 약 150개 참여 상점과 약 6,000명의 가입 시민에게 집행하는 서비스였습니다. 쿠폰 발급, 현장 카드결제, 사용 확인, 월별 정산까지 하나의 흐름으로 연결했고, 4인 개발팀의 개발 리드로 시스템 초안과 전체 데이터 흐름, 프론트 화면 구조, 쿠폰·결제·정산 상태 모델을 설계했습니다.
운영 당시 결과물을 보존한 공개 아카이브 화면
2024년 시스템에서는 마일리지 차감, 쿠폰 생성, 예산 차감이 서로 분리된 쓰기로 실행됐습니다. 합계 조회와 생성 사이에 동시성 제어가 없었고, 취소·환급·예산 복원과 사용 처리도 원자적인 상태 전이로 묶여 있지 않았습니다. 당시 별도 PG 승인 결제는 없었지만, 운영 규모가 커질수록 데이터가 어긋날 가능성이 남는 구조였습니다.
2024년에는 핵심 구현을 위임하면서 설계 기준, 리뷰와 테스트 기준을 충분히 마련하지 못했습니다. 2025년에는 개발 리드로서 팀이 공유할 상태 모델과 데이터 흐름을 먼저 정리하는 데 집중했습니다.
그래서 2025년에는 구현을 분배하기 전에 시스템 초안을 직접 작성했습니다. 어떤 상태가 존재하고 어떤 요청이 상태를 바꾸는지, 예산·개인 한도·결제·정산이 어디에서 연결되는지를 먼저 정리한 뒤 팀 구현을 진행했습니다.
분리돼 있던 쓰기를 요청 단위 트랜잭션으로 묶고, Serializable 격리·상태 전이 검증·보상 작업·권한 모델을 적용했습니다.
정책이 다른 세 프로그램이 같은 사용자·상점·예산 체계 위에서 함께 운영됐습니다. 각 프로그램의 한도와 사용 조건을 서버가 일관되게 판단해야 했습니다.
한 상점당 최대 2장, 전체 최대 10장이라는 정책 아래 참여 상점의 방문을 유도하는 기본 프로그램입니다.
상품가의 30% 할인 프로그램으로, 전체 예산과 개인별 한도를 확인한 뒤 PG 결제 상태와 쿠폰 사용 상태를 맞춰야 했습니다.
여러 참여자의 인적사항과 희망 상점으로 신청하고, 추첨 당첨 후 정액 쿠폰을 지급하며 단체 인증사진으로 인원을 사후 증빙했습니다.
상점은 사용 확인뿐 아니라 월별 쿠폰 매출 확인, PASS 재인증, 정산 서명과 증빙 제출까지 처리했습니다.
NestJS CLS와 Prisma transactional adapter로 요청 단위 트랜잭션 컨텍스트를 전파했습니다. 쿠폰 발급·사용과 결제 주문에 Serializable 격리를 적용해 한 요청이 바꾸는 여러 상태를 같은 트랜잭션에서 확정하도록 설계했습니다.
카드결제는 사용자가 결제창에서 돌아왔다고 성공으로 간주하지 않았습니다. 주문을 먼저 PENDING으로 만들고, PG 서버 알림의 성공 코드·금액·해시·거래 유형을 검증한 뒤에만 PAID로 전환했습니다. 이미 PENDING이 아닌 주문은 다시 처리하지 않아 중복 콜백을 막고, 내부 처리 실패 시에는 PG 취소를 보상 흐름으로 실행했습니다.
완료되지 않은 주문이 예산을 계속 점유하는 문제는 BullMQ delayed job으로 5분 뒤 타임아웃 처리했습니다. 따라서 진행 중 주문은 잠시 예산을 점유하지만 결제가 끊기면 다시 사용 가능한 예산으로 돌아옵니다.
PASS 본인인증은 프론트 성공 콜백을 그대로 신뢰하지 않고, 서버가 공급사에서 결과를 다시 받아 거래 ID, 성공 코드와 생성 시각을 검증했습니다. 검증된 이름·전화번호는 신규 가입, 기존 OAuth 계정 연결, 상점 사업주 로그인, 정산 재인증에 공통으로 사용했습니다.
회차·기간·예산·활성 상태와 개인 한도는 관리자 화면에서 운영 중 조정할 수 있게 했습니다. 결제 주문을 만들 때 서버가 최신 설정을 다시 읽어 판단하므로, 정책 변경이 새로운 배포를 요구하지 않았습니다. 실제 운영 중 현장결제 개인 한도도 사용자 데이터에 맞춰 상향 조정됐습니다.
월별 정산에는 사용자 기능과 다른 종류의 반복 작업이 생깁니다. 세 쿠폰의 사용액을 상점별로 집계하고, 검수용 통합 Excel과 상점별 정산확인서를 만들고, 회식비 당첨팀을 중복·상점별 한도를 고려해 배정하고, 문자·알림톡 발송 결과와 비용을 집계해야 했습니다. 이런 작업을 운영 스크립트로 묶어 사람이 같은 데이터를 여러 번 가공하는 지점을 줄였습니다.
상점 측에는 회원가입을 새로 요구하기보다 사전에 등록된 사업주 전화번호와 PASS 인증 결과를 연결했습니다. associate → shop → manager → admin 계층의 RBAC와 상점 소속 Guard를 두어, 상점 직원이 다른 상점의 데이터를 읽지 못하게 서버에서 경계를 잡았습니다.
상점별 월 매출 확인, PASS 재인증, 서명·증빙 제출을 하나의 백오피스로 연결
운영자가 회차·기간·예산·개인 한도를 배포 없이 조정하고 주문 시 최신 정책을 재검증
정산·추첨·발송·리포트 작업을 스크립트화해 반복 운영 비용을 줄임
자동화 테스트 과정에서 외부 메시징 API의 테스트 모드 필드가 공급사 스펙과 다르게 적용되어 약 100명에게 테스트 문자가 두 차례 실제 발송됐습니다. 약 10건의 CS가 발생했고, 사고 범위가 CS팀에 충분히 공유되지 않아 일부 안내가 번복되는 2차 문제도 생겼습니다.
정정 문자를 발송하고 기존 관리자 공지 팝업과 배너를 이용해 공식 안내한 뒤, 외부 API 요청 스펙을 타입화했습니다. 비운영 발송은 내부 채널로 우회하고, 실제 발송을 포함하는 테스트는 일반 테스트 명령과 분리했습니다.
비운영 발송 경로와 실제 발송 테스트를 분리했습니다. 개발·운영·CS 사이의 사고 공유 절차 개선도 제안했으나, 정착 여부는 확인하지 못했습니다.
4인 개발팀의 리드로 상태 모델과 데이터 흐름을 선행 설계하고, 쿠폰 발급·결제·예산·정산을 연결했습니다. 운영자가 회차와 한도를 조정하고 상점이 매출 확인부터 정산 증빙 제출까지 처리할 수 있도록 구성했습니다.
아카이브 결과물 보기 ↗