앱 결제 편의 모듈 (iOS/AOS)
이해하기
본 문서에서는 샵바이 결제편의 모듈 네이티브 앱에 대한 결제 개발 가이드입니다.
샵바이 결제편의 모듈은 iOS/AOS 어플리케이션 내에 webview를 이용하여 연동합니다.
참고로, 네이티브 앱이 아닌 경우 본 문서가 아닌 결제편의 모듈(PC, 모바일웹 기준) 가이드를 참고하시길 바랍니다.
결제 흐름도 (다이어그램)
IOS 및 AOS 모두 아래 흐름도를 참고하시길 바랍니다.

샘플파일 (참고)
아래 샵바이 결제편의 모듈 (APP연동) 샘플을 참고하실 수 있습니다.
샵바이 API와 샵바이 결제편의 모듈을 연동하기 위한 최소한의 샘플이며
개발 환경과 앱 개발 상황에 따라 구현방식이 달라질 수 있으므로, 개발 시 참고용으로 사용하시길 바랍니다.
webview에서 샵바이 결제편의 모듈을 띄우는 방식은 기본 웹 연동(PC.모바일웹) 방법과 유사합니다.
결제편의 모듈(PC, 모바일웹 기준) 가이드를 참고하실 수 있습니다.
1
2 <!DOCTYPE html>
3 <html>
4 <head>
5 <meta charset="utf-8">
6 <meta name="description" content="">
7 <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
8 <script src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script>
9 <script type="text/javascript" src="https://spay.kcp.co.kr/plugin/kcp_spay_hub.js"></script>
10 <script type="text/javascript" src="https://nsp.pay.naver.com/sdk/js/naverpay.min.js"></script>
11 <script type="text/javascript" src="https://js.tosspayments.com/v1"></script>
12 <script language="javascript" type="text/javascript" src="https://chai.finance/js/v1/payment.min.js"></script>
13 <script language="javascript" src="https://xpay.tosspayments.com/xpay/js/xpay_crossplatform.js" type="text/javascript"></script>
14 <title> ShopbyPayment</title>
15 </head>
16 <body>
17 </body>
18 </html>
19 필요에 따라 각 PG사에서 제공하는 javascript를 import 해야합니다.
1
2 <script src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script>
3 <script src="https://nsp.pay.naver.com/sdk/js/naverpay.min.js"></script>
4 <script src="https://spay.kcp.co.kr/plugin/kcp_spay_hub.js"></script>
5 <script src="https://xpay.tosspayments.com/xpay/js/xpay_crossplatform.js"></script>
6 1
2 NCPPay.setConfiguration({
3 'clientId': ncp.clientId, // shopby에서 발급받은 clientId
4 'confirmUrl': 'scheme://confirm.html', // 결제 완료후 리턴을 받을 업체의 URL
5 'platform': 'IOS', // 'IOS or AOS'
6 'accessToken': '6022983d-1342-478b-b292-880aeb17cbd9', // 로그인한 유저의 엑세스 토큰 (OAuth2 사용할 경우)
7 'shopbyAuthorization': 'Bearer 6022983d-1342-478b-b292-880aeb17cbd9' // 로그인한 유저의 엑세스 토큰 (OAuth2 사용할 경우)
8 });
9 만약 accessToken 또는 shopbyAuthorization 값이 없는 경우, 비회원 주문 결제 진행이 됩니다.
request 값은 아래 API 데이터 양식을 참고하시길 바랍니다.
- post결제하기
- /payments/reserve
주문 된 결제를 진행합니다.
전달되어야 하는 request 값을 json 형태로 data 파라미터로 전달합니다.
1
2 NCPPay.reservation(data, function (response) {
3 alert("결제완료 되었습니다.");
4 });
5 - iOS12 이상
- webview를 이용하여 샵바이결제편의(ncppay) 모듈을 호출합니다.
iOS9부터 보안 강화 목적으로 앱을 호출할 때 앱스키마를 등록해주어야 동작합니다.
info.plist 파일에 LSApplicationQueriesSchemes 키에 각 PG사에서 제공한느 앱 스키마 리스트를 등록하시길 바랍니다.

