Add new error strings for credit cards.

main
Alex Hart 2022-11-29 11:01:07 -04:00 zatwierdzone przez GitHub
rodzic f6356c9720
commit eee4ff3f87
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
14 zmienionych plików z 200 dodań i 34 usunięć

Wyświetl plik

@ -11,6 +11,7 @@ import org.signal.core.util.money.FiatMoney
import org.signal.donations.GooglePayApi import org.signal.donations.GooglePayApi
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.signal.donations.StripeIntentAccessor import org.signal.donations.StripeIntentAccessor
import org.signal.donations.StripePaymentSourceType
import org.signal.donations.json.StripeIntentStatus import org.signal.donations.json.StripeIntentStatus
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
@ -86,12 +87,13 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
price: FiatMoney, price: FiatMoney,
badgeRecipient: RecipientId, badgeRecipient: RecipientId,
badgeLevel: Long, badgeLevel: Long,
paymentSourceType: StripePaymentSourceType
): Single<StripeIntentAccessor> { ): Single<StripeIntentAccessor> {
Log.d(TAG, "Creating payment intent for $price...", true) Log.d(TAG, "Creating payment intent for $price...", true)
return stripeApi.createPaymentIntent(price, badgeLevel) return stripeApi.createPaymentIntent(price, badgeLevel)
.onErrorResumeNext { .onErrorResumeNext {
handleCreatePaymentIntentError(it, badgeRecipient) handleCreatePaymentIntentError(it, badgeRecipient, paymentSourceType)
} }
.flatMap { result -> .flatMap { result ->
val recipient = Recipient.resolved(badgeRecipient) val recipient = Recipient.resolved(badgeRecipient)
@ -127,7 +129,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
Log.d(TAG, "Confirming payment intent...", true) Log.d(TAG, "Confirming payment intent...", true)
return stripeApi.confirmPaymentIntent(paymentSource, paymentIntent) return stripeApi.confirmPaymentIntent(paymentSource, paymentIntent)
.onErrorResumeNext { .onErrorResumeNext {
Single.error(DonationError.getPaymentSetupError(donationErrorSource, it)) Single.error(DonationError.getPaymentSetupError(donationErrorSource, it, paymentSource.type))
} }
} }
@ -196,7 +198,10 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
} }
} }
fun setDefaultPaymentMethod(paymentMethodId: String): Completable { fun setDefaultPaymentMethod(
paymentMethodId: String,
paymentSourceType: StripePaymentSourceType
): Completable {
return Single.fromCallable { return Single.fromCallable {
Log.d(TAG, "Getting the subscriber...") Log.d(TAG, "Getting the subscriber...")
SignalStore.donationsValues().requireSubscriber() SignalStore.donationsValues().requireSubscriber()
@ -209,6 +214,9 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
} }
}.flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().doOnComplete { }.flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().doOnComplete {
Log.d(TAG, "Set default payment method via Signal service!") Log.d(TAG, "Set default payment method via Signal service!")
}.andThen {
Log.d(TAG, "Storing the subscription payment source type locally.")
SignalStore.donationsValues().setSubscriptionPaymentSourceType(paymentSourceType)
} }
} }
@ -216,7 +224,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
Log.d(TAG, "Creating credit card payment source via Stripe api...") Log.d(TAG, "Creating credit card payment source via Stripe api...")
return stripeApi.createPaymentSourceFromCardData(cardData).map { return stripeApi.createPaymentSourceFromCardData(cardData).map {
when (it) { when (it) {
is StripeApi.CreatePaymentSourceFromCardDataResult.Failure -> throw DonationError.getPaymentSetupError(donationErrorSource, it.reason) is StripeApi.CreatePaymentSourceFromCardDataResult.Failure -> throw DonationError.getPaymentSetupError(donationErrorSource, it.reason, StripePaymentSourceType.CREDIT_CARD)
is StripeApi.CreatePaymentSourceFromCardDataResult.Success -> it.paymentSource is StripeApi.CreatePaymentSourceFromCardDataResult.Success -> it.paymentSource
} }
} }
@ -230,13 +238,13 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
companion object { companion object {
private val TAG = Log.tag(StripeRepository::class.java) private val TAG = Log.tag(StripeRepository::class.java)
fun <T> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId): Single<T> { private fun <T> handleCreatePaymentIntentError(throwable: Throwable, badgeRecipient: RecipientId, paymentSourceType: StripePaymentSourceType): Single<T> {
return if (throwable is DonationError) { return if (throwable is DonationError) {
Single.error(throwable) Single.error(throwable)
} else { } else {
val recipient = Recipient.resolved(badgeRecipient) val recipient = Recipient.resolved(badgeRecipient)
val errorSource = if (recipient.isSelf) DonationErrorSource.BOOST else DonationErrorSource.GIFT val errorSource = if (recipient.isSelf) DonationErrorSource.BOOST else DonationErrorSource.GIFT
Single.error(DonationError.getPaymentSetupError(errorSource, throwable)) Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
} }
} }
} }

