Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ce916d0
po_native_apm_confirm_capture_button_text
vitalii-vanziak-cko Sep 23, 2024
b39d6ef
ConfirmAction and paymentConfirmationPrimaryAction in nAPM (Views) co…
vitalii-vanziak-cko Sep 23, 2024
dc6b2e4
KDoc
vitalii-vanziak-cko Sep 23, 2024
cc9aee7
KDoc
vitalii-vanziak-cko Sep 23, 2024
7b24199
Added payment confirmation button, refactored footer
vitalii-vanziak-cko Sep 23, 2024
aa11649
Suppress expected deprecation
vitalii-vanziak-cko Sep 23, 2024
bd2ff04
set onConfirmPaymentClick()
vitalii-vanziak-cko Sep 23, 2024
74dc7c3
Apply custom button style
vitalii-vanziak-cko Sep 23, 2024
176a805
Handle payment confirmation
vitalii-vanziak-cko Sep 23, 2024
0812bd6
Send DidConfirmPayment event
vitalii-vanziak-cko Sep 23, 2024
575cbf1
Adde ConfirmAction to PONativeAlternativePaymentConfiguration
vitalii-vanziak-cko Sep 25, 2024
5316b51
KDoc and log
vitalii-vanziak-cko Sep 25, 2024
cc730cd
Hide payment confirmation button when there is no customer action and…
vitalii-vanziak-cko Sep 25, 2024
936a7f2
po_native_apm_confirm_payment_button_text
vitalii-vanziak-cko Sep 25, 2024
ad65fc3
Add payment confirmation button to nAPM Compose
vitalii-vanziak-cko Sep 25, 2024
383c366
enableCapturingProgressIndicator() when capture is actually started
vitalii-vanziak-cko Sep 25, 2024
d8d35e8
Decide when to start capture automatically not waiting confirmation
vitalii-vanziak-cko Sep 25, 2024
676a145
Start capture when confirmed by user and hide button
vitalii-vanziak-cko Sep 25, 2024
cc8cc22
Code improvement
vitalii-vanziak-cko Sep 25, 2024
d819cba
DC ConfirmButton config
vitalii-vanziak-cko Sep 26, 2024
6e66089
Add confirm payment button on DC UI
vitalii-vanziak-cko Sep 26, 2024
0aa0284
Enable ConfirmButton by default in example app
vitalii-vanziak-cko Sep 26, 2024
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 @@ -24,6 +24,8 @@ import com.processout.sdk.core.onFailure
import com.processout.sdk.core.onSuccess
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.AlternativePaymentConfiguration
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.AlternativePaymentConfiguration.PaymentConfirmationConfiguration
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.AlternativePaymentConfiguration.PaymentConfirmationConfiguration.ConfirmButton
import com.processout.sdk.ui.checkout.PODynamicCheckoutLauncher
import com.processout.sdk.ui.shared.view.dialog.POAlertDialog
import com.processout.sdk.ui.threeds.PO3DSRedirectCustomTabLauncher
Expand Down Expand Up @@ -94,7 +96,10 @@ class DynamicCheckoutFragment : BaseFragment<FragmentDynamicCheckoutBinding>(
clientSecret = uiModel.clientSecret
),
alternativePayment = AlternativePaymentConfiguration(
returnUrl = Constants.RETURN_URL
returnUrl = Constants.RETURN_URL,
paymentConfirmation = PaymentConfirmationConfiguration(
confirmButton = ConfirmButton()
)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ sealed class PONativeAlternativePaymentMethodEvent {
val additionalActionExpected: Boolean
) : PONativeAlternativePaymentMethodEvent()

/**
* Event is sent during the capture stage when the user confirms that they have completed required external action.
* Implementation proceeds with the actual capture process.
*/
data object DidConfirmPayment : PONativeAlternativePaymentMethodEvent()

/**
* Event is sent when user asked to confirm cancellation, e.g. via dialog.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal data class NativeAlternativePaymentMethodUiModel(
val customerActionImageUrl: String?,
val primaryActionText: String,
val secondaryAction: SecondaryActionUiModel?,
val paymentConfirmationPrimaryActionText: String?,
val paymentConfirmationSecondaryAction: SecondaryActionUiModel?,
val isPaymentConfirmationProgressIndicatorVisible: Boolean,
val isSubmitting: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,14 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
POLogger.info("All payment parameters has been submitted.")

if (options.waitsPaymentConfirmation) {
val customerActionMessage = parameterValues?.customerActionMessage ?: uiModel.customerActionMessageMarkdown
val updatedUiModel = uiModel.copy(
title = parameterValues?.providerName,
logoUrl = if (parameterValues?.providerName != null)
parameterValues.providerLogoUrl else uiModel.logoUrl,
customerActionMessageMarkdown = parameterValues?.customerActionMessage
?: uiModel.customerActionMessageMarkdown
customerActionMessageMarkdown = customerActionMessage,
paymentConfirmationPrimaryActionText = if (!customerActionMessage.isNullOrBlank())
uiModel.paymentConfirmationPrimaryActionText else null
)
POLogger.info("Waiting for capture confirmation.")
dispatch(
Expand All @@ -430,16 +432,12 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
animateViewTransition = true
_uiState.value = Capture(updatedUiModel)

options.showPaymentConfirmationProgressIndicatorAfterSeconds?.let { afterSeconds ->
showPaymentConfirmationProgressIndicator(
afterMillis = TimeUnit.SECONDS.toMillis(afterSeconds.toLong())
)
}
updatedUiModel.paymentConfirmationSecondaryAction?.let {
scheduleSecondaryActionEnabling(it) { enablePaymentConfirmationSecondaryAction() }
}

capture()
if (updatedUiModel.paymentConfirmationPrimaryActionText == null) {
capture()
}
return
}
_uiState.value = Success(uiModel.copy())
Expand Down Expand Up @@ -503,12 +501,28 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
}
else originalMessage

fun confirmPayment() {
_uiState.value.doWhenCapture { uiModel ->
POLogger.info("User confirmed that required external action is complete.")
dispatch(DidConfirmPayment)
_uiState.value = Capture(
uiModel.copy(paymentConfirmationPrimaryActionText = null)
)
capture()
}
}

private fun capture() {
if (captureStartTimestamp != 0L) {
return
}
captureStartTimestamp = System.currentTimeMillis()
options.showPaymentConfirmationProgressIndicatorAfterSeconds?.let { afterSeconds ->
showPaymentConfirmationProgressIndicator(
afterMillis = TimeUnit.SECONDS.toMillis(afterSeconds.toLong())
)
}
viewModelScope.launch {
captureStartTimestamp = System.currentTimeMillis()
val iterator = captureRetryStrategy.iterator
while (capturePassedTimestamp < options.paymentConfirmationTimeoutSeconds * 1000) {
val result = invoicesService.captureNativeAlternativePayment(invoiceId, gatewayConfigurationId)
Expand Down Expand Up @@ -616,6 +630,9 @@ internal class NativeAlternativePaymentMethodViewModel private constructor(
customerActionImageUrl = gateway.customerActionImageUrl,
primaryActionText = options.primaryActionText ?: invoice.formatPrimaryActionText(),
secondaryAction = options.secondaryAction?.toUiModel(),
paymentConfirmationPrimaryActionText = options.paymentConfirmationPrimaryAction?.let {
it.text ?: app.getString(R.string.po_native_apm_confirm_payment_button_text)
},
paymentConfirmationSecondaryAction = options.paymentConfirmationSecondaryAction?.toUiModel(),
isPaymentConfirmationProgressIndicatorVisible = false,
isSubmitting = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.activity.addCallback
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.children
import androidx.core.view.updateLayoutParams
import androidx.core.view.*
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
Expand Down Expand Up @@ -503,6 +501,11 @@ class PONativeAlternativePaymentMethodBottomSheet : BottomSheetDialogFragment(),
viewModel.submitPayment()
}

private fun onConfirmPaymentClick() {
bindingCapture.poPrimaryButton.isClickable = false
viewModel.confirmPayment()
}

private fun onCancelClick(confirmation: ActionConfirmation) {
with(confirmation) {
if (!enabled) {
Expand Down Expand Up @@ -586,7 +589,7 @@ class PONativeAlternativePaymentMethodBottomSheet : BottomSheetDialogFragment(),

private fun bindCapture(uiModel: NativeAlternativePaymentMethodUiModel) {
initCaptureView()
bindPaymentConfirmationSecondaryButton(uiModel)
bindCaptureFooter(uiModel)
if (uiModel.showCustomerAction()) {
bindingCapture.poCircularProgressIndicator.visibility = View.GONE
if (uiModel.isPaymentConfirmationProgressIndicatorVisible) {
Expand Down Expand Up @@ -665,21 +668,42 @@ class PONativeAlternativePaymentMethodBottomSheet : BottomSheetDialogFragment(),
bindingCapture.poMessage.visibility = View.VISIBLE
}

private fun bindPaymentConfirmationSecondaryButton(
private fun bindCaptureFooter(
uiModel: NativeAlternativePaymentMethodUiModel
) {
uiModel.paymentConfirmationSecondaryAction?.let { action ->
val visible = uiModel.paymentConfirmationPrimaryActionText != null
|| uiModel.paymentConfirmationSecondaryAction != null
if (visible) {
bindingCapture.poFooter.visibility = View.VISIBLE
with(bindingCapture.poPrimaryButton) {
uiModel.paymentConfirmationPrimaryActionText?.let { actionText ->
setOnClickListener { onConfirmPaymentClick() }
text = actionText
visibility = View.VISIBLE
} ?: run { visibility = View.GONE }
}
with(bindingCapture.poSecondaryButton) {
when (action) {
is SecondaryActionUiModel.Cancel -> {
setOnClickListener { onCancelClick(action.confirmation) }
text = action.text
setState(action.state)
bindingCapture.poFooter.visibility = View.VISIBLE
uiModel.paymentConfirmationSecondaryAction?.let { action ->
when (action) {
is SecondaryActionUiModel.Cancel -> {
setOnClickListener { onCancelClick(action.confirmation) }
if (uiModel.paymentConfirmationPrimaryActionText == null) {
updateLayoutParams<LinearLayout.LayoutParams> {
updateMarginsRelative(
top = resources.getDimensionPixelSize(R.dimen.po_bottomSheet_buttons_marginVertical)
)
}
}
text = action.text
setState(action.state)
visibility = View.VISIBLE
}
}
}
} ?: run { visibility = View.GONE }
}
} ?: run { bindingCapture.poFooter.visibility = View.GONE }
} else {
bindingCapture.poFooter.visibility = View.GONE
}
}

private fun handleSuccess(uiModel: NativeAlternativePaymentMethodUiModel) {
Expand Down Expand Up @@ -755,6 +779,7 @@ class PONativeAlternativePaymentMethodBottomSheet : BottomSheetDialogFragment(),
bindingCapture.poFooter.visibility = View.GONE
}

@Suppress("DEPRECATION")
private fun bindSuccessBackground() {
val backgroundDecorationSuccessColor =
when (val stateStyle = configuration?.style?.backgroundDecoration?.success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ data class PONativeAlternativePaymentMethodConfiguration(
* or will complete right after all user’s input is submitted. Default value is _true_.
* @param[paymentConfirmationTimeoutSeconds] Amount of time (in seconds) to wait for final payment confirmation.
* Default value is 3 minutes, while maximum value is 15 minutes.
* @param[paymentConfirmationPrimaryAction] Optional primary action for payment confirmation.
* To hide action use _null_, this is a default behaviour.
* @param[paymentConfirmationSecondaryAction] Action that could be optionally presented to user during payment confirmation stage.
* To hide action use _null_, this is a default behaviour.
* @param[showPaymentConfirmationProgressIndicatorAfterSeconds] Show progress indicator during payment confirmation after provided delay (in seconds).
Expand All @@ -61,6 +63,7 @@ data class PONativeAlternativePaymentMethodConfiguration(
val skipSuccessScreen: Boolean = false,
val waitsPaymentConfirmation: Boolean = true,
val paymentConfirmationTimeoutSeconds: Int = DEFAULT_PAYMENT_CONFIRMATION_TIMEOUT_SECONDS,
val paymentConfirmationPrimaryAction: ConfirmAction? = null,
val paymentConfirmationSecondaryAction: SecondaryAction? = null,
val showPaymentConfirmationProgressIndicatorAfterSeconds: Int? = null
) : Parcelable {
Expand All @@ -70,6 +73,16 @@ data class PONativeAlternativePaymentMethodConfiguration(
}
}

/**
* Action for confirmation.
*
* @param[text] Action text. Pass _null_ to use default text.
*/
@Parcelize
data class ConfirmAction(
val text: String? = null
) : Parcelable

/**
* Supported secondary actions.
*/
Expand All @@ -80,6 +93,7 @@ data class PONativeAlternativePaymentMethodConfiguration(
* @param[text] Action text. Pass _null_ to use default text.
* @param[disabledForSeconds] Initially disables action for the given amount of time in seconds.
* By default user can interact with action immediately when it's visible.
* @param[confirmation] Specifies action confirmation configuration (e.g. dialog). Disabled by default.
*/
@Parcelize
data class Cancel(
Expand All @@ -90,9 +104,9 @@ data class PONativeAlternativePaymentMethodConfiguration(
}

/**
* Specifies action confirmation behaviour and values.
* Specifies action confirmation configuration (e.g. dialog).
*
* @param[enabled] Enables action confirmation.
* @param[enabled] Enables action confirmation. Default value is _false_.
* @param[title] Custom title. Pass _null_ to use default text.
* @param[message] Custom message. Pass _null_ to use default text. Pass empty string to hide.
* @param[confirmActionText] Custom confirm action text. Pass _null_ to use default text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.processout.sdk.ui.shared.style.radio.PORadioButtonStyle
import com.processout.sdk.ui.shared.view.extension.dpToPx
import com.processout.sdk.ui.shared.view.extension.spToPx

@Suppress("DEPRECATION")
internal fun PoBottomSheetNativeApmBinding.applyStyle(
style: PONativeAlternativePaymentMethodConfiguration.Style
) {
Expand All @@ -51,6 +52,7 @@ internal fun PoBottomSheetCaptureBinding.applyStyle(
}
style.successImageResId?.let { poSuccessImage.setImageResource(it) }
style.message?.let { poMessage.applyStyle(it) }
style.primaryButton?.let { poPrimaryButton.applyStyle(it) }
style.secondaryButton?.let { poSecondaryButton.applyStyle(it) }
style.controlsTintColor?.let {
poMessage.applyControlsTintColor(it)
Expand Down
19 changes: 16 additions & 3 deletions sdk/src/main/res/layout/po_bottom_sheet_capture.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
android:id="@+id/po_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/po_bottomSheet_buttons_marginVertical"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
Expand All @@ -164,15 +165,27 @@
android:layout_height="@dimen/po_borderWidth"
android:background="@color/po_border_subtle" />

<com.processout.sdk.ui.shared.view.button.POButton
android:id="@+id/po_primary_button"
style="@style/Widget.ProcessOut.Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/po_bottomSheet_buttons_marginHorizontal"
android:layout_marginTop="@dimen/po_bottomSheet_buttons_marginVertical"
android:text="@string/po_native_apm_confirm_payment_button_text"
android:visibility="gone"
tools:visibility="visible" />

<com.processout.sdk.ui.shared.view.button.POButton
android:id="@+id/po_secondary_button"
style="@style/Widget.ProcessOut.Button.Secondary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/po_bottomSheet_buttons_marginHorizontal"
android:layout_marginTop="@dimen/po_bottomSheet_buttons_marginVertical"
android:layout_marginBottom="@dimen/po_bottomSheet_buttons_marginVertical"
android:text="@string/po_native_apm_cancel_button_text" />
android:layout_marginTop="@dimen/po_button_marginTop"
android:text="@string/po_native_apm_cancel_button_text"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>

</merge>
1 change: 1 addition & 0 deletions sdk/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="po_native_apm_submit_button_text_format">الدفع %s</string>
<string name="po_native_apm_submit_button_text">الدفع</string>
<string name="po_native_apm_cancel_button_text">إلغاء</string>
<string name="po_native_apm_confirm_payment_button_text">لقد دفعت</string>
<string name="po_native_apm_email_placeholder">name@example.com</string>
<string name="po_native_apm_phone_placeholder">أدخل رقم الهاتف</string>
<string name="po_native_apm_success_message">نجاح! تمت الموافقة على الدفع</string>
Expand Down
1 change: 1 addition & 0 deletions sdk/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="po_native_apm_submit_button_text_format">Payer %s</string>
<string name="po_native_apm_submit_button_text">Payer</string>
<string name="po_native_apm_cancel_button_text">Annuler</string>
<string name="po_native_apm_confirm_payment_button_text">J\'ai payé</string>
<string name="po_native_apm_email_placeholder">nom@exemple.fr</string>
<string name="po_native_apm_phone_placeholder">Entrez votre numéro de téléphone</string>
<string name="po_native_apm_success_message">Succès !\nPaiement confirmé.</string>
Expand Down
1 change: 1 addition & 0 deletions sdk/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="po_native_apm_submit_button_text_format">Zapłać %s</string>
<string name="po_native_apm_submit_button_text">Zapłać</string>
<string name="po_native_apm_cancel_button_text">Anuluj</string>
<string name="po_native_apm_confirm_payment_button_text">Płatność wykonana</string>
<string name="po_native_apm_email_placeholder">imię@przykład.pl</string>
<string name="po_native_apm_phone_placeholder">Twój numer telefonu</string>
<string name="po_native_apm_success_message">Sukces!\nPłatność przyjęta.</string>
Expand Down
1 change: 1 addition & 0 deletions sdk/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="po_native_apm_submit_button_text_format">Pagar %s</string>
<string name="po_native_apm_submit_button_text">Pagar</string>
<string name="po_native_apm_cancel_button_text">Cancelar</string>
<string name="po_native_apm_confirm_payment_button_text">Já paguei</string>
<string name="po_native_apm_email_placeholder">nome@exemplo.pt</string>
<string name="po_native_apm_phone_placeholder">Insira o seu número de telemóvel</string>
<string name="po_native_apm_success_message">Successo!\nPagamento aprovado.</string>
Expand Down
1 change: 1 addition & 0 deletions sdk/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="po_native_apm_submit_button_text_format">Pay %s</string>
<string name="po_native_apm_submit_button_text">Pay</string>
<string name="po_native_apm_cancel_button_text">Cancel</string>
<string name="po_native_apm_confirm_payment_button_text">I\'ve paid</string>
<string name="po_native_apm_email_placeholder">name@example.com</string>
<string name="po_native_apm_phone_placeholder">Enter phone number</string>
<string name="po_native_apm_success_message">Success!\nPayment approved.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() {
timeoutSeconds = paymentConfirmation?.timeoutSeconds ?: DEFAULT_TIMEOUT_SECONDS,
showProgressIndicatorAfterSeconds = paymentConfirmation?.showProgressIndicatorAfterSeconds,
hideGatewayDetails = true,
primaryAction = paymentConfirmation?.confirmButton?.let { ConfirmAction(text = it.text) },
secondaryAction = paymentConfirmation?.cancelButton?.toSecondaryAction()
),
inlineSingleSelectValuesLimit = configuration?.alternativePayment?.inlineSingleSelectValuesLimit ?: 5,
Expand Down
Loading