mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Add proper endpoint for setting iDEAL default payment method.
This commit is contained in:
@@ -202,23 +202,19 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
* that we are successful and proceed as normal. If the payment didn't actually succeed, then we
|
||||
* expect an error later in the chain to inform us of this.
|
||||
*/
|
||||
fun getStatusAndPaymentMethodId(stripeIntentAccessor: StripeIntentAccessor, paymentSourceType: PaymentSourceType): Single<StatusAndPaymentMethodId> {
|
||||
fun getStatusAndPaymentMethodId(stripeIntentAccessor: StripeIntentAccessor): Single<StatusAndPaymentMethodId> {
|
||||
return Single.fromCallable {
|
||||
when (stripeIntentAccessor.objectType) {
|
||||
StripeIntentAccessor.ObjectType.NONE -> StatusAndPaymentMethodId(StripeIntentStatus.SUCCEEDED, null)
|
||||
StripeIntentAccessor.ObjectType.NONE -> StatusAndPaymentMethodId(stripeIntentAccessor.intentId, StripeIntentStatus.SUCCEEDED, null)
|
||||
StripeIntentAccessor.ObjectType.PAYMENT_INTENT -> stripeApi.getPaymentIntent(stripeIntentAccessor).let {
|
||||
if (it.status == null) {
|
||||
Log.d(TAG, "Returned payment intent had a null status.", true)
|
||||
}
|
||||
StatusAndPaymentMethodId(it.status ?: StripeIntentStatus.SUCCEEDED, it.paymentMethod)
|
||||
StatusAndPaymentMethodId(stripeIntentAccessor.intentId, it.status ?: StripeIntentStatus.SUCCEEDED, it.paymentMethod)
|
||||
}
|
||||
|
||||
StripeIntentAccessor.ObjectType.SETUP_INTENT -> stripeApi.getSetupIntent(stripeIntentAccessor).let {
|
||||
if (paymentSourceType == PaymentSourceType.Stripe.IDEAL) {
|
||||
StatusAndPaymentMethodId(it.status, it.requireGeneratedSepaDebit())
|
||||
} else {
|
||||
StatusAndPaymentMethodId(it.status, it.paymentMethod)
|
||||
}
|
||||
StatusAndPaymentMethodId(stripeIntentAccessor.intentId, it.status, it.paymentMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,6 +222,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
|
||||
fun setDefaultPaymentMethod(
|
||||
paymentMethodId: String,
|
||||
setupIntentId: String,
|
||||
paymentSourceType: PaymentSourceType
|
||||
): Completable {
|
||||
return Single.fromCallable {
|
||||
@@ -235,9 +232,15 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
Log.d(TAG, "Setting default payment method via Signal service...")
|
||||
// TODO [sepa] -- iDEAL has its own call
|
||||
Single.fromCallable {
|
||||
ApplicationDependencies
|
||||
.getDonationsService()
|
||||
.setDefaultStripePaymentMethod(it.subscriberId, paymentMethodId)
|
||||
if (paymentSourceType == PaymentSourceType.Stripe.IDEAL) {
|
||||
ApplicationDependencies
|
||||
.getDonationsService()
|
||||
.setDefaultIdealPaymentMethod(it.subscriberId, setupIntentId)
|
||||
} else {
|
||||
ApplicationDependencies
|
||||
.getDonationsService()
|
||||
.setDefaultStripePaymentMethod(it.subscriberId, paymentMethodId)
|
||||
}
|
||||
}
|
||||
}.flatMap(ServiceResponse<EmptyResponse>::flattenResult).ignoreElement().doOnComplete {
|
||||
Log.d(TAG, "Set default payment method via Signal service!")
|
||||
@@ -267,6 +270,7 @@ class StripeRepository(activity: Activity) : StripeApi.PaymentIntentFetcher, Str
|
||||
}
|
||||
|
||||
data class StatusAndPaymentMethodId(
|
||||
val intentId: String,
|
||||
val status: StripeIntentStatus,
|
||||
val paymentMethod: String?
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.donations.StripeApi
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
/**
|
||||
@@ -21,7 +22,7 @@ object ExternalNavigationHelper {
|
||||
|
||||
fun maybeLaunchExternalNavigationIntent(context: Context, webRequestUri: Uri?, launchIntent: (Intent) -> Unit): Boolean {
|
||||
val url = webRequestUri ?: return false
|
||||
if (url.scheme?.startsWith("http") == true) {
|
||||
if (url.scheme?.startsWith("http") == true || url.scheme == StripeApi.RETURN_URL_SCHEME) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -165,14 +165,13 @@ class StripePaymentInProgressViewModel(
|
||||
paymentSourceProvider.paymentSourceType.code
|
||||
)
|
||||
)
|
||||
.flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult, paymentSourceProvider.paymentSourceType) }
|
||||
.map { (_, paymentMethod) -> paymentMethod ?: secure3DSAction.paymentMethodId!! }
|
||||
.flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult) }
|
||||
}
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it, paymentSourceProvider.paymentSourceType) }
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it.paymentMethod!!, it.intentId, paymentSourceProvider.paymentSourceType) }
|
||||
.onErrorResumeNext {
|
||||
when {
|
||||
it is DonationError -> Completable.error(it)
|
||||
it is DonationProcessorError -> Completable.error(it.toDonationError(DonationErrorSource.MONTHLY, paymentSourceProvider.paymentSourceType))
|
||||
when (it) {
|
||||
is DonationError -> Completable.error(it)
|
||||
is DonationProcessorError -> Completable.error(it.toDonationError(DonationErrorSource.MONTHLY, paymentSourceProvider.paymentSourceType))
|
||||
else -> Completable.error(DonationError.getPaymentSetupError(DonationErrorSource.MONTHLY, it, paymentSourceProvider.paymentSourceType))
|
||||
}
|
||||
}
|
||||
@@ -225,7 +224,7 @@ class StripePaymentInProgressViewModel(
|
||||
)
|
||||
)
|
||||
}
|
||||
.flatMap { stripeRepository.getStatusAndPaymentMethodId(it, paymentSourceProvider.paymentSourceType) }
|
||||
.flatMap { stripeRepository.getStatusAndPaymentMethodId(it) }
|
||||
.flatMapCompletable {
|
||||
oneTimeDonationRepository.waitForOneTimeRedemption(
|
||||
gatewayRequest = request,
|
||||
|
||||
@@ -303,7 +303,7 @@ public class DonationReceiptRedemptionJob extends BaseJob {
|
||||
}
|
||||
|
||||
private boolean isForSubscription() {
|
||||
return Objects.equals(getParameters().getQueue(), SUBSCRIPTION_QUEUE);
|
||||
return Objects.requireNonNull(getParameters().getQueue()).startsWith(SUBSCRIPTION_QUEUE);
|
||||
}
|
||||
|
||||
private boolean isForOneTimeDonation() {
|
||||
|
||||
@@ -139,8 +139,13 @@ class ExternalLaunchDonationJob private constructor(
|
||||
val subscriber = SignalStore.donationsValues().requireSubscriber()
|
||||
|
||||
Log.i(TAG, "Setting default payment method...", true)
|
||||
val setPaymentMethodResponse = ApplicationDependencies.getDonationsService()
|
||||
.setDefaultStripePaymentMethod(subscriber.subscriberId, stripeSetupIntent.paymentMethod!!)
|
||||
val setPaymentMethodResponse = if (stripe3DSData.paymentSourceType == PaymentSourceType.Stripe.IDEAL) {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.setDefaultIdealPaymentMethod(subscriber.subscriberId, stripeSetupIntent.id)
|
||||
} else {
|
||||
ApplicationDependencies.getDonationsService()
|
||||
.setDefaultStripePaymentMethod(subscriber.subscriberId, stripeSetupIntent.paymentMethod!!)
|
||||
}
|
||||
|
||||
getResultOrThrow(setPaymentMethodResponse)
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ class StripeApi(
|
||||
private val CARD_YEAR_KEY = "card[exp_year]"
|
||||
private val CARD_CVC_KEY = "card[cvc]"
|
||||
|
||||
private const val RETURN_URL_3DS = "sgnlpay://3DS"
|
||||
const val RETURN_URL_SCHEME = "sgnlpay"
|
||||
private const val RETURN_URL_3DS = "$RETURN_URL_SCHEME://3DS"
|
||||
}
|
||||
|
||||
sealed class CreatePaymentIntentResult {
|
||||
|
||||
@@ -15,24 +15,5 @@ data class StripeSetupIntent @JsonCreator constructor(
|
||||
@JsonProperty("client_secret") val clientSecret: String,
|
||||
@JsonProperty("status") val status: StripeIntentStatus,
|
||||
@JsonProperty("payment_method") val paymentMethod: String?,
|
||||
@JsonProperty("customer") val customer: String?,
|
||||
@JsonProperty("latest_attempt") val latestAttempt: LatestAttempt?
|
||||
) {
|
||||
|
||||
fun requireGeneratedSepaDebit(): String = latestAttempt!!.paymentMethodDetails!!.ideal!!.generatedSepaDebit!!
|
||||
|
||||
@JsonIgnoreProperties
|
||||
data class LatestAttempt @JsonCreator constructor(
|
||||
@JsonProperty("payment_method_details") val paymentMethodDetails: PaymentMethodDetails?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties
|
||||
data class PaymentMethodDetails @JsonCreator constructor(
|
||||
@JsonProperty("ideal") val ideal: Ideal?
|
||||
)
|
||||
|
||||
@JsonIgnoreProperties
|
||||
data class Ideal @JsonCreator constructor(
|
||||
@JsonProperty("generated_sepa_debit") val generatedSepaDebit: String?
|
||||
)
|
||||
}
|
||||
@JsonProperty("customer") val customer: String?
|
||||
)
|
||||
|
||||
@@ -228,6 +228,13 @@ public class DonationsService {
|
||||
});
|
||||
}
|
||||
|
||||
public ServiceResponse<EmptyResponse> setDefaultIdealPaymentMethod(SubscriberId subscriberId, String setupIntentId) {
|
||||
return wrapInServiceResponse(() -> {
|
||||
pushServiceSocket.setDefaultIdealSubscriptionPaymentMethod(subscriberId.serialize(), setupIntentId);
|
||||
return new Pair<>(EmptyResponse.INSTANCE, 200);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriberId The subscriber ID to create a payment method for.
|
||||
* @return Client secret for a SetupIntent. It should not be used with the PaymentIntent stripe APIs
|
||||
|
||||
@@ -281,6 +281,7 @@ public class PushServiceSocket {
|
||||
private static final String CREATE_STRIPE_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/create_payment_method?type=%s";
|
||||
private static final String CREATE_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/create_payment_method/paypal";
|
||||
private static final String DEFAULT_STRIPE_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method/stripe/%s";
|
||||
private static final String DEFAULT_IDEAL_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method_for_ideal/%s";
|
||||
private static final String DEFAULT_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method/braintree/%s";
|
||||
private static final String SUBSCRIPTION_RECEIPT_CREDENTIALS = "/v1/subscription/%s/receipt_credentials";
|
||||
private static final String CREATE_STRIPE_ONE_TIME_PAYMENT_INTENT = "/v1/subscription/boost/create";
|
||||
@@ -1245,6 +1246,10 @@ public class PushServiceSocket {
|
||||
makeServiceRequestWithoutAuthentication(String.format(DEFAULT_STRIPE_SUBSCRIPTION_PAYMENT_METHOD, subscriberId, paymentMethodId), "POST", "");
|
||||
}
|
||||
|
||||
public void setDefaultIdealSubscriptionPaymentMethod(String subscriberId, String setupIntentId) throws IOException {
|
||||
makeServiceRequestWithoutAuthentication(String.format(DEFAULT_IDEAL_SUBSCRIPTION_PAYMENT_METHOD, subscriberId, setupIntentId), "POST", "");
|
||||
}
|
||||
|
||||
public void setDefaultPaypalSubscriptionPaymentMethod(String subscriberId, String paymentMethodId) throws IOException {
|
||||
makeServiceRequestWithoutAuthentication(String.format(DEFAULT_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD, subscriberId, paymentMethodId), "POST", "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user