Skip to content

Commit bafd718

Browse files
committed
fix(onramp): align Coinbase error codes with official docs and group by UI
Add 9 missing Coinbase headless onramp error codes and group related errors into sealed subclass hierarchies (CardDeclined, BillingAddressInvalid, InternalFailure, TransactionFailed, UnknownFailure) so the UI handler matches on groups instead of individual variants. Fixes 4 TODO() crashes in showOnRampFailure. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e785077 commit bafd718

5 files changed

Lines changed: 193 additions & 80 deletions

File tree

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,9 @@
570570
<string name="error_title_onrampTransactionAvsValidationFailed">Billing Address Invalid</string>
571571
<string name="error_description_onrampTransactionAvsValidationFailed">Please check that your billing address is correct and try again</string>
572572

573+
<string name="error_title_onrampInvalidBillingName">Billing Name Invalid</string>
574+
<string name="error_description_onrampInvalidBillingName">Please check that the name on your card matches your billing name and try again</string>
575+
573576
<string name="error_title_onrampTransactionFailed">Something Went Wrong</string>
574577
<string name="error_description_onrampTransactionFailed">The Coinbase team has been notified and is investigating the issue. Your funds will arrive once resolved. We appreciate your patience</string>
575578

apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -62,39 +62,84 @@ fun CoinbaseOnRampHandler(
6262

6363
private fun showOnRampFailure(resources: Resources, error: CoinbaseOnRampWebError) {
6464
when (error) {
65-
is CoinbaseOnRampWebError.Unknown,
66-
is CoinbaseOnRampWebError.MissingTransactionUuid -> {
65+
// --- Grouped errors ---
66+
67+
is CoinbaseOnRampWebError.UnknownFailure -> {
6768
BottomBarManager.showError(
6869
title = resources.getString(R.string.error_title_onrampUnknownFailure),
6970
message = resources.getString(R.string.error_description_onrampUnknownFailure),
7071
)
7172
}
7273

74+
is CoinbaseOnRampWebError.CardDeclined -> {
75+
BottomBarManager.showAlert(
76+
title = resources.getString(R.string.error_title_onrampCardSoftDeclined),
77+
message = resources.getString(R.string.error_description_onrampCardSoftDeclined),
78+
)
79+
}
80+
81+
is CoinbaseOnRampWebError.BillingAddressInvalid -> {
82+
BottomBarManager.showAlert(
83+
title = resources.getString(R.string.error_title_onrampTransactionAvsValidationFailed),
84+
message = resources.getString(R.string.error_description_onrampTransactionAvsValidationFailed),
85+
)
86+
}
87+
88+
is CoinbaseOnRampWebError.InternalFailure -> {
89+
BottomBarManager.showError(
90+
title = resources.getString(R.string.error_title_onrampInternal),
91+
message = resources.getString(R.string.error_description_onrampInternal),
92+
)
93+
}
94+
95+
is CoinbaseOnRampWebError.TransactionFailed -> {
96+
BottomBarManager.showError(
97+
title = resources.getString(R.string.error_title_onrampTransactionFailed),
98+
message = resources.getString(R.string.error_description_onrampTransactionFailed),
99+
)
100+
}
101+
102+
// --- Single-variant errors ---
103+
73104
is CoinbaseOnRampWebError.GuestCardNotDebit -> {
74105
BottomBarManager.showAlert(
75106
title = resources.getString(R.string.error_title_onrampInvalidCard),
76107
message = resources.getString(R.string.error_description_onrampInvalidCard),
77108
)
78109
}
79110

111+
is CoinbaseOnRampWebError.GuestCardRiskDeclined -> {
112+
BottomBarManager.showAlert(
113+
title = resources.getString(R.string.error_title_onrampCardRiskDeclined),
114+
message = resources.getString(R.string.error_description_onrampCardRiskDeclined),
115+
)
116+
}
117+
118+
is CoinbaseOnRampWebError.GuestPermissionDenied -> {
119+
BottomBarManager.showAlert(
120+
title = resources.getString(R.string.error_title_onrampCardPermissionDenied),
121+
message = resources.getString(R.string.error_description_onrampCardPermissionDenied),
122+
)
123+
}
124+
80125
is CoinbaseOnRampWebError.GuestRegionMismatch -> {
81126
BottomBarManager.showAlert(
82127
title = resources.getString(R.string.error_title_onrampRegionMismatch),
83128
message = resources.getString(R.string.error_description_onrampRegionMismatch),
84129
)
85130
}
86131

87-
is CoinbaseOnRampWebError.AssetNotTradableInRegion -> {
132+
is CoinbaseOnRampWebError.GuestWeeklyTransactionLimitReached -> {
88133
BottomBarManager.showAlert(
89-
title = resources.getString(R.string.error_title_onrampRegionMismatch),
90-
message = resources.getString(R.string.error_description_onrampRegionMismatch),
134+
title = resources.getString(R.string.error_title_onrampTransactionLimit),
135+
message = resources.getString(R.string.error_description_onrampTransactionLimit),
91136
)
92137
}
93138

94-
is CoinbaseOnRampWebError.GuestGooglePayError -> {
95-
BottomBarManager.showError(
96-
title = resources.getString(R.string.error_title_onrampTransactionFailed),
97-
message = resources.getString(R.string.error_description_onrampTransactionFailed),
139+
is CoinbaseOnRampWebError.GuestTransactionMaxLimitReached -> {
140+
BottomBarManager.showAlert(
141+
title = resources.getString(R.string.error_title_onrampTransactionCount),
142+
message = resources.getString(R.string.error_description_onrampTransactionCount),
98143
)
99144
}
100145

@@ -105,40 +150,31 @@ private fun showOnRampFailure(resources: Resources, error: CoinbaseOnRampWebErro
105150
)
106151
}
107152

108-
is CoinbaseOnRampWebError.GuestTransactionBuyFailed -> {
109-
BottomBarManager.showError(
110-
title = resources.getString(R.string.error_title_onrampTransactionBuyFailed),
111-
message = resources.getString(R.string.error_description_onrampTransactionBuyFailed),
112-
)
113-
}
114-
115-
is CoinbaseOnRampWebError.GuestTransactionSendFailed -> {
116-
BottomBarManager.showError(
117-
title = resources.getString(R.string.error_title_onrampTransactionSendFailed),
118-
message = resources.getString(R.string.error_description_onrampTransactionSendFailed),
153+
is CoinbaseOnRampWebError.GuestGooglePayNotSupported -> {
154+
BottomBarManager.showAlert(
155+
title = resources.getString(R.string.error_title_onrampGooglePayNotSupported),
156+
message = resources.getString(R.string.error_description_onrampGooglePayNotSupported),
119157
)
120158
}
121159

122-
is CoinbaseOnRampWebError.GuestTransactionAvsValidationFailed -> {
123-
BottomBarManager.showError(
124-
title = resources.getString(R.string.error_title_onrampTransactionAvsValidationFailed),
125-
message = resources.getString(R.string.error_description_onrampTransactionAvsValidationFailed),
160+
is CoinbaseOnRampWebError.GuestCardInsufficientBalance -> {
161+
BottomBarManager.showAlert(
162+
title = resources.getString(R.string.error_title_onrampCardInsufficientBalance),
163+
message = resources.getString(R.string.error_description_onrampCardInsufficientBalance),
126164
)
127165
}
128166

129-
is CoinbaseOnRampWebError.GuestTransactionTransactionFailed -> {
130-
BottomBarManager.showError(
131-
title = resources.getString(R.string.error_title_onrampTransactionFailed),
132-
message = resources.getString(R.string.error_description_onrampTransactionFailed),
167+
is CoinbaseOnRampWebError.GuestCardPrepaidDeclined -> {
168+
BottomBarManager.showAlert(
169+
title = resources.getString(R.string.error_title_onrampCardPrepaidDeclined),
170+
message = resources.getString(R.string.error_description_onrampCardPrepaidDeclined),
133171
)
134172
}
135173

136-
is CoinbaseOnRampWebError.Internal,
137-
is CoinbaseOnRampWebError.GooglePayButtonNotFound,
138-
is CoinbaseOnRampWebError.WebViewTimeout -> {
139-
BottomBarManager.showError(
140-
title = resources.getString(R.string.error_title_onrampInternal),
141-
message = resources.getString(R.string.error_description_onrampInternal),
174+
is CoinbaseOnRampWebError.InvalidBillingName -> {
175+
BottomBarManager.showAlert(
176+
title = resources.getString(R.string.error_title_onrampInvalidBillingName),
177+
message = resources.getString(R.string.error_description_onrampInvalidBillingName),
142178
)
143179
}
144180

apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampWebview.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ private fun WebView.configureForCoinbaseOnRamp(
9797
"gmsVersion" to gmsVersion
9898
},
9999
)
100-
onPaymentFailure(CoinbaseOnRampWebError.WebViewTimeout())
100+
onPaymentFailure(CoinbaseOnRampWebError.InternalFailure.WebViewTimeout())
101101
}
102102
}
103103

@@ -221,7 +221,7 @@ private fun WebView.configureForCoinbaseOnRamp(
221221
error: WebResourceError?
222222
) {
223223
if (request?.isForMainFrame == true) {
224-
wrappedOnPaymentFailure(CoinbaseOnRampWebError.GuestGooglePayError())
224+
wrappedOnPaymentFailure(CoinbaseOnRampWebError.TransactionFailed.GooglePayError())
225225
}
226226
}
227227
}

apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/internal/CoinbaseOnRampEventHandler.kt

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -265,39 +265,90 @@ internal class CoinbaseOnRampEventHandler(
265265
}
266266
}
267267

268-
sealed class CoinbaseOnRampWebError(val data: String? = null): Throwable(data) {
269-
class Unknown(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
270-
class MissingTransactionUuid(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
268+
sealed class CoinbaseOnRampWebError(val data: String? = null) : Throwable(data) {
269+
270+
// --- Grouped errors (shared UI) ---
271+
272+
/** "Something Went Wrong" — unknown / unmapped error codes */
273+
sealed class UnknownFailure(data: String?) : CoinbaseOnRampWebError(data), NotifiableError {
274+
class Unknown(data: String? = null) : UnknownFailure(data)
275+
class MissingTransactionUuid(data: String? = null) : UnknownFailure(data)
276+
}
277+
278+
/** "Card Declined" — declined by issuing bank */
279+
sealed class CardDeclined(data: String?) : CoinbaseOnRampWebError(data) {
280+
class Soft(data: String? = null) : CardDeclined(data)
281+
class Hard(data: String? = null) : CardDeclined(data)
282+
class BuyFailed(data: String? = null) : CardDeclined(data)
283+
}
284+
285+
/** "Billing Address Invalid" — AVS / zip / address mismatch */
286+
sealed class BillingAddressInvalid(data: String?) : CoinbaseOnRampWebError(data) {
287+
class AvsValidationFailed(data: String? = null) : BillingAddressInvalid(data)
288+
class InvalidZip(data: String? = null) : BillingAddressInvalid(data)
289+
class InvalidAddress(data: String? = null) : BillingAddressInvalid(data)
290+
}
291+
292+
/** "Something Went Wrong" — internal / infra failures */
293+
sealed class InternalFailure(data: String?) : CoinbaseOnRampWebError(data), NotifiableError {
294+
class Internal(data: String? = null) : InternalFailure(data)
295+
class GooglePayButtonNotFound(data: String? = null) : InternalFailure(data)
296+
class WebViewTimeout(data: String? = null) : InternalFailure(data)
297+
class InitError(data: String? = null) : InternalFailure(data)
298+
}
299+
300+
/** "Something Went Wrong" — transaction processing failure */
301+
sealed class TransactionFailed(data: String?) : CoinbaseOnRampWebError(data) {
302+
class GooglePayError(data: String? = null) : TransactionFailed(data)
303+
class SendFailed(data: String? = null) : TransactionFailed(data), NotifiableError
304+
class ProcessingFailed(data: String? = null) : TransactionFailed(data), NotifiableError
305+
}
306+
307+
// --- Single-variant errors ---
308+
271309
class GuestCardNotDebit(data: String? = null) : CoinbaseOnRampWebError(data)
272-
class GuestGooglePayError(data: String? = null) : CoinbaseOnRampWebError(data)
273-
class GuestTransactionBuyFailed(data: String? = null) : CoinbaseOnRampWebError(data)
274-
class GuestTransactionSendFailed(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
275-
class GuestTransactionAvsValidationFailed(data: String? = null) : CoinbaseOnRampWebError(data)
276-
class GuestTransactionTransactionFailed(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
310+
class GuestCardRiskDeclined(data: String? = null) : CoinbaseOnRampWebError(data)
311+
class GuestPermissionDenied(data: String? = null) : CoinbaseOnRampWebError(data)
277312
class GuestRegionMismatch(data: String? = null) : CoinbaseOnRampWebError(data)
278-
class AssetNotTradableInRegion(data: String? = null): CoinbaseOnRampWebError(data)
313+
class GuestWeeklyTransactionLimitReached(data: String? = null) : CoinbaseOnRampWebError(data)
314+
class GuestTransactionMaxLimitReached(data: String? = null) : CoinbaseOnRampWebError(data)
279315
class GuestGooglePayNotReady(data: String? = null) : CoinbaseOnRampWebError(data)
280-
class Internal(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
281-
class GooglePayButtonNotFound(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
282-
class WebViewTimeout(data: String? = null) : CoinbaseOnRampWebError(data), NotifiableError
316+
class GuestGooglePayNotSupported(data: String? = null) : CoinbaseOnRampWebError(data)
317+
class GuestCardInsufficientBalance(data: String? = null) : CoinbaseOnRampWebError(data)
318+
class GuestCardPrepaidDeclined(data: String? = null) : CoinbaseOnRampWebError(data)
319+
class InvalidBillingName(data: String? = null) : CoinbaseOnRampWebError(data)
283320
class PaymentSheetTimeout(data: String? = null) : CoinbaseOnRampWebError(data)
284321

285322
companion object {
286323
fun fromErrorCode(errorCode: String, data: String? = null): CoinbaseOnRampWebError {
287324
return when (errorCode) {
288-
"ERROR_CODE_MISSING_TRANSACTION_UUID" -> MissingTransactionUuid(data)
289-
"ERROR_CODE_ASSET_NOT_TRADABLE" -> AssetNotTradableInRegion(data)
325+
"ERROR_CODE_MISSING_TRANSACTION_UUID" -> UnknownFailure.MissingTransactionUuid(data)
326+
"ERROR_CODE_GUEST_INVALID_CARD" -> GuestCardNotDebit(data)
290327
"ERROR_CODE_GUEST_CARD_NOT_DEBIT" -> GuestCardNotDebit(data)
291-
"ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" -> GuestGooglePayError(data)
292-
"ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" -> GuestTransactionBuyFailed(data)
293-
"ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" -> GuestTransactionSendFailed(data)
294-
"ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" -> GuestTransactionAvsValidationFailed(data)
295-
"ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" -> GuestTransactionTransactionFailed(data)
328+
"ERROR_CODE_GUEST_TRANSACTION_LIMIT" -> GuestWeeklyTransactionLimitReached(data)
329+
"ERROR_CODE_GUEST_TRANSACTION_COUNT" -> GuestTransactionMaxLimitReached(data)
330+
"ERROR_CODE_GUEST_CARD_RISK_DECLINED" -> GuestCardRiskDeclined(data)
331+
"ERROR_CODE_ASSET_NOT_TRADABLE" -> GuestRegionMismatch(data)
296332
"ERROR_CODE_GUEST_REGION_MISMATCH" -> GuestRegionMismatch(data)
333+
"ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" -> TransactionFailed.GooglePayError(data)
334+
"ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" -> CardDeclined.BuyFailed(data)
335+
"ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" -> TransactionFailed.SendFailed(data)
336+
"ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" -> BillingAddressInvalid.AvsValidationFailed(data)
337+
"ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" -> TransactionFailed.ProcessingFailed(data)
338+
"ERROR_CODE_GUEST_PERMISSION_DENIED" -> GuestPermissionDenied(data)
297339
"ERROR_CODE_GUEST_GOOGLE_PAY_NOT_READY" -> GuestGooglePayNotReady(data)
298-
"ERROR_CODE_INTERNAL" -> Internal(data)
299-
"ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" -> GooglePayButtonNotFound(data)
300-
else -> Unknown(data)
340+
"ERROR_CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED" -> GuestGooglePayNotSupported(data)
341+
"ERROR_CODE_GUEST_CARD_SOFT_DECLINED" -> CardDeclined.Soft(data)
342+
"ERROR_CODE_GUEST_CARD_HARD_DECLINED" -> CardDeclined.Hard(data)
343+
"ERROR_CODE_GUEST_CARD_INSUFFICIENT_BALANCE" -> GuestCardInsufficientBalance(data)
344+
"ERROR_CODE_GUEST_CARD_PREPAID_DECLINED" -> GuestCardPrepaidDeclined(data)
345+
"ERROR_CODE_INVALID_BILLING_ZIP" -> BillingAddressInvalid.InvalidZip(data)
346+
"ERROR_CODE_INVALID_BILLING_ADDRESS" -> BillingAddressInvalid.InvalidAddress(data)
347+
"ERROR_CODE_INVALID_BILLING_NAME" -> InvalidBillingName(data)
348+
"ERROR_CODE_INIT" -> InternalFailure.InitError(data)
349+
"ERROR_CODE_INTERNAL" -> InternalFailure.Internal(data)
350+
"ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" -> InternalFailure.GooglePayButtonNotFound(data)
351+
else -> UnknownFailure.Unknown(data)
301352
}
302353
}
303354
}

0 commit comments

Comments
 (0)