Tokenization and 3D Secure failures are reported through CompletePaymentSheet's onFailure callback. Each failure carries a FinixErrorCode describing the exact failure mode.
The deprecated signature had no failure callback — errors were surfaced via a toast inside the sheet. If you're still using the deprecated signature, tokenization errors won't reach your code. Migrate to the new CompletePaymentSheet signature.
A sealed interface — pattern-match on the specific type in when to branch per failure mode. Every variant carries a human-readable message suitable for surfacing to the buyer.
FinixErrorCode shape
sealed interface FinixErrorCode {
val message: String
data object NetworkError : FinixErrorCode {
override val message = "Network error. Please check your connection."
}
data object Timeout : FinixErrorCode {
override val message = "Request timed out. Please try again."
}
data object PaymentFailed : FinixErrorCode {
override val message = "Payment failed. Please try again."
}
data class MissingConfiguration(val field: String) : FinixErrorCode {
override val message = "3DS is enabled but required field '$field' is missing."
}
data class ThreeDSFailed(val failureCode: ThreeDSFailureCode?) : FinixErrorCode {
override val message =
failureCode?.failureMessage ?: "3DS authentication failed. Please try again."
}
data class Unknown(val detail: String = "") : FinixErrorCode {
override val message = detail.ifEmpty { "An unexpected error occurred." }
}
}| Variant | Meaning |
|---|---|
NetworkError | The device could not reach Finix (no connectivity, DNS failure, TLS error). |
Timeout | A Finix API call did not return within the SDK's timeout window. |
PaymentFailed | Tokenization reached Finix but did not succeed. Typically an invalid or declined card. |
MissingConfiguration(field) | 3DS was enabled but a required field is missing from PaymentSheetConfiguration — the field property names which one (e.g. "merchantId", "apiUsername"). |
ThreeDSFailed(failureCode) | 3D Secure authentication failed. The nested ThreeDSFailureCode describes the specific reason. No liability shift — do not charge. |
Unknown(detail) | An unexpected error. detail carries any available context from the SDK. |
Nested inside FinixErrorCode.ThreeDSFailed and populated on ThreeDSResponse.failureCode. Each case carries a failureMessage string explaining the failure.
ThreeDSFailureCode cases
enum class ThreeDSFailureCode(val failureMessage: String) {
ACCESS_CONTROL_SERVER_UNAVAILABLE,
AUTHENTICATION_FAILED,
AUTHENTICATION_TIMED_OUT,
CARD_NOT_ENROLLED,
CHALLENGE_REQUIRED,
DIRECTORY_SERVER_UNAVAILABLE,
INVALID_ACQUIRER,
INVALID_CARD,
PROTOCOL_ERROR,
REQUIRED_DATA_ELEMENT_MISSING,
SUSPECTED_FRAUD,
TRANSACTION_CANCELLED_BY_CARDHOLDER,
TRANSACTION_NOT_PERMITTED,
TRANSIENT_SYSTEM_FAILURE,
UNSUPPORTED_3D_SECURE_VERSION,
}| Case | Meaning |
|---|---|
ACCESS_CONTROL_SERVER_UNAVAILABLE | The card issuer's authentication server was unavailable. Retry later. |
AUTHENTICATION_FAILED | The cardholder failed to provide the correct authentication details. Treat as potentially fraudulent. |
AUTHENTICATION_TIMED_OUT | Authentication timed out, likely because the cardholder abandoned the transaction. |
CARD_NOT_ENROLLED | The card is not enrolled in 3DS and cannot be authenticated. |
CHALLENGE_REQUIRED | A challenge was required but the integration is configured to fail on challenge. |
DIRECTORY_SERVER_UNAVAILABLE | The card network's directory server was unavailable. Retry later. |
INVALID_ACQUIRER | The acquirer details provided are invalid. |
INVALID_CARD | The card details are invalid. |
PROTOCOL_ERROR | A protocol error occurred during authentication. |
REQUIRED_DATA_ELEMENT_MISSING | A required data element is missing from the authentication request. |
SUSPECTED_FRAUD | The transaction was flagged as suspected fraud by the issuer. |
TRANSACTION_CANCELLED_BY_CARDHOLDER | The cardholder explicitly cancelled the authentication challenge. |
TRANSACTION_NOT_PERMITTED | The transaction type is not permitted for this card. |
TRANSIENT_SYSTEM_FAILURE | A transient system failure occurred. Retry later. |
UNSUPPORTED_3D_SECURE_VERSION | The 3D Secure version required by the issuer is not supported. |
Handling onFailure
Pattern-match on the error code to branch per failure mode. message is safe to surface directly to the buyer.
onFailure = { code ->
when (code) {
FinixErrorCode.NetworkError,
FinixErrorCode.Timeout -> showError(code.message)
FinixErrorCode.PaymentFailed -> showError(code.message)
is FinixErrorCode.MissingConfiguration ->
// Bug in the integration — missing field named by code.field
logConfigurationError(code.field)
is FinixErrorCode.ThreeDSFailed ->
// No liability shift — do not charge
showError(code.message)
is FinixErrorCode.Unknown -> showError(code.message)
}
}sealed interface FinixErrorCode {
val message: String
data object NetworkError : FinixErrorCode {
override val message = "Network error. Please check your connection."
}
data object Timeout : FinixErrorCode {
override val message = "Request timed out. Please try again."
}
data object PaymentFailed : FinixErrorCode {
override val message = "Payment failed. Please try again."
}
data class MissingConfiguration(val field: String) : FinixErrorCode {
override val message = "3DS is enabled but required field '$field' is missing."
}
data class ThreeDSFailed(val failureCode: ThreeDSFailureCode?) : FinixErrorCode {
override val message =
failureCode?.failureMessage ?: "3DS authentication failed. Please try again."
}
data class Unknown(val detail: String = "") : FinixErrorCode {
override val message = detail.ifEmpty { "An unexpected error occurred." }
}
}
enum class ThreeDSFailureCode(val failureMessage: String) {
ACCESS_CONTROL_SERVER_UNAVAILABLE(
"The card issuer's authentication server was unavailable. Retry later."
),
AUTHENTICATION_FAILED(
"The cardholder failed to provide the correct authentication details. " +
"This transaction should be considered potentially fraudulent."
),
AUTHENTICATION_TIMED_OUT(
"The authentication timed out, likely because the cardholder abandoned the transaction."
),
CARD_NOT_ENROLLED(
"The card is not enrolled in 3DS and cannot be authenticated."
),
CHALLENGE_REQUIRED(
"A challenge was required but the integration is configured to fail on challenge."
),
DIRECTORY_SERVER_UNAVAILABLE(
"The card network's directory server was unavailable. Retry later."
),
INVALID_ACQUIRER(
"The acquirer details provided are invalid."
),
INVALID_CARD(
"The card details are invalid."
),
PROTOCOL_ERROR(
"A protocol error occurred during the authentication process."
),
REQUIRED_DATA_ELEMENT_MISSING(
"A required data element is missing from the authentication request."
),
SUSPECTED_FRAUD(
"The transaction was flagged as suspected fraud by the issuer."
),
TRANSACTION_CANCELLED_BY_CARDHOLDER(
"The cardholder explicitly cancelled the authentication challenge."
),
TRANSACTION_NOT_PERMITTED(
"The transaction type is not permitted for this card."
),
TRANSIENT_SYSTEM_FAILURE(
"A transient system failure occurred. Retry later."
),
UNSUPPORTED_3D_SECURE_VERSION(
"The 3D Secure version required by the issuer is not supported."
),
}
onFailure = { code ->
when (code) {
FinixErrorCode.NetworkError,
FinixErrorCode.Timeout -> showError(code.message)
FinixErrorCode.PaymentFailed -> showError(code.message)
is FinixErrorCode.MissingConfiguration ->
// Bug in the integration — missing field named by code.field
logConfigurationError(code.field)
is FinixErrorCode.ThreeDSFailed ->
// No liability shift — do not charge
showError(code.message)
is FinixErrorCode.Unknown -> showError(code.message)
}
}