diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index 46da99ed8..e93d3261b 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -570,6 +570,9 @@ Billing Address Invalid Please check that your billing address is correct and try again + Billing Name Invalid + Please check that the name on your card matches your billing name and try again + Something Went Wrong The Coinbase team has been notified and is investigating the issue. Your funds will arrive once resolved. We appreciate your patience diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt index a45ed0806..e6537cd52 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt @@ -62,14 +62,45 @@ fun CoinbaseOnRampHandler( private fun showOnRampFailure(resources: Resources, error: CoinbaseOnRampWebError) { when (error) { - is CoinbaseOnRampWebError.Unknown, - is CoinbaseOnRampWebError.MissingTransactionUuid -> { + // --- Grouped errors --- + + is CoinbaseOnRampWebError.UnknownFailure -> { BottomBarManager.showError( title = resources.getString(R.string.error_title_onrampUnknownFailure), message = resources.getString(R.string.error_description_onrampUnknownFailure), ) } + is CoinbaseOnRampWebError.CardDeclined -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampCardSoftDeclined), + message = resources.getString(R.string.error_description_onrampCardSoftDeclined), + ) + } + + is CoinbaseOnRampWebError.BillingAddressInvalid -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampTransactionAvsValidationFailed), + message = resources.getString(R.string.error_description_onrampTransactionAvsValidationFailed), + ) + } + + is CoinbaseOnRampWebError.InternalFailure -> { + BottomBarManager.showError( + title = resources.getString(R.string.error_title_onrampInternal), + message = resources.getString(R.string.error_description_onrampInternal), + ) + } + + is CoinbaseOnRampWebError.TransactionFailed -> { + BottomBarManager.showError( + title = resources.getString(R.string.error_title_onrampTransactionFailed), + message = resources.getString(R.string.error_description_onrampTransactionFailed), + ) + } + + // --- Single-variant errors --- + is CoinbaseOnRampWebError.GuestCardNotDebit -> { BottomBarManager.showAlert( title = resources.getString(R.string.error_title_onrampInvalidCard), @@ -77,68 +108,73 @@ private fun showOnRampFailure(resources: Resources, error: CoinbaseOnRampWebErro ) } - is CoinbaseOnRampWebError.GuestRegionMismatch -> { + is CoinbaseOnRampWebError.GuestCardRiskDeclined -> { BottomBarManager.showAlert( - title = resources.getString(R.string.error_title_onrampRegionMismatch), - message = resources.getString(R.string.error_description_onrampRegionMismatch), + title = resources.getString(R.string.error_title_onrampCardRiskDeclined), + message = resources.getString(R.string.error_description_onrampCardRiskDeclined), + ) + } + + is CoinbaseOnRampWebError.GuestPermissionDenied -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampCardPermissionDenied), + message = resources.getString(R.string.error_description_onrampCardPermissionDenied), ) } - is CoinbaseOnRampWebError.AssetNotTradableInRegion -> { + is CoinbaseOnRampWebError.RegionNotSupported -> { BottomBarManager.showAlert( title = resources.getString(R.string.error_title_onrampRegionMismatch), message = resources.getString(R.string.error_description_onrampRegionMismatch), ) } - is CoinbaseOnRampWebError.GuestGooglePayError -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampTransactionFailed), - message = resources.getString(R.string.error_description_onrampTransactionFailed), + is CoinbaseOnRampWebError.GuestWeeklyTransactionLimitReached -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampTransactionLimit), + message = resources.getString(R.string.error_description_onrampTransactionLimit), ) } - is CoinbaseOnRampWebError.GuestGooglePayNotReady -> { + is CoinbaseOnRampWebError.GuestTransactionMaxLimitReached -> { BottomBarManager.showAlert( - title = resources.getString(R.string.error_title_onrampGooglePayNotReady), - message = resources.getString(R.string.error_description_onrampGooglePayNotReady), + title = resources.getString(R.string.error_title_onrampTransactionCount), + message = resources.getString(R.string.error_description_onrampTransactionCount), ) } - is CoinbaseOnRampWebError.GuestTransactionBuyFailed -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampTransactionBuyFailed), - message = resources.getString(R.string.error_description_onrampTransactionBuyFailed), + is CoinbaseOnRampWebError.GuestGooglePayNotReady -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampGooglePayNotReady), + message = resources.getString(R.string.error_description_onrampGooglePayNotReady), ) } - is CoinbaseOnRampWebError.GuestTransactionSendFailed -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampTransactionSendFailed), - message = resources.getString(R.string.error_description_onrampTransactionSendFailed), + is CoinbaseOnRampWebError.GuestGooglePayNotSupported -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampGooglePayNotSupported), + message = resources.getString(R.string.error_description_onrampGooglePayNotSupported), ) } - is CoinbaseOnRampWebError.GuestTransactionAvsValidationFailed -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampTransactionAvsValidationFailed), - message = resources.getString(R.string.error_description_onrampTransactionAvsValidationFailed), + is CoinbaseOnRampWebError.GuestCardInsufficientBalance -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampCardInsufficientBalance), + message = resources.getString(R.string.error_description_onrampCardInsufficientBalance), ) } - is CoinbaseOnRampWebError.GuestTransactionTransactionFailed -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampTransactionFailed), - message = resources.getString(R.string.error_description_onrampTransactionFailed), + is CoinbaseOnRampWebError.GuestCardPrepaidDeclined -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampCardPrepaidDeclined), + message = resources.getString(R.string.error_description_onrampCardPrepaidDeclined), ) } - is CoinbaseOnRampWebError.Internal, - is CoinbaseOnRampWebError.GooglePayButtonNotFound, - is CoinbaseOnRampWebError.WebViewTimeout -> { - BottomBarManager.showError( - title = resources.getString(R.string.error_title_onrampInternal), - message = resources.getString(R.string.error_description_onrampInternal), + is CoinbaseOnRampWebError.InvalidBillingName -> { + BottomBarManager.showAlert( + title = resources.getString(R.string.error_title_onrampInvalidBillingName), + message = resources.getString(R.string.error_description_onrampInvalidBillingName), ) } diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampWebview.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampWebview.kt index 673f31509..8275186ab 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampWebview.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampWebview.kt @@ -97,7 +97,7 @@ private fun WebView.configureForCoinbaseOnRamp( "gmsVersion" to gmsVersion }, ) - onPaymentFailure(CoinbaseOnRampWebError.WebViewTimeout()) + onPaymentFailure(CoinbaseOnRampWebError.InternalFailure.WebViewTimeout()) } } @@ -221,7 +221,7 @@ private fun WebView.configureForCoinbaseOnRamp( error: WebResourceError? ) { if (request?.isForMainFrame == true) { - wrappedOnPaymentFailure(CoinbaseOnRampWebError.GuestGooglePayError()) + wrappedOnPaymentFailure(CoinbaseOnRampWebError.TransactionFailed.GooglePayError()) } } } diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt index 5a96cc7da..f0c42f1d2 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt @@ -187,7 +187,7 @@ internal class CoinbaseOnRampEventHandler( metadata = { "webViewVersion" to webViewVersion "gmsVersion" to gmsVersion - if (errorCode == "ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" && data != null) { + if (errorCode == CoinbaseOnRampWebError.CODE_GOOGLE_PAY_BUTTON_NOT_FOUND && data != null) { "buttons" to data.optInt("buttons", -1) "gpayElements" to data.optInt("gpayElements", -1) "iframes" to data.optInt("iframes", -1) @@ -265,40 +265,124 @@ internal class CoinbaseOnRampEventHandler( } } -sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable(data) { - class Unknown(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError - class MissingTransactionUuid(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError +/** @see [Docs](https://docs.cdp.coinbase.com/onramp/headless-onramp/overview#events-names) */ +sealed class CoinbaseOnRampWebError(val data: String? = null) : Throwable(data) { + + // --- Grouped errors (shared UI) --- + + /** "Something Went Wrong" — unknown / unmapped error codes */ + sealed class UnknownFailure(data: String?) : CoinbaseOnRampWebError(data), NotifiableError { + class Unknown(data: String? = null) : UnknownFailure(data) + class MissingTransactionUuid(data: String? = null) : UnknownFailure(data) + } + + /** "Card Declined" — declined by issuing bank */ + sealed class CardDeclined(data: String?) : CoinbaseOnRampWebError(data) { + class Soft(data: String? = null) : CardDeclined(data) + class Hard(data: String? = null) : CardDeclined(data) + class BuyFailed(data: String? = null) : CardDeclined(data) + } + + /** "Billing Address Invalid" — AVS / zip / address mismatch */ + sealed class BillingAddressInvalid(data: String?) : CoinbaseOnRampWebError(data) { + class AvsValidationFailed(data: String? = null) : BillingAddressInvalid(data) + class InvalidZip(data: String? = null) : BillingAddressInvalid(data) + class InvalidAddress(data: String? = null) : BillingAddressInvalid(data) + } + + /** "Something Went Wrong" — internal / infra failures */ + sealed class InternalFailure(data: String?) : CoinbaseOnRampWebError(data), NotifiableError { + class Internal(data: String? = null) : InternalFailure(data) + class GooglePayButtonNotFound(data: String? = null) : InternalFailure(data) + class WebViewTimeout(data: String? = null) : InternalFailure(data) + class InitError(data: String? = null) : InternalFailure(data) + } + + /** "Something Went Wrong" — transaction processing failure */ + sealed class TransactionFailed(data: String?) : CoinbaseOnRampWebError(data) { + class GooglePayError(data: String? = null) : TransactionFailed(data) + class SendFailed(data: String? = null) : TransactionFailed(data), NotifiableError + class ProcessingFailed(data: String? = null) : TransactionFailed(data), NotifiableError + } + + /** "Your Region Isn't Supported" — region / asset availability */ + sealed class RegionNotSupported(data: String?) : CoinbaseOnRampWebError(data) { + class RegionMismatch(data: String? = null) : RegionNotSupported(data) + class AssetNotTradable(data: String? = null) : RegionNotSupported(data) + } + + // --- Single-variant errors --- + class GuestCardNotDebit(data: String? = null) : CoinbaseOnRampWebError(data) - class GuestGooglePayError(data: String? = null) : CoinbaseOnRampWebError(data) - class GuestTransactionBuyFailed(data: String? = null) : CoinbaseOnRampWebError(data) - class GuestTransactionSendFailed(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError - class GuestTransactionAvsValidationFailed(data: String? = null) : CoinbaseOnRampWebError(data) - class GuestTransactionTransactionFailed(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError - class GuestRegionMismatch(data: String? = null) : CoinbaseOnRampWebError(data) - class AssetNotTradableInRegion(data: String? = null): CoinbaseOnRampWebError(data) + class GuestCardRiskDeclined(data: String? = null) : CoinbaseOnRampWebError(data) + class GuestPermissionDenied(data: String? = null) : CoinbaseOnRampWebError(data) + class GuestWeeklyTransactionLimitReached(data: String? = null) : CoinbaseOnRampWebError(data) + class GuestTransactionMaxLimitReached(data: String? = null) : CoinbaseOnRampWebError(data) class GuestGooglePayNotReady(data: String? = null) : CoinbaseOnRampWebError(data) - class Internal(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError - class GooglePayButtonNotFound(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError - class WebViewTimeout(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError + class GuestGooglePayNotSupported(data: String? = null) : CoinbaseOnRampWebError(data) + class GuestCardInsufficientBalance(data: String? = null) : CoinbaseOnRampWebError(data) + class GuestCardPrepaidDeclined(data: String? = null) : CoinbaseOnRampWebError(data) + class InvalidBillingName(data: String? = null) : CoinbaseOnRampWebError(data) class PaymentSheetTimeout(data: String? = null) : CoinbaseOnRampWebError(data) companion object { - fun fromErrorCode(errorCode: String, data: String? = null): CoinbaseOnRampWebError { - return when (errorCode) { - "ERROR_CODE_MISSING_TRANSACTION_UUID" -> MissingTransactionUuid(data) - "ERROR_CODE_ASSET_NOT_TRADABLE" -> AssetNotTradableInRegion(data) - "ERROR_CODE_GUEST_CARD_NOT_DEBIT" -> GuestCardNotDebit(data) - "ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" -> GuestGooglePayError(data) - "ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" -> GuestTransactionBuyFailed(data) - "ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" -> GuestTransactionSendFailed(data) - "ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" -> GuestTransactionAvsValidationFailed(data) - "ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" -> GuestTransactionTransactionFailed(data) - "ERROR_CODE_GUEST_REGION_MISMATCH" -> GuestRegionMismatch(data) - "ERROR_CODE_GUEST_GOOGLE_PAY_NOT_READY" -> GuestGooglePayNotReady(data) - "ERROR_CODE_INTERNAL" -> Internal(data) - "ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" -> GooglePayButtonNotFound(data) - else -> Unknown(data) - } - } + const val CODE_INIT = "ERROR_CODE_INIT" + const val CODE_INTERNAL = "ERROR_CODE_INTERNAL" + const val CODE_MISSING_TRANSACTION_UUID = "ERROR_CODE_MISSING_TRANSACTION_UUID" + const val CODE_GOOGLE_PAY_BUTTON_NOT_FOUND = "ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" + const val CODE_GUEST_INVALID_CARD = "ERROR_CODE_GUEST_INVALID_CARD" + const val CODE_GUEST_CARD_NOT_DEBIT = "ERROR_CODE_GUEST_CARD_NOT_DEBIT" + const val CODE_GUEST_CARD_SOFT_DECLINED = "ERROR_CODE_GUEST_CARD_SOFT_DECLINED" + const val CODE_GUEST_CARD_HARD_DECLINED = "ERROR_CODE_GUEST_CARD_HARD_DECLINED" + const val CODE_GUEST_CARD_RISK_DECLINED = "ERROR_CODE_GUEST_CARD_RISK_DECLINED" + const val CODE_GUEST_CARD_INSUFFICIENT_BALANCE = "ERROR_CODE_GUEST_CARD_INSUFFICIENT_BALANCE" + const val CODE_GUEST_CARD_PREPAID_DECLINED = "ERROR_CODE_GUEST_CARD_PREPAID_DECLINED" + const val CODE_GUEST_PERMISSION_DENIED = "ERROR_CODE_GUEST_PERMISSION_DENIED" + const val CODE_GUEST_REGION_MISMATCH = "ERROR_CODE_GUEST_REGION_MISMATCH" + const val CODE_GUEST_GOOGLE_PAY_ERROR = "ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" + const val CODE_GUEST_GOOGLE_PAY_NOT_READY = "ERROR_CODE_GUEST_GOOGLE_PAY_NOT_READY" + const val CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED = "ERROR_CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED" + const val CODE_GUEST_TRANSACTION_LIMIT = "ERROR_CODE_GUEST_TRANSACTION_LIMIT" + const val CODE_GUEST_TRANSACTION_COUNT = "ERROR_CODE_GUEST_TRANSACTION_COUNT" + const val CODE_GUEST_TRANSACTION_BUY_FAILED = "ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" + const val CODE_GUEST_TRANSACTION_SEND_FAILED = "ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" + const val CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED = "ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" + const val CODE_GUEST_TRANSACTION_TRANSACTION_FAILED = "ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" + const val CODE_ASSET_NOT_TRADABLE = "ERROR_CODE_ASSET_NOT_TRADABLE" + const val CODE_INVALID_BILLING_ZIP = "ERROR_CODE_INVALID_BILLING_ZIP" + const val CODE_INVALID_BILLING_ADDRESS = "ERROR_CODE_INVALID_BILLING_ADDRESS" + const val CODE_INVALID_BILLING_NAME = "ERROR_CODE_INVALID_BILLING_NAME" + + private val codeMap: Map CoinbaseOnRampWebError> = mapOf( + CODE_MISSING_TRANSACTION_UUID to { UnknownFailure.MissingTransactionUuid(it) }, + CODE_GUEST_INVALID_CARD to ::GuestCardNotDebit, + CODE_GUEST_CARD_NOT_DEBIT to ::GuestCardNotDebit, + CODE_GUEST_TRANSACTION_LIMIT to ::GuestWeeklyTransactionLimitReached, + CODE_GUEST_TRANSACTION_COUNT to ::GuestTransactionMaxLimitReached, + CODE_GUEST_CARD_RISK_DECLINED to ::GuestCardRiskDeclined, + CODE_ASSET_NOT_TRADABLE to { RegionNotSupported.AssetNotTradable(it) }, + CODE_GUEST_REGION_MISMATCH to { RegionNotSupported.RegionMismatch(it) }, + CODE_GUEST_GOOGLE_PAY_ERROR to { TransactionFailed.GooglePayError(it) }, + CODE_GUEST_TRANSACTION_BUY_FAILED to { CardDeclined.BuyFailed(it) }, + CODE_GUEST_TRANSACTION_SEND_FAILED to { TransactionFailed.SendFailed(it) }, + CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED to { BillingAddressInvalid.AvsValidationFailed(it) }, + CODE_GUEST_TRANSACTION_TRANSACTION_FAILED to { TransactionFailed.ProcessingFailed(it) }, + CODE_GUEST_PERMISSION_DENIED to ::GuestPermissionDenied, + CODE_GUEST_GOOGLE_PAY_NOT_READY to ::GuestGooglePayNotReady, + CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED to ::GuestGooglePayNotSupported, + CODE_GUEST_CARD_SOFT_DECLINED to { CardDeclined.Soft(it) }, + CODE_GUEST_CARD_HARD_DECLINED to { CardDeclined.Hard(it) }, + CODE_GUEST_CARD_INSUFFICIENT_BALANCE to ::GuestCardInsufficientBalance, + CODE_GUEST_CARD_PREPAID_DECLINED to ::GuestCardPrepaidDeclined, + CODE_INVALID_BILLING_ZIP to { BillingAddressInvalid.InvalidZip(it) }, + CODE_INVALID_BILLING_ADDRESS to { BillingAddressInvalid.InvalidAddress(it) }, + CODE_INVALID_BILLING_NAME to ::InvalidBillingName, + CODE_INIT to { InternalFailure.InitError(it) }, + CODE_INTERNAL to { InternalFailure.Internal(it) }, + CODE_GOOGLE_PAY_BUTTON_NOT_FOUND to { InternalFailure.GooglePayButtonNotFound(it) }, + ) + + fun fromErrorCode(errorCode: String, data: String? = null): CoinbaseOnRampWebError = + codeMap[errorCode]?.invoke(data) ?: UnknownFailure.Unknown(data) } } diff --git a/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandlerTest.kt b/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandlerTest.kt index 28fb3db6d..a4e55d528 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandlerTest.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/test/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandlerTest.kt @@ -6,6 +6,32 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import kotlin.test.assertEquals import kotlin.time.TimeSource +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_ASSET_NOT_TRADABLE +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GOOGLE_PAY_BUTTON_NOT_FOUND +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_HARD_DECLINED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_INSUFFICIENT_BALANCE +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_NOT_DEBIT +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_PREPAID_DECLINED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_RISK_DECLINED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_CARD_SOFT_DECLINED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_GOOGLE_PAY_ERROR +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_GOOGLE_PAY_NOT_READY +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_INVALID_CARD +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_PERMISSION_DENIED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_REGION_MISMATCH +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_BUY_FAILED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_COUNT +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_LIMIT +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_SEND_FAILED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_GUEST_TRANSACTION_TRANSACTION_FAILED +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_INIT +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_INTERNAL +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_INVALID_BILLING_ADDRESS +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_INVALID_BILLING_NAME +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_INVALID_BILLING_ZIP +import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError.Companion.CODE_MISSING_TRANSACTION_UUID import com.getcode.utils.NotifiableError import kotlin.test.assertFalse import kotlin.test.assertIs @@ -66,19 +92,19 @@ class CoinbaseOnRampEventHandlerTest { @Test fun commitErrorTriggersFailure() { handler.handleEvent("""{"eventName":"onramp_api.commit_error","data":{"errorCode":"ERROR_CODE_INTERNAL"}}""") - assertIs(lastError) + assertIs(lastError) } @Test fun loadErrorTriggersFailure() { handler.handleEvent("""{"eventName":"onramp_api.load_error","data":{"errorCode":"ERROR_CODE_GUEST_GOOGLE_PAY_ERROR"}}""") - assertIs(lastError) + assertIs(lastError) } @Test fun pollingErrorTriggersFailure() { handler.handleEvent("""{"eventName":"onramp_api.polling_error","data":{"errorCode":"ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED"}}""") - assertIs(lastError) + assertIs(lastError) } @Test @@ -90,19 +116,19 @@ class CoinbaseOnRampEventHandlerTest { @Test fun errorWithUnknownCodeFallsBackToUnknown() { handler.handleEvent("""{"eventName":"onramp_api.commit_error","data":{"errorCode":"SOME_NEW_ERROR"}}""") - assertIs(lastError) + assertIs(lastError) } @Test fun errorWithMissingDataFallsBackToUnknown() { handler.handleEvent("""{"eventName":"onramp_api.commit_error"}""") - assertIs(lastError) + assertIs(lastError) } @Test fun errorWithEmptyErrorCodeFallsBackToUnknown() { handler.handleEvent("""{"eventName":"onramp_api.commit_error","data":{"errorCode":""}}""") - assertIs(lastError) + assertIs(lastError) } // --- Data payload --- @@ -111,7 +137,7 @@ class CoinbaseOnRampEventHandlerTest { fun errorCarriesJsonData() { handler.handleEvent("""{"eventName":"onramp_api.commit_error","data":{"errorCode":"ERROR_CODE_INTERNAL","transactionId":"abc-123"}}""") val error = lastError - assertIs(error) + assertIs(error) assertNotNull(error.data) assertTrue(error.data!!.contains("abc-123")) } @@ -203,16 +229,32 @@ class CoinbaseOnRampWebErrorTest { @Test fun fromErrorCodeAllKnownCodes() { val expected = mapOf( - "ERROR_CODE_MISSING_TRANSACTION_UUID" to CoinbaseOnRampWebError.MissingTransactionUuid::class, - "ERROR_CODE_GUEST_CARD_NOT_DEBIT" to CoinbaseOnRampWebError.GuestCardNotDebit::class, - "ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" to CoinbaseOnRampWebError.GuestGooglePayError::class, - "ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" to CoinbaseOnRampWebError.GuestTransactionBuyFailed::class, - "ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" to CoinbaseOnRampWebError.GuestTransactionSendFailed::class, - "ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" to CoinbaseOnRampWebError.GuestTransactionAvsValidationFailed::class, - "ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" to CoinbaseOnRampWebError.GuestTransactionTransactionFailed::class, - "ERROR_CODE_GUEST_REGION_MISMATCH" to CoinbaseOnRampWebError.GuestRegionMismatch::class, - "ERROR_CODE_INTERNAL" to CoinbaseOnRampWebError.Internal::class, - "ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" to CoinbaseOnRampWebError.GooglePayButtonNotFound::class, + CODE_MISSING_TRANSACTION_UUID to CoinbaseOnRampWebError.UnknownFailure.MissingTransactionUuid::class, + CODE_GUEST_CARD_NOT_DEBIT to CoinbaseOnRampWebError.GuestCardNotDebit::class, + CODE_GUEST_INVALID_CARD to CoinbaseOnRampWebError.GuestCardNotDebit::class, + CODE_GUEST_GOOGLE_PAY_ERROR to CoinbaseOnRampWebError.TransactionFailed.GooglePayError::class, + CODE_GUEST_TRANSACTION_BUY_FAILED to CoinbaseOnRampWebError.CardDeclined.BuyFailed::class, + CODE_GUEST_TRANSACTION_SEND_FAILED to CoinbaseOnRampWebError.TransactionFailed.SendFailed::class, + CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED to CoinbaseOnRampWebError.BillingAddressInvalid.AvsValidationFailed::class, + CODE_GUEST_TRANSACTION_TRANSACTION_FAILED to CoinbaseOnRampWebError.TransactionFailed.ProcessingFailed::class, + CODE_GUEST_REGION_MISMATCH to CoinbaseOnRampWebError.RegionNotSupported.RegionMismatch::class, + CODE_GUEST_TRANSACTION_LIMIT to CoinbaseOnRampWebError.GuestWeeklyTransactionLimitReached::class, + CODE_GUEST_TRANSACTION_COUNT to CoinbaseOnRampWebError.GuestTransactionMaxLimitReached::class, + CODE_GUEST_CARD_RISK_DECLINED to CoinbaseOnRampWebError.GuestCardRiskDeclined::class, + CODE_GUEST_PERMISSION_DENIED to CoinbaseOnRampWebError.GuestPermissionDenied::class, + CODE_ASSET_NOT_TRADABLE to CoinbaseOnRampWebError.RegionNotSupported.AssetNotTradable::class, + CODE_GUEST_GOOGLE_PAY_NOT_READY to CoinbaseOnRampWebError.GuestGooglePayNotReady::class, + CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED to CoinbaseOnRampWebError.GuestGooglePayNotSupported::class, + CODE_GUEST_CARD_SOFT_DECLINED to CoinbaseOnRampWebError.CardDeclined.Soft::class, + CODE_GUEST_CARD_HARD_DECLINED to CoinbaseOnRampWebError.CardDeclined.Hard::class, + CODE_GUEST_CARD_INSUFFICIENT_BALANCE to CoinbaseOnRampWebError.GuestCardInsufficientBalance::class, + CODE_GUEST_CARD_PREPAID_DECLINED to CoinbaseOnRampWebError.GuestCardPrepaidDeclined::class, + CODE_INVALID_BILLING_ZIP to CoinbaseOnRampWebError.BillingAddressInvalid.InvalidZip::class, + CODE_INVALID_BILLING_ADDRESS to CoinbaseOnRampWebError.BillingAddressInvalid.InvalidAddress::class, + CODE_INVALID_BILLING_NAME to CoinbaseOnRampWebError.InvalidBillingName::class, + CODE_INIT to CoinbaseOnRampWebError.InternalFailure.InitError::class, + CODE_INTERNAL to CoinbaseOnRampWebError.InternalFailure.Internal::class, + CODE_GOOGLE_PAY_BUTTON_NOT_FOUND to CoinbaseOnRampWebError.InternalFailure.GooglePayButtonNotFound::class, ) for ((code, expectedType) in expected) { @@ -223,29 +265,36 @@ class CoinbaseOnRampWebErrorTest { @Test fun fromErrorCodeUnknownCodeReturnsUnknown() { - assertIs(CoinbaseOnRampWebError.fromErrorCode("SOMETHING_NEW")) + assertIs(CoinbaseOnRampWebError.fromErrorCode("SOMETHING_NEW")) } @Test fun fromErrorCodeEmptyStringReturnsUnknown() { - assertIs(CoinbaseOnRampWebError.fromErrorCode("")) + assertIs(CoinbaseOnRampWebError.fromErrorCode("")) } @Test fun fromErrorCodeCaseSensitive() { - assertIs(CoinbaseOnRampWebError.fromErrorCode("error_code_internal")) + assertIs(CoinbaseOnRampWebError.fromErrorCode("error_code_internal")) } @Test - fun webViewTimeoutImplementsNotifiableError() { - val error = CoinbaseOnRampWebError.WebViewTimeout() - assertIs(error) - assertIs(error) + fun internalFailureGroupImplementsNotifiableError() { + assertIs(CoinbaseOnRampWebError.InternalFailure.InitError()) + assertIs(CoinbaseOnRampWebError.InternalFailure.Internal()) + assertIs(CoinbaseOnRampWebError.InternalFailure.WebViewTimeout()) + assertIs(CoinbaseOnRampWebError.InternalFailure.GooglePayButtonNotFound()) + } + + @Test + fun unknownFailureGroupImplementsNotifiableError() { + assertIs(CoinbaseOnRampWebError.UnknownFailure.Unknown()) + assertIs(CoinbaseOnRampWebError.UnknownFailure.MissingTransactionUuid()) } @Test fun guestRegionMismatchIsNotNotifiable() { - val error = CoinbaseOnRampWebError.GuestRegionMismatch() + val error = CoinbaseOnRampWebError.RegionNotSupported.RegionMismatch() assertFalse(error is NotifiableError) }