Skip to content

Commit 4a200d1

Browse files
authored
feat!: drop deprecated APIs for v3 (#199)
### Summary Remove all deprecated public APIs and aliases for the 3.0.0 major release. Unify tokens to `purchaseToken`, drop legacy fields, and delete iOS alias wrappers. ### Changes - Remove deprecated functions: getProducts, getSubscriptions, requestProducts, requestSubscription, getPurchaseHistory - Remove iOS wrapper aliases (non-*IOS) and `transactionUpdatedIOS` - Unify Android/iOS token fields to `purchaseToken`; drop `purchaseTokenAndroid` and `jwsRepresentationIOS` - Update helpers/tests to stop using removed fields (use `id` instead of `transactionId`) Focus on minimal breaking surface per v3 plan. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Breaking Changes** * Many deprecated/legacy APIs and iOS alias/wrapper helpers removed; OpenIapEvent.TransactionIapUpdated removed. * Public types simplified: transactionId, purchaseTokenAndroid, and jwsRepresentationIOS removed. * Android unified to use purchaseToken; iOS manage-subscriptions endpoint now returns detailed purchase data (not boolean). * **Migration** * Use fetchProducts and purchaseUpdatedListener; migrate to id and purchaseToken as canonical identifiers. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 7d815a9 commit 4a200d1

File tree

16 files changed

+146
-781
lines changed

16 files changed

+146
-781
lines changed

android/src/main/java/expo/modules/iap/ExpoIapModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ class ExpoIapModule : Module() {
259259
scope.launch {
260260
try {
261261
openIap.consumePurchaseAndroid(token)
262-
promise.resolve(mapOf("responseCode" to 0, "purchaseTokenAndroid" to token))
262+
promise.resolve(mapOf("responseCode" to 0, "purchaseToken" to token))
263263
} catch (e: Exception) {
264264
promise.reject(OpenIapError.E_SERVICE_ERROR, e.message, null)
265265
}

docs/blog/2025-09-12-v3.0.0.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
slug: v3-0-0
3+
title: v3.0.0 — unified tokens, simplified API, iOS parity
4+
tags: [release, breaking-change, android, ios]
5+
---
6+
7+
v3.0.0 is a major cleanup release that removes long‑deprecated APIs, unifies token handling across platforms, and aligns iOS/Android behavior behind a smaller, clearer surface area.
8+
9+
Highlights
10+
11+
- Unified tokens: `Purchase.purchaseToken` is the single source for both iOS and Android. iOS no longer exposes `jwsRepresentationIOS`; use `purchaseToken` instead.
12+
- Simpler API: Legacy helpers are gone — use `fetchProducts`, `requestPurchase`, `getAvailablePurchases`, and listeners.
13+
- iOS parity: Only `*IOS` methods remain for iOS‑specific behavior. Also, `showManageSubscriptionsIOS()` now returns purchases (array) rather than just a boolean.
14+
- Stronger types: Consolidated product/purchase types; removed legacy aliases and fields.
15+
16+
Breaking changes
17+
18+
- Removed functions
19+
- `getProducts`, `getSubscriptions`, `requestProducts`, `requestSubscription`
20+
- `getPurchaseHistory` / `getPurchaseHistories`
21+
- Non‑suffixed iOS aliases (use `*IOS` variants)
22+
23+
- Changed behavior
24+
- `showManageSubscriptionsIOS(): Promise<Purchase[]>` — now returns purchases instead of `boolean`.
25+
- `getAvailablePurchases` options: only accepts `alsoPublishToEventListenerIOS` and `onlyIncludeActiveItemsIOS`.
26+
27+
Quick migration
28+
29+
- Fetch products (in‑app or subs):
30+
31+
```ts
32+
import { fetchProducts, type SubscriptionProduct } from 'expo-iap';
33+
34+
const inapps = await fetchProducts({ skus: ['prod1', 'prod2'], type: 'inapp' });
35+
36+
const subs = (await fetchProducts({
37+
skus: ['sub_monthly'],
38+
type: 'subs',
39+
})) as SubscriptionProduct[];
40+
```
41+
42+
- Request purchase (products or subs):
43+
44+
```ts
45+
import { requestPurchase } from 'expo-iap';
46+
47+
// In‑app
48+
await requestPurchase({
49+
request: { ios: { sku: 'prod1' }, android: { skus: ['prod1'] } },
50+
type: 'inapp',
51+
});
52+
53+
// Subscriptions (Android supply offer tokens)
54+
await requestPurchase({
55+
request: {
56+
ios: { sku: 'sub_monthly' },
57+
android: {
58+
skus: ['sub_monthly'],
59+
subscriptionOffers: [{ sku: 'sub_monthly', offerToken: 'your-offer-token' }],
60+
},
61+
},
62+
type: 'subs',
63+
});
64+
```
65+
66+
- Restore/available purchases:
67+
68+
```ts
69+
import { getAvailablePurchases, restorePurchases } from 'expo-iap';
70+
71+
// iOS‑specific flags only
72+
const purchases = await getAvailablePurchases({
73+
alsoPublishToEventListenerIOS: false,
74+
onlyIncludeActiveItemsIOS: true,
75+
});
76+
77+
// Cross‑platform helper that syncs on iOS then lists
78+
const restored = await restorePurchases({
79+
alsoPublishToEventListenerIOS: false,
80+
onlyIncludeActiveItemsIOS: true,
81+
});
82+
```
83+
84+
- Tokens and finishing transactions:
85+
86+
```ts
87+
import { finishTransaction, type Purchase } from 'expo-iap';
88+
89+
function getToken(p: Purchase) {
90+
return p.purchaseToken; // iOS JWS or Android token
91+
}
92+
93+
await finishTransaction({ purchase: somePurchase, isConsumable: true });
94+
```
95+
96+
Notes
97+
98+
- iOS promoted products listener remains `promotedProductListenerIOS`.
99+
- `getStorefront()` stays as the cross‑platform helper; call `getStorefrontIOS()` for iOS‑explicit.
100+
101+
Changelog
102+
103+
See `CHANGELOG.md` for the full list of changes in 3.0.0.
104+

example/__tests__/core-functions.test.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,7 @@ describe('Core Functions Tests', () => {
1717
expect(typeof ExpoIap.fetchProducts).toBe('function');
1818
});
1919

20-
it('should export deprecated requestProducts function', () => {
21-
expect(ExpoIap.requestProducts).toBeDefined();
22-
expect(typeof ExpoIap.requestProducts).toBe('function');
23-
});
24-
25-
it('should export deprecated getProducts function', () => {
26-
expect(ExpoIap.getProducts).toBeDefined();
27-
expect(typeof ExpoIap.getProducts).toBe('function');
28-
});
29-
30-
it('should export deprecated getSubscriptions function', () => {
31-
expect(ExpoIap.getSubscriptions).toBeDefined();
32-
expect(typeof ExpoIap.getSubscriptions).toBe('function');
33-
});
20+
// v3: legacy helpers removed (requestProducts/getProducts/getSubscriptions)
3421

3522
it('should export requestPurchase function', () => {
3623
expect(ExpoIap.requestPurchase).toBeDefined();
@@ -52,7 +39,7 @@ describe('Core Functions Tests', () => {
5239
expect(typeof ExpoIap.getStorefrontIOS).toBe('function');
5340
});
5441

55-
it('should export deprecated getStorefront function', () => {
42+
it('should export getStorefront function', () => {
5643
expect(ExpoIap.getStorefront).toBeDefined();
5744
expect(typeof ExpoIap.getStorefront).toBe('function');
5845
});

example/__tests__/ios-functions.test.tsx

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ describe('iOS Functions Tests', () => {
1212
expect(typeof ExpoIap.validateReceiptIOS).toBe('function');
1313
});
1414

15-
it('should export deprecated getAppTransaction function', () => {
16-
expect(ExpoIap.getAppTransaction).toBeDefined();
17-
expect(typeof ExpoIap.getAppTransaction).toBe('function');
18-
});
19-
2015
// Note: validateReceiptIOS is not deprecated, but there might be deprecated
2116
// aliases for backward compatibility
2217
});
@@ -102,33 +97,6 @@ describe('iOS Functions Tests', () => {
10297
'getAppTransaction requires Xcode 15.0+ with iOS 16.0 SDK for compilation',
10398
);
10499
});
105-
106-
it('should handle deprecated getAppTransaction function', async () => {
107-
const mockTransaction: ExpoIap.AppTransactionIOS = {
108-
appTransactionId: 'deprecated-test',
109-
bundleId: 'com.example.app',
110-
appVersion: '1.0.0',
111-
originalAppVersion: '1.0.0',
112-
originalPurchaseDate: Date.now(),
113-
deviceVerification: 'verification',
114-
deviceVerificationNonce: 'nonce',
115-
environment: 'Sandbox',
116-
signedDate: Date.now(),
117-
appId: 123456,
118-
appVersionId: 789012,
119-
originalPlatform: 'iOS',
120-
};
121-
122-
(ExpoIap.getAppTransaction as jest.Mock).mockResolvedValue(
123-
mockTransaction,
124-
);
125-
126-
const result = await ExpoIap.getAppTransaction();
127-
128-
expect(ExpoIap.getAppTransaction).toHaveBeenCalledTimes(1);
129-
expect(result).toEqual(mockTransaction);
130-
expect(result?.environment).toBe('Sandbox');
131-
});
132100
});
133101

134102
describe('Type Exports', () => {
@@ -169,20 +137,5 @@ describe('iOS Functions Tests', () => {
169137
expect(ExpoIap.getTransactionJwsIOS).toBeDefined();
170138
expect(ExpoIap.presentCodeRedemptionSheetIOS).toBeDefined();
171139
});
172-
173-
it('should export deprecated iOS functions', () => {
174-
// Deprecated functions without IOS suffix
175-
expect(ExpoIap.sync).toBeDefined();
176-
expect(ExpoIap.isEligibleForIntroOffer).toBeDefined();
177-
expect(ExpoIap.subscriptionStatus).toBeDefined();
178-
expect(ExpoIap.currentEntitlement).toBeDefined();
179-
expect(ExpoIap.latestTransaction).toBeDefined();
180-
expect(ExpoIap.beginRefundRequest).toBeDefined();
181-
expect(ExpoIap.showManageSubscriptions).toBeDefined();
182-
expect(ExpoIap.getReceiptIOS).toBeDefined();
183-
expect(ExpoIap.isTransactionVerified).toBeDefined();
184-
expect(ExpoIap.getTransactionJws).toBeDefined();
185-
expect(ExpoIap.presentCodeRedemptionSheet).toBeDefined();
186-
});
187140
});
188141
});

