Add new error strings for credit cards.
This commit is contained in:
parent
f6356c9720
commit
eee4ff3f87
14 changed files with 200 additions and 34 deletions
|
@ -11,6 +11,7 @@ import org.signal.core.util.money.FiatMoney
|
|||
import org.signal.donations.GooglePayApi
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.signal.donations.StripePaymentSourceType
|
||||
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.DonationErrorSource
|
||||
|
@ -86,12 +87,13 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
|||
price: FiatMoney,
|
||||
badgeRecipient: RecipientId,
|
||||
badgeLevel: Long,
|
||||
paymentSourceType: StripePaymentSourceType
|
||||
): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Creating payment intent for $price...", true)
|
||||
|
||||
return stripeApi.createPaymentIntent(price, badgeLevel)
|
||||
.onErrorResumeNext {
|
||||
handleCreatePaymentIntentError(it, badgeRecipient)
|
||||
handleCreatePaymentIntentError(it, badgeRecipient, paymentSourceType)
|
||||
}
|
||||
.flatMap { result ->
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
|
@ -127,7 +129,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
|||
Log.d(TAG, "Confirming payment intent...", true)
|
||||
return stripeApi.confirmPaymentIntent(paymentSource, paymentIntent)
|
||||
.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 {
|
||||
Log.d(TAG, "Getting the subscriber...")
|
||||
SignalStore.donationsValues().requireSubscriber()
|
||||
|
@ -209,6 +214,9 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
|||
}
|
||||
}.flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().doOnComplete {
|
||||
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...")
|
||||
return stripeApi.createPaymentSourceFromCardData(cardData).map {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -230,13 +238,13 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
|||
companion object {
|
||||
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) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
val recipient = Recipient.resolved(badgeRecipient)
|
||||
val errorSource = if (recipient.isSelf) DonationErrorSource.BOOST else DonationErrorSource.GIFT
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable))
|
||||
Single.error(DonationError.getPaymentSetupError(errorSource, throwable, paymentSourceType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.SpannableStringBuilder
|
||||
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.errors.DonationError
|
||||
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.models.CurrencySelection
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||
|
@ -395,9 +397,22 @@ class DonateToSignalFragment :
|
|||
errorDialog = DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
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() {
|
||||
errorDialog = null
|
||||
findNavController().popBackStack()
|
||||
if (!tryCCAgain) {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.signal.donations.GooglePayPaymentSource
|
||||
import org.signal.donations.StripeApi
|
||||
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.OneTimeDonationRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
|
||||
|
@ -75,7 +76,7 @@ class StripePaymentInProgressViewModel(
|
|||
DonateToSignalType.GIFT -> DonationErrorSource.GIFT
|
||||
}
|
||||
|
||||
val paymentSourceProvider: Single<StripeApi.PaymentSource> = resolvePaymentSourceProvider(errorSource)
|
||||
val paymentSourceProvider: PaymentSourceProvider = resolvePaymentSourceProvider(errorSource)
|
||||
|
||||
return when (request.donateToSignalType) {
|
||||
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 cardData = this.cardData
|
||||
|
||||
return when {
|
||||
paymentData == null && cardData == null -> error("No payment provider available.")
|
||||
paymentData != null && cardData != null -> error("Too many providers available")
|
||||
paymentData != null -> Single.just<StripeApi.PaymentSource>(GooglePayPaymentSource(paymentData))
|
||||
cardData != null -> stripeRepository.createCreditCardPaymentSource(errorSource, cardData)
|
||||
paymentData != null -> PaymentSourceProvider(
|
||||
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.")
|
||||
}.doAfterTerminate { clearPaymentInformation() }
|
||||
}
|
||||
}
|
||||
|
||||
fun providePaymentData(paymentData: PaymentData) {
|
||||
|
@ -118,9 +125,9 @@ class StripePaymentInProgressViewModel(
|
|||
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 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())
|
||||
|
||||
Log.d(TAG, "Starting subscription payment pipeline...", true)
|
||||
|
@ -134,12 +141,12 @@ class StripePaymentInProgressViewModel(
|
|||
.flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult) }
|
||||
.map { (_, paymentMethod) -> paymentMethod ?: secure3DSAction.paymentMethodId!! }
|
||||
}
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it) }
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it, paymentSourceProvider.paymentSourceType) }
|
||||
.onErrorResumeNext {
|
||||
if (it is DonationError) {
|
||||
Completable.error(it)
|
||||
} 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(
|
||||
request: GatewayRequest,
|
||||
paymentSourceProvider: Single<StripeApi.PaymentSource>,
|
||||
paymentSourceProvider: PaymentSourceProvider,
|
||||
nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>
|
||||
) {
|
||||
Log.w(TAG, "Beginning one-time payment pipeline...", true)
|
||||
|
||||
val amount = request.fiat
|
||||
|
||||
val continuePayment: Single<StripeIntentAccessor> = stripeRepository.continuePayment(amount, request.recipientId, request.level)
|
||||
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider, ::Pair)
|
||||
val continuePayment: Single<StripeIntentAccessor> = stripeRepository.continuePayment(amount, request.recipientId, request.level, paymentSourceProvider.paymentSourceType)
|
||||
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider.paymentSource, ::Pair)
|
||||
|
||||
disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) ->
|
||||
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(
|
||||
private val stripeRepository: StripeRepository,
|
||||
private val monthlyDonationRepository: MonthlyDonationRepository = MonthlyDonationRepository(ApplicationDependencies.getDonationsService()),
|
||||
|
|
|
@ -7,6 +7,7 @@ import io.reactivex.rxjava3.subjects.Subject
|
|||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeError
|
||||
import org.signal.donations.StripePaymentSourceType
|
||||
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPaymentSetupError(source: DonationErrorSource, throwable: Throwable): DonationError {
|
||||
fun getPaymentSetupError(source: DonationErrorSource, throwable: Throwable, method: StripePaymentSourceType): DonationError {
|
||||
return if (throwable is StripeError.PostError) {
|
||||
val declineCode: StripeDeclineCode? = throwable.declineCode
|
||||
val errorCode: String? = throwable.errorCode
|
||||
|
||||
when {
|
||||
declineCode != null -> PaymentSetupError.DeclinedError(source, throwable, declineCode)
|
||||
declineCode != null -> PaymentSetupError.DeclinedError(source, throwable, declineCode, method)
|
||||
errorCode != null -> PaymentSetupError.CodedError(source, throwable, errorCode)
|
||||
else -> PaymentSetupError.GenericError(source, throwable)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ object DonationErrorDialogs {
|
|||
return builder.show()
|
||||
}
|
||||
|
||||
open class DialogCallback : DonationErrorParams.Callback<Unit> {
|
||||
abstract class DialogCallback : DonationErrorParams.Callback<Unit> {
|
||||
|
||||
override fun onCancel(context: Context): DonationErrorParams.ErrorAction<Unit>? {
|
||||
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>? {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__learn_more,
|
||||
|
|
|
@ -63,6 +63,8 @@ object DonationErrorNotifications {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<PendingIntent>? = null
|
||||
|
||||
override fun onGoToGooglePay(context: Context): DonationErrorParams.ErrorAction<PendingIntent> {
|
||||
return createAction(
|
||||
context = context,
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors
|
|||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripePaymentSourceType
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
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> {
|
||||
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) {
|
||||
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.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)
|
||||
StripeDeclineCode.Code.APPROVE_WITH_ID -> getStripeDeclineCodePositiveActionParams(
|
||||
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.EXPIRED_CARD -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_has_expired)
|
||||
StripeDeclineCode.Code.INCORRECT_NUMBER -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_number_is_incorrect)
|
||||
StripeDeclineCode.Code.INCORRECT_CVC -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_cards_cvc_number_is_incorrect)
|
||||
StripeDeclineCode.Code.EXPIRED_CARD -> getStripeDeclineCodePositiveActionParams(
|
||||
context, callback,
|
||||
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.INVALID_CVC -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_cards_cvc_number_is_incorrect)
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_MONTH -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__the_expiration_month)
|
||||
StripeDeclineCode.Code.INVALID_EXPIRY_YEAR -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__the_expiration_year)
|
||||
StripeDeclineCode.Code.INVALID_NUMBER -> getGoToGooglePayParams(context, callback, R.string.DeclineCode__your_card_number_is_incorrect)
|
||||
StripeDeclineCode.Code.INVALID_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.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.PROCESSING_ERROR -> 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)
|
||||
)
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -135,5 +204,6 @@ class DonationErrorParams<V> private constructor(
|
|||
fun onLearnMore(context: Context): ErrorAction<V>?
|
||||
fun onContactSupport(context: Context): ErrorAction<V>?
|
||||
fun onGoToGooglePay(context: Context): ErrorAction<V>?
|
||||
fun onTryCreditCardAgain(context: Context): ErrorAction<V>?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -300,7 +300,8 @@ public class SubscriptionReceiptRequestResponseJob extends BaseJob {
|
|||
paymentSetupError = new DonationError.PaymentSetupError.DeclinedError(
|
||||
getErrorSource(),
|
||||
new Exception(chargeFailure.getMessage()),
|
||||
declineCode
|
||||
declineCode,
|
||||
SignalStore.donationsValues().getSubscriptionPaymentSourceType()
|
||||
);
|
||||
} else {
|
||||
paymentSetupError = new DonationError.PaymentSetupError.CodedError(
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject
|
|||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripePaymentSourceType
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException
|
||||
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.
|
||||
*/
|
||||
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
|
||||
|
@ -112,7 +119,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
|
|||
SUBSCRIPTION_CREDENTIAL_RECEIPT,
|
||||
SUBSCRIPTION_EOP_STARTED_TO_CONVERT,
|
||||
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()) }
|
||||
|
@ -442,6 +450,14 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
|
|||
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 subscriptionEndOfPeriodRedemptionStarted by longValue(SUBSCRIPTION_EOP_STARTED_TO_REDEEM, 0L)
|
||||
var subscriptionEndOfPeriodRedeemed by longValue(SUBSCRIPTION_EOP_REDEEMED, 0L)
|
||||
|
|
|
@ -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>
|
||||
<!-- Stripe decline code go to google pay action label -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
|
@ -4622,6 +4624,22 @@
|
|||
<!-- Stripe decline code processing error -->
|
||||
<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 -->
|
||||
<string name="EditNotificationProfileFragment__name_your_profile">Name your profile</string>
|
||||
<!-- Hint text for create/edit notification profile name -->
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.json.JSONObject
|
|||
class CreditCardPaymentSource(
|
||||
private val payload: JSONObject
|
||||
) : StripeApi.PaymentSource {
|
||||
override val type = StripePaymentSourceType.CREDIT_CARD
|
||||
override fun parameterize(): JSONObject = payload
|
||||
override fun getTokenId(): String = parameterize().getString("id")
|
||||
override fun email(): String? = null
|
||||
|
|
|
@ -4,6 +4,8 @@ import com.google.android.gms.wallet.PaymentData
|
|||
import org.json.JSONObject
|
||||
|
||||
class GooglePayPaymentSource(private val paymentData: PaymentData) : StripeApi.PaymentSource {
|
||||
override val type = StripePaymentSourceType.GOOGLE_PAY
|
||||
|
||||
override fun parameterize(): JSONObject {
|
||||
val jsonData = JSONObject(paymentData.toJson())
|
||||
val paymentMethodJsonData = jsonData.getJSONObject("paymentMethodData")
|
||||
|
|
|
@ -520,6 +520,7 @@ class StripeApi(
|
|||
) : Parcelable
|
||||
|
||||
interface PaymentSource {
|
||||
val type: StripePaymentSourceType
|
||||
fun parameterize(): JSONObject
|
||||
fun getTokenId(): String
|
||||
fun email(): String?
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue