├── .gitignore ├── .npmignore ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ ├── jeongjuwon │ └── iamport │ │ ├── IAmPortModule.java │ │ ├── IAmPortPackage.java │ │ ├── IAmPortViewManager.java │ │ ├── IAmPortWebView.java │ │ └── UrlLoadingCallBack.java │ └── siot │ └── iamportsdk │ ├── CallbackWebViewClient.java │ ├── KakaoWebViewClient.java │ ├── KcpWebViewClient.java │ ├── NiceWebViewClient.java │ ├── PaycoWebViewClient.java │ └── PaymentScheme.java ├── etc └── scheme_ios.png ├── index.android.js ├── index.ios.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | settings.json 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .DS_Store 15 | 16 | android/build/ 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-iamport 2 | 3 | 4 | 리엑트 네이티브 아임포트 공식 라이브러리 [iamport](https://github.com/iamport/iamport-react-native)가 나옴에 따라 본 레파지토리는 더 이상 업데이트 되지 않습니다. 5 | 그 동안 사용해주셔서 감사합니다. 6 | 7 | 공식 주소: https://github.com/iamport/iamport-react-native 8 | 9 | -------- 10 | 11 | 12 | 리액트 네이티브용 아임포트 모듈입니다. 필요에 의해 나이스페이와 카카오페이, 페이코만 구현하였습니다. 안드로이드에서의 리액트네이티브 웹뷰는 이벤트 핸들링이 뜻대로 되질 않아 직접 안드로이드로 모듈 구현하였습니다. 필요에 따라 더 구현을 해서 사용하실 분들을 위해 공개합니다. 13 | 14 | # Installation 15 | 16 | ``` 17 | npm install --save react-native-iamport 18 | react-native link react-native-iamport 19 | ``` 20 | 21 | # IOS 설정 22 | 23 | ### XCode 프로젝트의 Target에서 info 에 scheme 정보 추가 24 | 25 | app_scheme는 IAmPort 모듈에 정의할 것을 입력한다. (아래 Example 참조) 26 | 27 | ![iOS Scheme](etc/scheme_ios.png) 28 | 29 | 30 | ### Info.plist 파일에 NSAppTransportSecurity 내용 추가 31 | 32 | ``` 33 | NSAppTransportSecurity 34 | 35 | 36 | NSExceptionDomains 37 | 38 | localhost 39 | 40 | NSExceptionAllowsInsecureHTTPLoads 41 | 42 | 43 | 44 | NSAllowsArbitraryLoads <-- 추가 45 | <-- 추가 46 | NSAllowsArbitraryLoadsInWebContent <-- 추가 47 | <-- 추가 48 | 49 | ``` 50 | 51 | ### Info.plist 파일에 LSApplicationQueriesSchemes 내용 추가 52 | ``` 53 | LSApplicationQueriesSchemes 54 | 55 | kakao0123456789abcdefghijklmn 56 | kakaokompassauth 57 | storykompassauth 58 | kakaolink 59 | kakaotalk 60 | kakaostory 61 | storylink 62 | payco 63 | kftc-bankpay 64 | ispmobile 65 | itms-apps 66 | hdcardappcardansimclick 67 | smhyundaiansimclick 68 | shinhan-sr-ansimclick 69 | smshinhanansimclick 70 | kb-acp 71 | mpocket.online.ansimclick 72 | ansimclickscard 73 | ansimclickipcollect 74 | vguardstart 75 | samsungpay 76 | scardcertiapp 77 | lottesmartpay 78 | lotteappcard 79 | cloudpay 80 | nhappvardansimclick 81 | nonghyupcardansimclick 82 | citispay 83 | citicardappkr 84 | citimobileapp 85 | itmss 86 | 87 | ``` 88 | 89 | # 안드로이드 설정 90 | 91 | ### AndroidManifest.xml 추가 92 | 93 | 리엑트 네이티브 안드로이드 환경에서는 manifest상에 redirect를 위한 **appscheme을 등록하지 않습니다.** 94 | 95 | ``` 96 | <-- singletask launchMode 추가 101 | 102 | 103 | 104 | 105 | <-- 인텐트 필터 추가 106 | 107 | 108 | 109 | <-- iOS와 달리 파라미터에 사용된 appscheme을 등록하지 않습니다. 110 | 111 | 112 | ``` 113 | 114 | # Example 115 | 116 | ```javascript 117 | import React, {Component} from 'react'; 118 | 119 | import { View } from 'react-native'; 120 | 121 | import IAmPort from 'react-native-iamport'; 122 | 123 | export default class Payment extends Component { 124 | 125 | _onPaymentResultReceive(response) { 126 | 127 | if (response.result == "success") { 128 | 129 | //성공시의 로직 130 | } else { 131 | 132 | //실패시의 로직 133 | } 134 | } 135 | 136 | render() { 137 | 138 | return ( 139 | 152 | ) 153 | } 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 16 9 | targetSdkVersion 22 10 | versionCode 14 11 | versionName "1.1.10" 12 | } 13 | } 14 | 15 | dependencies { 16 | compile 'com.facebook.react:react-native:+' 17 | provided 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/jeongjuwon/iamport/IAmPortModule.java: -------------------------------------------------------------------------------- 1 | package com.jeongjuwon.iamport; 2 | 3 | import android.widget.Toast; 4 | import android.content.Intent; 5 | import android.app.Activity; 6 | import android.net.Uri; 7 | import android.util.Log; 8 | 9 | import com.facebook.react.bridge.Promise; 10 | import com.facebook.react.bridge.NativeModule; 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.bridge.ActivityEventListener; 16 | import com.facebook.react.bridge.BaseActivityEventListener; 17 | 18 | import java.util.*; 19 | 20 | public class IAmPortModule extends ReactContextBaseJavaModule implements ActivityEventListener { 21 | 22 | public IAmPortModule(ReactApplicationContext reactContext) { 23 | 24 | super(reactContext); 25 | 26 | reactContext.addActivityEventListener(this); 27 | } 28 | 29 | @Override 30 | public String getName() { 31 | 32 | return "IAmPortModule"; 33 | } 34 | 35 | @Override 36 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) { 37 | 38 | } 39 | 40 | @Override 41 | public void onNewIntent(Intent intent) { 42 | 43 | Activity currentActivity = getCurrentActivity(); 44 | 45 | Log.i("iamport", "onNewIntent - Module Listener"); 46 | currentActivity.setIntent(intent); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/src/main/java/com/jeongjuwon/iamport/IAmPortPackage.java: -------------------------------------------------------------------------------- 1 | package com.jeongjuwon.iamport; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.bridge.JavaScriptModule; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.*; 10 | 11 | public class IAmPortPackage implements ReactPackage { 12 | @Override 13 | public List createViewManagers(ReactApplicationContext reactContext) { 14 | 15 | return Arrays.asList( 16 | new IAmPortViewManager() 17 | ); 18 | } 19 | 20 | @Override 21 | public List createNativeModules(ReactApplicationContext reactContext) { 22 | 23 | List modules = new ArrayList<>(); 24 | 25 | modules.add(new IAmPortModule(reactContext)); 26 | return modules; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/java/com/jeongjuwon/iamport/IAmPortViewManager.java: -------------------------------------------------------------------------------- 1 | package com.jeongjuwon.iamport; 2 | 3 | import javax.annotation.Nullable; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import android.view.ViewGroup.LayoutParams; 9 | import android.webkit.WebSettings; 10 | import android.webkit.CookieManager; 11 | import android.util.Log; 12 | import android.app.Activity; 13 | import android.content.Intent; 14 | import android.net.Uri; 15 | 16 | import com.facebook.react.ReactActivity; 17 | import com.facebook.react.bridge.ReadableMap; 18 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 19 | 20 | import com.facebook.react.bridge.ReadableArray; 21 | import com.facebook.react.bridge.WritableMap; 22 | import com.facebook.react.bridge.Arguments; 23 | import com.facebook.react.common.MapBuilder; 24 | import com.facebook.react.uimanager.SimpleViewManager; 25 | import com.facebook.react.uimanager.ThemedReactContext; 26 | import com.facebook.react.uimanager.events.RCTEventEmitter; 27 | import com.facebook.react.uimanager.annotations.ReactProp; 28 | import com.facebook.react.common.annotations.VisibleForTesting; 29 | import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; 30 | 31 | import com.siot.iamportsdk.CallbackWebViewClient; 32 | import com.siot.iamportsdk.KakaoWebViewClient; 33 | import com.siot.iamportsdk.NiceWebViewClient; 34 | import com.siot.iamportsdk.PaycoWebViewClient; 35 | import com.siot.iamportsdk.KcpWebViewClient; 36 | import com.siot.iamportsdk.CallbackWebViewClient; 37 | 38 | public class IAmPortViewManager extends SimpleViewManager { 39 | 40 | private static final String HTML_MIME_TYPE = "text/html"; 41 | 42 | private HashMap headerMap = new HashMap<>(); 43 | private IAmPortPackage aPackage; 44 | private Activity activity; 45 | private ThemedReactContext reactContext; 46 | 47 | @VisibleForTesting 48 | public static final String REACT_CLASS = "IAmPortViewManager"; 49 | 50 | @Override 51 | public String getName() { 52 | 53 | return REACT_CLASS; 54 | } 55 | 56 | @Override 57 | public IAmPortWebView createViewInstance(ThemedReactContext context) { 58 | 59 | IAmPortWebView webView = new IAmPortWebView(this, context); 60 | 61 | reactContext = context; 62 | 63 | activity = context.getCurrentActivity(); 64 | 65 | // Fixes broken full-screen modals/galleries due to body 66 | // height being 0. 67 | webView.setLayoutParams( 68 | new LayoutParams(LayoutParams.MATCH_PARENT, 69 | LayoutParams.MATCH_PARENT) 70 | ); 71 | CookieManager.getInstance().setAcceptCookie(true); // add default cookie support 72 | CookieManager.getInstance().setAcceptFileSchemeCookies(true); // add default cookie support 73 | 74 | return webView; 75 | } 76 | 77 | public void setPackage(IAmPortPackage aPackage) { 78 | 79 | this.aPackage = aPackage; 80 | } 81 | 82 | public IAmPortPackage getPackage() { 83 | 84 | return this.aPackage; 85 | } 86 | 87 | public void emitPaymentEvent(String result, String imp_uid, String merchant_uid){ 88 | 89 | WritableMap params = Arguments.createMap(); 90 | params.putString("result", result); 91 | params.putString("imp_uid", imp_uid); 92 | params.putString("merchant_uid", merchant_uid); 93 | 94 | reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("paymentEvent", params); 95 | } 96 | 97 | @ReactProp(name = "html") 98 | public void setHtml(IAmPortWebView view, @Nullable String html) { 99 | Log.i("iamport", "setHtml: " + html); 100 | view.loadDataWithBaseURL(view.getBaseUrl(), html, HTML_MIME_TYPE, view.getCharset(), null); 101 | } 102 | 103 | @ReactProp(name = "appScheme") 104 | public void setAppScheme(IAmPortWebView view, @Nullable String appScheme) { 105 | 106 | view.setAppScheme(appScheme); 107 | } 108 | 109 | @ReactProp(name = "source") 110 | public void setSource(IAmPortWebView view, @Nullable String source) { 111 | 112 | setHtml(view, source); 113 | } 114 | 115 | @ReactProp(name = "pg") 116 | public void setPG(IAmPortWebView view, @Nullable String pg) { 117 | 118 | Log.i("iamport", "PG - " + pg); 119 | 120 | if(pg.equals("nice")){ 121 | NiceWebViewClient webViewClient = new NiceWebViewClient(activity, view, new UrlLoadingCallBack() { 122 | 123 | @Override 124 | public void shouldOverrideUrlLoadingCallBack(String s) { 125 | Log.i("iamport", "NiceWebViewClient.shouldOverrideUrlLoadingCallBack - " + s); 126 | emitPaymentEvent(s, s, s); 127 | } 128 | 129 | }); 130 | view.setWebViewClient(webViewClient); 131 | } else if(pg.equals("kakaopay")){ 132 | view.setWebViewClient(new KakaoWebViewClient(activity, view)); 133 | } else if(pg.equals("payco")){ 134 | PaycoWebViewClient webViewClient = new PaycoWebViewClient(activity, view, new UrlLoadingCallBack() { 135 | 136 | @Override 137 | public void shouldOverrideUrlLoadingCallBack(String s) { 138 | Log.i("iamport", "PaycoWebViewClient.shouldOverrideUrlLoadingCallBack - " + s); 139 | emitPaymentEvent(s, s, s); 140 | } 141 | 142 | }); 143 | view.setWebViewClient(webViewClient); 144 | } else if(pg.equals("kcp")){ 145 | KcpWebViewClient webViewClient = new KcpWebViewClient(activity, view, new UrlLoadingCallBack() { 146 | @Override 147 | public void shouldOverrideUrlLoadingCallBack(String s) { 148 | Log.i("iamport", "KcpWebViewClient.shouldOverrideUrlLoadingCallBack - " + s); 149 | emitPaymentEvent(s, s, s); 150 | } 151 | }); 152 | view.setWebViewClient(webViewClient); 153 | } else { 154 | CallbackWebViewClient defaultWebViewClient = new CallbackWebViewClient(activity, view, new UrlLoadingCallBack() { 155 | @Override 156 | public void shouldOverrideUrlLoadingCallBack(String s) { 157 | Log.i("iamport", "CallbackWebViewClient.shouldOverrideUrlLoadingCallBack - " + s); 158 | emitPaymentEvent(s, s, s); 159 | } 160 | }); 161 | view.setWebViewClient(defaultWebViewClient); 162 | } 163 | } 164 | 165 | @Override 166 | public void onDropViewInstance(IAmPortWebView webView) { 167 | 168 | super.onDropViewInstance(webView); 169 | ((ThemedReactContext) webView.getContext()).removeLifecycleEventListener(webView); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /android/src/main/java/com/jeongjuwon/iamport/IAmPortWebView.java: -------------------------------------------------------------------------------- 1 | package com.jeongjuwon.iamport; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import android.net.Uri; 6 | import android.webkit.WebView; 7 | import android.webkit.WebSettings; 8 | import android.webkit.WebViewClient; 9 | import android.webkit.JavascriptInterface; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.app.Activity; 13 | import android.content.Intent; 14 | 15 | 16 | import com.facebook.react.bridge.WritableMap; 17 | import com.facebook.react.bridge.Arguments; 18 | import com.facebook.react.bridge.LifecycleEventListener; 19 | import com.facebook.react.uimanager.ThemedReactContext; 20 | import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; 21 | 22 | class IAmPortWebView extends WebView implements LifecycleEventListener { 23 | 24 | private final IAmPortViewManager mViewManager; 25 | private final ThemedReactContext mReactContext; 26 | 27 | private Activity activity; 28 | 29 | private String appScheme = "appScheme"; 30 | private String charset = "UTF-8"; 31 | private String baseUrl = "file:///"; 32 | private String injectedJavaScript = null; 33 | private boolean allowUrlRedirect = false; 34 | 35 | private class IAmPortWebViewBridge { 36 | 37 | IAmPortWebView mContext; 38 | 39 | IAmPortWebViewBridge(IAmPortWebView c) { 40 | mContext = c; 41 | } 42 | 43 | @JavascriptInterface 44 | public void receiveResult(String result, String imp_uid, String merchant_uid) { 45 | 46 | mContext.emitPaymentEvent(result, imp_uid, merchant_uid); 47 | } 48 | } 49 | 50 | public IAmPortWebView(IAmPortViewManager viewManager, ThemedReactContext reactContext) { 51 | 52 | super(reactContext); 53 | 54 | mViewManager = viewManager; 55 | mReactContext = reactContext; 56 | 57 | activity = reactContext.getCurrentActivity(); 58 | 59 | this.getSettings().setJavaScriptEnabled(true); 60 | this.getSettings().setBuiltInZoomControls(false); 61 | this.getSettings().setDomStorageEnabled(true); 62 | this.getSettings().setGeolocationEnabled(false); 63 | this.getSettings().setPluginState(WebSettings.PluginState.ON); 64 | this.getSettings().setAllowFileAccess(true); 65 | this.getSettings().setAllowFileAccessFromFileURLs(true); 66 | this.getSettings().setAllowUniversalAccessFromFileURLs(true); 67 | this.getSettings().setLoadsImagesAutomatically(true); 68 | this.getSettings().setBlockNetworkImage(false); 69 | this.getSettings().setBlockNetworkLoads(false); 70 | 71 | reactContext.addLifecycleEventListener(this); 72 | 73 | this.addJavascriptInterface(new IAmPortWebViewBridge(this), "iamport"); 74 | } 75 | 76 | public void setAppScheme(String appScheme){ 77 | 78 | this.appScheme = appScheme; 79 | } 80 | 81 | public String getAppScheme(){ 82 | 83 | return this.appScheme; 84 | } 85 | 86 | public void setBaseUrl(String baseUrl) { 87 | 88 | this.baseUrl = baseUrl; 89 | } 90 | 91 | public String getBaseUrl() { 92 | 93 | return this.baseUrl; 94 | } 95 | 96 | public String getCharset() { 97 | 98 | return this.charset; 99 | } 100 | 101 | public void emitPaymentEvent(String result, String imp_uid, String merchant_uid){ 102 | 103 | WritableMap params = Arguments.createMap(); 104 | params.putString("result", result); 105 | params.putString("imp_uid", imp_uid); 106 | params.putString("merchant_uid", merchant_uid); 107 | 108 | mReactContext.getJSModule(RCTDeviceEventEmitter.class).emit("paymentEvent", params); 109 | } 110 | 111 | @Override 112 | public void onHostResume() { 113 | 114 | Intent intent = activity.getIntent(); 115 | 116 | if ( intent != null ) { 117 | 118 | Uri intentData = intent.getData(); 119 | 120 | if ( intentData != null ) { 121 | 122 | //카카오페이 인증 후 복귀했을 때 결제 후속조치 123 | String url = intentData.toString(); 124 | 125 | Log.i("iamport", "onHostResume URL - " + url); 126 | 127 | if ( url.startsWith(this.getAppScheme() + "://process") ) { 128 | 129 | Log.i("iamport", "process"); 130 | this.loadUrl("javascript:IMP.communicate({result:'process'})"); 131 | } 132 | else if ( url.startsWith(this.getAppScheme() + "://cancel") ) { 133 | 134 | Log.i("iamport", "cancel"); 135 | this.loadUrl("javascript:IMP.communicate({result:'cancel'})"); 136 | } 137 | else if ( url.startsWith(this.getAppScheme() + "://success") ) { 138 | 139 | Log.i("iamport", "success"); 140 | 141 | Uri uri = Uri.parse(url); 142 | String imp_uid = uri.getQueryParameter("imp_uid"); 143 | String merchant_uid = uri.getQueryParameter("merchant_uid"); 144 | 145 | this.emitPaymentEvent("success", imp_uid, merchant_uid); 146 | } 147 | 148 | //TODO 149 | this.emitPaymentEvent("onHostResume", url, url); 150 | 151 | intent.replaceExtras(new Bundle()); 152 | intent.setAction(""); 153 | intent.setData(null); 154 | intent.setFlags(0); 155 | } 156 | } 157 | } 158 | 159 | @Override 160 | public void onHostPause() { 161 | 162 | Log.i("iamport", "onHostPause - IAmPortWebView"); 163 | } 164 | 165 | @Override 166 | public void onHostDestroy() { 167 | 168 | Log.i("iamport", "onHostDestroy - IAmPortWebView"); 169 | destroy(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /android/src/main/java/com/jeongjuwon/iamport/UrlLoadingCallBack.java: -------------------------------------------------------------------------------- 1 | package com.jeongjuwon.iamport; 2 | 3 | public interface UrlLoadingCallBack { 4 | 5 | public void shouldOverrideUrlLoadingCallBack(String url); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/CallbackWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | import android.app.Activity; 4 | import android.util.Log; 5 | import android.webkit.WebView; 6 | import android.webkit.WebViewClient; 7 | 8 | import com.jeongjuwon.iamport.UrlLoadingCallBack; 9 | 10 | /** 11 | * Created by jang on 2018. 5. 31.. 12 | */ 13 | 14 | public class CallbackWebViewClient extends WebViewClient { 15 | 16 | private Activity activity; 17 | UrlLoadingCallBack mCallBack; 18 | 19 | public CallbackWebViewClient(Activity activity, WebView target, UrlLoadingCallBack callBack) { 20 | this.activity = activity; 21 | this.mCallBack = callBack; 22 | } 23 | 24 | @Override 25 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 26 | Log.i("iamport", "CallbackWebViewClient.shouldOverrideUrlLoading: " + url); 27 | mCallBack.shouldOverrideUrlLoadingCallBack(url); 28 | 29 | return super.shouldOverrideUrlLoading(view, url); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/KakaoWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | import java.net.URISyntaxException; 4 | 5 | import android.app.Activity; 6 | import android.content.ActivityNotFoundException; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | import android.util.Log; 12 | 13 | public class KakaoWebViewClient extends WebViewClient { 14 | 15 | private Activity activity; 16 | 17 | public KakaoWebViewClient(Activity activity, WebView target) { 18 | this.activity = activity; 19 | } 20 | 21 | @Override 22 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 23 | Log.i("iamport", "KakaoWebViewClient.shouldOverrideUrlLoading - " + url); 24 | 25 | if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("javascript:")) { 26 | Intent intent = null; 27 | 28 | try { 29 | intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); //IntentURI처리 30 | Uri uri = Uri.parse(intent.getDataString()); 31 | 32 | Log.e("iamport", uri.toString()); 33 | 34 | activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); 35 | return true; 36 | } catch (URISyntaxException ex) { 37 | return false; 38 | } catch (ActivityNotFoundException e) { 39 | if ( intent == null ) return false; 40 | 41 | String packageName = intent.getPackage(); //packageName should be com.kakao.talk 42 | if (packageName != null) { 43 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName))); 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/KcpWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | import android.app.Activity; 4 | import android.content.ActivityNotFoundException; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.util.Log; 8 | import android.webkit.WebView; 9 | import android.webkit.WebViewClient; 10 | 11 | import java.io.UnsupportedEncodingException; 12 | import java.net.URISyntaxException; 13 | import java.net.URLDecoder; 14 | import java.net.URLEncoder; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | import com.jeongjuwon.iamport.UrlLoadingCallBack; 20 | 21 | /** 22 | * from https://github.com/iamport/kcp-android-graddle/blob/master/app/src/main/java/kr/iamport/sdk/KcpWebViewClient.java 23 | * Created by jang on 2017. 9. 14.. 24 | */ 25 | 26 | public class KcpWebViewClient extends WebViewClient { 27 | 28 | private Activity activity; 29 | private WebView target; 30 | final String KTFC_PACKAGE = "com.kftc.bankpay.android"; 31 | UrlLoadingCallBack mCallBack; 32 | 33 | public KcpWebViewClient(Activity activity, WebView target, UrlLoadingCallBack callBack) { 34 | this.activity = activity; 35 | this.target = target; 36 | this.mCallBack = callBack; 37 | } 38 | 39 | @Override 40 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 41 | Log.i("iamport", "KcpWebViewClient.shouldOverrideUrlLoading - " + url); 42 | mCallBack.shouldOverrideUrlLoadingCallBack(url); 43 | 44 | if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("javascript:")) { 45 | Intent intent = null; 46 | 47 | try { 48 | intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); //IntentURI처리 49 | Uri uri = Uri.parse(intent.getDataString()); 50 | 51 | activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); 52 | return true; 53 | } catch (URISyntaxException ex) { 54 | return false; 55 | } catch (ActivityNotFoundException e) { 56 | if (intent == null) return false; 57 | 58 | if (handleNotFoundPaymentScheme(intent.getScheme())) return true; 59 | 60 | String packageName = intent.getPackage(); 61 | if (packageName != null) { 62 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName))); 63 | return true; 64 | } 65 | 66 | return false; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | /** 74 | * @param scheme 75 | * @return 해당 scheme에 대해 처리를 직접 하는지 여부 76 | *

77 | * 결제를 위한 3rd-party 앱이 아직 설치되어있지 않아 ActivityNotFoundException이 발생하는 경우 처리합니다. 78 | * 여기서 handler되지않은 scheme에 대해서는 intent로부터 Package정보 추출이 가능하다면 다음에서 packageName으로 market이동합니다. 79 | */ 80 | protected boolean handleNotFoundPaymentScheme(String scheme) { 81 | //PG사에서 호출하는 url에 package정보가 없어 ActivityNotFoundException이 난 후 market 실행이 안되는 경우 82 | if (PaymentScheme.ISP.equalsIgnoreCase(scheme)) { 83 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + PaymentScheme.PACKAGE_ISP))); 84 | return true; 85 | } else if (PaymentScheme.BANKPAY.equalsIgnoreCase(scheme)) { 86 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + PaymentScheme.PACKAGE_BANKPAY))); 87 | return true; 88 | } 89 | 90 | return false; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/NiceWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import org.apache.http.NameValuePair; 10 | import org.apache.http.client.utils.URLEncodedUtils; 11 | import org.apache.http.util.EncodingUtils; 12 | 13 | import android.app.Activity; 14 | import android.content.ActivityNotFoundException; 15 | import android.content.ComponentName; 16 | import android.content.Intent; 17 | import android.net.Uri; 18 | import android.webkit.WebView; 19 | import android.webkit.WebViewClient; 20 | import android.webkit.WebResourceRequest; 21 | import android.webkit.WebResourceResponse; 22 | import android.util.Log; 23 | import com.jeongjuwon.iamport.UrlLoadingCallBack; 24 | 25 | public class NiceWebViewClient extends WebViewClient { 26 | 27 | private Activity activity; 28 | private WebView target; 29 | private String BANK_TID = ""; 30 | UrlLoadingCallBack mCallBack; 31 | 32 | final int RESCODE = 1; 33 | final String NICE_URL = "https://web.nicepay.co.kr/smart/interfaceURL.jsp"; // NICEPAY SMART 요청 URL 34 | final String NICE_BANK_URL = "https://web.nicepay.co.kr/smart/bank/payTrans.jsp"; // 계좌이체 거래 요청 URL 35 | final String KTFC_PACKAGE = "com.kftc.bankpay.android"; 36 | 37 | public NiceWebViewClient(Activity activity, WebView target, UrlLoadingCallBack callBack) { 38 | this.activity = activity; 39 | this.target = target; 40 | this.mCallBack = callBack; 41 | } 42 | 43 | public void bankPayPostProcess(String bankpayCode, String bankpayValue) { 44 | String postData = "callbackparam2="+BANK_TID+"&bankpay_code="+bankpayCode+"&bankpay_value="+bankpayValue; 45 | target.postUrl(NICE_BANK_URL,EncodingUtils.getBytes(postData,"euc-kr")); 46 | } 47 | 48 | @Override 49 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 50 | 51 | // TODO: emit event 52 | Log.i("iamport", "NiceWebViewClient.shouldOverrideUrlLoading: " + url); 53 | mCallBack.shouldOverrideUrlLoadingCallBack(url); 54 | 55 | if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("javascript:")) { 56 | Intent intent = null; 57 | 58 | try { 59 | /* START - BankPay(실시간계좌이체)에 대해서는 예외적으로 처리 */ 60 | if ( url.startsWith(PaymentScheme.BANKPAY) ) { 61 | try { 62 | String reqParam = makeBankPayData(url); 63 | 64 | intent = new Intent(Intent.ACTION_MAIN); 65 | intent.setComponent(new ComponentName("com.kftc.bankpay.android","com.kftc.bankpay.android.activity.MainActivity")); 66 | intent.putExtra("requestInfo",reqParam); 67 | activity.startActivityForResult(intent,RESCODE); 68 | 69 | return true; 70 | } catch (URISyntaxException e) { 71 | return false; 72 | } 73 | } 74 | /* END - BankPay(실시간계좌이체)에 대해서는 예외적으로 처리 */ 75 | 76 | intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); //IntentURI처리 77 | Uri uri = Uri.parse(intent.getDataString()); 78 | 79 | activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); 80 | return true; 81 | } catch (URISyntaxException ex) { 82 | return false; 83 | } catch (ActivityNotFoundException e) { 84 | if ( intent == null ) return false; 85 | 86 | if ( handleNotFoundPaymentScheme(intent.getScheme()) ) return true; 87 | 88 | String packageName = intent.getPackage(); 89 | if (packageName != null) { 90 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName))); 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | /** 102 | * @param scheme 103 | * @return 해당 scheme에 대해 처리를 직접 하는지 여부 104 | * 105 | * 결제를 위한 3rd-party 앱이 아직 설치되어있지 않아 ActivityNotFoundException이 발생하는 경우 처리합니다. 106 | * 여기서 handler되지않은 scheme에 대해서는 intent로부터 Package정보 추출이 가능하다면 다음에서 packageName으로 market이동합니다. 107 | * 108 | */ 109 | protected boolean handleNotFoundPaymentScheme(String scheme) { 110 | //PG사에서 호출하는 url에 package정보가 없어 ActivityNotFoundException이 난 후 market 실행이 안되는 경우 111 | if ( PaymentScheme.ISP.equalsIgnoreCase(scheme) ) { 112 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + PaymentScheme.PACKAGE_ISP))); 113 | return true; 114 | } else if ( PaymentScheme.BANKPAY.equalsIgnoreCase(scheme) ) { 115 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + PaymentScheme.PACKAGE_BANKPAY))); 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | private String makeBankPayData(String url) throws URISyntaxException { 123 | BANK_TID = ""; 124 | List params = URLEncodedUtils.parse(new URI(url), "UTF-8"); 125 | 126 | StringBuilder ret_data = new StringBuilder(); 127 | List keys = Arrays.asList(new String[] {"firm_name", "amount", "serial_no", "approve_no", "receipt_yn", "user_key", "callbackparam2", ""}); 128 | 129 | String k,v; 130 | for (NameValuePair param : params) { 131 | k = param.getName(); 132 | v = param.getValue(); 133 | 134 | if ( keys.contains(k) ) { 135 | if ( "user_key".equals(k) ) { 136 | BANK_TID = v; 137 | } 138 | ret_data.append("&").append(k).append("=").append(v); 139 | } 140 | } 141 | 142 | ret_data.append("&callbackparam1="+"nothing"); 143 | ret_data.append("&callbackparam3="+"nothing"); 144 | 145 | return ret_data.toString(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/PaycoWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | import java.net.URISyntaxException; 4 | 5 | import android.app.Activity; 6 | import android.content.ActivityNotFoundException; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.webkit.WebView; 10 | import android.webkit.WebViewClient; 11 | import android.util.Log; 12 | 13 | import android.webkit.WebResourceRequest; 14 | import android.webkit.WebResourceResponse; 15 | import com.jeongjuwon.iamport.UrlLoadingCallBack; 16 | 17 | public class PaycoWebViewClient extends WebViewClient { 18 | 19 | private Activity activity; 20 | UrlLoadingCallBack mCallBack; 21 | 22 | public PaycoWebViewClient(Activity activity, WebView target, UrlLoadingCallBack callBack) { 23 | this.activity = activity; 24 | this.mCallBack = callBack; 25 | } 26 | 27 | @Override 28 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 29 | 30 | // TODO: emit event 31 | Log.i("iamport", "PaycoWebViewClient.shouldOverrideUrlLoading: " + url); 32 | mCallBack.shouldOverrideUrlLoadingCallBack(url); 33 | 34 | if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("javascript:")) { 35 | Intent intent = null; 36 | 37 | try { 38 | intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); //IntentURI처리 39 | Uri uri = Uri.parse(intent.getDataString()); 40 | 41 | Log.e("iamport", uri.toString()); 42 | 43 | activity.startActivity(new Intent(Intent.ACTION_VIEW, uri)); 44 | return true; 45 | } catch (URISyntaxException ex) { 46 | return false; 47 | } catch (ActivityNotFoundException e) { 48 | if ( intent == null ) return false; 49 | 50 | String packageName = intent.getPackage(); //packageName should be com.kakao.talk 51 | if (packageName != null) { 52 | activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName))); 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /android/src/main/java/com/siot/iamportsdk/PaymentScheme.java: -------------------------------------------------------------------------------- 1 | package com.siot.iamportsdk; 2 | 3 | public class PaymentScheme { 4 | 5 | public final static String ISP = "ispmobile"; // ISP모바일 : ispmobile://TID=nictest00m01011606281506341724 6 | public final static String BANKPAY = "kftc-bankpay"; 7 | 8 | public final static String LOTTE_APPCARD = "lotteappcard"; // 롯데앱카드 : intent://lottecard/data?acctid=120160628150229605882165497397&apptid=964241&IOS_RETURN_APP=#Intent;scheme=lotteappcard;package=com.lcacApp;end 9 | public final static String HYUNDAI_APPCARD = "hdcardappcardansimclick"; // 현대앱카드 : intent:hdcardappcardansimclick://appcard?acctid=201606281503270019917080296121#Intent;package=com.hyundaicard.appcard;end; 10 | public final static String SAMSUNG_APPCARD = "mpocket.online.ansimclick"; // 삼성앱카드 : intent://xid=4752902#Intent;scheme=mpocket.online.ansimclick;package=kr.co.samsungcard.mpocket;end; 11 | public final static String NH_APPCARD = "nhappcardansimclick"; // NH 앱카드 : intent://appcard?ACCTID=201606281507175365309074630161&P1=1532151#Intent;scheme=nhappcardansimclick;package=nh.smart.mobilecard;end; 12 | public final static String KB_APPCARD = "kb-acp"; // KB 앱카드 : intent://pay?srCode=0613325&kb-acp://#Intent;scheme=kb-acp;package=com.kbcard.cxh.appcard;end; 13 | public final static String MOBIPAY = "cloudpay"; // 하나(모비페이) : intent://?tid=2238606309025172#Intent;scheme=cloudpay;package=com.hanaskcard.paycla;end; 14 | 15 | public final static String PACKAGE_ISP = "kvp.jjy.MispAndroid320"; 16 | public final static String PACKAGE_BANKPAY = "com.kftc.bankpay.android"; 17 | 18 | public final static String KAKAO = "kakaotalk"; 19 | public final static String PACKAGE_KAKAO = "com.kakao.talk"; 20 | } 21 | -------------------------------------------------------------------------------- /etc/scheme_ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rm3rdmodules/react-native-iamport/d962f45f0a6c65200bf8f74d367dc27a97e586a0/etc/scheme_ios.png -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import { StyleSheet } from 'react-native'; 5 | import { requireNativeComponent, DeviceEventEmitter } from 'react-native'; 6 | 7 | const IAmPortViewManager = requireNativeComponent('IAmPortViewManager', null); 8 | 9 | class IAmPort extends Component { 10 | 11 | componentDidMount() { 12 | 13 | DeviceEventEmitter.addListener('paymentEvent', this.paymentEvent.bind(this)); 14 | } 15 | 16 | componentWillUnmount() { 17 | 18 | DeviceEventEmitter.removeAllListeners('paymentEvent'); 19 | } 20 | 21 | getParameterByName(name, url) { 22 | 23 | if (!url) { 24 | url = window.location.href; 25 | } 26 | name = name.replace(/[\[\]]/g, "\\$&"); 27 | var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), 28 | results = regex.exec(url); 29 | if (!results) 30 | return null; 31 | if (!results[2]) 32 | return ''; 33 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 34 | } 35 | 36 | paymentEvent(e) { 37 | 38 | var url = e.result; 39 | var original = e; 40 | 41 | if (e.result == "success" || e.result == "failed") { 42 | this.props.onPaymentResultReceive(e); 43 | } 44 | 45 | var imp_uid = this.getParameterByName("imp_uid", url), 46 | merchant_uid = this.getParameterByName("merchant_uid", url), 47 | result = ""; 48 | 49 | if (url.includes('success=false')) { // 취소 버튼을 눌렀거나 결제 실패시 50 | result = "failed" 51 | } else if (url.includes('success=true')) { 52 | result = "success"; 53 | } else if (url.includes('payments/vbank')) { 54 | result = "vbank"; 55 | } 56 | 57 | if (result) { 58 | this.props.onPaymentResultReceive({result, imp_uid, merchant_uid, original}); 59 | } 60 | 61 | return true; 62 | } 63 | 64 | getRequestContent() { 65 | 66 | let params = this.props.params; 67 | const merchant_uid = params.merchant_uid || ('merchant_' + new Date().getTime()); 68 | const m_redirect_url = params.m_redirect_url || 'https://service.iamport.kr/payments/success'; 69 | let HTML = ` 70 | 71 | 72 | 73 | i'mport react native payment module 74 | 75 | 76 | 77 | 78 | 79 | 107 | 108 | 109 | `; 110 | return HTML; 111 | } 112 | 113 | _onPaymentResultReceive(e) { 114 | 115 | if (this.props.onPaymentResultReceive) { 116 | 117 | this.props.onPaymentResultReceive(e); 118 | } 119 | } 120 | 121 | render() { 122 | 123 | return ( 124 | 131 | ); 132 | } 133 | } 134 | 135 | const style = StyleSheet.create({ 136 | flex: 1 137 | }); 138 | 139 | module.exports = IAmPort; 140 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { WebView } from 'react-native'; 4 | 5 | export default class IAmPort extends Component { 6 | 7 | constructor(props) { 8 | 9 | super(props); 10 | } 11 | 12 | getRequestContent() { 13 | 14 | let params = this.props.params; 15 | const merchant_uid = params.merchant_uid || ('merchant_' + new Date().getTime()); 16 | let HTML = ` 17 | 18 | 19 | 20 | i'mport react native payment module 21 | 22 | 23 | 24 | 25 | 26 | 58 | 59 | 60 | `; 61 | return HTML; 62 | } 63 | 64 | getParameterByName(name, url) { 65 | 66 | if (!url) { 67 | url = window.location.href; 68 | } 69 | name = name.replace(/[\[\]]/g, "\\$&"); 70 | var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), 71 | results = regex.exec(url); 72 | if (!results) 73 | return null; 74 | if (!results[2]) 75 | return ''; 76 | return decodeURIComponent(results[2].replace(/\+/g, " ")); 77 | } 78 | 79 | _onMessage(e) { 80 | 81 | console.log('_onMessage native event', e.nativeEvent); 82 | const { data, action } = e.nativeEvent; 83 | const { params, onPaymentResultReceive } = this.props; 84 | 85 | if(params.pg !== "kakaopay") { 86 | 87 | //iamport 내부적으로 사용되는 다른 action 들이 추가로 더 있기 때문에 done 타입의 action이 아닌 경우 onMessage를 무시해야 함. 88 | if ( action !== "done" ) return; 89 | } 90 | 91 | if (!data) { 92 | return; 93 | } 94 | 95 | const res = JSON.parse(data); 96 | const { success, request_id, imp_uid, merchant_uid, error_msg } = res; 97 | const result = success ? "success" : "cancel"; 98 | console.log('_onMessage', result); 99 | 100 | onPaymentResultReceive({ result, imp_uid, merchant_uid }); 101 | } 102 | 103 | _onShouldStartLoadWithRequest(e) { 104 | 105 | var url = e.url; 106 | var me = this; 107 | 108 | console.log("onShouldStartLoadWithRequest", e); 109 | 110 | var imp_uid = this.getParameterByName("imp_uid", url), 111 | merchant_uid = this.getParameterByName("merchant_uid", url), 112 | result = ""; 113 | 114 | if (url.includes('imp_success=false')) { // 취소 버튼을 눌렀거나 결제 실패시 115 | result = "failed" 116 | } else if (url.indexOf(this.props.params.app_scheme + '://success') == 0) { 117 | result = "success"; 118 | } else if (url.indexOf(this.props.params.app_scheme + '://cancel') == 0) { 119 | result = "canceled"; 120 | } 121 | console.log("onShouldStartLoadWithRequest result", result); 122 | 123 | if (result) { 124 | this.props.onPaymentResultReceive({result, imp_uid, merchant_uid}); 125 | } 126 | 127 | return true; 128 | } 129 | 130 | injectPostMessageFetch() { 131 | 132 | const patchPostMessageFunction = function () { 133 | var originalPostMessage = window.postMessage; 134 | 135 | var patchedPostMessage = function (message, targetOrigin, transfer) { 136 | originalPostMessage(message, targetOrigin, transfer); 137 | }; 138 | 139 | patchedPostMessage.toString = function () { 140 | return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage'); 141 | }; 142 | 143 | window.postMessage = patchedPostMessage; 144 | }; 145 | 146 | return '(' + String(patchPostMessageFunction) + ')();'; 147 | } 148 | 149 | render() { 150 | return ( 151 | { 160 | return null; 161 | }} 162 | style={this.props.style} /> 163 | ); 164 | } 165 | } 166 | 167 | module.exports = IAmPort; 168 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Jeong Juwon", 4 | "email": "2jw0718@gmail.com" 5 | }, 6 | "bugs": { 7 | "url": "https://github.com/jeongjuwon/react-native-iamport/issues" 8 | }, 9 | "dependencies": {}, 10 | "description": "Simple react native i'mport module", 11 | "devDependencies": {}, 12 | "homepage": "https://github.com/jeongjuwon/react-native-iamport", 13 | "keywords": [ 14 | "react", 15 | "react-native", 16 | "react-component", 17 | "iamport", 18 | "import" 19 | ], 20 | "license": "MIT", 21 | "main": "index", 22 | "maintainers": [ 23 | { 24 | "name": "Jeong Juwon", 25 | "email": "2jw0718@gmail.com" 26 | }, 27 | { 28 | "name": "Jeff Gu Kang", 29 | "email": "jeffgukang@gmail.com" 30 | } 31 | ], 32 | "name": "react-native-iamport", 33 | "optionalDependencies": {}, 34 | "peerDependencies": { 35 | "react-native": ">=0.40" 36 | }, 37 | "readme": "ERROR: No README data found!", 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/jeongjuwon/react-native-iamport.git" 41 | }, 42 | "scripts": { 43 | "test": "echo \"Error: no test specified\" && exit 1" 44 | }, 45 | "version": "0.4.0" 46 | } 47 | --------------------------------------------------------------------------------