Architecture

잃어버린 내 빅맥 - 1편

jjunhub 2024. 12. 13. 15:45

부제 : 결제 누락 방지 구조 설계하기 with PortOne

                                                                                                                                                                                                                                                                                                                                                                                                                                                      

개요

새로운 프로젝트에서 결제 서비스를 도입하고 난 뒤에, 현재 구조가 결제 누락을 방지할 수 있을까?를 생각하다가 갑자기 문득 빅맥 결제 누락 경험이 떠올랐다. M오더 앱을 통해서 빅맥을 주문하여, 결제를 완료한 직후에 너무나 배고팠던 나머지 앱을 무의식적으로 종료하였더니 매장에 나의 빅맥 주문이 반영되지 않던 것이었다. 이 과정에 대해서 곰곰히 생각해보니 현재 구현된 결제 서비스 구조도 이러한 상황에서 동일하게 결제 후 주문 누락이 발생할 수 있음을 알게 되었다. 서버 개발자의 관점에서 이를 어떻게 해결해나가는지를 글에서 서술할 예정이다.

 

문제 상황

맥도날드에서 빅맥을 결제하였는데, 결제 직후 맥도날드 어플을 종료하였더니 돈만 빠져나가고 빅맥이 주문이 되지 않았다. 주문이 들어가지 않은 결제 건은 다음 날에 환불되었다. 이러한 일이 나에게만 일어난 것일까 궁금하여 찾아보니, 실제로 다수의 사람들이 겪은 문제였다.

 

맥도날드 M오더 오류, 키오스크 오류

M오더 오류가 너무 많이 남 포인트로 음료 마시는데 키오스크도 에러 나서 카운터에서 해결해 마시고 햄버...

blog.naver.com

 

블라블라: 맥도날드 M오더 오류 ㅋㅋㅋ

내돈만 결제나가고 주문은 안나가가고뭐 이런 ㅋㅋㅋ다시 결제하고...매니저 말로는 취소될걸라는데 믿을수가 있나그냥 5천원미만이라서 넘어간다 ㅋㅋ

www.teamblind.com

먼저 맥도날드 앱의 결제 시스템이 어떻게 구축되어 있는지를 예측해보자.

 

맥도날드 앱 결제 서비스 구조 예측해보기

우선 앞서 서술한 것처럼, 맥도날드가 PortOne 결제 서비스를 사용하는 것으로 가정하여 다이어그램을 그려보았다. 현재 맥도날드 측의 예상 결제 구조는 다음과 같다.

맥도날드 예상 결제 흐름도

  1. 사용자가 앱을 통해서 주문을 진행할 때, 주문 목록을 서버 측에 요청한다.
  2. 서버 측으로부터 주문 목록을 받아 앱 화면에 띄운다.
  3. 사용자가 원하는 메뉴를 선택하여 주문을 진행한다.
  4. 앱에서 결제사(PortOne) 측 sdk를 통해 사용자에게 결제 페이지를 제공한다.
  5. 사용자가 결제 페이지를 통해 결제를 완료한다.
  6. 결제사 측에서 앱으로 결제 완료 사실을 전달한다.
  7. 앱에서 서버에게 주문 정보와 결제 완료 정보를 함께 전달한다.
  8. 서버 측에서 결제 정보에 대한 정보를 결제사 측으로 전달하여 검증을 진행한다.
  9. 검증이 완료되면, 서버 측에서 주문에 따른 추가적인 작업(매장에 실제 주문 정보를 전달하는 등)을 수행한다.
  10. 서버 측에서 앱 측으로 주문 완료 사실을 전달한다.

위 과정만 놓고 봤을 때는, 아무런 문제가 없어보인다. 하지만 다음과 같은 상황을 가정해보자.

결제 누락 시나리오

