mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-21 10:17:56 +00:00
Add support for Credit Card 3DS during subscriptions.
This commit is contained in:
committed by
Cody Henthorne
parent
844480786e
commit
d1df069669
@@ -19,6 +19,7 @@ import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.GooglePayApi
|
||||
import org.signal.donations.GooglePayPaymentSource
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.badges.gifts.Gifts
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationEvent
|
||||
@@ -168,8 +169,8 @@ class GiftFlowViewModel(
|
||||
|
||||
store.update { it.copy(stage = GiftFlowState.Stage.PAYMENT_PIPELINE) }
|
||||
|
||||
val continuePayment: Single<StripeApi.PaymentIntent> = donationPaymentRepository.continuePayment(gift.price, recipient, gift.level)
|
||||
val intentAndSource: Single<Pair<StripeApi.PaymentIntent, StripeApi.PaymentSource>> = Single.zip(continuePayment, Single.just(GooglePayPaymentSource(paymentData)), ::Pair)
|
||||
val continuePayment: Single<StripeIntentAccessor> = donationPaymentRepository.continuePayment(gift.price, recipient, gift.level)
|
||||
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, Single.just(GooglePayPaymentSource(paymentData)), ::Pair)
|
||||
|
||||
disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) ->
|
||||
donationPaymentRepository.confirmPayment(paymentSource, paymentIntent, recipient)
|
||||
|
||||
@@ -10,6 +10,8 @@ import org.signal.core.util.logging.Log
|
||||
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.json.StripeIntentStatus
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase
|
||||
@@ -133,7 +135,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
price: FiatMoney,
|
||||
badgeRecipient: RecipientId,
|
||||
badgeLevel: Long,
|
||||
): Single<StripeApi.PaymentIntent> {
|
||||
): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Creating payment intent for $price...", true)
|
||||
|
||||
return stripeApi.createPaymentIntent(price, badgeLevel)
|
||||
@@ -207,7 +209,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
|
||||
fun confirmPayment(
|
||||
paymentSource: StripeApi.PaymentSource,
|
||||
paymentIntent: StripeApi.PaymentIntent,
|
||||
paymentIntent: StripeIntentAccessor,
|
||||
badgeRecipient: RecipientId
|
||||
): Single<StripeApi.Secure3DSAction> {
|
||||
val isBoost = badgeRecipient == Recipient.self().id
|
||||
@@ -222,7 +224,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
|
||||
fun waitForOneTimeRedemption(
|
||||
price: FiatMoney,
|
||||
paymentIntent: StripeApi.PaymentIntent,
|
||||
paymentIntent: StripeIntentAccessor,
|
||||
badgeRecipient: RecipientId,
|
||||
additionalMessage: String?,
|
||||
badgeLevel: Long,
|
||||
@@ -382,7 +384,7 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchPaymentIntent(price: FiatMoney, level: Long): Single<StripeApi.PaymentIntent> {
|
||||
override fun fetchPaymentIntent(price: FiatMoney, level: Long): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Fetching payment intent from Signal service for $price... (Locale.US minimum precision: ${price.minimumUnitPrecisionString})")
|
||||
return Single
|
||||
.fromCallable {
|
||||
@@ -392,13 +394,17 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
}
|
||||
.flatMap(ServiceResponse<SubscriptionClientSecret>::flattenResult)
|
||||
.map {
|
||||
StripeApi.PaymentIntent(it.id, it.clientSecret)
|
||||
StripeIntentAccessor(
|
||||
objectType = StripeIntentAccessor.ObjectType.PAYMENT_INTENT,
|
||||
intentId = it.id,
|
||||
intentClientSecret = it.clientSecret
|
||||
)
|
||||
}.doOnSuccess {
|
||||
Log.d(TAG, "Got payment intent from Signal service!")
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchSetupIntent(): Single<StripeApi.SetupIntent> {
|
||||
override fun fetchSetupIntent(): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Fetching setup intent from Signal service...")
|
||||
return Single.fromCallable { SignalStore.donationsValues().requireSubscriber() }
|
||||
.flatMap {
|
||||
@@ -409,12 +415,34 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
}
|
||||
}
|
||||
.flatMap(ServiceResponse<SubscriptionClientSecret>::flattenResult)
|
||||
.map { StripeApi.SetupIntent(it.id, it.clientSecret) }
|
||||
.map {
|
||||
StripeIntentAccessor(
|
||||
objectType = StripeIntentAccessor.ObjectType.SETUP_INTENT,
|
||||
intentId = it.id,
|
||||
intentClientSecret = it.clientSecret
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
Log.d(TAG, "Got setup intent from Signal service!")
|
||||
}
|
||||
}
|
||||
|
||||
// We need to get the status and payment id from the intent.
|
||||
|
||||
fun getStatusAndPaymentMethodId(stripeIntentAccessor: StripeIntentAccessor): Single<StatusAndPaymentMethodId> {
|
||||
return Single.fromCallable {
|
||||
when (stripeIntentAccessor.objectType) {
|
||||
StripeIntentAccessor.ObjectType.NONE -> StatusAndPaymentMethodId(StripeIntentStatus.SUCCEEDED, null)
|
||||
StripeIntentAccessor.ObjectType.PAYMENT_INTENT -> stripeApi.getPaymentIntent(stripeIntentAccessor).let {
|
||||
StatusAndPaymentMethodId(it.status, it.paymentMethod)
|
||||
}
|
||||
StripeIntentAccessor.ObjectType.SETUP_INTENT -> stripeApi.getSetupIntent(stripeIntentAccessor).let {
|
||||
StatusAndPaymentMethodId(it.status, it.paymentMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultPaymentMethod(paymentMethodId: String): Completable {
|
||||
return Single.fromCallable {
|
||||
Log.d(TAG, "Getting the subscriber...")
|
||||
@@ -441,6 +469,11 @@ class DonationPaymentRepository(activity: Activity) : StripeApi.PaymentIntentFet
|
||||
}
|
||||
}
|
||||
|
||||
data class StatusAndPaymentMethodId(
|
||||
val status: StripeIntentStatus,
|
||||
val paymentMethod: String?
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(DonationPaymentRepository::class.java)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -92,6 +93,8 @@ class DonateToSignalFragment : DSLSettingsFragment(
|
||||
}
|
||||
}
|
||||
|
||||
private var errorDialog: DialogInterface? = null
|
||||
|
||||
private val args: DonateToSignalFragmentArgs by navArgs()
|
||||
private val viewModel: DonateToSignalViewModel by viewModels(factoryProducer = {
|
||||
DonateToSignalViewModel.Factory(args.startType)
|
||||
@@ -462,7 +465,7 @@ class DonateToSignalFragment : DSLSettingsFragment(
|
||||
}
|
||||
|
||||
private fun registerGooglePayCallback() {
|
||||
donationPaymentComponent.googlePayResultPublisher.subscribeBy(
|
||||
disposables += donationPaymentComponent.googlePayResultPublisher.subscribeBy(
|
||||
onNext = { paymentResult ->
|
||||
viewModel.consumeGatewayRequestForGooglePay()?.let {
|
||||
donationPaymentComponent.donationPaymentRepository.onActivityResult(
|
||||
@@ -478,15 +481,20 @@ class DonateToSignalFragment : DSLSettingsFragment(
|
||||
}
|
||||
|
||||
private fun showErrorDialog(throwable: Throwable) {
|
||||
Log.d(TAG, "Displaying donation error dialog.", true)
|
||||
DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
override fun onDialogDismissed() {
|
||||
findNavController().popBackStack()
|
||||
if (errorDialog != null) {
|
||||
Log.d(TAG, "Already displaying an error dialog. Skipping.", throwable, true)
|
||||
} else {
|
||||
Log.d(TAG, "Displaying donation error dialog.", true)
|
||||
errorDialog = DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
override fun onDialogDismissed() {
|
||||
errorDialog = null
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimationAboveSelectedBoost(view: View) {
|
||||
|
||||
@@ -8,9 +8,11 @@ import android.view.View
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.databinding.Stripe3dsDialogFragmentBinding
|
||||
@@ -23,7 +25,6 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.stripe_3ds_dialog_fragme
|
||||
|
||||
companion object {
|
||||
const val REQUEST_KEY = "stripe_3ds_dialog_fragment"
|
||||
private const val STRIPE_3DS_COMPLETE = "https://hooks.stripe.com/3d_secure/complete/tdsrc_complete"
|
||||
}
|
||||
|
||||
val binding by ViewBinderDelegate(Stripe3dsDialogFragmentBinding::bind) {
|
||||
@@ -33,6 +34,8 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.stripe_3ds_dialog_fragme
|
||||
|
||||
val args: Stripe3DSDialogFragmentArgs by navArgs()
|
||||
|
||||
var result: Bundle? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
|
||||
@@ -47,7 +50,9 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.stripe_3ds_dialog_fragme
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
setFragmentResult(REQUEST_KEY, Bundle())
|
||||
val result = this.result
|
||||
this.result = null
|
||||
setFragmentResult(REQUEST_KEY, result ?: Bundle())
|
||||
}
|
||||
|
||||
private inner class Stripe3DSWebClient : WebViewClient() {
|
||||
@@ -61,7 +66,10 @@ class Stripe3DSDialogFragment : DialogFragment(R.layout.stripe_3ds_dialog_fragme
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
if (url == STRIPE_3DS_COMPLETE) {
|
||||
if (url?.startsWith(args.returnUri.toString()) == true) {
|
||||
val stripeIntentAccessor = StripeIntentAccessor.fromUri(url)
|
||||
|
||||
result = bundleOf(REQUEST_KEY to stripeIntentAccessor)
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
@@ -111,22 +112,27 @@ class StripePaymentInProgressFragment : DialogFragment(R.layout.stripe_payment_i
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecure3dsAction(secure3dsAction: StripeApi.Secure3DSAction): Completable {
|
||||
private fun handleSecure3dsAction(secure3dsAction: StripeApi.Secure3DSAction): Single<StripeIntentAccessor> {
|
||||
return when (secure3dsAction) {
|
||||
is StripeApi.Secure3DSAction.NotNeeded -> {
|
||||
Log.d(TAG, "No 3DS action required.")
|
||||
Completable.complete()
|
||||
Single.just(StripeIntentAccessor.NO_ACTION_REQUIRED)
|
||||
}
|
||||
is StripeApi.Secure3DSAction.ConfirmRequired -> {
|
||||
Log.d(TAG, "3DS action required. Displaying dialog...")
|
||||
Completable.create { emitter ->
|
||||
val listener = FragmentResultListener { _, _ ->
|
||||
emitter.onComplete()
|
||||
Single.create<StripeIntentAccessor> { emitter ->
|
||||
val listener = FragmentResultListener { _, bundle ->
|
||||
val result: StripeIntentAccessor? = bundle.getParcelable(Stripe3DSDialogFragment.REQUEST_KEY)
|
||||
if (result != null) {
|
||||
emitter.onSuccess(result)
|
||||
} else {
|
||||
emitter.onError(Exception("User did not complete 3DS Authorization."))
|
||||
}
|
||||
}
|
||||
|
||||
parentFragmentManager.setFragmentResultListener(Stripe3DSDialogFragment.REQUEST_KEY, this, listener)
|
||||
|
||||
findNavController().safeNavigate(StripePaymentInProgressFragmentDirections.actionStripePaymentInProgressFragmentToStripe3dsDialogFragment(secure3dsAction.uri))
|
||||
findNavController().safeNavigate(StripePaymentInProgressFragmentDirections.actionStripePaymentInProgressFragmentToStripe3dsDialogFragment(secure3dsAction.uri, secure3dsAction.returnUri))
|
||||
|
||||
emitter.setCancellable {
|
||||
parentFragmentManager.clearFragmentResultListener(Stripe3DSDialogFragment.REQUEST_KEY)
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.GooglePayPaymentSource
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
@@ -62,7 +63,7 @@ class StripePaymentInProgressViewModel(
|
||||
disposables.clear()
|
||||
}
|
||||
|
||||
fun processNewDonation(request: GatewayRequest, nextActionHandler: (StripeApi.Secure3DSAction) -> Completable) {
|
||||
fun processNewDonation(request: GatewayRequest, nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>) {
|
||||
Log.d(TAG, "Proceeding with donation...", true)
|
||||
|
||||
val errorSource = when (request.donateToSignalType) {
|
||||
@@ -112,7 +113,7 @@ class StripePaymentInProgressViewModel(
|
||||
cardData = null
|
||||
}
|
||||
|
||||
private fun proceedMonthly(request: GatewayRequest, paymentSourceProvider: Single<StripeApi.PaymentSource>, nextActionHandler: (StripeApi.Secure3DSAction) -> Completable) {
|
||||
private fun proceedMonthly(request: GatewayRequest, paymentSourceProvider: Single<StripeApi.PaymentSource>, nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>) {
|
||||
val ensureSubscriberId: Completable = donationPaymentRepository.ensureSubscriberId()
|
||||
val createAndConfirmSetupIntent: Single<StripeApi.Secure3DSAction> = paymentSourceProvider.flatMap { donationPaymentRepository.createAndConfirmSetupIntent(it) }
|
||||
val setLevel: Completable = donationPaymentRepository.setSubscriptionLevel(request.level.toString())
|
||||
@@ -123,7 +124,11 @@ class StripePaymentInProgressViewModel(
|
||||
val setup: Completable = ensureSubscriberId
|
||||
.andThen(cancelActiveSubscriptionIfNecessary())
|
||||
.andThen(createAndConfirmSetupIntent)
|
||||
.flatMap { secure3DSAction -> nextActionHandler(secure3DSAction).andThen(Single.just(secure3DSAction.paymentMethodId!!)) }
|
||||
.flatMap { secure3DSAction ->
|
||||
nextActionHandler(secure3DSAction)
|
||||
.flatMap { secure3DSResult -> donationPaymentRepository.getStatusAndPaymentMethodId(secure3DSResult) }
|
||||
.map { (_, paymentMethod) -> paymentMethod ?: secure3DSAction.paymentMethodId!! }
|
||||
}
|
||||
.flatMapCompletable { donationPaymentRepository.setDefaultPaymentMethod(it) }
|
||||
.onErrorResumeNext { Completable.error(DonationError.getPaymentSetupError(DonationErrorSource.SUBSCRIPTION, it)) }
|
||||
|
||||
@@ -163,7 +168,7 @@ class StripePaymentInProgressViewModel(
|
||||
private fun proceedOneTime(
|
||||
request: GatewayRequest,
|
||||
paymentSourceProvider: Single<StripeApi.PaymentSource>,
|
||||
nextActionHandler: (StripeApi.Secure3DSAction) -> Completable
|
||||
nextActionHandler: (StripeApi.Secure3DSAction) -> Single<StripeIntentAccessor>
|
||||
) {
|
||||
Log.w(TAG, "Beginning one-time payment pipeline...", true)
|
||||
|
||||
@@ -171,13 +176,14 @@ class StripePaymentInProgressViewModel(
|
||||
val recipient = Recipient.self().id
|
||||
val level = SubscriptionLevels.BOOST_LEVEL.toLong()
|
||||
|
||||
val continuePayment: Single<StripeApi.PaymentIntent> = donationPaymentRepository.continuePayment(amount, recipient, level)
|
||||
val intentAndSource: Single<Pair<StripeApi.PaymentIntent, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider, ::Pair)
|
||||
val continuePayment: Single<StripeIntentAccessor> = donationPaymentRepository.continuePayment(amount, recipient, level)
|
||||
val intentAndSource: Single<Pair<StripeIntentAccessor, StripeApi.PaymentSource>> = Single.zip(continuePayment, paymentSourceProvider, ::Pair)
|
||||
|
||||
disposables += intentAndSource.flatMapCompletable { (paymentIntent, paymentSource) ->
|
||||
donationPaymentRepository.confirmPayment(paymentSource, paymentIntent, recipient)
|
||||
.flatMapCompletable { nextActionHandler(it) }
|
||||
.andThen(donationPaymentRepository.waitForOneTimeRedemption(amount, paymentIntent, recipient, null, level))
|
||||
.flatMap { nextActionHandler(it) }
|
||||
.flatMap { donationPaymentRepository.getStatusAndPaymentMethodId(it) }
|
||||
.flatMapCompletable { donationPaymentRepository.waitForOneTimeRedemption(amount, paymentIntent, recipient, null, level) }
|
||||
}.subscribeBy(
|
||||
onError = { throwable ->
|
||||
Log.w(TAG, "Failure in one-time payment pipeline...", throwable, true)
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.donations.StripeApi;
|
||||
import org.signal.donations.StripeIntentAccessor;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
import org.signal.libsignal.zkgroup.receipts.ClientZkReceiptOperations;
|
||||
@@ -55,7 +55,7 @@ public class BoostReceiptRequestResponseJob extends BaseJob {
|
||||
private final String paymentIntentId;
|
||||
private final long badgeLevel;
|
||||
|
||||
private static BoostReceiptRequestResponseJob createJob(StripeApi.PaymentIntent paymentIntent, DonationErrorSource donationErrorSource, long badgeLevel) {
|
||||
private static BoostReceiptRequestResponseJob createJob(StripeIntentAccessor paymentIntent, DonationErrorSource donationErrorSource, long badgeLevel) {
|
||||
return new BoostReceiptRequestResponseJob(
|
||||
new Parameters
|
||||
.Builder()
|
||||
@@ -65,13 +65,13 @@ public class BoostReceiptRequestResponseJob extends BaseJob {
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
null,
|
||||
paymentIntent.getId(),
|
||||
paymentIntent.getIntentId(),
|
||||
donationErrorSource,
|
||||
badgeLevel
|
||||
);
|
||||
}
|
||||
|
||||
public static JobManager.Chain createJobChainForBoost(@NonNull StripeApi.PaymentIntent paymentIntent) {
|
||||
public static JobManager.Chain createJobChainForBoost(@NonNull StripeIntentAccessor paymentIntent) {
|
||||
BoostReceiptRequestResponseJob requestReceiptJob = createJob(paymentIntent, DonationErrorSource.BOOST, Long.parseLong(SubscriptionLevels.BOOST_LEVEL));
|
||||
DonationReceiptRedemptionJob redeemReceiptJob = DonationReceiptRedemptionJob.createJobForBoost();
|
||||
RefreshOwnProfileJob refreshOwnProfileJob = RefreshOwnProfileJob.forBoost();
|
||||
@@ -84,7 +84,7 @@ public class BoostReceiptRequestResponseJob extends BaseJob {
|
||||
.then(multiDeviceProfileContentUpdateJob);
|
||||
}
|
||||
|
||||
public static JobManager.Chain createJobChainForGift(@NonNull StripeApi.PaymentIntent paymentIntent,
|
||||
public static JobManager.Chain createJobChainForGift(@NonNull StripeIntentAccessor paymentIntent,
|
||||
@NonNull RecipientId recipientId,
|
||||
@Nullable String additionalMessage,
|
||||
long badgeLevel)
|
||||
|
||||
Reference in New Issue
Block a user