(예시) INICIS에서 스키마 리스트는 아래와 같습니다.
1
2 <key>LSApplicationQueriesSchemes</key>
3 <array>
4 <string>shinhan-sr-ansimclick</string>
5 <string>smshinhanansimclick</string>
6 <string>hdcardappcardansimclick</string>
7 <string>smhyundaiansimclick</string>
8 <string>mpocket.online.ansimclick</string>
9 <string>scardcertiapp</string>
10 <string>cloudpay</string>
11 <string>hanawalletmembers</string>
12 <string>hanaskcardmobileportal</string>
13 <string>nhallonepayansimclick</string>
14 <string>nonghyupcardansimclick</string>
15 <string>kb-acp</string>
16 <string>kbbank</string>
17 <string>newliiv</string>
18 <string>Liivbank</string>
19 <string>NewSmartPib</string>
20 <string>com.wooricard.wcard</string>
21 <string>wooripay</string>
22 <string>citimobileapp</string>
23 <string>lotteappcard</string>
24 <string>lottesmartpay</string>
25 <string>spmobile</string>
26 <string>kpay</string>
27 <string>payco</string>
28 <string>paycoapplogin</string>
29 <string>kakaotalk</string>
30 <string>lpayapp</string>
31 <string>lmslpay</string>
32 <string>shinsegaeeasypayment</string>
33 <string>supertoss</string>
34 <string>tmoneypay</string>
35 <string>chai</string>
36 <string>tswansimclick</string>
37 <string>uppay</string>
38 <string>kftc-bankpay</string>
39 <string>bankwallet</string>
40 <string>ukbanksmartbanknonloginpay</string>
41 <string>kdb-bankpay</string>
42 <string>ibk-bankpay</string>
43 <string>kb-bankpay</string>
44 <string>keb-bankpay</string>
45 <string>sh-bankpay</string>
46 <string>nhb-bankpay</string>
47 <string>nh-bankpay</string>
48 <string>wr-bankpay</string>
49 <string>sc-bankpay</string>
50 <string>s-bankpay</string>
51 <string>ct-bankpay</string>
52 <string>dg-bankpay</string>
53 <string>bnk-bankpay</string>
54 <string>kj-bankpay</string>
55 <string>jj-bankpay</string>
56 <string>jb-bankpay</string>
57 <string>kn-bankpay</string>
58 <string>kp-bankpay</string>
59 <string>cu-bankpay</string>
60 <string>mg-bankpay</string>
61 <string>kbn-bankpay</string>
62 <string>kkb-bankpay</string>
63 </array>
64 딥링크를 설정해야, 카드사 앱 결제 후 자동으로 다시 원래 앱으로 돌아옵니다.
정상동작을 위해서는 샵바이 결제편의 모듈 호출을 위해 구성한 reservation data중 inAppYn, extraData값을 같이 설정해야합니다.
1
2 "inAppYn": "Y",
3 "extraData": {
4 "appUrl":"scheme://" // 앱스키마명
5 },
6 샵바이 결제편의 모듈 호출을 위한 configuration 파라미터 중 confirmUrl에도 사용됩니다.
1
2 NCPPay.setConfiguration({
3 'clientId': ncp.clientId, // shopby에서 발급받은 clientId
4 'confirmUrl': 'scheme://confirm.html', // 결제 완료후 리턴을 받을 업체의 URL
5 ...
6 .
7 info.plist 에 url scheme를 추가합니다.
1
2 <key>CFBundleURLTypes</key>
3 <array>
4 <dict>
5 <key>CFBundleTypeRole</key>
6 <string>Editor</string>
7 <key>CFBundleURLName</key>
8 <string>앱식별자</string> // 번들명(bundle identifier)
9 <key>CFBundleURLSchemes</key>
10 <array>
11 <string>앱스키마명</string> // 앱스키마명(scheme://)
12 </array>
13 </dict>
14 </array>
15 1
2 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
3 HTTPCookieStorage.shared.cookieAcceptPolicy = .always
4 ...
5
6 또는
7 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
8 HTTPCookieStorage.shared.cookieAcceptPolicy = .always
9 ....
10 local 또는 remote(url) 방식으로 호출할 수 있습니다.
local은 앱 안에 넣어서 호출하는 방식이며, remote는 도메인에 있는 url로 호출하는 방식입니다.
로컬 호출방식에도 아래 2가지 방식이 있습니다.
- (방식1) html File로 호출하기ㄱ. 웹뷰(webview)에서 local html 파일 load 하기
본 문서 내 (step1) 준비하기 -2 를 참고하여, (예)ncppay_module.html 파일을 만들어 호출합니다.
알파/리얼 환경에 따라 호출해야할 html이 다릅니다.1 2 guard let filePath = Bundle.main.path(forResource: "ncppay_module", ofType: "html") else { return } 3 let url = URL(fileURLWithPath: filePath) 4 let request = URLRequest(url: url) 5 wkWebView.load(request) 6ㄴ. 웹뷰(webview) evaluateJavaScript를 이용해 NCPPay 모듈의 setConfiguration, reservation 함수 호출하기
본 문서 내 (step1) 준비하기 -3,4 를 참고하시길 바랍니다.1 2 let injectJavaScript: String = """ 3 NCPPay.setConfiguration({ 4 'clientId': '(API_CLIENT_ID)', 5 'confirmUrl': '앱스키마명://confirm.html', 6 'platform': 'IOS', 'accessToken':'(accessToken)' 7 }); 8 9 const obj = JSON.parse("(data)"); 10 11 NCPPay.reservation(obj, function (response) { 12 13 }); 14 15 """ 16 var jsStr = injectJavaScript.trimmingCharacters(in: .whitespaces) 17 18 jsStr = jsStr.trimmingCharacters(in: .whitespacesAndNewlines) 19 jsStr = jsStr.trimmingCharacters(in: .newlines) 20 21 wkWebView.evaluateJavaScript(jsStr, completionHandler: { result, error in 22 if let result = result { 23 print("injected evaluateJavaScript JS: (result)") 24 } 25 }) 26 - (방식2) html String으로 호출하기
웹뷰(webview)에서 local html에 있는 내용을 String으로 만들어서 직접 호출하는 방식입니다.
알파/ 리얼 환경에 따라 호출해야할 javascript가 다릅니다.
setConfiguration, reservation 함수 호출에 필요한 데이터가 준비되면 아래처럼 html String을 만들어 호출합니다.
본 문서 내 (step1) 준비하기-2, 3,4 를 참고하시길 바랍니다.1 2 let htmlString: String = """ 3 <script src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script> 4 <script type="text/javascript" src="https://spay.kcp.co.kr/plugin/kcp_spay_hub.js"></script> 5 <script type="text/javascript" src="https://nsp.pay.naver.com/sdk/js/naverpay.min.js"></script> 6 <script type="text/javascript" src="https://js.tosspayments.com/v1"></script> 7 // 기타 필요한 PG사 스크립트를 import 합니다. 8 <title>ShopbyPayment</title> 9 <script> 10 NCPPay.setConfiguration({ 11 'clientId': '(API_CLIENT_ID)', 12 'confirmUrl': '앱스키마명://comfirm.html', 13 'platform': 'IOS', 14 'accessToken':'', 15 'shopbyAuthorization':'Bearer (accessToken)' 16 }); 17 const obj = JSON.parse("(data)"); 18 NCPPay.reservation(obj, function (response) { 19 }); 20 </script> 21 """ 22 wkWebView.loadHTMLString(htmlString, baseURL: Bundle.main.bundleURL) 23
방식은 위 🅐-1. local 에서 호출하기의 (방식1) html File로 호출하기과 동일합니다.
1
2 let url = URL(string: "https://도메인.com/ncppay_module.html")
3 let request = URLRequest(url: url!)
4 wkWebView.load(request)
5 경우에 따라서는, 다음 값을 설정해야 카드사앱 결제 후 confirmUrl 딥링크(앱 간 이동) 통해 다시 원래 앱으로 돌아옵니다.
reservation data중 inAppYn, extraData값을 같이 설정해야 합니다.
본 문서 내 (step2) iOS 연동하기_Swift 의 'info.plist 설정' 부분을 참고하시길 바랍니다.
1
2 "inAppYn": "Y",
3 "extraData": {
4 "appUrl":"scheme://" // 앱 스키마명
5 },
6 NCPPay.setConfiguration에 설정한 confirmUrl로 성공 또는 실패 결과를 리턴합니다.
Uri =/confirmhtml?orderSheetNo=주문번호&guestToken(비회원주문)=토큰&PayType=CREDIT_CARD&result=(SUCCESS 또는 FAIL)
회원 주문인 경우 guestToken은 제외됩니다. guestToken은 비회원 주문 상세 조회 API와 같이 비회원 관련 구현이 필요한 곳에 사용됩니다.
- 성공 시
- request parameter 로 결과 값을 SUCCESS 로 전달합니다.
- 주문이 성공한 주문 번호를 함께 전달합니다. ex)result=SUCCESS&orderNo=123 - 실패 시
- request parameter 로 결과 값을 FAIL로 전달합니다.
- message에 실패 사유를 함께 전달합니다.
ex)result=FAIL&message=[01]+PG사+결제+실패하였습니다:+사용자가+결제를+취소+하였습니다.
아래를 참고하여 웹뷰를 설정하시길 바랍니다. (딥링크: 앱 간 이동 처리 포함)
1
2 // ios12
3 public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
4 var policy = WKNavigationActionPolicy.allow
5 if let requestURL = navigationAction.request.url {
6 if (requestURL.host == "itunes.apple.com" || requestURL.scheme == "tel" || requestURL.scheme == "sms" || requestURL.scheme == "mailto") && UIApplication.shared.canOpenURL(requestURL) {
7 UIApplication.shared.open(requestURL, options: [:], completionHandler: nil)
8 policy = .cancel
9 }
10 else if requestURL.scheme != "http" && requestURL.scheme != "https" {
11 if requestURL.scheme == "file" {
12 policy = .allow
13 } else {
14 // confirmUrl로 설정한 '앱스키마명://comfirm.html'을 통해 앱간 이동으로 결제결과값이 전송됩니다.
15 if requestURL.scheme == "앱스키마명" {
16 if let host = requestURL.host, host == "confirm.html" {
17 if let _ = requestURL.query as String? {
18 // url query parameter parse
19 // orderSheetNo=주문번호&guestToken(or accessToken)=토큰&PayType=CREDIT_CARD&result=SUCCESS
20 returnConfirmUrl(requestURL: requestURL)
21 }
22 policy = .allow
23 decisionHandler(policy)
24 return
25 }
26 }
27 UIApplication.shared.open(requestURL, options: [:], completionHandler: nil)
28 policy = .cancel
29 }
30 }
31 }
32 decisionHandler(policy)
33 }
34
35 @available(iOS 13.0, *)
36 public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
37 // 위 ios12 와 동일하게 구현
38 ...
39 생략
40 ..
41
42 }
43
44 private func returnConfirmUrl(requestURL: URL) {
45 if let urlComponent = URLComponents(url: requestURL, resolvingAgainstBaseURL: true) {
46 let queryItems = urlComponent.queryItems
47 let successValue = queryItems?.first(where: { $0.name == "result" })?.value
48 if successValue == nil { print("Key success not found") }
49 else {
50 if successValue == "SUCCESS" {
51 // 성공 프로세스 구현
52 // 주문번호(orderNo)
53 guard let orderNo = queryItems?.first(where: { $0.name == "orderNo" })?.value else { return }
54 guard let guestToken = queryItems?.first(where: { $0.name == "guestToken" })?.value else { return }
55 ...
56 ..
57 } else if successValue == "FAIL" {
58 // 실패 프로세스 구현
59 guard let message = queryItems?.first(where: { $0.name == "message" })?.value else { return }
60 print("Fail! error message: (message)")
61 } else {
62 print("An unknown error has occurred")
63 }
64 }
65 }
66 }
67 - info.plist 에 ATS설정을 추가
- local에서 샵바이결제편의모듈(ncppay) 을 호출하고 토스에서 제공하는 javascript를 호출하는 경우 사용합니다. 토스 javascript내에서 http를 사용하고 있기 때문에, ATS 설정을 하지 않을 경우 팝업이 노출되지 않는 점 주의하시길 바랍니다.
- remote에서 샵바이결제편의모듈(ncppay) 을 호출하는 경우에는 팝업이 정상 노출됩니다.
1
2 <key>NSAppTransportSecurity</key>
3 <dict>
4 <key>NSAllowsArbitraryLoads</key>
5 <true/>
6 </dict>
7 - pgType=NAVER_EASY_PAY 이고 local에서 샵바이결제편의(ncppay)을 호출하는 경우, 팝업창을 노출하기 위해서는 wkWebview의 allowFileAccessFromFileURLs를 true로 설정합니다.
- remote에서 샵바이결제편의모듈(ncppay) 을 호출하는 경우에는 팝업이 정상 노출됩니다.
1 wkWebView.configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") AOS 매뉴얼
webview에서 샵바이 결제편의 모듈을 띄우는 방식은 기본 웹 연동(PC.모바일웹) 방법과 유사합니다.
결제편의 모듈(PC, 모바일웹 기준) 가이드를 참고하실 수 있습니다.
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="description" content="">
6 <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
7 <script src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script>
8<script type="text/javascript" src="https://spay.kcp.co.kr/plugin/kcp_spay_hub.js"></script>
9<script type="text/javascript" src="https://nsp.pay.naver.com/sdk/js/naverpay.min.js"></script>
10<script type="text/javascript" src="https://js.tosspayments.com/v1"></script>
11<script language="javascript" type="text/javascript" src="https://chai.finance/js/v1/payment.min.js"></script>
12<script language="javascript" src="https://xpay.tosspayments.com/xpay/js/xpay_crossplatform.js" type="text/javascript"></script>
13 <title>ShopbyPayment</title>
14</head>
15<body>
16</body>
17</html>필요에 따라 각 PG사에서 제공하는 javascript를 import 해야합니다.
1<script src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script>
2<script src='https://nsp.pay.naver.com/sdk/js/naverpay.min.js'></script>
3
4<script src='https://spay.kcp.co.kr/plugin/kcp_spay_hub.js'></script>
5<script src='https://pretest.uplus.co.kr:9443/xpay/js/xpay_crossplatform.js'></script>1NCPPay.setConfiguration({
2 'clientId': ncp.clientId, // shopby에서 발급받은 clientId
3 'confirmUrl': 'scheme://confirm.html', // 결제 완료후 리턴을 받을 업체의 URL
4 'platform': 'AOS', // 'IOS or AOS'
5 'accessToken': '6022983d-1342-478b-b292-880aeb17cbd9', // 로그인한 유저의 엑세스 토큰 (OAuth2 사용할 경우)
6 'shopbyAuthorization': 'Bearer 6022983d-1342-478b-b292-880aeb17cbd9' // 로그인한 유저의 엑세스 토큰 (OAuth2 사용할 경우)
7});만약 accessToken 또는 shopbyAuthorization 값이 없는 경우, 비회원 주문 결제 진행이 됩니다.
request 값은 아래 API 데이터 양식을 참고하시길 바랍니다.
- post결제하기
- /payments/reserve
주문 된 결제를 진행합니다.
전달되어야 하는 request 값을 json 형태로 data 파라미터로 전달합니다.
1NCPPay.reservation(data, function (response) {
2 alert("결제완료 되었습니다.");
3});- Android 5.0 이상
- webview를 이용하여 샵바이 결제편의(ncppay) 모듈을 호출합니다.
| APP | 패키지명 | APP | 패키지명 |
|---|---|---|---|
| 페이코 | com.nhnent.payapp | ISP | kvp.jjy.MispAndroid320 |
| SSGPAY | com.ssg.serviceapp.android.egiftcertificate | KBPay | com.kbcard.cxh.appcard |
| LPAY | com.lottemembers.android | liivbank | com.kbstar.liivbank |
| KPAY | com.inicis.kpay | newliiv | com.kbstar.reboot |
| 티머니페이 | com.tmoney.tmpay | KB 스타뱅킹 | com.kbstar.kbbank |
| 토스페이 | viva.republica.toss | 삼성페이 | com.samsung.android.spay |
| 삼성페이 | com.samsung.android.spay | 삼성페이미니 | com.samsung.android.spaylite |
| 카카오페이 | com.kakao.talk | 엘지페이 | com.lge.lgpay |
| 차이페이 | finance.chai.app | 하나 | com.hanaskcard.paycla |
| 네이버 | com.nhn.android.search | 롯데 | com.lcacApp |
| 삼성 | kr.co.samsungcard.mpocket | 신한 | com.shcard.smartpay |
| 신한(ARS/일반/smart) | com.shinhancard.smartshinhan | 현대 | com.hyundaicard.appcard |
| 농협 | nh.smart.nhallonepay | 씨티 | kr.co.citibank.citimobile |
| 우리 WON 카드 | com.wooricard.smartapp | 우리 WON 뱅킹 | com.wooribank.smart.npib |
| 백신명 | 패키지명 |
|---|---|
| 터치엔 엠백신 | com.TouchEn.mVaccine.webs |
| V3 | com.ahnlab.v3mobileplus |
| V-Guard | kr.co.shiftworks.vguardweb |
| 카드사 | 패키지명 |
|---|---|
| 하나 | com.hanaskcard.rocomo.potal |
| 현대 | com.lumensoft.touchenappfree |
| 계좌이체 | 패키지명 |
|---|---|
| 뱅크페이 | com.kftc.bankpay.android |
| 케이뱅크 | com.kbankwith.smartbank |
| 해외결제 | 패키지명 |
|---|---|
| 알리페이 | com.eg.android.AlipayGphone |
| 위챗페이 | com.tencent.mm |
웹뷰 사용을 위해 인터넷 퍼미션과 useCleartextTraffic을 설정합니다.
1<uses-permission android:name="android.permission.INTERNET" />
2
3<application
4 ...
5 android:usesCleartextTraffic="true"
6 ...>
7 ...
8</application>웹뷰 AssetLoader를 사용하기 위한 웹킷을 앱 수준의 build.gradle에서 종속 항목으로 추가합니다.
1dependencies {
2 implementation "androidx.webkit:webkit:1.4.0"
3}WebViewClient를 상속받은 클래스를 구현합니다.
shouldOverrideUrlLoading에는 들어오는 url에 대하여 호스트가 처리할지, WebView에서 load할지 결정합니다.
url은 다양한 scheme 형태로 들어올 수 있기 때문에 이에 대한 예외처리가 필요합니다.
1override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
2 view ?: return false
3 url ?: return false
4
5 if(url.isBlank()) return false
6
7 // scheme url 형태인지 판별해야합니다.
8 if (!(url.startsWith("http") || url.startsWith("https"))) {
9 // 만약 앱이 scheme 형태로 들어왔다면, 이에 대한 처리를 해주어야합니다.
10 try {
11 Log.e(NCMWebViewClient.TAG,"isUrlScheme NEW[$url]")
12 return when {
13 url.startsWith("intent://") -> urlIntent(view.context, url)
14 url.startsWith("market://") -> urlMarket(view.context, url)
15 url.startsWith("ncppay://") -> urlNcpPay(url)
16 else -> {
17 Log.e(NCMWebViewClient.TAG, "else => $url")
18 return try {
19 view.context.startActivity(Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url) })
20 true
21 } catch (e: Exception) {
22 urlIntent(view.context, url)
23 }
24 }
25 }
26 } catch (e: Exception) {
27 Log.e(NCMWebViewClient.TAG, "Exception[${e.message}]")
28 e.printStackTrace()
29 }
30 } else {
31 view.loadUrl(url)
32 }
33 return true
34
35}NCPPay.setConfiguration에 설정한 confirmUrl로 성공 또는 실패 결과를 리턴합니다.
Uri =/confirmhtml?orderSheetNo=주문번호&guestToken(비회원주문)=토큰&PayType=CREDIT_CARD&result=(SUCCESS 또는 FAIL)
회원 주문인 경우 guestToken은 제외됩니다.
guestToken은 비회원 주문 상세 조회 API와 같이 비회원 관련 구현이 필요한 곳에 사용됩니다.
- 성공 시
- request parameter 로 결과 값을 SUCCESS 로 전달합니다.
- 주문이 성공한 주문 번호를 함께 전달합니다.
ex)result=SUCCESS&orderNo=123 - 실패 시
- request parameter 로 결과 값을 FAIL로 전달합니다.
- message에 실패 사유를 함께 전달합니다.
ex)result=FAIL&message=[01]+PG사+결제+실패하였습니다:+사용자가+결제를+취소+하였습니다.
1// if (url.startWith("ncppay://")
2private fun urlNcpPay(url: String) : Boolean {
3 val uri = Uri.parse(url)
4 val params: Set<String> = uri.queryParameterNames // url의 파라미터를 Set으로 불러온다.
5 if (params.contains("result")) {
6 val result = uri.getQueryParameter("result") ?: ""
7 val orderNo =
8 uri.getQueryParameter("orderNo") ?: "알 수 없음"
9
10 when (result) {
11 RESULT_SUCCESS_DEFAULT -> {
12 // result = SUCCESS 일 때 처리를 하면 된다.
13 return true
14 }
15 RESULT_FAILED_DEFAULT -> {
16 // result = FAIL 일 때 처리를 하면 된다.
17 return true
18 }
19 }
20 }
21 return false
22}api 30이상을 타겟팅 하는 경우, 패키지 가시성 정책이 변경되어 특정 사유가 있지 않는 한 QUERY_ALL_PACKAGES 의 사용이 제한됩니다. 해당 권한에 대응방안으로 AndroidManifest.xml파일에 패키지를 정의하거나, ActivityNotFoundException을 캐치하여 앱 설치 체크를 해야합니다.
매니페스트 파일에 카드사 패키지를 정의하여 가시성을 확보합니다. 카드사별 패키지명은 페이지 상단에 기재되어 있습니다.
1<queries>
2<package android:name="com.nhnent.payapp" /> <!--페이코 -->
3<package android:name="com.lottemembers.android" /> <!--LPAY -- >
4<package android:name="com.ssg.serviceapp.android.egiftcertificate" /> <!--SSGPAY -- >
5<!-- ....-- >
6</queries>startActivity() 호출 시 ActivityNotFoundException이 발생한다면, 이는 단말에 앱이 설치되어있지 않은 경우이므로 마켓으로 이동하여 설치를 유도해야합니다. 다음 코드 스니펫은 앱 설치 여부에 따라 설치 페이지로 이동하거나, 새로운 액티비티에서 앱을 실행하는 로직입니다.
1private fun urlIntent(context: Context, url: String): Boolean {
2 val intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME)
3 val intentPackageName = intent.getPackage()
4
5 Log.e(NCMWebViewClient.TAG," intentPackageName => $intentPackageName")
6
7 try {
8 context.startActivity(intent)
9 return true
10 }
11 catch (e: ActivityNotFoundException) {
12 val builder = AlertDialog.Builder(context).apply {
13 setTitle(context.getText(R.string.app_install_title))
14 setMessage(context.getText(R.string.app_install_message))
15 setPositiveButton(context.getText(R.string.confirm)) { _, _ ->
16 val marketIntent = Intent(Intent.ACTION_VIEW)
17 marketIntent.data = Uri.parse("market://details?id=$intentPackageName")
18 try {
19 context.startActivity(marketIntent)
20 } catch (e: ActivityNotFoundException) {
21 Log.e(NCMWebViewClient.TAG, "플레이 스토어를 설치하고 다시 실행해주세요.")
22 }
23 }
24// setNegativeButton(context.getText(R.string.cancel)) { _, _ -> }
25 setCancelable(false)
26 }
27
28 val dialog = builder.create()
29 dialog.show()
30 return true
31 }
32 catch (e: Exception) {
33 Log.e(NCMWebViewClient.TAG, "Exception[${e.message}]")
34 e.printStackTrace()
35 context.startActivity(intent)
36 return true
37 }
38}페이지의 상태(Start, Finish, Error)에 따라 액티비티에서 예외처리를 하려면 아래와 같이 리스너를 이용합니다.
페이지가 Finished 되면 액티비티에서 NCPPay관련 함수를 호출해야하기 때문에 아래와 같이 각각의 상태에 따라 리스너를 호출합니다.
구현 방법은 아래 문서 🅖에서 설명하고 있습니다.
1class MyWebViewClient : WebViewClient() {
2 private var mListener: NCMWebListener? = null
3
4 fun setWebListener(listener: NCMWebListener) {
5 mListener = listener
6 }
7
8 override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
9 Log.d(TAG, "onPageStarted Cookie[$hasCookie] URL[$url]")
10
11 mListener?.onPageStarted(url, favicon)
12 }
13
14 override fun onPageFinished(view: WebView?, url: String?) {
15 Log.d(TAG, "onPageFinished URL[$url]")
16 mListener?.onPageFinished(url)
17 }
18}
19
20interface NCMWebListener {
21 /* ViewClient */
22 fun onPageStarted(url: String?, favicon: Bitmap?) {}
23 fun onPageFinished(url: String?) {}
24 fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {}
25}WebViewClient를 작성하였다면 액티비티에서 웹뷰 관련 설정, 상태에 대한 처리를 합니다. 앞서 작성한 WebListener,PayResultListener를 사용할 것이기 때문에 두 인터페이스를 상속하고 작성된 WebViewClient와 리스너를 등록하고, 웹뷰 설정을 합니다.
1class PgWebViewActivity : AppCompatActivity(), NCMWebListener, PayResultListener {
2...
3override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 binding = ActivityPgWebviewBinding.inflate(layoutInflater)
6 setContentView(binding.root)
7
8 WebView.setWebContentsDebuggingEnabled(true)
9 binding.webview.apply {
10 webViewClient = NCMWebViewClient(applicationContext).apply {
11 setWebListener(this@PgWebViewActivity)
12 setPayResultListener(this@PgWebViewActivity)
13 }
14 webChromeClient = WebChromeClient()
15 settings.apply {
16 javaScriptCanOpenWindowsAutomatically = true // JS 에서 새창 실행
17 javaScriptEnabled = true // JS 허용여부
18 setSupportMultipleWindows(true) // 새창 허용여부
19
20 // Main WebView Only
21 loadWithOverviewMode = true // html의 컨텐츠가 웹뷰보다 클 경우 스크린 크기에 맞게 조정
22 useWideViewPort = true // 화면 맞추기 허용여부
23 cacheMode = WebSettings.LOAD_DEFAULT // 브라우저 캐시 사용 재정
24 }
25 }
26 }
27}결제 Url을 로드하기위해 아래와 같은 코드를 작성합니다.
로컬 html 파일로 load 하는 방법은 아래 문서 🅗-1를 참고 부탁드립니다.
1webview.loadUrl("https://도메인.com/NCPPay.html") // 간편결제 Url 로딩리스너를 통해 상태를 전달 받기 위해 WebListener에 대한 함수를 오버라이딩합니다. 웹 상태가 Finish가 되면 NCPPay를 자바스크립트로 호출합니다.
1class PgWebViewActivity : AppCompatActivity(), NCMWebListener {
2...
3 override fun onCreate(savedInstanceState: Bundle?) {
4 super.onCreate(savedInstanceState)
5 ...
6 webViewClient = NCMWebViewClient(applicationContext).apply {
7 setWebListener(this@PgWebViewActivity) // 리스너 등록
8 }
9 }
10 override fun onPageStarted(url: String?, favicon: Bitmap?) {
11 super.onPageStarted(url, favicon)
12 binding.progressBar2.isVisible = true
13 }
14 override fun onPageFinished(url: String?) {
15 super.onPageFinished(url)
16 if (url == Constants.SHOPBY_PAYMENT_URL) {
17 executePaymentProcess(
18 clientId = Constants.TEST_CLIENT_ID,
19 platform = "AOS",
20 confirmUrl = "ncppay://confirm.html",
21 data = Gson().toJson(MainApplication.orderRequestData),
22 accessToken = userAccessToken
23 )
24 }
25 binding.progressBar2.isVisible = false
26 }
27}호출해야할 자바스크립트는 아래와 같습니다.
아래 코드에 대한 자세한 사항은 결제편의 모듈(PC, 모바일웹 기준) 가이드를 참고하실 수 있습니다.
1...
2 fun executePaymentProcess(clientId: String, confirmUrl: String, platform: String, data: String, accessToken: String? = null) {
3 Log.d(TAG,"executePaymentPrecess() called")
4 Log.d(TAG,"$data")
5 setScript(
6 """
7 NCPPay.setConfiguration({
8 'clientId': "$clientId",
9 'confirmUrl': '$confirmUrl',
10 'platform': '$platform',
11 'shopbyAuthorization': 'Bearer $accessToken',
12 'accessToken': ''
13 });
14
15 NCPPay.reservation(JSON.parse(`$data`), function (response) {
16 alert("결제완료 되었습니다.");
17 });
18 """.trimIndent()
19 )
20 }
21 private fun setScript(script: String, callback: ValueCallback<String>? = null) {
22 binding.webview.evaluateJavascript(script, callback)
23 }
24...로컬 호출방식에는 아래 2가지 방식이 있습니다.
프로젝트 내부 html asset을 불러올 때, 안드로이드에서는 file://형태의 url의 사용을 보안 상의 이유로 지양하고 있습니다.
그로 인해 안드로이드 API 30(11) 버전 부터 allowFileAccessFromFileURLs이 deprecated 되었으며, 대신에 AssetLoader를 통하여 html 에셋에 접근하도록 권장하고 있습니다.
AssetLoader 는WebViewClient의 shouldInterceptRequest를 오버라이딩하여 적용할 수 있습니다. 적용하기 위해서는Context가 필요합니다.
1class MyWebViewClient(context: Context) : WebViewClient() {
2...
3val assetLoader = WebViewAssetLoader.Builder()
4 .addPathHandler("/assets/", WebViewAssetLoader.AssetsPathHandler(context))
5 .build()
6
7 @RequiresApi(24)
8 override fun shouldInterceptRequest(
9 view: WebView?,
10 request: WebResourceRequest
11 ): WebResourceResponse? {
12 Log.d(TAG, "shouldInterceptRequest() called")
13 return assetLoader.shouldInterceptRequest(request.url)
14 }
15
16 @SuppressWarnings("deprecation") // suppress the warning if your target build API is lower than 21
17 override fun shouldInterceptRequest(
18 view: WebView?,
19 request: String?
20 ): WebResourceResponse? {
21 Log.d(TAG, "shouldInterceptRequest() called")
22 return assetLoader.shouldInterceptRequest(Uri.parse(request))
23 }
24}이후 웹뷰가 속해있는 액티비티에서 다음과 같은 url로 로드해주면 assets 폴더 내의 html 파일을 안전하게 불러올 수 있습니다.
다음 예시 코드는 assets폴더 안의 index.html를 호출하는 코드입니다.
1binding.webView.loadUrl("https://appassets.androidplatform.net/assets/index.html")1const val SHOPBY_PAYMENT_HTML = """
2 <!DOCTYPE html>
3 <html>
4
5 <head>
6 <meta charset="utf-8">
7 <meta name="description" content="">
8 <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
9 <script type="text/javascript" src="https://shop-api.e-ncp.com/payments/ncp_pay.js"></script>
10 <script type="text/javascript" src="https://spay.kcp.co.kr/plugin/kcp_spay_hub.js"></script>
11 <script type="text/javascript" src="https://nsp.pay.naver.com/sdk/js/naverpay.min.js"></script>
12 <script type="text/javascript" src="https://js.tosspayments.com/v1"></script>
13 <script type="text/javascript" src="https://chai.finance/js/v1/payment.min.js"></script>
14 <title>ShopbyPayment</title>
15 </head>
16
17 <body>
18
19 </body>
20 </html>
21 """위와 같은 String 타입의 HTML 코드를 불러오려면 webView의 loadDataWithBaseUrl속성을 이용하여 웹을 로드합니다. BaseUrl 미설정 시 about:blank라는 url로 로드되므로, https://로 시작하는 임의의 url을 인자로 넣습니다.
1webView.loadDataWithBaseURL(
2 baseUrl = SHOPBY_PAYMENT_BASE,
3 data = SHOPBY_PAYMENT_HTML,
4 mimeType = "text/html; charset=utf-8",
5 encoding = "uft-8",
6 historyUrl = null
7)