mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-07-06 22:15:43 +01:00
Implement Stripe Failure Code support.
This commit is contained in:
committed by
Cody Henthorne
parent
9da5f47623
commit
280da481ee
+8
@@ -4,6 +4,7 @@ import androidx.fragment.app.FragmentManager
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.badges.models.ExpiredBadge
|
||||
@@ -38,6 +39,7 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
val badge: Badge = args.badge
|
||||
val cancellationReason = UnexpectedSubscriptionCancellation.fromStatus(args.cancelationReason)
|
||||
val declineCode: StripeDeclineCode? = args.chargeFailure?.let { StripeDeclineCode.getFromCode(it) }
|
||||
val failureCode: StripeFailureCode? = args.chargeFailure?.let { StripeFailureCode.getFromCode(it) }
|
||||
val isLikelyASustainer = SignalStore.donationsValues().isLikelyASustainer()
|
||||
val inactive = cancellationReason == UnexpectedSubscriptionCancellation.INACTIVE
|
||||
|
||||
@@ -69,6 +71,12 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
|
||||
getString(declineCode.mapToErrorStringResource()),
|
||||
badge.name
|
||||
)
|
||||
} else if (failureCode != null) {
|
||||
getString(
|
||||
R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled_s,
|
||||
getString(failureCode.mapToErrorStringResource()),
|
||||
badge.name
|
||||
)
|
||||
} else if (inactive) {
|
||||
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
|
||||
} else {
|
||||
|
||||
+13
-3
@@ -284,20 +284,30 @@ class DonationCheckoutDelegate(
|
||||
fragment!!.requireContext(),
|
||||
throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
var tryCCAgain = false
|
||||
var tryAgain = false
|
||||
|
||||
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<Unit> {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__try,
|
||||
action = {
|
||||
tryCCAgain = true
|
||||
tryAgain = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTryBankTransferAgain(context: Context): DonationErrorParams.ErrorAction<Unit> {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__try,
|
||||
action = {
|
||||
tryAgain = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDialogDismissed() {
|
||||
errorDialog = null
|
||||
if (!tryCCAgain) {
|
||||
if (!tryAgain) {
|
||||
tryAgain = false
|
||||
fragment!!.findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -8,6 +8,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeError
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
|
||||
sealed class DonationError(val source: DonationErrorSource, cause: Throwable) : Exception(cause) {
|
||||
@@ -64,6 +65,11 @@ sealed class DonationError(val source: DonationErrorSource, cause: Throwable) :
|
||||
*/
|
||||
class StripeDeclinedError(source: DonationErrorSource, cause: Throwable, val declineCode: StripeDeclineCode, val method: PaymentSourceType.Stripe) : PaymentSetupError(source, cause)
|
||||
|
||||
/**
|
||||
* Bank Transfer failed, with a specific reason told to us by Stripe
|
||||
*/
|
||||
class StripeFailureCodeError(source: DonationErrorSource, cause: Throwable, val failureCode: StripeFailureCode, val method: PaymentSourceType.Stripe) : PaymentSetupError(source, cause)
|
||||
|
||||
/**
|
||||
* Payment setup failed in some way, which we are told about by PayPal.
|
||||
*/
|
||||
@@ -186,16 +192,16 @@ sealed class DonationError(val source: DonationErrorSource, cause: Throwable) :
|
||||
@JvmStatic
|
||||
fun getPaymentSetupError(source: DonationErrorSource, throwable: Throwable, method: PaymentSourceType): DonationError {
|
||||
return when (throwable) {
|
||||
is StripeError.PostError -> {
|
||||
val declineCode: StripeDeclineCode? = throwable.declineCode
|
||||
is StripeError.PostError.Generic -> {
|
||||
val errorCode: String? = throwable.errorCode
|
||||
|
||||
when {
|
||||
declineCode != null && method is PaymentSourceType.Stripe -> PaymentSetupError.StripeDeclinedError(source, throwable, declineCode, method)
|
||||
errorCode != null && method is PaymentSourceType.Stripe -> PaymentSetupError.StripeCodedError(source, throwable, errorCode)
|
||||
else -> PaymentSetupError.GenericError(source, throwable)
|
||||
if (errorCode != null) {
|
||||
PaymentSetupError.StripeCodedError(source, throwable, errorCode)
|
||||
} else {
|
||||
PaymentSetupError.GenericError(source, throwable)
|
||||
}
|
||||
}
|
||||
is StripeError.PostError.Declined -> PaymentSetupError.StripeDeclinedError(source, throwable, throwable.declineCode, method as PaymentSourceType.Stripe)
|
||||
is StripeError.PostError.Failed -> PaymentSetupError.StripeFailureCodeError(source, throwable, throwable.failureCode, method as PaymentSourceType.Stripe)
|
||||
|
||||
is UserCancelledPaymentError -> {
|
||||
return throwable
|
||||
|
||||
+7
@@ -68,6 +68,13 @@ object DonationErrorDialogs {
|
||||
)
|
||||
}
|
||||
|
||||
override fun onTryBankTransferAgain(context: Context): DonationErrorParams.ErrorAction<Unit>? {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__try,
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLearnMore(context: Context): DonationErrorParams.ErrorAction<Unit>? {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__learn_more,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ object DonationErrorNotifications {
|
||||
}
|
||||
|
||||
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<PendingIntent>? = null
|
||||
override fun onTryBankTransferAgain(context: Context): DonationErrorParams.ErrorAction<PendingIntent>? = null
|
||||
|
||||
override fun onGoToGooglePay(context: Context): DonationErrorParams.ErrorAction<PendingIntent> {
|
||||
return createAction(
|
||||
|
||||
+54
-7
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
class DonationErrorParams<V> private constructor(
|
||||
@@ -26,15 +27,10 @@ class DonationErrorParams<V> private constructor(
|
||||
return when (throwable) {
|
||||
is DonationError.GiftRecipientVerificationError -> getVerificationErrorParams(context, throwable, callback)
|
||||
is DonationError.PaymentSetupError.StripeDeclinedError -> getStripeDeclinedErrorParams(context, throwable, callback)
|
||||
is DonationError.PaymentSetupError.StripeFailureCodeError -> getStripeFailureCodeErrorParams(context, throwable, callback)
|
||||
is DonationError.PaymentSetupError.PayPalDeclinedError -> getPayPalDeclinedErrorParams(context, throwable, callback)
|
||||
is DonationError.PaymentSetupError -> DonationErrorParams(
|
||||
title = R.string.DonationsErrors__error_processing_payment,
|
||||
message = R.string.DonationsErrors__your_payment,
|
||||
positiveAction = callback.onOk(context),
|
||||
negativeAction = null
|
||||
)
|
||||
is DonationError.PaymentSetupError -> getGenericPaymentSetupErrorParams(context, callback)
|
||||
|
||||
// TODO [sepa] -- This is only used for the notification, and will be rare, but we should probably have better copy here.
|
||||
is DonationError.BadgeRedemptionError.DonationPending -> DonationErrorParams(
|
||||
title = R.string.DonationsErrors__still_processing,
|
||||
message = R.string.DonationsErrors__your_payment_is_still,
|
||||
@@ -110,6 +106,10 @@ class DonationErrorParams<V> private constructor(
|
||||
}
|
||||
|
||||
private fun <V> getStripeDeclinedErrorParams(context: Context, declinedError: DonationError.PaymentSetupError.StripeDeclinedError, callback: Callback<V>): DonationErrorParams<V> {
|
||||
if (!declinedError.method.hasDeclineCodeSupport()) {
|
||||
return getGenericPaymentSetupErrorParams(context, callback)
|
||||
}
|
||||
|
||||
fun unexpectedDeclinedError(declinedError: DonationError.PaymentSetupError.StripeDeclinedError): Nothing {
|
||||
error("Unexpected declined error: ${declinedError.declineCode} during ${declinedError.method} processing.")
|
||||
}
|
||||
@@ -224,6 +224,43 @@ class DonationErrorParams<V> private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <V> getStripeFailureCodeErrorParams(context: Context, failureCodeError: DonationError.PaymentSetupError.StripeFailureCodeError, callback: Callback<V>): DonationErrorParams<V> {
|
||||
if (!failureCodeError.method.hasFailureCodeSupport()) {
|
||||
return getGenericPaymentSetupErrorParams(context, callback)
|
||||
}
|
||||
|
||||
return when (failureCodeError.failureCode) {
|
||||
is StripeFailureCode.Known -> {
|
||||
val errorText = failureCodeError.failureCode.mapToErrorStringResource()
|
||||
when (failureCodeError.failureCode.code) {
|
||||
StripeFailureCode.Code.REFER_TO_CUSTOMER -> getTryBankTransferAgainParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.INSUFFICIENT_FUNDS -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.DEBIT_DISPUTED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.AUTHORIZATION_REVOKED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.DEBIT_NOT_AUTHORIZED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.ACCOUNT_CLOSED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.BANK_ACCOUNT_RESTRICTED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.DEBIT_AUTHORIZATION_NOT_MATCH -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.RECIPIENT_DECEASED -> getLearnMoreParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.BRANCH_DOES_NOT_EXIST -> getTryBankTransferAgainParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.INCORRECT_ACCOUNT_HOLDER_NAME -> getTryBankTransferAgainParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.INVALID_ACCOUNT_NUMBER -> getTryBankTransferAgainParams(context, callback, errorText)
|
||||
StripeFailureCode.Code.GENERIC_COULD_NOT_PROCESS -> getTryBankTransferAgainParams(context, callback, errorText)
|
||||
}
|
||||
}
|
||||
is StripeFailureCode.Unknown -> getGenericPaymentSetupErrorParams(context, callback)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <V> getGenericPaymentSetupErrorParams(context: Context, callback: Callback<V>): DonationErrorParams<V> {
|
||||
return DonationErrorParams(
|
||||
title = R.string.DonationsErrors__error_processing_payment,
|
||||
message = R.string.DonationsErrors__your_payment,
|
||||
positiveAction = callback.onOk(context),
|
||||
negativeAction = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun <V> getLearnMoreParams(context: Context, callback: Callback<V>, message: Int): DonationErrorParams<V> {
|
||||
return DonationErrorParams(
|
||||
title = R.string.DonationsErrors__error_processing_payment,
|
||||
@@ -250,6 +287,15 @@ class DonationErrorParams<V> private constructor(
|
||||
negativeAction = callback.onCancel(context)
|
||||
)
|
||||
}
|
||||
|
||||
private fun <V> getTryBankTransferAgainParams(context: Context, callback: Callback<V>, message: Int): DonationErrorParams<V> {
|
||||
return DonationErrorParams(
|
||||
title = R.string.DonationsErrors__error_processing_payment,
|
||||
message = message,
|
||||
positiveAction = callback.onTryBankTransferAgain(context),
|
||||
negativeAction = callback.onCancel(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback<V> {
|
||||
@@ -259,5 +305,6 @@ class DonationErrorParams<V> private constructor(
|
||||
fun onContactSupport(context: Context): ErrorAction<V>?
|
||||
fun onGoToGooglePay(context: Context): ErrorAction<V>?
|
||||
fun onTryCreditCardAgain(context: Context): ErrorAction<V>?
|
||||
fun onTryBankTransferAgain(context: Context): ErrorAction<V>?
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -2,8 +2,31 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
@StringRes
|
||||
fun StripeFailureCode.mapToErrorStringResource(): Int {
|
||||
return when (this) {
|
||||
is StripeFailureCode.Known -> when (this.code) {
|
||||
StripeFailureCode.Code.REFER_TO_CUSTOMER -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
StripeFailureCode.Code.INSUFFICIENT_FUNDS -> R.string.StripeFailureCode__the_bank_account_provided
|
||||
StripeFailureCode.Code.DEBIT_DISPUTED -> R.string.StripeFailureCode__verify_your_bank_details_are_correct // TODO [sepa] -- verify
|
||||
StripeFailureCode.Code.AUTHORIZATION_REVOKED -> R.string.StripeFailureCode__this_payment_was_revoked
|
||||
StripeFailureCode.Code.DEBIT_NOT_AUTHORIZED -> R.string.StripeFailureCode__this_payment_was_revoked
|
||||
StripeFailureCode.Code.ACCOUNT_CLOSED -> R.string.StripeFailureCode__the_bank_details_provided_could_not_be_processed
|
||||
StripeFailureCode.Code.BANK_ACCOUNT_RESTRICTED -> R.string.StripeFailureCode__the_bank_details_provided_could_not_be_processed
|
||||
StripeFailureCode.Code.DEBIT_AUTHORIZATION_NOT_MATCH -> R.string.StripeFailureCode__an_error_occurred_while_processing_this_payment
|
||||
StripeFailureCode.Code.RECIPIENT_DECEASED -> R.string.StripeFailureCode__the_bank_details_provided_could_not_be_processed
|
||||
StripeFailureCode.Code.BRANCH_DOES_NOT_EXIST -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
StripeFailureCode.Code.INCORRECT_ACCOUNT_HOLDER_NAME -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
StripeFailureCode.Code.INVALID_ACCOUNT_NUMBER -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
StripeFailureCode.Code.GENERIC_COULD_NOT_PROCESS -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
}
|
||||
is StripeFailureCode.Unknown -> R.string.StripeFailureCode__verify_your_bank_details_are_correct
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
fun StripeDeclineCode.mapToErrorStringResource(): Int {
|
||||
return when (this) {
|
||||
|
||||
+4
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors
|
||||
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.internal.push.exceptions.DonationProcessorError
|
||||
|
||||
@@ -18,8 +19,11 @@ fun DonationProcessorError.toDonationError(
|
||||
ActiveSubscription.Processor.STRIPE -> {
|
||||
check(method is PaymentSourceType.Stripe)
|
||||
val declineCode = StripeDeclineCode.getFromCode(chargeFailure.code)
|
||||
val failureCode = StripeFailureCode.getFromCode(chargeFailure.code)
|
||||
if (declineCode.isKnown()) {
|
||||
DonationError.PaymentSetupError.StripeDeclinedError(source, this, declineCode, method)
|
||||
} else if (failureCode.isKnown) {
|
||||
DonationError.PaymentSetupError.StripeFailureCodeError(source, this, failureCode, method)
|
||||
} else if (chargeFailure.code != null) {
|
||||
DonationError.PaymentSetupError.StripeCodedError(source, this, chargeFailure.code)
|
||||
} else {
|
||||
|
||||
+9
@@ -7,6 +7,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.donations.PaymentSourceType;
|
||||
import org.signal.donations.StripeDeclineCode;
|
||||
import org.signal.donations.StripeFailureCode;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||
@@ -308,6 +309,7 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
||||
Log.d(TAG, "Stripe charge failure detected: " + chargeFailure, true);
|
||||
|
||||
StripeDeclineCode declineCode = StripeDeclineCode.Companion.getFromCode(chargeFailure.getOutcomeNetworkReason());
|
||||
StripeFailureCode failureCode = StripeFailureCode.Companion.getFromCode(chargeFailure.getCode());
|
||||
DonationError.PaymentSetupError paymentSetupError;
|
||||
PaymentSourceType paymentSourceType = SignalStore.donationsValues().getSubscriptionPaymentSourceType();
|
||||
boolean isStripeSource = paymentSourceType instanceof PaymentSourceType.Stripe;
|
||||
@@ -319,6 +321,13 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
||||
declineCode,
|
||||
(PaymentSourceType.Stripe) paymentSourceType
|
||||
);
|
||||
} else if (failureCode.isKnown() && isStripeSource) {
|
||||
paymentSetupError = new DonationError.PaymentSetupError.StripeFailureCodeError(
|
||||
getErrorSource(),
|
||||
new Exception(chargeFailure.getMessage()),
|
||||
failureCode,
|
||||
(PaymentSourceType.Stripe) paymentSourceType
|
||||
);
|
||||
} else if (isStripeSource) {
|
||||
paymentSetupError = new DonationError.PaymentSetupError.StripeCodedError(
|
||||
getErrorSource(),
|
||||
|
||||
@@ -4917,6 +4917,18 @@
|
||||
<!-- Stripe decline code incorrect_number and invalid_number for credit cards displayed in a notification or dialog -->
|
||||
<string name="DeclineCode__your_card_number_is_incorrect_verify_your_card_details">Your card number is incorrect. Verify your card details are correct and try again.</string>
|
||||
|
||||
<!-- Stripe Failure Codes for failed bank transfers -->
|
||||
<!-- Failure code text for insufficient funds, displayed in a dialog or notification -->
|
||||
<string name="StripeFailureCode__the_bank_account_provided">The bank account provided has insufficient funds to complete this purchase, try again or contact your bank for more information.</string>
|
||||
<!-- Failure code text for revoked authorization of payment, displayed in a dialog or notification -->
|
||||
<string name="StripeFailureCode__this_payment_was_revoked">This payment was revoked by the account holder and could not be processed. You haven\'t been charged.</string>
|
||||
<!-- Failure code text for a payment lacking an authorized mandate or incorrect mandate, displayed in a dialog or notification -->
|
||||
<string name="StripeFailureCode__an_error_occurred_while_processing_this_payment">An error occurred while processing this payment, please try again.</string>
|
||||
<!-- Failure code text for a closed account, deceased recipient, or one with blocked direct debits, displayed in a dialog or notification -->
|
||||
<string name="StripeFailureCode__the_bank_details_provided_could_not_be_processed">The bank details provided could not be processed, contact your bank for more information.</string>
|
||||
<!-- Failure code text for a non-existent bank branch, invalid account holder, invalid iban, generic failure, or unknown bank failure, displayed in a dialog or notification -->
|
||||
<string name="StripeFailureCode__verify_your_bank_details_are_correct">Verify your bank details are correct and try again. If the problem continues, contact your bank.</string>
|
||||
|
||||
<!-- Title of create notification profile screen -->
|
||||
<string name="EditNotificationProfileFragment__name_your_profile">Name your profile</string>
|
||||
<!-- Hint text for create/edit notification profile name -->
|
||||
|
||||
@@ -16,6 +16,9 @@ sealed class PaymentSourceType {
|
||||
object CreditCard : Stripe(Codes.CREDIT_CARD.code, "CARD", false)
|
||||
object GooglePay : Stripe(Codes.GOOGLE_PAY.code, "CARD", false)
|
||||
object SEPADebit : Stripe(Codes.SEPA_DEBIT.code, "SEPA_DEBIT", true)
|
||||
|
||||
fun hasDeclineCodeSupport(): Boolean = this !is SEPADebit
|
||||
fun hasFailureCodeSupport(): Boolean = this is SEPADebit
|
||||
}
|
||||
|
||||
private enum class Codes(val code: String) {
|
||||
|
||||
@@ -305,12 +305,15 @@ class StripeApi(
|
||||
val body = response.body()?.string()
|
||||
val errorCode = parseErrorCode(body)
|
||||
val declineCode = parseDeclineCode(body) ?: StripeDeclineCode.getFromCode(errorCode)
|
||||
val failureCode = parseFailureCode(body) ?: StripeFailureCode.getFromCode(errorCode)
|
||||
|
||||
throw StripeError.PostError(
|
||||
response.code(),
|
||||
errorCode,
|
||||
declineCode
|
||||
)
|
||||
if (failureCode is StripeFailureCode.Known) {
|
||||
throw StripeError.PostError.Failed(response.code(), failureCode)
|
||||
} else if (declineCode is StripeDeclineCode.Known) {
|
||||
throw StripeError.PostError.Declined(response.code(), declineCode)
|
||||
} else {
|
||||
throw StripeError.PostError.Generic(response.code(), errorCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +345,20 @@ class StripeApi(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseFailureCode(body: String?): StripeFailureCode? {
|
||||
if (body == null) {
|
||||
Log.d(TAG, "parseFailureCode: No body.", true)
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
StripeFailureCode.getFromCode(JSONObject(body).getJSONObject("error").getString("failure_code"))
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "parseFailureCode: Failed to parse failure code.", e, true)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
object Validation {
|
||||
private val MAX_AMOUNT = BigDecimal(99_999_999)
|
||||
|
||||
|
||||
@@ -7,5 +7,11 @@ sealed class StripeError(message: String) : Exception(message) {
|
||||
class FailedToParseSetupIntentResponseError(invalidDefCause: InvalidDefinitionException?) : StripeError("Failed to parse setup intent response: ${invalidDefCause?.type} ${invalidDefCause?.property} ${invalidDefCause?.beanDescription}")
|
||||
object FailedToParsePaymentMethodResponseError : StripeError("Failed to parse payment method response")
|
||||
object FailedToCreatePaymentSourceFromCardData : StripeError("Failed to create payment source from card data")
|
||||
class PostError(val statusCode: Int, val errorCode: String?, val declineCode: StripeDeclineCode?) : StripeError("postForm failed with code: $statusCode. errorCode: $errorCode. declineCode: $declineCode")
|
||||
sealed class PostError(
|
||||
override val message: String
|
||||
) : StripeError(message) {
|
||||
class Generic(statusCode: Int, val errorCode: String?) : PostError("postForm failed with code: $statusCode errorCode: $errorCode")
|
||||
class Declined(statusCode: Int, val declineCode: StripeDeclineCode) : PostError("postForm failed with code: $statusCode declineCode: $declineCode")
|
||||
class Failed(statusCode: Int, val failureCode: StripeFailureCode) : PostError("postForm failed with code: $statusCode failureCode: $failureCode")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.donations
|
||||
|
||||
/**
|
||||
* Bank Transfer failure codes, as detailed here:
|
||||
* https://stripe.com/docs/payments/sepa-debit#failed-payments
|
||||
*/
|
||||
sealed interface StripeFailureCode {
|
||||
data class Known(val code: Code) : StripeFailureCode
|
||||
data class Unknown(val code: String) : StripeFailureCode
|
||||
|
||||
val isKnown get() = this is Known
|
||||
enum class Code(val code: String) {
|
||||
REFER_TO_CUSTOMER("refer_to_customer"),
|
||||
INSUFFICIENT_FUNDS("insufficient_funds"),
|
||||
DEBIT_DISPUTED("debit_disputed"),
|
||||
AUTHORIZATION_REVOKED("authorization_revoked"),
|
||||
DEBIT_NOT_AUTHORIZED("debit_not_authorized"),
|
||||
ACCOUNT_CLOSED("account_closed"),
|
||||
BANK_ACCOUNT_RESTRICTED("bank_account_restricted"),
|
||||
DEBIT_AUTHORIZATION_NOT_MATCH("debit_authorization_not_match"),
|
||||
RECIPIENT_DECEASED("recipient_deceased"),
|
||||
BRANCH_DOES_NOT_EXIST("branch_does_not_exist"),
|
||||
INCORRECT_ACCOUNT_HOLDER_NAME("incorrect_account_holder_name"),
|
||||
INVALID_ACCOUNT_NUMBER("invalid_account_number"),
|
||||
GENERIC_COULD_NOT_PROCESS("generic_could_not_process")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getFromCode(code: String?): StripeFailureCode {
|
||||
if (code == null) {
|
||||
return Unknown("null")
|
||||
}
|
||||
|
||||
val typedCode: Code? = Code.values().firstOrNull { it.code == code }
|
||||
return typedCode?.let { Known(typedCode) } ?: Unknown(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user