예약시스템에서의 토스연동 문의
현재 개인 숙박예약 사이트를 구현하고있습니다.
플랫폼은 아니고 개인 민박 운영사이트입니다.
[질문 1]
예약 절차는 아래와같습니다.
1) 유저가 날짜를 선택하고 예약 버튼을 누르면 토스위젯이 열립니다.
2) 이때, 토스위젯이 열리기 전, 중복예약방지를 위해서 해당 날짜에 대해 가예약 선점을 합니다
3) 위젯이 열리고나서 유저가 다양한수단으로 결제를 하겠죠
4) 이때 onSuccess(result)의 결과에 따라 서버로 최종 confirm승인을 요청하게되는데,
- 아래 두가지 상황이 있습니다.
A) 유저가 최종결제까지 사이트를 나가지 않는다. -> onconfirm의 결과를 유저가 받는다.
B) 유저가 카카오페이같은 휴대폰 결제링크를 보내고 사이트를 나간다. 그리고 그 링크에서 결제완료한다.
A같은경우는 유저가 confirm의 결과를 바로 받으나, B같은경우는 유저가 사이트를 나가고 외부링크에서 결제한거기 때문에 서버에서 직접 toss로 confirm요청이 안되는것같은데 이때 필요한게 웹훅인가요? [질문 2]) 서버에서 직접 onconfirm요청을 할때와 유저가 외부링크결제를 통해 서버가 토스로부터 받은 웹훅과 충돌이 나나요??? [질문 3] 내부적으로 가예약을 하게되면 결제완료까지 expire 유효시간이 있습니다. 그게 예를들어 10분이라고 치면. 예를들어 유저가 9분 30초까지 위젯만 띄워놓고 9분 30초부터 결제를 하게되면 결제완료가 10분이 넘어서 될수가 있겠죠? 그럼 서버에서는 cronjob이 이건 10분이 넘었으니 expired야 하고 날짜 블록처리를 해제 합니다.
그럼 cronjob에의해 가예약은 파긱되고, 실제 결제는 10분넘어서 완료된 모순된상황이 발생할 수 있습니다. 어떻게 대처해야합니까??
A같은경우는 유저가 confirm의 결과를 바로 받으나, B같은경우는 유저가 사이트를 나가고 외부링크에서 결제한거기 때문에 서버에서 직접 toss로 confirm요청이 안되는것같은데 이때 필요한게 웹훅인가요? [질문 2]) 서버에서 직접 onconfirm요청을 할때와 유저가 외부링크결제를 통해 서버가 토스로부터 받은 웹훅과 충돌이 나나요??? [질문 3] 내부적으로 가예약을 하게되면 결제완료까지 expire 유효시간이 있습니다. 그게 예를들어 10분이라고 치면. 예를들어 유저가 9분 30초까지 위젯만 띄워놓고 9분 30초부터 결제를 하게되면 결제완료가 10분이 넘어서 될수가 있겠죠? 그럼 서버에서는 cronjob이 이건 10분이 넘었으니 expired야 하고 날짜 블록처리를 해제 합니다.
그럼 cronjob에의해 가예약은 파긱되고, 실제 결제는 10분넘어서 완료된 모순된상황이 발생할 수 있습니다. 어떻게 대처해야합니까??
41 Replies
⏳ 잠시만 기다려주세요! 곧 답변드리겠습니다
오류 문의일 경우 아래 정보를 미리 전달해주시면, 빠른 답변에 도움이 됩니다.
- 주문번호(orderId) :
- 문의 내용 :
(img를 함께 첨부해주시면 도움이됩니다)
* 계약관련 내용은 1544-7772로 문의주세요.
* 주말/공휴일에는 답변이 늦을 수 있어요.
질문 1.
A) PC에서는 유저가 최종결제까지 사이트를 나가지 않습니다. successUrl로 결과를 받고, 승인 API 호출이 되고, 돈이 빠져나갑니다.
B) 모바일에서는 카카오페이가 아니더라도 거의 모든 결제수단에서 카드사 앱이나 간편결제 앱이 열립니다. 카드사 앱이나 간편결제 앱으로 넘어간다고 사이트에서 나가지는게 아닙니다!
카드사앱이나 간편결제 앱에서 결제 후 다시 귀사 사이트의 successUrl로 돌아가 귀사에서 결과를 받고, 승인 API 호출까지 되어야 고객 카드/계좌에서 돈이 빠져나갑니다.
즉 원 문의글의 B 부분이 성립하지 않습니다. 만약 유저가 다시 귀사 사이트로 돌아가지 않는다면 고객 카드/계좌에서 돈이 빠져나가지 않고 이에 웹훅도 나가지 않습니다!
질문 2.
질문 1에 따라서 질문 1-B 상황에서 웹훅이 나가지 않으므로 질문이 성립하지 않습니다.
질문 3.
successUrl에서 시간을 체크하시고, 시간이 넘친 상태면 승인 API 호출을 하지 않고 유저에게 결제 제한시간이 만료되었다는 안내를 띄우시면 됩니다. (API 호출 시 즉시 고객 카드/계좌에서 돈이 빠져나갑니다!)
+
웹훅은 아래의 경우를 위해 존재합니다.
1. 승인 API 호출을 했는데 귀사 서버가 응답을 망실한 경우 (혹은 토스 서버의 지연으로 고객 카드/계좌에서 돈이 빠져나갔지만 응답을 해주지 못한 경우)
2. 가상계좌의 결제 응답을 위해 (승인 API 호출한다고 돈이 나가지지 않으므로... 승인 API에서 발급된 가상계좌로 입금이 되었을 때의 응답을 위해)
아 질문1 - B에서 궁금한게있습니다.
제가 결제위젯 연동 키를 발급 신청한게 아니라 api문서에 잇는 테스트 키로 하고있는중이거든요.
카카오 QR결제나 휴대폰링크를 선택할때, 결제창까지 떴다고 했을때.
이때 유저가 pc 사이트의 결제창을 닫아버립니다. 그리고 토스위젯으로부터 catch쪽에 '사용자가 결제를 취소했습ㄴ디ㅏ.'
라고 뜨는데, 휴대폰링크를 통한 결제가 문제없이 진행되거든요. 이게 테스트 환경이라 그런가요? 저는 결제취소를했기때문에 링크를 받아도 안될줄알았는데요.
네, 해당 단계는 인증 단계입니다.
PC에서 결제가 종료되어도, 카카오톡 등으로 알림이 나간 링크는 작동할 수 있습니다.
하지만 인증 단계이고 PC에서 창이 닫혔기 때문에, 다음 단계로 넘어가지지 않아 고객에게서 돈이 빠져나가지 않습니다. (결제 종료)
아. 그러면 실제 환경에서는 돈이 나가지 않는거네요?
네네, PC에서 창이 닫혔다면 그 상태로 끝입니다. 링크가 눌러지고 비밀번호를 입력했더라도 그 상태로 끝입니다. (돈이 안빠져 나갑니다.)
새로 결제창을 열고, 새로 링크를 받고, PC의 결제창이 켜져있는 상태여야 결제가 마저 진행이 됩니다.
네 그러면 유저가 qr이나 휴대폰링크를 받아서 결제를 진행하더라도 이미 사이트에서 결제취소된 링크나 QR이라면 결제가 되지않는다. 이렇게 이해하면
되겠죠.?
네네 맞습니다.
그러면. 계좌이체를 제외한 모든결제수단이 현사이트를 나가거나 취소하면 안되는 건가요?
정확히는 가상계좌 (a.k.a 무통장입금)를 제외한 모든 결제수단이 그렇습니다.
퀵계좌이체 (a.k.a 실시간 계좌이체)는 창을 닫으면 똑같이 결제가 안됩니다.
아 그렇군요. 그러면 제가 아까말씀드린 QR 이나 링크에서요
유저가 링크나 QR을 보내놓고 결제취소 (X)버튼을 누르지않고 그냥 브라우저를 꺼버리면 어떻게되나요?
똑같이 아무일도 일어나지 않습니다.
감사합니다.
질문 3번
successUrl에서 시간을 체크하시고, 시간이 넘친 상태면 승인 API 호출을 하지 않고 유저에게 결제 제한시간이 만료되었다는 안내를 띄우시면 됩니다. (API 호출 시 즉시 고객 카드/계좌에서 돈이 빠져나갑니다!)
이렇게답변주셨는데.
현재 SPA 구조라 사이트이동이 없거든요.
(싱글페이지 스크롤기반 이라서)
그래서 successURL failURL은 없는상황인데요
onSuccess에서 최종시간을 확인한다음 confirm API를 호출하면될까요?
근데 그렇게 하시면 모바일에서 결제가 안되세요
successUrl, failUrl을 만드셔야 해요
그리고 confirm API를 브라우저에서 호출하시면 안되고
필히 서버에서 호출하셔야 해요
(confirm API에 들어가는 시크릿키가 있으면 마음껏 결제 를 발생시키고, 취소시키고 모든걸 다 할 수 있어서 위험합니다.)
confirm API는 nextjs 서버액션에서 진행하고 => 최종 toss API confirm요청은 nestjs 에서 진행하는방식입닌다
엔드포인트를 안보이게 막아놔서요
그러면 successUrl, failUrl을 하나 만들고, 그 페이지에서 서버엑션을 호출하시는건 어떠세요?
그리고 그 서버엑션이나 Nestjs에서 시간을 체크하시면 될거같은데요
시간을 브라우저에서 검증하면 쉽게 우회가 되실 것 같습니다.
네 그렇게 만들어볼게요
모바일에서 무조건있어야한다고하셨으니..
그러면 successUrl은 언제 작동하나요? 이게 유저가 결제완료한 순간에 successURl로 이동할까요?
네 인증 성공시 (지금 onSuccess받는 타이밍)에서 됩니다.
참고로 PC에서는...
successUrl이 있다 -> 인증 성공 시 onSuccess 대신 successUrl로 넘어감
failUrl이 있다 -> 결제 취소시 그래도 catch로 내려감
모바일에서는...
successUrl이 있다 -> onSuccess 무시하고 successUrl로 넘어감
failUrl이 있다 ->결제 취소시 catch 무시하고 failUrl로 넘어감
뭔가 유저입장에서는 단순히 결제수단을 바꾸려고 취소한건데? failurl로 무조건 넘어가는건 어쩔수 없다 이렇게 이해하면되나요?
네 failUrl로 넘어가요
failUrl에서 다시 원래 결제화면으로 redirect시켜주시면 됩니다
successUrl로 이동할때,
nextjs 서버코드쪽에서 toss confirm결과를 받은다음 그결과에 따라
결제가 성공했습니다.
혹은 어떤 이유로 결제가 안됐다면 fail페이지로 리다이렉트 하면되나요?
페이지 자체를 먼저 렌더링하지않고 서버의 결과를 받고 렌더링한다는 의미입니다
fail로 보내든 그냥 alert 띄우시든 해당 부분 구현은 자체적으로 해주시면 됩니다.
어떤 이유로 결제가 안되었는데 승인 API 호출이 정상으로 된 경우라면
취소 API 호출만 잘 해주시면 됩니다
답변감사합니다
혹시
successURL에서
메인페이지로 바로 리다이렉트 시켜서 결제가 완료되었습니다 alert를 띄워도 상관없나요?
그리고 기존 스크롤위치같은건 복구못하겠죠?
successURL에서 confirm 호출 후 리다이렉트 하셔도 무관합니다.
successURL -> confirm 호출 부분부터는 100% 가맹점 구현 부분이라서 가이드가 어렵습니다.
스크롤 위치 등은 가맹점 구현에 따라 다르고 PG에서 가이드가 어렵습니다.
안녕하세요 지금 Url로 바꾸고 테스트해보니까
결제성공시 successUrl로 넘어가지만
결제방법 변경시 (예를들어서 카카오결제를 띄웠다가 창닫고 신용카드로 선택하고 결제하기 버튼을 누른경우)
이런경우는 failUrl로 안가지는데 맞나요?
모바일에서는 failUrl로 가질거에요
PC에서는 catch로 내려갈겁니다
❤️ 기술문의 경험이 어떠셨나요?!
간단히 코멘트 남겨주세요! 제품 발전에 큰 힘이 됩니다.
안녕하세요.
지금 구현해놓은 방식은
1) 유저가 예약버튼을 누르면 위젯이 열리기전 db에 가예약 레코드가 생성됩니다.
가예약은 10분정도의 유효시간을 가지고있습니다.
2) 유저가 결제를 실패하면 failurl로 이동시 가예약을 삭제합니다.
3) 유저가 결제를 하고 토스응답이 DONE이면 내가예약 레코드를 예약확정 처리합니다. 4) 유저가 결제를 하고 토스응답이 DONE이 아니면서 ALREADY_PROCESSED_PAYMENT 인경우 가예약을 삭제하지 않고, ALREADY_PROCESSED_PAYMENT 이외의 메세지의 경우 가예약을 삭제합니다. 5) 유저가 사이트를 나가더라도 CRON에 의해 유효시간(10분)이 지났는데 토스결제가 안된경우 가예약을 삭제합니다. 근데 이 모든건 toss에서 confirm에 대한 응답을 100% 받는다는 가정하에 인데, 좀 우려되는건 실제 DONE인데 응답오류로인해서 DONE을 못받는다면 내부 가예약을 삭제해버리고있는데 이부분이 좀 걸립니다. 단순상품구매가 아니라 예약이라 이걸 삭제하지않으면 다른사람이 그날짜를 예약을 못하게되구요.. 가예약을 삭제처리를 하지않고 그날짜를 풀어놓는다고 하더라도 A라는 사람이 결제가 실제로는 DONE인데 응답오류나 기타문제로 내부 시스템에는 DONE이 아닌경우 B라는 사람이 그날짜를 예약해버리는경우 같은날짜에 중복결제가 되버립니다. 현재 시스템에 문제는없을가요? 쓸데없이 응답을 못받는경우까지 생각하는게 아닌지 좀 제가 궁금해서요.
3) 유저가 결제를 하고 토스응답이 DONE이면 내가예약 레코드를 예약확정 처리합니다. 4) 유저가 결제를 하고 토스응답이 DONE이 아니면서 ALREADY_PROCESSED_PAYMENT 인경우 가예약을 삭제하지 않고, ALREADY_PROCESSED_PAYMENT 이외의 메세지의 경우 가예약을 삭제합니다. 5) 유저가 사이트를 나가더라도 CRON에 의해 유효시간(10분)이 지났는데 토스결제가 안된경우 가예약을 삭제합니다. 근데 이 모든건 toss에서 confirm에 대한 응답을 100% 받는다는 가정하에 인데, 좀 우려되는건 실제 DONE인데 응답오류로인해서 DONE을 못받는다면 내부 가예약을 삭제해버리고있는데 이부분이 좀 걸립니다. 단순상품구매가 아니라 예약이라 이걸 삭제하지않으면 다른사람이 그날짜를 예약을 못하게되구요.. 가예약을 삭제처리를 하지않고 그날짜를 풀어놓는다고 하더라도 A라는 사람이 결제가 실제로는 DONE인데 응답오류나 기타문제로 내부 시스템에는 DONE이 아닌경우 B라는 사람이 그날짜를 예약해버리는경우 같은날짜에 중복결제가 되버립니다. 현재 시스템에 문제는없을가요? 쓸데없이 응답을 못받는경우까지 생각하는게 아닌지 좀 제가 궁금해서요.
그러면
5) 유저가 사이트를 나가더라도 CRON에 의해 유효시간(10분)이 지났는데 토스결제가 안된경우 가예약을 삭제합니다.이때 저희 결제 조회 API 를 확인해서 결제가 되었는지를 확인하신후 삭제 여부를 판단해 주시면 될것 같습니다. 명시적으로 실패를 받지 않은 경우도 동일하게 10분후에 조회하신후에 날짜를 풀어 두시는 방법을 쓰시는건 어떨까요?
Cron에서 결제 조회 API 를 확인해서 결제가 되었는지를 확인하신후 삭제 여부를 판단해 주시면 될것 같습니다.
-> 이걸적용한다면
2) 유저가 결제를 실패하면 failurl로 이동시 가예약을 삭제합니다. (이것은 그대로 진행해도 문제없을지?)
4) 유저가 결제를 하고 토스응답이 DONE이 아닌경우를 빈상태로놔두고 크론에 맡길까요?
=> 이것의 단점은 가예약을 크론이 처리해줄때까지 예약이 블로킹된다는점입니다.
cron 은 OS 의 cron 을 의미하는걸까요?
그렇다면 이것은 특정 시간 주기적으로 처리하실게 아니고
고객의 결제 상태에따라 (위에선 명시적이라고 표현되어있네요) 결제조회를 활용하시면 됩니다.
우선 결제조회를 일으킬 시간을 상대적으로 잡으셔야하구요
1. 고객이 정상적으로 결제를 했다 --> 가예약을 진성예약으로 업데이트
2. 고객이 결제를 실패했다 --> 가예약의 status 를 결제실패로 인한 예약취소로 업데이트
3. 1,2,에 걸리지 않고 고객이 결제를 성공한지 실패한지 모르겠다 --> 결제가 시작된 시간을 기점으로 특정시간(10분)으로 결제조회
3. 에서 얻게되는 상태가 이런값들인데요

