IAP Resolve and synchronize payment method from subscription object.

This commit is contained in:
Alex Hart
2025-03-04 16:32:13 -04:00
committed by GitHub
parent d87ee495d3
commit be43f8ce88
9 changed files with 102 additions and 32 deletions

View File

@@ -210,7 +210,7 @@ class StripeRepository(
}
StripeIntentAccessor.ObjectType.SETUP_INTENT -> stripeApi.getSetupIntent(stripeIntentAccessor).let {
StatusAndPaymentMethodId(stripeIntentAccessor.intentId, it.status, it.paymentMethod)
StatusAndPaymentMethodId(stripeIntentAccessor.intentId, it.status, it.paymentMethodId)
}
}
}

View File

@@ -352,7 +352,7 @@ class DonateToSignalFragment :
R.string.DonateToSignalFragment__your_payment_is_still_being_processed_onetime
}
} else {
if (state.monthlyDonationState.activeSubscription?.paymentMethod == ActiveSubscription.PAYMENT_METHOD_SEPA_DEBIT) {
if (state.monthlyDonationState.activeSubscription?.paymentMethod == ActiveSubscription.PaymentMethod.SEPA_DEBIT) {
R.string.DonateToSignalFragment__bank_transfers_usually_take_1_business_day_to_process_monthly
} else if (state.monthlyDonationState.nonVerifiedMonthlyDonation != null) {
R.string.DonateToSignalFragment__your_ideal_payment_is_still_processing

View File

@@ -86,7 +86,7 @@ data class DonateToSignalState(
}
val isUpdateLongRunning: Boolean
get() = monthlyDonationState.activeSubscription?.paymentMethod == ActiveSubscription.PAYMENT_METHOD_SEPA_DEBIT
get() = monthlyDonationState.activeSubscription?.paymentMethod == ActiveSubscription.PaymentMethod.SEPA_DEBIT
data class OneTimeDonationState(
val badge: Badge? = null,

View File

@@ -228,7 +228,7 @@ class InAppPaymentAuthCheckJob private constructor(parameters: Parameters) : Bas
val setPaymentMethodResponse = if (inAppPayment.data.paymentMethodType == InAppPaymentData.PaymentMethodType.IDEAL) {
AppDependencies.donationsService.setDefaultIdealPaymentMethod(subscriber.subscriberId, stripeSetupIntent.id)
} else {
AppDependencies.donationsService.setDefaultStripePaymentMethod(subscriber.subscriberId, stripeSetupIntent.paymentMethod)
AppDependencies.donationsService.setDefaultStripePaymentMethod(subscriber.subscriberId, stripeSetupIntent.paymentMethodId)
}
when (val result = checkResult(setPaymentMethodResponse)) {
@@ -239,6 +239,10 @@ class InAppPaymentAuthCheckJob private constructor(parameters: Parameters) : Bas
Log.d(TAG, "Set default payment method via Signal service.", true)
SignalDatabase.inAppPaymentSubscribers.setPaymentMethod(subscriber.subscriberId, inAppPayment.data.paymentMethodType)
Log.d(TAG, "Wrote default payment method to subscriber database entry.", true)
val level = inAppPayment.data.level.toString()
try {

View File

@@ -235,13 +235,33 @@ class InAppPaymentKeepAliveJob private constructor(
SignalStore.inAppPayments.setLastEndOfPeriod(endOfCurrentPeriod.inWholeSeconds)
}
val subscriptionPaymentMethodType: InAppPaymentData.PaymentMethodType = subscription.paymentMethod.toInAppPaymentDataPaymentMethodType()
val subscriberPaymentMethodType: InAppPaymentData.PaymentMethodType = subscriber.paymentMethodType
val newInAppPaymentMethodType: InAppPaymentData.PaymentMethodType = when (subscriptionPaymentMethodType) {
InAppPaymentData.PaymentMethodType.CARD -> {
if (subscriberPaymentMethodType == InAppPaymentData.PaymentMethodType.GOOGLE_PAY) {
InAppPaymentData.PaymentMethodType.GOOGLE_PAY
} else {
InAppPaymentData.PaymentMethodType.CARD
}
}
InAppPaymentData.PaymentMethodType.UNKNOWN -> {
subscriberPaymentMethodType
}
else -> subscriptionPaymentMethodType
}
info(type, "Resolved payment method type from subscription: $newInAppPaymentMethodType")
SignalDatabase.inAppPaymentSubscribers.setPaymentMethod(subscriber.subscriberId, paymentMethodType = newInAppPaymentMethodType)
val inAppPaymentId = SignalDatabase.inAppPayments.insert(
type = type.inAppPaymentType,
state = InAppPaymentTable.State.PENDING,
subscriberId = subscriber.subscriberId,
endOfPeriod = endOfCurrentPeriod,
inAppPaymentData = InAppPaymentData(
paymentMethodType = subscriber.paymentMethodType,
paymentMethodType = newInAppPaymentMethodType,
badge = badge,
amount = FiatValue(
currencyCode = subscription.currency,
@@ -298,6 +318,18 @@ class InAppPaymentKeepAliveJob private constructor(
Log.w(TAG, "[$type] $message", throwable, true)
}
private fun ActiveSubscription.PaymentMethod.toInAppPaymentDataPaymentMethodType(): InAppPaymentData.PaymentMethodType {
return when (this) {
ActiveSubscription.PaymentMethod.UNKNOWN -> InAppPaymentData.PaymentMethodType.UNKNOWN
ActiveSubscription.PaymentMethod.CARD -> InAppPaymentData.PaymentMethodType.CARD
ActiveSubscription.PaymentMethod.PAYPAL -> InAppPaymentData.PaymentMethodType.PAYPAL
ActiveSubscription.PaymentMethod.SEPA_DEBIT -> InAppPaymentData.PaymentMethodType.SEPA_DEBIT
ActiveSubscription.PaymentMethod.IDEAL -> InAppPaymentData.PaymentMethodType.IDEAL
ActiveSubscription.PaymentMethod.GOOGLE_PLAY_BILLING -> InAppPaymentData.PaymentMethodType.GOOGLE_PLAY_BILLING
ActiveSubscription.PaymentMethod.APPLE_APP_STORE -> InAppPaymentData.PaymentMethodType.UNKNOWN
}
}
override fun serialize(): ByteArray? = JsonJobData.Builder().putInt(DATA_TYPE, type.code).build().serialize()
override fun getFactoryKey(): String = KEY

View File

@@ -33,18 +33,19 @@ final class LogSectionBadges implements LogSection {
InAppPaymentTable.InAppPayment latestRecurringDonation = SignalDatabase.inAppPayments().getLatestInAppPaymentByType(InAppPaymentType.RECURRING_DONATION);
if (latestRecurringDonation != null) {
return new StringBuilder().append("Badge Count : ").append(Recipient.self().getBadges().size()).append("\n")
.append("ExpiredBadge : ").append(SignalStore.inAppPayments().getExpiredBadge() != null).append("\n")
.append("LastKeepAliveLaunchTime : ").append(SignalStore.inAppPayments().getLastKeepAliveLaunchTime()).append("\n")
.append("LastEndOfPeriod : ").append(SignalStore.inAppPayments().getLastEndOfPeriod()).append("\n")
.append("InAppPayment.State : ").append(latestRecurringDonation.getState()).append("\n")
.append("InAppPayment.EndOfPeriod : ").append(latestRecurringDonation.getEndOfPeriodSeconds()).append("\n")
.append("InAppPaymentData.RedemptionState: ").append(getRedemptionStage(latestRecurringDonation.getData())).append("\n")
.append("InAppPaymentData.Error : ").append(getError(latestRecurringDonation.getData())).append("\n")
.append("InAppPaymentData.Cancellation : ").append(getCancellation(latestRecurringDonation.getData())).append("\n")
.append("DisplayBadgesOnProfile : ").append(SignalStore.inAppPayments().getDisplayBadgesOnProfile()).append("\n")
.append("ShouldCancelBeforeNextAttempt : ").append(InAppPaymentsRepository.getShouldCancelSubscriptionBeforeNextSubscribeAttempt(InAppPaymentSubscriberRecord.Type.DONATION)).append("\n")
.append("IsUserManuallyCancelledDonation : ").append(SignalStore.inAppPayments().isDonationSubscriptionManuallyCancelled()).append("\n");
return new StringBuilder().append("Badge Count : ").append(Recipient.self().getBadges().size()).append("\n")
.append("ExpiredBadge : ").append(SignalStore.inAppPayments().getExpiredBadge() != null).append("\n")
.append("LastKeepAliveLaunchTime : ").append(SignalStore.inAppPayments().getLastKeepAliveLaunchTime()).append("\n")
.append("LastEndOfPeriod : ").append(SignalStore.inAppPayments().getLastEndOfPeriod()).append("\n")
.append("InAppPayment.State : ").append(latestRecurringDonation.getState()).append("\n")
.append("InAppPayment.EndOfPeriod : ").append(latestRecurringDonation.getEndOfPeriodSeconds()).append("\n")
.append("InAppPaymentData.PaymentMethodType: ").append(getPaymentMethod(latestRecurringDonation.getData())).append("\n")
.append("InAppPaymentData.RedemptionState : ").append(getRedemptionStage(latestRecurringDonation.getData())).append("\n")
.append("InAppPaymentData.Error : ").append(getError(latestRecurringDonation.getData())).append("\n")
.append("InAppPaymentData.Cancellation : ").append(getCancellation(latestRecurringDonation.getData())).append("\n")
.append("DisplayBadgesOnProfile : ").append(SignalStore.inAppPayments().getDisplayBadgesOnProfile()).append("\n")
.append("ShouldCancelBeforeNextAttempt : ").append(InAppPaymentsRepository.getShouldCancelSubscriptionBeforeNextSubscribeAttempt(InAppPaymentSubscriberRecord.Type.DONATION)).append("\n")
.append("IsUserManuallyCancelledDonation : ").append(SignalStore.inAppPayments().isDonationSubscriptionManuallyCancelled()).append("\n");
} else {
return new StringBuilder().append("Badge Count : ").append(Recipient.self().getBadges().size()).append("\n")
@@ -63,6 +64,10 @@ final class LogSectionBadges implements LogSection {
}
}
private @NonNull String getPaymentMethod(@NonNull InAppPaymentData inAppPaymentData) {
return inAppPaymentData.paymentMethodType.toString();
}
private @NonNull String getRedemptionStage(@NonNull InAppPaymentData inAppPaymentData) {
if (inAppPaymentData.redemption == null) {
return "null";

View File

@@ -14,6 +14,6 @@ data class StripeSetupIntent @JsonCreator constructor(
@JsonProperty("id") val id: String,
@JsonProperty("client_secret") val clientSecret: String,
@JsonProperty("status") val status: StripeIntentStatus,
@JsonProperty("payment_method") val paymentMethod: String?,
@JsonProperty("payment_method") val paymentMethodId: String?,
@JsonProperty("customer") val customer: String?
)

View File

@@ -63,7 +63,7 @@ class StripeSetupIntentTest {
assertEquals(intent.id, "seti_1LyzgK2eZvKYlo2C3AhgI5IC")
assertEquals(intent.clientSecret, "seti_1LyzgK2eZvKYlo2C3AhgI5IC_secret_MiQXAjP1ZBdORqQWNuJOcLqk9570HkA")
assertEquals(intent.paymentMethod, "pm_sldalskdjhfalskjdhf")
assertEquals(intent.paymentMethodId, "pm_sldalskdjhfalskjdhf")
assertEquals(intent.status, StripeIntentStatus.REQUIRES_PAYMENT_METHOD)
assertEquals(intent.customer, "cus_Fh6d95jDS2fVSL")
}

View File

@@ -43,6 +43,35 @@ public final class ActiveSubscription {
}
}
/**
* As per API documentation
*/
public enum PaymentMethod {
UNKNOWN("UNKNOWN"),
CARD("CARD"),
PAYPAL("PAYPAL"),
SEPA_DEBIT("SEPA_DEBIT"),
IDEAL("IDEAL"),
GOOGLE_PLAY_BILLING("GOOGLE_PLAY_BILLING"),
APPLE_APP_STORE("APPLE_APP_STORE");
private String code;
PaymentMethod(String code) {
this.code = code;
}
static PaymentMethod fromCode(String code) {
for (PaymentMethod method : PaymentMethod.values()) {
if (Objects.equals(method.code, code)) {
return method;
}
}
return PaymentMethod.UNKNOWN;
}
}
private enum Status {
/**
* The subscription is currently in a trial period and it's safe to provision your product for your customer.
@@ -153,17 +182,17 @@ public final class ActiveSubscription {
}
public static final class Subscription {
private final int level;
private final String currency;
private final BigDecimal amount;
private final long endOfCurrentPeriod;
private final boolean isActive;
private final long billingCycleAnchor;
private final boolean willCancelAtPeriodEnd;
private final String status;
private final Processor processor;
private final String paymentMethod;
private final boolean paymentPending;
private final int level;
private final String currency;
private final BigDecimal amount;
private final long endOfCurrentPeriod;
private final boolean isActive;
private final long billingCycleAnchor;
private final boolean willCancelAtPeriodEnd;
private final String status;
private final Processor processor;
private final PaymentMethod paymentMethod;
private final boolean paymentPending;
@JsonCreator
public Subscription(@JsonProperty("level") int level,
@@ -187,7 +216,7 @@ public final class ActiveSubscription {
this.willCancelAtPeriodEnd = willCancelAtPeriodEnd;
this.status = status;
this.processor = Processor.fromCode(processor);
this.paymentMethod = paymentMethod;
this.paymentMethod = PaymentMethod.fromCode(paymentMethod);
this.paymentPending = paymentPending;
}
@@ -243,7 +272,7 @@ public final class ActiveSubscription {
return processor;
}
public String getPaymentMethod() {
public PaymentMethod getPaymentMethod() {
return paymentMethod;
}