토스 결제 API

남예준·2025년 9월 29일

TIL을 날짜 별로 말고 주제별로 정리하는 게 좋을 거 같다.

Version 2

순서

  1. 고객이 앱을 통해 결제 요청

  2. 클라이언트가 그 내용을 정리해서 토스에 보냄

  3. 그럼 토스가 결제창을 띄워서 보여줌

  4. 결제창에서 해야 할 일은 결제 수단에 대한 인증을 진행해야 함 ⇒ 아마 이 과정에서 결제 수단에 대한 정보를 프론트엔드에 줘야 할 수도 있겠네(원하는 모양대로)

  5. 인증 여부에 따라 분기가 된다.

    1. 결제 인증이 성공됐다면 successUrl로 이동한다.

      1. /success?orderId={ORDER_ID}&paymentKey={PAYMENT_KEY}&amount={AMOUNT}
        위의 Url로 query 파라미터를 들고 이동한다. 참고로 이건 프론트쪽 Url일 수도 서버쪽일 수도 있는데 위 그림에서는 프론트 쪽 Url이라고 돼있다.

      2. 그러면 ORDER_ID, PAYMENT_KEY, AMOUNT를 서버에 저장해주면 된다.

      3. 그 이후 프론트가 서버에 저 파라미터로 서버에 요청을 보내면 그걸로 DB(결제)에 저장을 하면 된다.

      4. 그 정보로 하여금 백엔드는 토스 API 서버로 승인요청을 보내게 된다.

      5. Go To 6 (결제 승인)

    2. 결제 인증이 실패했다면 failUrl로 이동한다.

      1. /fail?code={ERROR_CODE}&message={ERROR_MESSAGE}&orderId={ORDER_ID}
        위의 Url로 query 파라미터를 들고 이동한다.
      2. 그러면 에러 코드와 메시지를 확인해서 구매자에게 적절한 안내 메시지 전달
      3. 이건 프론트 단에서 처리할 수 있다.
  6. 서버는 결제 승인 요청을 Toss 서버로 보내줘야 한다.
    결제 승인에 대한 Response Body다. 이것도 선별해서 DB(결제 승인)에 저장하면 된다.

결제 취소

  1. 전액 취소하기
    1. 결제 승인 API 요청 결과로 받은 payment Key를 Path 파라미터로 추가해서
      https://api.tosspayments.com/v1/payments/{paymentKey}/cancel
      위의 url로 헤더에 auth 값과 content type json을 넣고 body에 결제 취소 이유를 넣은 다음 POST 요청을 하면 된다.
    2. 응답으로 결제 취소 정보 객체를 받을 수 있다.
      각 취소 거래마다 거래를 구분하는 transactionKey를 가지고 있다.
    3. 이 응답이 오면 골라서 저장하면 된다.
  2. 부분 취소
    1. 전체 취소와 크게 다를 것은 없다.
      https://api.tosspayments.com/v1/payments/wi6l-EBYXg5AI0ndBCqNH/cancel
      위의 url로 헤더에 토큰과 content type json으로 두고 body에 취소 이유와 환불 금액을 명시하면 된다. 만약 부분 취소를 여러 번 하면 cancels 필드에 취소 객체가 여러 개 들어온다.

프론트쪽의 명세를 보려면

https://docs.tosspayments.com/sdk/v2/js

우리가 개발해야 하는 API 통신에 대한 정보를 보려면

https://docs.tosspayments.com/reference

orderId는 프론트쪽에서 만들어서 주는 방법도 괜찮은 거 같고

아니면 우리가 만들어서 주는 것도 괜찮은 거 같다.

결제와 주문 관련 ERD를 수정해야 하므로 어디서부터 사고를 시작하면 될까…

먼저 장바구니는 프론트엔드에서 관리한다.

⇒ 대신 amount 검증 필요

  1. 프론트에서 장바구니로 하여금 주문 객체를 만들어줄 것을 요청한다.

    1. 아마 뭐 가게, 배달지 주소, 연락처, amount, 요청사항 등이 담길 거임.
  2. 그럼 장바구니의 내용을 토대로 주문 객체에 대한 정보를 전달해준다.

    1. 단 amount에 대한 검증이 필요하다 직접 해당 상품들로 amount를 뽑아내는 것이 중요할 듯.
    2. 만약 amount에 대한 검증 내용이 일치한다면 아래와 같이 orderId를 포함해서 아래의 정보를 보내준다. 속성 이름은 그대로 복붙해오긴 했다. 결제창 띄우는 파라미터는 어차피 프론트엔드 개발자가 설정해서 할 거라 크게 신경 안써도 될 것 같다.
      orderId: ~,
      orderName: "~~~~",
      customerEmail: "customer123@gmail.com",
      customerName: "김토스",
  3. 그럼 해당 정보를 토대로 프론트엔드가 고객에게 결제창을 띄워줄 것이고 그렇게 success url을 거쳐 우리 백엔드는 그 정보로 결제 승인 요청을 하게 된다.

    1. successUrl로 들어온 정보는 payment Key와 amount로 orderId에 종속해서 만들면 된다.
  4. 결제 승인에 대한 response body를 저장하면 된다.

  5. 결제 취소에 대한 건은 계속 쌓아주는 걸로

    1. 결제 취소 금액은 owner가 직접 입력해주는 것으로 결정 → owner, manger, master

https://docs.tosspayments.com/guides/v2/payment-window/integration

다시 정리

  1. 장바구니로 주문 객체 요청하기
  2. 주문 객체 정보 보내기
  3. 주문 객체로 하여금 결제 창 만들고 결제 수단 인증하기
  4. success Url에서 받은 정보로 결제 객체 만들기
  5. 해당 객체 정보로 결제 승인 요청 보내기

이슈 사항

기본적으로 프론트엔드에게 많이 맡기자라는 생각으로 처음부터 생각한 설계입니다.

  1. 주문에 배달비 속성이 존재하는 것에 대해서 약간 로직이 꼬일 수 있다고 생각
    1. 제가 설계한 ERD는 장바구니에 대한 관리를 전적으로 프론트엔드한테 위임했기 때문에 배달비에 대한 가격 관리를 하려면 뭐 가게 테이블에 배달비가 명시되어 있다든지 그래야 합니다. 아니면 아예 가격을 통일해버리든…? ⇒ 로그아웃 했다가 왔을 때 장바구니를 유지해야 하나라는 생각이 좀 크긴 했습니다
    2. 장바구니에서 주문으로 요청하기 때문에 고객이 장바구니에서 아 이 가격이 나오는구나 하고 반환된 주문 엔터티를 보면 배달비가 포함되어 가격이 달라질 수 있으니 이런 부분 어떻게 할지는 좀 흐름을 명확히 하는 게 좋을 것 같습니다.
    3. 그래서 일단은 그냥 가격(amount)이라는 속성으로 통일했습니다. 뭐 가게 별로 배달비를 다르게 한다든가 아니면 아예 통일시킨다는 생각을 할 수 도 있다고 생각합니다.
  2. 결제에 대한 객체는 주문 객체가 생성된 이후 결제 승인 요청을 보낼 때 생성된다
  3. 결제 수단에 대한 결정은 결제 UI가 뜬 이후로 생성되므로 주문 객체가 생성된 이후이다.
  4. 주문 객체와 결제 객체는 가능하면 1대1로 하는 게 좋을 것 같다고 생각한다.
    결제에 실패하는 상황이 오면 원래 가지고 있던 장바구니로 다시 주문 객체부터 생성을 요청하도록

0개의 댓글