🆕 Recent Updates
+
+
+ Released November 6, 2025
+
+
+ -
+ Suspended Subscriptions -{' '}
+
Purchase.isSuspended() to detect payment failures
+
+ -
+ Pre-order Products -
PreorderDetails{' '}
+ for one-time purchase pre-orders
+
+ -
+ minSdk 23 - Minimum SDK increased to Android 6.0
+
+ -
+ Kotlin 2.2.0 - Requires Kotlin 2.2.0 or higher
+
+ -
+ Deprecated API -{' '}
+
setSubscriptionReplacementMode() deprecated in favor of{' '}
+ SubscriptionProductReplacementParams
+
+
+
Google Play Billing Library v7 (May 2024)
Released at Google I/O 2024
diff --git a/packages/google/build.gradle.kts b/packages/google/build.gradle.kts
index 4ded9d3c..b4f79a7f 100644
--- a/packages/google/build.gradle.kts
+++ b/packages/google/build.gradle.kts
@@ -1,8 +1,8 @@
plugins {
id("com.android.library") version "8.5.0" apply false
id("com.android.application") version "8.5.0" apply false
- id("org.jetbrains.kotlin.android") version "2.0.21" apply false
- id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
+ id("org.jetbrains.kotlin.android") version "2.2.0" apply false
+ id("org.jetbrains.kotlin.plugin.compose") version "2.2.0" apply false
id("com.vanniktech.maven.publish") version "0.29.0" apply false
}
diff --git a/packages/google/gradle.properties b/packages/google/gradle.properties
index e86ebcd1..4af10984 100644
--- a/packages/google/gradle.properties
+++ b/packages/google/gradle.properties
@@ -31,5 +31,7 @@ POM_DEVELOPER_ID=hyochan
POM_DEVELOPER_NAME=hyo.dev
# Compose versions (shown on Gradle sync)
+# Note: Since Kotlin 2.0+, Compose compiler is bundled with Kotlin
+# COMPOSE_COMPILER_VERSION should match the Kotlin version
COMPOSE_UI_VERSION=1.6.8
-COMPOSE_COMPILER_VERSION=1.5.14
+COMPOSE_COMPILER_VERSION=2.2.0
diff --git a/packages/google/openiap/build.gradle.kts b/packages/google/openiap/build.gradle.kts
index 7562ea56..aaffce27 100644
--- a/packages/google/openiap/build.gradle.kts
+++ b/packages/google/openiap/build.gradle.kts
@@ -17,7 +17,7 @@ android {
compileSdk = 34
defaultConfig {
- minSdk = 21
+ minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
@@ -86,8 +86,8 @@ dependencies {
// - Horizon flavor uses Meta Horizon Billing Compatibility Library
// Play flavor: Google Play Billing API (compile + runtime)
- add("playCompileOnly", "com.android.billingclient:billing-ktx:8.0.0")
- add("playApi", "com.android.billingclient:billing-ktx:8.0.0")
+ add("playCompileOnly", "com.android.billingclient:billing-ktx:8.1.0")
+ add("playApi", "com.android.billingclient:billing-ktx:8.1.0")
// Horizon flavor: Meta Horizon Platform SDK and Billing Compatibility Library (compile + runtime)
add("horizonCompileOnly", "com.meta.horizon.platform.ovr:android-platform-sdk:77.0.1")
@@ -112,7 +112,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
// Add Google Play Billing for tests (all flavors need it for OpenIapErrorTest)
- testImplementation("com.android.billingclient:billing-ktx:8.0.0")
+ testImplementation("com.android.billingclient:billing-ktx:8.1.0")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
diff --git a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
index 6c5b3e15..0de9a3f4 100644
--- a/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
+++ b/packages/google/openiap/src/main/java/dev/hyo/openiap/Types.kt
@@ -846,6 +846,39 @@ public data class FetchProductsResultProducts(val value: List?) : Fetch
public data class FetchProductsResultSubscriptions(val value: List?) : FetchProductsResult
+/**
+ * Pre-order details for one-time purchase products (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+public data class PreorderDetailsAndroid(
+ /**
+ * Pre-order presale end time in milliseconds since epoch.
+ * This is when the presale period ends and the product will be released.
+ */
+ val preorderPresaleEndTimeMillis: String,
+ /**
+ * Pre-order release time in milliseconds since epoch.
+ * This is when the product will be available to users who pre-ordered.
+ */
+ val preorderReleaseTimeMillis: String
+) {
+
+ companion object {
+ fun fromJson(json: Map): PreorderDetailsAndroid {
+ return PreorderDetailsAndroid(
+ preorderPresaleEndTimeMillis = json["preorderPresaleEndTimeMillis"] as String,
+ preorderReleaseTimeMillis = json["preorderReleaseTimeMillis"] as String,
+ )
+ }
+ }
+
+ fun toJson(): Map = mapOf(
+ "__typename" to "PreorderDetailsAndroid",
+ "preorderPresaleEndTimeMillis" to preorderPresaleEndTimeMillis,
+ "preorderReleaseTimeMillis" to preorderReleaseTimeMillis,
+ )
+}
+
public data class PricingPhaseAndroid(
val billingCycleCount: Int,
val billingPeriod: String,
@@ -953,6 +986,11 @@ public data class ProductAndroid(
public data class ProductAndroidOneTimePurchaseOfferDetail(
val formattedPrice: String,
+ /**
+ * Pre-order details for products available for pre-order (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ val preorderDetailsAndroid: PreorderDetailsAndroid? = null,
val priceAmountMicros: String,
val priceCurrencyCode: String
) {
@@ -961,6 +999,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail(
fun fromJson(json: Map): ProductAndroidOneTimePurchaseOfferDetail {
return ProductAndroidOneTimePurchaseOfferDetail(
formattedPrice = json["formattedPrice"] as String,
+ preorderDetailsAndroid = (json["preorderDetailsAndroid"] as Map?)?.let { PreorderDetailsAndroid.fromJson(it) },
priceAmountMicros = json["priceAmountMicros"] as String,
priceCurrencyCode = json["priceCurrencyCode"] as String,
)
@@ -970,6 +1009,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail(
fun toJson(): Map = mapOf(
"__typename" to "ProductAndroidOneTimePurchaseOfferDetail",
"formattedPrice" to formattedPrice,
+ "preorderDetailsAndroid" to preorderDetailsAndroid?.toJson(),
"priceAmountMicros" to priceAmountMicros,
"priceCurrencyCode" to priceCurrencyCode,
)
@@ -1212,6 +1252,14 @@ public data class PurchaseAndroid(
override val ids: List? = null,
val isAcknowledgedAndroid: Boolean? = null,
override val isAutoRenewing: Boolean,
+ /**
+ * Whether the subscription is suspended (Android)
+ * A suspended subscription means the user's payment method failed and they need to fix it.
+ * Users should be directed to the subscription center to resolve the issue.
+ * Do NOT grant entitlements for suspended subscriptions.
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ val isSuspendedAndroid: Boolean? = null,
val obfuscatedAccountIdAndroid: String? = null,
val obfuscatedProfileIdAndroid: String? = null,
val packageNameAndroid: String? = null,
@@ -1240,6 +1288,7 @@ public data class PurchaseAndroid(
ids = (json["ids"] as List<*>?)?.map { it as String },
isAcknowledgedAndroid = json["isAcknowledgedAndroid"] as Boolean?,
isAutoRenewing = json["isAutoRenewing"] as Boolean,
+ isSuspendedAndroid = json["isSuspendedAndroid"] as Boolean?,
obfuscatedAccountIdAndroid = json["obfuscatedAccountIdAndroid"] as String?,
obfuscatedProfileIdAndroid = json["obfuscatedProfileIdAndroid"] as String?,
packageNameAndroid = json["packageNameAndroid"] as String?,
@@ -1266,6 +1315,7 @@ public data class PurchaseAndroid(
"ids" to ids?.map { it },
"isAcknowledgedAndroid" to isAcknowledgedAndroid,
"isAutoRenewing" to isAutoRenewing,
+ "isSuspendedAndroid" to isSuspendedAndroid,
"obfuscatedAccountIdAndroid" to obfuscatedAccountIdAndroid,
"obfuscatedProfileIdAndroid" to obfuscatedProfileIdAndroid,
"packageNameAndroid" to packageNameAndroid,
diff --git a/packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt b/packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
index 1744950d..4a5f04d7 100644
--- a/packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
+++ b/packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
@@ -433,7 +433,7 @@ class OpenIapModule(
"• Wait for Google approval\n" +
"• Test with license tester accounts\n\n" +
"Current mode: ALTERNATIVE_ONLY\n" +
- "Library: Billing 8.0.0"
+ "Library: Billing 8.1.0"
)
purchaseErrorListeners.forEach { listener -> runCatching { listener.onPurchaseError(err) } }
@@ -632,7 +632,11 @@ class OpenIapModule(
.setOldPurchaseToken(androidArgs.purchaseTokenAndroid)
// Set replacement mode - this is critical for upgrades
+ // Note: setSubscriptionReplacementMode() is deprecated in Billing 8.1.0
+ // in favor of SubscriptionProductReplacementParams for per-product control.
+ // However, for single-product upgrades, the legacy API still works.
val replacementMode = androidArgs.replacementModeAndroid ?: 5 // Default to CHARGE_FULL_PRICE
+ @Suppress("DEPRECATION")
updateParamsBuilder.setSubscriptionReplacementMode(replacementMode)
OpenIapLog.d(" - Final replacement mode: $replacementMode", TAG)
@@ -1084,7 +1088,7 @@ class OpenIapModule(
} catch (e: NoSuchMethodException) {
OpenIapLog.e("✗ enableAlternativeBillingOnly() method not found", e, TAG)
OpenIapLog.e("This method requires Billing Library 6.2+", tag = TAG)
- OpenIapLog.e("Current library version: 8.0.0", tag = TAG)
+ OpenIapLog.e("Current library version: 8.1.0", tag = TAG)
OpenIapLog.e("Alternative billing will NOT work - standard Google Play billing will be used", tag = TAG)
} catch (e: Exception) {
OpenIapLog.e("✗ Failed to enable alternative billing only: ${e.javaClass.simpleName}: ${e.message}", e, TAG)
diff --git a/packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt b/packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
index 3d68e18d..07cc7ea0 100644
--- a/packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
+++ b/packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
@@ -7,6 +7,7 @@ import dev.hyo.openiap.PricingPhaseAndroid
import dev.hyo.openiap.PricingPhasesAndroid
import dev.hyo.openiap.Product
import dev.hyo.openiap.ProductAndroid
+import dev.hyo.openiap.PreorderDetailsAndroid
import dev.hyo.openiap.ProductAndroidOneTimePurchaseOfferDetail
import dev.hyo.openiap.ProductSubscriptionAndroid
import dev.hyo.openiap.ProductSubscriptionAndroidOfferDetails
@@ -26,6 +27,14 @@ internal object BillingConverters {
val currency = offer?.priceCurrencyCode.orEmpty()
val priceAmountMicros = offer?.priceAmountMicros ?: 0L
+ // Extract preorder details (available in Billing Library 8.1.0+)
+ val preorderDetails = offer?.preorderDetails?.let { preorder ->
+ PreorderDetailsAndroid(
+ preorderPresaleEndTimeMillis = preorder.preorderPresaleEndTimeMillis.toString(),
+ preorderReleaseTimeMillis = preorder.preorderReleaseTimeMillis.toString()
+ )
+ }
+
return ProductAndroid(
currency = currency,
debugDescription = description,
@@ -38,7 +47,8 @@ internal object BillingConverters {
ProductAndroidOneTimePurchaseOfferDetail(
formattedPrice = it.formattedPrice,
priceAmountMicros = it.priceAmountMicros.toString(),
- priceCurrencyCode = it.priceCurrencyCode
+ priceCurrencyCode = it.priceCurrencyCode,
+ preorderDetailsAndroid = preorderDetails
)
},
platform = IapPlatform.Android,
@@ -100,6 +110,19 @@ internal object BillingConverters {
fun BillingPurchase.toPurchase(productType: String, basePlanId: String? = null): PurchaseAndroid {
val state = PurchaseState.fromBillingState(purchaseState)
+
+ // Check if subscription is suspended (available in Billing Library 8.1.0+)
+ // Suspended subscriptions should NOT grant entitlements - direct users to subscription center
+ val isSuspended = if (productType == BillingClient.ProductType.SUBS) {
+ runCatching {
+ // Use reflection to maintain backward compatibility
+ val method = this::class.java.getMethod("isSuspended")
+ method.invoke(this) as? Boolean
+ }.getOrNull()
+ } else {
+ null
+ }
+
return PurchaseAndroid(
autoRenewingAndroid = isAutoRenewing,
currentPlanId = basePlanId,
@@ -109,6 +132,7 @@ internal object BillingConverters {
ids = products,
isAcknowledgedAndroid = isAcknowledged,
isAutoRenewing = isAutoRenewing,
+ isSuspendedAndroid = isSuspended,
obfuscatedAccountIdAndroid = accountIdentifiers?.obfuscatedAccountId,
obfuscatedProfileIdAndroid = accountIdentifiers?.obfuscatedProfileId,
packageNameAndroid = packageName,
diff --git a/packages/gql/src/generated/Types.kt b/packages/gql/src/generated/Types.kt
index 7557f855..3d811ee3 100644
--- a/packages/gql/src/generated/Types.kt
+++ b/packages/gql/src/generated/Types.kt
@@ -913,6 +913,39 @@ public data class FetchProductsResultProducts(val value: List?) : Fetch
public data class FetchProductsResultSubscriptions(val value: List?) : FetchProductsResult
+/**
+ * Pre-order details for one-time purchase products (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+public data class PreorderDetailsAndroid(
+ /**
+ * Pre-order presale end time in milliseconds since epoch.
+ * This is when the presale period ends and the product will be released.
+ */
+ val preorderPresaleEndTimeMillis: String,
+ /**
+ * Pre-order release time in milliseconds since epoch.
+ * This is when the product will be available to users who pre-ordered.
+ */
+ val preorderReleaseTimeMillis: String
+) {
+
+ companion object {
+ fun fromJson(json: Map): PreorderDetailsAndroid {
+ return PreorderDetailsAndroid(
+ preorderPresaleEndTimeMillis = json["preorderPresaleEndTimeMillis"] as String,
+ preorderReleaseTimeMillis = json["preorderReleaseTimeMillis"] as String,
+ )
+ }
+ }
+
+ fun toJson(): Map = mapOf(
+ "__typename" to "PreorderDetailsAndroid",
+ "preorderPresaleEndTimeMillis" to preorderPresaleEndTimeMillis,
+ "preorderReleaseTimeMillis" to preorderReleaseTimeMillis,
+ )
+}
+
public data class PricingPhaseAndroid(
val billingCycleCount: Int,
val billingPeriod: String,
@@ -1020,6 +1053,11 @@ public data class ProductAndroid(
public data class ProductAndroidOneTimePurchaseOfferDetail(
val formattedPrice: String,
+ /**
+ * Pre-order details for products available for pre-order (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ val preorderDetailsAndroid: PreorderDetailsAndroid? = null,
val priceAmountMicros: String,
val priceCurrencyCode: String
) {
@@ -1028,6 +1066,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail(
fun fromJson(json: Map): ProductAndroidOneTimePurchaseOfferDetail {
return ProductAndroidOneTimePurchaseOfferDetail(
formattedPrice = json["formattedPrice"] as String,
+ preorderDetailsAndroid = (json["preorderDetailsAndroid"] as Map?)?.let { PreorderDetailsAndroid.fromJson(it) },
priceAmountMicros = json["priceAmountMicros"] as String,
priceCurrencyCode = json["priceCurrencyCode"] as String,
)
@@ -1037,6 +1076,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail(
fun toJson(): Map = mapOf(
"__typename" to "ProductAndroidOneTimePurchaseOfferDetail",
"formattedPrice" to formattedPrice,
+ "preorderDetailsAndroid" to preorderDetailsAndroid?.toJson(),
"priceAmountMicros" to priceAmountMicros,
"priceCurrencyCode" to priceCurrencyCode,
)
@@ -1279,6 +1319,14 @@ public data class PurchaseAndroid(
override val ids: List? = null,
val isAcknowledgedAndroid: Boolean? = null,
override val isAutoRenewing: Boolean,
+ /**
+ * Whether the subscription is suspended (Android)
+ * A suspended subscription means the user's payment method failed and they need to fix it.
+ * Users should be directed to the subscription center to resolve the issue.
+ * Do NOT grant entitlements for suspended subscriptions.
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ val isSuspendedAndroid: Boolean? = null,
val obfuscatedAccountIdAndroid: String? = null,
val obfuscatedProfileIdAndroid: String? = null,
val packageNameAndroid: String? = null,
@@ -1307,6 +1355,7 @@ public data class PurchaseAndroid(
ids = (json["ids"] as List<*>?)?.map { it as String },
isAcknowledgedAndroid = json["isAcknowledgedAndroid"] as Boolean?,
isAutoRenewing = json["isAutoRenewing"] as Boolean,
+ isSuspendedAndroid = json["isSuspendedAndroid"] as Boolean?,
obfuscatedAccountIdAndroid = json["obfuscatedAccountIdAndroid"] as String?,
obfuscatedProfileIdAndroid = json["obfuscatedProfileIdAndroid"] as String?,
packageNameAndroid = json["packageNameAndroid"] as String?,
@@ -1333,6 +1382,7 @@ public data class PurchaseAndroid(
"ids" to ids?.map { it },
"isAcknowledgedAndroid" to isAcknowledgedAndroid,
"isAutoRenewing" to isAutoRenewing,
+ "isSuspendedAndroid" to isSuspendedAndroid,
"obfuscatedAccountIdAndroid" to obfuscatedAccountIdAndroid,
"obfuscatedProfileIdAndroid" to obfuscatedProfileIdAndroid,
"packageNameAndroid" to packageNameAndroid,
diff --git a/packages/gql/src/generated/Types.swift b/packages/gql/src/generated/Types.swift
index 496b4e0c..924839d0 100644
--- a/packages/gql/src/generated/Types.swift
+++ b/packages/gql/src/generated/Types.swift
@@ -376,6 +376,17 @@ public enum FetchProductsResult {
case subscriptions([ProductSubscription]?)
}
+/// Pre-order details for one-time purchase products (Android)
+/// Available in Google Play Billing Library 8.1.0+
+public struct PreorderDetailsAndroid: Codable {
+ /// Pre-order presale end time in milliseconds since epoch.
+ /// This is when the presale period ends and the product will be released.
+ public var preorderPresaleEndTimeMillis: String
+ /// Pre-order release time in milliseconds since epoch.
+ /// This is when the product will be available to users who pre-ordered.
+ public var preorderReleaseTimeMillis: String
+}
+
public struct PricingPhaseAndroid: Codable {
public var billingCycleCount: Int
public var billingPeriod: String
@@ -407,6 +418,9 @@ public struct ProductAndroid: Codable, ProductCommon {
public struct ProductAndroidOneTimePurchaseOfferDetail: Codable {
public var formattedPrice: String
+ /// Pre-order details for products available for pre-order (Android)
+ /// Available in Google Play Billing Library 8.1.0+
+ public var preorderDetailsAndroid: PreorderDetailsAndroid?
public var priceAmountMicros: String
public var priceCurrencyCode: String
}
@@ -488,6 +502,12 @@ public struct PurchaseAndroid: Codable, PurchaseCommon {
public var ids: [String]?
public var isAcknowledgedAndroid: Bool?
public var isAutoRenewing: Bool
+ /// Whether the subscription is suspended (Android)
+ /// A suspended subscription means the user's payment method failed and they need to fix it.
+ /// Users should be directed to the subscription center to resolve the issue.
+ /// Do NOT grant entitlements for suspended subscriptions.
+ /// Available in Google Play Billing Library 8.1.0+
+ public var isSuspendedAndroid: Bool?
public var obfuscatedAccountIdAndroid: String?
public var obfuscatedProfileIdAndroid: String?
public var packageNameAndroid: String?
diff --git a/packages/gql/src/generated/types.dart b/packages/gql/src/generated/types.dart
index ab530cde..390ebf41 100644
--- a/packages/gql/src/generated/types.dart
+++ b/packages/gql/src/generated/types.dart
@@ -1069,6 +1069,41 @@ class FetchProductsResultSubscriptions extends FetchProductsResult {
final List? value;
}
+/// Pre-order details for one-time purchase products (Android)
+/// Available in Google Play Billing Library 8.1.0+
+class PreorderDetailsAndroid {
+ const PreorderDetailsAndroid({
+ /// Pre-order presale end time in milliseconds since epoch.
+ /// This is when the presale period ends and the product will be released.
+ required this.preorderPresaleEndTimeMillis,
+ /// Pre-order release time in milliseconds since epoch.
+ /// This is when the product will be available to users who pre-ordered.
+ required this.preorderReleaseTimeMillis,
+ });
+
+ /// Pre-order presale end time in milliseconds since epoch.
+ /// This is when the presale period ends and the product will be released.
+ final String preorderPresaleEndTimeMillis;
+ /// Pre-order release time in milliseconds since epoch.
+ /// This is when the product will be available to users who pre-ordered.
+ final String preorderReleaseTimeMillis;
+
+ factory PreorderDetailsAndroid.fromJson(Map json) {
+ return PreorderDetailsAndroid(
+ preorderPresaleEndTimeMillis: json['preorderPresaleEndTimeMillis'] as String,
+ preorderReleaseTimeMillis: json['preorderReleaseTimeMillis'] as String,
+ );
+ }
+
+ Map toJson() {
+ return {
+ '__typename': 'PreorderDetailsAndroid',
+ 'preorderPresaleEndTimeMillis': preorderPresaleEndTimeMillis,
+ 'preorderReleaseTimeMillis': preorderReleaseTimeMillis,
+ };
+ }
+}
+
class PricingPhaseAndroid {
const PricingPhaseAndroid({
required this.billingCycleCount,
@@ -1204,17 +1239,24 @@ class ProductAndroid extends Product implements ProductCommon {
class ProductAndroidOneTimePurchaseOfferDetail {
const ProductAndroidOneTimePurchaseOfferDetail({
required this.formattedPrice,
+ /// Pre-order details for products available for pre-order (Android)
+ /// Available in Google Play Billing Library 8.1.0+
+ this.preorderDetailsAndroid,
required this.priceAmountMicros,
required this.priceCurrencyCode,
});
final String formattedPrice;
+ /// Pre-order details for products available for pre-order (Android)
+ /// Available in Google Play Billing Library 8.1.0+
+ final PreorderDetailsAndroid? preorderDetailsAndroid;
final String priceAmountMicros;
final String priceCurrencyCode;
factory ProductAndroidOneTimePurchaseOfferDetail.fromJson(Map json) {
return ProductAndroidOneTimePurchaseOfferDetail(
formattedPrice: json['formattedPrice'] as String,
+ preorderDetailsAndroid: json['preorderDetailsAndroid'] != null ? PreorderDetailsAndroid.fromJson(json['preorderDetailsAndroid'] as Map) : null,
priceAmountMicros: json['priceAmountMicros'] as String,
priceCurrencyCode: json['priceCurrencyCode'] as String,
);
@@ -1224,6 +1266,7 @@ class ProductAndroidOneTimePurchaseOfferDetail {
return {
'__typename': 'ProductAndroidOneTimePurchaseOfferDetail',
'formattedPrice': formattedPrice,
+ 'preorderDetailsAndroid': preorderDetailsAndroid?.toJson(),
'priceAmountMicros': priceAmountMicros,
'priceCurrencyCode': priceCurrencyCode,
};
@@ -1535,6 +1578,12 @@ class PurchaseAndroid extends Purchase implements PurchaseCommon {
this.ids,
this.isAcknowledgedAndroid,
required this.isAutoRenewing,
+ /// Whether the subscription is suspended (Android)
+ /// A suspended subscription means the user's payment method failed and they need to fix it.
+ /// Users should be directed to the subscription center to resolve the issue.
+ /// Do NOT grant entitlements for suspended subscriptions.
+ /// Available in Google Play Billing Library 8.1.0+
+ this.isSuspendedAndroid,
this.obfuscatedAccountIdAndroid,
this.obfuscatedProfileIdAndroid,
this.packageNameAndroid,
@@ -1559,6 +1608,12 @@ class PurchaseAndroid extends Purchase implements PurchaseCommon {
final List? ids;
final bool? isAcknowledgedAndroid;
final bool isAutoRenewing;
+ /// Whether the subscription is suspended (Android)
+ /// A suspended subscription means the user's payment method failed and they need to fix it.
+ /// Users should be directed to the subscription center to resolve the issue.
+ /// Do NOT grant entitlements for suspended subscriptions.
+ /// Available in Google Play Billing Library 8.1.0+
+ final bool? isSuspendedAndroid;
final String? obfuscatedAccountIdAndroid;
final String? obfuscatedProfileIdAndroid;
final String? packageNameAndroid;
@@ -1584,6 +1639,7 @@ class PurchaseAndroid extends Purchase implements PurchaseCommon {
ids: (json['ids'] as List?) == null ? null : (json['ids'] as List?)!.map((e) => e as String).toList(),
isAcknowledgedAndroid: json['isAcknowledgedAndroid'] as bool?,
isAutoRenewing: json['isAutoRenewing'] as bool,
+ isSuspendedAndroid: json['isSuspendedAndroid'] as bool?,
obfuscatedAccountIdAndroid: json['obfuscatedAccountIdAndroid'] as String?,
obfuscatedProfileIdAndroid: json['obfuscatedProfileIdAndroid'] as String?,
packageNameAndroid: json['packageNameAndroid'] as String?,
@@ -1612,6 +1668,7 @@ class PurchaseAndroid extends Purchase implements PurchaseCommon {
'ids': ids == null ? null : ids!.map((e) => e).toList(),
'isAcknowledgedAndroid': isAcknowledgedAndroid,
'isAutoRenewing': isAutoRenewing,
+ 'isSuspendedAndroid': isSuspendedAndroid,
'obfuscatedAccountIdAndroid': obfuscatedAccountIdAndroid,
'obfuscatedProfileIdAndroid': obfuscatedProfileIdAndroid,
'packageNameAndroid': packageNameAndroid,
diff --git a/packages/gql/src/generated/types.ts b/packages/gql/src/generated/types.ts
index 46b44743..8992b97d 100644
--- a/packages/gql/src/generated/types.ts
+++ b/packages/gql/src/generated/types.ts
@@ -310,6 +310,23 @@ export type MutationVerifyPurchaseWithProviderArgs = VerifyPurchaseWithProviderP
export type PaymentModeIOS = 'empty' | 'free-trial' | 'pay-as-you-go' | 'pay-up-front';
+/**
+ * Pre-order details for one-time purchase products (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+export interface PreorderDetailsAndroid {
+ /**
+ * Pre-order presale end time in milliseconds since epoch.
+ * This is when the presale period ends and the product will be released.
+ */
+ preorderPresaleEndTimeMillis: string;
+ /**
+ * Pre-order release time in milliseconds since epoch.
+ * This is when the product will be available to users who pre-ordered.
+ */
+ preorderReleaseTimeMillis: string;
+}
+
export interface PricingPhaseAndroid {
billingCycleCount: number;
billingPeriod: string;
@@ -343,6 +360,11 @@ export interface ProductAndroid extends ProductCommon {
export interface ProductAndroidOneTimePurchaseOfferDetail {
formattedPrice: string;
+ /**
+ * Pre-order details for products available for pre-order (Android)
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ preorderDetailsAndroid?: (PreorderDetailsAndroid | null);
priceAmountMicros: string;
priceCurrencyCode: string;
}
@@ -454,6 +476,14 @@ export interface PurchaseAndroid extends PurchaseCommon {
ids?: (string[] | null);
isAcknowledgedAndroid?: (boolean | null);
isAutoRenewing: boolean;
+ /**
+ * Whether the subscription is suspended (Android)
+ * A suspended subscription means the user's payment method failed and they need to fix it.
+ * Users should be directed to the subscription center to resolve the issue.
+ * Do NOT grant entitlements for suspended subscriptions.
+ * Available in Google Play Billing Library 8.1.0+
+ */
+ isSuspendedAndroid?: (boolean | null);
obfuscatedAccountIdAndroid?: (string | null);
obfuscatedProfileIdAndroid?: (string | null);
packageNameAndroid?: (string | null);
diff --git a/packages/gql/src/type-android.graphql b/packages/gql/src/type-android.graphql
index 5e1047c5..c81074d3 100644
--- a/packages/gql/src/type-android.graphql
+++ b/packages/gql/src/type-android.graphql
@@ -14,10 +14,32 @@ type PricingPhaseAndroid {
recurrenceMode: Int!
}
+"""
+Pre-order details for one-time purchase products (Android)
+Available in Google Play Billing Library 8.1.0+
+"""
+type PreorderDetailsAndroid {
+ """
+ Pre-order presale end time in milliseconds since epoch.
+ This is when the presale period ends and the product will be released.
+ """
+ preorderPresaleEndTimeMillis: String!
+ """
+ Pre-order release time in milliseconds since epoch.
+ This is when the product will be available to users who pre-ordered.
+ """
+ preorderReleaseTimeMillis: String!
+}
+
type ProductAndroidOneTimePurchaseOfferDetail {
priceCurrencyCode: String!
formattedPrice: String!
priceAmountMicros: String!
+ """
+ Pre-order details for products available for pre-order (Android)
+ Available in Google Play Billing Library 8.1.0+
+ """
+ preorderDetailsAndroid: PreorderDetailsAndroid
}
type ProductSubscriptionAndroidOfferDetails {
@@ -93,6 +115,14 @@ type PurchaseAndroid implements PurchaseCommon {
developerPayloadAndroid: String
obfuscatedAccountIdAndroid: String
obfuscatedProfileIdAndroid: String
+ """
+ Whether the subscription is suspended (Android)
+ A suspended subscription means the user's payment method failed and they need to fix it.
+ Users should be directed to the subscription center to resolve the issue.
+ Do NOT grant entitlements for suspended subscriptions.
+ Available in Google Play Billing Library 8.1.0+
+ """
+ isSuspendedAndroid: Boolean
}
# Android inputs