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.
For initialising the account linking flow, you will need to get state and pass it to the SDK - a description on how to get state can be found here.
Firstly, you will need to integrate kevin. API and fetch account linking state. Instructions on how to do it can be found here:
AccountSessionConfiguration has multiple settings to tackle:
setLinkingType
- allows to choose if you want to link bank account or payment card. By default it will link bank accountsetPreselectedCountry
- preselects country in our prebuilt UIsetDisableCountrySelection
- uses only preselected country and does not allow to change itsetCountryFilter
- allows only certain countries to be supportedsetBankFilter
- allows to filter banks and show only selected ones to user. It accepts list of bank ids that should be show to user.setPreselectedBank
- preselects bank in our prebuilt UIsetSkipBankSelection
- skips bank and country selection and redirects user to the bank SCA immediatelysetAccountLinkingType
- sets linking type to bank or card. Default is bank.
Example:
// initialize configuration with state fetched from the API
val config = AccountSessionConfiguration.Builder("state")
.setLinkingType(AccountLinkingType.BANK)
.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)
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
}
is SessionResult.Canceled -> {
// do something on user cancellation
}
is SessionResult.Failure -> {
// do something on 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)
}
}
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
}
is SessionResult.Canceled -> {
// do something on user cancellation
}
is SessionResult.Failure -> {
// do something on 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()
}
)
}
}
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 modified 1mo ago