목차

브라우저가 아닌 모바일 웹뷰로 카드 결제창을 띄울 때 카드사별 결제수단을 인증하려면 외부 앱(3rd-party 앱)을 연동해야 합니다. 연동에 필요한 외부 앱 스킴(App URL Scheme)목록과 추가 로직을 살펴보세요.

시작하기 전에

웹뷰는 아래와 같은 과정에서 사용됩니다.

  1. 주문 정보를 작성하고 결제창을 호출합니다.
  2. 카드사를 선택하고 다음 단계로 이동합니다.
  3. 선택한 카드사·은행의 결제 앱이 열립니다. 외부 앱이 실행되는 시점입니다.
  4. 결제 정보를 입력하고 결제를 완료합니다.
  5. 상점의 결제 페이지로 돌아옵니다.
    • 앱 스킴(appScheme) 파라미터를 추가하면 별도의 처리 없이도 외부 앱에서 상점 앱으로 돌아올 수 있습니다. 카드 결제 정보 파라미터를 참고하세요.

이 페이지에서는 3번 단계에서 외부 앱을 여는 방법을 다룹니다. 웹뷰에서 각 은행의 결제 앱을 실행시키면서 앱 간 일어나는 이동(App to App)입니다.

앱 스킴 리스트

내 상점 앱에서 인증을 위해 이동하게 되는 3rd-party 앱에는 ISP 앱, 카드사별 앱카드 등이 있습니다. 이동할 앱 스킴 리스트를 보고 추가해보세요.

카드사·본인확인기관앱 스킴
토스페이supertoss://
국민카드kb-acp://, liivbank:/, newliiv://, kbbank://
농협카드nhappcardansimclick://, nhallonepayansimclick://, nonghyupcardansimclick://
롯데카드lottesmartpay://, lotteappcard://
삼성카드mpocket.online.ansimclick://, ansimclickscard://, tswansimclick://, ansimclickipcollect://, vguardstart://, samsungpay://, scardcertiapp://
신한카드shinhan-sr-ansimclick://, smshinhanansimclick://
우리카드com.wooricard.wcard://, newsmartpib://
씨티카드citispay://, citicardappkr://, citimobileapp://
하나카드cloudpay://, hanawalletmembers://
현대카드hdcardappcardansimclick://, smhyundaiansimclick://
간편결제shinsegaeeasypayment://, payco://, lpayapp://
ISP(BC/국민)ispmobile://
모바일 PASS(본인 인증)tauthlink://, ktauthexternalcall://, upluscorporation://

Android

먼저 AndroidManifest.xml 파일에 카드앱·은행앱 패키지를 등록합니다. 패키지를 등록하지 않으면 앱이 설치되어 있어도 스토어로 이동하고, Google 정책을 위반하게 됩니다.

// AndroidManifest.xml
<queries>
<package android:name="com.kakao.talk" /> <!-- 카카오톡 -->
<package android:name="com.shcard.smartpay" /> <!-- 신한페이판 -->
<package android:name="com.shinhancard.smartshinhan" /> <!-- 신한페이판-공동인증서 -->
<package android:name="com.hyundaicard.appcard" /> <!-- 현대카드 -->
<package android:name="com.lumensoft.touchenappfree" /> <!-- 현대카드-공동인증서 -->
<package android:name="kr.co.samsungcard.mpocket" /> <!-- 삼성카드 -->
<package android:name="nh.smart.nhallonepay" /> <!-- 올원페이 -->
<package android:name="com.kbcard.cxh.appcard" /> <!-- KB Pay -->
<package android:name="com.kbstar.liivbank" /> <!-- Liiv(KB국민은행) -->
<package android:name="com.kbstar.reboot" /> <!-- Liiv Reboot(KB국민은행) -->
<package android:name="kvp.jjy.MispAndroid320" /> <!-- ISP/페이북 -->
<package android:name="com.lcacApp" /> <!-- 롯데카드 -->
<package android:name="com.hanaskcard.paycla" /> <!-- 하나카드 -->
<package android:name="kr.co.hanamembers.hmscustomer" /> <!-- 하나멤버스 -->
<package android:name="kr.co.citibank.citimobile" /> <!-- 씨티모바일 -->
<package android:name="com.wooricard.wpay" /> <!-- 우리페이 -->
<package android:name="com.wooricard.smartapp" /> <!-- 우리카드 -->
<package android:name="com.wooribank.smart.npib" /> <!-- 우리WON뱅킹 -->
<package android:name="viva.republica.toss" /> <!-- 토스뱅크 -->
<package android:name="com.nhnent.payapp" /> <!-- PAYCO -->
<package android:name="com.ssg.serviceapp.android.egiftcertificate" /> <!-- SSGPAY -->
<package android:name="com.kakao.talk" /> <!-- 카카오페이 -->
<package android:name="com.nhn.android.search" /> <!-- 네이버페이 -->
<package android:name="com.lottemembers.android" /> <!-- L.POINT -->
<package android:name="com.samsung.android.spay" /> <!-- 삼성페이 -->
<package android:name="com.samsung.android.spaylite" /> <!-- 삼성페이 -->
<package android:name="com.lge.lgpay" /> <!-- 엘지페이 -->
<package android:name="com.lguplus.paynow" /> <!-- 페이나우 -->
<package android:name="com.kftc.bankpay.android" /> <!-- 뱅크페이 -->
<package android:name="com.TouchEn.mVaccine.webs" /> <!-- TouchEn mVaccine (신한) -->
<package android:name="kr.co.shiftworks.vguardweb" /> <!-- V-Guard (삼성) -->
<package android:name="com.ahnlab.v3mobileplus" /> <!-- V3 (NH, 현대) -->
</queries>

