Add better error handling for subscriptions.

This commit is contained in:
Alex Hart
2022-02-10 14:26:59 -04:00
committed by GitHub
parent ae0d6b5926
commit 5a6d77bae4
35 changed files with 978 additions and 247 deletions

View File

@@ -10,7 +10,6 @@ import okhttp3.Response
import okio.ByteString
import org.json.JSONObject
import org.signal.core.util.money.FiatMoney
import java.io.IOException
import java.math.BigDecimal
import java.util.Locale
@@ -90,7 +89,7 @@ class StripeApi(
val paymentMethodObject = body.string().replace("\n", "").let { JSONObject(it) }
paymentMethodObject.getString("id")
} else {
throw IOException("Failed to parse payment method response")
throw StripeError.FailedToParsePaymentMethodResponseError
}
}
}
@@ -128,19 +127,36 @@ class StripeApi(
if (response.isSuccessful) {
return response
} else {
throw IOException("postForm failed with code: ${response.code()}. errorCode: ${parseErrorCode(response.body()?.string())}")
val body = response.body()?.toString()
throw StripeError.PostError(
response.code(),
parseErrorCode(body),
parseDeclineCode(body)
)
}
}
private fun parseErrorCode(body: String?): String? {
if (body == null) {
return "No body."
return null
}
return try {
JSONObject(body).getJSONObject("error").getString("code")
} catch (e: Exception) {
"Unable to parse error code."
null
}
}
private fun parseDeclineCode(body: String?): StripeDeclineCode? {
if (body == null) {
return null
}
return try {
StripeDeclineCode.getFromCode(JSONObject(body).getJSONObject("error").getString("decline_code"))
} catch (e: Exception) {
null
}
}

View File

@@ -0,0 +1,59 @@
package org.signal.donations
/**
* Stripe Payment Processor decline codes
*/
sealed class StripeDeclineCode {
data class Known(val code: Code) : StripeDeclineCode()
data class Unknown(val code: String) : StripeDeclineCode()
enum class Code(val code: String) {
AUTHENTICATION_REQUIRED("authentication_required"),
APPROVE_WITH_ID("approve_with_id"),
CALL_ISSUER("call_issuer"),
CARD_NOT_SUPPORTED("card_not_supported"),
CARD_VELOCITY_EXCEEDED("card_velocity_exceeded"),
CURRENCY_NOT_SUPPORTED("currency_not_supported"),
DO_NOT_HONOR("do_not_honor"),
DO_NOT_TRY_AGAIN("do_not_try_again"),
DUPLICATE_TRANSACTION("duplicate_transaction"),
EXPIRED_CARD("expired_card"),
FRAUDULENT("fraudulent"),
GENERIC_DECLINE("generic_decline"),
INCORRECT_NUMBER("incorrect_number"),
INCORRECT_CVC("incorrect_cvc"),
INSUFFICIENT_FUNDS("insufficient_funds"),
INVALID_ACCOUNT("invalid_account"),
INVALID_AMOUNT("invalid_amount"),
INVALID_CVC("invalid_cvc"),
INVALID_EXPIRY_MONTH("invalid_expiry_month"),
INVALID_EXPIRY_YEAR("invalid_expiry_year"),
INVALID_NUMBER("invalid_number"),
ISSUER_NOT_AVAILABLE("issuer_not_available"),
LOST_CARD("lost_card"),
MERCHANT_BLACKLIST("merchant_blacklist"),
NEW_ACCOUNT_INFORMATION_AVAILABLE("new_account_information_available"),
NO_ACTION_TAKEN("no_action_taken"),
NOT_PERMITTED("not_permitted"),
PROCESSING_ERROR("processing_error"),
REENTER_TRANSACTION("reenter_transaction"),
RESTRICTED_CARD("restricted_card"),
REVOCATION_OF_ALL_AUTHORIZATIONS("revocation_of_all_authorizations"),
REVOCATION_OF_AUTHORIZATION("revocation_of_authorization"),
SECURITY_VIOLATION("security_violation"),
SERVICE_NOT_ALLOWED("service_not_allowed"),
STOLEN_CARD("stolen_card"),
STOP_PAYMENT_ORDER("stop_payment_order"),
TRANSACTION_NOT_ALLOWED("transaction_not_allowed"),
TRY_AGAIN_LATER("try_again_later"),
WITHDRAWAL_COUNT_LIMIT_EXCEEDED("withdrawal_count_limit_exceeded")
}
companion object {
fun getFromCode(code: String): StripeDeclineCode {
val typedCode: Code? = Code.values().firstOrNull { it.code == code }
return typedCode?.let { Known(typedCode) } ?: Unknown(code)
}
}
}

View File

@@ -0,0 +1,6 @@
package org.signal.donations
sealed class StripeError(message: String) : Exception(message) {
object FailedToParsePaymentMethodResponseError : StripeError("Failed to parse payment method response")
class PostError(val statusCode: Int, val errorCode: String?, val declineCode: StripeDeclineCode?) : StripeError("postForm failed with code: $statusCode. errorCode: $errorCode. declineCode: $declineCode")
}