서버 SDK 없는 토스 정기결제 서버 연동

웹 · 앱 개발5분 읽기

토스페이먼츠로 정기결제를 붙이려고 문서를 뒤지다 보면 한 가지 사실을 마주합니다. 프런트엔드용 결제 위젯 SDK는 잘 갖춰져 있지만 서버에서 실제로 청구를 올리는 공식 Node 서버 SDK는 없습니다. 처음에는 당황스럽지만 알고 보면 서버 연동은 단순한 REST 호출 몇 개로 끝납니다. 자체 호스팅 콘솔에 월 구독을 붙이며 서버 쪽 토스 연동을 어떻게 구성했는지 정리합니다.

서버 SDK가 없다는 것의 의미

프런트엔드에서는 토스가 제공하는 결제 위젯 SDK를 써서 고객이 카드 정보를 입력하고 인증하는 화면을 띄웁니다. 이 과정이 끝나면 프런트엔드는 인증 키 하나를 손에 쥡니다. 이 뒤부터가 서버의 몫인데 여기에는 공식 SDK가 없습니다. 대신 토스 개발자 문서가 권하는 방식은 명확합니다. 서버에서 토스 REST API를 직접 호출하면 됩니다.

인증은 시크릿 키를 이용한 기본 인증 방식입니다. 시크릿 키 뒤에 콜론을 붙여 인코딩한 값을 인증 헤더에 담아 보냅니다. 이 시크릿 키는 절대 브라우저로 나가면 안 되는 값이라 토스를 호출하는 코드는 반드시 서버 쪽에서만 실행되도록 격리했습니다. 시크릿 키가 환경 변수에 없으면 호출 함수가 아예 오류를 내도록 해 설정이 빠진 채로 결제가 시도되는 일을 막았습니다.

빌링키를 발급받고 첫 결제를 올린다

정기결제의 출발점은 빌링키입니다. 빌링키는 특정 고객의 카드로 앞으로 반복해서 청구할 수 있는 열쇠입니다. 프런트엔드에서 카드 인증이 끝나면 넘어온 인증 키를 서버가 토스의 빌링키 발급 엔드포인트로 보냅니다. 이때 고객을 식별하는 키도 함께 넘겨 발급받은 빌링키가 어느 고객의 것인지 묶습니다. 발급이 성공하면 이 빌링키를 구독에 저장해 두고 매달 청구에 씁니다.

빌링키를 받은 직후 첫 결제를 바로 올립니다. 빌링키로 청구를 올리는 엔드포인트에 금액과 주문 번호, 주문 이름을 담아 호출하면 실제 결제가 일어나고 결제 키가 돌아옵니다. 여기서 중요한 것은 빌링키 발급, 첫 결제, 구독 생성이 하나의 흐름으로 이어지지만 실패 지점이 여럿이라는 점입니다. 빌링키는 발급됐는데 결제가 실패할 수도, 결제는 됐는데 구독 저장이 실패할 수도 있습니다.

그래서 결제가 성공한 뒤 구독과 첫 인보이스, 결제 이벤트 로그를 하나의 트랜잭션으로 묶어 저장합니다. 이 셋이 전부 저장되거나 전부 되돌아가도록 해 결제는 됐는데 구독 기록이 없는 어긋난 상태를 막았습니다. 트랜잭션 밖에서 실패가 나면 사용자에게는 다듬은 오류 메시지를 돌려주고 내부에는 원인을 로그로 남깁니다.

매달 청구도 같은 방식으로

첫 결제 이후의 정기 청구도 빌링키로 청구를 올리는 같은 엔드포인트를 씁니다. 다만 이번엔 사람이 아니라 매일 도는 워커가 이 호출을 만듭니다. 주문 번호는 구독 아이디와 청구 월을 조합한 고정된 값으로 만들어 같은 달의 같은 구독에 대한 청구가 우연히 두 번 나가더라도 토스 쪽에서 같은 주문으로 인식되게 했습니다. 청구가 성공하면 결제 키를 인보이스에 기록하고 다음 청구 기간을 갱신합니다.

