본문으로 건너뛰기

🍓 ReadyVery

간편 선결제 테이크아웃 서비스
"고객이 미리 주문/결제하고 매장에서 바로 픽업 또는 테이블에 주문 전달 가능"한 서비스를 목표로 개발


  1. 프로젝트 개요
  2. 주요 기능
  3. 구성
  4. 기술 스택
  5. 담당 역할
  6. 핵심 기술 도전
  7. 주요 성과
  8. 배운 점
  9. 관련 링크

📋 프로젝트 개요​

항목내용
기간2023.12 ~ 2024.05 (6개월)
팀 구성개발팀 BE 2명·FE 4명 / 협업 PM 1명·Designer 1명·Marketer 1명
역할프론트엔드 개발 및 API 인터페이스 설계
배포AWS EC2 + Nginx

🎯 주요 기능​

고객 사이드​

  • 🔐 소셜 로그인: Kakao OAuth2 연동
  • 🛒 선결제 테이크아웃: 메뉴 선택 → 결제 → 픽업
  • 🍽️ 테이블 오더: QR 코드 스캔 → 주문 → 알림
  • 💳 결제: Toss Payments SDK 연동 (카드/간편결제)
  • 🎟️ 쿠폰/포인트: 할인 및 적립 시스템

사장님 사이드​

  • 📊 매장 관리: 정보 수정, 영업 시간 설정
  • 🍔 메뉴 관리: 메뉴 등록/수정/삭제, 카테고리 관리
  • 📦 주문 확인: 실시간 주문 알림, 상태 관리
  • 💰 매출 통계: 일별/월별 매출 현황

🏗️ 구성​

화면 구성​

figma


디렉토리 구조​

// 사장님
src/
├── components/
│ ├── views/ # 공통 뷰
│ └── Sales/ # 매출 관리 차트
├── pages/
│ ├── OrderManage/
│ ├── ...
├── hooks/
├── Atom/
├── util/
└── constants/

// 고객
src/
├── components/
│ ├── Authentication/
│ └── views/
├── pages/
│ ├── HomePage/
│ ├── PaymentPage/
│ └── MyPage/
├── hooks/
├── Atom/
├── utils/
└── constants/

핵심 설계 원칙:

  • 관심사 분리: UI 컴포넌트와 비즈니스 로직(Custom Hooks) 분리
  • 재사용성 강화: Header, NavBar 등 공통 요소를 views/ 또는 components/ 하위로 분리
  • 상태 관리 최적화: 전역(Recoil) / 서버(React Query) 상태 명확히 구분하여 불필요한 리랜더링 방지

ERD​

ERD_Customer - Customer ERD
ERD - Owner ERD

🔧 기술 스택​

Frontend​

기술선택 이유
React + Vite빠른 빌드 속도, HMR 지원으로 개발 생산성 향상
Recoil간결한 API, 비동기 처리 용이, 작은 번들 사이즈
React Query서버 상태 캐싱, 자동 재검증, 로딩/에러 상태 관리
React Hook Form성능 최적화된 폼 처리, 유효성 검사 간소화
Toss Payments SDK국내 환경 최적화 PG 연동
AxiosInterceptor로 토큰/에러 중앙 관리

Backend​

  • Java + Spring Boot: 안정적인 서버 개발
  • JPA + MySQL: 데이터 영속성 관리
  • Redis: 세션 관리

🎯 담당 역할​

1. Toss Payments SDK 통합 및 결제 시스템 구축​

문제: 쿠폰·포인트 상태가 바뀔 때 Toss Payments Widget이 반복 초기화되며 로딩 지연과 화면 깜빡임 발생

해결: useEffect 최적화 + useRef 활용