example/__tests__/subscription-flow.test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ const createMockAndroidSubscription = () => ({
5858
const mockUseIAP = jest.fn();
5959
jest.mock('../../src', () => ({
6060
initConnection: mockInitConnection,
61-
requestProducts: jest.fn(),
6261
requestPurchase: mockRequestPurchase,
6362
useIAP: () => mockUseIAP(),
6463
}));

example/jest.setup.js

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,16 @@ jest.mock('expo-iap', () => {
3232
const mockGetProducts = jest.fn();
3333
const mockGetSubscriptions = jest.fn();
3434
const mockRequestPurchase = jest.fn();
35-
// Removed in v2.9.0
3635

3736
return {
3837
// Core functions
3938
initConnection: jest.fn(),
4039
endConnection: jest.fn(),
41-
getProducts: mockGetProducts,
42-
getSubscriptions: mockGetSubscriptions,
4340
fetchProducts: mockFetchProducts,
44-
requestProducts: jest.fn(),
41+
4542
requestPurchase: mockRequestPurchase,
4643
finishTransaction: mockFinishTransaction,
47-
// getPurchaseHistories removed in v2.9.0
44+
4845
getAvailablePurchases: mockGetAvailablePurchases,
4946

5047
// iOS functions with IOS suffix
@@ -63,21 +60,8 @@ jest.mock('expo-iap', () => {
6360
getAppTransactionIOS: jest.fn(),
6461
validateReceiptIOS: jest.fn(),
6562

66-
// Deprecated iOS functions
63+
// Cross-platform storefront helper
6764
getStorefront: jest.fn(),
68-
sync: jest.fn(),
69-
isEligibleForIntroOffer: jest.fn(),
70-
subscriptionStatus: jest.fn(),
71-
currentEntitlement: jest.fn(),
72-
latestTransaction: jest.fn(),
73-
beginRefundRequest: jest.fn(),
74-
showManageSubscriptions: jest.fn(),
75-
getReceiptIOS: jest.fn(),
76-
isTransactionVerified: jest.fn(),
77-
getTransactionJws: jest.fn(),
78-
presentCodeRedemptionSheet: jest.fn(),
79-
getAppTransaction: jest.fn(),
80-
validateReceiptIOS: jest.fn(),
8165

8266
// Android functions
8367
deepLinkToSubscriptionsAndroid: jest.fn(),
@@ -98,8 +82,7 @@ jest.mock('expo-iap', () => {
9882
currentPurchase: null,
9983
currentPurchaseError: null,
10084
fetchProducts: mockFetchProducts,
101-
getProducts: mockGetProducts,
102-
getSubscriptions: mockGetSubscriptions,
85+
10386
requestPurchase: mockRequestPurchase,
10487
getAvailablePurchases: mockGetAvailablePurchases,
10588
finishTransaction: mockFinishTransaction,

ios/ExpoIapModule.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,13 @@ public class ExpoIapModule: Module {
271271
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
272272
}
273273

274+
// Backward-compatible alias expected by JS layer/tests
275+
AsyncFunction("getReceiptDataIOS") { () async throws -> String in
276+
try await ensureConnection()
277+
logDebug("getReceiptDataIOS called (alias of getReceiptIOS)")
278+
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
279+
}
280+
274281
AsyncFunction("requestReceiptRefreshIOS") { () async throws -> String in
275282
try await ensureConnection()
276283
logDebug("requestReceiptRefreshIOS called")
@@ -307,11 +314,11 @@ public class ExpoIapModule: Module {
307314
return true
308315
}
309316

310-
AsyncFunction("showManageSubscriptionsIOS") { () async throws -> Bool in
317+
AsyncFunction("showManageSubscriptionsIOS") { () async throws -> [[String: Any?]] in
311318
try await ensureConnection()
312319
logDebug("showManageSubscriptionsIOS called")
313-
let _ = try await OpenIapModule.shared.showManageSubscriptionsIOS()
314-
return true
320+
let purchases = try await OpenIapModule.shared.showManageSubscriptionsIOS()
321+
return OpenIapSerialization.purchases(purchases)
315322
}
316323

317324
AsyncFunction("deepLinkToSubscriptionsIOS") { () async throws in

src/ExpoIap.types.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export type PurchaseCommon = {
3737
id: string; // Transaction identifier - used by finishTransaction
3838
productId: string; // Product identifier - which product was purchased
3939
ids?: string[]; // Product identifiers for purchases that include multiple products
40-
transactionId?: string; // @deprecated - use id instead
4140
transactionDate: number;
4241
transactionReceipt: string;
4342
purchaseToken?: string; // Unified purchase token (jwsRepresentation for iOS, purchaseToken for Android)
@@ -70,17 +69,11 @@ export type Purchase =
7069
| (PurchaseAndroid & AndroidPlatform)
7170
| (PurchaseIOS & IosPlatform);
7271

73-
// Removed legacy type aliases `ProductPurchase` and `SubscriptionPurchase` in v2.9.0
74-
7572
export type PurchaseResult = {
7673
responseCode?: number;
7774
debugMessage?: string;
7875
code?: string;
7976
message?: string;
80-
/**
81-
* @deprecated Use `purchaseToken` instead. This field will be removed in a future version.
82-
*/
83-
purchaseTokenAndroid?: string;
8477
purchaseToken?: string;
8578
};
8679
/**
@@ -399,7 +392,6 @@ export interface RequestPurchaseAndroidProps {
399392
*/
400393
export interface RequestSubscriptionAndroidProps
401394
extends RequestPurchaseAndroidProps {
402-
readonly purchaseTokenAndroid?: string;
403395
readonly replacementModeAndroid?: number;
404396
readonly subscriptionOffers: {
405397
sku: string;

0 commit comments

Comments
 (0)