From d4e5c96b63d36f0fe679cf1e4de91a75ab0bf148 Mon Sep 17 00:00:00 2001 From: "i.khafizov" Date: Thu, 8 Sep 2022 18:02:45 +0300 Subject: [PATCH] 2.9.0 Added ability to launch 3DS authentication via `ThreeDsHelper` --- changelog.md | 8 +- gradle.properties | 2 +- gradle/versions.gradle | 4 +- .../sample/ui/AttachCardManuallyDialog.kt | 93 ++++++ .../acquiring/sample/ui/MainActivity.kt | 30 +- .../acquiring/sample/ui/PayableActivity.kt | 1 + .../layout/dialog_attach_card_manually.xml | 67 +++++ sample/src/main/res/menu/main_menu.xml | 5 + sample/src/main/res/values-ru/strings.xml | 1 + sample/src/main/res/values/strings.xml | 1 + ui/build.gradle | 2 +- .../tinkoff/acquiring/sdk/TinkoffAcquiring.kt | 5 +- .../tinkoff/acquiring/sdk/models/AsdkState.kt | 5 +- .../tinkoff/acquiring/sdk/models/ViewState.kt | 5 +- .../sdk/models/options/FeaturesOptions.kt | 10 + .../acquiring/sdk/models/result/CardResult.kt | 2 +- .../acquiring/sdk/payment/PaymentProcess.kt | 73 ++--- .../acquiring/sdk/threeds/ThreeDsHelper.kt | 280 ++++++++++++++++-- .../sdk/ui/activities/AttachCardActivity.kt | 10 +- .../sdk/ui/activities/PaymentActivity.kt | 12 +- .../sdk/ui/activities/ThreeDsActivity.kt | 59 +--- .../sdk/ui/activities/TransparentActivity.kt | 32 +- .../sdk/viewmodel/AttachCardViewModel.kt | 2 +- .../sdk/viewmodel/BaseAcquiringViewModel.kt | 9 + .../sdk/viewmodel/PaymentViewModel.kt | 4 +- .../sdk/viewmodel/ThreeDsViewModel.kt | 54 ---- .../sdk/viewmodel/ViewModelProviderFactory.kt | 2 +- 27 files changed, 545 insertions(+), 233 deletions(-) create mode 100644 sample/src/main/java/ru/tinkoff/acquiring/sample/ui/AttachCardManuallyDialog.kt create mode 100644 sample/src/main/res/layout/dialog_attach_card_manually.xml diff --git a/changelog.md b/changelog.md index 608ff11e..90f55049 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +## 2.9.0 + +#### Fixed +#### Changes +Added ability to launch 3DS authentication via `ThreeDsHelper` +#### Additions + ## 2.8.2 #### Fixed @@ -5,7 +12,6 @@ Send `software_version` and `device_model` in Init request #### Additions - ## 2.8.1 #### Fixed diff --git a/gradle.properties b/gradle.properties index a1a30bc7..c02d18af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=2.8.2 +VERSION_NAME=2.9.0 VERSION_CODE=15 GROUP=ru.tinkoff.acquiring diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 64e3f146..10ca8aa4 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -11,9 +11,9 @@ ext { gradlePluginVersion = '7.1.2' dokkaVersion = '1.7.10' - appCompatVersion = '1.1.0' + appCompatVersion = '1.4.0' preferenceVersion = '1.1.1' - lifecycleExtensionsVersion = '2.2.0' + lifecycleExtensionsVersion = '2.5.1' cardIoVersion = '5.5.1' gsonVersion = '2.8.6' coreNfcVersion = '1.0.2' diff --git a/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/AttachCardManuallyDialog.kt b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/AttachCardManuallyDialog.kt new file mode 100644 index 00000000..7b7210c1 --- /dev/null +++ b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/AttachCardManuallyDialog.kt @@ -0,0 +1,93 @@ +package ru.tinkoff.acquiring.sample.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.dialog_attach_card_manually.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import ru.tinkoff.acquiring.sample.R +import ru.tinkoff.acquiring.sample.SampleApplication +import ru.tinkoff.acquiring.sample.ui.MainActivity.Companion.toast +import ru.tinkoff.acquiring.sample.utils.SessionParams +import ru.tinkoff.acquiring.sample.utils.SettingsSdkManager +import ru.tinkoff.acquiring.sdk.models.enums.ResponseStatus +import ru.tinkoff.acquiring.sdk.models.options.screen.BaseAcquiringOptions +import ru.tinkoff.acquiring.sdk.models.paysources.CardData +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper + +class AttachCardManuallyDialogFragment : DialogFragment() { + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + private val sdk = SampleApplication.tinkoffAcquiring.sdk + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.dialog_attach_card_manually, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + close.setOnClickListener { dismiss() } + + attach.setOnClickListener { + val cardData = CardData(pan.text.toString(), date.text.toString(), cvv.text.toString()) + try { + cardData.validate() + } catch (e: Throwable) { + requireActivity().toast(e.message!!) + return@setOnClickListener + } + addCardManually(cardData) + } + } + + private fun addCardManually(cardData: CardData) { + val settings = SettingsSdkManager(requireContext()) + val params = SessionParams[settings.terminalKey] + + val addCard = sdk.addCard { + customerKey = params.customerKey + checkType = "3DS" + } + + coroutineScope.launch(Dispatchers.IO) { + addCard.execute({ + attachCardManually(params, it.requestKey!!, cardData) + }, { }) + } + } + + private fun attachCardManually(params: SessionParams, requestKey: String, cardData: CardData) { + val attachCard = sdk.attachCard { + this.requestKey = requestKey + this.cardData = cardData + } + val options = BaseAcquiringOptions().apply { + setTerminalParams(params.terminalKey, params.publicKey) + } + + coroutineScope.launch(Dispatchers.IO) { + attachCard.execute({ + when (it.status) { + ResponseStatus.THREE_DS_CHECKING -> ThreeDsHelper.Launch.launchBrowserBased( + requireActivity(), MainActivity.THREE_DS_REQUEST_CODE, options, it.getThreeDsData()) + null -> { + requireActivity().toast("Attach success") + dismiss() + } + else -> requireActivity().toast("Attach failure: ${it.status}") + } + }, { requireActivity().toast("Attach failure: ${it.message}") }) + } + } + + companion object { + + const val TAG = "AttachCardManuallyDialogFragment" + } +} \ No newline at end of file diff --git a/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/MainActivity.kt b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/MainActivity.kt index 649af92b..01b421da 100644 --- a/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/MainActivity.kt +++ b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/MainActivity.kt @@ -16,6 +16,7 @@ package ru.tinkoff.acquiring.sample.ui +import android.app.Activity import android.content.Intent import android.content.IntentFilter import android.os.Build @@ -25,6 +26,8 @@ import android.view.MenuItem import android.widget.ListView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import ru.tinkoff.acquiring.sample.R import ru.tinkoff.acquiring.sample.SampleApplication import ru.tinkoff.acquiring.sample.adapters.BooksListAdapter @@ -42,6 +45,8 @@ import ru.tinkoff.acquiring.sdk.localization.Language import ru.tinkoff.acquiring.sdk.models.options.FeaturesOptions import ru.tinkoff.acquiring.sdk.models.options.screen.AttachCardOptions import ru.tinkoff.acquiring.sdk.models.options.screen.SavedCardsOptions +import ru.tinkoff.acquiring.sdk.models.result.CardResult +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper /** * @author Mariya Chernyadieva @@ -103,6 +108,11 @@ class MainActivity : AppCompatActivity(), BooksListAdapter.BookDetailsClickListe openAttachCardScreen() true } + R.id.menu_action_attach_card_manually -> { + AttachCardManuallyDialogFragment().show(supportFragmentManager, + AttachCardManuallyDialogFragment.TAG) + true + } R.id.menu_action_saved_cards -> { openSavedCardsScreen() true @@ -171,6 +181,20 @@ class MainActivity : AppCompatActivity(), BooksListAdapter.BookDetailsClickListe Toast.LENGTH_SHORT).show() } } + THREE_DS_REQUEST_CODE -> { + when (resultCode) { + RESULT_OK -> { + val result = data?.getSerializableExtra(ThreeDsHelper.Launch.RESULT_DATA) as CardResult + toast("Attach success: cardId = ${result.cardId} ") + } + RESULT_CANCELED -> toast("Attach canceled") + RESULT_ERROR -> { + val error = data?.getSerializableExtra(ThreeDsHelper.Launch.ERROR_DATA) as Throwable + error.printStackTrace() + toast("Attach failure: ${error.message}") + } + } + } else -> super.onActivityResult(requestCode, resultCode, data) } } @@ -250,7 +274,11 @@ class MainActivity : AppCompatActivity(), BooksListAdapter.BookDetailsClickListe private const val ATTACH_CARD_REQUEST_CODE = 11 private const val STATIC_QR_REQUEST_CODE = 12 private const val SAVED_CARDS_REQUEST_CODE = 13 - const val NOTIFICATION_PAYMENT_REQUEST_CODE = 14 + const val THREE_DS_REQUEST_CODE = 15 + + fun Activity.toast(message: String) = runOnUiThread { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } } } diff --git a/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/PayableActivity.kt b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/PayableActivity.kt index 6dd5cdf8..e38c0df8 100644 --- a/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/PayableActivity.kt +++ b/sample/src/main/java/ru/tinkoff/acquiring/sample/ui/PayableActivity.kt @@ -208,6 +208,7 @@ open class PayableActivity : AppCompatActivity() { darkThemeMode = settings.resolveDarkThemeMode() theme = settings.resolvePaymentStyle() userCanSelectCard = true + duplicateEmailToReceipt = true } } } diff --git a/sample/src/main/res/layout/dialog_attach_card_manually.xml b/sample/src/main/res/layout/dialog_attach_card_manually.xml new file mode 100644 index 00000000..717ec690 --- /dev/null +++ b/sample/src/main/res/layout/dialog_attach_card_manually.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/menu/main_menu.xml b/sample/src/main/res/menu/main_menu.xml index f2c40220..16a3e2d3 100644 --- a/sample/src/main/res/menu/main_menu.xml +++ b/sample/src/main/res/menu/main_menu.xml @@ -50,6 +50,11 @@ android:title="@string/activity_title_settings" app:showAsAction="never"/> + + Книга Корзина Привязать карту + Привязать карту вручную О программе Открыть статический QR код Принять оплату QR diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 19919ccd..3bb6e75c 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ Book Cart Attach card + Attach card manually About Open static QR code Get QR payment diff --git a/ui/build.gradle b/ui/build.gradle index 425436b1..7f5bc62e 100644 --- a/ui/build.gradle +++ b/ui/build.gradle @@ -32,7 +32,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "androidx.appcompat:appcompat:$appCompatVersion" - implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleExtensionsVersion" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleExtensionsVersion" implementation "ru.tinkoff.core.components.nfc:nfc:$coreNfcVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/TinkoffAcquiring.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/TinkoffAcquiring.kt index b7901666..692dc82f 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/TinkoffAcquiring.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/TinkoffAcquiring.kt @@ -41,6 +41,7 @@ import ru.tinkoff.acquiring.sdk.models.paysources.CardData import ru.tinkoff.acquiring.sdk.models.paysources.GooglePay import ru.tinkoff.acquiring.sdk.payment.PaymentProcess import ru.tinkoff.acquiring.sdk.responses.TinkoffPayStatusResponse +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper import ru.tinkoff.acquiring.sdk.ui.activities.AttachCardActivity import ru.tinkoff.acquiring.sdk.ui.activities.BaseAcquiringActivity import ru.tinkoff.acquiring.sdk.ui.activities.NotificationPaymentActivity @@ -137,7 +138,7 @@ class TinkoffAcquiring( @JvmOverloads fun openPaymentScreen(activity: Activity, options: PaymentOptions, requestCode: Int, state: AsdkState = DefaultState) { if (state is CollectDataState) { - state.data.putAll(ThreeDsActivity.collectData(activity, state.response)) + state.data.putAll(ThreeDsHelper.CollectData(activity, state.response)) } else { options.asdkState = state val intent = prepareIntent(activity, options, PaymentActivity::class.java) @@ -157,7 +158,7 @@ class TinkoffAcquiring( @JvmOverloads fun openPaymentScreen(fragment: Fragment, options: PaymentOptions, requestCode: Int, state: AsdkState = DefaultState) { if (state is CollectDataState) { - state.data.putAll(ThreeDsActivity.collectData(fragment.requireContext(), state.response)) + state.data.putAll(ThreeDsHelper.CollectData(fragment.requireContext(), state.response)) } else { options.asdkState = state val intent = prepareIntent(fragment.requireContext(), options, PaymentActivity::class.java) diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/AsdkState.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/AsdkState.kt index 7d0cbcc6..04bc3114 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/AsdkState.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/AsdkState.kt @@ -16,10 +16,9 @@ package ru.tinkoff.acquiring.sdk.models -import com.emvco3ds.sdk.spec.Transaction import ru.tinkoff.acquiring.sdk.AcquiringSdk import ru.tinkoff.acquiring.sdk.responses.Check3dsVersionResponse -import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsAppBasedTransaction import java.io.Serializable /** @@ -56,7 +55,7 @@ object FpsState : AsdkState() * Состояние проверки 3DS. На экране пользователю будет предложено пройти подтверждение платежа * по технологии 3D-Secure */ -class ThreeDsState(val data: ThreeDsData, val threeDSWrapper: ThreeDSWrapper?, val transaction: Transaction?) : AsdkState() +class ThreeDsState(val data: ThreeDsData, val transaction: ThreeDsAppBasedTransaction?) : AsdkState() /** * Состояние, когда необходимо собрать информацию об устройстве для прохождения 3DS diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/ViewState.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/ViewState.kt index 54c941e4..994e6d57 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/ViewState.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/ViewState.kt @@ -16,9 +16,8 @@ package ru.tinkoff.acquiring.sdk.models -import com.emvco3ds.sdk.spec.Transaction import ru.tinkoff.acquiring.sdk.responses.Check3dsVersionResponse -import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsAppBasedTransaction /** * @author Mariya Chernyadieva @@ -36,7 +35,7 @@ internal object FpsScreenState: Screen() internal class BrowseFpsBankScreenState(val paymentId: Long, val deepLink: String, val banks: Set?) : Screen() internal class OpenTinkoffPayBankScreenState(val paymentId: Long, val deepLink: String) : Screen() internal class RejectedCardScreenState(val cardId: String, val rejectedPaymentId: Long) : Screen() -internal class ThreeDsScreenState(val data: ThreeDsData, val wrapper: ThreeDSWrapper?, val transaction: Transaction?) : Screen() +internal class ThreeDsScreenState(val data: ThreeDsData, val transaction: ThreeDsAppBasedTransaction?) : Screen() internal class ThreeDsDataCollectScreenState(val response: Check3dsVersionResponse) : Screen() internal class LoopConfirmationScreenState(val requestKey: String) : Screen() diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/options/FeaturesOptions.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/options/FeaturesOptions.kt index 7d12c277..77c82e29 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/options/FeaturesOptions.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/options/FeaturesOptions.kt @@ -112,6 +112,14 @@ class FeaturesOptions() : Options(), Parcelable { */ var emailRequired: Boolean = true + /** + * При выставлении параметра в true, введенный пользователем на форме оплаты email будет + * продублирован в объект чека при отправке запроса Init. + * + * Не имеет эффекта если объект чека отсутствует. + */ + var duplicateEmailToReceipt: Boolean = false + /** * Следует ли при валидации данных карты показывать пользователю ошибку, если введенная * им срок действия карты уже истек. @@ -133,6 +141,7 @@ class FeaturesOptions() : Options(), Parcelable { selectedCardId = readString() handleErrorsInSdk = readByte().toInt() != 0 emailRequired = readByte().toInt() != 0 + duplicateEmailToReceipt = readByte().toInt() != 0 userCanSelectCard = readByte().toInt() != 0 showOnlyRecurrentCards = readByte().toInt() != 0 validateExpiryDate = readByte().toInt() != 0 @@ -152,6 +161,7 @@ class FeaturesOptions() : Options(), Parcelable { writeString(selectedCardId) writeByte((if (handleErrorsInSdk) 1 else 0).toByte()) writeByte((if (emailRequired) 1 else 0).toByte()) + writeByte((if (duplicateEmailToReceipt) 1 else 0).toByte()) writeByte((if (userCanSelectCard) 1 else 0).toByte()) writeByte((if (showOnlyRecurrentCards) 1 else 0).toByte()) writeByte((if (validateExpiryDate) 1 else 0).toByte()) diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/result/CardResult.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/result/CardResult.kt index 6a7e3cd6..2c84eb71 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/result/CardResult.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/models/result/CardResult.kt @@ -19,4 +19,4 @@ package ru.tinkoff.acquiring.sdk.models.result /** * @author Mariya Chernyadieva */ -internal class CardResult(var cardId: String? = null) : AsdkResult \ No newline at end of file +class CardResult(var cardId: String? = null) : AsdkResult \ No newline at end of file diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/payment/PaymentProcess.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/payment/PaymentProcess.kt index df5df037..e75e9f2d 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/payment/PaymentProcess.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/payment/PaymentProcess.kt @@ -18,12 +18,9 @@ package ru.tinkoff.acquiring.sdk.payment import android.content.Context import android.os.Build -import android.util.Base64 -import com.emvco3ds.sdk.spec.Transaction import ru.tinkoff.acquiring.sdk.AcquiringSdk import ru.tinkoff.acquiring.sdk.BuildConfig import ru.tinkoff.acquiring.sdk.exceptions.AcquiringApiException -import ru.tinkoff.acquiring.sdk.exceptions.AcquiringSdkException import ru.tinkoff.acquiring.sdk.localization.AsdkLocalization import ru.tinkoff.acquiring.sdk.models.AsdkState import ru.tinkoff.acquiring.sdk.models.BrowseFpsBankState @@ -46,12 +43,10 @@ import ru.tinkoff.acquiring.sdk.network.AcquiringApi.RECURRING_TYPE_VALUE import ru.tinkoff.acquiring.sdk.requests.InitRequest import ru.tinkoff.acquiring.sdk.responses.ChargeResponse import ru.tinkoff.acquiring.sdk.responses.Check3dsVersionResponse +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsAppBasedTransaction import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper -import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper.cleanupSafe import ru.tinkoff.acquiring.sdk.utils.CoroutineManager import ru.tinkoff.acquiring.sdk.utils.getIpAddress -import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper -import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.closeSafe /** * Позволяет создавать и управлять процессом оплаты @@ -102,7 +97,12 @@ internal constructor( paymentOptions.validateRequiredFields() this.paymentSource = paymentSource - this.initRequest = sdk.init { configure(paymentOptions) } + this.initRequest = sdk.init { + configure(paymentOptions) + if (paymentOptions.features.duplicateEmailToReceipt && !email.isNullOrEmpty()) { + receipt?.email = email + } + } this.paymentType = CardPaymentType this.email = email this.sdkState = paymentOptions.asdkState @@ -304,68 +304,31 @@ internal constructor( coroutine.launchOnBackground { val threeDsVersion = response.version - var threeDSWrapper: ThreeDSWrapper? = null - var threeDsTransaction: Transaction? = null + var threeDsTransaction: ThreeDsAppBasedTransaction? = null if (ThreeDsHelper.isAppBasedFlow(threeDsVersion)) { - threeDSWrapper = ThreeDsHelper.initWrapper(context) - threeDsTransaction = handleThreeDsAppBased( - threeDSWrapper, threeDsVersion!!, response.paymentSystem!!, data) ?: return@launchOnBackground + try { + threeDsTransaction = ThreeDsHelper.CreateAppBasedTransaction( + context, threeDsVersion!!, response.paymentSystem!!, data) + } catch (e: Throwable) { + handleException(e) + return@launchOnBackground + } } callFinishAuthorizeRequest(paymentId, paymentSource, email, data, - threeDsVersion, threeDSWrapper, threeDsTransaction) + threeDsVersion, threeDsTransaction) } }) } - private fun handleThreeDsAppBased( - threeDSWrapper: ThreeDSWrapper, - threeDsVersion: String, - paymentSystem: String, - data: MutableMap - ): Transaction? { - val dsId = ThreeDsHelper.getDsId(paymentSystem) - if (dsId == null) { - threeDSWrapper.cleanupSafe(context) - handleException(AcquiringSdkException(IllegalArgumentException( - "Directory server ID for payment system \"$paymentSystem\" can't be found"))) - return null - } - var transaction: Transaction? = null - - try { - transaction = threeDSWrapper.createTransaction(dsId, threeDsVersion) - val authParams = transaction.authenticationRequestParameters - - data["sdkAppID"] = authParams.sdkAppID - data["sdkEncData"] = Base64.encodeToString( - authParams.deviceData.toByteArray(Charsets.UTF_8), Base64.NO_WRAP) - data["sdkEphemPubKey"] = Base64.encodeToString( - authParams.sdkEphemeralPublicKey.toByteArray(Charsets.UTF_8), Base64.NO_WRAP) - data["sdkMaxTimeout"] = ThreeDsHelper.maxTimeout.toString() - data["sdkReferenceNumber"] = authParams.sdkReferenceNumber - data["sdkTransID"] = authParams.sdkTransactionID - data["sdkInterface"] = "03" - data["sdkUiType"] = "01,02,03,04,05" - - } catch (e: Throwable) { - transaction?.closeSafe() - threeDSWrapper.cleanupSafe(context) - handleException(e) - return null - } - return transaction - } - private fun callFinishAuthorizeRequest( paymentId: Long, paymentSource: PaymentSource, email: String? = null, data: Map? = null, threeDsVersion: String? = null, - threeDSWrapper: ThreeDSWrapper? = null, - threeDsTransaction: Transaction? = null + threeDsTransaction: ThreeDsAppBasedTransaction? = null ) { val ipAddress = if (data != null) getIpAddress() else null @@ -384,7 +347,7 @@ internal constructor( val cardId = if (paymentSource is AttachedCard) paymentSource.cardId else null if (threeDsData.isThreeDsNeed) { - sdkState = ThreeDsState(threeDsData, threeDSWrapper, threeDsTransaction) + sdkState = ThreeDsState(threeDsData, threeDsTransaction) sendToListener(PaymentState.THREE_DS_NEEDED) } else { paymentResult = PaymentResult(response.paymentId, cardId, response.rebillId) diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/threeds/ThreeDsHelper.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/threeds/ThreeDsHelper.kt index 02ee4bf3..d64fa662 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/threeds/ThreeDsHelper.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/threeds/ThreeDsHelper.kt @@ -1,21 +1,44 @@ package ru.tinkoff.acquiring.sdk.threeds +import android.app.Activity import android.content.Context +import android.graphics.Point +import android.view.WindowManager +import android.webkit.WebView +import com.emvco3ds.sdk.spec.CompletionEvent +import com.emvco3ds.sdk.spec.ProtocolErrorEvent +import com.emvco3ds.sdk.spec.RuntimeErrorEvent +import com.emvco3ds.sdk.spec.Transaction import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.json.JSONObject import ru.tinkoff.acquiring.sdk.AcquiringSdk +import ru.tinkoff.acquiring.sdk.exceptions.AcquiringSdkException import ru.tinkoff.acquiring.sdk.localization.AsdkLocalization +import ru.tinkoff.acquiring.sdk.models.ThreeDsData +import ru.tinkoff.acquiring.sdk.models.options.screen.BaseAcquiringOptions +import ru.tinkoff.acquiring.sdk.network.AcquiringApi +import ru.tinkoff.acquiring.sdk.network.AcquiringApi.COMPLETE_3DS_METHOD_V2 +import ru.tinkoff.acquiring.sdk.responses.Check3dsVersionResponse import ru.tinkoff.acquiring.sdk.threeds.ThreeDsCertInfo.CertType.Companion.toWrapperType +import ru.tinkoff.acquiring.sdk.ui.activities.ThreeDsActivity +import ru.tinkoff.acquiring.sdk.utils.Base64 +import ru.tinkoff.acquiring.sdk.utils.getTimeZoneOffsetInMinutes +import ru.tinkoff.core.components.threedswrapper.ChallengeStatusReceiverAdapter import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.cancelButtonCustomization +import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.closeSafe import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.setSdkAppId import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.submitButtonCustomization import ru.tinkoff.core.components.threedswrapper.ThreeDSWrapper.Companion.toolbarCustomization +import java.net.URLEncoder +import java.util.Locale import java.util.UUID import java.util.concurrent.TimeUnit +import kotlin.jvm.Throws object ThreeDsHelper { @@ -58,38 +81,10 @@ object ThreeDsHelper { internal var threeDsStatus: ThreeDsStatus? = null - suspend fun initWrapper(context: Context): ThreeDSWrapper { - val localisation = if (AsdkLocalization.isInitialized()) AsdkLocalization.resources else null - - val wrapper = ThreeDSWrapper(when (AcquiringSdk.isDeveloperMode) { - true -> ThreeDSWrapper.EmbeddedCertsInfo.TEST - else -> ThreeDSWrapper.EmbeddedCertsInfo.PROD - }).init(context, ThreeDSWrapper.newConfigParameters { - setSdkAppId(getSdkAppId(context).toString()) - }, null, ThreeDSWrapper.newUiCustomization { - toolbarCustomization { - headerText = localisation?.threeDsConfirmation ?: "Confirmation" - buttonText = localisation?.threeDsCancel ?: "Cancel" - backgroundColor = "#888888" - } - submitButtonCustomization { - backgroundColor = "#ffdd2d" - } - cancelButtonCustomization { - textColor = "#ffffff" - } - }) - val config = updateCertsConfigIfNeeded() - wrapper.updateCertsIfNeeded(config) - config?.certsInfo?.let { psToDsIdMap = it.mapPsToDsId() } - config?.certCheckInterval?.toLongOrNull()?.let { certConfigUpdateInterval = it } - return wrapper - } - fun getDsId(paymentSystem: String): String? = psToDsIdMap[paymentSystem] fun isAppBasedFlow(threeDsVersion: String?) = when (threeDsVersion) { -// "2.1.0" -> true todo uncomment then app-based 3DS is fixed +// "2.1.0" -> true // todo uncomment then app-based 3DS is fixed else -> false } @@ -157,6 +152,226 @@ object ThreeDsHelper { else -> CERTS_CONFIG_URL_PROD } + object CollectData { + + private const val THREE_DS_CALLED_FLAG = "Y" + private const val THREE_DS_NOT_CALLED_FLAG = "N" + + private val NOTIFICATION_URL = "${AcquiringApi.getUrl(COMPLETE_3DS_METHOD_V2)}/$COMPLETE_3DS_METHOD_V2" + private val TERM_URL_V2 = ThreeDsActivity.TERM_URL_V2 + + operator fun invoke(context: Context, response: Check3dsVersionResponse): MutableMap { + var threeDSCompInd = THREE_DS_NOT_CALLED_FLAG + if (response.threeDsMethodUrl != null) { + val hiddenWebView = WebView(context) + + val threeDsMethodData = JSONObject().apply { + put("threeDSMethodNotificationURL", NOTIFICATION_URL) + put("threeDSServerTransID", response.serverTransId) + } + + val dataBase64 = Base64.encodeToString(threeDsMethodData.toString().toByteArray(), Base64.NO_PADDING).trim() + val params = "threeDSMethodData=${URLEncoder.encode(dataBase64, "UTF-8")}" + + hiddenWebView.postUrl(response.threeDsMethodUrl!!, params.toByteArray()) + threeDSCompInd = THREE_DS_CALLED_FLAG + } + + val display = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay + val point = Point() + display.getSize(point) + + return mutableMapOf().apply { + put("threeDSCompInd", threeDSCompInd) + put("language", Locale.getDefault().toString().replace("_", "-")) + put("timezone", getTimeZoneOffsetInMinutes()) + put("screen_height", "${point.y}") + put("screen_width", "${point.x}") + put("cresCallbackUrl", TERM_URL_V2) + } + } + } + + object CreateAppBasedTransaction { + + @Throws(Throwable::class) + suspend operator fun invoke( + context: Context, + threeDsVersion: String, + paymentSystem: String, + data: MutableMap + ): ThreeDsAppBasedTransaction { + val wrapper = initWrapper(context) + val transaction = initTransaction(context, wrapper, threeDsVersion, paymentSystem, data) + return ThreeDsAppBasedTransaction(wrapper, transaction) + } + + suspend fun initWrapper(context: Context): ThreeDSWrapper { + val localisation = if (AsdkLocalization.isInitialized()) AsdkLocalization.resources else null + + val wrapper = ThreeDSWrapper(when (AcquiringSdk.isDeveloperMode) { + true -> ThreeDSWrapper.EmbeddedCertsInfo.TEST + else -> ThreeDSWrapper.EmbeddedCertsInfo.PROD + }).init(context, ThreeDSWrapper.newConfigParameters { + setSdkAppId(getSdkAppId(context).toString()) + }, null, ThreeDSWrapper.newUiCustomization { + toolbarCustomization { + headerText = localisation?.threeDsConfirmation ?: "Confirmation" + buttonText = localisation?.threeDsCancel ?: "Cancel" + backgroundColor = "#888888" + } + submitButtonCustomization { + backgroundColor = "#ffdd2d" + } + cancelButtonCustomization { + textColor = "#ffffff" + } + }) + val config = updateCertsConfigIfNeeded() + wrapper.updateCertsIfNeeded(config) + config?.certsInfo?.let { psToDsIdMap = it.mapPsToDsId() } + config?.certCheckInterval?.toLongOrNull()?.let { certConfigUpdateInterval = it } + return wrapper + } + + @Throws(Throwable::class) + fun initTransaction( + context: Context, + threeDSWrapper: ThreeDSWrapper, + threeDsVersion: String, + paymentSystem: String, + data: MutableMap + ): Transaction { + val dsId = getDsId(paymentSystem) + if (dsId == null) { + threeDSWrapper.cleanupSafe(context) + throw AcquiringSdkException(IllegalArgumentException( + "Directory server ID for payment system \"$paymentSystem\" can't be found")) + } + var transaction: Transaction? = null + + try { + transaction = threeDSWrapper.createTransaction(dsId, threeDsVersion) + val authParams = transaction.authenticationRequestParameters + + data["sdkAppID"] = authParams.sdkAppID + data["sdkEncData"] = android.util.Base64.encodeToString( + authParams.deviceData.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP) + data["sdkEphemPubKey"] = android.util.Base64.encodeToString( + authParams.sdkEphemeralPublicKey.toByteArray(Charsets.UTF_8), android.util.Base64.NO_WRAP) + data["sdkMaxTimeout"] = ThreeDsHelper.maxTimeout.toString() + data["sdkReferenceNumber"] = authParams.sdkReferenceNumber + data["sdkTransID"] = authParams.sdkTransactionID + data["sdkInterface"] = "03" + data["sdkUiType"] = "01,02,03,04,05" + + } catch (e: Throwable) { + transaction?.closeSafe() + threeDSWrapper.cleanupSafe(context) + throw e + } + return transaction + } + } + + object Launch { + + const val RESULT_DATA = "result_data" + const val ERROR_DATA = "result_error" + const val RESULT_ERROR = 564 + + @Throws(Throwable::class) + suspend operator fun invoke( + activity: Activity, + requestCode: Int, + options: BaseAcquiringOptions?, + threeDsData: ThreeDsData, + appBasedTransaction: ThreeDsAppBasedTransaction? + ) { + if (isAppBasedFlow(threeDsData.version)) { + launchAppBased(activity, threeDsData, appBasedTransaction!!) + } else { + launchBrowserBased(activity, requestCode, options!!, threeDsData) + } + } + + @Throws(Throwable::class) + suspend fun launchAppBased( + activity: Activity, + threeDsData: ThreeDsData, + appBasedTransaction: ThreeDsAppBasedTransaction + ) { + val wrapper = appBasedTransaction.wrapper + val transaction = appBasedTransaction.transaction + + val challengeParameters = ThreeDSWrapper.newChallengeParameters { + set3DSServerTransactionID(threeDsData.tdsServerTransId) + acsTransactionID = threeDsData.acsTransId + acsRefNumber = threeDsData.acsRefNumber + acsSignedContent = threeDsData.acsSignedContent + } + val progressDialog = try { + transaction.getProgressView(activity) + } catch (e: Throwable) { + wrapper.cleanupSafe(activity) + transaction.closeSafe() + throw e + } + withContext(Dispatchers.IO) { + transaction.doChallenge(activity, challengeParameters, + object : ChallengeStatusReceiverAdapter(transaction, progressDialog) { + override fun completed(event: CompletionEvent?) { + super.completed(event) + wrapper.cleanupSafe(activity) + threeDsStatus = ThreeDsStatusSuccess(threeDsData, event!!.transactionStatus) + } + + override fun cancelled() { + super.cancelled() + wrapper.cleanupSafe(activity) + threeDsStatus = ThreeDsStatusCanceled() + } + + override fun timedout() { + super.timedout() + wrapper.cleanupSafe(activity) + val error = RuntimeException("3DS SDK transaction timeout") + threeDsStatus = ThreeDsStatusError(error) + } + + override fun protocolError(event: ProtocolErrorEvent?) { + super.protocolError(event) + wrapper.cleanupSafe(activity) + val error = RuntimeException("3DS SDK protocol error: sdkTransactionID - ${event?.sdkTransactionID}, message - ${event?.errorMessage}") + threeDsStatus = ThreeDsStatusError(error) + } + + override fun runtimeError(event: RuntimeErrorEvent?) { + super.runtimeError(event) + wrapper.cleanupSafe(activity) + val error = RuntimeException("3DS SDK runtime error: code - ${event?.errorCode}, message - ${event?.errorMessage}") + threeDsStatus = ThreeDsStatusError(error) + } + }, maxTimeout) + } + } + + fun launchBrowserBased( + activity: Activity, + requestCode: Int, + options: BaseAcquiringOptions, + threeDsData: ThreeDsData + ) { + val intent = ThreeDsActivity.createIntent(activity, options, threeDsData) + activity.startActivityForResult(intent, requestCode) + } + } + + fun checkoutTransactionStatus(action: (status: ThreeDsStatus?) -> Unit) { + action(threeDsStatus) + threeDsStatus = null + } + fun ThreeDSWrapper.cleanupSafe(context: Context) { if (isInitialized()) { try { @@ -166,4 +381,9 @@ object ThreeDsHelper { } } } -} \ No newline at end of file +} + +class ThreeDsAppBasedTransaction( + val wrapper: ThreeDSWrapper, + val transaction: Transaction +) \ No newline at end of file diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/AttachCardActivity.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/AttachCardActivity.kt index 4129cad7..7bd555f3 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/AttachCardActivity.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/AttachCardActivity.kt @@ -28,6 +28,7 @@ import ru.tinkoff.acquiring.sdk.models.ScreenState import ru.tinkoff.acquiring.sdk.models.SingleEvent import ru.tinkoff.acquiring.sdk.models.ThreeDsScreenState import ru.tinkoff.acquiring.sdk.models.options.screen.AttachCardOptions +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper import ru.tinkoff.acquiring.sdk.ui.fragments.AttachCardFragment import ru.tinkoff.acquiring.sdk.ui.fragments.LoopConfirmationFragment import ru.tinkoff.acquiring.sdk.viewmodel.AttachCardViewModel @@ -80,7 +81,14 @@ internal class AttachCardActivity : TransparentActivity() { private fun handleScreenChangeEvent(screenChangeEvent: SingleEvent) { screenChangeEvent.getValueIfNotHandled()?.let { screen -> when (screen) { - is ThreeDsScreenState -> openThreeDs(screen) + is ThreeDsScreenState -> attachCardViewModel.launchOnMain { + try { + ThreeDsHelper.Launch(this@AttachCardActivity, + THREE_DS_REQUEST_CODE, options, screen.data, screen.transaction) + } catch (e: Throwable) { + finishWithError(e) + } + } is LoopConfirmationScreenState -> showFragment(LoopConfirmationFragment.newInstance(screen.requestKey)) } } diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/PaymentActivity.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/PaymentActivity.kt index a4617209..a0917ec6 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/PaymentActivity.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/PaymentActivity.kt @@ -49,6 +49,7 @@ import ru.tinkoff.acquiring.sdk.models.SingleEvent import ru.tinkoff.acquiring.sdk.models.ThreeDsDataCollectScreenState import ru.tinkoff.acquiring.sdk.models.ThreeDsScreenState import ru.tinkoff.acquiring.sdk.models.options.screen.PaymentOptions +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper import ru.tinkoff.acquiring.sdk.ui.customview.NotificationDialog import ru.tinkoff.acquiring.sdk.ui.fragments.PaymentFragment import ru.tinkoff.acquiring.sdk.viewmodel.PaymentViewModel @@ -174,9 +175,16 @@ internal class PaymentActivity : TransparentActivity() { val state = RejectedState(screen.cardId, screen.rejectedPaymentId) showFragment(PaymentFragment.newInstance(paymentOptions.customer.customerKey, state)) } - is ThreeDsScreenState -> openThreeDs(screen) + is ThreeDsScreenState -> paymentViewModel.launchOnMain { + try { + ThreeDsHelper.Launch(this@PaymentActivity, + THREE_DS_REQUEST_CODE, options, screen.data, screen.transaction) + } catch (e: Throwable) { + finishWithError(e) + } + } is ThreeDsDataCollectScreenState -> { - paymentViewModel.collectedDeviceData = ThreeDsActivity.collectData(this, screen.response) + paymentViewModel.collectedDeviceData = ThreeDsHelper.CollectData(this, screen.response) } is BrowseFpsBankScreenState -> openBankChooser(screen.deepLink, screen.banks) is FpsScreenState -> paymentViewModel.startFpsPayment(paymentOptions) diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/ThreeDsActivity.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/ThreeDsActivity.kt index 962e79b0..189301c8 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/ThreeDsActivity.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/ThreeDsActivity.kt @@ -20,10 +20,8 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.Point import android.os.Bundle import android.view.View -import android.view.WindowManager import android.webkit.WebView import android.webkit.WebViewClient import androidx.lifecycle.Observer @@ -37,15 +35,12 @@ import ru.tinkoff.acquiring.sdk.models.ThreeDsData import ru.tinkoff.acquiring.sdk.models.options.screen.BaseAcquiringOptions import ru.tinkoff.acquiring.sdk.models.result.AsdkResult import ru.tinkoff.acquiring.sdk.network.AcquiringApi -import ru.tinkoff.acquiring.sdk.network.AcquiringApi.COMPLETE_3DS_METHOD_V2 import ru.tinkoff.acquiring.sdk.network.AcquiringApi.SUBMIT_3DS_AUTHORIZATION import ru.tinkoff.acquiring.sdk.network.AcquiringApi.SUBMIT_3DS_AUTHORIZATION_V2 -import ru.tinkoff.acquiring.sdk.responses.Check3dsVersionResponse +import ru.tinkoff.acquiring.sdk.threeds.ThreeDsHelper import ru.tinkoff.acquiring.sdk.utils.Base64 -import ru.tinkoff.acquiring.sdk.utils.getTimeZoneOffsetInMinutes import ru.tinkoff.acquiring.sdk.viewmodel.ThreeDsViewModel import java.net.URLEncoder -import java.util.* internal class ThreeDsActivity : BaseAcquiringActivity() { @@ -57,62 +52,24 @@ internal class ThreeDsActivity : BaseAcquiringActivity() { companion object { - const val RESULT_DATA = "result_data" - const val ERROR_DATA = "result_error" - const val RESULT_ERROR = 564 - const val THREE_DS_DATA = "three_ds_data" - private const val OPTIONS = "options" - - private const val THREE_DS_CALLED_FLAG = "Y" - private const val THREE_DS_NOT_CALLED_FLAG = "N" private const val WINDOW_SIZE_CODE = "05" private const val MESSAGE_TYPE = "CReq" private val TERM_URL = "${AcquiringApi.getUrl(SUBMIT_3DS_AUTHORIZATION)}/$SUBMIT_3DS_AUTHORIZATION" - private val TERM_URL_V2 = "${AcquiringApi.getUrl(SUBMIT_3DS_AUTHORIZATION_V2)}/$SUBMIT_3DS_AUTHORIZATION_V2" - private val NOTIFICATION_URL = "${AcquiringApi.getUrl(COMPLETE_3DS_METHOD_V2)}/$COMPLETE_3DS_METHOD_V2" + val TERM_URL_V2 = "${AcquiringApi.getUrl(SUBMIT_3DS_AUTHORIZATION_V2)}/$SUBMIT_3DS_AUTHORIZATION_V2" private val cancelActions = arrayOf("cancel.do", "cancel=true") fun createIntent(context: Context, options: BaseAcquiringOptions, data: ThreeDsData): Intent { val intent = Intent(context, ThreeDsActivity::class.java) intent.putExtra(THREE_DS_DATA, data) - intent.putExtra(OPTIONS, options) + intent.putExtras(Bundle().apply { + putParcelable(EXTRA_OPTIONS, options) + }) return intent } - - fun collectData(context: Context, response: Check3dsVersionResponse): MutableMap { - var threeDSCompInd = THREE_DS_NOT_CALLED_FLAG - if (response.threeDsMethodUrl != null) { - val hiddenWebView = WebView(context) - - val threeDsMethodData = JSONObject().apply { - put("threeDSMethodNotificationURL", NOTIFICATION_URL) - put("threeDSServerTransID", response.serverTransId) - } - - val dataBase64 = Base64.encodeToString(threeDsMethodData.toString().toByteArray(), Base64.NO_PADDING).trim() - val params = "threeDSMethodData=${URLEncoder.encode(dataBase64, "UTF-8")}" - - hiddenWebView.postUrl(response.threeDsMethodUrl!!, params.toByteArray()) - threeDSCompInd = THREE_DS_CALLED_FLAG - } - - val display = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay - val point = Point() - display.getSize(point) - - return mutableMapOf().apply { - put("threeDSCompInd", threeDSCompInd) - put("language", Locale.getDefault().toString().replace("_", "-")) - put("timezone", getTimeZoneOffsetInMinutes()) - put("screen_height", "${point.y}") - put("screen_width", "${point.x}") - put("cresCallbackUrl", TERM_URL_V2) - } - } } @SuppressLint("SetJavaScriptEnabled") @@ -141,14 +98,14 @@ internal class ThreeDsActivity : BaseAcquiringActivity() { override fun setSuccessResult(result: AsdkResult) { val intent = Intent() - intent.putExtra(RESULT_DATA, result) + intent.putExtra(ThreeDsHelper.Launch.RESULT_DATA, result) setResult(Activity.RESULT_OK, intent) } override fun setErrorResult(throwable: Throwable) { val intent = Intent() - intent.putExtra(ERROR_DATA, throwable) - setResult(RESULT_ERROR, intent) + intent.putExtra(ThreeDsHelper.Launch.ERROR_DATA, throwable) + setResult(ThreeDsHelper.Launch.RESULT_ERROR, intent) } private fun observeLiveData() { diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/TransparentActivity.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/TransparentActivity.kt index a179ca61..689070c6 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/TransparentActivity.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/ui/activities/TransparentActivity.kt @@ -89,21 +89,22 @@ internal open class TransparentActivity : BaseAcquiringActivity() { override fun onResume() { super.onResume() - when (val threeDsStatus = ThreeDsHelper.threeDsStatus) { - is ThreeDsStatusSuccess -> threeDsViewModel.submitAuthorization(threeDsStatus.threeDsData, threeDsStatus.transStatus) - is ThreeDsStatusCanceled -> finishWithCancel() - is ThreeDsStatusError -> finishWithError(threeDsStatus.error) - else -> Unit + ThreeDsHelper.checkoutTransactionStatus { status -> + when (status) { + is ThreeDsStatusSuccess -> threeDsViewModel.submitAuthorization(status.threeDsData, status.transStatus) + is ThreeDsStatusCanceled -> finishWithCancel() + is ThreeDsStatusError -> finishWithError(status.error) + else -> Unit + } } - ThreeDsHelper.threeDsStatus = null } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == THREE_DS_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { - finishWithSuccess(data.getSerializableExtra(ThreeDsActivity.RESULT_DATA) as AsdkResult) - } else if (resultCode == ThreeDsActivity.RESULT_ERROR) { - finishWithError(data?.getSerializableExtra(ThreeDsActivity.ERROR_DATA) as Throwable) + finishWithSuccess(data.getSerializableExtra(ThreeDsHelper.Launch.RESULT_DATA) as AsdkResult) + } else if (resultCode == ThreeDsHelper.Launch.RESULT_ERROR) { + finishWithError(data?.getSerializableExtra(ThreeDsHelper.Launch.ERROR_DATA) as Throwable) } else { setResult(Activity.RESULT_CANCELED) closeActivity() @@ -198,17 +199,6 @@ internal open class TransparentActivity : BaseAcquiringActivity() { bottomContainer.showInitAnimation = showBottomView } - protected fun openThreeDs(screenState: ThreeDsScreenState) { - val threeDsData = screenState.data - if (ThreeDsHelper.isAppBasedFlow(threeDsData.version)) { - threeDsViewModel.launchThreeDsAppBased(this, - screenState.data, screenState.wrapper!!, screenState.transaction!!) - } else { - val intent = ThreeDsActivity.createIntent(this, options, threeDsData) - startActivityForResult(intent, THREE_DS_REQUEST_CODE) - } - } - private fun setupTranslucentStatusBar() { if (Build.VERSION.SDK_INT in 19..20) { window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) @@ -238,7 +228,7 @@ internal open class TransparentActivity : BaseAcquiringActivity() { private const val FULL_SCREEN_INDEX = 0 private const val EXPANDED_INDEX = 1 - private const val THREE_DS_REQUEST_CODE = 143 + internal const val THREE_DS_REQUEST_CODE = 143 private const val STATE_SHOW_BOTTOM = "state_show_bottom" } diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/AttachCardViewModel.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/AttachCardViewModel.kt index 2a36add4..2a4af33a 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/AttachCardViewModel.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/AttachCardViewModel.kt @@ -91,7 +91,7 @@ internal class AttachCardViewModel( coroutine.call(attachCardRequest, onSuccess = { when (it.status) { - ResponseStatus.THREE_DS_CHECKING -> changeScreenState(ThreeDsScreenState(it.getThreeDsData(), null, null)) + ResponseStatus.THREE_DS_CHECKING -> changeScreenState(ThreeDsScreenState(it.getThreeDsData(), null)) ResponseStatus.LOOP_CHECKING -> changeScreenState(LoopConfirmationScreenState(it.requestKey!!)) null -> attachCardResult.value = CardResult(it.cardId) else -> { diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/BaseAcquiringViewModel.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/BaseAcquiringViewModel.kt index ff9459aa..6cea5b74 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/BaseAcquiringViewModel.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/BaseAcquiringViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import kotlinx.coroutines.CoroutineScope import ru.tinkoff.acquiring.sdk.AcquiringSdk import ru.tinkoff.acquiring.sdk.exceptions.AcquiringApiException import ru.tinkoff.acquiring.sdk.exceptions.NetworkException @@ -93,4 +94,12 @@ internal open class BaseAcquiringViewModel( fallbackMessage } } + + fun launchOnMain(block: suspend CoroutineScope.() -> Unit) { + coroutine.launchOnMain(block) + } + + fun launchOnBackground(block: suspend CoroutineScope.() -> Unit) { + coroutine.launchOnBackground(block) + } } \ No newline at end of file diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/PaymentViewModel.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/PaymentViewModel.kt index a7e75690..cf86f9d6 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/PaymentViewModel.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/PaymentViewModel.kt @@ -83,7 +83,7 @@ internal class PaymentViewModel( fun checkoutAsdkState(state: AsdkState) { when (state) { - is ThreeDsState -> changeScreenState(ThreeDsScreenState(state.data, state.threeDSWrapper, state.transaction)) + is ThreeDsState -> changeScreenState(ThreeDsScreenState(state.data, state.transaction)) is RejectedState -> changeScreenState(RejectedCardScreenState(state.cardId, state.rejectedPaymentId)) is BrowseFpsBankState -> changeScreenState(BrowseFpsBankScreenState(state.paymentId, state.deepLink, state.banks)) is FpsState -> changeScreenState(FpsScreenState) @@ -226,7 +226,7 @@ internal class PaymentViewModel( override fun onUiNeeded(state: AsdkState) { when (state) { is ThreeDsState -> { - changeScreenState(ThreeDsScreenState(state.data, state.threeDSWrapper, state.transaction)) + changeScreenState(ThreeDsScreenState(state.data, state.transaction)) coroutine.runWithDelay(500) { changeScreenState(LoadedState) } diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ThreeDsViewModel.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ThreeDsViewModel.kt index f6dbac98..b9987498 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ThreeDsViewModel.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ThreeDsViewModel.kt @@ -51,60 +51,6 @@ internal class ThreeDsViewModel( private val asdkResult: MutableLiveData = MutableLiveData() val resultLiveData: LiveData = asdkResult - fun launchThreeDsAppBased(activity: BaseAcquiringActivity, threeDsData: ThreeDsData, - wrapper: ThreeDSWrapper, transaction: Transaction) { - val challengeParameters = ThreeDSWrapper.newChallengeParameters { - set3DSServerTransactionID(threeDsData.tdsServerTransId) - acsTransactionID = threeDsData.acsTransId - acsRefNumber = threeDsData.acsRefNumber - acsSignedContent = threeDsData.acsSignedContent - } - val progressDialog = try { - transaction.getProgressView(activity) - } catch (e: Throwable) { - transaction.closeSafe() - activity.finishWithError(e) - return - } - coroutine.doOnBackground { - transaction.doChallenge(activity, challengeParameters, - object : ChallengeStatusReceiverAdapter(transaction, progressDialog) { - override fun completed(event: CompletionEvent?) { - super.completed(event) - wrapper.cleanupSafe(activity) - ThreeDsHelper.threeDsStatus = ThreeDsStatusSuccess(threeDsData, event!!.transactionStatus) - } - - override fun cancelled() { - super.cancelled() - wrapper.cleanupSafe(activity) - ThreeDsHelper.threeDsStatus = ThreeDsStatusCanceled() - } - - override fun timedout() { - super.timedout() - wrapper.cleanupSafe(activity) - val error = RuntimeException("3DS SDK transaction timeout") - ThreeDsHelper.threeDsStatus = ThreeDsStatusError(error) - } - - override fun protocolError(event: ProtocolErrorEvent?) { - super.protocolError(event) - wrapper.cleanupSafe(activity) - val error = RuntimeException("3DS SDK protocol error: sdkTransactionID - ${event?.sdkTransactionID}, message - ${event?.errorMessage}") - ThreeDsHelper.threeDsStatus = ThreeDsStatusError(error) - } - - override fun runtimeError(event: RuntimeErrorEvent?) { - super.runtimeError(event) - wrapper.cleanupSafe(context) - val error = RuntimeException("3DS SDK runtime error: code - ${event?.errorCode}, message - ${event?.errorMessage}") - ThreeDsHelper.threeDsStatus = ThreeDsStatusError(error) - } - }, ThreeDsHelper.maxTimeout) - } - } - fun submitAuthorization(threeDsData: ThreeDsData, transStatus: String) { changeScreenState(LoadingState) diff --git a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ViewModelProviderFactory.kt b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ViewModelProviderFactory.kt index 110ec9ad..1e6292d2 100644 --- a/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ViewModelProviderFactory.kt +++ b/ui/src/main/java/ru/tinkoff/acquiring/sdk/viewmodel/ViewModelProviderFactory.kt @@ -38,7 +38,7 @@ internal class ViewModelProviderFactory( NotificationPaymentViewModel::class.java to NotificationPaymentViewModel(application, handleErrorsInSdk, sdk)) @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { + override fun create(modelClass: Class): T { return viewModelCollection[modelClass] as T } } \ No newline at end of file