플러터 포트원 웹뷰 연동

나고수·2023년 11월 30일
0

플러터

목록 보기
2/5
post-thumbnail

웹뷰는 webview_flutter 를 사용했습니다.
요약: 포트원으로 결제를 구현한 페이지를 웹뷰로 불러올 때, 앱쪽에서 처리해 줘야하는 것들이 있었다.

안드로이드 설정 1- intent 설정

ios는 문제없지만 안드로이드는 카드앱을 열 때 intent 파싱 처리를 해 줘야 함
왜냐면 카드앱이 intent://로 시작하는 url인데, 플러터에서는 해당 url을 바로 처리 할 수 없다.
따라서 네이티브단에서 플러터가 처리 할 수 있는 url로 파싱 해줘야한다.

const MethodChannel methodChannel = MethodChannel('default_channel');
final WebViewController _webViewController = WebViewController();

      _webViewController
      //웹뷰 페이지 이동 이벤트를 제어하는 데 사용되는 콜백 함수를 설정하는 메서드
      ..setNavigationDelegate(
        NavigationDelegate(
          onNavigationRequest: (NavigationRequest request) async {
            //카드 앱의 url을 가져옴
            //why?)
            //플러터는 intent://로 시작하는 url을 지원하지 않음
            //but, 카드앱 url이 intent://로 시작함
            //so, 네이티브 코드를 통해 플러터가 처리할 수 있는 url로 변환
            getAppUrl(String url) async {
              var parsingURl = await methodChannel
                  .invokeMethod('getAppUrl', <String, Object>{'url': url});
              return parsingURl;
            }

            //앱이 설치 되지 않았을 경우
            //카드 앱의 플레이스토어의 url을 가져옴
            //why?)
            //플러터는 intent://로 시작하는 url을 지원하지 않음
            //but, 플레이스토어 url이 intent://로 시작함
            //so, 네이티브 코드를 통해 플러터가 처리할 수 있는 url로 변환
            getMarketUrl(String url) async {
              var parsingURl = await methodChannel
                  .invokeMethod('getMarketUrl', <String, Object>{'url': url});
              return parsingURl;
            }

            if (!request.url.startsWith('http') &&
                !request.url.startsWith('https')) {
             //url이 http와 https로 시작하지 않으면 inent로 시작한다고 가정하고 이 함수를 타게 함
             //안드로이드일때
              if (Platform.isAndroid) {
                //플러터가 처리 할 수 있는 카드 앱 url을 얻어옴
                var value = await getAppUrl(request.url.toString());
                String url = value.toString();
                if (await canLaunchUrl(Uri.parse(url))) {
                  //카드 앱이 설치 되어 있으면 카드 앱을 실행
                  await launchUrl(Uri.parse(url));
                } else {
                  //앱이 설치 되어 있지 않으면 플레이 스토어로 이동
                  //플러터가 처리 할 수 있는 플레이스토어 앱 url을 얻어옴
                  var value = await getMarketUrl(request.url.toString());
                  String marketUrl = value.toString();
                  //플레이스토어로 이동
                  await launchUrl(Uri.parse(marketUrl));
                }
                //웹뷰의 페이지가 이동하지 않도록 처리
                return NavigationDecision.prevent;
              } else if (Platform.isIOS) {
                //ios는 launchUrl을 통해 바로 카드앱 열 수 있음.
                if (await canLaunchUrl(Uri.parse(request.url))) {
                  await launchUrl(
                    Uri.parse(request.url),
                  );
                  //웹뷰의 페이지가 이동하지 않도록 처리
                  return NavigationDecision.prevent;
                }
                //안드로이드는 마켓 url을 패키지네임으로 알 수 있어서 앱이 깔려 있지 않으면 마켓으로 이동 시킬 수 있다.
                //ios는 마켓 url이 AppleID에 따라 다른데 다른 앱의 AppleID을 알 수 있는 방법은 없어서 앱이 깔려있지 않으면 마켓으로 보낼 수 있는 방법은 없는 것 같다.
                //배민, 요기요도 앱이 깔려 있지 않은 경우에는 그냥 아무 반응 없음.
              }
            }
            return NavigationDecision.navigate;
          },
        ),
      )