사용자가 결제 완료 직후에 앱을 종료하였다고 생각해보자. 또는 결제 완료 직후에 와이파이가 끊어지는 네트워크 드랍이 발생한다고 가정해보자.  현재 구조에서는 사용자의 주문 정보가 서버 측으로 전달될 방법이 없다. 즉 결제 이후에 주문 누락이 발생하는 것이다.

문제 해결을 위한 시나리오 작성

이러한 구조를 해결하고자 여러 가지 해결 방안을 모색해보았다. 여러 가지 사례를 살펴보니, 결제 시스템과 관련하여 주문 정보 또는 결제사 측에서 제공하는 webhook 등의 방법을 추가한 것들을 알게 되었다. 따라서 두 가지 방법들을 적절하게 섞어서 해결해보고자 하였다.

다시 말하지만, 해결해야하는 문제는 결제 이후에 주문 누락을 방지하는 것이다. 각각의 시나리오에서 결제 이후에 주문 누락을 방지할 수 있는지를 주목해보자.

주문 O, Webhook X

첫번째로, 결제 전에 서버 측에 주문 정보를 저장한 뒤에 결제 완료 시에 이 주문에 대한 상태를 결제 완료와 같이 변경하여 처리하는 구조이다. 이렇게 한다면, 주문 정보가 서버 측에 먼저 저장되므로 주문 누락이 방지되지 않을까하는 막연한 생각이었다.

주문 O

하지만 이 구조도 마찬가지로 사용자가 결제 직후에 앱을 종료한다면, 앞서 제공한 주문 정보에 대해서 결제 완료의 상태로 변경할 수 없다. 즉 결제 이후에 주문 누락이 동일하게 발생하는 것이다. ( 주문이 결제 완료 상태로 바뀌어야만, 매장에서 실제 주문으로 등록되므로 )

주문 X, Webhook O

두번째로, 결제 이후에 결제사 측에서 제공하는 webhook 기능을 통해서 서버 측으로 결제에 대한 정보를 전달하는 것이다. 딱 여기까지만 들으면, 결제사 서버가 다운되지 않는 한 서버에 결제 정보를 전달할 수 있지 않나?라고 생각이 든다.

Webhook O

 

하지만 이 구조는 실제 구현 시에, 문제점을 알게 되었다. 결제사 측에서 제공하는 webhook으로 보내주는 정보가 대부분 결제자에 대한 정보로 이루어져 있다는 것이다. 즉 누가 결제를 했는지는 확인할 수 있지만, 이 사람이 어떠한 내용으로 결제했는 지는 webhook으로 알 수가 없다는 것이다. 결제 누락은 방지되지만.. 어떤 주문의 결제인지 알 수 없는 미묘한 구조였다. 따라서 이 구조도 폐기 되었다.

🎉 주문 O, Webhook O

마지막으로, 결제 전에 서버 측에 주문 정보를 저장한 뒤에 결제 완료 시에 결제사 측에서 제공하는 webhook을 통해 결제에 대한 정보를 주문에 반영하는 구조이다.

주문 O, Webhook O

이미 등록된 주문 정보 + 결제사의 webhook을 통해서 주문자의 상호작용과 무관하게 주문이 결제 상태로 바뀔 수 있게 되었다. 이 구조를 통해서, 사용자가 결제 직후에 앱을 종료하여도 주문이 정상적으로 반영되는 구조로 개선할 수 있었다.

 

하지만 이 구조에서 정상적인 시나리오로 진행된다면, 결제사 측 Webhook맥도날드 앱 측의 결제 완료 요청을 동시에 맥도날드 서버가 받게 될 것이다. 이렇게 되면 주문 테이블 1개에 대해서 동시에 접근하므로 추가적인 동시성 제어가 요구된다. 이 동시성 제어에 대해선 이후 포스트에서 서술할 예정이다.

결론.

왜 결제 서비스 예시 코드에서 주문이라는 객체가 존재했는지 이해할 수 있게 되었다.

또한 이제는 빅맥을 잃어버리지 않고 주문할 수 있다. 결제하고 얌전히 앱을 켜두자.