3일차(2)/ 토스 페이먼츠 결제 연동: 프론트엔드를 건너뛴 대가

3일차(2)/ 토스 페이먼츠 결제 연동: 프론트엔드를 건너뛴 대가

FastAPI로 백엔드 결제 연동만 확인하고 프론트엔드 템플릿 확인을 건너뛴 탓에 checkout.html, success.html, fail.html, content-cta.hbs 등 주요 화면들이 연결돼 있다는 걸 뒤늦게 알았다. 그 결과 ui화면과 결제창의 연결이 끊겼다. 강의를 다시 보며 프론트 파일을 복구하고 템플릿 수정을 해야 했다.


1. 결제 연동은 되었지만… 뭔가 허전

FastAPI로 토스 페이먼츠 API를 붙이는 작업은 비교적 수월했다. 토스 페이먼츠 문서가 매우 직관적인 편인 것 같다. 테스트 결제 요청도 정상적으로 전송되고 응답도 잘 받았다. 하지만 이상하게도 프론트 화면에는 아무런 변화가 없었다.
"결제가 붙었다면 뭔가 화면이 떠야 하지 않나..?" 어디선가 놓쳤구나, 깨닫게 되었다.

2. 프론트 템플릿을 건너뛴 실수

당시 프론트엔드 파트의 강의를 앞부분만 보고(폰트 사이즈나 백그라운드 컬러 등을 수정하는 부분만 보고.. 이런 내용이 반복되는 줄 알고) 건너뛰었다. 그러나 이후 강의에서 결제 연동 시 필요한 html 작업 방법, Ghost 테마 엔진에서 사용하는 .hbs 파일 경로 등이 안내된 것이었다.

  • templates/checkout.html: 결제 버튼이 렌더링되는 핵심 페이지
  • templates/success.html & fail.html: 결제 성공/실패 후 사용자가 보게 되는 결과 페이지
  • content-cta.hbs: 결제로 넘어가기 전 보여지는 Call-To-Action 템플릿
checkout.html, success.html, fail.html :토스 페이먼츠 가이드에서 제공하는 파일이어서 안내되는 대로 세팅

content-cta.hbs의 경우 Ghost의 CMS가 서버에 설치돼 있어서 해당 파일도 서버 안의 특정 폴더에 위치. 브라우저에서는 직접 접근 불가해 터미널이나 FTP 프로그램을 통해 접근해야 확인 가능

*FTP: File Transfer Protocol. 원격 서버와 로컬 컴퓨터 간에 파일을 전송하거나 탐색할 수 있는 방법

3. 다시 프론트엔드 파트로 회귀

어디서 놓친 건지 알아보기 위해 강의를 프론트엔드 부분부터 다시 봤다. 그런 뒤에야 이 템플릿들이 실제 결제 흐름에서 어떻게 사용되는지, 왜 필요한지 알게 되었다.

4. 서버 안에 숨어 있던 frontend 디렉토리

프론트엔드 파일들은 /frontend 디렉토리에 들어 있었다. 이 디렉토리는 로컬에서 별도로 접근하거나 수정되지 않도록 서버 컨테이너 안에 감추어져 있었다.
강의를 따라 해당 디렉토리를 서버에서 꺼내는 명령어를 적어 frontend 폴더를 로컬로 다운로드했다.

docker cp 컨테이너 ID:/var/lib/ghost/current/core/frontend ./frontend

그런 뒤에 frontend 폴더의 이 디렉토리 ./frontend/helpers/tpl/content-cta.hbs 에서 content-cta.hbs 파일을 찾아서 현재 포맷에 맞춰 수정. 지금의 모습을 구현할 수 있었다.

결제 test 환경 구현_완료_0716
Thoughts, stories and ideas.

