diff --git a/packages/apple/Sources/Models/Types.swift b/packages/apple/Sources/Models/Types.swift index 496b4e0c..924839d0 100644 --- a/packages/apple/Sources/Models/Types.swift +++ b/packages/apple/Sources/Models/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/docs/src/pages/docs/types.tsx b/packages/docs/src/pages/docs/types.tsx index 3596931d..437f7019 100644 --- a/packages/docs/src/pages/docs/types.tsx +++ b/packages/docs/src/pages/docs/types.tsx @@ -370,7 +370,18 @@ function Types() { For one-time purchases. Contains:{' '} formattedPrice,{' '} priceAmountMicros (divide by 1,000,000),{' '} - priceCurrencyCode + priceCurrencyCode,{' '} + preorderDetailsAndroid (for pre-order + products, contains preorderPresaleEndTimeMillis{' '} + and preorderReleaseTimeMillis -{' '} + + Billing Library 8.1.0+ + + ) @@ -1167,6 +1178,25 @@ function Types() { Obfuscated profile ID you provided + + + isSuspendedAndroid + + + Whether the subscription is suspended due to payment + failure. Suspended subscriptions should NOT grant + entitlements - direct users to the subscription center + to resolve payment issues. ( + + Billing Library 8.1.0+ + + ) + + diff --git a/packages/docs/src/pages/docs/updates/notes.tsx b/packages/docs/src/pages/docs/updates/notes.tsx index 286a11e0..63bf9251 100644 --- a/packages/docs/src/pages/docs/updates/notes.tsx +++ b/packages/docs/src/pages/docs/updates/notes.tsx @@ -19,6 +19,93 @@ function Notes() {

📝 API & Terminology Changes

+
+

+ 📅 openiap-google v1.3.11 / openiap-gql v1.3.1 -{' '} + + Google Play Billing 8.1.0 + {' '} + Support +

+

+ Google Play Billing Library Upgrade: +

+ +

+ New Features: +

+ + + {`// Handling suspended subscriptions +val purchase = getAvailablePurchases() +if (purchase.isSuspendedAndroid == true) { + // ❌ Do NOT grant entitlements + // ✅ Direct user to subscription center + showMessage("Payment issue detected. Please update your payment method.") + deepLinkToSubscriptions() +} + +// Pre-order details +val product = fetchProducts(skus) +product.oneTimePurchaseOfferDetailsAndroid?.preorderDetailsAndroid?.let { + val releaseTime = it.preorderReleaseTimeMillis.toLong() + val presaleEndTime = it.preorderPresaleEndTimeMillis.toLong() +}`} + +

+ See:{' '} + Purchase Platform Fields + , Product Platform Fields +

+
+
Purchase.platform → Purchase.store - The{' '} platform field is deprecated. Use store{' '} - instead which returns 'apple' or 'google'. + instead which returns 'apple' or{' '} + 'google'.
  • requestPurchase props - The ios and{' '} @@ -102,7 +190,9 @@ purchase.platform // deprecated`}

    Starting from openiap v1.2.6, the{' '} - validateReceipt{' '} + + validateReceipt + {' '} API is deprecated in favor of verifyPurchase.

    @@ -177,7 +267,9 @@ purchase.platform // deprecated`}

    See:{' '} - External Purchase Guide + + External Purchase Guide +

  • @@ -226,7 +318,11 @@ purchase.platform // deprecated`}

    - See: External Purchase Guide,{' '} + See:{' '} + + External Purchase Guide + + ,{' '} User Choice Billing Event @@ -418,16 +514,24 @@ const testProduct = await fetchProducts(['your_real_product_id']) - v7.x + v8.x ✅ Current + TBD + + Latest recommended version (requires minSdk 23, Kotlin 2.2.0) + + + + v7.x + ✅ Supported August 31, 2025 - Latest recommended version + User Choice Billing support v6.x ✅ Supported August 31, 2025 - Minimum required version + Alternative Billing support v5.x @@ -448,6 +552,41 @@ const testProduct = await fetchProducts(['your_real_product_id'])

    🆕 Recent Updates

    +

    + + Google Play Billing Library v8.1 + {' '} + (November 2025) +

    +

    + Released November 6, 2025 +

    + +

    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