diff --git a/app/build.gradle b/app/build.gradle
index 863cb6e9..87559580 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -54,6 +54,7 @@ android {
buildConfigField 'String', 'NOTIFLY_PASSWORD', '"c6c585db0aaa4189be44d0467c7d66b6@A"'
buildConfigField 'String', 'KAKAO_APP_KEY', '"231cf78acfa8252fca38b9eedf87c5cb"'
buildConfigField 'String', 'GOOGLE_CLIENT_ID', '"983594297130-5hrmkh6vpskeq6v34350kmilf74574h2.apps.googleusercontent.com"'
+ buildConfigField 'String', 'APPSCHEME', '"voiceon"'
manifestPlaceholders = [
URISCHEME : "voiceon",
APPLINK_HOST : "voiceon.onelink.me",
@@ -79,6 +80,7 @@ android {
buildConfigField 'String', 'NOTIFLY_PASSWORD', '"c6c585db0aaa4189be44d0467c7d66b6@A"'
buildConfigField 'String', 'KAKAO_APP_KEY', '"20cf19413d63bfdfd30e8e6dff933d33"'
buildConfigField 'String', 'GOOGLE_CLIENT_ID', '"758414412471-mosodbj2chno7l1j0iihldh6edmk0gk9.apps.googleusercontent.com"'
+ buildConfigField 'String', 'APPSCHEME', '"voiceon-test"'
manifestPlaceholders = [
URISCHEME : "voiceon-test",
APPLINK_HOST : "voiceon-test.onelink.me",
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a83761bc..e18f1a14 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -85,6 +85,15 @@
+
+
+
+
+
+
+
-
+
diff --git a/app/src/main/assets/payverse_starter.html b/app/src/main/assets/payverse_starter.html
new file mode 100644
index 00000000..59c1ed69
--- /dev/null
+++ b/app/src/main/assets/payverse_starter.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/payverse_starter_debug.html b/app/src/main/assets/payverse_starter_debug.html
new file mode 100644
index 00000000..16af628d
--- /dev/null
+++ b/app/src/main/assets/payverse_starter_debug.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt
index a364310b..c9b9ea90 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/main/DeepLinkActivity.kt
@@ -1,19 +1,39 @@
package kr.co.vividnext.sodalive.main
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
+import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentActivity
import kr.co.vividnext.sodalive.splash.SplashActivity
class DeepLinkActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ val data: Uri? = intent?.data
+ if (data != null && data.scheme != null) {
+ val host = data.host
+ val path = data.path
+ // Payverse 결제 완료 딥링크라면 결제 화면으로 직접 전달
+ if (host == "payverse" && path == "/result") {
+ val paymentIntent = Intent(this, CanPaymentActivity::class.java).apply {
+ action = Intent.ACTION_VIEW
+ this.data = data
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
+ }
+ startActivity(paymentIntent)
+ finish()
+ return
+ }
+ }
+
+ // 그 외 일반 딥링크는 기존처럼 Splash로 위임
startActivity(
Intent(applicationContext, SplashActivity::class.java).apply {
- data = intent.data
+ setData(intent.data)
}
)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt
index 15f395f7..356217c1 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt
@@ -8,6 +8,9 @@ import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
import kr.co.vividnext.sodalive.mypage.can.coupon.UseCanCouponRequest
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseChargeRequest
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseChargeResponse
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseVerifyRequest
import kr.co.vividnext.sodalive.mypage.can.status.GetCanStatusResponse
import kr.co.vividnext.sodalive.mypage.can.status.charge.GetCanChargeStatusResponseItem
import kr.co.vividnext.sodalive.mypage.can.status.use.GetCanUseStatusResponseItem
@@ -42,6 +45,18 @@ interface CanApi {
@Header("Authorization") authHeader: String
): Single>
+ @POST("/charge/payverse")
+ fun payverseChargeCan(
+ @Body request: PayverseChargeRequest,
+ @Header("Authorization") authHeader: String
+ ): Single>
+
+ @POST("/charge/payverse/verify")
+ fun payverseVerifyCharge(
+ @Body request: PayverseVerifyRequest,
+ @Header("Authorization") authHeader: String
+ ): Single>
+
@GET("/can")
fun getCans(
@Header("Authorization") authHeader: String
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt
index 3bd5e2fb..31ee1e1f 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt
@@ -4,6 +4,8 @@ import kr.co.vividnext.sodalive.mypage.can.charge.iap.GoogleChargeRequest
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
import kr.co.vividnext.sodalive.mypage.can.coupon.UseCanCouponRequest
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseChargeRequest
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseVerifyRequest
import java.util.TimeZone
class CanRepository(private val api: CanApi) {
@@ -57,4 +59,14 @@ class CanRepository(private val api: CanApi) {
request = UseCanCouponRequest(couponNumber),
authHeader = token
)
+
+ fun payverseChargeCan(canId: Long, token: String) = api.payverseChargeCan(
+ request = PayverseChargeRequest(canId),
+ authHeader = token
+ )
+
+ fun payverseVerify(transactionId: String, orderId: String, token: String) = api.payverseVerifyCharge(
+ request = PayverseVerifyRequest(transactionId = transactionId, orderId = orderId),
+ authHeader = token
+ )
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentActivity.kt
index b49c0611..4ba01c65 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentActivity.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentActivity.kt
@@ -2,19 +2,26 @@ package kr.co.vividnext.sodalive.mypage.can.payment
import android.annotation.SuppressLint
import android.content.Intent
+import android.net.Uri
import android.os.Handler
import android.os.Looper
+import android.view.View
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.content.res.ResourcesCompat
+import androidx.core.net.toUri
import com.google.gson.Gson
import com.orhanobut.logger.Logger
import kr.co.bootpay.android.Bootpay
import kr.co.bootpay.android.events.BootpayEventListener
import kr.co.bootpay.android.models.BootUser
import kr.co.bootpay.android.models.Payload
+import kr.co.bootpay.android.webview.BootpayUrlHelper
import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseActivity
@@ -27,14 +34,14 @@ import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.CanResponse
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
import kr.co.vividnext.sodalive.mypage.can.status.CanStatusActivity
+import org.json.JSONObject
import org.koin.android.ext.android.inject
class CanPaymentActivity : BaseActivity(
ActivityCanPaymentBinding::inflate
) {
enum class PaymentMethod(val method: String) {
- CARD("카드"),
- BANK("계좌이체"),
+ UNIFIED("통합 결제"),
PHONE("휴대폰"),
KAKAOPAY("카카오페이")
}
@@ -106,8 +113,7 @@ class CanPaymentActivity : BaseActivity(
requestCharge()
}
- binding.tvMethodCard.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.CARD) }
- binding.tvMethodBank.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.BANK) }
+ binding.tvMethodUnified.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.UNIFIED) }
binding.tvMethodPhone.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.PHONE) }
binding.flMethodKakaopay.setOnClickListener {
viewModel.setPaymentMethod(PaymentMethod.KAKAOPAY)
@@ -118,8 +124,7 @@ class CanPaymentActivity : BaseActivity(
if (it != null) {
when (it) {
- PaymentMethod.CARD -> paymentMethodSelect(binding.tvMethodCard)
- PaymentMethod.BANK -> paymentMethodSelect(binding.tvMethodBank)
+ PaymentMethod.UNIFIED -> paymentMethodSelect(binding.tvMethodUnified)
PaymentMethod.PHONE -> paymentMethodSelect(binding.tvMethodPhone)
PaymentMethod.KAKAOPAY -> {
isKakao = true
@@ -133,8 +138,7 @@ class CanPaymentActivity : BaseActivity(
private fun allPaymentMethodSelectFalse() {
isKakao = false
- paymentMethodSelectFalse(binding.tvMethodBank)
- paymentMethodSelectFalse(binding.tvMethodCard)
+ paymentMethodSelectFalse(binding.tvMethodUnified)
paymentMethodSelectFalse(binding.tvMethodPhone)
binding.flMethodKakaopay
.setBackgroundResource(R.drawable.bg_round_corner_10_232323_777777)
@@ -161,16 +165,37 @@ class CanPaymentActivity : BaseActivity(
}
private fun requestCharge() {
- viewModel.chargeCan(
- canId = canResponse!!.id,
- paymentGateway = PaymentGateway.PG,
- onSuccess = {
- requestPayment(chargeId = it)
- },
- onFailure = {
- Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
+ when (viewModel.paymentMethodLiveData.value) {
+ PaymentMethod.UNIFIED -> {
+ viewModel.payverseChargeCan(
+ canId = canResponse!!.id,
+ onSuccess = { response ->
+ // Payverse payloadJson을 이용하여 WebView 결제 시작
+ startPayverse(response.payloadJson)
+ },
+ onFailure = {
+ Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
+ }
+ )
}
- )
+
+ PaymentMethod.KAKAOPAY, PaymentMethod.PHONE -> {
+ viewModel.chargeCan(
+ canId = canResponse!!.id,
+ paymentGateway = PaymentGateway.PG,
+ onSuccess = {
+ requestPayment(chargeId = it)
+ },
+ onFailure = {
+ Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
+ }
+ )
+ }
+
+ else -> {
+ Toast.makeText(applicationContext, "결제수단을 다시 선택해 주세요.", Toast.LENGTH_LONG).show()
+ }
+ }
}
private fun requestPayment(chargeId: Long) {
@@ -259,4 +284,99 @@ class CanPaymentActivity : BaseActivity(
}
)
}
+
+ @SuppressLint("SetJavaScriptEnabled")
+ private fun startPayverse(payloadJson: String) {
+ try {
+ val appScheme = BuildConfig.APPSCHEME
+ val payload = JSONObject(payloadJson)
+ payload.put("returnUrl", "$appScheme://payverse/result")
+ payload.put("webhookUrl", "${BuildConfig.BASE_URL}/charge/payverse/webhook")
+ payload.put("appScheme", appScheme)
+
+ val jsonForJs = payload.toString()
+
+ val webView: WebView = binding.webviewPayverse
+ webView.visibility = View.VISIBLE
+ webView.settings.javaScriptEnabled = true
+ webView.webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView, url: String) {
+ super.onPageFinished(view, url)
+ val escaped = JSONObject.quote(jsonForJs)
+ view.evaluateJavascript("startPay($escaped)", null)
+ }
+
+ override fun shouldOverrideUrlLoading(
+ view: WebView,
+ request: WebResourceRequest
+ ): Boolean {
+ val url = request.url.toString()
+ if (url.startsWith("$appScheme://")) {
+ startActivity(Intent(Intent.ACTION_VIEW, url.toUri()))
+ return true
+ }
+ return BootpayUrlHelper.shouldOverrideUrlLoading(view, url)
+ }
+ }
+
+ if (BuildConfig.DEBUG && appScheme.contains("test", ignoreCase = true)) {
+ webView.loadUrl("file:///android_asset/payverse_starter_debug.html")
+ } else {
+ webView.loadUrl("file:///android_asset/payverse_starter.html")
+ }
+ } catch (e: Exception) {
+ Logger.e(e.message ?: "payverse start error")
+ Toast.makeText(applicationContext, "결제 초기화에 실패했습니다.", Toast.LENGTH_LONG).show()
+ }
+ }
+
+ override fun onNewIntent(intent: Intent?) {
+ super.onNewIntent(intent)
+ intent?.data?.let { handlePayverseDeeplink(it) }
+ }
+
+ private fun handlePayverseDeeplink(uri: Uri) {
+ val resultStatus = uri.getQueryParameter("resultStatus")
+ val transactionId = uri.getQueryParameter("tid")
+ val orderId = uri.getQueryParameter("orderId")
+ if (
+ resultStatus == "FAILED" ||
+ resultStatus == "DECLINE" ||
+ transactionId.isNullOrEmpty() ||
+ orderId.isNullOrEmpty()
+ ) {
+ Toast.makeText(
+ applicationContext,
+ "결제를 하지 못했습니다.\n다시 시도해 주세요",
+ Toast.LENGTH_LONG
+ ).show()
+
+ binding.webviewPayverse.visibility = View.GONE
+ finish()
+ return
+ }
+
+ viewModel.payverseVerify(
+ transactionId = transactionId,
+ orderId = orderId,
+ onSuccess = {
+ completePaymentSuccess()
+ },
+ onFailure = {
+ Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show()
+ }
+ )
+ }
+
+ private fun completePaymentSuccess() {
+ Toast.makeText(applicationContext, "캔이 충전되었습니다", Toast.LENGTH_LONG).show()
+ SharedPreferenceManager.can += (canResponse!!.rewardCan + canResponse!!.can)
+ if (gotoPrevPage) {
+ setResult(RESULT_OK)
+ } else {
+ val intent = Intent(applicationContext, CanStatusActivity::class.java)
+ startActivity(intent)
+ }
+ finish()
+ }
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempActivity.kt
index f9b9a692..14666708 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempActivity.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempActivity.kt
@@ -31,7 +31,7 @@ class CanPaymentTempActivity : BaseActivity(
ActivityCanPaymentBinding::inflate
) {
enum class PaymentMethod(val method: String) {
- CARD("카드"), BANK("계좌이체"), PHONE("휴대폰")
+ UNIFIED("통합 결제"), PHONE("휴대폰")
}
private val viewModel: CanPaymentTempViewModel by inject()
@@ -101,14 +101,12 @@ class CanPaymentTempActivity : BaseActivity(
requestCharge()
}
- binding.tvMethodCard.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.CARD) }
- binding.tvMethodBank.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.BANK) }
+ binding.tvMethodUnified.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.UNIFIED) }
binding.tvMethodPhone.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.PHONE) }
}
private fun allPaymentMethodSelectFalse() {
- paymentMethodSelectFalse(binding.tvMethodBank)
- paymentMethodSelectFalse(binding.tvMethodCard)
+ paymentMethodSelectFalse(binding.tvMethodUnified)
paymentMethodSelectFalse(binding.tvMethodPhone)
}
@@ -219,8 +217,7 @@ class CanPaymentTempActivity : BaseActivity(
if (it != null) {
when (it) {
- PaymentMethod.CARD -> paymentMethodSelect(binding.tvMethodCard)
- PaymentMethod.BANK -> paymentMethodSelect(binding.tvMethodBank)
+ PaymentMethod.UNIFIED -> paymentMethodSelect(binding.tvMethodUnified)
PaymentMethod.PHONE -> paymentMethodSelect(binding.tvMethodPhone)
}
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentViewModel.kt
index 3ae7e809..ccecd2e8 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentViewModel.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentViewModel.kt
@@ -10,6 +10,7 @@ import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.mypage.can.CanRepository
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
+import kr.co.vividnext.sodalive.mypage.can.payment.payverse.PayverseChargeResponse
class CanPaymentViewModel(private val repository: CanRepository) : BaseViewModel() {
@@ -120,4 +121,66 @@ class CanPaymentViewModel(private val repository: CanRepository) : BaseViewModel
fun setPaymentMethod(paymentMethod: CanPaymentActivity.PaymentMethod) {
_paymentMethodLiveData.value = paymentMethod
}
+
+ fun payverseChargeCan(
+ canId: Long,
+ onSuccess: (PayverseChargeResponse) -> Unit,
+ onFailure: (String) -> Unit
+ ) {
+ _isLoading.value = true
+ compositeDisposable.add(
+ repository.payverseChargeCan(
+ canId = canId,
+ token = "Bearer ${SharedPreferenceManager.token}"
+ ).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ _isLoading.value = false
+ if (it.success && it.data != null) {
+ onSuccess(it.data)
+ } else {
+ onFailure(it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ },
+ {
+ _isLoading.value = false
+ it.message?.let { m -> Logger.e(m) }
+ onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+
+ fun payverseVerify(
+ transactionId: String,
+ orderId: String,
+ onSuccess: () -> Unit,
+ onFailure: (String) -> Unit
+ ) {
+ _isLoading.value = true
+ compositeDisposable.add(
+ repository.payverseVerify(
+ transactionId = transactionId,
+ orderId = orderId,
+ token = "Bearer ${SharedPreferenceManager.token}"
+ ).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ {
+ _isLoading.value = false
+ if (it.success) {
+ onSuccess()
+ } else {
+ onFailure(it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ },
+ {
+ _isLoading.value = false
+ it.message?.let { m -> Logger.e(m) }
+ onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/payverse/PayverseChargeDto.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/payverse/PayverseChargeDto.kt
new file mode 100644
index 00000000..a696d57f
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/payverse/PayverseChargeDto.kt
@@ -0,0 +1,20 @@
+package kr.co.vividnext.sodalive.mypage.can.payment.payverse
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+
+@Keep
+data class PayverseChargeRequest(
+ @SerializedName("canId") val canId: Long
+)
+
+@Keep
+data class PayverseChargeResponse(
+ @SerializedName("chargeId") val chargeId: Long,
+ @SerializedName("payloadJson") val payloadJson: String
+)
+
+data class PayverseVerifyRequest(
+ @SerializedName("transactionId") val transactionId: String,
+ @SerializedName("orderId") val orderId: String
+)
diff --git a/app/src/main/res/layout/activity_can_payment.xml b/app/src/main/res/layout/activity_can_payment.xml
index 4fa54a54..f38e861a 100644
--- a/app/src/main/res/layout/activity_can_payment.xml
+++ b/app/src/main/res/layout/activity_can_payment.xml
@@ -103,10 +103,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="13.3dp"
- android:layout_marginTop="16.7dp">
+ android:layout_marginTop="16.7dp"
+ android:orientation="horizontal">
-
-
+ android:paddingVertical="8.3dp">
-
+
+
-
-
-
-
-
+
+