API 응답을 받고 나면 어떻게 해요?

by 토스페이먼츠

REST API를 호출하면 HTTP 응답을 받아요. 그럼 API 응답을 확인하고, 응답에 맞는 처리를 해야겠죠. 토스페이먼츠 API를 예시로 HTTP 응답이 어떻게 구성되었는지, 어떤 부분을 중심으로 확인해야 되는지 알아볼게요.

REST API? HTTP?

먼저 REST API와 HTTP는 어떻게 연결되어 있을까요?

HTTP는 원래 웹 브라우저와 웹 서버가 통신하기 위해 만들어진 프로토콜이에요. 브라우저와 서버가 일종의 규칙을 지키는 것과 같죠. 브라우저에서 특정 형태로 요청을 보내면, 서버에서도 특정 형태로 응답을 돌려줘요. 서로의 요청과 응답을 쉽게 해석할 수 있고 성공・실패 여부를 전달해요.

반면 REST API는 소프트웨어 아키텍처에요. 규칙이라기보다 소프트웨어 설계에 대한 철학을 정의하고 있어요. 일관성 있는 무상태(Stateless) 인터페이스가 REST API의 특징이지만, 개별 컴포넌트는 개발자가 자유롭게 구현할 수 있어요.

REST 아키텍처의 클라이언트-서버 통신은 HTTP 프로토콜을 사용해요. 물론 REST 아키텍처에서 다른 통신 프로토콜을 사용할 수도 있지만, 일반적으로 HTTP가 활용돼요. 그래서 대부분의 REST API에서 받는 응답은 HTTP 응답 형식을 따르고요. 토스페이먼츠 API 역시 REST 아키텍처를 기반으로 하기 때문에 HTTP 응답을 내리고 있어요.

승인 API 응답

이제 토스페이먼츠 API로 HTTP 상태 코드, 응답 헤더, 본문을 더 자세히 알아볼게요.

토스페이먼츠 결제 승인 API를 호출해볼게요. 상태 줄, 응답 헤더까지 확인하기 위해 -v 를 추가해 상세 출력 모드로 API를 호출하세요.

curl -v --request POST \
  --url https://api.tosspayments.com/v1/payments/confirm \
  --header 'Authorization: Basic dGVzdF9za19hQlg3emsyeWQ4eW9Yd29KMGdxVng5UE9McUtROg==' \
  --header 'Content-Type: application/json' \
  --data '{"paymentKey":"5zJ4xY7m0kODnyRpQWGrN2xqGlNvLrKwv1M9ENjbeoPaZdL6","orderId":"a4CWyWY5m89PNh7xJwhk1","amount":15000}'

응답이 아래와 같이 출력돼요. 길고 복잡해 보이는데, 하나씩 차근차근 알아볼게요.

< HTTP/2 404 
< date: Tue, 09 May 2023 02:13:44 GMT
< content-type: application/json
< content-length: 85
< vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
< access-control-allow-credentials: true
< access-control-allow-methods: POST, GET, OPTIONS, DELETE
< access-control-max-age: 3600
< access-control-allow-headers: Origin, Content-Type, Accept, X-Requested-With, Key, Authorization, Referer-Policy, x-secure-keyboard-id, x-secure-keyboard-fields, sentry-trace, x-tosspayments-device-id, x-tosspayments-session-id, x-publickey-id, tosspayments-test-code, tosspayments-mid, idempotency-key
< referrer-policy: no-referrer-when-downgrade
< x-tosspayments-trace-id: 562e7f63de4da484940242680b47f25a
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< cache-control: no-cache, no-store, max-age=0, must-revalidate
< pragma: no-cache
< expires: 0
< strict-transport-security: max-age=31536000
< server: tc
< x-envoy-upstream-service-time: 62
< 
* Connection #0 to host api.tosspayments.com left intact
{"code":"NOT_FOUND_PAYMENT","message":"존재하지 않는 결제 정보 입니다."}

1️⃣ 응답 상태 줄

응답의 첫 줄은 상태 줄(Status Line)인데요. HTTP 프로토콜 버전상태 코드를 확인할 수 있어요. 토스페이먼츠는 HTTP/2 프로토콜 버전을 사용하고, 위에 보낸 API 요청은 404 상태로 응답이 왔어요.