Wyświetl plik

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate package org.thoughtcrime.securesms.components.settings.app.subscription.donate
import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
import android.view.View import android.view.View
@ -31,6 +32,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boo
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorParams
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
@ -395,9 +397,22 @@ class DonateToSignalFragment :
errorDialog = DonationErrorDialogs.show( errorDialog = DonationErrorDialogs.show(
requireContext(), throwable, requireContext(), throwable,
object : DonationErrorDialogs.DialogCallback() { object : DonationErrorDialogs.DialogCallback() {
var tryCCAgain = false
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<Unit>? {
return DonationErrorParams.ErrorAction(
label = R.string.DeclineCode__try,
action = {
tryCCAgain = true
}
)
}
override fun onDialogDismissed() { override fun onDialogDismissed() {
errorDialog = null errorDialog = null
findNavController().popBackStack() if (!tryCCAgain) {
findNavController().popBackStack()
}
} }
} }
) )

Wyświetl plik

@ -14,6 +14,7 @@ import org.signal.core.util.logging.Log
import org.signal.donations.GooglePayPaymentSource import org.signal.donations.GooglePayPaymentSource
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.signal.donations.StripeIntentAccessor import org.signal.donations.StripeIntentAccessor
import org.signal.donations.StripePaymentSourceType
import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeDonationRepository import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeDonationRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
@ -75,7 +76,7 @@ class StripePaymentInProgressViewModel(
DonateToSignalType.GIFT -> DonationErrorSource.GIFT DonateToSignalType.GIFT -> DonationErrorSource.GIFT
} }
val paymentSourceProvider: Single<StripeApi.PaymentSource> = resolvePaymentSourceProvider(errorSource) val paymentSourceProvider: PaymentSourceProvider = resolvePaymentSourceProvider(errorSource)
return when (request.donateToSignalType) { return when (request.donateToSignalType) {
DonateToSignalType.MONTHLY -> proceedMonthly(request, paymentSourceProvider, nextActionHandler) DonateToSignalType.MONTHLY -> proceedMonthly(request, paymentSourceProvider, nextActionHandler)
@ -84,17 +85,23 @@ class StripePaymentInProgressViewModel(
} }
} }
private fun resolvePaymentSourceProvider(errorSource: DonationErrorSource): Single<StripeApi.PaymentSource> { private fun resolvePaymentSourceProvider(errorSource: DonationErrorSource): PaymentSourceProvider {
val paymentData = this.paymentData val paymentData = this.paymentData
val cardData = this.cardData val cardData = this.cardData
return when { return when {
paymentData == null && cardData == null -> error("No payment provider available.") paymentData == null && cardData == null -> error("No payment provider available.")
paymentData != null && cardData != null -> error("Too many providers available") paymentData != null && cardData != null -> error("Too many providers available")
paymentData != null -> Single.just<StripeApi.PaymentSource>(GooglePayPaymentSource(paymentData)) paymentData != null -> PaymentSourceProvider(
cardData != null -> stripeRepository.createCreditCardPaymentSource(errorSource, cardData) StripePaymentSourceType.GOOGLE_PAY,
Single.just<StripeApi.PaymentSource>(GooglePayPaymentSource(paymentData)).doAfterTerminate { clearPaymentInformation() }
)
cardData != null -> PaymentSourceProvider(
StripePaymentSourceType.CREDIT_CARD,
stripeRepository.createCreditCardPaymentSource(errorSource, cardData).doAfterTerminate { clearPaymentInformation() }
)
else -> error("This should never happen.") else -> error("This should never happen.")
}.doAfterTerminate { clearPaymentInformation() } }
} }
fun providePaymentData(paymentData: PaymentData) { fun providePaymentData(paymentData: PaymentData) {
@ -118,9 +125,9 @@ class StripePaymentInProgressViewModel(
cardData = null cardData = null
} }
private fun proceedMonthly(request: GatewayRequest, paymentSourceProvider: Single<StripeApi.PaymentSource>, nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>) { private fun proceedMonthly(request: GatewayRequest, paymentSourceProvider: PaymentSourceProvider, nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>) {
val ensureSubscriberId: Completable = monthlyDonationRepository.ensureSubscriberId() val ensureSubscriberId: Completable = monthlyDonationRepository.ensureSubscriberId()
val createAndConfirmSetupIntent: Single<StripeApi.Secure3DSAction> = paymentSourceProvider.flatMap { stripeRepository.createAndConfirmSetupIntent(it) } val createAndConfirmSetupIntent: Single<StripeApi.Secure3DSAction> = paymentSourceProvider.paymentSource.flatMap { stripeRepository.createAndConfirmSetupIntent(it) }
val setLevel: Completable = monthlyDonationRepository.setSubscriptionLevel(request.level.toString()) val setLevel: Completable = monthlyDonationRepository.setSubscriptionLevel(request.level.toString())
Log.d(TAG, "Starting subscription payment pipeline...", true) Log.d(TAG, "Starting subscription payment pipeline...", true)
@ -134,12 +141,12 @@ class StripePaymentInProgressViewModel(
.flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult) } .flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult) }
.map { (_, paymentMethod) -> paymentMethod ?: secure3DSAction.paymentMethodId!! } .map { (_, paymentMethod) -> paymentMethod ?: secure3DSAction.paymentMethodId!! }
} }
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it) } .flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it, paymentSourceProvider.paymentSourceType) }
.onErrorResumeNext { .onErrorResumeNext {
if (it is DonationError) { if (it is DonationError) {
Completable.error(it) Completable.error(it)
} else { } else {
Completable.error(DonationError.getPaymentSetupError(DonationErrorSource.SUBSCRIPTION, it)) Completable.error(DonationError.getPaymentSetupError(DonationErrorSource.SUBSCRIPTION, it, paymentSourceProvider.paymentSourceType))
} }
} }
@ -164,15 +171,15 @@ class StripePaymentInProgressViewModel(
private fun proceedOneTime( private fun proceedOneTime(
request: GatewayRequest, request: GatewayRequest,
paymentSourceProvider: Single<StripeApi.PaymentSource>, paymentSourceProvider: PaymentSourceProvider,
nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor> nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>
) { ) {
Log.w(TAG, "Beginning one-time payment pipeline...", true) Log.w(TAG, "Beginning one-time payment pipeline...", true)
val amount = request.fiat val amount = request.fiat
val continuePayment: Single<StripeIntentAccessor> = stripeRepository.continuePayment(amount, request.recipientId, request.level) val continuePayment: Single<StripeIntentAccessor> = stripeRepository.continuePayment(amount, request.recipientId, request.level, paymentSourceProvider.paymentSourceType)
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider, ::Pair) val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider.paymentSource, ::Pair)
disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) -> disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) ->
stripeRepository.confirmPayment(paymentSource, paymentIntent, request.recipientId) stripeRepository.confirmPayment(paymentSource, paymentIntent, request.recipientId)
@ -249,6 +256,11 @@ class StripePaymentInProgressViewModel(
) )
} }
private data class PaymentSourceProvider(
val paymentSourceType: StripePaymentSourceType,
val paymentSource: Single<StripeApi.PaymentSource>
)
class Factory( class Factory(
private val stripeRepository: StripeRepository, private val stripeRepository: StripeRepository,
private val monthlyDonationRepository: MonthlyDonationRepository = MonthlyDonationRepository(ApplicationDependencies.getDonationsService()), private val monthlyDonationRepository: MonthlyDonationRepository = MonthlyDonationRepository(ApplicationDependencies.getDonationsService()),

Wyświetl plik

@ -7,6 +7,7 @@ import io.reactivex.rxjava3.subjects.Subject
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.donations.StripeDeclineCode import org.signal.donations.StripeDeclineCode
import org.signal.donations.StripeError import org.signal.donations.StripeError
import org.signal.donations.StripePaymentSourceType
sealed class DonationError(val source: DonationErrorSource, cause: Throwable) : Exception(cause) { sealed class DonationError(val source: DonationErrorSource, cause: Throwable) : Exception(cause) {
@ -55,7 +56,7 @@ sealed class DonationError(val source: DonationErrorSource, cause: Throwable) :
/** /**
* Payment failed by the credit card processor, with a specific reason told to us by Stripe. * Payment failed by the credit card processor, with a specific reason told to us by Stripe.
*/ */
class DeclinedError(source: DonationErrorSource, cause: Throwable, val declineCode: StripeDeclineCode) : PaymentSetupError(source, cause) class DeclinedError(source: DonationErrorSource, cause: Throwable, val declineCode: StripeDeclineCode, val method: StripePaymentSourceType) : PaymentSetupError(source, cause)
} }
/** /**
@ -132,13 +133,13 @@ sealed class DonationError(val source: DonationErrorSource, cause: Throwable) :
* charge has occurred. * charge has occurred.
*/ */
@JvmStatic @JvmStatic
fun getPaymentSetupError(source: DonationErrorSource, throwable: Throwable): DonationError { fun getPaymentSetupError(source: DonationErrorSource, throwable: Throwable, method: StripePaymentSourceType): DonationError {
return if (throwable is StripeError.PostError) { return if (throwable is StripeError.PostError) {
val declineCode: StripeDeclineCode? = throwable.declineCode val declineCode: StripeDeclineCode? = throwable.declineCode
val errorCode: String? = throwable.errorCode val errorCode: String? = throwable.errorCode
when { when {
declineCode != null -> PaymentSetupError.DeclinedError(source, throwable, declineCode) declineCode != null -> PaymentSetupError.DeclinedError(source, throwable, declineCode, method)
errorCode != null -> PaymentSetupError.CodedError(source, throwable, errorCode) errorCode != null -> PaymentSetupError.CodedError(source, throwable, errorCode)
else -> PaymentSetupError.GenericError(source, throwable) else -> PaymentSetupError.GenericError(source, throwable)
} }

Wyświetl plik

@ -36,7 +36,7 @@ object DonationErrorDialogs {
return builder.show() return builder.show()
} }
open class DialogCallback : DonationErrorParams.Callback<Unit> { abstract class DialogCallback : DonationErrorParams.Callback<Unit> {
override fun onCancel(context: Context): DonationErrorParams.ErrorAction<Unit>? { override fun onCancel(context: Context): DonationErrorParams.ErrorAction<Unit>? {
return DonationErrorParams.ErrorAction( return DonationErrorParams.ErrorAction(
@ -61,6 +61,13 @@ object DonationErrorDialogs {
) )
} }
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<Unit>? {
return DonationErrorParams.ErrorAction(
label = R.string.DeclineCode__try,
action = {}
)
}
override fun onLearnMore(context: Context): DonationErrorParams.ErrorAction<Unit>? { override fun onLearnMore(context: Context): DonationErrorParams.ErrorAction<Unit>? {
return DonationErrorParams.ErrorAction( return DonationErrorParams.ErrorAction(
label = R.string.DeclineCode__learn_more, label = R.string.DeclineCode__learn_more,

Wyświetl plik

@ -63,6 +63,8 @@ object DonationErrorNotifications {
) )
} }
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<PendingIntent>? = null
override fun onGoToGooglePay(context: Context): DonationErrorParams.ErrorAction<PendingIntent> { override fun onGoToGooglePay(context: Context): DonationErrorParams.ErrorAction<PendingIntent> {
return createAction( return createAction(
context = context, context = context,

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors
import android.content.Context import android.content.Context
import androidx.annotation.StringRes import androidx.annotation.StringRes
import org.signal.donations.StripeDeclineCode import org.signal.donations.StripeDeclineCode
import org.signal.donations.StripePaymentSourceType
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
class DonationErrorParams<V> private constructor( class DonationErrorParams<V> private constructor(
@ -88,19 +89,78 @@ class DonationErrorParams<V> private constructor(
} }
private fun <V> getDeclinedErrorParams(context: Context, declinedError: DonationError.PaymentSetupError.DeclinedError, callback: Callback<V>): DonationErrorParams<V> { private fun <V> getDeclinedErrorParams(context: Context, declinedError: DonationError.PaymentSetupError.DeclinedError, callback: Callback<V>): DonationErrorParams<V> {
val getStripeDeclineCodePositiveActionParams: (Context, Callback<V>, Int) -> DonationErrorParams<V> = when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> this::getTryCreditCardAgainParams
StripePaymentSourceType.GOOGLE_PAY -> this::getGoToGooglePayParams
}
return when (declinedError.declineCode) { return when (declinedError.declineCode) {
is StripeDeclineCode.Known -> when (declinedError.declineCode.code) { is StripeDeclineCode.Known -> when (declinedError.declineCode.code) {
StripeDeclineCode.Code.APPROVE_WITH_ID -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again) StripeDeclineCode.Code.APPROVE_WITH_ID -> getStripeDeclineCodePositiveActionParams(
StripeDeclineCode.Code.CALL_ISSUER -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again_if_the_problem) context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__verify_your_card_details_are_correct_and_try_again
}
)
StripeDeclineCode.Code.CALL_ISSUER -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__verify_your_card_details_are_correct_and_try_again_if_the_problem_continues
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__verify_your_payment_method_is_up_to_date_in_google_pay_and_try_again_if_the_problem
}
)
StripeDeclineCode.Code.CARD_NOT_SUPPORTED -> getLearnMoreParams(context, callback, R.string.DeclineCode__your_card_does_not_support_this_type_of_purchase) StripeDeclineCode.Code.CARD_NOT_SUPPORTED -> getLearnMoreParams(context, callback, R.string.DeclineCode__your_card_does_not_support_this_type_of_purchase)
StripeDeclineCode.Code.EXPIRED_CARD -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_has_expired) StripeDeclineCode.Code.EXPIRED_CARD -> getStripeDeclineCodePositiveActionParams(
StripeDeclineCode.Code.INCORRECT_NUMBER -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_number_is_incorrect) context, callback,
StripeDeclineCode.Code.INCORRECT_CVC -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_cards_cvc_number_is_incorrect) when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__your_card_has_expired_verify_your_card_details
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__your_card_has_expired
}
)
StripeDeclineCode.Code.INCORRECT_NUMBER -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__your_card_number_is_incorrect_verify_your_card_details
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__your_card_number_is_incorrect
}
)
StripeDeclineCode.Code.INCORRECT_CVC -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect_verify_your_card_details
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect
}
)
StripeDeclineCode.Code.INSUFFICIENT_FUNDS -> getLearnMoreParams(context, callback, R.string.DeclineCode__your_card_does_not_have_sufficient_funds) StripeDeclineCode.Code.INSUFFICIENT_FUNDS -> getLearnMoreParams(context, callback, R.string.DeclineCode__your_card_does_not_have_sufficient_funds)
StripeDeclineCode.Code.INVALID_CVC -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_cards_cvc_number_is_incorrect) StripeDeclineCode.Code.INVALID_CVC -> getStripeDeclineCodePositiveActionParams(
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__the_expiration_month) context, callback,
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__the_expiration_year) when (declinedError.method) {
StripeDeclineCode.Code.INVALID_NUMBER -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_number_is_incorrect) StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect_verify_your_card_details
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__your_cards_cvc_number_is_incorrect
}
)
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__the_expiration_month_on_your_card_is_incorrect
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__the_expiration_month
}
)
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__the_expiration_year_on_your_card_is_incorrect
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__the_expiration_year
}
)
StripeDeclineCode.Code.INVALID_NUMBER -> getStripeDeclineCodePositiveActionParams(
context, callback,
when (declinedError.method) {
StripePaymentSourceType.CREDIT_CARD -> R.string.DeclineCode__your_card_number_is_incorrect_verify_your_card_details
StripePaymentSourceType.GOOGLE_PAY -> R.string.DeclineCode__your_card_number_is_incorrect
}
)
StripeDeclineCode.Code.ISSUER_NOT_AVAILABLE -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_completing_the_payment_again) StripeDeclineCode.Code.ISSUER_NOT_AVAILABLE -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_completing_the_payment_again)
StripeDeclineCode.Code.PROCESSING_ERROR -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_again) StripeDeclineCode.Code.PROCESSING_ERROR -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_again)
StripeDeclineCode.Code.REENTER_TRANSACTION -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_again) StripeDeclineCode.Code.REENTER_TRANSACTION -> getLearnMoreParams(context, callback, R.string.DeclineCode__try_again)
@ -127,6 +187,15 @@ class DonationErrorParams<V> private constructor(
negativeAction = callback.onCancel(context) negativeAction = callback.onCancel(context)
) )
} }
private fun <V> getTryCreditCardAgainParams(context: Context, callback: Callback<V>, message: Int): DonationErrorParams<V> {
return DonationErrorParams(
title = R.string.DonationsErrors__error_processing_payment,
message = message,
positiveAction = callback.onTryCreditCardAgain(context),
negativeAction = callback.onCancel(context)
)
}
} }
interface Callback<V> { interface Callback<V> {
@ -135,5 +204,6 @@ class DonationErrorParams<V> private constructor(
fun onLearnMore(context: Context): ErrorAction<V>? fun onLearnMore(context: Context): ErrorAction<V>?
fun onContactSupport(context: Context): ErrorAction<V>? fun onContactSupport(context: Context): ErrorAction<V>?
fun onGoToGooglePay(context: Context): ErrorAction<V>? fun onGoToGooglePay(context: Context): ErrorAction<V>?
fun onTryCreditCardAgain(context: Context): ErrorAction<V>?
} }
} }

