"결제하기 눌렀는데... 왜 아무 반응이 없죠?"
앱에서 PG 연동을 하다 보면 자주 겪는 문제인데요.
"ISP 결제 버튼을 눌렀는데 아무 일도 안 일어나요." "카카오페이 결제하려는데 앱으로 이동을 안 해요."
PC 브라우저와 모바일 크롬에서는 잘 되던 결제가, 앱 내 웹뷰(WebView) 에서만 먹통이에요. 분명 코드는 똑같은데 왜 웹뷰에서만 이런 문제가 생길까요?
범인은 바로 App Scheme(앱 스킴) 처리 방식의 차이예요.
이번 아티클에서는 웹뷰에서 결제가 안 되는 이유와 해결 방법을 알아볼게요.
'App Scheme' 적용
우리가 흔히 쓰는 http://나 https://는 웹 페이지를 열라는 약속이에요.
반면 ispmobile://, kakaotalk:// 같은 주소는 "특정 앱을 실행해라"라는 약속이에요. 이걸 App Scheme 이라고 해요.
- 모바일 브라우저 (Chrome, Safari):
ispmobile://같은 주소를 만나면 알아서 "어? 이건 앱을 열어야겠네?" 하고 해당 앱을 실행시켜 줘요. - 웹뷰 (WebView): 기본적으로
http/https만 처리할 줄 알아요. 낯선 주소(ispmobile://)가 오면 "이게 뭐지? 에러!" 하고 무시해버려요.
그래서 개발자가 "이런 주소가 오면 앱을 열어줘!"라고 웹뷰에게 직접 알려줘야 해요.
Android 설정 방법
안드로이드에서는 앱 호출 권한, URL 처리, WebView 기본 설정을 함께 확인해야 해요.
1. 권한 설정 (AndroidManifest.xml)
Android 11(API 30) 이상부터는 내 앱이 다른 앱(카드사 앱 등)을 실행하려면 "나 이 앱들 부를 거야"라고 미리 선언해야 해요. <queries> 태그에 결제 과정에서 호출할 앱의 패키지를 등록해주세요. 필요한 패키지명은 하단의 주요 앱 스킴 리스트를 참고할 수 있어요.
<!-- AndroidManifest.xml -->
<manifest ...>
<queries>
<!-- 카카오톡 -->
<package android:name="com.kakao.talk" />
<!-- ISP/페이북 -->
<package android:name="kvp.jjy.MispAndroid320" />
<!-- 사용하는 결제수단에 따라 카드사/은행 앱 패키지명 추가 필요 -->
</queries>
</manifest>
2. 코드 구현 (WebViewClient)
shouldOverrideUrlLoading 메서드를 오버라이딩하여, http/https가 아닌 주소가 들어왔을 때 Intent를 통해 앱을 실행하도록 처리해요.
이미 WebViewClient를 사용 중이라면 중복으로 설정하지 말고, 기존 shouldOverrideUrlLoading 구현부에 외부 앱 호출 URL 처리 로직을 병합하세요.
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.webkit.URLUtil;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.net.URISyntaxException;
// WebViewClient에 외부 앱 호출 URL 처리 로직을 추가합니다.
webView.setWebViewClient(new WebViewClient() {
// Android N(API 24) 이상에서 호출되는 URL 처리 메서드입니다.
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
return handlePaymentUrl(view, url);
}
// Android N(API 24) 미만 단말을 지원하는 경우 함께 구현합니다.
@SuppressWarnings("deprecation")
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return handlePaymentUrl(view, url);
}
return super.shouldOverrideUrlLoading(view, url);
}
private boolean handlePaymentUrl(WebView view, String url) {
if (TextUtils.isEmpty(url)) {
return false;
}
// http, https, javascript URL은 WebView가 계속 처리하도록 false를 반환합니다.
if (URLUtil.isNetworkUrl(url) || URLUtil.isJavaScriptUrl(url)) {
return false;
}
final Uri uri;
try {
uri = Uri.parse(url);
} catch (Exception e) {
return false;
}
// intent:// 형태의 URL은 Android Intent URI 규격에 따라 파싱하여 실행합니다.
if ("intent".equals(uri.getScheme())) {
final Intent intent;
try {
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
return false;
}
try {
view.getContext().startActivity(intent);
return true;
} catch (ActivityNotFoundException e) {
// intent URL에 package 정보가 포함된 경우 앱 미설치 시 마켓으로 이동합니다.
String packageName = intent.getPackage();
if (!TextUtils.isEmpty(packageName)) {
try {
Intent marketIntent = new Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageName)
);
view.getContext().startActivity(marketIntent);
return true;
} catch (Exception marketException) {
return false;
}
}
return false;
} catch (Exception e) {
return false;
}
}
// ispmobile://, kftc-bankpay:// 등 일반 App Scheme URL은 외부 앱으로 전달합니다.
try {
Intent schemeIntent = new Intent(Intent.ACTION_VIEW, uri);
view.getContext().startActivity(schemeIntent);
return true;
} catch (Exception e) {
// 앱이 설치되어 있지 않거나 처리 가능한 Activity가 없는 경우입니다.
// 마켓 이동이 필요한 경우 하단 App Scheme 리스트의 Package Name을 참고해 처리하세요.
return false;
}
}
});
주의
3. WebView 기본 설정
결제창은 JavaScript, 로컬 저장소, 쿠키를 사용하는 경우가 많아요. 결제창을 로드하기 전에 WebView 설정을 확인해주세요.
import android.os.Build;
import android.webkit.CookieManager;
import android.webkit.WebSettings;
WebSettings settings = webView.getSettings();
// 결제창 스크립트 실행을 위해 JavaScript 사용을 허용합니다.
settings.setJavaScriptEnabled(true);
// 결제창에서 Local Storage를 사용할 수 있도록 DOM Storage를 허용합니다.
settings.setDomStorageEnabled(true);
// 기본 캐시 정책을 사용합니다.
settings.setCacheMode(WebSettings.LOAD_DEFAULT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 결제/인증 과정에서 http 리소스가 포함될 수 있는 경우를 대비합니다.
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
// 결제 인증 흐름에서 필요한 쿠키를 허용합니다.
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setAcceptThirdPartyCookies(webView, true);
}
결제창에서 alert 또는 confirm을 사용하는 경우, 앱에서 팝업을 표시할 수 있도록 WebChromeClient도 함께 설정해요. 이미 WebChromeClient를 사용 중이라면 기존 구현에 아래 로직을 병합하세요.
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
webView.setWebChromeClient(new WebChromeClient() {
// JavaScript alert 호출 시 앱 네이티브 다이얼로그로 표시합니다.
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setCancelable(false)
.create()
.show();
return true;
}
// JavaScript confirm 호출 시 확인/취소 선택 결과를 결제창에 전달합니다.
@Override
public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
})
.create()
.show();
return true;
}
});
iOS 설정 방법
iOS도 비슷해요. 허용 목록을 등록하고, 코드와 쿠키 정책을 함께 확인해야 해요.
1. 허용 목록 등록 (Info.plist)
LSApplicationQueriesSchemes 키에 실행을 허용할 앱 스킴들을 배열로 등록해야 해요.
앱 스킴 목록은 카드사/결제앱 제공 정책에 따라 추가되거나 변경될 수 있으므로, 하단의 주요 앱 스킴 리스트를 함께 확인해주세요.
<!-- Info.plist -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>kakaotalk</string> <!-- 카카오톡 -->
<string>ispmobile</string> <!-- ISP -->
<string>kb-acp</string> <!-- KB국민카드 -->
<!-- 기타 카드사 스킴 추가 -->
</array>
주의
2. 코드 구현 (WKNavigationDelegate)
decidePolicyFor 메서드에서 http/https가 아닌 스킴을 감지하여 처리해요.
// ViewController.swift
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
// http, https가 아닌 경우 (앱 스킴)
if url.scheme != "http" && url.scheme != "https" {
// 앱 설치 여부 확인 및 실행
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
decisionHandler(.cancel) // 웹뷰 이동 취소
return
}
}
}
decisionHandler(.allow) // 일반 웹 페이지는 이동 허용
}
3. 쿠키 허용
iOS 6 이상에서는 Safari 쿠키 설정에 따라 결제 인증 과정에서 세션 관련 오류가 발생할 수 있어요. 필요한 경우 앱 실행 시 쿠키를 항상 허용하도록 설정해요.
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
return YES;
}
주요 앱 스킴(App Scheme) 리스트
자주 사용되는 금융/결제 앱들의 스킴이에요. iOS는 Info.plist의 LSApplicationQueriesSchemes, Android는 AndroidManifest.xml의 <queries>에 등록할 때 참고하세요. Android에서 앱 미설치 시 마켓 이동 처리를 추가하는 경우 Package Name을 사용할 수 있어요.
AOS AppScheme
| App | Scheme | Package Name |
|---|---|---|
| ISP모바일 | ispmobile | kvp.jjy.MispAndroid320 |
| KBAPP카드 | kb-acp | com.kbcard.cxh.appcard |
| KB스타뱅킹 | kbbank | com.kbstar.kbbank |
| L.POINT | com.lottemembers.android | |
| L.pay | lpayapp | com.lotte.lpay |
| LG페이 | callonlinepay | com.lge.lgpay |
| LiiV(국민은행) | liivbank | com.kbstar.liivbank |
| NHAPP카드 | nhappcardansimclick | nh.smart.mobilecard |
| NH올원페이 | nhallonepayansimclick | nh.smart.nhallonepay |
| PAYCO간편결제 | payco | com.nhnent.payapp |
| SSGPAY | com.ssg.serviceapp.android.egiftcertificate | |
| V3 | ahnlabv3mobileplus | com.ahnlab.v3mobileplus |
| VG웹백신 | kr.co.shiftworks.vguardweb | |
| mVaccine | mvaccinestart | com.TouchEn.mVaccine.webs |
| 계좌이체 | kftc-bankpay | com.kftc.bankpay.android |
| 네이버페이 | com.nhn.android.search | |
| 롯데APP카드 | lotteappcard | com.lcacApp |
| 롯데모바일결제 | lottesmartpay | com.lotte.lottesmartpay |
| 리브Next | newliiv | com.kbstar.reboot |
| 삼성APP카드 | mpocket.online.ansimclick | kr.co.samsungcard.mpocket |
| 삼성페이 | samsungpay | com.samsung.android.spay |
| 삼성페이(미니) | com.samsung.android.spaylite | |
| 모니모페이 | net.ib.android.smcard | |
| 신한페이판(공동인증서) | com.shinhancard.smartshinhan | |
| 신한 SOL뱅크 | com.shinhan.sbanking | |
| 신한APP카드 | shinhan-sr-ansimclick | com.shcard.smartpay |
| 신한 슈퍼 SOL | com.shinhan.smartcaremgr | |
| 신한카드 트래블월렛 | com.mobiletoong.travelwallet | |
| 씨티공인인증서/스마트간편결제 | smartpay | kr.co.citibank.citimobile |
| 씨티모바일앱 | citimobile | kr.co.citibank.citimobile |
| 씨티앱공인인증서 | citicardapp | com.citibank.cardapp |
| 씨티앱스마트간편결제 | citispay | com.citibank.cardapp |
| 우리WON뱅킹 | wooribank | com.wooribank.smart.npib |
| 우리WON카드 | com.wooricard.smartapp | com.wooricard.smartapp |
| 우리앱카드 | wooripay | com.wooricard.wpay |
| 카카오페이 | com.kakao.talk | |
| 코나김포페이 | gov.gimpo.gpay | |
| 토스 | supertoss | viva.republica.toss |
| 티머니댐댐 | com.tmoney.nfc_pay | |
| 티머니인앱 | com.tmoney.inapp | |
| 페이핀 | paypin | com.skp.android.paypin |
| 하나(모비페이) | cloudpay | com.hanaskcard.paycla |
| 하나카드 | com.hanaskcard.rocomo.potal | |
| 하나멤버스 | kr.co.hanamembers.hmscustomer | |
| 하나멤버스월렛 | hanawalletmembers | com.hanaskcard.paycla |
| 현대APP카드 | hdcardappcardansimclick | com.hyundaicard.appcard |
| 현대카드(공동인증서) | com.lumensoft.touchenappfree | |
| 페이나우 | com.lguplus.paynow | |
| 카카오뱅크 | kakaobank | com.kakaobank.channel |
iOS AppScheme
| App | Scheme |
|---|---|
| ISP 모바일 | ispmobile |
| KB APP 카드 | kb-acp |
| LiiV(국민은행) | liivbank |
| 리브 Next | newliiv |
| KB스타뱅킹 | kbbank |
| 롯데 APP 카드 | lotteappcard |
| 롯데 스마트 페이 | lottesmartpay |
| 모니모페이 | monimopay |
| 모니모페이 인증 | monimopayauth |
| 현대 APP 카드 | hdcardappcardansimclick |
| 현대 공인인증 앱 | smhyundaiansimclick |
| 삼성APP카드 | mpocket.online.ansimclick |
| 삼성카드 안심클릭 | ansimclickscard |
| ISP 수집 | ansimclickipcollect |
| V-Guard | vguardstart |
| 삼성페이 | samsungpay |
| 삼성 공인인증 앱 | scardcertiapp |
| 신한APP카드 | shinhan-sr-ansimclick |
| 신한 공인인증 앱 | smshinhanansimclick |
| NH APP카드 | nhappcardansimclick |
| NH 올원페이 | nhallonepayansimclick |
| NH 공인인증 앱 | nonghyupcardansimclick |
| 하나(모비페이) | cloudpay |
| 씨티 APP 카드 | citispay |
| 씨티 공인인증 앱 | citicardappkr |
| 씨티공인인증서/스마트간편결제(신규) | citimobileapp |
| mVaccine | NA |
| 계좌이체 | kftc-bankpay |
| KB 계좌이체 | kb-bankpay |
| 페이핀 | paypin |
| PAYCO 간편결제 | payco, paycoapplogin (2개 모두) |
| 시럽 APP카드 | tswansimclick |
| 뱅크월렛 | bankwallet |
| 은련카드 | uppay |
| 하나카드 | hanaskcardmobileportal |
| LG페이 | Callonlinepay |
| L.pay | lpayapp |
| L.pay LMS | lmslpay |
| 우리앱카드 | wooripay |
| 하나멤버스월렛 | hanawalletmembers |
| 우리WON카드 | com.wooricard.wcard |
| 하나모아사인 | hanamopmoasign |
| 우리WON뱅킹 | newsmartpib |
| Liiv(KB국민은행) | liivbank |
| 신세계간편결제 | shinsegaeeasypayment |
| T 인증 | tauthlink |
| KT 인증 | ktauthexternalcall |
| U+ 인증 | upluscorporation |
| 카카오톡 | kakaotalk |
| 네이버페이 | naversearchthirdlogin |
| 토스 | supertoss |
| 카카오뱅크 | kakaobank |
웹뷰 결제, 세 가지만 챙기세요
웹뷰는 브라우저처럼 똑똑하지 않아요. 하나하나 알려줘야 하죠.
- Android:
AndroidManifest.xml에 패키지 등록 +shouldOverrideUrlLoading구현 - iOS:
Info.plist에 스킴 등록 +decidePolicyFor구현 - 공통: 각 카드사/은행 앱의 Scheme 과 Package Name 을 미리 파악해서 등록해두기
이 세 가지만 기억하면, 웹뷰에서도 매끄러운 결제 경험을 제공할 수 있어요.
연동 중 문의사항은 기술지원팀()으로 연락 주세요.
