From b3d8e0c7901c6cc947b45838e6039944ade01903 Mon Sep 17 00:00:00 2001 From: Hyo Date: Wed, 11 Feb 2026 07:09:28 +0900 Subject: [PATCH] feat: sync with openiap v1.3.17 - Update openiap-versions.json (gql: 1.3.17, apple: 1.3.14, google: 1.3.28) - Add InstallmentPlanDetailsAndroid data class (Billing Library 7.0+) - Add PendingPurchaseUpdateAndroid data class (Billing Library 5.0+) - Add purchaseOptionIdAndroid field to DiscountOffer (Billing Library 7.0+) - Update llms.txt with new type documentation - Add release blog post Co-Authored-By: Claude Opus 4.5 --- docs/blog/2026-02-11-release-1.3.7.md | 72 +++++++++++ docs/static/llms.txt | 15 ++- .../io/github/hyochan/kmpiap/openiap/Types.kt | 118 +++++++++++++++++- openiap-versions.json | 4 +- 4 files changed, 204 insertions(+), 5 deletions(-) create mode 100644 docs/blog/2026-02-11-release-1.3.7.md diff --git a/docs/blog/2026-02-11-release-1.3.7.md b/docs/blog/2026-02-11-release-1.3.7.md new file mode 100644 index 00000000..87fd14c8 --- /dev/null +++ b/docs/blog/2026-02-11-release-1.3.7.md @@ -0,0 +1,72 @@ +--- +slug: 1.3.7 +title: 1.3.7 - OpenIAP 1.3.17 Sync +authors: [hyochan] +tags: [release, openiap, android, billing-library] +date: 2026-02-11 +--- + +# 1.3.7 Release Notes + +This release syncs with [OpenIAP v1.3.17](https://www.openiap.dev/docs/updates/notes#gql-1317), adding new types for Google Play Billing Library 5.0+ and 7.0+ features. + + + +## New Types + +### InstallmentPlanDetailsAndroid (Billing Library 7.0+) + +Subscription installment plan details for plans that allow users to pay in installments. + +```kotlin +data class InstallmentPlanDetailsAndroid( + /** Committed payments count after signup (e.g., 12 monthly payments) */ + val commitmentPaymentsCount: Int, + /** Subsequent commitment payments when plan renews (0 if reverts to normal) */ + val subsequentCommitmentPaymentsCount: Int +) +``` + +This is available on `ProductSubscriptionAndroidOfferDetails.installmentPlanDetails`. + +### PendingPurchaseUpdateAndroid (Billing Library 5.0+) + +Details about pending subscription upgrades/downgrades. + +```kotlin +data class PendingPurchaseUpdateAndroid( + /** Product IDs for the pending purchase update */ + val products: List, + /** Purchase token for the pending transaction */ + val purchaseToken: String +) +``` + +This is available on `PurchaseAndroid.pendingPurchaseUpdateAndroid`. + +### purchaseOptionIdAndroid (Billing Library 7.0+) + +New field on `DiscountOffer` and `ProductAndroidOneTimePurchaseOfferDetail` to identify which purchase option the user selected. + +```kotlin +// Available on DiscountOffer +discountOffer.purchaseOptionIdAndroid // String? +``` + +## OpenIAP Versions + +| Package | Version | +|---------|---------| +| openiap-gql | 1.3.17 | +| openiap-google | 1.3.28 | +| openiap-apple | 1.3.14 | + +## Installation + +```kotlin +implementation("io.github.hyochan:kmp-iap:1.3.7") +``` + +## Related + +- [OpenIAP Release Notes](https://www.openiap.dev/docs/updates/notes#gql-1317) diff --git a/docs/static/llms.txt b/docs/static/llms.txt index 53c4b300..50db3d1e 100644 --- a/docs/static/llms.txt +++ b/docs/static/llms.txt @@ -253,7 +253,20 @@ data class DiscountOffer( // Android-specific val offerTokenAndroid: String?, val percentageDiscountAndroid: Int?, - val formattedDiscountAmountAndroid: String? + val formattedDiscountAmountAndroid: String?, + val purchaseOptionIdAndroid: String? // v1.3.7+, Billing Library 7.0+ +) + +// Installment plan details (v1.3.7+, Billing Library 7.0+) +data class InstallmentPlanDetailsAndroid( + val commitmentPaymentsCount: Int, // Initial commitment (e.g., 12 months) + val subsequentCommitmentPaymentsCount: Int // Renewal commitment (0 if reverts to normal) +) + +// Pending subscription update (v1.3.7+, Billing Library 5.0+) +data class PendingPurchaseUpdateAndroid( + val products: List, // New products being switched to + val purchaseToken: String // Pending transaction token ) ``` diff --git a/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt b/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt index db7531aa..8a45bfab 100644 --- a/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt +++ b/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/openiap/Types.kt @@ -1486,6 +1486,12 @@ public data class DiscountOffer( * Numeric price value */ val price: Double, + /** + * [Android] Purchase option ID for this offer. + * Used to identify which purchase option the user selected. + * Available in Google Play Billing Library 7.0+ + */ + val purchaseOptionIdAndroid: String? = null, /** * [Android] Rental details if this is a rental offer. */ @@ -1516,6 +1522,7 @@ public data class DiscountOffer( percentageDiscountAndroid = (json["percentageDiscountAndroid"] as? Number)?.toInt(), preorderDetailsAndroid = (json["preorderDetailsAndroid"] as? Map)?.let { PreorderDetailsAndroid.fromJson(it) }, price = (json["price"] as? Number)?.toDouble() ?: 0.0, + purchaseOptionIdAndroid = json["purchaseOptionIdAndroid"] as? String, rentalDetailsAndroid = (json["rentalDetailsAndroid"] as? Map)?.let { RentalDetailsAndroid.fromJson(it) }, type = (json["type"] as? String)?.let { DiscountOfferType.fromJson(it) } ?: DiscountOfferType.Introductory, validTimeWindowAndroid = (json["validTimeWindowAndroid"] as? Map)?.let { ValidTimeWindowAndroid.fromJson(it) }, @@ -1537,6 +1544,7 @@ public data class DiscountOffer( "percentageDiscountAndroid" to percentageDiscountAndroid, "preorderDetailsAndroid" to preorderDetailsAndroid?.toJson(), "price" to price, + "purchaseOptionIdAndroid" to purchaseOptionIdAndroid, "rentalDetailsAndroid" to rentalDetailsAndroid?.toJson(), "type" to type.toJson(), "validTimeWindowAndroid" to validTimeWindowAndroid?.toJson(), @@ -1731,7 +1739,7 @@ public data class ExternalPurchaseCustomLinkTokenResultIOS( } /** - * Result of presenting an external purchase link (iOS 17.4+) + * Result of presenting an external purchase link */ public data class ExternalPurchaseLinkResultIOS( /** @@ -1807,6 +1815,43 @@ public data class FetchProductsResultProducts(val value: List?) : Fetch public data class FetchProductsResultSubscriptions(val value: List?) : FetchProductsResult +/** + * Installment plan details for subscription offers (Android) + * Contains information about the installment plan commitment. + * Available in Google Play Billing Library 7.0+ + */ +public data class InstallmentPlanDetailsAndroid( + /** + * Committed payments count after a user signs up for this subscription plan. + * For example, for a monthly subscription with commitmentPaymentsCount of 12, + * users will be charged monthly for 12 months after signup. + */ + val commitmentPaymentsCount: Int, + /** + * Subsequent committed payments count after the subscription plan renews. + * For example, for a monthly subscription with subsequentCommitmentPaymentsCount of 12, + * users will be committed to another 12 monthly payments when the plan renews. + * Returns 0 if the installment plan has no subsequent commitment (reverts to normal plan). + */ + val subsequentCommitmentPaymentsCount: Int +) { + + companion object { + fun fromJson(json: Map): InstallmentPlanDetailsAndroid { + return InstallmentPlanDetailsAndroid( + commitmentPaymentsCount = (json["commitmentPaymentsCount"] as? Number)?.toInt() ?: 0, + subsequentCommitmentPaymentsCount = (json["subsequentCommitmentPaymentsCount"] as? Number)?.toInt() ?: 0, + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "InstallmentPlanDetailsAndroid", + "commitmentPaymentsCount" to commitmentPaymentsCount, + "subsequentCommitmentPaymentsCount" to subsequentCommitmentPaymentsCount, + ) +} + /** * Limited quantity information for one-time purchase offers (Android) * Available in Google Play Billing Library 7.0+ @@ -1838,6 +1883,42 @@ public data class LimitedQuantityInfoAndroid( ) } +/** + * Pending purchase update for subscription upgrades/downgrades (Android) + * When a user initiates a subscription change (upgrade/downgrade), the new purchase + * may be pending until the current billing period ends. This type contains the + * details of the pending change. + * Available in Google Play Billing Library 5.0+ + */ +public data class PendingPurchaseUpdateAndroid( + /** + * Product IDs for the pending purchase update. + * These are the new products the user is switching to. + */ + val products: List, + /** + * Purchase token for the pending transaction. + * Use this token to track or manage the pending purchase update. + */ + val purchaseToken: String +) { + + companion object { + fun fromJson(json: Map): PendingPurchaseUpdateAndroid { + return PendingPurchaseUpdateAndroid( + products = (json["products"] as? List<*>)?.mapNotNull { it as? String } ?: emptyList(), + purchaseToken = json["purchaseToken"] as? String ?: "", + ) + } + } + + fun toJson(): Map = mapOf( + "__typename" to "PendingPurchaseUpdateAndroid", + "products" to products, + "purchaseToken" to purchaseToken, + ) +} + /** * Pre-order details for one-time purchase products (Android) * Available in Google Play Billing Library 8.1.0+ @@ -2051,6 +2132,12 @@ public data class ProductAndroidOneTimePurchaseOfferDetail( val preorderDetailsAndroid: PreorderDetailsAndroid? = null, val priceAmountMicros: String, val priceCurrencyCode: String, + /** + * Purchase option ID for this offer (Android) + * Used to identify which purchase option the user selected. + * Available in Google Play Billing Library 7.0+ + */ + val purchaseOptionId: String? = null, /** * Rental details for rental offers */ @@ -2074,6 +2161,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail( preorderDetailsAndroid = (json["preorderDetailsAndroid"] as? Map)?.let { PreorderDetailsAndroid.fromJson(it) }, priceAmountMicros = json["priceAmountMicros"] as? String ?: "", priceCurrencyCode = json["priceCurrencyCode"] as? String ?: "", + purchaseOptionId = json["purchaseOptionId"] as? String, rentalDetailsAndroid = (json["rentalDetailsAndroid"] as? Map)?.let { RentalDetailsAndroid.fromJson(it) }, validTimeWindow = (json["validTimeWindow"] as? Map)?.let { ValidTimeWindowAndroid.fromJson(it) }, ) @@ -2092,6 +2180,7 @@ public data class ProductAndroidOneTimePurchaseOfferDetail( "preorderDetailsAndroid" to preorderDetailsAndroid?.toJson(), "priceAmountMicros" to priceAmountMicros, "priceCurrencyCode" to priceCurrencyCode, + "purchaseOptionId" to purchaseOptionId, "rentalDetailsAndroid" to rentalDetailsAndroid?.toJson(), "validTimeWindow" to validTimeWindow?.toJson(), ) @@ -2264,6 +2353,12 @@ public data class ProductSubscriptionAndroid( */ public data class ProductSubscriptionAndroidOfferDetails( val basePlanId: String, + /** + * Installment plan details for this subscription offer. + * Only set for installment subscription plans; null for non-installment plans. + * Available in Google Play Billing Library 7.0+ + */ + val installmentPlanDetails: InstallmentPlanDetailsAndroid? = null, val offerId: String? = null, val offerTags: List, val offerToken: String, @@ -2274,6 +2369,7 @@ public data class ProductSubscriptionAndroidOfferDetails( fun fromJson(json: Map): ProductSubscriptionAndroidOfferDetails { return ProductSubscriptionAndroidOfferDetails( basePlanId = json["basePlanId"] as? String ?: "", + installmentPlanDetails = (json["installmentPlanDetails"] as? Map)?.let { InstallmentPlanDetailsAndroid.fromJson(it) }, offerId = json["offerId"] as? String, offerTags = (json["offerTags"] as? List<*>)?.mapNotNull { it as? String } ?: emptyList(), offerToken = json["offerToken"] as? String ?: "", @@ -2285,6 +2381,7 @@ public data class ProductSubscriptionAndroidOfferDetails( fun toJson(): Map = mapOf( "__typename" to "ProductSubscriptionAndroidOfferDetails", "basePlanId" to basePlanId, + "installmentPlanDetails" to installmentPlanDetails?.toJson(), "offerId" to offerId, "offerTags" to offerTags, "offerToken" to offerToken, @@ -2410,6 +2507,13 @@ public data class PurchaseAndroid( val obfuscatedAccountIdAndroid: String? = null, val obfuscatedProfileIdAndroid: String? = null, val packageNameAndroid: String? = null, + /** + * Pending purchase update for uncommitted subscription upgrade/downgrade (Android) + * Contains the new products and purchase token for the pending transaction. + * Returns null if no pending update exists. + * Available in Google Play Billing Library 5.0+ + */ + val pendingPurchaseUpdateAndroid: PendingPurchaseUpdateAndroid? = null, override val platform: IapPlatform, override val productId: String, override val purchaseState: PurchaseState, @@ -2439,6 +2543,7 @@ public data class PurchaseAndroid( obfuscatedAccountIdAndroid = json["obfuscatedAccountIdAndroid"] as? String, obfuscatedProfileIdAndroid = json["obfuscatedProfileIdAndroid"] as? String, packageNameAndroid = json["packageNameAndroid"] as? String, + pendingPurchaseUpdateAndroid = (json["pendingPurchaseUpdateAndroid"] as? Map)?.let { PendingPurchaseUpdateAndroid.fromJson(it) }, platform = (json["platform"] as? String)?.let { IapPlatform.fromJson(it) } ?: IapPlatform.Ios, productId = json["productId"] as? String ?: "", purchaseState = (json["purchaseState"] as? String)?.let { PurchaseState.fromJson(it) } ?: PurchaseState.Pending, @@ -2466,6 +2571,7 @@ public data class PurchaseAndroid( "obfuscatedAccountIdAndroid" to obfuscatedAccountIdAndroid, "obfuscatedProfileIdAndroid" to obfuscatedProfileIdAndroid, "packageNameAndroid" to packageNameAndroid, + "pendingPurchaseUpdateAndroid" to pendingPurchaseUpdateAndroid?.toJson(), "platform" to platform.toJson(), "productId" to productId, "purchaseState" to purchaseState.toJson(), @@ -2876,6 +2982,12 @@ public data class SubscriptionOffer( * - Android: offerId from ProductSubscriptionAndroidOfferDetails */ val id: String, + /** + * [Android] Installment plan details for this subscription offer. + * Only set for installment subscription plans; null for non-installment plans. + * Available in Google Play Billing Library 7.0+ + */ + val installmentPlanDetailsAndroid: InstallmentPlanDetailsAndroid? = null, /** * [iOS] Key identifier for signature validation. * Used with server-side signature generation for promotional offers. @@ -2947,6 +3059,7 @@ public data class SubscriptionOffer( currency = json["currency"] as? String, displayPrice = json["displayPrice"] as? String ?: "", id = json["id"] as? String ?: "", + installmentPlanDetailsAndroid = (json["installmentPlanDetailsAndroid"] as? Map)?.let { InstallmentPlanDetailsAndroid.fromJson(it) }, keyIdentifierIOS = json["keyIdentifierIOS"] as? String, localizedPriceIOS = json["localizedPriceIOS"] as? String, nonceIOS = json["nonceIOS"] as? String, @@ -2971,6 +3084,7 @@ public data class SubscriptionOffer( "currency" to currency, "displayPrice" to displayPrice, "id" to id, + "installmentPlanDetailsAndroid" to installmentPlanDetailsAndroid?.toJson(), "keyIdentifierIOS" to keyIdentifierIOS, "localizedPriceIOS" to localizedPriceIOS, "nonceIOS" to nonceIOS, @@ -4593,7 +4707,7 @@ public interface MutationResolver { */ suspend fun presentCodeRedemptionSheetIOS(): Boolean /** - * Present external purchase custom link with StoreKit UI (iOS 17.4+) + * Present external purchase custom link with StoreKit UI */ suspend fun presentExternalPurchaseLinkIOS(url: String): ExternalPurchaseLinkResultIOS /** diff --git a/openiap-versions.json b/openiap-versions.json index 8f9a5d84..6facf95b 100644 --- a/openiap-versions.json +++ b/openiap-versions.json @@ -1,6 +1,6 @@ { "apple": "1.3.14", - "google": "1.3.27", - "gql": "1.3.16", + "google": "1.3.28", + "gql": "1.3.17", "kmp-iap": "1.3.6" }