[Trip Split] 여행비 정산 관리 서비스

과정
풀스택
노출 페이지
대표 이미지
대표이미지
서비스 한 줄 소개
회차
5 more properties

배경

여행 후 마주한 현실: 쌓여 있는 영수증들
딥다이브 부트캠프 기간 중 친구들과 주말에 짧게 여행을 다녀온 적이 있습니다. 여행이 끝난 뒤 정산을 하려고 보니, 처리해야 할 영수증이 한가득 쌓여 있었습니다. 매번 결제할 때마다 정산하기가 번거로워 한 번에 몰아서 정산하려 했지만, 시간이 지나자 각 영수증이 누구와 함께 사용한 금액인지 기억하기 어려웠습니다.
단순 1/N 정산이 안 되는 현실적인 문제
단순히 총 금액을 1/n 하는 것만으로는 해결되지 않는 상황들이 많았습니다. 기념품 샵에서 각자 다른 물품을 구매한 경우에는 품목별 정산이 필요했고, 하나의 품목을 여러 명이 나눠 사용한 경우에는 다시 1/n 처리를 해야 했습니다. 정산 로직이 점점 복잡해지면서 수작업으로 관리하는 데 한계를 느꼈습니다.
여행 후 책상 위에 쌓여버린 영수증들 - 정산의 시작은 항상 혼란입니다…
기존 여행 기록 서비스의 한계
여행 기록을 위해 트리플과 같은 서비스들을 사용했지만, 결제 금액, 결제 수단, 내용, 카테고리, 결제자, 참여자, 장소 등 한두번은 괜찮았는데, 결제 횟수가 늘어날 수록 기록해야 할 정보가 너무 많아 실제로는 모든 데이터를 기록하기 어려웠습니다. 결과적으로 정산 과정에서 신뢰 문제로 이어집니다.
여행 기록 앱의 복잡한 입력 폼 - 실제로는 기록을 포기하게 되는 UX
영수증 분실과 정산 신뢰 문제
기록의 부제는 정산 과정에서 신뢰 문제가 발생하기도 했습니다. 일부 항목은 영수증을 통해 증빙하려 했지만, 영수증을 분실하는 상황도 발생했습니다. 이 과정에서 정산에 대한 신뢰 문제도 생기면서, 여행 이후 정산 과정이 가장 피곤한 단계가 되었습니다.
영수증 분실과 정산 불신 — 여행 후 가장 피곤한 순간입니다.
기존 정산 서비스의 불편함
카카오페이나 토스와 같은 기존 정산 서비스들은 정산 시점에 모든 정보를 직접 입력해야 하는 구조로, 여행 중에 미리 기록된 데이터를 활용할 수 없다는 불편함이 있었습니다. 이미 발생한 데이터를 다시 입력하는 중복 작업이 반복되고 있었습니다.
기존 서비스의 한계 - 분산된 기능들
문제 인식: “이미 데이터는 존재하고 있었다”
이 과정에서 다음과 같은 문제의식이 들었습니다.
“여행 중에 찍은 사진이나 영수증 안에 이미 대부분의 데이터가 들어 있을 텐데, 왜 우리가 다시 입력해야 할까?”
영수증 이미지의 OCR 데이터와 사진의 메타데이터를 결합하면 지출 내역을 반자동으로 기록하고 정산까지 이어지는 시스템을 만들 수 있지 않을까라는 아이디어에서 프로젝트를 시작하게 되었습니다.
여행 사진 속 데이터 — 이미 필요 정보는 존재하고 있었습니다.
MVP 방향 결정: 카카오톡 기반 자동 정산
팀원들과 논의한 결과, 각자의 학업 및 직장 환경을 고려하여 접근성이 가장 높은 카카오톡 기반 서비스로 MVP를 구현하기로 결정했습니다. 핵심 목표는 다음과 같았습니다. • 영수증 OCR 기반 자동 지출 기록 • 그룹 단위 복잡한 정산 로직 자동 계산 • 카카오페이 송금 요청 메시지 자동 전송
이를 중심으로 기획과 개발을 진행했습니다.

서비스 소개