청구가 실패하는 경우도 REST 응답으로 그대로 넘어옵니다. 카드 한도나 유효기간 문제로 토스가 청구를 거절하면 응답이 오류로 돌아오고 서버는 이를 붙잡아 실패 인보이스를 남기고 구독을 연체로 바꿉니다. 결제사 호출을 감싸는 함수가 오류 응답을 예외로 바꿔 던지도록 해 청구 로직이 성공과 실패를 명확히 갈라 다룰 수 있게 했습니다.

웹훅은 왜 라우트 핸들러여야 하는가

토스는 결제 상태 변화를 웹훅으로도 알려 줍니다. 이 웹훅을 받는 자리를 정할 때 한 가지 함정이 있었습니다. 최신 프레임워크에서는 서버 액션이라는 편리한 방식으로 서버 로직을 부르는데 웹훅 수신에는 이 방식을 쓸 수 없습니다. 서버 액션은 우리 프런트엔드에서 부르는 내부 호출용이라 토스 같은 외부 서비스가 보내는 순수한 HTTP 요청을 직접 받지 못합니다.

그래서 웹훅은 반드시 라우트 핸들러로 만들어야 합니다. 외부에서 오는 요청 본문을 그대로 받아 처리하는 엔드포인트를 열고 토스가 보낸 요청이 정말 토스에서 온 것인지 검증한 뒤 결제 상태를 반영합니다. 서버 액션과 라우트 핸들러의 쓰임을 구분하지 못하면 여기서 시간을 많이 잃기 쉬운데 원칙은 간단합니다. 우리가 부르는 것은 서버 액션으로, 남이 우리를 부르는 것은 라우트 핸들러로 받습니다.

정리하며

서버 SDK가 없다는 사실은 처음엔 벽처럼 보이지만 실제로는 REST 호출 몇 개와 인증 헤더 하나로 정리됩니다. 오히려 SDK가 감춰 줄 세부를 직접 다루게 되면서 빌링키 발급과 청구, 실패 처리, 웹훅 수신의 흐름을 더 또렷하게 이해하게 됐습니다. 핵심은 시크릿 키를 서버에 가두고 결제와 구독 저장을 트랜잭션으로 묶고 웹훅은 라우트 핸들러로 받는 것이었습니다.

이 청구가 매달 무인으로 도는 방식은 스케줄러 없는 결제사로 정기결제 만들기에서, 콘솔의 전체 구조는 SaaS 운영 콘솔 구축기에서 이어집니다.

자주 묻는 질문

토스페이먼츠는 서버용 SDK가 없나요?

프런트엔드 결제 위젯 SDK는 있지만 서버에서 청구를 올리는 공식 Node 서버 SDK는 없습니다. 토스 개발자 문서는 서버에서 REST API를 직접 호출하라고 권합니다. 시크릿 키로 기본 인증 헤더를 만들어 빌링키 발급과 청구 엔드포인트를 호출하면 됩니다.

빌링키가 뭔가요?

특정 고객의 카드로 앞으로 반복해서 청구할 수 있는 열쇠입니다. 고객이 프런트엔드에서 카드 인증을 마치면 서버가 그 인증 키로 빌링키를 발급받아 구독에 저장하고 매달 이 빌링키로 청구를 올립니다. 카드 정보 자체는 우리가 보관하지 않고 빌링키만 다룹니다.

웹훅을 서버 액션으로 받으면 안 되나요?

안 됩니다. 서버 액션은 우리 프런트엔드가 부르는 내부 호출용이라 외부 서비스가 보내는 순수한 HTTP 요청을 직접 받지 못합니다. 토스 같은 외부에서 오는 웹훅은 라우트 핸들러로 열어 요청 본문을 받고 서명을 검증한 뒤 처리해야 합니다.

#토스페이먼츠#빌링키#서버연동#REST#결제

업무자동화 구축이 필요하신가요?

무료 상담 신청