Fix crash loop when writing invalid currency .

This commit is contained in:
Alex Hart 2024-06-13 18:03:20 -03:00 committed by GitHub
parent 71979b34db
commit cb171092cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 63 additions and 40 deletions

View file

@ -47,6 +47,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.io.ByteArrayInputStream
import java.util.Currency
import java.util.UUID
import kotlin.random.Random
@ -253,7 +254,7 @@ class BackupTest {
SignalDatabase.recipients.setProfileName(self.id, ProfileName.fromParts("Peter", "Parker"))
SignalDatabase.recipients.setProfileAvatar(self.id, "https://example.com/")
InAppPaymentsRepository.setSubscriber(InAppPaymentSubscriberRecord(SubscriberId.generate(), "USD", InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN))
InAppPaymentsRepository.setSubscriber(InAppPaymentSubscriberRecord(SubscriberId.generate(), Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN))
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE

View file

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.util.Currency
@RunWith(AndroidJUnit4::class)
class SubscriberIdMigrationJobTest {
@ -35,7 +36,7 @@ class SubscriberIdMigrationJobTest {
@Test
fun givenUSDSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectASingleEntry() {
val subscriberId = SubscriberId.generate()
SignalStore.donationsValues().setSubscriberCurrency("USD", InAppPaymentSubscriberRecord.Type.DONATION)
SignalStore.donationsValues().setSubscriberCurrency(Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION)
SignalStore.donationsValues().setSubscriber("USD", subscriberId)
SignalStore.donationsValues().setSubscriptionPaymentSourceType(PaymentSourceType.PayPal)
SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt = true
@ -48,7 +49,7 @@ class SubscriberIdMigrationJobTest {
actual!!.subscriberId.bytes assertIs subscriberId.bytes
actual.paymentMethodType assertIs InAppPaymentData.PaymentMethodType.PAYPAL
actual.requiresCancel assertIs true
actual.currencyCode assertIs "USD"
actual.currency assertIs Currency.getInstance("USD")
actual.type assertIs InAppPaymentSubscriberRecord.Type.DONATION
}
}

View file

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents
import org.whispersystems.signalservice.api.storage.StorageRecordProtoUtil.defaultAccountRecord
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.Currency
import kotlin.jvm.optionals.getOrNull
object AccountDataProcessor {
@ -51,7 +52,7 @@ object AccountDataProcessor {
subscriptionManuallyCancelled = InAppPaymentsRepository.isUserManuallyCancelled(InAppPaymentSubscriberRecord.Type.DONATION),
username = self.username.getOrNull(),
subscriberId = subscriber?.subscriberId?.bytes?.toByteString() ?: defaultAccountRecord.subscriberId,
subscriberCurrencyCode = subscriber?.currencyCode ?: defaultAccountRecord.subscriberCurrencyCode,
subscriberCurrencyCode = subscriber?.currency?.currencyCode ?: defaultAccountRecord.subscriberCurrencyCode,
accountSettings = AccountData.AccountSettings(
storyViewReceiptsEnabled = SignalStore.storyValues().viewedReceiptsEnabled,
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context),
@ -108,7 +109,7 @@ object AccountDataProcessor {
val subscriber = InAppPaymentSubscriberRecord(
remoteSubscriberId,
accountData.subscriberCurrencyCode,
Currency.getInstance(accountData.subscriberCurrencyCode),
InAppPaymentSubscriberRecord.Type.DONATION,
localSubscriber?.requiresCancel ?: false,
InAppPaymentsRepository.getLatestPaymentMethodType(InAppPaymentSubscriberRecord.Type.DONATION)

View file

@ -115,7 +115,7 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer
InAppPaymentsRepository.setSubscriber(
InAppPaymentSubscriberRecord(
subscriberId = subscriberId,
currencyCode = SignalStore.donationsValues().getSubscriptionCurrency(subscriberType).currencyCode,
currency = SignalStore.donationsValues().getSubscriptionCurrency(subscriberType),
type = subscriberType,
requiresCancel = false,
paymentMethodType = InAppPaymentData.PaymentMethodType.UNKNOWN
@ -193,7 +193,7 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer
AppDependencies.donationsService.updateSubscriptionLevel(
subscriber.subscriberId,
subscriptionLevel,
subscriber.currencyCode,
subscriber.currency.currencyCode,
levelUpdateOperation.idempotencyKey.serialize(),
subscriberType
)

View file

@ -50,7 +50,7 @@ class SetCurrencyViewModel(
InAppPaymentsRepository.setSubscriber(
InAppPaymentSubscriberRecord(
subscriberId = SubscriberId.generate(),
currencyCode = currency.currencyCode,
currency = currency,
type = inAppPaymentType.requireSubscriberType(),
requiresCancel = false,
paymentMethodType = InAppPaymentData.PaymentMethodType.UNKNOWN

View file

@ -392,7 +392,7 @@ class DonateToSignalViewModel(
if (selectedCurrency !in priceCurrencies) {
Log.w(TAG, "Unsupported currency selection. Defaulting to USD. $selectedCurrency isn't supported.")
val usd = PlatformCurrencyUtil.USD
val newSubscriber = InAppPaymentsRepository.getSubscriber(usd, InAppPaymentSubscriberRecord.Type.DONATION) ?: InAppPaymentSubscriberRecord(SubscriberId.generate(), usd.currencyCode, InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN)
val newSubscriber = InAppPaymentsRepository.getSubscriber(usd, InAppPaymentSubscriberRecord.Type.DONATION) ?: InAppPaymentSubscriberRecord(SubscriberId.generate(), usd, InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN)
InAppPaymentsRepository.setSubscriber(newSubscriber)
subscriptionsRepository.syncAccountRecord().subscribe()
}

View file

@ -240,7 +240,7 @@ class InternalConversationSettingsFragment : DSLSettingsFragment(
// TODO [alex] - DB on main thread!
val subscriber: InAppPaymentSubscriberRecord? = InAppPaymentsRepository.getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)
val summary = if (subscriber != null) {
"""currency code: ${subscriber.currencyCode}
"""currency code: ${subscriber.currency.currencyCode}
|subscriber id: ${subscriber.subscriberId.serialize()}
""".trimMargin()
} else {

View file

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.util.Currency
/**
* A table matching up SubscriptionIds to currency codes and type
@ -77,7 +78,7 @@ class InAppPaymentSubscriberTable(
* This is a destructive, mutating operation. For setting specific values, prefer the alternative setters available on this table class.
*/
fun insertOrReplace(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord) {
Log.i(TAG, "Setting subscriber for currency ${inAppPaymentSubscriberRecord.currencyCode}", Exception(), true)
Log.i(TAG, "Setting subscriber for currency ${inAppPaymentSubscriberRecord.currency.currencyCode}", Exception(), true)
writableDatabase.withinTransaction { db ->
db.insertInto(TABLE_NAME)
@ -85,7 +86,7 @@ class InAppPaymentSubscriberTable(
.run(conflictStrategy = SQLiteDatabase.CONFLICT_REPLACE)
SignalStore.donationsValues().setSubscriberCurrency(
inAppPaymentSubscriberRecord.currencyCode,
inAppPaymentSubscriberRecord.currency,
inAppPaymentSubscriberRecord.type
)
}
@ -137,7 +138,7 @@ class InAppPaymentSubscriberTable(
override fun serialize(data: InAppPaymentSubscriberRecord): ContentValues {
return contentValuesOf(
SUBSCRIBER_ID to data.subscriberId.serialize(),
CURRENCY_CODE to data.currencyCode.uppercase(),
CURRENCY_CODE to data.currency.currencyCode.uppercase(),
TYPE to TypeSerializer.serialize(data.type),
REQUIRES_CANCEL to data.requiresCancel,
PAYMENT_METHOD_TYPE to data.paymentMethodType.value
@ -145,11 +146,13 @@ class InAppPaymentSubscriberTable(
}
override fun deserialize(input: Cursor): InAppPaymentSubscriberRecord {
val type = TypeSerializer.deserialize(input.requireInt(TYPE))
val currencyCode = input.requireNonNullString(CURRENCY_CODE).takeIf { it.isNotEmpty() }
return InAppPaymentSubscriberRecord(
subscriberId = SubscriberId.deserialize(input.requireNonNullString(SUBSCRIBER_ID)),
currencyCode = input.requireNonNullString(CURRENCY_CODE),
type = TypeSerializer.deserialize(input.requireInt(TYPE)),
requiresCancel = input.requireBoolean(REQUIRES_CANCEL),
currency = currencyCode?.let { Currency.getInstance(it) } ?: SignalStore.donationsValues().getSubscriptionCurrency(type),
type = type,
requiresCancel = input.requireBoolean(REQUIRES_CANCEL) || currencyCode.isNullOrBlank(),
paymentMethodType = InAppPaymentData.PaymentMethodType.fromValue(input.requireInt(PAYMENT_METHOD_TYPE)) ?: InAppPaymentData.PaymentMethodType.UNKNOWN
)
}

View file

@ -8,6 +8,7 @@ package org.thoughtcrime.securesms.database.model
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.util.Currency
/**
* Represents a SubscriberId and metadata that can be used for a recurring
@ -15,7 +16,7 @@ import org.whispersystems.signalservice.api.subscriptions.SubscriberId
*/
data class InAppPaymentSubscriberRecord(
val subscriberId: SubscriberId,
val currencyCode: String,
val currency: Currency,
val type: Type,
val requiresCancel: Boolean,
val paymentMethodType: InAppPaymentData.PaymentMethodType

View file

@ -164,7 +164,7 @@ class ExternalLaunchDonationJob private constructor(
val updateSubscriptionLevelResponse = AppDependencies.donationsService.updateSubscriptionLevel(
subscriber.subscriberId,
subscriptionLevel,
subscriber.currencyCode,
subscriber.currency.currencyCode,
levelUpdateOperation.idempotencyKey.serialize(),
subscriber.type
)

View file

@ -233,7 +233,7 @@ class InAppPaymentAuthCheckJob private constructor(parameters: Parameters) : Bas
val updateLevelResponse = AppDependencies.donationsService.updateSubscriptionLevel(
subscriber.subscriberId,
level,
subscriber.currencyCode,
subscriber.currency.currencyCode,
updateOperation.idempotencyKey.serialize(),
subscriber.type
)

View file

@ -250,7 +250,7 @@ class InAppPaymentKeepAliveJob private constructor(
inAppPaymentData = InAppPaymentData(
badge = badge,
amount = FiatValue(
currencyCode = subscriber.currencyCode,
currencyCode = subscriber.currency.currencyCode,
amount = subscription.amount.toDecimalValue()
),
error = null,

View file

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.Cha
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.Subscription
import org.whispersystems.signalservice.internal.ServiceResponse
import java.io.IOException
import java.util.Currency
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@ -288,10 +289,15 @@ class InAppPaymentRecurringContextJob private constructor(
}
private fun handlePaymentFailure(inAppPayment: InAppPaymentTable.InAppPayment, subscription: Subscription, chargeFailure: ChargeFailure?) {
val subscriber = SignalDatabase.inAppPaymentSubscribers.getBySubscriberId(inAppPayment.subscriberId!!)
if (subscriber != null) {
InAppPaymentsRepository.setShouldCancelSubscriptionBeforeNextSubscribeAttempt(subscriber, true)
}
SignalDatabase.inAppPaymentSubscribers.insertOrReplace(
InAppPaymentSubscriberRecord(
subscriberId = inAppPayment.subscriberId!!,
currency = Currency.getInstance(inAppPayment.data.amount!!.currencyCode),
type = inAppPayment.type.requireSubscriberType(),
requiresCancel = true,
paymentMethodType = inAppPayment.data.paymentMethodType
)
)
if (inAppPayment.data.redemption?.keepAlive == true) {
info("Cancellation occurred during keep-alive. Setting cancellation state.")

View file

@ -245,7 +245,7 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
} else {
InAppPaymentSubscriberRecord(
SubscriberId.fromBytes(subscriberIdBytes),
currencyCode,
currency,
InAppPaymentSubscriberRecord.Type.DONATION,
shouldCancelSubscriptionBeforeNextSubscribeAttempt,
getSubscriptionPaymentSourceType().toPaymentMethodType()
@ -253,19 +253,19 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
}
}
fun setSubscriberCurrency(currencyCode: String, type: InAppPaymentSubscriberRecord.Type) {
fun setSubscriberCurrency(currency: Currency, type: InAppPaymentSubscriberRecord.Type) {
if (type == InAppPaymentSubscriberRecord.Type.DONATION) {
store.beginWrite()
.putString(KEY_DONATION_SUBSCRIPTION_CURRENCY_CODE, currencyCode)
.putString(KEY_DONATION_SUBSCRIPTION_CURRENCY_CODE, currency.currencyCode)
.apply()
recurringDonationCurrencyPublisher.onNext(Currency.getInstance(currencyCode))
recurringDonationCurrencyPublisher.onNext(currency)
} else {
store.beginWrite()
.putString(KEY_BACKUPS_SUBSCRIPTION_CURRENCY_CODE, currencyCode)
.putString(KEY_BACKUPS_SUBSCRIPTION_CURRENCY_CODE, currency.currencyCode)
.apply()
recurringBackupCurrencyPublisher.onNext(Currency.getInstance(currencyCode))
recurringBackupCurrencyPublisher.onNext(currency)
}
}

View file

@ -37,7 +37,7 @@ internal class SubscriberIdMigrationJob(
SignalDatabase.inAppPaymentSubscribers.insertOrReplace(
InAppPaymentSubscriberRecord(
subscriber.subscriberId,
subscriber.currencyCode,
subscriber.currency,
InAppPaymentSubscriberRecord.Type.DONATION,
SignalStore.donationsValues().shouldCancelSubscriptionBeforeNextSubscribeAttempt,
SignalStore.donationsValues().getSubscriptionPaymentSourceType().toPaymentMethodType()

View file

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.database.model.RecipientRecord;
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
@ -32,6 +33,7 @@ import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState;
import org.whispersystems.signalservice.internal.storage.protos.GroupV2Record;
import java.util.Currency;
import java.util.List;
import java.util.stream.Collectors;
@ -281,24 +283,32 @@ public final class StorageSyncModels {
if (subscriber == null) {
return new SignalAccountRecord.Subscriber(null, null);
} else {
return new SignalAccountRecord.Subscriber(subscriber.getCurrencyCode(), subscriber.getSubscriberId().getBytes());
return new SignalAccountRecord.Subscriber(subscriber.getCurrency().getCurrencyCode(), subscriber.getSubscriberId().getBytes());
}
}
/**
* TODO - We need to store the subscriber type.
*/
public static @Nullable InAppPaymentSubscriberRecord remoteToLocalSubscriber(
@NonNull SignalAccountRecord.Subscriber subscriber,
@NonNull InAppPaymentSubscriberRecord.Type type
) {
if (subscriber.getId().isPresent()) {
SubscriberId subscriberId = SubscriberId.fromBytes(subscriber.getId().get());
InAppPaymentSubscriberRecord localSubscriberRecord = SignalDatabase.inAppPaymentSubscribers().getBySubscriberId(subscriberId);
boolean requiresCancel = localSubscriberRecord != null && localSubscriberRecord.getRequiresCancel();
InAppPaymentData.PaymentMethodType paymentMethodType = localSubscriberRecord != null ? localSubscriberRecord.getPaymentMethodType() : InAppPaymentData.PaymentMethodType.UNKNOWN;
SubscriberId subscriberId = SubscriberId.fromBytes(subscriber.getId().get());
InAppPaymentSubscriberRecord localSubscriberRecord = SignalDatabase.inAppPaymentSubscribers().getBySubscriberId(subscriberId);
boolean requiresCancel = localSubscriberRecord != null && localSubscriberRecord.getRequiresCancel();
InAppPaymentData.PaymentMethodType paymentMethodType = localSubscriberRecord != null ? localSubscriberRecord.getPaymentMethodType() : InAppPaymentData.PaymentMethodType.UNKNOWN;
return new InAppPaymentSubscriberRecord(subscriberId, subscriber.getCurrencyCode().get(), type, requiresCancel, paymentMethodType);
Currency currency;
if (subscriber.getCurrencyCode().isEmpty()) {
return null;
} else {
try {
currency = Currency.getInstance(subscriber.getCurrencyCode().get());
} catch (IllegalArgumentException e) {
return null;
}
}
return new InAppPaymentSubscriberRecord(subscriberId, currency, type, requiresCancel, paymentMethodType);
} else {
return null;
}