Account Linking

Good to know: account linking is useful if you wish to use AIS (Account Information Service) or skip authentication steps when initiating payments via the in-app payments module.

Initialize Account Linking

Firstly, you will need to integrate kevin. API and fetch account linking state.

Instructions on how to do it can be found here:

Authentication

Account Linking configuration

AccountSessionConfiguration can be configured with following properties:

  • setPreselectedCountry - allows to set a default country to be selected in the bank selection screen. If this parameter is not specified, the selection will be based on the SIM card information. In case the SIM card information is not available, the SDK will use the device locale. If the SIM card information and device locale are unavailable, the SDK will select the first country from the country filter (or fall back to the first bank on the list).

  • setPreselectedBank - allows to specify a default bank that will be preselected in the bank selection screen.

  • setCountryFilter - allows only certain countries to be supported.

  • setBankFilter - allows to filter banks and show only selected ones to the user. It requires a list of bank IDs that should be shown.

  • setDisableCountrySelection - if set to true, country selection will be disabled, and only preselected country banks will be available to choose from.

  • setSkipBankSelection - if set to true, and bank selection step will be skipped, and the user will be redirected to the bank SCA immediately.

Example:

// initialize configuration with state fetched from the API 
val config = AccountSessionConfiguration.Builder("state")
    .setPreselectedCountry(KevinCountry.LITHUANIA)
    .setCountryFilter(
         listOf(
             KevinCountry.LITHUANIA, 
             KevinCountry.ESTONIA, 
             KevinCountry.LATVIA
         )
    )
    .setBankFilter(
         listOf(
             "SWEDBANK_LT",
             "SEB_LT",
             "LUMINOR_LT",
             "REVOLUT_LT"
         )
    )
    .setPreselectedBank("SOME_BANK_ID") 
    .build()
linkAccount.launch(config)

Initialize Account Linking callback

Fragment or Activity

If you intend to call account linking from Activity or Fragment, registerForActivityResult can be used for that:

class SampleFragment : Fragment() {

    private val viewModel: SampleViewModel by viewModels()

    private val linkAccount = registerForActivityResult(AccountSessionContract()) {
        when (it) {
            is SessionResult.Success -> {
                // get linkingType by calling it.value.linkingType
                // get account token by calling it.value.authorizationCode
                // get bank info by calling it.value.bank
            }
            is SessionResult.Canceled -> {
                // handle user cancellation
            }
            is SessionResult.Failure -> {
                // handle linking session failure
            }
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, 
        container: ViewGroup?, 
        savedInstanceState: Bundle?
    ): View {
        observeChanges()
        return FragmentSampleBinding.inflate(inflater).apply {
            linkAccountButton.setDebounceClickListener {
                // fetch account linking state from kevin. API
                viewModel.initializeAccountLinking()
            }
        }.root
    }

    private fun observeChanges() {
        // observe returned state here
        lifecycleScope.launchWhenStarted {
            viewModel.state.onEach { state ->
                openAccountLinkingSession(state)
            }.launchIn(this)
        }
    }

    private fun openAccountLinkingSession(state: String) {
        val config = AccountSessionConfiguration.Builder(state)
            .setPreselectedCountry(KevinCountry.LITHUANIA)
            .build()
        linkAccount.launch(config)
    }
}

Jetpack Compose

The same solution is available on Jetpack Compose:

@Composable
fun SampleWindow(viewModel: SampleViewModel) {

    val linkAccount = rememberLauncherForActivityResult(AccountSessionContract()) { result ->
        when (it) {
            is SessionResult.Success -> {
                // get linkingType by calling it.value.linkingType
                // get account token by calling it.value.authorizationCode
                // get bank info by calling it.value.bank
            }
            is SessionResult.Canceled -> {
                // handle user cancellation
            }
            is SessionResult.Failure -> {
                // handle linking session failure
            }
        }
    }

    LaunchedEffect(viewModel) {
        viewModel.state.onEach { state ->
            val config = AccountSessionConfiguration.Builder(state)
                .setPreselectedCountry(KevinCountry.LITHUANIA)
                .build()
            linkAccount.launch(config)
        }.launchIn(this)
    }

    Scaffold {
        Button(
            text = "Link Account",
            onClick = { 
                // fetch account linking state from kevin. API
                viewModel.initializeAccountLinking() 
            }
        )
    }
}

Custom solutions

For non Fragment, Activity or Jetpack Compose windows please use custom ActivityResultRegistry:

@Singleton
class AccountLinkingObserver @Inject constructor() : DefaultLifecycleObserver {

    private var callback: ((SessionResult<AccountSessionResult>) -> Unit)? = null
    private var registry : WeakReference<ActivityResultRegistry?> = WeakReference(null)

    private lateinit var accountLauncher: ActivityResultLauncher<AccountSessionConfiguration>

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        accountLauncher = registry.get()!!.register("session", AccountSessionContract()) {
            callback?.invoke(it)
        }
    }

    fun startAccountLinking(
        configuration: AccountSessionConfiguration,
        callback: ((SessionResult<AccountSessionResult>) -> Unit)? = null
    ) {
        this.callback = callback
        accountLauncher.launch(configuration)
    }

    fun setRegistry(activityResultRegistry: ActivityResultRegistry) {
        registry = WeakReference(activityResultRegistry)
    }

    fun disposeReferences() {
        callback = null
        registry = WeakReference(null)
    }
}

And in your root Activity listen to the newly created observer:

class SampleActivity : AppCompatActivity() {

    @Inject lateinit var accountLinkingObserver: AccountLinkingObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        with(accountLinkingObserver) {
            setRegistry(activityResultRegistry)
            lifecycle.addObserver(this)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        with(accountLinkingObserver) {
            disposeReferences()
            lifecycle.removeObserver(this)
        }
    }
}

Last updated