AndroidManifest.xml 파일에 필요한 앱 스킴을 추가했다면, 앱 간(App to App) 이동에 필요한 아래 코드를 살펴보세요.

WebViewClientshouldOverrideUrlLoading 함수에 오버라이딩 로직을 추가하세요. 추가하지 않으면 웹뷰에서 외부 앱을 호출하거나 마켓(market://)으로 연결할 때 net::ERR_UNKNOWN_URL_SCHEME 에러가 발생합니다.

Kotlin
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean
url?.let {
if (!URLUtil.isNetworkUrl(url) && !URLUtil.isJavaScriptUrl(url)) {
val uri = try {
Uri.parse(url)
} catch (e: Exception) {
return false
}
return when (uri.scheme) {
"intent" -> {
startSchemeIntent(it)
}
else -> {
return try {
startActivity(Intent(Intent.ACTION_VIEW, uri))
true
} catch (e: java.lang.Exception) {
false
}
}
}
} else {
return false
}
} ?: return false
private fun startSchemeIntent(url: String): Boolean {
val schemeIntent: Intent = try {
Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
} catch (e: URISyntaxException) {
return false
}
try {
startActivity(schemeIntent)
return true
} catch (e: ActivityNotFoundException) {
val packageName = schemeIntent.getPackage()
if (!packageName.isNullOrBlank()) {
startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=$packageName")
)
)
return true
}
}
return false
}
Java
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!URLUtil.isNetworkUrl(url) && !URLUtil.isJavaScriptUrl(url)) {
final Uri uri;
try {
uri = Uri.parse(url);
} catch (Exception e) {
return false;
}
if ("intent".equals(uri.getScheme())) {
return startSchemeIntent(url);
} else {
try {
startActivity(new Intent(Intent.ACTION_VIEW, uri));
return true;
} catch (Exception e) {
return false;
}
}
}
return false;
}
private boolean startSchemeIntent(String url) {
final Intent schemeIntent;
try {
schemeIntent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
try {
startActivity(schemeIntent);
return true;
} catch (ActivityNotFoundException e) {
final String packageName = schemeIntent.getPackage();
if (!TextUtils.isEmpty(packageName)) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)));
return true;
}
}
return false;
}
Java

위와 같이 구현하기 어려우면 패키지 공개 상태 관리 대응을 확인해보세요.

iOS

먼저 Info.plist 에 LSApplicationQueriesSchemes 를 추가하고 카드사, 은행의 앱 스킴을 배열에 넣어 주세요. 설정하지 않으면, 앱이 열리지 않고 콘솔 쪽에 canOpenURL : failed for URL 에러가 발생합니다.

//Info.plist
<key>LSApplicationQueriesSchemes</key>
<array>
<string>kb-acp</string>
<string>liivbank</string>
<string>newliiv</string>
<string>kbbank</string>
<string>nhappcardansimclick</string>
<string>nhallonepayansimclick</string>
<string>nonghyupcardansimclick</string>
<string>lottesmartpay</string>
<string>lotteappcard</string>
<string>mpocket.online.ansimclick</string>
<string>ansimclickscard</string>
<string>tswansimclick</string>
<string>ansimclickipcollect</string>
<string>vguardstart</string>
<string>samsungpay</string>
<string>scardcertiapp</string>
<string>shinhan-sr-ansimclick</string>
<string>smshinhanansimclick</string>
<string>com.wooricard.wcard</string>
<string>newsmartpib</string>
<string>citispay</string>
<string>citicardappkr</string>
<string>citimobileapp</string>
<string>cloudpay</string>
<string>hanawalletmembers</string>
<string>hdcardappcardansimclick</string>
<string>smhyundaiansimclick</string>
<string>shinsegaeeasypayment</string>
<string>payco</string>
<string>lpayapp</string>
<string>ispmobile</string>
<string>tauthlink</string>
<string>ktauthexternalcall</string>
<string>upluscorporation</string>
</array>

