Skip to content

Commit 2a2d6f5

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 2a2d6f5

5 files changed

Lines changed: 203 additions & 85 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: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -62,83 +62,119 @@ 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

80-
is CoinbaseOnRampWebError.GuestRegionMismatch -> {
111+
is CoinbaseOnRampWebError.GuestCardRiskDeclined -> {
81112
BottomBarManager.showAlert(
82-
title = resources.getString(R.string.error_title_onrampRegionMismatch),
83-
message = resources.getString(R.string.error_description_onrampRegionMismatch),
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),
84122
)
85123
}
86124

87-
is CoinbaseOnRampWebError.AssetNotTradableInRegion -> {
125+
is CoinbaseOnRampWebError.RegionNotSupported -> {
88126
BottomBarManager.showAlert(
89127
title = resources.getString(R.string.error_title_onrampRegionMismatch),
90128
message = resources.getString(R.string.error_description_onrampRegionMismatch),
91129
)
92130
}
93131

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),
132+
is CoinbaseOnRampWebError.GuestWeeklyTransactionLimitReached -> {
133+
BottomBarManager.showAlert(
134+
title = resources.getString(R.string.error_title_onrampTransactionLimit),
135+
message = resources.getString(R.string.error_description_onrampTransactionLimit),
98136
)
99137
}
100138

101-
is CoinbaseOnRampWebError.GuestGooglePayNotReady -> {
139+
is CoinbaseOnRampWebError.GuestTransactionMaxLimitReached -> {
102140
BottomBarManager.showAlert(
103-
title = resources.getString(R.string.error_title_onrampGooglePayNotReady),
104-
message = resources.getString(R.string.error_description_onrampGooglePayNotReady),
141+
title = resources.getString(R.string.error_title_onrampTransactionCount),
142+
message = resources.getString(R.string.error_description_onrampTransactionCount),
105143
)
106144
}
107145

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),
146+
is CoinbaseOnRampWebError.GuestGooglePayNotReady -> {
147+
BottomBarManager.showAlert(
148+
title = resources.getString(R.string.error_title_onrampGooglePayNotReady),
149+
message = resources.getString(R.string.error_description_onrampGooglePayNotReady),
112150
)
113151
}
114152

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: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -265,39 +265,95 @@ 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+
/** "Your Region Isn't Supported" — region / asset availability */
308+
sealed class RegionNotSupported(data: String?) : CoinbaseOnRampWebError(data) {
309+
class RegionMismatch(data: String? = null) : RegionNotSupported(data)
310+
class AssetNotTradable(data: String? = null) : RegionNotSupported(data)
311+
}
312+
313+
// --- Single-variant errors ---
314+
271315
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
277-
class GuestRegionMismatch(data: String? = null) : CoinbaseOnRampWebError(data)
278-
class AssetNotTradableInRegion(data: String? = null): CoinbaseOnRampWebError(data)
316+
class GuestCardRiskDeclined(data: String? = null) : CoinbaseOnRampWebError(data)
317+
class GuestPermissionDenied(data: String? = null) : CoinbaseOnRampWebError(data)
318+
class GuestWeeklyTransactionLimitReached(data: String? = null) : CoinbaseOnRampWebError(data)
319+
class GuestTransactionMaxLimitReached(data: String? = null) : CoinbaseOnRampWebError(data)
279320
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
321+
class GuestGooglePayNotSupported(data: String? = null) : CoinbaseOnRampWebError(data)
322+
class GuestCardInsufficientBalance(data: String? = null) : CoinbaseOnRampWebError(data)
323+
class GuestCardPrepaidDeclined(data: String? = null) : CoinbaseOnRampWebError(data)
324+
class InvalidBillingName(data: String? = null) : CoinbaseOnRampWebError(data)
283325
class PaymentSheetTimeout(data: String? = null) : CoinbaseOnRampWebError(data)
284326

285327
companion object {
286328
fun fromErrorCode(errorCode: String, data: String? = null): CoinbaseOnRampWebError {
287329
return when (errorCode) {
288-
"ERROR_CODE_MISSING_TRANSACTION_UUID" -> MissingTransactionUuid(data)
289-
"ERROR_CODE_ASSET_NOT_TRADABLE" -> AssetNotTradableInRegion(data)
330+
"ERROR_CODE_MISSING_TRANSACTION_UUID" -> UnknownFailure.MissingTransactionUuid(data)
331+
"ERROR_CODE_GUEST_INVALID_CARD" -> GuestCardNotDebit(data)
290332
"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)
296-
"ERROR_CODE_GUEST_REGION_MISMATCH" -> GuestRegionMismatch(data)
333+
"ERROR_CODE_GUEST_TRANSACTION_LIMIT" -> GuestWeeklyTransactionLimitReached(data)
334+
"ERROR_CODE_GUEST_TRANSACTION_COUNT" -> GuestTransactionMaxLimitReached(data)
335+
"ERROR_CODE_GUEST_CARD_RISK_DECLINED" -> GuestCardRiskDeclined(data)
336+
"ERROR_CODE_ASSET_NOT_TRADABLE" -> RegionNotSupported.AssetNotTradable(data)
337+
"ERROR_CODE_GUEST_REGION_MISMATCH" -> RegionNotSupported.RegionMismatch(data)
338+
"ERROR_CODE_GUEST_GOOGLE_PAY_ERROR" -> TransactionFailed.GooglePayError(data)
339+
"ERROR_CODE_GUEST_TRANSACTION_BUY_FAILED" -> CardDeclined.BuyFailed(data)
340+
"ERROR_CODE_GUEST_TRANSACTION_SEND_FAILED" -> TransactionFailed.SendFailed(data)
341+
"ERROR_CODE_GUEST_TRANSACTION_AVS_VALIDATION_FAILED" -> BillingAddressInvalid.AvsValidationFailed(data)
342+
"ERROR_CODE_GUEST_TRANSACTION_TRANSACTION_FAILED" -> TransactionFailed.ProcessingFailed(data)
343+
"ERROR_CODE_GUEST_PERMISSION_DENIED" -> GuestPermissionDenied(data)
297344
"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)
345+
"ERROR_CODE_GUEST_GOOGLE_PAY_NOT_SUPPORTED" -> GuestGooglePayNotSupported(data)
346+
"ERROR_CODE_GUEST_CARD_SOFT_DECLINED" -> CardDeclined.Soft(data)
347+
"ERROR_CODE_GUEST_CARD_HARD_DECLINED" -> CardDeclined.Hard(data)
348+
"ERROR_CODE_GUEST_CARD_INSUFFICIENT_BALANCE" -> GuestCardInsufficientBalance(data)
349+
"ERROR_CODE_GUEST_CARD_PREPAID_DECLINED" -> GuestCardPrepaidDeclined(data)
350+
"ERROR_CODE_INVALID_BILLING_ZIP" -> BillingAddressInvalid.InvalidZip(data)
351+
"ERROR_CODE_INVALID_BILLING_ADDRESS" -> BillingAddressInvalid.InvalidAddress(data)
352+
"ERROR_CODE_INVALID_BILLING_NAME" -> InvalidBillingName(data)
353+
"ERROR_CODE_INIT" -> InternalFailure.InitError(data)
354+
"ERROR_CODE_INTERNAL" -> InternalFailure.Internal(data)
355+
"ERROR_CODE_GOOGLE_PAY_BUTTON_NOT_FOUND" -> InternalFailure.GooglePayButtonNotFound(data)
356+
else -> UnknownFailure.Unknown(data)
301357
}
302358
}
303359
}

0 commit comments

Comments
 (0)