// pages/PaymentPage.jsx
const PaymentPage = () => {
const paymentWidgetRef = useRef(null);
const paymentMethodsWidgetRef = useRef(null);

const paymentRequest = () => {
const paymentWidget = paymentWidgetRef.current;
const paymentMethodsWidget = paymentMethodsWidgetRef.current;

requestPayment(cartId, couponId, paymentWidget, paymentMethodsWidget, usedPoint);
};

// SDK 초기화를 금액 상태 변경과 분리
useEffect(() => {
(async () => {
const paymentWidget = await loadPaymentWidget(clientKey, customerKey);
paymentWidgetRef.current = paymentWidget;

const paymentMethodsWidget = paymentWidget.renderPaymentMethods(...);
paymentMethodsWidgetRef.current = paymentMethodsWidget;
})();
}, []); // 의존성 배열 비움 → 1회만 실행

return (


결제하기

);
};
// hooks/useRequestPayment
const useRequestPayment = () => {
const requestPayment = async (
cartId,
couponId,
paymentWidget,
paymentMethodsWidget,
point,
) => {
try {
// 1. 서버에 결제 정보 전송
const response = await api.post("/api/payments/prepare", {
cartId,
couponId,
point,
});

// 2. 서버가 계산한 최종 금액으로 위젯 업데이트
paymentMethodsWidget.updateAmount(Math.max(response.data.amount, 0));

// 3. Toss 결제창 호출
paymentWidget?.requestPayment(response.data);
} catch (error) {
console.error("결제 실패:", error);
}
};

return requestPayment;
};

핵심 개선

  • ✅ SDK 초기화를 금액 관련 상태 변경과 분리
  • ✅ 결제 버튼 클릭 시 서버 금액 기반으로 updateAmount 호출
  • ✅ useRef로 SDK 인스턴스 값을 관리 → 불필요한 리렌더링 제거

상세 분석 보기 →


2. 홈/마이페이지, 매장 관리 화면 전체 개발​

  • 마이페이지: 쿠폰/포인트/주문 내역 조회
  • 매장 관리: 정보 관리, 메뉴 관리, 주문 확인 UI

3. Recoil 기반 전역 상태관리 및 React Query 데이터 페칭 최적화​

// Atom 정의
export const cartState = atom({
key: "cartState",
default: [],
});

export const selectedCouponState = atom({
key: "selectedCouponState",
default: null,
});

// 서버 상태 관리 (React Query)
const useMenu = (storeId) => {
return useQuery({
queryKey: ["menu", storeId],
queryFn: () => api.get(`/api/stores/${storeId}/menu`),
staleTime: 1000 * 60 * 5, // 5분간 캐시 유지
});
};

🔥 핵심 기술 도전​

결제 SDK의 반복 초기화 개선​

문제: 결제 금액 상태 변경 시 Toss SDK 초기화와 금액 갱신 로직이 반복 실행

해결: useEffect 의존성 최적화 + useRef 활용

항목BeforeAfter
SDK 초기화금액 관련 상태 변경마다 재실행컴포넌트 생명주기에 맞춰 실행
금액 갱신별도 Effect에서 실행결제 요청 시 서버 응답 후 실행
화면 깜빡임반복 초기화 과정에서 발생반복 초기화 제거 후 완화

상세 분석 보기 →


📊 주요 성과​

실사용 배포 및 운영​

  • ✅ 교내 축제에서 테이블 오더 서비스 운영
  • ✅ 학교 인근 카페 2곳 실사용 배포 및 실제 운영
  • ✅ 점주와 사용자 피드백을 반영해 주문 UI 개선

기술적 성과​

  • ✅ Toss SDK 초기화와 금액 갱신의 실행 시점 분리
  • ✅ SDK 반복 초기화를 제거해 화면 깜빡임 완화
  • ✅ Recoil + React Query로 전역/서버 상태 명확히 분리

💡 배운 점​

비즈니스적 배움​

  • 기획–디자인–백엔드–마케팅과의 협업 과정을 통해, 서비스 전반을 바라보는 시야 확보
  • 실제 점주 및 사용자 피드백을 반영하며, 비즈니스 관점에서 기능을 우선순위화하는 경험

기술적 배움​

  • useEffect 의존성 관리 중요성
    • 외부 라이브러리는 React의 렌더링 사이클과 분리해서 관리
  • 비동기 작업 순서 관리 경험
    • SDK 인스턴스 초기화와 금액 갱신의 실행 순서가 결과에 영향을 줄 수 있음을 확인
    • 결제 버튼 클릭을 금액 갱신의 단일 진입점으로 구성
  • 백엔드 개발 전환 계기
    • API 설계 과정에서 데이터 정합성, 트랜잭션 처리의 중요성 인식
    • 결제 금액의 최종 검증과 주문 상태 변경을 서버가 책임지고, 클라이언트가 그 결과를 일관되게 표현하는 구조의 중요성을 체감
    • 결제/주문 도메인의 복잡성을 이해하며 백엔드 개발에 대한 관심 증대

🔗 관련 링크​