< HTTP/2 404

404는 무슨 의미일까요? HTTP 상태 코드는 아래와 같이 5개의 유형으로 나뉘어요. 상태 코드의 첫 번째 숫자로 HTTP 상태를 알 수 있어요. 4xx 상태 코드는 클라이언트 에러를 가리키는데요, 404 NOT FOUND 에러는 클라이언트가 서버에 없는 리소스를 요청했다는 뜻이에요. 어떤 리소스가 서버에 없는지는 응답 본문을 확인해야 알 수 있어요. 404와 같이 각 유형에 있는 자세한 코드는 HTTP 상태 코드에서 알아보세요.

2️⃣ 응답 헤더

상태 줄 밑에 Key:Value 형태로 나오는 줄은 응답 헤더에 해당해요. 결제 연동할 때 응답 헤더는 꼭 확인해야 되는 건 아니지만, 서버-클라이언트 통신에 대한 추가적인 정보를 제공하고 있어요. 받은 응답의 일부 헤더를 자세히 살펴보면서 토스페이먼츠 API는 콘텐츠, 보안 측면에서 어떻게 만들어졌는지 알아볼게요.

< date: Tue, 09 May 2023 02:13:44 GMT
< content-type: application/json
< content-length: 85
< vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Origin,Access-Control-Request-Method,Access-Control-Request-Headers
< access-control-allow-credentials: true
< access-control-allow-methods: POST, GET, OPTIONS, DELETE
< access-control-max-age: 3600
< access-control-allow-headers: Origin, Content-Type, Accept, X-Requested-With, Key, Authorization, Referer-Policy, x-secure-keyboard-id, x-secure-keyboard-fields, sentry-trace, x-tosspayments-device-id, x-tosspayments-session-id, x-publickey-id, tosspayments-test-code, tosspayments-mid, idempotency-key
< referrer-policy: no-referrer-when-downgrade
< x-tosspayments-trace-id: 562e7f63de4da484940242680b47f25a
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< cache-control: no-cache, no-store, max-age=0, must-revalidate
< pragma: no-cache
< expires: 0
< strict-transport-security: max-age=31536000
< server: tc
< x-envoy-upstream-service-time: 62

콘텐츠

  • content-type: application/json
    • 응답 본문은 JSON 파일이에요. JSON을 파싱해서 응답 본문을 확인하세요.
  • content-length: 85
    • 응답 본문의 길이는 85 바이트(byte)예요.

보안

  • referrer-policy: no-referrer-when-downgrade
    • 프로토콜 보안이 동일한 경우(HTTP → HTTP)에는 오리진, 패스, 쿼리 스트링을 모두 보내요: https://api.tosspayments.com/v1/payments/confirm. 하지만 보안이 더 낮은 환경(HTTPS → HTTP)에서 응답을 받으면 오리진만 보내요: https://api.tosspayments.com/.
  • x-content-type-options: nosniff
    • 콘텐츠 스니핑을 방지해요. 콘텐츠 스니핑은 응답 본문의 콘텐츠를 사용해서 MIME(Multipurpose Internet Mail Extensions) 타입을 유추하는데요. 콘텐츠를 표시할 때 도움 될 수도 있지만, 악용되면 스크립트에 악의적인 코드가 삽입될 수도 있어요.
  • x-xss-protection: 1; mode=block
    • XSS 필터링을 사용해요. 공격이 의심되면 페이지 렌더링을 멈춰요.
  • strict-transport-security: max-age=31536000
    • HTTPS만을 통해서 접근할 수 있다는 것을 클라이언트가 31,536,000초(1년) 동안 기억해요.

3️⃣ 응답 본문

실패

응답의 마지막 줄에 있는 본문을 확인할게요. 만약 에러 상태 코드를 받았다면 꼭 응답 본문을 확인해서 원인을 파악하세요. 토스페이먼츠 에러 본문에는 아래와 같이 코드와 메시지가 있어요.

승인 API의 응답으로 받은 메시지를 확인하니 존재하지 않는 paymentKey, orderId 정보로 API를 호출했기 때문에 에러가 발생한 것을 알 수 있어요. 토스페이먼츠 연동 과정에서 발생할 수 있는 모든 에러를 확인하고 에러에 맞는 대응을 해주세요.

