회사 앱을 하이브리드 앱으로 전환하기로 결정했습니다.
이유는 다음과 같았습니다.
하지만 잘 몰랐기 때문에 걱정되는 점이 있었습니다.
“웹뷰로 결제가 가능한가?"
이 부분에 확신이 없었기 때문에 결제는 Flutter로 구현하고, 나머지는 웹뷰로 구성하는 방식을 처음에 고려했습니다.
하지만 생각만해도 귀찮지 않나요. 결제 부분은 민감해야하는 곳인데, 그 정보들을 어떤 방식으로 Flutter로 넘길 것이며, 그 많은 결제 관련 어플에 다 대응해야하고… 웹뷰만으로 결제를 할 방법을 찾아내야했습니다. 이번 게시글에서는 이를 구현하기 위해 고군분투했던 저의 경험을 작성합니다.
“페이” 춘추전국시대
모든 금융 회사들이 각자의 페이를 만들고 경쟁하는 지금은 바야흐로 2023년. 너무 많습니다. 각 서비들은 개발자센터를 운영하며 플랫폼별 개발 가이드를 제시합니다만, 이걸 “혼자서” 직접 다 구현하는 것은 현실적으로 불가능합니다.
결제 서비스들의 개발 가이드 페이지
그래서 이 결제 서비스들을 하나로 묶어서 제공해주는 서비스가 있습니다. 바로 포트원(구 아임포트)입니다.
심지어 무료
입니다. 그리고 포트원 개발자 센터에도 마찬가지로 서비스 개발 가이드를 제공하고 있습니다. 기본적으로 JavaScript library를 CDN으로 다운받아 사용하는 형태입니다.
저는 가이드를 따라 결제창을 구현하고 웹뷰에서 확인해보았습니다.
웹뷰에서 진행해본 카카오페이 간편 결제
일반결제는 잘 되었지만 간편결제는 예상대로 안됐습니다. 지금부터가 진짜 문제라는게 확 느껴지네요.
예전에 잠시 android 개발을 해보았기에 intent
가 어떤 친구인지는 알고 있었습니다. android 문서에는 intent
를 다음과 같이 설명합니다.
intent
는 메시징 객체로, 다른 앱 구성 요소로부터 작업을 요청하는 데 사용할 수 있습니다.
URL의 scheme을 나타내는 자리에 intent
라니, 잠시 당황했지만 생각해보니 웹에서 어플을 열려면 어떤 채널로 메시지를 전달해야 할테니, 이게 그 방식이라는 것을 알아차렸습니다. 조금 더 찾아보니 deep link
의 존재를 알게 되었습니다.
마찬가지로 android 공식 문서에서 deep link는 다음과 같이 정의합니다.
딥 링크는 사용자를 앱의 특정 콘텐츠로 바로 연결하는 URL입니다.
open하면 해당 어플로 바로 연결시켜주는 URL이 바로 deep link
였습니다. 다음과 같이 사용한다고 합니다.
<intent-filter>
...
<data android:scheme="https" android:host="www.example.com" />
<data android:scheme="app" android:host="open.my.app" />
</intent-filter>
위와 같은 코드가 app manifest에 작성되어있으면 다음 URL들이 어플을 오픈합니다.
그래서 테스트로 fb://profile
, kakaopay://test
로 이동해보니, 페이스북의 프로필 페이지와, 카카오페이가 열리는 것을 확인했습니다(카카오페이는 해당 페이지가 없다고 하며 홈 화면으로 이동시킵니다.)
하지만 현재 포트원에서 연결해주는 URL 바로 열리지 않았습니다. deep link를 여는게 문제는 없는 걸 확인했으니, 모두 intent://
로 시작하는 URL을 진짜 Intent 객체로 변경해야 하겠다고 생각이 들었습니다.
Flutter에는 MethodChannel이라는 messing channel이 있습니다. Platform Specific한 기능이 필요할 때, 해당 채널로 message 통신으로 문제를 해결할 수 있도록 한것이죠.
저희는 현재 intent://
로 시작하는 URL을 intent로 변환해서 이동하는 것이 목표였습니다. 다행히도 intent를 일반적인 URL intent로 다시 parsing하는 방법이 있었습니다! 그리고 이 URL은intent://
로 시작하지 않습니다.
그래서 Flutter의 공식 문서를 참고하여서 android 부분의 코드를 코틀린으로 작성해보았습니다.
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler{call, result->
when (call.method) {
"convertUri" -> try {
val intent: Intent =
Intent.parseUri(call.argument<String>("uri"), Intent.URI_INTENT_SCHEME)
// 실제로 사용가능한 URL로 변환되는 과정
result.success(intent.getDataString()) // 반환
} catch (e: URISyntaxException) {
result.notImplemented()
} catch (e: ActivityNotFoundException) {
result.notImplemented()
}
}
}
Flutter 쪽의 코드는 다음과 같습니다.
if (Platform.isAndroid) {
otherAppUri = Uri.parse(await MethodChannel('kr.co.dapada')
.invokeMethod('convertUri', {"uri": request.url}));
}
webview_flutter
의 onNavigationRequest
부분에서 intent URL을 판단하고, 맞다면 위의 MethodChannel
을 통해 실행가능한 URL로 변경하고 해당 URL을 반환하여 이동합니다.
성공! 이제 Flutter의 Webview에서 deep link
를 모두 사용할 수 있게 처리했습니다!
크로스 플랫폼(Flutter)에서 하이브리드 앱(WebView)를 개발하는 것은 다양한 분야를 넘나드는 경험을 선사해줍니다🥲. 오랜만에 전문 분야가 아닌 곳에서(android) 꼬리에 꼬리를 무는 문제를 만났네요. 중간에 정말 포기하고 다른 방법을 찾고 싶었지만, 그곳이 더 먼 길이라는걸 분명히 느꼈기 때문에 해결할 수 있었습니다. (하지만 포스팅한 뒤에는 너무나도 당연해 보이는 문제들)
이전 포스팅에도 올렸듯이 리액트 아키텍쳐 변경 후 눈에 띄는 개발 속도 향상이 있었고, 회사 앱도 Flutter로 개발한 부분을 거의 대부분 리액트로 다시 개발했습니다. 덕분에 지금 결제와 로그인을 신경쓸 수 있는거겠죠. 감사하며 개발중인 요즘입니다. 하지만 구현 작업이 많고, 이 같이 시퀀스가 어려운 작업이 많지는 않기 때문에 포스팅이 뜸하게 되네요. 그래도 한 달에 1~2개는 꾸준히 쓰려 노력하겠습니다.