
📷 실제 카트로 바코드를 찍으면 앱 장바구니에 바로 반영됩니다
인천대학교 제22회 창의적종합설계경진대회에서 저희 팀 카트라이더(CartrAIder)가 인기상을 받았습니다. 저는 이 프로젝트에서 고객용 모바일 앱과 관리자 화면, 프론트엔드를 맡았습니다.
5월 아이디어 발표부터 10월 1일 최종 부스 발표까지, 어떤 흐름으로 무엇을 만들었고 어디서 막혔으며 어떻게 풀었는지 정리해 보려고 합니다.
📷 최종 발표자료 표지
| 시기 | 단계 |
|---|---|
| 2026년 5월 | 아이디어 발표, 파트별 개발 계획서 작성 |
| 7월 20일 | 중간발표 |
| 7월 ~ 9월 | 본격 개발 |
| 9월 말 | 실제 카트·서버·앱을 붙인 리허설 |
| 10월 1일(목) | 최종 발표. 하루 종일 부스 운영 → 인기상 |
저희 팀은 정보기술대학 컴퓨터공학부 중심이었습니다. 공대 동아리에서 나온 팀도 아니었고 사회공헌 아이디어도 아니어서 가산점을 받지 못했는데, 그런 조건에서 상을 받아 정말 만족스러운 결과였습니다.
📷 문제 제기 슬라이드
마트 계산대 줄은 셀프 계산대가 늘어도 여전히 깁니다. 긴 줄 때문에 구매를 포기한 경험이 있다는 사람이 39%, 더 빠른 계산이면 매장을 바꿀 수 있다는 사람이 56%라는 조사도 있었습니다. 셀프 계산이 늘면서 스캔 누락으로 인한 상품 손실이라는 새 문제도 생겼습니다.
그렇다고 스마트카트가 처음 나온 아이디어는 아닙니다. 이마트 일라이, Caper Cart, Shopic 같은 사례가 이미 있었는데, 공통적으로 화면·카메라·결제 기능을 전부 카트 안에 넣다 보니 단가와 유지보수 비용이 커져서 널리 보급되지 못했습니다.
그래서 저희는 반대로 갔습니다.
📷 솔루션: 두 개의 모듈
![]()
| 모듈 | 하는 일 |
|---|---|
| 카트 모듈(ESP32) | 바코드 리더로 상품을 찍으면 MQTT로 서버에 보내고, 앱 장바구니에 즉시 반영합니다. 바닥 경계선을 넘으면 서보모터가 바퀴를 잠급니다. |
| AI 출구 게이트(Raspberry Pi) | 파인튜닝한 YOLO11s(정확도 91%)로 카트 속 상품을 인식해 결제 내역과 대조합니다. |
| 모바일 앱(제 담당) | 카트 QR 연결, 실시간 장바구니, 토스 결제, 출구 QR, 상품·매장 지도, 관리자 기능 |
📷 카트 모듈 실물
📷 바닥선을 넘으면 카트가 멈춥니다
![]()
📷 AI 출구 게이트
📷 5월 초기 설계 → 9월 최종 설계
![]()
5월 첫 발표 때의 설계는 지금과 꽤 달랐습니다. 카트 안에 카메라를 달고 영상을 클라우드 AI로 보내 상품을 인식하는 구조였고, 서버 쪽도 FastAPI → Kafka → Spring Boot로 이어지는 파이프라인에 앱과는 WebSocket으로 통신할 계획이었습니다.
최종적으로는 이렇게 바뀌었습니다.
| 항목 | 초기(5월) | 최종(9월) |
|---|---|---|
| 상품 인식 | 카트 카메라 + 클라우드 AI | 카트의 바코드 리더 |
| 카트 하드웨어 | Raspberry Pi + DC 모터 | ESP32 + 서보 브레이크 |
| AI 위치 | 카트 | 출구 게이트 한 곳 |
| 메시징 | Kafka | MQTT |
| 서버 → 앱 | WebSocket | SSE |
바코드는 인식 오류가 거의 없고 저렴합니다. 비싼 비전 AI는 출구 한 곳에만 두면 비용이 매장당 한 번으로 끝납니다. 비용과 정확도라는 현실에 맞춘 단순화였습니다.
중간발표에서는 날카로운 질문과 피드백을 많이 받았습니다.
"바코드 리더기를 없애고 그냥 스마트폰 카메라를 쓰면 안 되나?"
"마트에서는 카트를 험하게 다루는데 내구성이 괜찮을까?"
"이런 주제는 기존에도 있는 걸로 아는데 진부하지 않나?" (심사위원들 사이에서도 의견이 갈렸습니다)
"이것보다 더 심플하게 하는 게 좋지 않나? 차라리 핸드폰을 카트에 붙이는 게 낫지 않나?"
이 피드백을 최종 발표까지 반영했습니다. 기존 사례는 회사별로 나눠 각각 왜 실패했는지 정리했고, "고객의 스마트폰이 곧 카트의 화면"이라는 구조를 전면에 내세웠습니다.
📷 기존 사례 슬라이드: 5월 vs 피드백 반영 후
다행히 앱 입장에서는 이 피벗이 큰 타격이 되지 않았습니다. 처음부터 "앱은 Spring Boot 서버 하나만 알고, 카트·AI·브로커는 모른다"는 경계를 정해 두었기 때문입니다. 카메라가 바코드로, Kafka가 MQTT로 바뀌어도 앱이 받는 것은 똑같이 "서버가 보내는 장바구니"였습니다.
저는 사실 React Native는 3년 전에 잠깐 다뤄 본 것이 전부였습니다. 이번처럼 앱 하나를 처음부터 끝까지 책임지고 적극적으로 써 본 것은 처음이었습니다.
3년이면 React Native 생태계가 많이 바뀌기에 충분한 시간이었습니다. 기억 속의 방식이 지금도 맞는지부터 의심해야 했고, 그래서 블로그 글보다 공식 문서를 먼저 확인하는 습관을 들였습니다.
src/app 폴더 구조로 화면 23개를 정리했습니다.StyleSheet.absoluteFillObject가 사라져 오버레이가 조용히 깨지던 문제, expo-media-library의 API가 바뀌어 결제 완료 화면이 크래시 나던 문제가 대표적이었습니다.runtimeVersion 정책을 문서를 보며 설정했습니다. eas update가 빌드 프로필의 환경변수를 읽지 않는다는 함정도 문서를 읽다가 발견했습니다."예전엔 이렇게 했던 것 같은데"로 시작하면 거의 항상 틀렸습니다. 공식 문서와 실제 동작을 확인하고 나서 코드를 쓰는 것이 결국 가장 빠른 길이었습니다.
이번 프로젝트에서는 Claude를 적극적으로 활용했습니다. 다만 코드부터 짜지 않고 아래 순서를 지켰습니다.
📷 7월 초 유저 플로우 메모
설계 결과는 레포의 CLAUDE.md, AGENTS.md(코딩 규칙, 폴더 구조, 쓰지 않을 라이브러리)와 스프린트 문서로 남겨 두었습니다. 덕분에 AI와 작업할 때도 같은 규칙 안에서 일관된 결과물이 나왔습니다.
라이브러리는 일부러 최소한으로 썼습니다.
| 쓰지 않은 것 | 이유 |
|---|---|
| Axios | fetch 래퍼 한 파일로 토큰 재발급·타임아웃까지 처리할 수 있었습니다. |
| TanStack Query | 실시간 데이터는 SSE가 맡고, 나머지는 요청 몇 개뿐이었습니다. |
| Redux / Zustand | 전역 상태가 5덩어리로 나뉘고 서로 거의 섞이지 않아 Context로 충분했습니다. |
| WebSocket | 서버 → 앱 단방향이면 충분했습니다. |
📷 초기 UI 시안 → 최종 앱
📷 카트 연결부터 출구까지
고객 화면 17개와 관리자 화면 6개, 총 23개 화면을 만들었습니다. 처음 계획한 화면은 5개였습니다.
📷 실시간 장바구니
📷 관리자 화면
📷 매장 지도 길 안내
📷 화면은 한 벌, 모드 토큰만 바꾼다
처음 문제의식 중 하나가 접근성이었기 때문에, 앱에는 노약자 모드가 있습니다. 중요하게 생각한 원칙은 화면은 한 벌만 만들고, 모드 토큰만 바꾼다는 것이었습니다. 화면을 두 벌로 만들면 한쪽만 고쳐지는 순간 "노약자 모드는 기능이 다른 앱"이 되어 버리기 때문입니다.
| 요소 | 일반 | 노약자 |
|---|---|---|
| 본문/버튼/금액 | 15/18/20pt | 18/24/30pt |
| 터치 영역 | 44px | 56px |
| 색 대비 | 표준 | WCAG AA |
| 상품 그리드 | 2열 | 1열 |
| 음성 안내 | 없음 | 상품 담김·결제 완료 안내 |
📷 최종 시스템 구성
📷 SSE 실시간 흐름
카트에서 바코드를 찍으면 서버가 SSE로 장바구니를 앱에 밀어줍니다. 단순해 보였지만 네 가지 문제가 있었습니다.
여기에 더해, 다시 연결될 때마다 현재 장바구니를 REST로 한 번 더 받아서 놓친 변경을 채웠습니다. 모두 "이벤트를 놓쳐도 결국 맞는 상태로 돌아오게" 하려는 장치였습니다.
📷 지도 버벅임 원인과 해결을 정리한 PR
상품 상세와 매장 지도에는 길 안내 애니메이션(경로, 카트 주행, 현위치 펄스, 목적지 핀)이 있습니다. 그런데 이 화면이 눈에 띄게 버벅였습니다.
transform 문자열로 움직이고 있었는데, react-native-svg가 transform을 JS에서 해석하기 때문이라고 판단했습니다. 숫자 prop(cx, cy)으로 바꾸고, 화면이 가려지면 애니메이션을 멈추게 했습니다.새 APK를 실기기에 설치해 버벅임이 사라진 것을 확인했습니다. "숫자 prop이니 빠르겠지"라는 1차 가설은 예전 아키텍처 기준의 지식이었습니다.
토스페이먼츠 결제창은 WebView에 띄웠습니다. 결제 성공·실패 리다이렉트 주소를 가로채 결제 키를 얻고, 최종 승인은 서버가 하도록 했습니다. 시크릿 키는 앱에서 쓰지 않습니다. 간편결제나 카드사 앱으로 넘어가는 intent:// 주소는 WebView가 열 수 없어서, 직접 파싱해 OS로 넘기고 앱이 없으면 스토어로 안내했습니다.
📷 개선 결과 수치
![]()
백엔드 API가 나오기 전에 모든 네트워크 함수를 mock 분기로 감싸고 전체 흐름을 먼저 완성했습니다. 덕분에 API가 없던 7월 초에도 앱 전체를 시연할 수 있었고, 실제 연동은 "mock을 진짜 호출로 바꾸는 작업"으로 진행할 수 있었습니다.
백엔드에 필요한 것은 GitHub 이슈로 정리해 요청했습니다. 무엇이 왜 필요한지, 엔드포인트·필드·상태코드, 그동안의 임시 우회 방법까지 적었습니다. 개인 주문 내역은 응답 구조와 결제 상태를 고르는 규칙까지 직접 제안했습니다.
📷 백엔드에 API 스펙을 제안한 이슈
인증번호를 무제한으로 대입할 수 있는 보안 문제를 발견했을 때는, 서버 저장소에서 직접 재현한 결과를 정리해 공유했습니다.
📷 보안 이슈를 재현해 공유한 이슈
앱 동작에 꼭 필요한 요청(모바일 토큰 방식, 회원 이름, 현재 장바구니 조회, 계정 관리, 내 정보, 주문 내역)은 같은 날부터 약 2주 안에 모두 반영되었습니다. 가장 빨랐던 것은 요청하고 약 1시간 만에 모바일 전용 인증 API가 생긴 경우였습니다.
📷 리허설 중 MQTT로 카트 메시지를 확인하던 화면
실제 카트, 운영 서버, APK로 빌드한 앱을 한꺼번에 붙인 리허설에서 문제가 많이 나왔습니다.
📷 실제 카트로 바코드를 찍는 장면
10월 1일 목요일, 하루 종일 부스를 운영했습니다. 심사위원과 기업 대표들이 직접 와서 평가했고, 학생들도 많이 찾아와 구경했습니다.
📷 부스 현장 사진
![]()
앱에 대한 반응이 특히 좋았습니다. 그중에서도 토스페이먼츠 결제 연동을 마음에 들어 하셨는데, 학생 프로젝트에서 실제 결제창을 띄우고 승인까지 이어지는 흐름을 신기해하셨습니다. 공대 교수님들이라 더 그렇게 보신 것 같습니다.
📷 인기상 사진
📷 결론 및 기대효과
이번 글은 지금까지 제 velog에 써 온 글들과는 조금 다른, 대회 회고록입니다. 5월 아이디어 발표부터 10월 1일 부스 발표까지 길게 이어진 대회였는데, 무사히 잘 마무리할 수 있어서 다행이라는 생각이 가장 먼저 듭니다.
3년 만에 다시 잡은 React Native로 실제 카트와 실제 결제까지 이어지는 앱을 끝까지 만들어 보니, 대회용 APK에서 멈추지 않고 실제로 앱을 출시해 보고 싶다는 생각도 들었습니다. 굳이 대회에 출품했던 이 앱 말고도, 스토어 심사를 거치고, 진짜 사용자를 만나고, 그 피드백으로 앱을 고쳐 나가는 경험까지 해 보고 싶어졌습니다.
무엇보다 하드웨어와 결합한 임베디드 성격의 개발은 이번이 처음이었습니다. 화면 안에서만 끝나던 개발과 달리, 카트에서 바코드를 찍는 순간이 MQTT와 서버를 거쳐 제 앱 화면에 뜨기까지 모든 구간이 이어져야 비로소 동작했습니다. 리허설 날 서버 로그와 카트 메시지를 하나씩 대조하며 끊긴 곳을 찾던 시간은 힘들었지만, 소프트웨어 바깥의 세계와 맞물려 돌아가는 시스템을 만든다는 게 어떤 것인지 몸으로 배운 뜻깊은 경험이었습니다.
학부생에게 하드웨어·AI·백엔드·앱을 한 팀에서 처음부터 끝까지 만들어 볼 기회는 흔치 않습니다. 몇 안 되는 기회라는 걸 알았기에 감사한 마음으로 더 열심히 준비했던 것 같고, 그 결과가 인기상이라는 형태로 돌아와 더 기쁩니다.
함께한 카트라이더 팀원들 감사합니다.