READY : 결제가 정상처리될 가능성이 있음
IN_PROGRESS : 승인전단계 -- 만나기 힘들겁니다
DONE : 결제가 완료된 상태 -- 진성계약으로 업데이트 해야하는 값
CANCELED : 가계약을 실패처리해야하는 값
ABORTED : 가계약을 실패처리해야하는 값
EXPIRED : 가계약을 실패처리해야하는 값
설명드린 내용은 카드, 간편결제 기준입니다.
READY받았다면 시간텀을 두고 다시 조회를 해야합니다. (고객이 진행중이므로)
IN_PROGRESS 를 받았다면 승인처리 이후에 승인결과를 확인하시면 됩니다
* READY, IN_PROGRESS 모두 시간 텀을 두고 조회를 해야하는건 똑같아요
(어쨋든 둘다 진행 중인거니까요.)
감사합니다. ㅎㅎ
의견주신 내용은 PG에서 승인응답이 늦게오거나 장애케이스인데 이 경우는 텀을 짧게 조회를 하는게 맞겠네요(10초 정도)
보통의 IN_PROGRESS 는 고객이 더 이상 해야하는 액션이 없어서 승인전 조회를했을때 나타나게됩니다. 가맹점에서 승인요청을 보냈다면 조회보다는 승인응답을 받아서 처리하는게 더 잘 맞구요
@Ayaan이안 님 의견대로 해주시면 PG의 예외도 처리할 수 있어서 더 좋은 서비스를 만들 수 있을거같습니다
1. Cron은 nestjs 의 스케줄러 Cron을 쓰고있습니다.
-> 유저가 예약을 진행중인데, 그냥 사이트를 나가거나 하면 주기적으로 취소처리를 하려고 쓰는 중입니다만
현재 아래 A와 B같은 상황은 전혀 문제가없고
A) 고객이 정상적으로 결제를 했다 --> 가예약을 진성예약으로 업데이트
B) 고객이 결제를 실패했다 --> 가예약의 status 를 결제실패로 인한 예약취소로 업데이트
Cron 수행 순서
1) 가예약의 유효시간 (expire_at) 이 now()보다 작은 리스트를 가져옵니다
2) list를 반복문으로 toss order_id를 추출하여 TOSS의 결제조회 API를 조회합니다.
3) 결제조회 api의 결과가 status === 'DONE' 이 아닌경우 예약을 취소처리하고
DONE인경우 expire_at 이 now보다 크더라도 예약을 다시 확정처리합니다.
위에 김차장님이 말씀주신거는 READY : 결제가 정상처리될 가능성이 있음 IN_PROGRESS : 승인전단계 -- 만나기 힘들겁니다 DONE : 결제가 완료된 상태 -- 진성계약으로 업데이트 해야하는 값 CANCELED : 가계약을 실패처리해야하는 값 ABORTED : 가계약을 실패처리해야하는 값 EXPIRED : 가계약을 실패처리해야하는 값 DONE만 확인하지말고 READY , IN_PROGRESS 인경우에 다음 cron상태에서 다시확인하거나? 혹은 10초뒤에 텀을 두고 다시 확인해보는 코드를 넣는게 좋다 이말씀이신가요? 최종으로 위처럼하면 큰문제는 없을지요.? 그리고 아직 사업자등록이 안나와서 상점 결제위젯 api키를 받지못했고 sk 테스트키로 하는중인데 그래서 결제승인은 테스트가 되는데 orderid로 결제조회를 하면 응답이 결제가 내역확인이 안되는데요 결제조회는 전자결제신청하고 실제 발급받은 테스트키로 조회를 해야 나오나요?
위에 김차장님이 말씀주신거는 READY : 결제가 정상처리될 가능성이 있음 IN_PROGRESS : 승인전단계 -- 만나기 힘들겁니다 DONE : 결제가 완료된 상태 -- 진성계약으로 업데이트 해야하는 값 CANCELED : 가계약을 실패처리해야하는 값 ABORTED : 가계약을 실패처리해야하는 값 EXPIRED : 가계약을 실패처리해야하는 값 DONE만 확인하지말고 READY , IN_PROGRESS 인경우에 다음 cron상태에서 다시확인하거나? 혹은 10초뒤에 텀을 두고 다시 확인해보는 코드를 넣는게 좋다 이말씀이신가요? 최종으로 위처럼하면 큰문제는 없을지요.? 그리고 아직 사업자등록이 안나와서 상점 결제위젯 api키를 받지못했고 sk 테스트키로 하는중인데 그래서 결제승인은 테스트가 되는데 orderid로 결제조회를 하면 응답이 결제가 내역확인이 안되는데요 결제조회는 전자결제신청하고 실제 발급받은 테스트키로 조회를 해야 나오나요?
결제조회 먼저 답변드리면 orderid말고 paymentKey 쓰셔야해요
태스트 키에서는 orderid 검색이 안되구요.
라이브에서도 권장하지않아요
네 그럼 paymentkey조회로 바꾸겠습니다.
paymentkey를
https://api.tosspayments.com/v1/payments/confirm api를 호출하기전에
내부db에 먼저 paymentkey를 저장해야 응답성공실패여부를 떠나서 나중에라도 조회해서 성공/실패로직을 짤수 있을것같은데 이렇게하는게 맞겠죠? 다른시스템들은 어떤지 몰라서 좀 많이여쭤보네요
그정도 구현하시면 제가 만난 가맹점중 상위 10%는 되실거에요
보통 문제가 발생하고 고치는 경우가 많아서 사전에 깊게 설계하진 않으시더라구요
티켓팅이나 예약시스템은 고민을 좀 해야하는 분야가 맞기도 합니다
내부db에 먼저 paymentkey를 저장
네 맞습니다. 인증결과로 받으신 값들 전부 저장하셔야하고 amount, orderId 가 동일한지 여부도 꼭 넣으셔야해요 -- 위변조 예방예 현재 confirm api 호출전
amount 가 db에서 가져온 amount, orderid가 일치하는지 등등
확인작업하고있고 위변조감지되면 confirm 호출이 안되게해놨습니다
나중에라도 문제생기는게싫어서 초반에 그냥 모든경우의수를 다 생해서 짜는편이라서.. 좀 제가 질문을 많이하네용.. 감사합니다
좋은 서비스 만드시길 바랍니다.