์๋๋ก์ด๋ ๋ค์ดํฐ๋ธ ์ฑ์์ ๊ฒฐ์ ๊ฐ๋ฐ์ ๊ฐํธํ๊ฒ ๋์์ฃผ๋ ์์ํฌํธ SDK ์ ๋๋ค.
-
PG ๋ค์ WebView ๊ธฐ๋ฐ์ผ๋ก ์ฐ๋๋์ด ์์ต๋๋ค.
-
์ถํ ์์ฐจ์ ์ผ๋ก ํ ๊ฐํธ๊ฒฐ์ ๋ค๋ ๋ค์ดํฐ๋ธ ์ฐ๋ ์์ ์ ๋๋ค.
project build.gradle
maven {
url 'https://jitpack.io'
}
app build.gradle
implementation 'com.github.iamport:iamport-android:vX.Y.Z'
ํ์๊ตฌํ ์ฌํญ
// ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ
// ์ฌ์ฉํ์๋ ์๋๋ก์ด๋ Application ํด๋์ค์ ์ถ๊ฐํ์ธ์
class BaseApplication : Application() {
override fun onCreate() {
..
Iamport.create(this)
}
}
// DI ๋ก koin ์ ์ฌ์ฉํ์๋ ๊ฒฝ์ฐ
// ์์ฑ๋ koinApplication ์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ์
์ผ ํฉ๋๋ค
// ์ฐธ๊ณ : ์ฝํ๋ฆฐ 1.5.0 ์ด์ ๋ฐ Koin 2.2.2 ๋ฅผ ์ฌ์ฉํ์๋ ๋ถ๋ค์ 2.2.3 ์ผ๋ก ์
๋ฐ์ดํธ ํ์๊ธฐ ๋ฐ๋๋๋ค.
// ์ฐธ๊ณ : v1.2.0 ๋ถํฐ koin 3.1.2 ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
class BaseApplication : Application() {
override fun onCreate() {
..
val koinApp = startKoin { .. }
Iamport.createWithKoin(this, koinApp)
}
// KoinApplication ์ด ํ์ํ ๊ฒฝ์ฐ
Iamport.getKoinApplition()
}
// SDK ์ด๊ธฐํ
// activity ์์ ํธ์ถ์ : LifecycleOwners must call register before they are STARTED.
// fragement ์์ ํธ์ถ์ : Fragments must call before they are created (i.e. initialization, onAttach(), or onCreate())
fun onCreate() {
Iamport.init(this)
..
}
// SDK ์ข
๋ฃ
// ๋ช
์์ ์ผ๋ก ํ๋ฉด์ ๋๊ฐ๋ ์์ , ๊บผ์ง๋ ์์ ๋ฑ์ ์ถ๊ฐ
Iamport.close()
// SDK ์ ๊ฒฐ์ ์์ฒญํ ๋ฐ์ดํฐ ๊ตฌ์ฑ
val request = IamPortRequest(
pg = "chai", // PG ์ฌ
pay_method = PayMethod.trans.name, // ๊ฒฐ์ ์๋จ
name = "์ฌ๊ธฐ์ฃผ๋ฌธ์ด์", // ์ฃผ๋ฌธ๋ช
merchant_uid = "mid_123456", // ์ฃผ๋ฌธ๋ฒํธ
amount = "3000", // ๊ฒฐ์ ๊ธ์ก
buyer_name = "ํ๊ธธ๋"
)
// ๊ฒฐ์ ์์ฒญ
Iamport.payment("imp123456", request,
approveCallback = { /* (Optional) CHAI ์ต์ข
๊ฒฐ์ ์ ์ฝ๋ฐฑ ํจ์. */ },
paymentResultCallback = { /* ์ต์ข
๊ฒฐ์ ๊ฒฐ๊ณผ ์ฝ๋ฐฑ ํจ์. */ })
ํผ์ณ๋ณด๊ธฐ
๋ณธ sdk ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฒฐ์ ์ฐ๋์ ํธ์๋ฅผ ์ ๊ณตํ๊ณ ์
Iamport.payment ๋ฅผ ํตํด ๊ฒฐ์ ์์ฒญ์ ์๋ก์ด Activity ๊ฐ ์ด๋ฆฌ๊ณ ,
๋ด๋ถ์ ์ผ๋ก WebView ๋ฅผ ์์ฑํ์ฌ ์ ๋ฌํด์ฃผ์ parameters ๋ฅผ ํตํด ๊ฒฐ์ ์ฐฝ์ ์ด๊ณ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ์์ฒญ์ ๋ฐ๋ผ ๊ฐ๋ฐ์ ์์ ๋๋ฅผ ๋๋ฆฌ๊ธฐ ์ํด WebView Mode, MobileWeb Mode ๋๊ฐ์ง๊ฐ ์ถ๊ฐ๋์์ต๋๋ค. ( <= 1.0.0-dev05 )
์ค๋ช
: ๊ฒฐ์ ํ์ด์ง๋ฅผ ์ง์ ์์ฑํ์๊ณ iamport-sdk ์ webview ๋ฅผ ๋๊ฒจ ๊ฒฐ์ ๋ฅผ ์งํํฉ๋๋ค.
ex) ๊ฒฐ์ Activity(or Fragment) ๋ฅผ ํตํด ์ง์ ๊ฒฐ์ ํ์ด์ง๋ฅผ ๊พธ๋ฏธ๊ธฐ ์ํ๋ ๋ถ.
๋ฐ์๋ฐฉ๋ฒ : ๊ธฐ์กด ํ์๊ตฌํ ์ฌํญ ๊ณผ ๊ฐ์ด iamport-sdk ์ธํ
์ ํฉ๋๋ค.
Iamport.payment ํธ์ถ ํ๋ผ๋ฏธํฐ ์ค webviewMode ์ webview ๋ฅผ ๋ฃ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
๊ทธ ์ธ๋ ๊ธฐ์กด์ ๋์๊ณผ ๊ฐ์ต๋๋ค.
์ํ์ฑ์ ์์ WebViewModeFragment.kt
Iamport.payment(๊ฐ๋งน์ ์๋ณ์ฝ๋, webviewMode = webview, ๊ธฐํ params, ์ฝ๋ฐฑ)
์ค๋ช
: ์์ํฌํธ๋ฅผ ์ฌ์ฉํ๋ Mobile ์นํ์ด์ง๊ฐ load ๋ webview ๋ฅผ ๋๊ฒจ ๊ฒฐ์ ์งํ์ ์ํฌํธ ํฉ๋๋ค.
ex) ์ด๋ฏธ ์น์ฌ์ดํธ์์ ์์ํฌํธ js sdk ๋ฅผ ์ด์ฉํ๊ณ ์๊ณ , ๋ณธ์ธ ์๋น์ค๋ฅผ app ์ผ๋ก๋ง ๊ฐ์ธ์ ์ถ์ ํ๊ณ ์ ํ์๋ ๋ถ.
๋ฐ์๋ฐฉ๋ฒ : ๊ธฐ์กด ํ์๊ตฌํ ์ฌํญ ๊ณผ ๊ฐ์ด iamport-sdk ์ธํ
์ ํฉ๋๋ค.
์ถ๊ฐ๋ก Iamport.pluginMobileWebSupporter(webview) ๋ฅผ ํธ์ถํ์ฌ ํ๋ผ๋ฏธํฐ๋ก webview ๋ฅผ ์ ๋ฌํฉ๋๋ค.
์ค์ ๊ฒฐ์ ์งํ์ ๊ณ ๊ฐ๋์ ์น์ฌ์ดํธ ๋ด์์ ์งํ๋ฉ๋๋ค.
์ํ์ฑ์ ์์ mobileweb.html (์์์ด๋ฉฐ ์ค์ ๋ก๋ ๊ณ ๊ฐ๋์ Front-End ๊ฐ ๋ฉ๋๋ค.)
์ํ์ฑ์ ์์ MobileWebViewModeFragment.kt
Iamport.pluginMobileWebSupporter(webview)
- Custom WebViewClient ์ ์ฌ์ฉ
์ํ์ฑ์ ์์ MyWebViewClient class
/**
webview url ์ ํตํด ์ฒ๋ฆฌํ๋ ๋ก์ง์ด ์์ ๊ฒฝ์ฐ์
[IamPortMobileModeWebViewClient] ์์ํ์ฌ ์ฌ์ฉ ํ์๊ฑฐ๋,
[Iamport.mobileWebModeShouldOverrideUrlLoading] ์ observe ์ ํตํด ๋ณ๊ฒฝ๋๋ url ์ ์ฒดํฌ ๊ฐ๋ฅํฉ๋๋ค.
*/
// CASE 1 : IamPortMobileModeWebViewClient ์์
open class MyWebViewClient : IamPortMobileModeWebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
Log.i("MyWebViewClient", "updated webview url ${view?.url}")
return super.shouldOverrideUrlLoading(view, request)
}
}
class MobileWebViewModeFragment : Fragment() {
override fun setupWebView() {
// IamPortMobileModeWebViewClient ์ฌ์ฉ
binding?.webview?.webViewClient = MyWebViewClient()
// CASE 2 : Iamport.mobileWebModeShouldOverrideUrlLoading ์ฌ์ฉ
// oreo ๋ฏธ๋ง์์ url ๋ณ๊ฒฝ๋ง ๋ณด๊ณ ์ถ์๊ฒฝ์ฐ (oreo ์ด์๋ถํฐ getWebViewClient ๊ฐ ์ง์๋๋ฏ๋ก)
Iamport.mobileWebModeShouldOverrideUrlLoading()?.observe(this, EventObserver { uri ->
Log.i("SAMPLE", "changed url :: $uri")
})
}
}
- Custom WebChromeClient ์ ์ฌ์ฉ
์ํ์ฑ์ ์์ MyWebViewChromeClient class
/**
[IamportWebChromeClient] ์์ํ์ฌ ์ฌ์ฉ
*/
// CASE 1 : IamportWebChromeClient ์์
open class MyWebViewChromeClient : IamportWebChromeClient() {
override fun onJsConfirm(view: WebView, url: String, message: String, result: JsResult): Boolean {
Log.i("MyWebViewChromeClient", "called this function")
return super.onJsConfirm(view, url, message, result)
}
}
class MobileWebViewModeFragment : Fragment() {
override fun setupWebView() {
// IamportWebChromeClient ์ฌ์ฉ
it.webChromeClient = MyWebViewChromeClient()
// ..
}
}
Optional ๊ตฌํ์ฌํญ : ๊ฒฐ์ ํ ๋์ ์ฑ์ผ๋ก์ ๋ณต๊ท(app_scheme ํ๋ผ๋ฏธํฐ) ์ ๋ํ์ฌ
ํผ์ณ๋ณด๊ธฐ
์๋๋ก์ด๋ ์์คํ
์ ์๋ก ์ฑ์ ๋์ฐ๊ณ ์ข
๋ฃ๊ฐ ๋๋ฉด ์๋์ผ๋ก ํธ์ถํ๋ ์ฑ์ผ๋ก ๋์์ค๊ฒ ๋์ด ์๊ธฐ์
๊ธฐ๋ณธ์ ์ผ๋ก app_scheme ํ๋ผ๋ฏธํฐ๋ ์ฌ์ฉํ์ค ํ์๊ฐ ์์ต๋๋ค. (iOS ์ ๊ฒฝ์ฐ ํด๋น ๊ธฐ๋ฅ์ด ์๊ธฐ์ ํ์์
๋๋ค.)
๊ทธ๋ผ์๋ ์ฌ์ฉ์ ์ํ์ ๋ค๋ฉด, ๊ฒฐ์ ์์ฒญ์ ๊ตฌ์ฑํ๋ IamPortRequest class ์ app_scheme ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ์ฌ์ผ ํฉ๋๋ค.
์ด ๋ฐ์ดํฐ๋ ์๋ํํฐ ๊ฒฐ์ ์ฑ(ํ์ด๋ถ, ๋ฑ ํฌํ์ด, toss ๋ฑ)์์ ๊ฒฐ์ ์ธ์ฆ์ด ์๋ฃ๋ ํ, ํธ์ถํ ๋์ ์ฑ์ ์คํ์ํค๋ ์ญํ ์ ํฉ๋๋ค.
๋ณธ SDK ์ WebView Mode / MobileWeb Mode ์์๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ฉฐ,
activity ์ launchMode ๋ฅผ singleInstance ๋ก ๊ตฌ์ฑํ์๊ณ ,
์๋ ์ฝ๋์ ๊ฐ์ด manifest ์์ intent-filter scheme ์ ์ค์ ํ์๊ธฐ ๋ฐ๋๋๋ค.
(PG ์ด๋์์ค์ ๊ฒฝ์ฐ scheme ์ . ๋ฅผ ํฌํจํ๋ฉด ๊ฒฐ์ ์คํจ ์ฒ๋ฆฌ๋๋ฏ๋ก ์ฃผ์ํ์๊ธฐ ๋ฐ๋๋๋ค.)
<activity
android:launchMode="singleInstance"
..
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mycustomappscheme" />
</intent-filter>
val request = IamPortRequest(
app_scheme = "mycustomappscheme"
..
์ผ๋ฐ ๊ฒฐ์ ๋ชจ๋์ ๊ฒฝ์ฐ ์๋ก์ด activity ๋ฅผ ๋์ ๊ฒฐ์ ๋ฅผ ์ฒ๋ฆฌํ๋ฏ๋ก app_scheme ์ฌ์ฉ์ด ๋ถ๊ฐํฉ๋๋ค.
ํผ์ณ๋ณด๊ธฐ
์๋ฐ ํ๋ก์ ํธ์์ app build.gradle ์์ kotin-stblib ์ถ๊ฐ๊ฐ ํ์ํฉ๋๋ค $์ฝํ๋ฆฐ-๋ฒ์
implementation "org.jetbrains.kotlin:kotlin-stdlib:$์ฝํ๋ฆฐ-๋ฒ์ "
ํ์๊ตฌํ ์ฌํญ. SDK ์ ๊ณต api ๋ณ ์ค๋ช ์ ์์ KOTLIN usage ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
// ์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ
// ์ฌ์ฉํ์๋ ์๋๋ก์ด๋ ์ดํ๋ฆฌ์ผ์ด์
ํด๋์ค์ ์ถ๊ฐํ์ธ์
public class BaseApplication extends Application {
@Override
public void onCreate() {
..
Iamport.INSTANCE.create(this, null);
}
}
// DI ๋ก koin ์ ์ฌ์ฉํ์๋ ๊ฒฝ์ฐ
// ์์ฑ๋ koinApplication ์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ์
์ผ ํฉ๋๋ค
// ์ฐธ๊ณ : ์ฝํ๋ฆฐ 1.5.0 ์ด์ ๋ฐ Koin 2.2.2 ๋ฅผ ์ฌ์ฉํ์๋ ๋ถ๋ค์ 2.2.3 ์ผ๋ก ์
๋ฐ์ดํธ ํ์๊ธฐ ๋ฐ๋๋๋ค.
public class BaseApplication extends Application {
@Override
public void onCreate() {
..
KoinApplication koinApp = ..
Iamport.INSTANCE.createWithKoin(this, koinApp);
}
}
@Override
public void onCreate() {
Iamport.INSTANCE.init(this);
..
}
// SDK ์ข
๋ฃ
// ๋ช
์์ ์ผ๋ก ํ๋ฉด์ ๋๊ฐ๋ ์์ , ๊บผ์ง๋ ์์ ๋ฑ์ ์ถ๊ฐ
Iamport.INSTANCE.close();
IamPortRequest request
= IamPortRequest.builder()
.pg("chai")
.pay_method(PayMethod.trans.name)
.name("์ฌ๊ธฐ์ฃผ๋ฌธ์ด์")
.merchant_uid("mid_123456")
.amount("3000")
.buyer_name("ํ๊ธธ๋").build();
Iamport.INSTANCE.payment("imp123456", request,
iamPortApprove -> {
// (Optional) CHAI ์ต์ข
๊ฒฐ์ ์ ์ฝ๋ฐฑ ํจ์.
return Unit.INSTANCE;
}, iamPortResponse -> {
// ์ต์ข
๊ฒฐ์ ๊ฒฐ๊ณผ ์ฝ๋ฐฑ ํจ์.
return Unit.INSTANCE;
});
- ์ฐจ์ด ๊ฒฐ์ ์์ approveCallback ์ด ์์ ๋ (์ต์ข ๊ฒฐ์ ์ ์ฌ๊ณ ํ์ธ ๋ฑ์ด ํ์ํ ๋)
Iamport.INSTANCE.chaiPayment(iamPortApprove) // ์ฌ๊ณ ๋ฑ ํ์ธ ํ, ์ฐจ์ด ์ต์ข
๊ฒฐ์ ์์ฒญ ์คํ.
- ์ฐจ์ด ๊ฒฐ์ ํด๋ง ์ฌ๋ถ ํ์ธ
Iamport.INSTANCE.isPolling().observe(this, EventObserver -> {
i("์ฐจ์ด ํด๋ง? :: " + it)
});
i("isPolling? " + Iamport.INSTANCE.isPollingValue())
- ์ฐจ์ด ๊ฒฐ์ ํด๋ง ์ค์๋ ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค๊ฐ ์๋์ ๋จ๊ฒ ๋ฉ๋๋ค.
Iamport.INSTANCE.enableChaiPollingForegroundService(true, true)
- ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ์๋ ๋ฐ ์ค์ง ๋ฒํผ ํด๋ฆญ์ ๋์์ ์๋ ๊ฐ์ ๋ธ๋ก๋ ์บ์คํธ ๋ฆฌ์๋ฒ๋ฅผ ํตํด ์บ์นํ ์ ์์ต๋๋ค.
const val BROADCAST_FOREGROUND_SERVICE = "com.iamport.sdk.broadcast.fgservice"
const val BROADCAST_FOREGROUND_SERVICE_STOP = "com.iamport.sdk.broadcast.fgservice.stop"
- git clone
- Android Studio project open
- build app
BaseApplication.kt (SDK ์์ฑ)
override fun onCreate() {
super.onCreate()
Iamport.create(this)
/**
* DI ๋ก KOIN ์ฌ์ฉ์ ์๋์ ๊ฐ์ด ์ฌ์ฉ
val koinApp = startKoin {
logger(AndroidLogger())
androidContext(this@BaseApplication)
}
Iamport.create(this, koinApp)
*/
}
// ์ด๊ธฐํ ์ฒ๋ฆฌ
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
Iamport.init(this)
..
}
PaymentFragment.kt (๊ฒฐ์ ํ๋ฉด)
// ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ์ฒ๋ฆฌ์ฉ ๋ธ๋ก๋ ์บ์คํธ ๋ฆฌ์๋ฒ ๋ฑ๋ก
override fun onAttach(context: Context) {
super.onAttach(context)
registForegroundServiceReceiver(context)
..
}
// ๊ฒฐ์ ๋ฒํผ ํด๋ฆญ
private fun onClickPayment() {
..
val request = IamPortRequest(
pg = pg.getPgSting(pgId = ""), // PG ์ฌ
pay_method = payMethod, // ๊ฒฐ์ ์๋จ
name = paymentName, // ์ฃผ๋ฌธ๋ช
merchant_uid = merchantUid, // ์ฃผ๋ฌธ๋ฒํธ
amount = amount, // ๊ฒฐ์ ๊ธ์ก
buyer_name = "๋จ๊ถ์๋
"
)
// ๊ฒฐ์ ํธ์ถ
Iamport.payment(userCode, request,
approveCallback = { approveCallback(it) },
paymentResultCallback = { callBackListener.result(it) })
}
// ์ฐจ์ด ๊ฒฐ์ ์ ์ฝ๋ฐฑ ๋ฐ ์ต์ข
๊ฒฐ์ ์์ฒญ ์ฒ๋ฆฌ
private fun approveCallback(iamPortApprove: IamPortApprove) {
val secUnit = 1000L
val sec = 1
GlobalScope.launch {
delay(sec * secUnit) // sec ์ด๊ฐ ์ฌ๊ณ ํ์ธ ํ๋ก์ธ์ค๋ฅผ ๊ฐ์ ํฉ๋๋ค
Iamport.chaiPayment(iamPortApprove) // TODO: ์ํ ํ์ธ ํ SDK ์ ์ต์ข
๊ฒฐ์ ์์ฒญ
}
}
// fragment ์์ ๋ช
์์ ์ธ ์ข
๋ฃํ ๋ ์ฒ๋ฆฌ Iamport.close()
private val backPressCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Builder(view?.context)
.setPositiveButton(android.R.string.ok) { _, _ ->
Iamport.close() // TODO ๋ช
์์ ์ธ SDK ์ข
๋ฃ
requireActivity().finish()
}
..
}
}