Wyświetl plik

@ -300,7 +300,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
paymentSetupError = new DonationError.PaymentSetupError.DeclinedError( paymentSetupError = new DonationError.PaymentSetupError.DeclinedError(
getErrorSource(), getErrorSource(),
new Exception(chargeFailure.getMessage()), new Exception(chargeFailure.getMessage()),
declineCode declineCode,
SignalStore.donationsValues().getSubscriptionPaymentSourceType()
); );
} else { } else {
paymentSetupError = new DonationError.PaymentSetupError.CodedError( paymentSetupError = new DonationError.PaymentSetupError.CodedError(

Wyświetl plik

@ -6,6 +6,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.Subject import io.reactivex.rxjava3.subjects.Subject
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.donations.StripeApi import org.signal.donations.StripeApi
import org.signal.donations.StripePaymentSourceType
import org.signal.libsignal.zkgroup.InvalidInputException import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.VerificationFailedException import org.signal.libsignal.zkgroup.VerificationFailedException
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
@ -95,6 +96,12 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
* assumed that there is no work to be done. * assumed that there is no work to be done.
*/ */
private const val SUBSCRIPTION_EOP_REDEEMED = "subscription.eop.redeemed" private const val SUBSCRIPTION_EOP_REDEEMED = "subscription.eop.redeemed"
/**
* Notes the type of payment the user utilized for the latest subscription. This is useful
* in determining which error messaging they should see if something goes wrong.
*/
private const val SUBSCRIPTION_PAYMENT_SOURCE_TYPE = "subscription.payment.source.type"
} }
override fun onFirstEverAppLaunch() = Unit override fun onFirstEverAppLaunch() = Unit
@ -112,7 +119,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
SUBSCRIPTION_CREDENTIAL_RECEIPT, SUBSCRIPTION_CREDENTIAL_RECEIPT,
SUBSCRIPTION_EOP_STARTED_TO_CONVERT, SUBSCRIPTION_EOP_STARTED_TO_CONVERT,
SUBSCRIPTION_EOP_STARTED_TO_REDEEM, SUBSCRIPTION_EOP_STARTED_TO_REDEEM,
SUBSCRIPTION_EOP_REDEEMED SUBSCRIPTION_EOP_REDEEMED,
SUBSCRIPTION_PAYMENT_SOURCE_TYPE
) )
private val subscriptionCurrencyPublisher: Subject<Currency> by lazy { BehaviorSubject.createDefault(getSubscriptionCurrency()) } private val subscriptionCurrencyPublisher: Subject<Currency> by lazy { BehaviorSubject.createDefault(getSubscriptionCurrency()) }
@ -442,6 +450,14 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
remove(SUBSCRIPTION_CREDENTIAL_RECEIPT) remove(SUBSCRIPTION_CREDENTIAL_RECEIPT)
} }
fun setSubscriptionPaymentSourceType(stripePaymentSourceType: StripePaymentSourceType) {
putString(SUBSCRIPTION_PAYMENT_SOURCE_TYPE, stripePaymentSourceType.code)
}
fun getSubscriptionPaymentSourceType(): StripePaymentSourceType {
return StripePaymentSourceType.fromCode(getString(SUBSCRIPTION_PAYMENT_SOURCE_TYPE, null))
}
var subscriptionEndOfPeriodConversionStarted by longValue(SUBSCRIPTION_EOP_STARTED_TO_CONVERT, 0L) var subscriptionEndOfPeriodConversionStarted by longValue(SUBSCRIPTION_EOP_STARTED_TO_CONVERT, 0L)
var subscriptionEndOfPeriodRedemptionStarted by longValue(SUBSCRIPTION_EOP_STARTED_TO_REDEEM, 0L) var subscriptionEndOfPeriodRedemptionStarted by longValue(SUBSCRIPTION_EOP_STARTED_TO_REDEEM, 0L)
var subscriptionEndOfPeriodRedeemed by longValue(SUBSCRIPTION_EOP_REDEEMED, 0L) var subscriptionEndOfPeriodRedeemed by longValue(SUBSCRIPTION_EOP_REDEEMED, 0L)