강의에서는 테스트 환경에서도 결제가 되는 것으로 보였는데, 지금 시점에서는 테스트 결제도 안되는 것 같다. 그리고 원래의 결제창 ui 샘플은 내가 원하는 모습으로 조금 커스텀도 했다. (변경 사항: 글간격, 폰트 사이즈, 결제창 백그라운드 컬러, 결제하기 버튼 hover 백그라운드, 계정 및 결제 정보 불필요한 애니메이션 삭제 등..)

checkout.html 변경하고 나서는 <유료 회원 결제하기> ui 안내 문구 폰트 사이즈도 너무 커보여서 수정했다. 공통 css 수정을 하려다보니 h2 모두 변경될 수 있어서 해당 화면에서만 폰트 사이즈 조정되도록 해두었다.

<h2 style="font-size: 18px;">이 {{#is "page"}}페이지{{else}}포스트{{/is}}는 유료 회원만 볼 수 있습니다</h2>

마무리하며

  • 단계를 쉽게 건너뛰지 말고 차근차근 스텝을 밟아야 한다.
  • 백엔드가 잘 작동해도 사용자가 마주하는 화면 등 직관적 경험은 프론트와 연결된다.
  • 서버 내 구조를 인지하지 못하면 중요한 파일을 놓치기 쉽다.
  • 백엔드 작업할 때에는 전체적인 구조와 역할 파악 필요하다.
단순히 폴더 이름을 읽어본다는 의미가 아님. 전체 시스템이 어떻게 작동하는지 '흐름'을 파악해야 함


📖 스터디

백엔드의 전체적인 구조와 역할은 구체적으로 어떻게 파악해야 하는지 궁금해져서 아래의 내용들을 찾아봤다.

1. "전체 구조"는 기능 흐름을 말한다

단순히 API만 만드는 것이 아니라 전체 시스템 흐름 안에서 어떤 역할을 하는지 이해해야 한다.

접근 방법

  • 엔드유저의 행동 → 서버의 흐름을 머릿속에 시뮬레이션
    "유저가 상품을 결제했을 때 무슨 일이 일어나는가?"
    → 프론트 요청 → 백엔드 컨트롤러 → 서비스 → DB → 외부 API → 응답
  • 그 흐름을 따라가며 코드 안에서 아래 내용 파악:
    • 어떤 파일들이 이 흐름을 처리하는가?
    • 어디서부터 어디까지 로직이 이어지는가?
    • 그걸 디렉토리 구조로 표현하면 어떻게 되어 있나?

2. 디렉토리 구조를 읽을 때의 스캔 포인트

폴더 이름 질문 (검토 포인트)
models/ 또는 entities/ 데이터 구조는 무엇인가? 이 앱의 핵심 도메인은 어떤 것인가?
services/ 비즈니스 로직이 잘 분리되어 있는가? 하나의 서비스가 너무 많은 역할을 하나?
repositories/ DB 접근 로직이 분리되어 있는가? ORM, 쿼리 빌더, 원시 쿼리 중 어떤 방식을 쓰는가? 트랜잭션 처리나 예외 처리는 일관적인가?
controllers/ 어떤 HTTP 요청을 처리하나? 어떤 도메인 (ex. 유저, 주문) 중심인가?
routes/ 또는 api/ API는 어떻게 구성되어 있고 버전관리는 되어 있나? RESTful하게 구성되어 있는가?
config/ 설정 정보가 잘 분리되어 있는가? 환경변수 관리는 적절한가?
middlewares/ 공통 로직은 어떤 게 있고, 어디에 적용되는가? (ex. 인증, 로깅, 에러 핸들링)
utils/ 보조 기능이 잘 정리되어 있는가? 반복되는 로직은 적절히 추출되어 있는가?
tests/ 테스트는 어떤 방식으로 구성되어 있는가? 유닛/통합 테스트가 존재하며, 중요한 기능은 커버되어 있나?
ORM / 쿼리 빌더 / 원시 쿼리:
ORM: 객체처럼 다룰 수 있는 도구 (예: Prisma, TypeORM)
쿼리 빌더: SQL을 함수처럼 조립하는 도구 (예: Knex.js)
원시 쿼리: 그냥 SQL문 직접 쓰는 것 (예: SELECT * FROM users)
RESTful한 구성:
URL 설계와 요청 방식이 명확하고 일관되며 의미가 잘 전달되는 구조
URL은 '무엇(자원)'을 다루는지를 표현하고, HTTP 메서드는 '무엇을 하려는지(동작: GET, POST, PUT, DELETE)'를 표현하는 구조
-
GET /users 사용자 목록 조회 *RESTful하지 않은 예: POST /getUserList
- POST /users는 새로운 사용자를 생성 *RESTful 하지 않은 예: POST /createUser

3. 실제 파일 구조 예시와 해석

작은 프로젝트

┣ 📂src
┃ ┣ 📂routes ← 클라이언트가 보낸 HTTP 요청이 들어오는 첫 접점
┃ ┣ 📂controllers ← HTTP 요청을 받아서 서비스 호출
┃ ┣ 📂services ← 핵심 비즈니스 로직 처리
┃ ┣ 📂repositories ← DB 접근 로직 (쿼리, ORM 등)
┃ ┣ 📂models ← DB 테이블과 1:1 매칭되는 데이터 구조, Entity
┃ ┣ 📂middlewares ← 인증, 로깅, 에러처리 등 공통 처리
┃ ┣ 📂utils ← 날짜/문자열 등 재사용 유틸 함수
┃ ┗ 📂config ← 환경변수, DB 설정 등 외부 설정
┣ 📂tests ← 유닛 테스트 / 통합 테스트
┣ 📄.env ← 환경 변수 파일 (.env)
┗ 📄main.ts ← 프로그램 시작 지점 (Express/Node일 경우)

비유
main.ts = "식당 문을 여는 주인"
routes = "식당 앞에서 손님을 담당 부서(홀/주방)로 안내하는 리셉션"
controller = "담당 부서에서 실제 손님 처리(음식 주문 등)"

도메인별 분리된 프로젝트

📂src
┣ 📂domains ← 도메인 단위로 비즈니스 로직 구성
┃ ┣ 📂user ← 유저 관련 기능
┃ ┃ ┣ 📄user.controller.js ← HTTP 요청 처리
┃ ┃ ┣ 📄user.service.js ← 비즈니스 로직
┃ ┃ ┗ 📄user.repository.js ← DB 접근
┃ ┗ 📂product ← 상품 관련 기능
┃ ┣ 📄product.controller.js ← HTTP 요청 처리
┃ ┣ 📄product.service.js ← 비즈니스 로직
┃ ┗ 📄product.repository.js ← DB 접근
┣ 📂shared ← 🔁 공통 모듈 (미들웨어, 유틸 등)
┗ 📂infrastructure ← 🌐 외부 시스템 연동 (DB, API 등)

4. 실무 팁

  • 무작정 코드부터 보지 말고 README(프로젝트 목적과 주요 기능 파악) → main entry point(어떤 미들웨어가 어떤 순서로 실행되는지 확인) → 라우터 → 컨트롤러 → 서비스 → 모델 순서 (실제 HTTP 요청이 처리되는 흐름과 동일, 데이터가 어떻게 변환되고 전달되는지 이해) 로 따라가기
  • 각 디렉토리/파일 옆에 메모하면서 "이건 어떤 요청을 처리하고, 어떤 데이터를 만들고, 누구한테 넘기지?"를 적어보기 *특히 머릿속 생각으로만 구조를 파악하려 하지 말고 흐름도 그려보기

이 내용들이 실제 서비스에서는 어떻게 적용되는지 궁금해져서 내가 업무했던 곳과 그 외 도메인 몇 곳을 적어서 대입해서 보았다. 그게 더 이해하기 좋았다. 글이 너무 길어져서 그 내용은 다음 포스트에서...