mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-25 05:27:42 +00:00
Donation CreatePaymentMethod 409 error recovery.
This commit is contained in:
committed by
Cody Henthorne
parent
c409d49f14
commit
4d640ec467
@@ -79,9 +79,29 @@ class MonthlyDonationRepository(private val donationsService: DonationsService)
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun ensureSubscriberId(): Completable {
|
||||
Log.d(TAG, "Ensuring SubscriberId exists on Signal service...", true)
|
||||
val subscriberId = SignalStore.donationsValues().getSubscriber()?.subscriberId ?: SubscriberId.generate()
|
||||
/**
|
||||
* Since PayPal and Stripe can't interoperate, we need to be able to rotate the subscriber ID
|
||||
* in case of failures.
|
||||
*/
|
||||
fun rotateSubscriberId(): Completable {
|
||||
Log.d(TAG, "Rotating SubscriberId due to alternate payment processor...", true)
|
||||
val cancelCompletable: Completable = if (SignalStore.donationsValues().getSubscriber() != null) {
|
||||
cancelActiveSubscription().andThen(updateLocalSubscriptionStateAndScheduleDataSync())
|
||||
} else {
|
||||
Completable.complete()
|
||||
}
|
||||
|
||||
return cancelCompletable.andThen(ensureSubscriberId(isRotation = true))
|
||||
}
|
||||
|
||||
fun ensureSubscriberId(isRotation: Boolean = false): Completable {
|
||||
Log.d(TAG, "Ensuring SubscriberId exists on Signal service {isRotation?$isRotation}...", true)
|
||||
val subscriberId: SubscriberId = if (isRotation) {
|
||||
SubscriberId.generate()
|
||||
} else {
|
||||
SignalStore.donationsValues().getSubscriber()?.subscriberId ?: SubscriberId.generate()
|
||||
}
|
||||
|
||||
return Single
|
||||
.fromCallable {
|
||||
donationsService.putSubscription(subscriberId)
|
||||
@@ -222,4 +242,18 @@ class MonthlyDonationRepository(private val donationsService: DonationsService)
|
||||
levelUpdateOperation
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update local state information and schedule a storage sync for the change. This method
|
||||
* assumes you've already properly called the DELETE method for the stored ID on the server.
|
||||
*/
|
||||
private fun updateLocalSubscriptionStateAndScheduleDataSync(): Completable {
|
||||
return Completable.fromAction {
|
||||
Log.d(TAG, "Marking subscription cancelled...", true)
|
||||
SignalStore.donationsValues().updateLocalStateForManualCancellation()
|
||||
MultiDeviceSubscriptionSyncRequestJob.enqueue()
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ class PayPalRepository(private val donationsService: DonationsService) {
|
||||
private val TAG = Log.tag(PayPalRepository::class.java)
|
||||
}
|
||||
|
||||
private val monthlyDonationRepository = MonthlyDonationRepository(donationsService)
|
||||
|
||||
fun createOneTimePaymentIntent(
|
||||
amount: FiatMoney,
|
||||
badgeRecipient: RecipientId,
|
||||
@@ -69,7 +71,12 @@ class PayPalRepository(private val donationsService: DonationsService) {
|
||||
}.flatMap { it.flattenResult() }.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun createPaymentMethod(): Single<PayPalCreatePaymentMethodResponse> {
|
||||
/**
|
||||
* Creates the PaymentMethod via the Signal Service. Note that if the operation fails with a 409,
|
||||
* it means that the PaymentMethod is already tied to a Stripe account. We can retry in this
|
||||
* situation by simply deleting the old subscriber id on the service and replacing it.
|
||||
*/
|
||||
fun createPaymentMethod(retryOn409: Boolean = true): Single<PayPalCreatePaymentMethodResponse> {
|
||||
return Single.fromCallable {
|
||||
donationsService.createPayPalPaymentMethod(
|
||||
Locale.getDefault(),
|
||||
@@ -77,7 +84,13 @@ class PayPalRepository(private val donationsService: DonationsService) {
|
||||
MONTHLY_RETURN_URL,
|
||||
CANCEL_URL
|
||||
)
|
||||
}.flatMap { it.flattenResult() }.subscribeOn(Schedulers.io())
|
||||
}.flatMap { serviceResponse ->
|
||||
if (retryOn409 && serviceResponse.status == 409) {
|
||||
monthlyDonationRepository.rotateSubscriberId().andThen(createPaymentMethod(retryOn409 = false))
|
||||
} else {
|
||||
serviceResponse.flattenResult()
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun setDefaultPaymentMethod(paymentMethodId: String): Completable {
|
||||
|
||||
@@ -47,6 +47,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
|
||||
private val googlePayApi = GooglePayApi(activity, StripeApi.Gateway(Environment.Donations.STRIPE_CONFIGURATION), Environment.Donations.GOOGLE_PAY_CONFIGURATION)
|
||||
private val stripeApi = StripeApi(Environment.Donations.STRIPE_CONFIGURATION, this, this, ApplicationDependencies.getOkHttpClient())
|
||||
private val monthlyDonationRepository = MonthlyDonationRepository(ApplicationDependencies.getDonationsService())
|
||||
|
||||
fun isGooglePayAvailable(): Completable {
|
||||
return googlePayApi.queryIsReadyToPay()
|
||||
@@ -153,8 +154,12 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchSetupIntent(): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Fetching setup intent from Signal service...")
|
||||
/**
|
||||
* Creates the PaymentMethod via the Signal Service. Note that if the operation fails with a 409,
|
||||
* it means that the PaymentMethod is already tied to a PayPal account. We can retry in this
|
||||
* situation by simply deleting the old subscriber id on the service and replacing it.
|
||||
*/
|
||||
private fun createPaymentMethod(retryOn409: Boolean = true): Single<StripeClientSecret> {
|
||||
return Single.fromCallable { SignalStore.donationsValues().requireSubscriber() }
|
||||
.flatMap {
|
||||
Single.fromCallable {
|
||||
@@ -163,7 +168,18 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
.createStripeSubscriptionPaymentMethod(it.subscriberId)
|
||||
}
|
||||
}
|
||||
.flatMap(ServiceResponse<StripeClientSecret>::flattenResult)
|
||||
.flatMap { serviceResponse ->
|
||||
if (retryOn409 && serviceResponse.status == 409) {
|
||||
monthlyDonationRepository.rotateSubscriberId().andThen(createPaymentMethod(retryOn409 = false))
|
||||
} else {
|
||||
serviceResponse.flattenResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchSetupIntent(): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Fetching setup intent from Signal service...")
|
||||
return createPaymentMethod()
|
||||
.map {
|
||||
StripeIntentAccessor(
|
||||
objectType = StripeIntentAccessor.ObjectType.SETUP_INTENT,
|
||||
@@ -191,6 +207,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
}
|
||||
StatusAndPaymentMethodId(it.status ?: StripeIntentStatus.SUCCEEDED, it.paymentMethod)
|
||||
}
|
||||
|
||||
StripeIntentAccessor.ObjectType.SETUP_INTENT -> stripeApi.getSetupIntent(stripeIntentAccessor).let {
|
||||
StatusAndPaymentMethodId(it.status, it.paymentMethod)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user