Trip Split은 여행 중 발생하는 지출을 기록하고, 방(Room) 단위로 정산을 관리할 수 있는 웹 서비스입니다. 사용자는 카카오 로그인을 통해 간편하게 접속할 수 있으며, 방을 생성하여 함께 여행하는 사람들과 지출을 공유하고 정산 결과를 확인할 수 있습니다.
[주요 기능 소개] 1. 카카오 로그인 기능 - 사용자는 카카오 계정을 통해 간편하게 로그인 할 수 있습니다. - 로그인 후에는 개인 정산 방 목록 화면으로 이동합니다. - 로그인 하지 않은 사용자는 서비스 내부 기능에 접근할 수 없습니다. (로그인 시연 영상 첨부)
2. 방(Room) 만들기 및 목록 조회 - 사용자는 여행 단위로 정산 방을 생성할 수 있습니다. - 방 생성 후 자동으로 방 목록에 추가됩니다. - 로그인한 사용자는 자신이 참여중인 모든 방을 한눈에 확인할 수 있습니다. - 설정 화면에서 초대 코드 복사 기능을 통해 친구들을 방에 초대할 수 있습니다. - 초대 코드를 공유받은 사용자는 해당 코드를 입력하여 방에 참여할 수 있습니다.
- 생성되거나 참여한 방은 방 목록에 표시되며, 클릭 시 상세 페이지로 이동합니다. (방 만들고 방 상세내용 설정)
3. 방 상세 페이지 - 선택한 방의 지출 내역을 확인할 수 있습니다. - 현재 방에 등록된 모든 지출 정보가 전체보기/날짜별로 보기 형태로 표시됩니다. - 방 내부에서 지출 등록 및 정산 확인이 가능합니다. (방 상세 화면 첨부)
4. 지출 등록 기능 - 사용자는 금액과 내용을 입력하여 새로운 지출을 등록할 수 있습니다. (ocr 영수증 인식 또는 직접 입력) - 등록 즉시 지출 목록에 반영됩니다. - 여행 중 발생하는 비용을 실시간으로 기록할 수 있습니다.
5. 자동 정산 계산 기능 - 등록된 지출을 바탕으로 정산 결과를 확인할 수 있습니다. - 각 사용자가 누구에게 얼마를 송금해야 하는지 안내됩니다. - 복잡한 계산 없이 정산 결과를 직관적으로 확인할 수 있습니다.
6. 정산 요청 기능 (카카오톡 연동) - 정산 결과 화면에서 “정산 요청” 버튼 클릭 시 해당 사용자에게 카카오톡 메시지를 전송합니다. - 메시지에는 정산 금액과 카카오페이 결제 링크를 포함합니다. - 수동 송금 요청이 아닌, 카카오톡 기반 직접 요청 가능 합니다. - 정산 과정을 앱 외부(카카오페이)까지 연결하여 실제 결제까지 이어지는 구조입니다. (지출등록 → 정산 요청 버튼 클릭 → 카톡 수신 화면 영상 )

아키텍처 및 핵심 기능

시스템 아키텍처

우리 서비스는 서비스의 안정적인 확장과 빠른 배포 사이클을 확보하기 위해 Docker 컨테이너 기반의 아키텍처와 GitHub Actions를 활용한 CI/CD 파이프라인을 구축했습니다.
서비스 인프라 및 네트워크 흐름
1.
사용자의 요청은 다음과 같은 계층을 거쳐 안전하고 빠르게 처리됩니다.
Nginx (Reverse Proxy): 최전방에서 사용자의 HTTPS 요청을 받아 정적 리소스(React)를 서빙하고, API 요청은 백엔드 컨테이너로 프록시합니다. 클라이언트와 서버 사이의 완충 지대 역할을 하며 보안과 부하 분산을 관리합니다.
Docker Compose: 프론트엔드, 백엔드, 데이터베이스(MySQL, Redis)를 각각 독립된 컨테이너로 캡슐화하여 관리합니다. 환경 변수 및 네트워크 설정을 코드(YAML)로 관리하여 환경 간의 격차를 해소했습니다.
AWS EC2: 가용성이 높은 클라우드 인프라 위에서 Docker 엔진을 통해 서비스 전체 시스템을 안정적으로 호스팅합니다.
2.
자동화된 배포 파이프라인 (CI/CD)
코드의 품질 관리와 신속한 배포를 위해 GitHub Actions를 도입했습니다.
Continuous Integration (CI): 푸시된 코드에 대해 JUnit을 활용한 테스트를 자동으로 수행하여 코드의 정합성을 검증합니다.
Continuous Deployment (CD): 테스트가 완료된 코드는 Docker 이미지로 빌드되어 EC2 서버로 전달되며, Docker Compose를 통해 무중단에 가까운 배포 과정을 거쳐 실서비스에 반영됩니다.
3.
데이터 및 외부 연동 전략
Naver CLOVA OCR: 여행 중 발생한 영수증 이미지를 분석하여 결제 금액, 날짜, 품목 정보를 정밀하게 추출합니다. 이를 통해 사용자의 수동 입력 리소스를 80% 이상 절감합니다.
MySQL & Redis
MySQL: 여행 방, 지출 내역, 유저 간 정산 관계 등 핵심 비즈니스 데이터를 관계형 모델로 저장하여 데이터 무결성을 보장합니다.
Redis: 카카오 OAuth 토큰 관리 및 세션 대체재로 활용하여 인증 절차의 응답 속도를 최적화했습니다.