Wyświetl plik

@ -4607,6 +4607,8 @@
<string name="DeclineCode__your_card_has_expired">Your card has expired. Update your payment method in Google Pay and try again.</string> <string name="DeclineCode__your_card_has_expired">Your card has expired. Update your payment method in Google Pay and try again.</string>
<!-- Stripe decline code go to google pay action label --> <!-- Stripe decline code go to google pay action label -->
<string name="DeclineCode__go_to_google_pay">Go to Google Pay</string> <string name="DeclineCode__go_to_google_pay">Go to Google Pay</string>
<!-- Stripe decline code try credit card again action label -->
<string name="DeclineCode__try">Try again</string>
<!-- Stripe decline code incorrect card number --> <!-- Stripe decline code incorrect card number -->
<string name="DeclineCode__your_card_number_is_incorrect">Your card number is incorrect. Update it in Google Pay and try again.</string> <string name="DeclineCode__your_card_number_is_incorrect">Your card number is incorrect. Update it in Google Pay and try again.</string>
<!-- Stripe decline code incorrect cvc --> <!-- Stripe decline code incorrect cvc -->
@ -4622,6 +4624,22 @@
<!-- Stripe decline code processing error --> <!-- Stripe decline code processing error -->
<string name="DeclineCode__try_again">Try again or contact your bank for more information.</string> <string name="DeclineCode__try_again">Try again or contact your bank for more information.</string>
<!-- Credit Card decline code error strings -->
<!-- Stripe decline code approve_with_id for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__verify_your_card_details_are_correct_and_try_again">Verify your card details are correct and try again.</string>
<!-- Stripe decline code call_issuer for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__verify_your_card_details_are_correct_and_try_again_if_the_problem_continues">Verify your card details are correct and try again. If the problem continues, contact your bank.</string>
<!-- Stripe decline code expired_card for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__your_card_has_expired_verify_your_card_details">Your card has expired. Verify your card details are correct and try again.</string>
<!-- Stripe decline code incorrect_cvc and invalid_cvc for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__your_cards_cvc_number_is_incorrect_verify_your_card_details">Your card\'s CVC number is incorrect. Verify your card details are correct and try again.</string>
<!-- Stripe decline code invalid_expiry_month for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__the_expiration_month_on_your_card_is_incorrect">The expiration month on your card is incorrect. Verify your card details are correct and try again.</string>
<!-- Stripe decline code invalid_expiry_year for credit cards displayed in a notification or dialog -->
<string name="DeclineCode__the_expiration_year_on_your_card_is_incorrect">The expiration year on your card is incorrect. Verify your card details are correct and try again.</string>
<!-- 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>
<!-- Title of create notification profile screen --> <!-- Title of create notification profile screen -->
<string name="EditNotificationProfileFragment__name_your_profile">Name your profile</string> <string name="EditNotificationProfileFragment__name_your_profile">Name your profile</string>
<!-- Hint text for create/edit notification profile name --> <!-- Hint text for create/edit notification profile name -->

