Payment Initiation
Good to know: payment initiation is responsible for bank and card payment support.
For initialising the payment initiation flow, you will need to get the payment id and pass it to the SDK. The description of how to get a payment id can be found here.

Customise session configuration

Firstly, you will need to integrate kevin. API and fetch payment ID. Instructions on how to do it can be found here:
PaymentSessionConfiguration has multiple settings to tackle:
  • setPaymentType - sets payment to be BANK or CARD. The payment needs to be initialised with the corresponding type. See reference here.
  • setPreselectedCountry - preselects country in our prebuilt UI
  • setDisableCountrySelection - uses only preselected country and does not allow to change it
  • setCountryFilter - allows only certain countries to be supported
  • setBankFilter - 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 UI
  • setSkipBankSelection - skips bank and country selection and redirects user to the bank SCA immediately
  • setSkipAuthentication - allows users to skip the SCA bank step and proceed directly to the payment confirmation. Bank payment needs to be initialised with an account access token. See reference here.
Example:
1
// initialize configuration with paymentId fetched from the API
2
val config = PaymentSessionConfiguration.Builder(paymentId)
3
.setPaymentType(PaymentType.BANK)
4
.setPreselectedCountry(KevinCountry.LITHUANIA)
5
.setCountryFilter(
6
listOf(
7
KevinCountry.LITHUANIA,
8
KevinCountry.ESTONIA,
9
KevinCountry.LATVIA
10
)
11
)
12
.setBankFilter(
13
listOf(
14
"SWEDBANK_LT",
15
"SEB_LT",
16
"LUMINOR_LT",
17
"REVOLUT_LT"
18
)
19
)
20
.setPreselectedBank("SOME_BANK_ID")
21
.build()
22
linkAccount.launch(config)
Copied!

Initiate payment callback

Fragment or Activity

If you use intend to call account linking from Activity or Fragment, registerForActivityResult can be used for that:
1
class SampleFragment : Fragment() {
2
3
private val viewModel: SampleViewModel by viewModels()
4
5
private val makePayment = registerForActivityResult(PaymentSessionContract()) {
6
when (result) {
7
is SessionResult.Success -> {
8
// get payment id by calling it.value.paymentId
9
}
10
is SessionResult.Canceled -> {
11
// do something on user cancellation
12
}
13
is SessionResult.Failure -> {
14
// do something on failure
15
}
16
}
17
}
18
19
override fun onCreateView(
20
inflater: LayoutInflater,
21
container: ViewGroup?,
22
savedInstanceState: Bundle?
23
): View {
24
observeChanges()
25
return FragmentMainBinding.inflate(inflater).apply {
26
makeBankPaymentButton.setDebounceClickListener {
27
// fetch payment id from kevin. API
28
viewModel.initializePayment()
29
}
30
}.root
31
}
32
33
private fun observeChanges() {
34
// observe returned payment here
35
lifecycleScope.launchWhenStarted {
36
viewModel.payment.onEach { payment ->
37
openPaymentSession(payment)
38
}.launchIn(this)
39
}
40
}
41
42
private fun openPaymentSession(payment: ApiPayment) {
43
val config = PaymentSessionConfiguration.Builder(payment.id)
44
.setPaymentType(PaymentType.BANK)
45
.setPreselectedCountry(KevinCountry.LITHUANIA)
46
.build()
47
makePayment.launch(config)
48
}
49
}
Copied!

Jetpack Compose

The same solution is available on Jetpack Compose:
1
@Composable
2
fun SampleWindow(viewModel: SampleViewModel) {
3
4
val paymentLauncher = rememberLauncherForActivityResult(PaymentSessionContract()) { result ->
5
when (result) {
6
is SessionResult.Success -> {
7
// get payment id by calling it.value.paymentId
8
}
9
is SessionResult.Canceled -> {
10
// do something on user cancellation
11
}
12
is SessionResult.Failure -> {
13
// do something on failure
14
}
15
}
16
}
17
18
LaunchedEffect(viewModel) {
19
viewModel.payment.onEach { payment ->
20
val config = PaymentSessionConfiguration.Builder(payment.id)
21
.setPaymentType(PaymentType.BANK)
22
.setPreselectedCountry(KevinCountry.LITHUANIA)
23
.build()
24
paymentLauncher.launch(config)
25
}.launchIn(this)
26
}
27
28
Scaffold {
29
Button(
30
text = "Make Payment",
31
onClick = {
32
// fetch payment id from kevin. API
33
viewModel.initializePayment()
34
}
35
)
36
}
37
}
Copied!

Custom solutions

For non Fragment, Activity or Jetpack Compose windows please use custom ActivityResultRegistry:
1
@Singleton
2
class PaymentObserver @Inject constructor() : DefaultLifecycleObserver {
3
4
private var callback: ((SessionResult<PaymentSessionResult>) -> Unit)? = null
5
private var registry : WeakReference<ActivityResultRegistry?> = WeakReference(null)
6
7
private lateinit var paymentLauncher: ActivityResultLauncher<PaymentSessionConfiguration>
8
9
override fun onCreate(owner: LifecycleOwner) {
10
super.onCreate(owner)
11
paymentLauncher = registry.get()!!.register("session", PaymentSessionContract()) {
12
callback?.invoke(it)
13
}
14
}
15
16
fun startPaymentInitiation(
17
configuration: PaymentSessionConfiguration,
18
callback: ((SessionResult<PaymentSessionResult>) -> Unit)? = null
19
) {
20
this.callback = callback
21
paymentLauncher.launch(configuration)
22
}
23
24
fun setRegistry(activityResultRegistry: ActivityResultRegistry) {
25
registry = WeakReference(activityResultRegistry)
26
}
27
28
fun disposeReferences() {
29
callback = null
30
registry = WeakReference(null)
31
}
32
}
Copied!
And in your root Activity listen to the newly created observer:
1
class SampleActivity : AppCompatActivity() {
2
3
@Inject lateinit var paymentObserver: PaymentObserver
4
5
override fun onCreate(savedInstanceState: Bundle?) {
6
super.onCreate(savedInstanceState)
7
with(paymentObserver) {
8
setRegistry(activityResultRegistry)
9
lifecycle.addObserver(this)
10
}
11
}
12
13
override fun onDestroy() {
14
super.onDestroy()
15
with(paymentObserver) {
16
disposeReferences()
17
lifecycle.removeObserver(this)
18
}
19
}
20
}
Copied!