Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -248,46 +248,6 @@ internal class NativeAlternativePaymentInteractor(
}
}

//region Next Step

private fun handleNextStep(
stateValue: NextStepStateValue,
elements: List<Element>,
redirect: PONativeAlternativePaymentRedirect?
) {
val parameters = elements.flatMap {
if (it is Element.Form) it.form.parameterDefinitions else emptyList()
}
if (parameters.isEmpty()) {
POLogger.warn(
message = "Parameters is empty in response.",
attributes = configuration.logAttributes
)
}
if (failWithUnknownParameter(parameters)) {
return
}
if (failWithUnknownRedirect(redirect)) {
return
}
val fields = parameters.toFields()
val updatedStateValue = stateValue.copy(
uuid = UUID.randomUUID().toString(),
redirect = redirect,
elements = elements,
fields = fields,
focusedFieldId = fields.firstFocusableFieldId()
)
_state.update {
if (_state.value is Loading) {
Loaded(updatedStateValue)
} else {
Submitted(updatedStateValue)
}
}
requestDefaultValues(parameters)
}

private suspend fun List<PONativeAlternativePaymentElement>.map(): List<Element> =
mapNotNull { element ->
when (element) {
Expand Down Expand Up @@ -345,16 +305,53 @@ internal class NativeAlternativePaymentInteractor(
}
}

private fun failWithUnknownParameter(
parameters: List<Parameter>
//region Next Step

private fun handleNextStep(
stateValue: NextStepStateValue,
elements: List<Element>,
redirect: PONativeAlternativePaymentRedirect?
) {
if (failWithUnsupportedHeadlessMode(redirect)) {
return
}
if (failWithUnknownRedirect(redirect)) {
return
}
val parameters = elements.flatMap {
if (it is Element.Form) it.form.parameterDefinitions else emptyList()
}
if (failWithUnknownParameter(parameters)) {
return
}
val fields = parameters.toFields()
val updatedStateValue = stateValue.copy(
uuid = UUID.randomUUID().toString(),
redirect = redirect,
elements = elements,
fields = fields,
focusedFieldId = fields.firstFocusableFieldId()
)
_state.update {
if (_state.value is Loading) {
Loaded(updatedStateValue)
} else {
Submitted(updatedStateValue)
}
}
requestDefaultValues(parameters)
}

private fun failWithUnsupportedHeadlessMode(
redirect: PONativeAlternativePaymentRedirect?
): Boolean {
parameters.find { it == Parameter.Unknown }?.let {
if (configuration.redirect?.enableHeadlessMode == true && redirect == null) {
val failure = ProcessOutResult.Failure(
code = Internal(),
message = "Unknown parameter type."
code = Generic(genericCode = mobileOperationNotSupported),
message = "Headless mode is not supported: redirect parameters are missing in the response."
)
POLogger.error(
message = "Unexpected response: %s", failure,
message = "Unsupported operation: %s", failure,
attributes = configuration.logAttributes
)
_completion.update { Failure(failure) }
Expand All @@ -381,6 +378,24 @@ internal class NativeAlternativePaymentInteractor(
return false
}

private fun failWithUnknownParameter(
parameters: List<Parameter>
): Boolean {
parameters.find { it == Parameter.Unknown }?.let {
val failure = ProcessOutResult.Failure(
code = Internal(),
message = "Unknown parameter type."
)
POLogger.error(
message = "Unexpected response: %s", failure,
attributes = configuration.logAttributes
)
_completion.update { Failure(failure) }
return true
}
return false
}

private fun List<Parameter>.toFields() =
map { parameter ->
val defaultValue = when (parameter) {
Expand Down Expand Up @@ -414,7 +429,7 @@ internal class NativeAlternativePaymentInteractor(
enableNextStepSecondaryAction()
POLogger.info("Started: waiting for payment parameters.")
dispatch(DidStart)
handleHeadlessRedirect()
handleAutoRedirect()
}

private fun continueNextStep(stateValue: NextStepStateValue) {
Expand All @@ -433,33 +448,24 @@ internal class NativeAlternativePaymentInteractor(
additionalParametersExpected = true
)
)
handleHeadlessRedirect()
handleAutoRedirect()
}

private fun handleHeadlessRedirect() {
if (configuration.redirect?.enableHeadlessMode != true) {
return
}
private fun handleAutoRedirect() {
_state.whenNextStep { stateValue ->
if (stateValue.redirect == null) {
val failure = ProcessOutResult.Failure(
code = Generic(genericCode = mobileOperationNotSupported),
message = "Headless mode is not supported: redirect parameters are missing in the response."
)
POLogger.error(
message = "Unsupported operation: %s", failure,
attributes = configuration.logAttributes
if (stateValue.redirect != null && shouldAutoRedirect()) {
redirect(
stateValue = stateValue,
redirect = stateValue.redirect
)
_completion.update { Failure(failure) }
return@whenNextStep
}
redirect(
stateValue = stateValue,
redirect = stateValue.redirect
)
}
}

private fun shouldAutoRedirect(): Boolean =
configuration.redirect?.enableHeadlessMode == true ||
configuration.redirect?.redirectButton == null

//endregion

//region Default Values
Expand Down Expand Up @@ -1314,7 +1320,7 @@ internal class NativeAlternativePaymentInteractor(
private fun saveBarcode() {
_state.whenNextStep { stateValue ->
val instructions = stateValue.elements.mapNotNull {
if (it is Element.Instruction) it else null
it as? Element.Instruction
}
instructions.forEach {
if (it.instruction is Instruction.Barcode) {
Expand All @@ -1325,7 +1331,7 @@ internal class NativeAlternativePaymentInteractor(
}
_state.whenPending { stateValue ->
val instructions = stateValue.elements?.mapNotNull {
if (it is Element.Instruction) it else null
it as? Element.Instruction
}
instructions?.forEach {
if (it.instruction is Instruction.Barcode) {
Expand Down Expand Up @@ -1357,7 +1363,7 @@ internal class NativeAlternativePaymentInteractor(
if (result.isGranted) {
_state.whenNextStep { stateValue ->
val instructions = stateValue.elements.mapNotNull {
if (it is Element.Instruction) it else null
it as? Element.Instruction
}
instructions.forEach {
if (it.instruction is Instruction.Barcode) {
Expand All @@ -1372,7 +1378,7 @@ internal class NativeAlternativePaymentInteractor(
}
_state.whenPending { stateValue ->
val instructions = stateValue.elements?.mapNotNull {
if (it is Element.Instruction) it else null
it as? Element.Instruction
}
instructions?.forEach {
if (it.instruction is Instruction.Barcode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,7 @@ internal class NativeAlternativePaymentViewModel private constructor(
),
elements = elements.map(fields)
),
primaryAction = POActionState(
id = primaryActionId,
text = redirect?.hint
?: configuration.submitButton.text
?: app.getString(R.string.po_native_apm_continue_button_text),
primary = true,
enabled = submitAllowed,
loading = submitting,
icon = configuration.submitButton.icon
),
primaryAction = toSubmitAction(),
secondaryAction = configuration.cancelButton?.toActionState(
id = secondaryAction.id,
enabled = secondaryAction.enabled && !submitting
Expand Down Expand Up @@ -567,6 +558,28 @@ internal class NativeAlternativePaymentViewModel private constructor(
private fun Invoice.priceSuccessMessage(): String? =
price()?.let { app.getString(R.string.po_native_apm_success_message_format, it) }

private fun NextStepStateValue.toSubmitAction(): POActionState? {
val submitAction = POActionState(
id = primaryActionId,
text = configuration.submitButton.text
?: app.getString(R.string.po_native_apm_continue_button_text),
primary = true,
enabled = submitAllowed,
loading = submitting,
icon = configuration.submitButton.icon
)
return if (redirect != null) {
configuration.redirect?.redirectButton?.let {
submitAction.copy(
text = it.text ?: redirect.hint,
icon = it.icon
)
}
} else {
submitAction
}
}

private fun CancelButton.toActionState(
id: String,
enabled: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ data class PONativeAlternativePaymentConfiguration(
* @param[invoiceId] Invoice identifier.
* @param[gatewayConfigurationId] Gateway configuration identifier.
* @param[customerTokenId] Optional customer token identifier that will be used for authorization.
* @param[configuration] Authorization configuration.
*/
@Parcelize
data class Authorization(
Expand All @@ -78,6 +79,7 @@ data class PONativeAlternativePaymentConfiguration(
* @param[customerId] Customer identifier.
* @param[customerTokenId] Customer token identifier.
* @param[gatewayConfigurationId] Gateway configuration identifier.
* @param[configuration] Tokenization configuration.
*/
@Parcelize
data class Tokenization(
Expand Down Expand Up @@ -394,11 +396,14 @@ data class PONativeAlternativePaymentConfiguration(
* The redirect (web or deep link) will be handled directly when it's the first step in the flow, without starting the bottom sheet.
* It will also capture the payment in the background when it's required by the flow.
* __Note:__ use only with flows that do not require user input or instructions in the native UI.
* @param[redirectButton] Redirect button configuration.
* Pass _null_ to hide and redirect automatically, this is a default behaviour.
*/
@Parcelize
data class RedirectConfiguration(
val returnUrl: String,
val enableHeadlessMode: Boolean = false
val enableHeadlessMode: Boolean = false,
val redirectButton: Button? = null
) : Parcelable

/**
Expand Down
Loading