Backend · 프로젝트 · 2025.05 – 09
푸시Push
푸드트럭·케이터링 사장님과 고객을 잇는 예약·결제·정산 서비스의 백엔드입니다. 고객 앱과 사장님 앱을 서버 하나로 지원하며, 1인 백엔드로 설계부터 운영까지 맡았습니다.
- 역할
- 백엔드 설계·개발·운영
- 운영
- 스토어 출시, 2026.06까지 운영
Case 01
PortOne V2 결제 연동
결제 승인 전에 서버가 예약 상태·금액·유효시간을 다시 확인하고, 실패 웹훅으로 예약을 되돌려 결제 대기 상태로 남는 예약을 막았습니다.
문제
- 결제 승인을 서버가 확인하지 않으면 예약 상태나 금액이 맞지 않는 결제, 유효시간이 지난 결제를 막을 수 없습니다.
- 결제가 실패해도 예약이 결제 대기 상태로 남는 정합성 문제가 있었습니다.
판단 근거
- 결제는 승인 전에 서버가 거부할 수 있어야 합니다. 그래서 승인 단계(Confirm)에서 검증하고, 실패는 비동기 웹훅으로 따로 받는 2단계 구조가 맞다고 판단했습니다.
- 웹훅 서명은 원본 body 기준으로 계산되므로, JSON 파싱 전에 원본을 남겨 둬야 했습니다.
해결
- 결제 ID를 예약 결제와 추가 결제로 구분되는 형식으로 만들어 결제 종류별로 분기했습니다.
- 결제를 승인하기 전에 DB에 저장된 예약 상태, 결제 금액, 유효시간(1시간)을 서버에서 다시 확인합니다.
- 웹훅은 서명을 검증한 뒤 결제 실패면 예약을 FAILED로 되돌립니다.
- 결제·정산 쓰기 작업은 트랜잭션 인터셉터로 묶어 원자성을 확보했습니다.
결과
- 결제 대기 상태로 남는 예약을 막고, 결제·정산 쓰기의 원자성을 확보
Case 02
결제 시점 정산 스냅샷 설계
결제가 확정되는 순간 수수료, VAT, 정산액, 환불 규정을 따로 저장해, 이후 정책이 바뀌어도 이미 결제된 건의 정산 근거가 변하지 않게 했습니다.
문제
- 정산할 때 현재 수수료율과 환불 규정을 다시 조회하면, 정책이 바뀐 뒤 과거 결제 건의 정산액이 달라집니다.
판단 근거
- 돈과 관련된 기록은 계산 결과뿐 아니라 계산에 쓴 입력값(요율, 규정)까지 바뀌지 않게 남겨야 분쟁이나 감사에 대응할 수 있습니다.
해결
- 정산 테이블에 플랫폼 수수료, PG 수수료, VAT, 정산액을 함께 저장했습니다.
- 환불 규정은 '7일=100%/3일=50%' 같은 문자열로 묶어 같이 보관합니다.
- 예약 결제와 추가 결제가 같은 정산 구조를 쓰고, 취소나 거절 시에는 정산 상태만 바꿉니다.
결과
- 정책 변경과 상관없이 결제 건마다 정산 근거를 추적할 수 있고, 관리자는 정산 완료 처리만 하면 됨
Case 03
고객 앱·사장님 앱 단일 서버 구조
서로 다른 Firebase 프로젝트를 쓰는 두 앱의 푸시를 서버 하나에서 보내도록 구성했습니다.
문제
- 고객 앱과 사장님 앱이 서로 다른 Firebase 프로젝트를 써서, 푸시를 보내는 자격증명이 둘이었습니다.
판단 근거
- 앱마다 서버를 따로 두면 예약·결제 로직이 갈라집니다. 서버는 하나로 두고, 앱별 연결만 나눠 주입하는 편이 단순합니다.
해결
- 두 앱의 Firebase Named App을 각각 DI 프로바이더로 분리해, 서비스는 필요한 쪽을 주입받아 푸시를 보냅니다.
- 이미 초기화된 앱은 다시 쓰도록 해 중복 초기화를 막았습니다.
결과
- 서버 하나로 고객 앱과 사장님 앱의 푸시를 모두 처리