-
[Flutter] 토스페이먼츠 결제모듈을 도입하다 #1 설계Flutter/project 2024. 2. 21. 15:47
결제 시스템을 도입할때, 모든 PG사를 하나하나 연결하는 것은 생각만해도 머리가 아픕니다. 이럴때 결제 플랫폼을 사용하면 여러 PG사와 간편결제 등 한번의 연동으로 쉽게 도입이 가능하죠. 연동 자체는 어렵지 않을 수 있으나 주문, 재고, 포인트, 쿠폰 등을 관리하는 것은 자체 서비스와 관련 있고, 또 금전적인 이슈가 발생할 수 있는 부분이니 문제 대처방법이 중요합니다. 오늘은 대중적인 결제플랫폼의 토스페이먼츠를 활용해 주문·결제 프로세스를 구축 시, 클라이언트에서 주의해야하는 부분에대해 이야기해보겠습니다.
해당 글은 연동을 위한 사전 설정(client key 발급 등)은 따로 다루지않습니다.
✓ 주문과 결제의 분리
토스페이먼츠 개발자센터 - 결제연동하기 토스페이먼츠 개발자센터 - 결제 흐름 이해하기 토스페이먼츠는 토스페이먼츠 측의 요구사항에따라 클라이언트와 서버의 역할을 명확하게 나눠서 제시하고 있습니다.
- Client의 역할 - 인증
- 결제위젯 렌더
- 결제 요청
- 결제정보 전달
- Server의 역할 - 승인
- 결제 승인 API 요청
- 클라이언트 측으로 승인 결과 전달
토스페이먼츠에 엑세스하는 상황들을 보면 결제에 관한 프로세스만 수행하고 주문에는 관여하지않는 것을 알 수 있습니다.
실제로 결제를 통해 주문이 발생하게 되는 것이지, 결제행위에 주문이 포함되어있거나 주문행위에 결제가 반드시 포함되어있지는 않습니다.
- 결제와 주문을 분리하는 이유
- 주문은 결제와 달리 재고, 포인트, 쿠폰 등 도메인 서버에서 관리하는 데이터 상태를 변화시킨다.
- 결제 도메인은 외부서버(토스페이먼츠 측)에 의존하고있으므로 주문과 결제 도메인을 서로 분리하는 것이 문제가 발생했을 때, 빠르고 유연한 대처 및 유지보수에 유리하다.
하지만 결제와 주문은 순차적으로 일어나야하기때문에 이들의 책임을 구분함과 동시에, 적절히 배치해야 할 필요성이 있습니다.
이에 프로젝트를 함께 했던 서버 개발자분께서 다음과 같은 주문·결제 프로세스를 제시했습니다.
녹색 선:인증요청, 파란 선: 승인요청 ✓ 상태관리 설계 및 UI 개선
위와같이 수많은 네트워크 통신이 오고가는 상황에서 클라이언트가 고려해야하는 것은 정해진 순서대로 API를 호출하는 것과 동시에, 현재 결제 및 주문 단계에따라 상태를 적절히 부여하고 상황에 맞게 UI를 유저에게 노출시켜야하는 것입니다.
출처: 미리 🤷🏻♂️ "결제와 주문을 분리했는데, 왜 주문 완료와 주문 실패 화면은 없나요?"
개발자 입장에서는 상태에따라 반영되는 UI도 각각 분리되어있는 것이 보기에도 좋고 유지보수측면에서도 좋을 수 있습니다.
하지만 글의 서두에서 주문과 결제의 분리에 대해 다뤘다는 것은, 우리가 평소에는 결제와 주문이 동시에 일어나는 것으로 인식하고있었다라는 것의 반증이기도 합니다.
(당연하게 주문과 결제를 구분하고 있었다면.. 죄송합니다🙇🏻♂️)유저 입장에서는 결제와 주문은 별개가 아니고, 주문을 하는 과정에서 결제가 실패했다고 생각하는 것이 보편적입니다.
그렇기때문에 결제가 실패되면 당연히 주문이 실패되었다고 생각할테고 주문을 실패하면 마찬가지로 결제도 실패할 것이라고 예상하고 있습니다. 따라서 주문 완료·실패 UI든 결제 완료·실패 UI든 하나만 보여주는 것이 유저에게 혼동을 야기하지않는 방법이라고 할 수 있습니다.
📌 상태 분리
클라이언트 입장에서 일어나는 네트워크 통신에 대한 상태를 정리하면 다음과 같습니다.
✍🏼 OrderState code
@freezed sealed class Order with _$Order { const factory Order.loading() = OrderLoading; const factory Order.userInfoData([OrderUserInfoModel? model]) = OrderUserInfoData; const factory Order.sheet([OrderModel? model]) = OrderSheet; const factory Order.id([OrderIdModel? model]) = OrderId; // 주문성공 const factory Order.success() = OrderSuccess; // 주문 실패 const factory Order.error([ErrorMapper? errorMapper]) = OrderError; const factory Order.cancelSuccess() = OrderCancelSuccess; }
✍🏼 PurchaseState code
@freezed sealed class Purchase with _$Purchase { const factory Purchase.loading() = PurchaseLoading; // 클라이언트 토스페이먼츠 엑세스 관련 - 인증성공 const factory Purchase.tossClientSuccess() = PurchaseTossClientSuccess; // 클라이언트 토스페이먼츠 엑세스 관련 - 인증실패 const factory Purchase.tossClientError([ErrorMapper? errorMapper]) = PurchaseTossClientError; // 서버 토스페이먼츠 엑세스 관련 - 승인성공 const factory Purchase.tossAppServerSuccess([ServerSuccessPayModel? model]) = PurchaseTossAppServerSuccess; // 서버 토스페이먼츠 엑세스 관련 - 승인성공 const factory Purchase.tossAppServerError([ErrorMapper? errorMapper]) = PurchaseTossAppServerError; // 주문요청 - 성공 const factory Purchase.orderSuccess([ServerSuccessPayModel? model]) = PurchaseOrderSuccess; // 주문실패 - 실패 const factory Purchase.orderError([ErrorMapper? errorMapper]) = PurchaseOrderError; }
- 주문 · 결제 프로세스 종료시점
- 주문요청 - 성공
- const factory Purchase.orderSuccess([ServerSuccessPayModel? model]) =
PurchaseOrderSuccess;
- const factory Purchase.orderSuccess([ServerSuccessPayModel? model]) =
- 주문실패 - 실패
- const factory Purchase.orderError([ErrorMapper? errorMapper]) =
PurchaseOrderError;
- const factory Purchase.orderError([ErrorMapper? errorMapper]) =
- 주문·결제의 최종단계인 "주문요청"이 성공적으로 끝나면 모든 프로세스가 완료된다.
- 주문요청 - 성공
주문과 결제는 별개의 관심사이기때문에 토스페이먼츠를 통해 결제가 이루어지는 것은 Purchase State로 분류해두었고,
결제가 성공적으로 이루어진 뒤 주문 요청을 할때 사용되는 네트워크 통신 상태는 Order state에 포함시켰습니다.
주문 · 결제프로세스는 결제로부터 시작되기때문에 controller는 Purchase State를 바라보고있습니다. 그렇기때문에 주문 · 결제프로세스의 마지막 단계인 주문요청을 통해 주문 상태(Order)에 대한 결과를 반환받는다고해서 Order State로 상태를 변경할 수는 없습니다.
이러한 이유로 주문 API를 통해 받은 Order State의 상태에따라 PurchaseOrderSuccess(or Error) 상태로 변경하는 로직을 추가해 두었습니다. 이부분은 다음 글에서 다시 다루도록 하겠습니다.
📌 Loading UI 추가
클라이언트는 인증요청이 성공적으로 마무리되어 토스페이먼츠 결제 위젯에서 벗어나면 곧바로 서버에게 "결제요청"을 하게됩니다.
그 후, "6.결제 요청"부터 "12. 주문결과 반환"까지 많은 네트워크통신으로인해 오랜 시간이 소요됩니다.
이러한 과정에서 사용자에게 결제와 주문이 원활히 진행되고 있음을 시각적으로 전달하기 위해 디자이너분과 회의를 통해 기존에 없던 Paying Screen을 추가했습니다.
결제 controller는 Purchase State를 관리하고있습니다. 하지만 주문요청 및 결과반환은 Order State의 영역입니다.
다음은 실제로 토스페이먼츠를 프로젝트에 적용한 모습을 코드와 함께 알아보도록 하겠습니다.
긴글 읽어주셔서 감사합니다.
[Flutter] 토스페이먼츠를 활용해 주문·결제 프로세스 적용해보기 feat. 인증 프로세스 #2
토스페이먼츠의 장점은 직접 UI를 제작하고 API를 통해 연동하는 방법뿐만아니라 Widget과 결제에 필요한 메서드를 제공해 비교적 수월하게 연동이 가능합니다. 물론 직접 UI를 제작하면 UI/UX 디자
nomal-dev.tistory.com
이 글이 꼭 정답은 아닙니다. 잘못된 부분이나 부족한 부분을 알려주시면 학습 후 수정하겠습니다.
'Flutter > project' 카테고리의 다른 글
[Flutter] 토스페이먼츠를 활용해 주문·결제 프로세스 적용해보기 feat. 승인 프로세스 + 주문 #3 (0) 2024.02.22 [Flutter] 토스페이먼츠를 활용해 주문·결제 프로세스 적용해보기 feat. 인증 프로세스 #2 (0) 2024.02.21 [Flutter] 클라이언트의 이미지 처리 전략 두가지 feat. AWS amplify, re-sizing (4) 2024.02.20 [Flutter] API 요청 횟수를 줄여 네트워크 대역폭을 절약해보자! feat. 배민 북마크 버그 (0) 2024.02.14 [Flutter] 무한스크롤 성능 최적화를 해보자 feat. Lazy Loading, Throttle #3 (0) 2024.02.13 - Client의 역할 - 인증