mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-23 02:10:44 +01:00
Implement initial support for IAP data.
This commit is contained in:
committed by
Greyson Parrelli
parent
f537fa6436
commit
f2b4bd0585
@@ -13,17 +13,21 @@ import androidx.core.content.contentValuesOf
|
||||
import org.signal.core.util.DatabaseSerializer
|
||||
import org.signal.core.util.Serializer
|
||||
import org.signal.core.util.insertInto
|
||||
import org.signal.core.util.isNotNullOrBlank
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.readToSingleObject
|
||||
import org.signal.core.util.requireBoolean
|
||||
import org.signal.core.util.requireInt
|
||||
import org.signal.core.util.requireLongOrNull
|
||||
import org.signal.core.util.requireNonNullString
|
||||
import org.signal.core.util.requireString
|
||||
import org.signal.core.util.select
|
||||
import org.signal.core.util.update
|
||||
import org.signal.core.util.withinTransaction
|
||||
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.storage.IAPSubscriptionId
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import java.util.Currency
|
||||
|
||||
@@ -61,7 +65,13 @@ class InAppPaymentSubscriberTable(
|
||||
/** Specifies which payment method was utilized for the latest transaction with this id */
|
||||
private const val PAYMENT_METHOD_TYPE = "payment_method_type"
|
||||
|
||||
const val CREATE_TABLE = """
|
||||
/** Google Play Billing purchase token, only valid for backup payments */
|
||||
private const val PURCHASE_TOKEN = "purchase_token"
|
||||
|
||||
/** iOS original transaction id token, only valid for synced backups that originated on iOS */
|
||||
private const val ORIGINAL_TRANSACTION_ID = "original_transaction_id"
|
||||
|
||||
val CREATE_TABLE = """
|
||||
CREATE TABLE $TABLE_NAME (
|
||||
$ID INTEGER PRIMARY KEY,
|
||||
$SUBSCRIBER_ID TEXT NOT NULL UNIQUE,
|
||||
@@ -69,7 +79,14 @@ class InAppPaymentSubscriberTable(
|
||||
$TYPE INTEGER NOT NULL,
|
||||
$REQUIRES_CANCEL INTEGER DEFAULT 0,
|
||||
$PAYMENT_METHOD_TYPE INTEGER DEFAULT 0,
|
||||
UNIQUE($CURRENCY_CODE, $TYPE)
|
||||
$PURCHASE_TOKEN TEXT,
|
||||
$ORIGINAL_TRANSACTION_ID INTEGER,
|
||||
UNIQUE($CURRENCY_CODE, $TYPE),
|
||||
CHECK (
|
||||
($CURRENCY_CODE != '' AND $PURCHASE_TOKEN IS NULL AND $ORIGINAL_TRANSACTION_ID IS NULL AND $TYPE = ${TypeSerializer.serialize(InAppPaymentSubscriberRecord.Type.DONATION)})
|
||||
OR ($CURRENCY_CODE = '' AND $PURCHASE_TOKEN IS NOT NULL AND $ORIGINAL_TRANSACTION_ID IS NULL AND $TYPE = ${TypeSerializer.serialize(InAppPaymentSubscriberRecord.Type.BACKUP)})
|
||||
OR ($CURRENCY_CODE = '' AND $PURCHASE_TOKEN IS NULL AND $ORIGINAL_TRANSACTION_ID IS NOT NULL AND $TYPE = ${TypeSerializer.serialize(InAppPaymentSubscriberRecord.Type.BACKUP)})
|
||||
)
|
||||
)
|
||||
"""
|
||||
}
|
||||
@@ -80,20 +97,31 @@ 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.currency.currencyCode}", Exception(), true)
|
||||
if (inAppPaymentSubscriberRecord.type == InAppPaymentSubscriberRecord.Type.DONATION) {
|
||||
Log.i(TAG, "Setting subscriber for currency ${inAppPaymentSubscriberRecord.currency?.currencyCode}", Exception(), true)
|
||||
}
|
||||
|
||||
writableDatabase.withinTransaction { db ->
|
||||
db.insertInto(TABLE_NAME)
|
||||
.values(InAppPaymentSubscriberSerializer.serialize(inAppPaymentSubscriberRecord))
|
||||
.run(conflictStrategy = SQLiteDatabase.CONFLICT_REPLACE)
|
||||
|
||||
SignalStore.inAppPayments.setSubscriberCurrency(
|
||||
inAppPaymentSubscriberRecord.currency,
|
||||
inAppPaymentSubscriberRecord.type
|
||||
)
|
||||
if (inAppPaymentSubscriberRecord.type == InAppPaymentSubscriberRecord.Type.DONATION) {
|
||||
SignalStore.inAppPayments.setRecurringDonationCurrency(
|
||||
inAppPaymentSubscriberRecord.currency!!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBackupsSubscriber(): InAppPaymentSubscriberRecord? {
|
||||
return readableDatabase.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$TYPE = ?", TypeSerializer.serialize(InAppPaymentSubscriberRecord.Type.BACKUP))
|
||||
.run()
|
||||
.readToSingleObject(InAppPaymentSubscriberSerializer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the subscriber in question requires a cancellation before a new subscription can be created.
|
||||
*/
|
||||
@@ -117,10 +145,10 @@ class InAppPaymentSubscriberTable(
|
||||
/**
|
||||
* Retrieves a subscriber for the given type by the currency code.
|
||||
*/
|
||||
fun getByCurrencyCode(currencyCode: String, type: InAppPaymentSubscriberRecord.Type): InAppPaymentSubscriberRecord? {
|
||||
fun getByCurrencyCode(currencyCode: String): InAppPaymentSubscriberRecord? {
|
||||
return readableDatabase.select()
|
||||
.from(TABLE_NAME)
|
||||
.where("$TYPE = ? AND $CURRENCY_CODE = ?", TypeSerializer.serialize(type), currencyCode.uppercase())
|
||||
.where("$CURRENCY_CODE = ?", currencyCode.uppercase())
|
||||
.run()
|
||||
.readToSingleObject(InAppPaymentSubscriberSerializer)
|
||||
}
|
||||
@@ -140,10 +168,12 @@ class InAppPaymentSubscriberTable(
|
||||
override fun serialize(data: InAppPaymentSubscriberRecord): ContentValues {
|
||||
return contentValuesOf(
|
||||
SUBSCRIBER_ID to data.subscriberId.serialize(),
|
||||
CURRENCY_CODE to data.currency.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
|
||||
PAYMENT_METHOD_TYPE to data.paymentMethodType.value,
|
||||
PURCHASE_TOKEN to data.iapSubscriptionId?.purchaseToken,
|
||||
ORIGINAL_TRANSACTION_ID to data.iapSubscriptionId?.originalTransactionId
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,12 +182,36 @@ class InAppPaymentSubscriberTable(
|
||||
val currencyCode = input.requireNonNullString(CURRENCY_CODE).takeIf { it.isNotEmpty() }
|
||||
return InAppPaymentSubscriberRecord(
|
||||
subscriberId = SubscriberId.deserialize(input.requireNonNullString(SUBSCRIBER_ID)),
|
||||
currency = currencyCode?.let { Currency.getInstance(it) } ?: SignalStore.inAppPayments.getSubscriptionCurrency(type),
|
||||
currency = resolveCurrency(currencyCode, type),
|
||||
type = type,
|
||||
requiresCancel = input.requireBoolean(REQUIRES_CANCEL) || currencyCode.isNullOrBlank(),
|
||||
paymentMethodType = InAppPaymentData.PaymentMethodType.fromValue(input.requireInt(PAYMENT_METHOD_TYPE)) ?: InAppPaymentData.PaymentMethodType.UNKNOWN
|
||||
paymentMethodType = InAppPaymentData.PaymentMethodType.fromValue(input.requireInt(PAYMENT_METHOD_TYPE)) ?: InAppPaymentData.PaymentMethodType.UNKNOWN,
|
||||
iapSubscriptionId = readIAPSubscriptionIdFromCursor(input)
|
||||
)
|
||||
}
|
||||
|
||||
private fun resolveCurrency(currencyCode: String?, type: InAppPaymentSubscriberRecord.Type): Currency? {
|
||||
return currencyCode?.let {
|
||||
Currency.getInstance(currencyCode)
|
||||
} ?: if (type == InAppPaymentSubscriberRecord.Type.DONATION) {
|
||||
SignalStore.inAppPayments.getRecurringDonationCurrency()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun readIAPSubscriptionIdFromCursor(cursor: Cursor): IAPSubscriptionId? {
|
||||
val purchaseToken = cursor.requireString(PURCHASE_TOKEN)
|
||||
val originalTransactionId = cursor.requireLongOrNull(ORIGINAL_TRANSACTION_ID)
|
||||
|
||||
return if (purchaseToken.isNotNullOrBlank()) {
|
||||
IAPSubscriptionId.GooglePlayBillingPurchaseToken(purchaseToken)
|
||||
} else if (originalTransactionId != null) {
|
||||
IAPSubscriptionId.AppleIAPOriginalTransactionId(originalTransactionId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TypeSerializer : Serializer<InAppPaymentSubscriberRecord.Type, Int> {
|
||||
|
||||
@@ -118,6 +118,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V258_FixGroupRevoke
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V259_AdjustNotificationProfileMidnightEndTimes
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V260_RemapQuoteAuthors
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V261_RemapCallRingers
|
||||
import org.thoughtcrime.securesms.database.helpers.migration.V262_InAppPaymentsSubscriberTableRebuild
|
||||
|
||||
/**
|
||||
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
|
||||
@@ -238,10 +239,11 @@ object SignalDatabaseMigrations {
|
||||
258 to V258_FixGroupRevokedInviteeUpdate,
|
||||
259 to V259_AdjustNotificationProfileMidnightEndTimes,
|
||||
260 to V260_RemapQuoteAuthors,
|
||||
261 to V261_RemapCallRingers
|
||||
261 to V261_RemapCallRingers,
|
||||
261 to V262_InAppPaymentsSubscriberTableRebuild
|
||||
)
|
||||
|
||||
const val DATABASE_VERSION = 261
|
||||
const val DATABASE_VERSION = 262
|
||||
|
||||
@JvmStatic
|
||||
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.database.helpers.migration
|
||||
|
||||
import android.app.Application
|
||||
import net.zetetic.database.sqlcipher.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Adds IAP fields and updates constraints.
|
||||
*/
|
||||
@Suppress("ClassName")
|
||||
object V262_InAppPaymentsSubscriberTableRebuild : SignalDatabaseMigration {
|
||||
|
||||
private const val DONOR_TYPE = 0
|
||||
private const val BACKUP_TYPE = 1
|
||||
|
||||
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
db.execSQL(
|
||||
"""
|
||||
CREATE TABLE in_app_payment_subscriber_tmp (
|
||||
_id INTEGER PRIMARY KEY,
|
||||
subscriber_id TEXT NOT NULL UNIQUE,
|
||||
currency_code TEXT NOT NULL,
|
||||
type INTEGER NOT NULL,
|
||||
requires_cancel INTEGER DEFAULT 0,
|
||||
payment_method_type INTEGER DEFAULT 0,
|
||||
purchase_token TEXT,
|
||||
original_transaction_id INTEGER,
|
||||
UNIQUE(currency_code, type),
|
||||
CHECK (
|
||||
(currency_code != '' AND purchase_token IS NULL AND original_transaction_id IS NULL AND type = $DONOR_TYPE)
|
||||
OR (currency_code = '' AND purchase_token IS NOT NULL AND original_transaction_id IS NULL AND type = $BACKUP_TYPE)
|
||||
OR (currency_code = '' AND purchase_token IS NULL AND original_transaction_id IS NOT NULL AND type = $BACKUP_TYPE)
|
||||
)
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"""
|
||||
INSERT INTO in_app_payment_subscriber_tmp (_id, subscriber_id, currency_code, type, requires_cancel, payment_method_type, purchase_token)
|
||||
SELECT
|
||||
_id,
|
||||
subscriber_id,
|
||||
CASE
|
||||
WHEN type = $DONOR_TYPE THEN currency_code
|
||||
ELSE ''
|
||||
END,
|
||||
type,
|
||||
requires_cancel,
|
||||
payment_method_type,
|
||||
CASE
|
||||
WHEN type = $BACKUP_TYPE THEN "-"
|
||||
ELSE NULL
|
||||
END
|
||||
FROM in_app_payment_subscriber
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
db.execSQL("DROP TABLE in_app_payment_subscriber")
|
||||
|
||||
db.execSQL("ALTER TABLE in_app_payment_subscriber_tmp RENAME TO in_app_payment_subscriber")
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.database.model
|
||||
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.whispersystems.signalservice.api.storage.IAPSubscriptionId
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
import java.util.Currency
|
||||
import java.util.concurrent.locks.Lock
|
||||
@@ -18,10 +19,11 @@ import java.util.concurrent.locks.ReentrantLock
|
||||
*/
|
||||
data class InAppPaymentSubscriberRecord(
|
||||
val subscriberId: SubscriberId,
|
||||
val currency: Currency,
|
||||
val type: Type,
|
||||
val requiresCancel: Boolean,
|
||||
val paymentMethodType: InAppPaymentData.PaymentMethodType
|
||||
val paymentMethodType: InAppPaymentData.PaymentMethodType,
|
||||
val currency: Currency?,
|
||||
val iapSubscriptionId: IAPSubscriptionId?
|
||||
) {
|
||||
/**
|
||||
* Serves as the mutex by which to perform mutations to subscriptions.
|
||||
|
||||
Reference in New Issue
Block a user