HTTP 헤더로 에러 테스트하기

HTTP 헤더는 클라이언트와 서버 간의 HTTP 요청과 응답에서 추가 정보를 전달하는 데 사용돼요. 이 정보에는 요청의 유형(Content-Type), 응답의 유형(Accept), 인증 정보(Authorization), 캐싱 제어(Cache-Control, ETag, Last-Modified) 등 다양한 메타데이터가 포함되죠.

HTTP 헤더는 이름-값 쌍 형식이라 {HTTP_HEADER_KEY}: {HTTP_HEADER_VALUE} 형식으로 쓰는데요. 예를 들어 볼게요. 토스페이먼츠 API의 모든 필드와 오류 메시지는 기본적으로 한글로 제공돼요. 만약 응답을 영문으로 받고 싶다면 요청 헤더에 Accept-Language: en-US를 포함하면 간단히 해결됩니다. 팝업 차단 에러를 방지하기 위한 헤더도 사용할 수 있는데요. 결제 과정에서 팝업 차단 에러가 나는 것을 방지하기 위해 COOP(Cross-Origin-Opener-Policy) 헤더를 Cross-Origin-Opener-Policy: same-origin-allow-popups와 같이 포함할 수 있어요.

이렇게 표준 HTTP 헤더 외에도 토스페이먼츠에서는 에러 시나리오를 테스트를 할 때 사용할 수 있는 커스텀 HTTP 헤더를 제공하는데요, 이번 포스트에서는 커스텀 HTTP 헤더에 대해 자세히 알아볼게요.

커스텀 HTTP 헤더란?

커스텀 HTTP 헤더는 개발자가 직접 정의해서 사용하는 HTTP 헤더입니다. 표준 HTTP 헤더가 아니라 특정 서비스에서만 사용되는 추가 정보를 전달하기 위해 사용하죠. 표준 HTTP 헤더와 마찬가지로 서버는 클라이언트로부터 받은 커스텀 헤더를 바탕으로 특정 동작을 수행하거나, 클라이언트에게 필요한 추가 정보를 제공하기 위해 커스텀 헤더를 사용할 수 있어요. 클라이언트 입장에서는 서버에게 특정 요청을 보낼 때 커스텀 헤더를 사용하여 추가 정보를 제공하거나, 서버의 응답에서 커스텀 헤더를 통해 받은 정보를 활용할 수 있어요. 사용은 표준 HTTP 헤더와 마찬가지로 헤더의 이름과 값을 지정해서 요청 또는 응답에 포함시키면 됩니다.

과거에 커스텀 HTTP 헤더의 이름은 X-로 시작하는 것이 일반적으로 권장되었어요. X-는 표준화되지 않은 헤더라는 것을 나타냈기 때문이에요. 그런데 최신 HTTP 표준에서는 이 방식을 지원 중단(Deprecated) 하기로 했어요. 즉, X- 없이도 사용자 정의 헤더를 정의할 수 있어요. 예를 들어 My-Custom-Header라는 이름의 커스텀 헤더를 사용하려면 X-My-Custom-Header가 아니라 아래와 같이 만들어서 사용하면 돼요.

My-Custom-Header: {CUSTOM_VALUE}

커스텀 HTTP 헤더를 사용할 때는 헤더 이름이 충돌하지 않도록 주의해야 해요. 특히 다른 표준 헤더와 이름이 겹치지 않도록 하는 것이 좋아요. 헤더 이름을 명확하고 식별 가능하게 지정하거나, 접두사를 사용하는 것도 좋아요. 토스페이먼츠에서는 Tosspayments라는 접두사를 붙였어요.

에러 시나리오 테스트하기

토스페이먼츠에서는 커스텀 HTTP 헤더를 이용해서 에러 시나리오를 테스트할 수 있도록 했어요. 이 헤더를 이용하면 라이브 환경에서 나는 에러를 연동 과정에서 미리 발생시키고, 해당 에러 시나리오에 맞는 처리를 추가할 수 있어요. 다양한 에러 케이스를 미리 테스트해 볼 수 있으니 편리해요. 에러 테스트용 커스텀 HTTP 헤더는 테스트 키를 사용할 때만 적용되고, 라이브 환경에서는 테스트 코드 헤더가 무시돼요.

테스트 환경에서 에러를 재현하는 방법을 알려드릴게요. 아래와 같이 TossPayments-Test-Code 헤더를 추가하고, {TEST_CODE} 값으로 테스트하고 싶은 에러 코드를 넣고 API를 실행하면 됩니다.

TossPayments-Test-Code: {TEST_CODE}

만약 카드 번호 결제 API를 연동하다가 잘못된 유효기간을 넣은 케이스의 에러 시나리오를 테스트하고 싶어요. 그러면 해당 케이스에 맞는 에러 코드 INVALID_CARD_EXPIRATION를 테스트 헤더에 추가해서 API 요청을 보내면 돼요. 터미널에서 아래 코드를 복사해서 요청해 보세요.