{"code":"NOT_FOUND_PAYMENT","message":"존재하지 않는 결제 정보 입니다."}

성공

토스페이먼츠 결제창 연동하기 가이드를 따라서 올바른 paymentKey, orderId 정보로 결제 승인 API를 호출하세요. 토스페이먼츠 승인 API를 성공적으로 호출하면 200 OK 상태 코드와 아래와 같은 Payment 객체가 응답 본문으로 돌아와요. Payment 객체를 더 자세히 살펴볼게요.

{
  "mId": "tosspayments",
  "version": "2022-11-16",
  "paymentKey": "GRZsY5vcKZmThoeJMcKwv",
  "status": "DONE",
  "lastTransactionKey": "Me5McMEK-M71nrunv_tke",
  "orderId": "TBtoNKlSpgNhr2T8ZkNau",
  "orderName": "토스 티셔츠 외 2건",
  "requestedAt": "2022-06-08T15:40:09+09:00",
  "approvedAt": "2022-06-08T15:40:49+09:00",
  "useEscrow": false,
  "cultureExpense": false,
  "card": {
    "issuerCode": "61",
    "acquirerCode": "31",
    "number": "12345678****789*",
    "installmentPlanMonths": 0,
    "isInterestFree": false,
    "interestPayer": null,
    "approveNo": "00000000",
    "useCardPoint": false,
    "cardType": "신용",
    "ownerType": "개인",
    "acquireStatus": "READY",
    "amount": 15000
  },
  "virtualAccount": null,
  "transfer": null,
  "mobilePhone": null,
  "giftCertificate": null,
  "cashReceipt": null,
  "cashReceipts": null,
  "discount": null,
  "cancels": null,
  "secret": null,
  "type": "NORMAL",
  "easyPay": null,
  "country": "KR",
  "failure": null,
  "isPartialCancelable": true,
  "receipt": {
    "url": "https://dashboard.tosspayments.com/sales-slip?transactionId=KAgfjGxIqVVXDxOiSW1wUnRWBS1dszn3DKcuhpm7mQlKP0iOdgPCKmwEdYglIHX&ref=PX"
  },
  "checkout": {
    "url": "https://api.tosspayments.com/v1/payments/GRZsY5vcKZmThoeJMcKwv/checkout"
  },
  "currency": "KRW",
  "totalAmount": 15000,
  "balanceAmount": 15000,
  "suppliedAmount": 13636,
  "vat": 1364,
  "taxFreeAmount": 0,
  "taxExemptionAmount": 0,
  "method": "카드"
}
  • paymentKey: 결제 정보를 다시 조회할 때, 결제를 취소하고 싶을 때 필요한 값이에요.
  • card: 결제창에서 결제수단으로 카드를 선택해서 카드 정보가 채워져있어요. 만약 결제수단으로 가상계좌를 선택했다면 card 필드는 null으로 돌아오고 virtualAccount 필드가 가상계좌 정보로 채워 돌아왔을 거예요.
  • receipt: 영수증(매출전표)를 확인할 수 있는 링크에요. 하지만 테스트 환경에서는 제공되지 않아요.
  • totalAmount: 총 결제액이에요.

응답으로 돌아온 Payment 객체를 꼼꼼히 확인하세요. 필요한 정보를 파싱해서 데이터베이스에 저장하세요.

📍 참고하면 좋을 자료

Writer 박수연 Graphic 이은호, 이나눔

토스페이먼츠의 모든 콘텐츠는 사업자에게 도움이 될 만한 일반적인 정보를 ‘참고 목적’으로 한정해 제공하고 있습니다. 구체적 사안에 관한 자문 또는 홍보를 위한 것이 아니므로 콘텐츠 내용의 적법성이나 정확성에 대해 보증하지 않으며, 콘텐츠에서 취득한 정보로 인해 직간접적인 손해가 발생해도 어떠한 법적 책임도 부담하지 않습니다.

ⓒ토스페이먼츠, 무단 전재 및 배포 금지

    의견 남기기
    토스페이먼츠

    고객사의 성장이 곧 우리의 성장이라는 확신을 가지고 더 나은 결제 경험을 만듭니다. 결제가 불편한 순간을 기록하고 바꿔갈게요.