핵심 기능

1.
정산 프로세스 및 상태 관리
정산은 UNSETTLED → REQUESTED → COMPLETED 세 단계로 관리됩니다.
목록 및 상세 조회: 사용자는 자신이 돈을 보내야 할 내역(Sender)과 받을 내역(Receiver)을 통합하여 조회할 수 있습니다.
사용자 간 요약: 특정 상대방과 주고받을 총금액을 상태별(미정산, 요청됨, 완료됨)로 합산하여 보여줍니다.
상태 변경: 정산 요청 메시지를 보내면 REQUESTED로, 돈을 받는 사람이 수령 확인을 하면 COMPLETED로 변경됩니다.
2.
카카오톡 연동 정산 요청 (핵심 차별점)
돈을 받을 사람(Receiver)이 보낼 사람(Sender)에게 카카오톡으로 정산 요청 메시지를 전송하는 기능입니다.
친구 매칭: 카카오 API를 통해 친구 목록을 불러와 시스템 사용자 ID와 매칭되는 카카오 UUID를 찾아 메시지를 보냅니다.
송금 링크 생성: 사용자의 kakaoPaySuffix와 정산 금액을 조합하여 카카오페이 송금 바로가기 딥링크를 생성합니다. (예: https://qr.kakaopay.com/...)
커스텀 메시지 카드: 지출 내역(품목명 또는 지출 제목)과 총 금액이 포함된 피드 형태의 카카오톡 메시지를 전송하여 편리한 송금을 유도합니다.
3.
지출(Expense) 도메인과의 긴밀한 결합
settlement 도메인은 독립적인 데이터를 가지기보다 expense 도메인에서 생성된 분담 내역(ExpenseAllocation)을 소비합니다.
N분의 1 정산: 지출 전체 금액을 인원수대로 나눈 내역을 처리합니다.
품목별 정산: 개별 품목(ExpenseItem)에 할당된 특정 금액에 대한 정산을 처리합니다.
4.
권한 및 검증 로직
Receiver 전용 권한: 정산 요청 메시지 전송과 정산 완료(COMPLETED) 처리는 오직 돈을 받을 사람(Receiver)만 가능하도록 제한되어 있습니다.
상태 검증: 이미 완료된 정산에 대해 중복 요청을 하거나, 요청되지 않은 상태에서 완료 처리를 하는 등의 잘못된 흐름을 방지합니다.
이 정산 기능은 단순한 장부 기록을 넘어 카카오톡과 카카오페이 링크를 활용해 실제 송금 액션까지 연결해주는 것이 가장 큰 특징입니다.

활용 라이브러리 및 개발 환경

백엔드

JAVA 17
백엔드 인원이 공통적으로 익숙하기에, 엔티티·서비스·API 전반을 하나의 언어로 통일해 유지보수와 팀 협업을 쉽게 했습니다.
LTS라 장기 지원이 보장되고, Spring Boot 3.x 요구사항을 만족했습니다.
Spring Boot
Spring Boot를 사용해 REST API 서버를 빠르게 구축하고, Web·JPA·Security·Redis 등의 자동 설정을 활용해 인프라 구성 코드를 최소화했습니다.
컨벤션 기반 설정을 통해 개발·배포 환경의 일관성을 유지했습니다.
JPA
JPA를 활용해 User, Group, Expense, ExpenseAllocation 등 관계형 데이터를 객체 모델로 설계하고, Repository와 트랜잭션을 일관된 방식으로 관리했습니다.
CRUD 및 쿼리 메서드를 활용해 반복적인 데이터 접근 코드를 줄이고 도메인 중심 설계를 적용했습니다.
MySQL
그룹·지출·정산 같은 관계가 있는 데이터를 저장하고 트랜잭션으로 정합성을 보장하기 위해 사용했습니다. 또한 널리 사용되는 RDBMS로서 운영, 백업, 마이그레이션 도구와 생태계가 풍부하다는 점을 고려해 선택했습니다.
Redis
카카오 OAuth 액세스 토큰을 영속 DB에 저장하지 않고 Redis에 저장해 TTL 기반 만료 관리를 적용했습니다.
인메모리 특성을 활용해 빠른 조회 성능을 확보하고, 토큰과 같은 휘발성 데이터를 효율적으로 관리했습니다.
Spring Security
Spring Security 필터 체인과 @AuthenticationPrincipal을 활용해 인증·인가 흐름을 통일하고, CORS 및 세션 정책(STATELESS)을 중앙에서 관리했습니다.
JWT와 결합해 서버에 세션을 두지 않는 구조를 만들었습니다.
JWT
로그인 시 JWT를 발급하고 토큰 기반으로 요청을 인증해 서버가 세션을 유지하지 않는 Stateless API를 구현했습니다.
만료 시간을 설정해 토큰 라이프사이클을 관리했습니다.
Gradle
Gradle을 사용해 의존성과 플러그인을 선언적으로 관리하고, Java 17 Toolchain을 명시해 개발·CI 환경의 빌드 일관성을 확보했습니다. 캐시 및 증분 빌드를 활용해 빌드 시간을 최적화했습니다.
Swagger
@Operation, @Parameter로 컨트롤러와 동기화된 API 문서를 제공해 프론트와 협업을 쉽게 했습니다. JWT Bearer 스킴을 명시해 인증이 필요한 API를 문서에서 구분할 수 있게 했습니다.
JUnit
서비스·API 동작을 자동으로 검증해 리팩터링 시 회귀를 막고, Spring Boot Test·Security Test로 통합·인증 경로까지 테스트했습니다.
Backend: Java 17, Spring Boot, JPA, MySQL, Redis, Spring Security, JWT
Build: Gradle
Docs/Test: Swagger, JUnit

프론트엔드

React 19
컴포넌트 기반 UI 아키텍처로 화면 단위를 모듈화하고, 상태와 UI를 선언적으로 관리하기 위해 사용했습니다.
React 19 런타임을 기반으로 훅 중심 컴포넌트 패턴으로 개발했습니다.
Vite 7
빠른 개발 피드백을 위해 ESM 기반 번들링과 HMR을 제공하는 Vite를 선택했습니다.
트리 쉐이킹으로 번들 크기를 최적화하고, @/ path alias를 적용해 import 경로 중첩을 줄였습니다.
React Router v7
SPA에서 URL 기반 상태 관리를 명확히 하고, 그룹 단위(/rooms/:roomId/*) 중첩 라우트로 공통 레이아웃을 재사용하기 위해 사용했습니다.
RequireAuth 패턴으로 인증이 필요한 구간을 라우트 레벨에서 보호해 페이지별 인증 로직 중복을 제거했습니다.
API Client (Fetch Wrapper)
베이스 URL, 인증 헤더, JSON/FormData 처리, 에러 파싱 정책을 중앙화해 네트워크 요청 로직의 중복을 제거했습니다.
HTTP 상태 코드 기반 예외 처리 방식을 통일했습니다.
도메인별 API 모듈
백엔드 도메인 구조와 매핑되는 모듈 구조로 책임을 분리했습니다.
일부 API는 응답 포맷 혼재를 고려해 res?.data || res로 정규화했고, 나머지는 raw 응답 또는 폴백 값을 반환하도록 처리했습니다.
정산 기능은 그룹·지출·유저 API를 조합해 구현했습니다.
Tailwind CSS v4
유틸리티 기반 스타일링으로 CSS 파일 관리 비용과 네이밍 규칙 설계 부담을 줄이기 위해 선택했습니다.
디자인 토큰(@theme)으로 다크 모드와 UI 스타일을 일관되게 관리했습니다.
Radix UI + CVA + cn
접근성 구현 비용을 줄이기 위해 Radix 프리미티브를 사용했습니다.
CVA로 variant/size 스타일을 선언적으로 관리하고, cn으로 클래스 충돌을 방지했습니다.
Lucide React
SVG 아이콘을 컴포넌트 단위로 사용해 트리 쉐이킹을 적용하고, 단일 아이콘 세트로 UI 톤을 통일했습니다.
RequireAuth + LocalStorage (JWT)
백엔드 JWT 기반 Stateless 인증 아키텍처에 맞춰 클라이언트는 토큰 저장·전달만 담당하도록 설계했습니다.
RequireAuth에서 토큰 존재 여부로 라우트 접근을 제어하고, 카카오 OAuth는 백엔드 스펙에 맞춰 리다이렉트 및 토큰 저장만 수행했습니다.
Frontend: React 19, Vite, React Router, Tailwind CSS, Radix UI Client: Fetch API, LocalStorage Auth: OAuth (Kakao), JWT

트러블 슈팅

카카오페이 API 중단

 이슈
카카오페이 API 중 코드송금 기능이 서비스 개선을 이유로 잠정 중단
서비스 핵심 기능이 해당 API에 의존하고 있어 대체 결제 흐름 설계가 필요
분석
카카오페이 QR 딥링크 구조를 분석한 결과, URL에 반복되는 패턴이 존재함을 확인
qr.kakaopay.com/{고정 문자열}{동일 금액의 경우 반복되는 문자열}{랜덤 4자리} [구분하시기 쉽게 중간에 구분자로 공백을 넣었습니다.] ex) 10,000원 qr.kakaopay.com/FQDhayxSR 13880 1213 qr.kakaopay.com/FQDhayxSR 13880 9311 ex) 12,345원 qr.kakaopay.com/FQDhayxSR 181C8 5812 qr.kakaopay.com/FQDhayxSR 181C8 2331 ex) 777,777원 qr.kakaopay.com/FQDhayxSR 5EE6E8 4132 qr.kakaopay.com/FQDhayxSR 5EE6E8 1322
JavaScript
복사
외부 자료 조사 및 역공학 결과
초기 고정 문자열은 사용자 UUID 기반 값
중간 금액 기반 문자열은 (금액 × 8)을 16진수로 변환한 문자열
마지막 4자리는 랜덤 값으로 추정
마지막 4자리 값은 동일 값으로 반복 사용하거나 임의로 변경해도 결제 동작에 영향을 주지 않았으며, 서버 측 유효성 검증에 사용되는 보안 서명(값)이 아닌 단순 중복 방지나 트래킹 목적의 식별자로 추정됨.
해결 방법
코드송금 API 대신 QR 딥링크를 직접 생성하는 방식으로 대체
카카오 친구 메시지 발송 API에 생성한 딥링크를 포함하여 송금 요청을 전달하도록 구현
API 중단 상황에서도 결제 플로우를 유지할 수 있도록 의존성 제거

카카오 소셜 인증 Redirect URL 문제

 이슈
카카오 인증 API를 통해 OAUTH2 소셜 로그인을 구현하는 과정에서 인증 API 오류로 CI/CD에 차질 발생
원인 분석
프론트엔드 로컬 개발용 Redirect URL
프론트엔드 배포 환경 Redirect URL
1.
배포 환경에서는 카카오 인증 API에 문제가 없으나 프론트엔드 로컬 개발 환경에서는 오류 Response를 반환하는 것을 파악
2.
카카오 API 요청 시 Redirect URL이 배포 환경의 것으로 고정되어 있어 로컬 개발 시에는 Redirect URL 불일치 문제로 인해 카카오 Access Token 발급 요청이 차단된 것으로 판단
 해결 방법
프론트엔드 로컬 개발용 인증 API를 따로 구현함으로써 로컬 환경에서도 인증 API를 원활히 사용할 수 있도록 개선
로컬 개발용 인증 API
백엔드 내부적으로는 Service Layer에서 간단한 분기를 통해 로컬 개발 환경과 배포 환경에 대한 API 로직 일관성을 유지하여 신뢰성 확보
카카오 Access Token 발급 로직의 분기 처리

팀 소개

깊이 있는 고민과 자연스러운 협업 흐름을 지향하는 DeepFlow팀입니다.
팀장 | 기획 / 백엔드 안녕하세요! 최대한 덜 일하기 위해 코드를 더 열심히 짜는 개발자입니다.
▸ 그룹 및 정산 로직 설계 및 구현 ▸  카카오 메시지 api를 이용한 송금 메시지 전송 구현 ▸  프론트엔드–백엔드 API 연동 및 통합 지원
| 프론트엔드
안녕하세요! 사용자 경험을 고민하며 꾸준히 성장하는 프론트엔드 개발자입니다!
▸ 프론트엔드 상태 관리 / 구조 설계·구현 ▸  프론트엔드 전반 단독 개발
| 백엔드 안녕하세요! 기획과 프론트를 이해하며 ‘숲’을 보는 풀스택 백엔드 개발자입니다.
▸ CI/CD 파이프라인 구축 ▸ 카카오 소셜 인증 구현 ▸ Docker·NGINX·EC2 HTTPS 배포 환경 운영 ▸ 프론트엔드-백엔드 API 연동 지원
| 백엔드
안녕하세요! 부족한 부분을 꾸준히 채워가며 개발을 이어가는 개발자입니다!
▸ 영수증 OCR 인식 구현 ▸ 지출 관리 및 등록 기능 구현