Wyświetl plik

@ -8,6 +8,7 @@ import org.json.JSONObject
class CreditCardPaymentSource( class CreditCardPaymentSource(
private val payload: JSONObject private val payload: JSONObject
) : StripeApi.PaymentSource { ) : StripeApi.PaymentSource {
override val type = StripePaymentSourceType.CREDIT_CARD
override fun parameterize(): JSONObject = payload override fun parameterize(): JSONObject = payload
override fun getTokenId(): String = parameterize().getString("id") override fun getTokenId(): String = parameterize().getString("id")
override fun email(): String? = null override fun email(): String? = null

Wyświetl plik

@ -4,6 +4,8 @@ import com.google.android.gms.wallet.PaymentData
import org.json.JSONObject import org.json.JSONObject
class GooglePayPaymentSource(private val paymentData: PaymentData) : StripeApi.PaymentSource { class GooglePayPaymentSource(private val paymentData: PaymentData) : StripeApi.PaymentSource {
override val type = StripePaymentSourceType.GOOGLE_PAY
override fun parameterize(): JSONObject { override fun parameterize(): JSONObject {
val jsonData = JSONObject(paymentData.toJson()) val jsonData = JSONObject(paymentData.toJson())
val paymentMethodJsonData = jsonData.getJSONObject("paymentMethodData") val paymentMethodJsonData = jsonData.getJSONObject("paymentMethodData")

Wyświetl plik

@ -520,6 +520,7 @@ class StripeApi(
) : Parcelable ) : Parcelable
interface PaymentSource { interface PaymentSource {
val type: StripePaymentSourceType
fun parameterize(): JSONObject fun parameterize(): JSONObject
fun getTokenId(): String fun getTokenId(): String
fun email(): String? fun email(): String?

Wyświetl plik

@ -0,0 +1,12 @@
package org.signal.donations
enum class StripePaymentSourceType(val code: String) {
CREDIT_CARD("credit_card"),
GOOGLE_PAY("google_pay");
companion object {
fun fromCode(code: String?): StripePaymentSourceType {
return values().firstOrNull { it.code == code } ?: GOOGLE_PAY
}
}
}