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.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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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