Skip to content

Commit d297ae3

Browse files
feat(ad-hoc): Support full headless mode for nAPM redirects (#366)
1 parent d9f87f3 commit d297ae3

3 files changed

Lines changed: 58 additions & 38 deletions

File tree

ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentInteractor.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,8 @@ internal class NativeAlternativePaymentInteractor(
965965
)
966966
_state.update { Pending(pendingStateValue) }
967967
enablePendingSecondaryAction()
968-
if (pendingStateValue.elements.isNullOrEmpty() ||
968+
if (configuration.redirect?.enableHeadlessMode == true ||
969+
pendingStateValue.elements.isNullOrEmpty() ||
969970
configuration.paymentConfirmation.confirmButton == null
970971
) {
971972
capture()

ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentConfiguration.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,9 @@ data class PONativeAlternativePaymentConfiguration(
392392
*
393393
* @param[returnUrl] Deep link return URL. Required for the flows that include web redirect.
394394
* @param[enableHeadlessMode] Enables headless mode.
395-
* The web redirect will be handled directly when it's the first step in the flow,
396-
* and if it's the only required step it will complete the flow without starting the bottom sheet.
395+
* The redirect (web or deep link) will be handled directly when it's the first step in the flow, without starting the bottom sheet.
396+
* It will also capture the payment in the background when it's required by the flow.
397+
* __Note:__ use only with flows that do not require user input or instructions in the native UI.
397398
*/
398399
@Parcelize
399400
data class RedirectConfiguration(

ui/src/main/kotlin/com/processout/sdk/ui/napm/PONativeAlternativePaymentLauncher.kt

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.processout.sdk.ui.napm
22

33
import android.app.Application
4-
import android.content.Context
54
import androidx.activity.ComponentActivity
65
import androidx.activity.result.ActivityResultLauncher
6+
import androidx.activity.viewModels
77
import androidx.core.app.ActivityOptionsCompat
88
import androidx.core.net.toUri
99
import androidx.fragment.app.Fragment
10+
import androidx.lifecycle.Lifecycle
1011
import androidx.lifecycle.lifecycleScope
12+
import androidx.lifecycle.repeatOnLifecycle
1113
import com.processout.sdk.R
1214
import com.processout.sdk.api.ProcessOut
1315
import com.processout.sdk.api.dispatcher.POEventDispatcher
@@ -41,17 +43,35 @@ import kotlinx.coroutines.launch
4143
* Launcher that starts [NativeAlternativePaymentActivity] and provides the result.
4244
*/
4345
class PONativeAlternativePaymentLauncher private constructor(
44-
private val app: Application,
45-
private val scope: CoroutineScope,
46+
private val hostActivity: ComponentActivity,
4647
private val launcher: ActivityResultLauncher<PONativeAlternativePaymentConfiguration>,
47-
private val activityOptions: ActivityOptionsCompat,
4848
private val delegate: PONativeAlternativePaymentDelegate,
4949
private val callback: (ProcessOutActivityResult<POUnit>) -> Unit,
5050
private val eventDispatcher: POEventDispatcher = POEventDispatcher.instance,
5151
private val invoicesService: POInvoicesService = ProcessOut.instance.invoices,
5252
private val customerTokensService: POCustomerTokensService = ProcessOut.instance.customerTokens
5353
) {
5454

55+
private val app: Application = hostActivity.application
56+
private val scope: CoroutineScope = hostActivity.lifecycleScope
57+
58+
private val activityOptions = ActivityOptionsCompat.makeCustomAnimation(
59+
hostActivity, R.anim.po_slide_in_vertical, 0
60+
)
61+
62+
private val viewModel: NativeAlternativePaymentViewModel by hostActivity.viewModels {
63+
NativeAlternativePaymentViewModel.Factory(
64+
app = app,
65+
configuration = PONativeAlternativePaymentConfiguration(
66+
flow = Authorization(
67+
invoiceId = String(),
68+
gatewayConfigurationId = String()
69+
),
70+
header = null
71+
)
72+
)
73+
}
74+
5575
private lateinit var customTabLauncher: POAlternativePaymentMethodCustomTabLauncher
5676

5777
private object LocalCache {
@@ -68,13 +88,11 @@ class PONativeAlternativePaymentLauncher private constructor(
6888
delegate: PONativeAlternativePaymentDelegate,
6989
callback: (ProcessOutActivityResult<POUnit>) -> Unit
7090
) = PONativeAlternativePaymentLauncher(
71-
app = from.requireActivity().application,
72-
scope = from.lifecycleScope,
91+
hostActivity = from.requireActivity(),
7392
launcher = from.registerForActivityResult(
7493
NativeAlternativePaymentActivityContract(),
7594
callback
7695
),
77-
activityOptions = createActivityOptions(from.requireContext()),
7896
delegate = delegate,
7997
callback = callback
8098
).apply {
@@ -96,13 +114,11 @@ class PONativeAlternativePaymentLauncher private constructor(
96114
delegate: com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentDelegate,
97115
callback: (ProcessOutActivityResult<POUnit>) -> Unit
98116
) = PONativeAlternativePaymentLauncher(
99-
app = from.requireActivity().application,
100-
scope = from.lifecycleScope,
117+
hostActivity = from.requireActivity(),
101118
launcher = from.registerForActivityResult(
102119
NativeAlternativePaymentActivityContract(),
103120
callback
104121
),
105-
activityOptions = createActivityOptions(from.requireContext()),
106122
delegate = object : PONativeAlternativePaymentDelegate {},
107123
callback = callback
108124
).apply {
@@ -121,13 +137,11 @@ class PONativeAlternativePaymentLauncher private constructor(
121137
from: Fragment,
122138
callback: (ProcessOutActivityResult<POUnit>) -> Unit
123139
) = PONativeAlternativePaymentLauncher(
124-
app = from.requireActivity().application,
125-
scope = from.lifecycleScope,
140+
hostActivity = from.requireActivity(),
126141
launcher = from.registerForActivityResult(
127142
NativeAlternativePaymentActivityContract(),
128143
callback
129144
),
130-
activityOptions = createActivityOptions(from.requireContext()),
131145
delegate = object : PONativeAlternativePaymentDelegate {},
132146
callback = callback
133147
).apply {
@@ -146,14 +160,12 @@ class PONativeAlternativePaymentLauncher private constructor(
146160
delegate: PONativeAlternativePaymentDelegate,
147161
callback: (ProcessOutActivityResult<POUnit>) -> Unit
148162
) = PONativeAlternativePaymentLauncher(
149-
app = from.application,
150-
scope = from.lifecycleScope,
163+
hostActivity = from,
151164
launcher = from.registerForActivityResult(
152165
NativeAlternativePaymentActivityContract(),
153166
from.activityResultRegistry,
154167
callback
155168
),
156-
activityOptions = createActivityOptions(from),
157169
delegate = delegate,
158170
callback = callback
159171
).apply {
@@ -175,14 +187,12 @@ class PONativeAlternativePaymentLauncher private constructor(
175187
delegate: com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentDelegate,
176188
callback: (ProcessOutActivityResult<POUnit>) -> Unit
177189
) = PONativeAlternativePaymentLauncher(
178-
app = from.application,
179-
scope = from.lifecycleScope,
190+
hostActivity = from,
180191
launcher = from.registerForActivityResult(
181192
NativeAlternativePaymentActivityContract(),
182193
from.activityResultRegistry,
183194
callback
184195
),
185-
activityOptions = createActivityOptions(from),
186196
delegate = object : PONativeAlternativePaymentDelegate {},
187197
callback = callback
188198
).apply {
@@ -201,14 +211,12 @@ class PONativeAlternativePaymentLauncher private constructor(
201211
from: ComponentActivity,
202212
callback: (ProcessOutActivityResult<POUnit>) -> Unit
203213
) = PONativeAlternativePaymentLauncher(
204-
app = from.application,
205-
scope = from.lifecycleScope,
214+
hostActivity = from,
206215
launcher = from.registerForActivityResult(
207216
NativeAlternativePaymentActivityContract(),
208217
from.activityResultRegistry,
209218
callback
210219
),
211-
activityOptions = createActivityOptions(from),
212220
delegate = object : PONativeAlternativePaymentDelegate {},
213221
callback = callback
214222
).apply {
@@ -217,18 +225,30 @@ class PONativeAlternativePaymentLauncher private constructor(
217225
callback = ::handleWebRedirect
218226
)
219227
}
220-
221-
private fun createActivityOptions(context: Context) =
222-
ActivityOptionsCompat.makeCustomAnimation(
223-
context, R.anim.po_slide_in_vertical, 0
224-
)
225228
}
226229

227230
init {
231+
collectViewModelCompletion()
228232
dispatchEvents()
229233
dispatchDefaultValues()
230234
}
231235

236+
private fun collectViewModelCompletion() {
237+
hostActivity.lifecycleScope.launch {
238+
hostActivity.repeatOnLifecycle(Lifecycle.State.STARTED) {
239+
viewModel.completion.collect { completion ->
240+
when (completion) {
241+
NativeAlternativePaymentCompletion.Success ->
242+
completeHeadlessMode(result = ProcessOutResult.Success(value = POUnit))
243+
is NativeAlternativePaymentCompletion.Failure ->
244+
completeHeadlessMode(result = completion.failure)
245+
else -> {}
246+
}
247+
}
248+
}
249+
}
250+
}
251+
232252
private fun dispatchEvents() {
233253
eventDispatcher.subscribe<PONativeAlternativePaymentEvent>(
234254
coroutineScope = scope
@@ -348,14 +368,11 @@ class PONativeAlternativePaymentLauncher private constructor(
348368
) {
349369
when (state) {
350370
NEXT_STEP_REQUIRED -> handleNextStep(redirect, configuration)
351-
PENDING -> launchActivity(configuration)
352-
SUCCESS ->
353-
if (configuration.success != null) {
354-
launchActivity(configuration)
355-
} else {
356-
POLogger.info("Success: payment completed.")
357-
completeHeadlessMode(result = ProcessOutResult.Success(value = POUnit))
358-
}
371+
PENDING -> viewModel.start(configuration)
372+
SUCCESS -> {
373+
POLogger.info("Success: payment completed.")
374+
completeHeadlessMode(result = ProcessOutResult.Success(value = POUnit))
375+
}
359376
UNKNOWN -> {
360377
val failure = ProcessOutResult.Failure(
361378
code = Internal(),
@@ -482,6 +499,7 @@ class PONativeAlternativePaymentLauncher private constructor(
482499
}
483500
}
484501
LocalCache.configuration = null
502+
viewModel.reset()
485503
callback(result.toActivityResult())
486504
}
487505
}

0 commit comments

Comments
 (0)