Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f9dec95
rebase: Fix invoice invalidation with nAPMs
vitalii-vanziak-cko Mar 17, 2026
c2e082a
scheme_selection_default_order
vitalii-vanziak-cko Mar 17, 2026
f55d167
Apply schemeSelectionDefaultOrder
vitalii-vanziak-cko Mar 24, 2026
9b39140
rebase: Extract methods
vitalii-vanziak-cko Mar 24, 2026
d17259c
Add fields "restrict_to_iins" & "restrict_to_schemes"
vitalii-vanziak-cko May 25, 2026
cff5b2c
Resolve card eligibility
vitalii-vanziak-cko May 27, 2026
7802bcc
authorizeV2() which returns customer token, applied to DC
vitalii-vanziak-cko Jun 3, 2026
1e0822d
Send DidTokenizePaymentMethod event
vitalii-vanziak-cko Jun 3, 2026
a4f3230
Allow to hide express checkout payment methods via configuration
vitalii-vanziak-cko Jun 11, 2026
99364cd
Card `SavingConfiguration` instead of `savingAllowed`
vitalii-vanziak-cko Jun 17, 2026
e5a2241
Apply card SavingConfiguration.required
vitalii-vanziak-cko Jun 17, 2026
69dbcc6
Apply card `SavingConfiguration`
vitalii-vanziak-cko Jun 17, 2026
e7af73b
Adde SavingConfiguration for DC
vitalii-vanziak-cko Jun 25, 2026
93a6c18
Map card SavingConfiguration on DC
vitalii-vanziak-cko Jun 25, 2026
d7b1bbb
Apply SavingConfiguration to APM on DC
vitalii-vanziak-cko Jun 25, 2026
8c1e509
POInvoiceAuthorizationOutcome
vitalii-vanziak-cko Jun 30, 2026
ce540de
Added RegularCheckout configuration
vitalii-vanziak-cko Jul 6, 2026
c129d49
Remove express checkout header if there is no content
vitalii-vanziak-cko Jul 6, 2026
679124e
Added string: po_dynamic_checkout_regular_checkout
vitalii-vanziak-cko Jul 8, 2026
e953eaa
Regular checkout section with custom title
vitalii-vanziak-cko Jul 8, 2026
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 @@ -30,6 +30,7 @@ import com.processout.sdk.netcetera.threeds.PONetcetera3DS2ServiceConfiguration
import com.processout.sdk.ui.card.tokenization.POCardTokenizationActivity
import com.processout.sdk.ui.card.tokenization.POCardTokenizationConfiguration
import com.processout.sdk.ui.card.tokenization.POCardTokenizationConfiguration.CardScannerConfiguration
import com.processout.sdk.ui.card.tokenization.POCardTokenizationConfiguration.SavingConfiguration
import com.processout.sdk.ui.card.tokenization.POCardTokenizationLauncher
import com.processout.sdk.ui.shared.view.dialog.POAlertDialog
import com.processout.sdk.ui.threeds.PO3DSRedirectCustomTabLauncher
Expand Down Expand Up @@ -95,9 +96,9 @@ class CardPaymentFragment : BaseFragment<FragmentCardPaymentBinding>(

private fun launchTokenization() {
launcher.launch(
POCardTokenizationConfiguration(
configuration = POCardTokenizationConfiguration(
cardScanner = CardScannerConfiguration(),
savingAllowed = true
saving = SavingConfiguration()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,51 @@
package com.processout.sdk.api.model.response

import com.processout.sdk.core.annotation.ProcessOutInternalApi
import com.processout.sdk.core.util.findBy
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
internal data class InvoiceAuthorizationResponse(
@Json(name = "outcome")
val rawOutcome: String,
@Json(name = "customer_action")
val customerAction: CustomerAction?
val customerAction: CustomerAction?,
@Json(name = "customer_token_id")
val customerTokenId: String?
) {

val outcome: POInvoiceAuthorizationOutcome
get() = POInvoiceAuthorizationOutcome::rawValue.findBy(rawOutcome) ?: POInvoiceAuthorizationOutcome.UNKNOWN
}

/**
* Invoice authorization response.
*
* @param[outcome] Invoice authorization outcome.
* @param[customerTokenId] Optional customer token ID.
* Available if invoice was authorized with `saveSource` flag set to `true` and implementation was able to tokenize the source.
*/
data class POInvoiceAuthorizationResponse(
val outcome: POInvoiceAuthorizationOutcome,
val customerTokenId: String?
)

/**
* Invoice authorization outcome determined by the request and the current transaction status.
*/
@JsonClass(generateAdapter = false)
enum class POInvoiceAuthorizationOutcome(val rawValue: String) {
/** Pending operation. */
PENDING("pending"),

/** Successful operation. */
SUCCESS("success"),

/**
* Placeholder that allows adding additional cases while staying backward compatible.
* __Warning:__ Do not match this case directly, use _when-else_ instead.
*/
@ProcessOutInternalApi
UNKNOWN(String())
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ sealed class PODynamicCheckoutPaymentMethod {
* @param[cvcRequired] Defines whether the card CVC should be collected.
* @param[cardholderNameRequired] Defines whether the cardholder name should be collected.
* @param[schemeSelectionAllowed] Defines whether the user will be asked to select the scheme if co-scheme is available.
* @param[schemeSelectionDefaultOrder] Schemes selection preferred order.
* When cards with multiple supported schemes are detected, the first matching scheme is preselected.
* @param[restrictToIins] Restrict card schemes to the ones with IINs in the set.
* @param[restrictToSchemes] Restrict card schemes to the ones in the set.
* @param[billingAddress] Card billing address configuration.
* @param[savingAllowed] Defines whether saving of the payment method is allowed for future payments.
*/
Expand All @@ -167,6 +171,12 @@ sealed class PODynamicCheckoutPaymentMethod {
val cardholderNameRequired: Boolean,
@Json(name = "scheme_selection_allowed")
val schemeSelectionAllowed: Boolean,
@Json(name = "scheme_selection_default_order")
val schemeSelectionDefaultOrder: List<String>?,
@Json(name = "restrict_to_iins")
val restrictToIins: Set<String>?,
@Json(name = "restrict_to_schemes")
val restrictToSchemes: Set<String>?,
@Json(name = "billing_address")
val billingAddress: BillingAddressConfiguration,
@Json(name = "saving_allowed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import com.processout.sdk.api.model.request.POInvoiceRequest
import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodRequest
import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentAuthorizationRequest
import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentUrlResolutionRequest
import com.processout.sdk.api.model.response.POInvoice
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethod
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodCapture
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodTransactionDetails
import com.processout.sdk.api.model.response.*
import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse
import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentUrlResolutionResponse
import com.processout.sdk.api.repository.InvoicesRepository
Expand Down Expand Up @@ -166,6 +163,68 @@ internal class DefaultInvoicesService(
}
}

override suspend fun authorizeV2(
request: POInvoiceAuthorizationRequest,
threeDSService: PO3DSService
): ProcessOutResult<POInvoiceAuthorizationResponse> {
val logAttributes = mapOf(POLogAttribute.INVOICE_ID to request.invoiceId)
return try {
repository.authorizeInvoice(request)
.fold(
onSuccess = { response ->
if (response.customerAction == null) {
threeDSService.cleanup()
val authorizationResponse = POInvoiceAuthorizationResponse(
outcome = response.outcome,
customerTokenId = response.customerTokenId
)
return@fold ProcessOutResult.Success(authorizationResponse)
}
customerActionsService.handle(response.customerAction, threeDSService)
.fold(
onSuccess = { newSource ->
authorizeV2(
request.copy(source = newSource),
threeDSService
)
},
onFailure = { failure ->
POLogger.warn(
message = "Failed to authorize invoice: %s", failure,
attributes = logAttributes
)
threeDSService.cleanup()
failure
}
)
},
onFailure = { failure ->
POLogger.warn(
message = "Failed to authorize invoice: %s", failure,
attributes = logAttributes
)
threeDSService.cleanup()
failure
}
)
} catch (e: CancellationException) {
coroutineScope {
val failure = ProcessOutResult.Failure(
code = Cancelled,
message = e.message,
cause = e
)
POLogger.info(
message = "Invoice authorization has been cancelled: %s", failure,
attributes = logAttributes
)
threeDSService.cleanup()
ensureActive()
failure
}
}
}

override suspend fun authorize(
request: PONativeAlternativePaymentAuthorizationRequest
): ProcessOutResult<PONativeAlternativePaymentAuthorizationResponse> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import com.processout.sdk.api.model.request.POInvoiceRequest
import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodRequest
import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentAuthorizationRequest
import com.processout.sdk.api.model.request.napm.v2.PONativeAlternativePaymentUrlResolutionRequest
import com.processout.sdk.api.model.response.POInvoice
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethod
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodCapture
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodTransactionDetails
import com.processout.sdk.api.model.response.*
import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentAuthorizationResponse
import com.processout.sdk.api.model.response.napm.v2.PONativeAlternativePaymentUrlResolutionResponse
import com.processout.sdk.core.ProcessOutCallback
Expand Down Expand Up @@ -67,6 +64,15 @@ interface POInvoicesService {
threeDSService: PO3DSService
): ProcessOutResult<Unit>

/**
* Authorize invoice with the given request and 3DS service implementation.
* Provides the result with [POInvoiceAuthorizationResponse].
*/
suspend fun authorizeV2(
request: POInvoiceAuthorizationRequest,
threeDSService: PO3DSService
): ProcessOutResult<POInvoiceAuthorizationResponse>

/**
* Authorize invoice with the given request.
*/
Expand Down
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 @@ -5,6 +5,7 @@

<!-- Dynamic Checkout -->
<string name="po_dynamic_checkout_express_checkout">الدفع السريع</string>
<string name="po_dynamic_checkout_regular_checkout">طرق دفع أخرى</string>
<string name="po_dynamic_checkout_button_pay">ادفع</string>
<string name="po_dynamic_checkout_button_cancel">إلغاء</string>
<string name="po_dynamic_checkout_save_payment_method">حِفْظُ هذِهِ الطَّريقَةِ لِلدَّفْعِ في المُستَقبَلِ</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 @@ -5,6 +5,7 @@

<!-- Dynamic Checkout -->
<string name="po_dynamic_checkout_express_checkout">Paiement express</string>
<string name="po_dynamic_checkout_regular_checkout">Autres modes de paiement</string>
<string name="po_dynamic_checkout_button_pay">Payer</string>
<string name="po_dynamic_checkout_button_cancel">Annuler</string>
<string name="po_dynamic_checkout_save_payment_method">Enregistrer cette méthode pour les paiements futurs</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 @@ -5,6 +5,7 @@

<!-- Dynamic Checkout -->
<string name="po_dynamic_checkout_express_checkout">Szybka płatność</string>
<string name="po_dynamic_checkout_regular_checkout">Inne metody płatności</string>
<string name="po_dynamic_checkout_button_pay">Zapłać</string>
<string name="po_dynamic_checkout_button_cancel">Anuluj</string>
<string name="po_dynamic_checkout_save_payment_method">Zapisz tę metodę płatności na przyszłe płatności</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 @@ -5,6 +5,7 @@

<!-- Dynamic Checkout -->
<string name="po_dynamic_checkout_express_checkout">Pagamento expresso</string>
<string name="po_dynamic_checkout_regular_checkout">Outras formas de pagamento</string>
<string name="po_dynamic_checkout_button_pay">Pagar</string>
<string name="po_dynamic_checkout_button_cancel">Cancelar</string>
<string name="po_dynamic_checkout_save_payment_method">Guardar este método para futuros pagamentos</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 @@ -5,6 +5,7 @@

<!-- Dynamic Checkout -->
<string name="po_dynamic_checkout_express_checkout">Express checkout</string>
<string name="po_dynamic_checkout_regular_checkout">Other payment methods</string>
<string name="po_dynamic_checkout_button_pay">Pay</string>
<string name="po_dynamic_checkout_button_cancel">Cancel</string>
<string name="po_dynamic_checkout_save_payment_method">Save this method for future payments</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.processout.sdk.ui.card.tokenization.CardTokenizationCompletion.Succes
import com.processout.sdk.ui.card.tokenization.CardTokenizationEvent.CardScannerResult
import com.processout.sdk.ui.card.tokenization.CardTokenizationEvent.Dismiss
import com.processout.sdk.ui.card.tokenization.CardTokenizationSideEffect.CardScanner
import com.processout.sdk.ui.card.tokenization.POCardTokenizationConfiguration.Button
import com.processout.sdk.ui.card.tokenization.screen.CardTokenizationScreen
import com.processout.sdk.ui.core.theme.ProcessOutTheme
import com.processout.sdk.ui.shared.component.displayCutoutHeight
Expand Down Expand Up @@ -60,7 +59,7 @@ internal class CardTokenizationBottomSheet : BaseBottomSheetDialogFragment<POCar
super.onAttach(context)
@Suppress("DEPRECATION")
configuration = arguments?.getParcelable(EXTRA_CONFIGURATION)
?: POCardTokenizationConfiguration(submitButton = Button())
?: POCardTokenizationConfiguration(saving = null)
cardScannerLauncher = POCardScannerLauncher.create(
from = this,
callback = { result ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,24 +139,30 @@ internal class CardTokenizationInteractor(
latestShouldContinueRequest = null
}

private fun initState() = CardTokenizationInteractorState(
cardFields = cardFields(),
addressFields = emptyList(),
preferredSchemeField = Field(
id = FieldId.PREFERRED_SCHEME,
shouldCollect = false
),
saveCardField = Field(
id = FieldId.SAVE_CARD,
value = TextFieldValue(text = "false"),
shouldCollect = configuration.savingAllowed
),
focusedFieldId = CardFieldId.NUMBER,
pendingFocusedFieldId = null,
primaryActionId = ActionId.SUBMIT,
secondaryActionId = ActionId.CANCEL,
cardScannerActionId = ActionId.CARD_SCANNER
)
private fun initState(): CardTokenizationInteractorState {
val saveCardFieldValue: Boolean = configuration.saving?.let {
it.required || it.enabledByDefault
} ?: false
return CardTokenizationInteractorState(
cardFields = cardFields(),
addressFields = emptyList(),
preferredSchemeField = Field(
id = FieldId.PREFERRED_SCHEME,
shouldCollect = false
),
saveCardField = Field(
id = FieldId.SAVE_CARD,
value = TextFieldValue(text = saveCardFieldValue.toString()),
enabled = configuration.saving?.required != true,
shouldCollect = configuration.saving != null
),
focusedFieldId = CardFieldId.NUMBER,
pendingFocusedFieldId = null,
primaryActionId = ActionId.SUBMIT,
secondaryActionId = ActionId.CANCEL,
cardScannerActionId = ActionId.CARD_SCANNER
)
}

private fun cardFields(): List<Field> = mutableListOf(
Field(id = CardFieldId.NUMBER),
Expand Down Expand Up @@ -303,7 +309,7 @@ internal class CardTokenizationInteractor(
field.copy(enabled = enabled)
},
preferredSchemeField = it.preferredSchemeField.copy(enabled = enabled),
saveCardField = it.saveCardField.copy(enabled = enabled)
saveCardField = it.saveCardField.copy(enabled = configuration.saving?.required != true)
)
}
}
Expand Down
Loading
Loading