토스페이먼츠 브랜드페이 React-Native에서 커스텀하게 사용하기
개요
저희 나비얌 서비스는 90% 이상이 react-native로 개발되어있어요. 또한 결제 모듈은 특정 부분(급식카드 결제 등)을 제외하고는 모두 토스페이먼츠에 의존하고 있어요.
기존에 토스페이먼츠에서는 javascript sdk를 이용한 방법만 제공하고 있었기에, 저희는 결제 페이지만 따로 웹뷰로 구현해서 연결하는 방식을 채택했었어요. 하지만 토스페이먼츠에서도 react-native sdk(@tosspayments/widget-sdk-react-native
)가 나오고, 나비얌에서도 결제 페이지의 리팩터링과 UX개선의 필요성이 대두되면서 결제 페이지를 react-native 기반으로 아예 새로 만들어야겠다는 결심을 하게 되었어요.
토스페이먼츠는 브랜드페이와 일반결제 두 가지 결제방식을 제공하는데, 일반결제는 카드사결제나 간편결제를 제공하고 브랜드페이는 자사 고유의 페이를 만들어서 여기에 카드나 계좌를 등록해서 바로 결제가 가능하게 제공하고 있어요.
이 중 일반 결제는 토스에서 제공하는 react-native sdk로도 충분히 동작했지만, 문제는 브랜드페이였어요. 저희는 브랜드페이의 UI를 커스텀해서 사용하고 있었고, 앞으로 변경될 결제 페이지에서도 마찬가지였어요.
문제 상황
react-native sdk의 기능으로 커스텀 UI를 만들기는 어려웠어요. 브랜드페이는 심지어 커스텀 ui를 만들 때 브랜드페이 설정 창 열기(openSetting
), 결제 수단 추가하기(addPaymentMethod
) 같은 메서드를 지원해주지 않았어요.
따라서 토스페이먼츠에서 제공하는 또 다른 방식인 브랜드페이 API를 이용해서 커스텀 브랜드페이 UI를 구축하려고 시도했어요.
여기서 또 다른 문제점이 나타났어요. 토스페이먼츠에서는 openSetting
, addPaymentMethod
와 관련된 sdk나 api를 제공하지 않기에 직접 구현해야 한다는 점이었어요. 토스페이먼츠 디스코드 기술도움 채널에 문의를 해보니 커스텀 UI는 API를 이용해서 구현하라고 되어있었지만, 정작 API에서 openSetting
이나 addPaymentMethod
를 지원해주지 않고 있었어요.
해결 과정
우선, 토스페이먼츠의 react-native sdk가 어떻게 동작하는지 라이브러리 내부 구조를 뜯어보면서 면밀히 분석했어요.
대부분의 기능이 사용자가 특정 메서드를 호출하면 해당 메서드에 맞는 html이 웹뷰에 inject 되고, 그 html 내부에서 특정 메서드를 실행시키는 구조였어요. 사용자는 토스페이먼츠의 특정 기능을 웹뷰 형식으로 보게되고, 내부적으로는 특정 HTML과 자바스크립트 로직이 동적으로 돌아가고 있었어요.
하지만 문제는 브랜드페이 관련 기능인 openSetting
과 addPaymentMethod
와 관련된 기능은 해당 라이브러리의 코드에 존재하지 않았어요. 그래서 저는 모든 기능에 로그를 찍어보면서 브랜드페이 sdk에서 openSetting
과 addPaymentMethod
를 실행하면 어떤 html이 inject가 되고 웹뷰상에서 어떻게 동작하는지 분석했어요.
결론적으로 openSetting
과 addPaymentMethod
를 sdk 웹뷰상에서 호출하면 어떤 html이 웹뷰에 inject되고 어떤 로직이 돌아가게 되는지 파악하는데 성공했어요.
모든 html 코드가 https://js.tosspayments.com/v1/brandpay
script를 import 하여 사용하고, 해당 script 내부에서 선언된 Brandpay
객체를 필요한 데이터(예: customerKey, clientKey 등)와 함께 초기화한 후에 해당 객체에서 필요한 메서드를 호출하는 방식으로 구현되어 있었어요.
예시) openSettings
메서드를 호출하기 위한 html
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script src="https://js.tosspayments.com/v1/brandpay"></script>
</head>
<body>
<script>
var brandpay = BrandPay({
...
});
let payload = "";
brandpay.openSettings().then(() => {
payload = JSON.stringify({
...
});
}).catch(error => {
payload = JSON.stringify({
...
});
}).finally(() => {
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(payload);
}
});
</script>
</body>
</html>
저는 이 정보를 토대로 커스텀 UI를 만들기 시작했어요. 디자이너가 요구한 방향으로 컴포넌트를 구성하고, 로직을 재구성하고하여 openSetting
과 addPaymentMethod
함수를 자체적으로 구현했어요.
react-native
의 Modal
안에 tosspayments-react-native-webview
의 Webview
를 넣고, 특정 메서드가 호출될 때 inject되어야 할 html 코드를 새로 업데이트하면서 구현했어요.
결과
(구현된 화면)
이 과정을 통해서 아래와 같은 성과를 얻을 수 있었어요.
- UI/UX: 기존 웹뷰 방식을 버리고 react-native 환경에서 재구축하여 더욱 자연스러운 페이지의 전환과 매끄러운 사용자 경험을 제공할 수 있었어요.
- 유연한 커스터마이징: 토스페이먼츠에서 직접 제공하지 않는 기능을 직접 분석해 UI와 로직을 원하는 방식으로 제어할 수 있게 되었어요.
- 확장 가능성: 토스페이먼츠의 내부 동작 방식을 이해할 수 있게 되었어요. 따라서 차후 다른 추가 기능이나 다른 결제 서비스 연동 시에도 유사한 접근 방식을 적용해볼 수 있는 기반이 생겼어요.
물론 앞으로 토스페이먼츠의 sdk나 api 업데이트에 따라서 로직이 변경될 수 있어요. 하지만 이번 문제 해결 과정을 통해서 토스페이먼츠의 내부 핵심 원리를 터득했다는 점과, 웹뷰에 inject되는 html 코드를 매번 업데이트 하는 방식으로 여러 웹뷰 기반 기능들을 구현할 수 있겠다는 인사이트를 얻었다는 점이 큰 수확이었어요.
또한 결제 시스템을 커스터마이징할 때는 공식 문서의 꼼꼼한 확인과 sdk의 내부 구조 분석이 필수적이라는 걸 강조하고 싶어요. 결제 시스템은 회사의 매출과 보안과도 직결되는 민감한 부분이다 보니, 미처 놓친 부분에서 큰 문제가 발생할 수도 있어요.