curl --request POST \
  --url https://api.tosspayments.com/v1/payments/key-in \
  --header 'Authorization: Basic dGVzdF9za196WExrS0V5cE5BcldtbzUwblgzbG1lYXhZRzVSOg==' \
  --header 'Content-Type: application/json' \
  --header 'TossPayments-Test-Code: INVALID_CARD_EXPIRATION'

아래와 같은 응답이 돌아오는지 확인해 보세요.

{ 
	"code": "INVALID_CARD_EXPIRATION",
	"message":"카드 정보를 다시 확인해주세요.\n(유효기간)"
}

프로젝트에 적용해보기

결제위젯 프로젝트에 적용해볼게요. 가장 중요한 마지막 결제 승인 요청에서 생길 수 있는 에러 중 카드 유효기간 정보 에러를 테스트 해 볼게요. 먼저 서버 코드에 테스트할 에러 코드와 TossPayments-Test-Code를 추가해요.

// ...

var secretKey = "test_sk_zXLkKEypNArWmo50nX3lmeaxYG5R";
var testCode = "INVALID_CARD_EXPIRATION"; // 에러 테스트용 코드

app.use(express.static("./client"));

app.get("/", function (req, res) {
  var path = resolve("./client/index.html");
  res.sendFile(path);
});

app.get("/success", function (req, res) {
  var { paymentKey, orderId, amount } = req.query;

  var encryptedSecretKey =
    "Basic " + Buffer.from(secretKey + ":").toString("base64");

  got
    .post('https://api.tosspayments.com/v1/payments/confirm', {
      headers: {
        Authorization: encryptedSecretKey,
        'Content-Type': 'application/json',
        'TossPayments-Test-Code': testCode, // 에러 테스트용 커스텀 헤더 추가
      },
      json: {
        orderId: orderId,
        amount: amount,
        paymentKey: paymentKey,
      },
      responseType: 'json',
    })
    .then(function (response) {
      console.log(response.body);

      var path = resolve('./client/success.html');
      res.sendFile(path);
    })
    .catch(function (error) {
      res.redirect(
        `/fail?code=${error.response?.body?.code}&message=${error.response?.body?.message}`
      );
    });
});

// ...

클라이언트 코드에는 에러를 콘솔로 볼 수 있도록 맨 아래에 간단히 콘솔로 에러 이벤트만 추가해볼게요.

// ...

const paymentRequestButton = document.getElementById('payment-request-button');
paymentRequestButton.addEventListener('click', () => {
  paymentWidget.requestPayment({
    orderId: generateRandomString(),
    orderName: '토스 티셔츠 외 2건',
    successUrl: window.location.origin + '/success',
    failUrl: window.location.origin + '/fail',
  });
});

const generateRandomString = () => window.btoa(Math.random()).slice(0, 20);

window.addEventListener('error', function (event) {
  console.error('An error occurred:', event.error);
});

테스트 결제를 진행하면 실제로 에러가 발생한 것처럼 failUrl로 이동해요. 아래처럼 에러 코드와 메시지가 돌아온 것을 확인할 수 있어요.

http://localhost:4242/fail?code=INVALID_CARD_EXPIRATION&message=카드 정보를 다시 확인해주세요.(유효기간) 

브라우저 콘솔창에 찍힌 에러도 확인해보세요.

참고: 구현 이해하기

에러 테스트용 커스텀 HTTP 헤더가 요청됐을 때의 서버 처리 프로세스는 대략 아래와 같아요.

TEST_ERROR_CODE = REQUEST_HEADER['Tosspayments-Test-Code'];

if TEST_ERROR_CODE && TEST_AUTH_KEY {
  ERROR = ERROR_CODES[testCode];
	if ERROR {
	  return ERROR;
	} else {				
	  return INVALID_TEST_CODE);
	}
	return;
}
  1. HTTP 요청 헤더에서 Tosspayments-Test-Code에 해당하는 헤더 값을 가져옵니다. 이 값이 에러 테스트용 커스텀 헤더입니다.
  2. 인증 키가 존재하고, Authorization 인증 키가 테스트 키라면 다음 단계를 진행합니다.
  3. 커스텀 헤더로 들어온 에러 코드가 정의된 에러 코드 중 하나인지 확인합니다.
    • 보낸 에러 코드가 정의된 에러 목록에 존재한다면, 해당 테스트 코드에 해당하는 에러 객체를 보내주고 요청 처리를 중단합니다.
    • 존재하지 않는 에러 코드라면, INVALID_TEST_CODE에 해당하는 에러 메시지를 전달하고 요청 처리를 중단합니다.
  4. 테스트 코드 헤더가 존재하지 않는다면, 요청 처리를 계속합니다.

토스페이먼츠 API를 사용하면 커스텀 HTTP 헤더로 다양한 에러 시나리오를 테스트하고 개발 과정에서 예상치 못한 문제를 미리 발견하고 대응할 수 있어요. 토스페이먼츠와 함께 더욱 견고한 결제 시스템을 구축해보세요.

Writer 한주연 Graphic 이은호, 이나눔

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

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

    의견 남기기

    연관 콘텐츠