XCode에 필요한 앱 스킴을 추가했다면, 앱 간(App to App) 이동에 필요한 아래 코드를 살펴보세요.

웹뷰에서 URL이 변경될 때 페이지 전환 대신 내 상점의 앱 스킴을 실행시키려면 WKNavigationDelegate protocol 중 아래 코드에 해당하는 메서드를 구현해야 합니다.

Swift
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
if let url = navigationAction.request.url,
url.scheme != "http" && url.scheme != "https" {
UIApplication.shared.open(url, options: [:], completionHandler:{ (success) in
if !(success){
/*앱이 설치되어 있지 않을 때*/
}
})
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
Objective-C
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL* url = navigationAction.request.URL;
if (url != nil && ![url.scheme isEqual:@"http"] &&
![url.scheme isEqual:@"https"]) {
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
decisionHandler(WKNavigationActionPolicyCancel);
return;
}
decisionHandler(WKNavigationActionPolicyAllow);
}

Flutter

최소 요구 사항

  • Flutter 3.0 이상
  • Android compileSdkVersion 33 이상
  • Android minSdkVersion 21 이상
  • iOS 11 이상

플러그인 추가하기

pubspec.yaml 파일에 url_launcher, webview_flutter 플러그인을 추가하세요.

pubspec.yaml
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.1.5
webview_flutter: ^3.0.4

연동하기

각 OS에 필요한 앱 스킴을 추가했다면, 앱 간(App to App) 이동에 필요한 아래 코드를 살펴보세요.

Android에서 카드사·은행 앱으로 이동할 때 Intent 스킴 URL을 사용합니다. Native에서 Intent 스킴 URL을 처리하세요.

샘플 소스

Dart
static const channel = const MethodChannel('com.flutter.tosspayments/convertUrl');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Tosspayments Flutter Sample")),
body: WebView(
initialUrl: "#### 웹뷰 URL을 설정하세요 #####",
onPageStarted: (url) {},
onPageFinished: (url) {},
navigationDelegate: (request) async {
Uri uri = Uri.parse(request.url);
String finalUrl = request.url;
// 웹뷰 브라우저에서 접근 가능한 주소(https 등)라면 URL로 이동
if (uri.scheme == 'http' ||
uri.scheme == 'https' ||
uri.scheme == 'about') {
return NavigationDecision.navigate;
}
// OS별로 구분하여 기타 주소 실행
if (Platform.isAndroid) {
// Android는 Native(Kotlin)로 URL을 전달해 Intent 처리 후 리턴
await _convertIntentToAppUrl(request.url).then((value) async {
finalUrl = value; // 앱이 설치되었을 경우
});
try {
await launchUrlString(finalUrl);
} catch(e){ // URL 실행 불가 시, 앱 미설치로 판단하여 마켓 URL 실행
finalUrl= await _convertIntentToMarketUrl(request.url);
launchUrlString(finalUrl);
}
} else if(Platform.isIOS){
launchUrlString(finalUrl);
}
return NavigationDecision.prevent;
},
javascriptMode: JavascriptMode.unrestricted,
),
);
}
// Intent 스킴 URL을 안드로이드 웹뷰에서 접근 가능하도록 변환
Future<String> _convertIntentToAppUrl(String text) async {
return await channel.invokeMethod('getAppUrl', <String, Object>{'url': text});
}
// Intent 스킴 URL을 Market URL로 변환
Future<String> _convertIntentToMarketUrl(String text) async {
return await channel.invokeMethod('getMarketUrl', <String, Object>{'url': text});
}

Android에서 Intent 스킴 처리하기

  1. MethodChannel을 사용해서 Intent 스킴 URL을 Flutter에서 Android Native로 보냅니다.
  2. Android의 Intent 라이브러리로 Intent 스킴 URL을 앱 스킴 URL로 변환하고 다시 Flutter로 전송합니다.
MainActivity.kt
private val CHANNEL = "com.flutter.tosspayments/convertUrl"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(flutterEngine!!)
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when {
// Intent 스킴 URL을 안드로이드 웹뷰에서 접근가능하도록 변환
call.method.equals("getAppUrl") -> {
val url: String = call.argument("url")!!
val intent = Intent.parseUri(url, URI_INTENT_SCHEME)
result.success(intent.dataString)
}
// Intent 스킴 URL을 playStore Market URL로 변환
call.method.equals("getMarketUrl") -> {
val url: String = call.argument("url")!!
val packageName = Intent.parseUri(url, URI_INTENT_SCHEME).getPackage()
val marketUrl = Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=$packageName")
)
result.success(marketUrl.dataString)
}
}
}
}
  • 더 궁금한 내용이 있나요?
  • 코드 샘플을 참고하세요
  • 기술지원이 필요한가요?
    디스코드 채팅|이메일 보내기