From c1fe22de34e93b71e7e9fbf4c907475c6fc14757 Mon Sep 17 00:00:00 2001 From: Hyo Date: Wed, 11 Feb 2026 07:08:33 +0900 Subject: [PATCH 1/2] 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 type (Billing Library 7.0+) - Add PendingPurchaseUpdateAndroid type (Billing Library 5.0+) - Add purchaseOptionIdAndroid field to DiscountOffer (Billing Library 7.0+) - Add tests for new types - Update llms.txt with new type documentation - Add release blog post Co-Authored-By: Claude Opus 4.5 --- .../blog/2026-02-11-14.7.10-openiap-1.3.17.md | 72 ++++++++++ docs/static/llms.txt | 28 ++++ openiap-versions.json | 6 +- .../standardized-offer-types.test.ts | 135 ++++++++++++++++++ src/types.ts | 72 ++++++++++ 5 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 docs/blog/2026-02-11-14.7.10-openiap-1.3.17.md diff --git a/docs/blog/2026-02-11-14.7.10-openiap-1.3.17.md b/docs/blog/2026-02-11-14.7.10-openiap-1.3.17.md new file mode 100644 index 000000000..ced13a52b --- /dev/null +++ b/docs/blog/2026-02-11-14.7.10-openiap-1.3.17.md @@ -0,0 +1,72 @@ +--- +slug: 14.7.10 +title: 14.7.10 - OpenIAP 1.3.17 Sync +authors: [hyochan] +tags: [release, openiap, android, billing-library] +date: 2026-02-11 +--- + +# 14.7.10 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. + +```typescript +interface InstallmentPlanDetailsAndroid { + /** Committed payments count after signup (e.g., 12 monthly payments) */ + commitmentPaymentsCount: number; + /** Subsequent commitment payments when plan renews (0 if reverts to normal) */ + subsequentCommitmentPaymentsCount: number; +} +``` + +This is available on `ProductSubscriptionAndroidOfferDetails.installmentPlanDetails`. + +### PendingPurchaseUpdateAndroid (Billing Library 5.0+) + +Details about pending subscription upgrades/downgrades. + +```typescript +interface PendingPurchaseUpdateAndroid { + /** Product IDs for the pending purchase update */ + products: string[]; + /** Purchase token for the pending transaction */ + 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. + +```typescript +// Available on DiscountOffer +discountOffer.purchaseOptionIdAndroid // string | null +``` + +## OpenIAP Versions + +| Package | Version | +|---------|---------| +| openiap-gql | 1.3.17 | +| openiap-google | 1.3.28 | +| openiap-apple | 1.3.14 | + +## Installation + +```bash +yarn add react-native-iap@14.7.10 +``` + +## 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 4419e5a47..e0ce970e8 100644 --- a/docs/static/llms.txt +++ b/docs/static/llms.txt @@ -212,9 +212,36 @@ interface DiscountOffer { offerTokenAndroid?: string; discountAmountMicrosAndroid?: string; formattedDiscountAmountAndroid?: string; + purchaseOptionIdAndroid?: string; // v14.7.10+, Billing Library 7.0+ } ``` +### InstallmentPlanDetailsAndroid (v14.7.10+, Billing Library 7.0+) + +Subscription installment plan details: + +```tsx +interface InstallmentPlanDetailsAndroid { + commitmentPaymentsCount: number; // Initial commitment (e.g., 12 months) + subsequentCommitmentPaymentsCount: number; // Renewal commitment (0 if reverts to normal) +} +``` + +Available on `ProductSubscriptionAndroidOfferDetails.installmentPlanDetails`. + +### PendingPurchaseUpdateAndroid (v14.7.10+, Billing Library 5.0+) + +Pending subscription upgrade/downgrade details: + +```tsx +interface PendingPurchaseUpdateAndroid { + products: string[]; // New products being switched to + purchaseToken: string; // Pending transaction token +} +``` + +Available on `PurchaseAndroid.pendingPurchaseUpdateAndroid`. + ### Purchase ```tsx @@ -231,6 +258,7 @@ interface Purchase { autoRenewingAndroid?: boolean; packageNameAndroid?: string; isSuspendedAndroid?: boolean; // v14.7.3+, Billing Library 8.1+ + pendingPurchaseUpdateAndroid?: PendingPurchaseUpdateAndroid; // v14.7.10+, Billing Library 5.0+ } ``` diff --git a/openiap-versions.json b/openiap-versions.json index 757a66a26..b4fd1bc00 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", - "docs": "1.3.16" + "google": "1.3.28", + "gql": "1.3.17", + "docs": "1.3.17" } diff --git a/src/__tests__/standardized-offer-types.test.ts b/src/__tests__/standardized-offer-types.test.ts index b744c6c79..b7ac7e37f 100644 --- a/src/__tests__/standardized-offer-types.test.ts +++ b/src/__tests__/standardized-offer-types.test.ts @@ -17,6 +17,9 @@ import type { ProductAndroid, ProductSubscriptionAndroid, RequestPurchaseAndroidProps, + InstallmentPlanDetailsAndroid, + PendingPurchaseUpdateAndroid, + PurchaseAndroid, } from '../types'; describe('Standardized Offer Types', () => { @@ -466,4 +469,136 @@ describe('Standardized Offer Types', () => { expect(purchaseRequest.offerToken).toBe('summer_sale_offer_token'); }); }); + + describe('InstallmentPlanDetailsAndroid', () => { + it('should have correct structure for subscription installment plans', () => { + // Installment plan details are available in Google Play Billing Library 7.0+ + const installmentDetails: InstallmentPlanDetailsAndroid = { + commitmentPaymentsCount: 12, + subsequentCommitmentPaymentsCount: 12, + }; + + expect(installmentDetails.commitmentPaymentsCount).toBe(12); + expect(installmentDetails.subsequentCommitmentPaymentsCount).toBe(12); + }); + + it('should support zero subsequentCommitmentPaymentsCount for non-recurring installments', () => { + // When subsequentCommitmentPaymentsCount is 0, plan reverts to normal after initial commitment + const nonRecurringInstallment: InstallmentPlanDetailsAndroid = { + commitmentPaymentsCount: 6, + subsequentCommitmentPaymentsCount: 0, + }; + + expect(nonRecurringInstallment.commitmentPaymentsCount).toBe(6); + expect(nonRecurringInstallment.subsequentCommitmentPaymentsCount).toBe(0); + }); + }); + + describe('PendingPurchaseUpdateAndroid', () => { + it('should have correct structure for pending subscription changes', () => { + // Pending purchase updates are available in Google Play Billing Library 5.0+ + const pendingUpdate: PendingPurchaseUpdateAndroid = { + products: ['premium_yearly'], + purchaseToken: 'pending_token_abc123', + }; + + expect(pendingUpdate.products).toEqual(['premium_yearly']); + expect(pendingUpdate.purchaseToken).toBe('pending_token_abc123'); + }); + + it('should support multiple products in pending update', () => { + // When upgrading to a bundle or combined subscription + const bundlePendingUpdate: PendingPurchaseUpdateAndroid = { + products: ['premium_yearly', 'addon_storage'], + purchaseToken: 'bundle_pending_token_xyz', + }; + + expect(bundlePendingUpdate.products).toHaveLength(2); + expect(bundlePendingUpdate.products).toContain('premium_yearly'); + expect(bundlePendingUpdate.products).toContain('addon_storage'); + }); + }); + + describe('purchaseOptionIdAndroid on DiscountOffer', () => { + it('should support purchaseOptionIdAndroid field', () => { + // purchaseOptionIdAndroid is available in Google Play Billing Library 7.0+ + const discountOfferWithPurchaseOption: DiscountOffer = { + id: 'limited_offer', + displayPrice: '$3.99', + price: 3.99, + currency: 'USD', + type: 'one-time', + offerTokenAndroid: 'offer_token_123', + purchaseOptionIdAndroid: 'purchase_option_456', + }; + + expect(discountOfferWithPurchaseOption.purchaseOptionIdAndroid).toBe( + 'purchase_option_456', + ); + }); + + it('should allow purchaseOptionIdAndroid to be optional', () => { + const offerWithoutPurchaseOption: DiscountOffer = { + displayPrice: '$4.99', + price: 4.99, + currency: 'USD', + type: 'promotional', + // No purchaseOptionIdAndroid - not all offers have this + }; + + expect( + offerWithoutPurchaseOption.purchaseOptionIdAndroid, + ).toBeUndefined(); + }); + }); + + describe('PurchaseAndroid with pendingPurchaseUpdateAndroid', () => { + it('should include pendingPurchaseUpdateAndroid for subscription upgrades', () => { + // When a subscription upgrade/downgrade is pending + const purchaseWithPendingUpdate: PurchaseAndroid = { + id: 'purchase_id_abc', + productId: 'premium_monthly', + platform: 'android', + transactionDate: 1704067200000, + purchaseToken: 'current_token', + purchaseState: 'purchased', + packageNameAndroid: 'com.example.app', + isAutoRenewing: true, + quantity: 1, + store: 'google', + pendingPurchaseUpdateAndroid: { + products: ['premium_yearly'], + purchaseToken: 'pending_upgrade_token', + }, + }; + + expect( + purchaseWithPendingUpdate.pendingPurchaseUpdateAndroid, + ).toBeDefined(); + expect( + purchaseWithPendingUpdate.pendingPurchaseUpdateAndroid?.products, + ).toEqual(['premium_yearly']); + expect( + purchaseWithPendingUpdate.pendingPurchaseUpdateAndroid?.purchaseToken, + ).toBe('pending_upgrade_token'); + }); + + it('should allow pendingPurchaseUpdateAndroid to be undefined for regular purchases', () => { + const regularPurchase: PurchaseAndroid = { + id: 'purchase_id_xyz', + productId: 'consumable_coins', + platform: 'android', + transactionDate: 1704067200000, + purchaseToken: 'regular_token', + purchaseState: 'purchased', + packageNameAndroid: 'com.example.app', + isAutoRenewing: false, + quantity: 1, + store: 'google', + // No pending update - this is a regular purchase + }; + + expect(regularPurchase.pendingPurchaseUpdateAndroid).toBeUndefined(); + }); + }); }); diff --git a/src/types.ts b/src/types.ts index 992bedd26..43cbc2259 100644 --- a/src/types.ts +++ b/src/types.ts @@ -258,6 +258,12 @@ export interface DiscountOffer { preorderDetailsAndroid?: (PreorderDetailsAndroid | null); /** Numeric price value */ price: number; + /** + * [Android] Purchase option ID for this offer. + * Used to identify which purchase option the user selected. + * Available in Google Play Billing Library 7.0+ + */ + purchaseOptionIdAndroid?: (string | null); /** [Android] Rental details if this is a rental offer. */ rentalDetailsAndroid?: (RentalDetailsAndroid | null); /** Type of discount offer */ @@ -478,6 +484,27 @@ export interface InitConnectionConfig { enableBillingProgramAndroid?: (BillingProgramAndroid | null); } +/** + * Installment plan details for subscription offers (Android) + * Contains information about the installment plan commitment. + * Available in Google Play Billing Library 7.0+ + */ +export interface 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. + */ + commitmentPaymentsCount: number; + /** + * 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). + */ + subsequentCommitmentPaymentsCount: number; +} + /** * Parameters for launching an external link (Android) * Used with launchExternalLink to initiate external offer or app install flows @@ -680,6 +707,26 @@ export type PaymentMode = 'free-trial' | 'pay-as-you-go' | 'pay-up-front' | 'unk export type PaymentModeIOS = 'empty' | 'free-trial' | 'pay-as-you-go' | 'pay-up-front'; +/** + * 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+ + */ +export interface PendingPurchaseUpdateAndroid { + /** + * Product IDs for the pending purchase update. + * These are the new products the user is switching to. + */ + products: string[]; + /** + * Purchase token for the pending transaction. + * Use this token to track or manage the pending purchase update. + */ + purchaseToken: string; +} + /** * Pre-order details for one-time purchase products (Android) * Available in Google Play Billing Library 8.1.0+ @@ -791,6 +838,12 @@ export interface ProductAndroidOneTimePurchaseOfferDetail { preorderDetailsAndroid?: (PreorderDetailsAndroid | null); priceAmountMicros: string; 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+ + */ + purchaseOptionId?: (string | null); /** Rental details for rental offers */ rentalDetailsAndroid?: (RentalDetailsAndroid | null); /** Valid time window for the offer */ @@ -911,6 +964,12 @@ export interface ProductSubscriptionAndroid extends ProductCommon { */ export interface ProductSubscriptionAndroidOfferDetails { 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+ + */ + installmentPlanDetails?: (InstallmentPlanDetailsAndroid | null); offerId?: (string | null); offerTags: string[]; offerToken: string; @@ -1000,6 +1059,13 @@ export interface PurchaseAndroid extends PurchaseCommon { obfuscatedAccountIdAndroid?: (string | null); obfuscatedProfileIdAndroid?: (string | null); 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+ + */ + pendingPurchaseUpdateAndroid?: (PendingPurchaseUpdateAndroid | null); /** @deprecated Use store instead */ platform: IapPlatform; productId: string; @@ -1533,6 +1599,12 @@ export interface SubscriptionOffer { * - Android: offerId from ProductSubscriptionAndroidOfferDetails */ 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+ + */ + installmentPlanDetailsAndroid?: (InstallmentPlanDetailsAndroid | null); /** * [iOS] Key identifier for signature validation. * Used with server-side signature generation for promotional offers. From 9aa74a7de02ca8e7a11ab04d157112369ae8fde2 Mon Sep 17 00:00:00 2001 From: Hyo Date: Wed, 11 Feb 2026 11:03:23 +0900 Subject: [PATCH 2/2] 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 type (Billing Library 7.0+) - Add PendingPurchaseUpdateAndroid type (Billing Library 5.0+) - Add purchaseOptionIdAndroid field to DiscountOffer (Billing Library 7.0+) - Add purchaseOptionId field to ProductAndroidOneTimePurchaseOfferDetail (Billing Library 7.0+) - Add tests for new types - Update llms.txt with new type documentation - Add release blog post Co-Authored-By: Claude Opus 4.5 --- docs/bun.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/bun.lock b/docs/bun.lock index 7b78e7bdc..8cc7be82d 100644 --- a/docs/bun.lock +++ b/docs/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "react-native-iap-docs",