class MainActivity : FlutterActivity() {
    private var CHANNEL = "default_channel"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        MethodChannel(
            flutterEngine!!.dartExecutor.binaryMessenger,
            CHANNEL
        ).setMethodCallHandler { call, result ->

            when {
                //카드 앱 url을 플러터에서 처리할 수 있는 url로 변경해서 리턴하는 함수
                call.method.equals("getAppUrl") -> {
                    try {
                        //플러터에서 넘겨받은 url
                        //url : intent://pay?payToken=3BG7uOVAQyycgM9nBP7P91#Intent;scheme=supertoss;package=viva.republica.toss;end
                        val url: String = call.argument("url")!!
                        val intent = Intent.parseUri(url, URI_INTENT_SCHEME)
                        result.success(intent.dataString)
                        //Uri.parse(intent.dataString) : supertoss://pay?payToken=3BG7uOVAQyycgM9nBP7P91
                        //ispmobile-비씨카드앱인듯?-일때는 이코드를 넣어줘야 결제가 된다. 
                        //근데 카카오페이 간편결제는 이 코드를 넣으면 작동을 안함
                        //왜 ispmobile만 네이티브에서 직접 앱을 열어야 하는지 잘 모르겠음ㅠ
                        if(url.contains("ispmobile")) {
                            startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(intent.dataString)))
                        }
                    } catch (e: URISyntaxException) {
                        result.notImplemented()
                    } catch (e: ActivityNotFoundException) {
                        result.notImplemented()
                    }
                }
                //카드 플레이 스토어 앱 url을 플러터에서 처리할 수 있는 url로 변경해서 리턴하는 함수
                call.method.equals("getMarketUrl") -> {
                    try {
                        val url: String = call.argument("url")!!
                        ////플러터에서 넘겨받은 url
                        //intent://appcard?ACCTID=202311301452029076694362247591&P1=1520115#Intent;scheme=nhallonepayansimclick;package=nh.smart.nhallonepay;end;
                        val intent = Intent.parseUri(url, URI_INTENT_SCHEME)
                        val scheme = intent.scheme
                        val packageName = intent.getPackage()
                        if (packageName != null) {
                            //packageName : nh.smart.nhallonepay
                            result.success("market://details?id=$packageName")
                        }
                        result.notImplemented()
                    } catch (e: URISyntaxException) {
                        result.notImplemented()
                    } catch (e: ActivityNotFoundException) {
                        result.notImplemented()
                    }
                }
                else -> {}
            }
        }

    }
}

안드로이드 설정 2 - 오픈 할 외부 앱 리스트 설정

안드로이드에서는 manifest.xml에 오픈 할 외부 앱 리스트 설정을 해줘야한다.

ios 설정

ios에서는 info.plist에 1. 카드앱에서 내 앱으로 돌아갈 App Scheme과 2. LSApplicationQueriesSchemes를 추가해줘야한다.
LSApplicationQueriesSchemes에 있는 외부앱만 열 수 있음
++)ios에서는 링크에 있는 것 뿐만 아니라 이것도 info.plist에 추가해줘야함

<dict>
		<key>NSExceptionDomains</key>
		<dict>
			<key>웹뷰 도메인</key>
			<dict>
				<key>NSExceptionAllowsInsecureHTTPLoads</key>
				<true/>
				<key>NSIncludesSubdomains</key>
				<true/>
			</dict>
		</dict>
		<key>NSAllowsArbitraryLoads</key>
		<true/>
		<key>NSAllowsArbitraryLoadsInWebContent</key>
		<true/>
	</dict>

그렇지 않으면

[assertion] Error acquiring assertion: <Error Domain=RBSServiceErrorDomain Code=1 "target is not running or doesn't have entitlement com.apple.runningboard.assertions.webkit" UserInfo={NSLocalizedFailureReason=target is not running or doesn't have entitlement com.apple.runningboard.assertions.webkit}>
[ProcessSuspension] 0x11401c120 - ProcessAssertion::acquireSync Failed to acquire RBS assertion 'XPCConnectionTerminationWatchdog' for process with PID=3207, error: Error Domain=RBSServiceErrorDomain Code=1 "target is not running or doesn't have entitlement com.apple.runningboard.assertions.webkit" UserInfo={NSLocalizedFailureReason=target is not running or doesn't have entitlement com.apple.runningboard.assertions.webkit}

에러가 났다.
웹뷰 도메인에 대해 HTTPS가 아니라 SSH 보안이 없는 HTTP 연결을 허용하도록 하는 부분이라고 한다.
안드로이드의 이코드와 같은 기능을 하는듯 (안드로이드는 모든 도메인에 대해 ios는 특정 도메인에 대해 http 연결을 허용 하는 점만 다른거같다)

android:usesCleartextTraffic="true"

근데 혹시 그러면 안드로이드도 android:usesCleartextTraffic="true" 이 코드를 넣어줘야할 수도 있겠다. 내가 결제를 붙일떄는 이미 해당 코드가 들어가 있어서 이 코드를 넣지 않았을때도 결제가 작동하는지 테스트 안해봄.

++) ios 네이버 간편결제 네이버앱이 이미 있는데 안열리는 경우 > info.plist에 LSApplicationQueriesSchemes에 naversearchthirdlogin를 등록해야한다.

profile
되고싶다

0개의 댓글