expo-iap 2.7.14 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.copilot-instructions.md +5 -5
  2. package/.cursorrules +31 -13
  3. package/CHANGELOG.md +16 -1
  4. package/CLAUDE.md +5 -1
  5. package/build/ExpoIap.types.d.ts +19 -11
  6. package/build/ExpoIap.types.d.ts.map +1 -1
  7. package/build/ExpoIap.types.js.map +1 -1
  8. package/build/helpers/subscription.d.ts.map +1 -1
  9. package/build/helpers/subscription.js +24 -17
  10. package/build/helpers/subscription.js.map +1 -1
  11. package/build/index.d.ts +2 -2
  12. package/build/index.js +8 -8
  13. package/build/index.js.map +1 -1
  14. package/build/modules/ios.d.ts +10 -23
  15. package/build/modules/ios.d.ts.map +1 -1
  16. package/build/modules/ios.js +9 -23
  17. package/build/modules/ios.js.map +1 -1
  18. package/build/types/{ExpoIapIos.types.d.ts → ExpoIapIOS.types.d.ts} +41 -33
  19. package/build/types/{ExpoIapIos.types.d.ts.map → ExpoIapIOS.types.d.ts.map} +1 -1
  20. package/build/types/ExpoIapIOS.types.js +2 -0
  21. package/build/types/ExpoIapIOS.types.js.map +1 -0
  22. package/build/{useIap.d.ts → useIAP.d.ts} +2 -2
  23. package/build/{useIap.d.ts.map → useIAP.d.ts.map} +1 -1
  24. package/build/{useIap.js → useIAP.js} +7 -6
  25. package/build/useIAP.js.map +1 -0
  26. package/bun.lock +677 -61
  27. package/ios/ExpoIapModule.swift +46 -46
  28. package/jest.config.js +43 -0
  29. package/package.json +8 -3
  30. package/src/ExpoIap.types.ts +29 -15
  31. package/src/helpers/subscription.ts +27 -20
  32. package/src/index.ts +11 -11
  33. package/src/modules/ios.ts +13 -40
  34. package/src/types/{ExpoIapIos.types.ts → ExpoIapIOS.types.ts} +42 -32
  35. package/src/{useIap.ts → useIAP.ts} +7 -6
  36. package/build/types/ExpoIapIos.types.js +0 -2
  37. package/build/types/ExpoIapIos.types.js.map +0 -1
  38. package/build/useIap.js.map +0 -1
@@ -22,12 +22,12 @@ struct IapEvent {
22
22
  }
23
23
 
24
24
  @available(iOS 15.0, *)
25
- func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: String? = nil) -> [String: Any?] {
25
+ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIOS: String? = nil) -> [String: Any?] {
26
26
  let _ =
27
27
  transaction.productType.rawValue.lowercased().contains("renewable")
28
28
  || transaction.expirationDate != nil
29
29
 
30
- var transactionReasonIos: String? = nil
30
+ var transactionReasonIOS: String? = nil
31
31
  var webOrderLineItemId: Int? = nil
32
32
  var jsonData: [String: Any]? = nil
33
33
  var jwsReceipt: String = ""
@@ -38,7 +38,7 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
38
38
  do {
39
39
  if let jsonObj = try JSONSerialization.jsonObject(with: jsonRep) as? [String: Any] {
40
40
  jsonData = jsonObj
41
- transactionReasonIos = jsonObj["transactionReason"] as? String
41
+ transactionReasonIOS = jsonObj["transactionReason"] as? String
42
42
  if let webOrderId = jsonObj["webOrderLineItemID"] as? NSNumber {
43
43
  webOrderLineItemId = webOrderId.intValue
44
44
  }
@@ -56,47 +56,47 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
56
56
  "transactionReceipt": jwsReceipt,
57
57
  "platform": "ios",
58
58
 
59
- "quantityIos": transaction.purchasedQuantity,
60
- "originalTransactionDateIos": transaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
61
- "originalTransactionIdentifierIos": String(transaction.originalID),
59
+ "quantityIOS": transaction.purchasedQuantity,
60
+ "originalTransactionDateIOS": transaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
61
+ "originalTransactionIdentifierIOS": String(transaction.originalID),
62
62
  "appAccountToken": transaction.appAccountToken?.uuidString,
63
63
 
64
- "appBundleIdIos": transaction.appBundleID,
65
- "productTypeIos": transaction.productType.rawValue,
66
- "subscriptionGroupIdIos": transaction.subscriptionGroupID,
64
+ "appBundleIdIOS": transaction.appBundleID,
65
+ "productTypeIOS": transaction.productType.rawValue,
66
+ "subscriptionGroupIdIOS": transaction.subscriptionGroupID,
67
67
 
68
- "webOrderLineItemIdIos": webOrderLineItemId,
68
+ "webOrderLineItemIdIOS": webOrderLineItemId,
69
69
 
70
- "expirationDateIos": transaction.expirationDate.map { $0.timeIntervalSince1970 * 1000 },
70
+ "expirationDateIOS": transaction.expirationDate.map { $0.timeIntervalSince1970 * 1000 },
71
71
 
72
- "isUpgradedIos": transaction.isUpgraded,
73
- "ownershipTypeIos": transaction.ownershipType.rawValue,
72
+ "isUpgradedIOS": transaction.isUpgraded,
73
+ "ownershipTypeIOS": transaction.ownershipType.rawValue,
74
74
 
75
- "revocationDateIos": transaction.revocationDate.map { $0.timeIntervalSince1970 * 1000 },
76
- "revocationReasonIos": transaction.revocationReason?.rawValue,
77
- "transactionReasonIos": transactionReasonIos,
75
+ "revocationDateIOS": transaction.revocationDate.map { $0.timeIntervalSince1970 * 1000 },
76
+ "revocationReasonIOS": transaction.revocationReason?.rawValue,
77
+ "transactionReasonIOS": transactionReasonIOS,
78
78
  ]
79
79
 
80
- if (jwsRepresentationIos != nil) {
81
- logDebug("serializeTransaction adding jwsRepresentationIos with length: \(jwsRepresentationIos!.count)")
82
- purchaseMap["jwsRepresentationIos"] = jwsRepresentationIos
83
- purchaseMap["purchaseToken"] = jwsRepresentationIos
80
+ if (jwsRepresentationIOS != nil) {
81
+ logDebug("serializeTransaction adding jwsRepresentationIOS with length: \(jwsRepresentationIOS!.count)")
82
+ purchaseMap["jwsRepresentationIOS"] = jwsRepresentationIOS
83
+ purchaseMap["purchaseToken"] = jwsRepresentationIOS
84
84
  } else {
85
- logDebug("serializeTransaction jwsRepresentationIos is nil")
85
+ logDebug("serializeTransaction jwsRepresentationIOS is nil")
86
86
  }
87
87
 
88
88
  if #available(iOS 16.0, *) {
89
- purchaseMap["environmentIos"] = transaction.environment.rawValue
89
+ purchaseMap["environmentIOS"] = transaction.environment.rawValue
90
90
  }
91
91
 
92
92
  if #available(iOS 17.0, *) {
93
- purchaseMap["storefrontCountryCodeIos"] = transaction.storefront.countryCode
94
- purchaseMap["reasonIos"] = transaction.reason.rawValue
93
+ purchaseMap["storefrontCountryCodeIOS"] = transaction.storefront.countryCode
94
+ purchaseMap["reasonIOS"] = transaction.reason.rawValue
95
95
  }
96
96
 
97
97
  if #available(iOS 17.2, *) {
98
98
  if let offer = transaction.offer {
99
- purchaseMap["offerIos"] = [
99
+ purchaseMap["offerIOS"] = [
100
100
  "id": offer.id ?? "",
101
101
  "type": offer.type.rawValue,
102
102
  "paymentMode": offer.paymentMode?.rawValue ?? "",
@@ -106,10 +106,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
106
106
 
107
107
  if #available(iOS 15.4, *), let jsonData = jsonData {
108
108
  if let price = jsonData["price"] as? NSNumber {
109
- purchaseMap["priceIos"] = price.doubleValue
109
+ purchaseMap["priceIOS"] = price.doubleValue
110
110
  }
111
111
  if let currency = jsonData["currency"] as? String {
112
- purchaseMap["currencyIos"] = currency
112
+ purchaseMap["currencyIOS"] = currency
113
113
  }
114
114
  }
115
115
 
@@ -118,7 +118,7 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
118
118
 
119
119
  private let DEFAULT_SUBSCRIPTION_PERIOD_UNIT = "DAY" // Default fallback unit for subscription periods.
120
120
 
121
- func getPeriodIos(_ unit: Product.SubscriptionPeriod.Unit) -> String {
121
+ func getPeriodIOS(_ unit: Product.SubscriptionPeriod.Unit) -> String {
122
122
  return switch (unit) {
123
123
  case .day: "DAY"
124
124
  case .week: "WEEK"
@@ -135,7 +135,7 @@ func serializeOffer(_ offer: Product.SubscriptionOffer?) -> [String: Any?]? {
135
135
  return [
136
136
  "id": offer.id,
137
137
  "period": [
138
- "unit": getPeriodIos(offer.period.unit),
138
+ "unit": getPeriodIOS(offer.period.unit),
139
139
  "value": offer.period.value
140
140
  ],
141
141
  "periodCount": offer.periodCount,
@@ -151,9 +151,9 @@ func serializeSubscription(_ s: Product.SubscriptionInfo?) -> [String: Any?]? {
151
151
  return [
152
152
  "introductoryOffer": serializeOffer(s.introductoryOffer),
153
153
  "promotionalOffers": s.promotionalOffers.map(serializeOffer),
154
- "subscriptionGroupID": s.subscriptionGroupID,
154
+ "subscriptionGroupId": s.subscriptionGroupID,
155
155
  "subscriptionPeriod": [
156
- "unit": getPeriodIos(s.subscriptionPeriod.unit),
156
+ "unit": getPeriodIOS(s.subscriptionPeriod.unit),
157
157
  "value": s.subscriptionPeriod.value
158
158
  ],
159
159
  ]
@@ -290,7 +290,7 @@ public class ExpoIapModule: Module {
290
290
  }
291
291
 
292
292
  var result: [String: Any?] = [
293
- "bundleID": appTransaction.bundleID,
293
+ "bundleId": appTransaction.bundleID,
294
294
  "appVersion": appTransaction.appVersion,
295
295
  "originalAppVersion": appTransaction.originalAppVersion,
296
296
  "originalPurchaseDate": appTransaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
@@ -298,13 +298,13 @@ public class ExpoIapModule: Module {
298
298
  "deviceVerificationNonce": appTransaction.deviceVerificationNonce.uuidString,
299
299
  "environment": appTransaction.environment.rawValue,
300
300
  "signedDate": appTransaction.signedDate.timeIntervalSince1970 * 1000,
301
- "appID": appTransaction.appID,
302
- "appVersionID": appTransaction.appVersionID,
301
+ "appId": appTransaction.appID,
302
+ "appVersionId": appTransaction.appVersionID,
303
303
  "preorderDate": appTransaction.preorderDate.map { $0.timeIntervalSince1970 * 1000 }
304
304
  ]
305
305
 
306
306
  if #available(iOS 18.4, *) {
307
- result["appTransactionID"] = appTransaction.appTransactionID
307
+ result["appTransactionId"] = appTransaction.appTransactionID
308
308
  result["originalPlatform"] = appTransaction.originalPlatform.rawValue
309
309
  }
310
310
 
@@ -393,8 +393,8 @@ public class ExpoIapModule: Module {
393
393
 
394
394
  var purchasedItemsSerialized: [[String: Any?]] = []
395
395
 
396
- func addTransaction(transaction: Transaction, jwsRepresentationIos: String? = nil) {
397
- let serialized = serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos)
396
+ func addTransaction(transaction: Transaction, jwsRepresentationIOS: String? = nil) {
397
+ let serialized = serializeTransaction(transaction, jwsRepresentationIOS: jwsRepresentationIOS)
398
398
  purchasedItemsSerialized.append(serialized)
399
399
 
400
400
  if alsoPublishToEventListener {
@@ -408,7 +408,7 @@ public class ExpoIapModule: Module {
408
408
  do {
409
409
  let transaction = try self.checkVerified(verification)
410
410
  if !onlyIncludeActiveItems {
411
- addTransaction(transaction: transaction, jwsRepresentationIos: verification.jwsRepresentation)
411
+ addTransaction(transaction: transaction, jwsRepresentationIOS: verification.jwsRepresentation)
412
412
  continue
413
413
  }
414
414
  switch transaction.productType {
@@ -416,7 +416,7 @@ public class ExpoIapModule: Module {
416
416
  if await self.productStore?.getProduct(productID: transaction.productID)
417
417
  != nil
418
418
  {
419
- addTransaction(transaction: transaction, jwsRepresentationIos: verification.jwsRepresentation)
419
+ addTransaction(transaction: transaction, jwsRepresentationIOS: verification.jwsRepresentation)
420
420
  }
421
421
  case .nonRenewable:
422
422
  if await self.productStore?.getProduct(productID: transaction.productID)
@@ -426,7 +426,7 @@ public class ExpoIapModule: Module {
426
426
  let expirationDate = Calendar(identifier: .gregorian).date(
427
427
  byAdding: DateComponents(year: 1), to: transaction.purchaseDate)!
428
428
  if currentDate < expirationDate {
429
- addTransaction(transaction: transaction, jwsRepresentationIos: verification.jwsRepresentation)
429
+ addTransaction(transaction: transaction, jwsRepresentationIOS: verification.jwsRepresentation)
430
430
  }
431
431
  }
432
432
  default:
@@ -537,10 +537,10 @@ public class ExpoIapModule: Module {
537
537
  return nil
538
538
  } else {
539
539
  self.transactions[String(transaction.id)] = transaction
540
- let serialized = serializeTransaction(transaction, jwsRepresentationIos: verification.jwsRepresentation)
540
+ let serialized = serializeTransaction(transaction, jwsRepresentationIOS: verification.jwsRepresentation)
541
541
 
542
- // Debug: Check if jwsRepresentationIos is included in serialized result
543
- logDebug("buyProduct serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
542
+ // Debug: Check if jwsRepresentationIOS is included in serialized result
543
+ logDebug("buyProduct serialized includes JWS: \(serialized["jwsRepresentationIOS"] != nil)")
544
544
 
545
545
  self.sendEvent(IapEvent.PurchaseUpdated, serialized)
546
546
  return serialized
@@ -888,7 +888,7 @@ public class ExpoIapModule: Module {
888
888
  // If this doesn't throw, the transaction is verified
889
889
  let transaction = try self.checkVerified(result)
890
890
  isValid = true
891
- latestTransaction = serializeTransaction(transaction, jwsRepresentationIos: result.jwsRepresentation)
891
+ latestTransaction = serializeTransaction(transaction, jwsRepresentationIOS: result.jwsRepresentation)
892
892
  } catch {
893
893
  isValid = false
894
894
  }
@@ -974,7 +974,7 @@ public class ExpoIapModule: Module {
974
974
  let transaction = try self.checkVerified(result)
975
975
  self.transactions[String(transaction.id)] = transaction
976
976
  if self.hasListeners {
977
- let serialized = serializeTransaction(transaction, jwsRepresentationIos: result.jwsRepresentation)
977
+ let serialized = serializeTransaction(transaction, jwsRepresentationIOS: result.jwsRepresentation)
978
978
  self.sendEvent(IapEvent.PurchaseUpdated, serialized)
979
979
  }
980
980
  } catch {
@@ -1076,7 +1076,7 @@ public class ExpoIapModule: Module {
1076
1076
  previousWillAutoRenew != currentWillAutoRenew {
1077
1077
 
1078
1078
  // Use the jwsRepresentation when serializing the transaction
1079
- var purchaseMap = serializeTransaction(transaction, jwsRepresentationIos: result.jwsRepresentation)
1079
+ var purchaseMap = serializeTransaction(transaction, jwsRepresentationIOS: result.jwsRepresentation)
1080
1080
 
1081
1081
  if case .verified(let renewalInfo) = status.renewalInfo {
1082
1082
  if let renewalInfoDict = serializeRenewalInfo(.verified(renewalInfo)) {
package/jest.config.js ADDED
@@ -0,0 +1,43 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/src'],
5
+ testMatch: [
6
+ '**/__tests__/**/*.+(ts|tsx|js)',
7
+ '**/?(*.)+(spec|test).+(ts|tsx|js)',
8
+ ],
9
+ transform: {
10
+ '^.+\\.(ts|tsx)$': [
11
+ 'ts-jest',
12
+ {
13
+ tsconfig: {
14
+ jsx: 'react',
15
+ esModuleInterop: true,
16
+ allowSyntheticDefaultImports: true,
17
+ moduleResolution: 'node',
18
+ skipLibCheck: true,
19
+ },
20
+ },
21
+ ],
22
+ },
23
+ moduleNameMapper: {
24
+ '^react-native$': '<rootDir>/src/__mocks__/react-native.js',
25
+ '^expo-modules-core$': '<rootDir>/src/__mocks__/expo-modules-core.js',
26
+ },
27
+ collectCoverageFrom: [
28
+ 'src/**/*.{ts,tsx}',
29
+ '!src/**/*.d.ts',
30
+ '!src/**/__tests__/**',
31
+ '!src/**/__mocks__/**',
32
+ '!src/ExpoIapModule.ts',
33
+ '!src/ExpoIapModule.web.ts',
34
+ ],
35
+ coverageThreshold: {
36
+ global: {
37
+ branches: 15,
38
+ functions: 15,
39
+ lines: 15,
40
+ statements: 15,
41
+ },
42
+ },
43
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.7.14",
3
+ "version": "2.8.0",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -12,6 +12,8 @@
12
12
  "lint:prettier": "prettier --write \"**/*.{md,js,jsx,ts,tsx}\"",
13
13
  "lint:tsc": "tsc -p tsconfig.json --noEmit --skipLibCheck",
14
14
  "lint:ci": "bun run lint:tsc && bun run lint:eslint && bun run lint:prettier",
15
+ "test": "jest",
16
+ "test:coverage": "jest --coverage",
15
17
  "prepare": "expo-module prepare",
16
18
  "expo-module": "expo-module",
17
19
  "open:ios": "xed example/ios",
@@ -38,15 +40,18 @@
38
40
  "author": "hyochan <hyochan.dev@gmail.com> (https://github.com/hyochan)",
39
41
  "license": "MIT",
40
42
  "homepage": "https://github.com/hyochan/expo-iap#readme",
41
- "dependencies": {},
42
43
  "devDependencies": {
44
+ "@jest/globals": "^30.0.5",
45
+ "@types/jest": "^30.0.0",
43
46
  "@types/react": "~19.1.7",
44
47
  "eslint": "8.57.0",
45
48
  "eslint-config-expo": "^9.2.0",
46
49
  "eslint-config-prettier": "^10.1.5",
47
50
  "eslint-plugin-prettier": "^5.4.1",
48
51
  "expo-module-scripts": "^4.1.7",
49
- "expo-modules-core": "^2.4.0"
52
+ "expo-modules-core": "^2.4.0",
53
+ "jest": "^29.7.0",
54
+ "ts-jest": "^29.4.1"
50
55
  },
51
56
  "peerDependencies": {
52
57
  "expo": "*",
@@ -4,10 +4,10 @@ import {
4
4
  SubscriptionProductAndroid,
5
5
  } from './types/ExpoIapAndroid.types';
6
6
  import {
7
- ProductIos,
8
- ProductPurchaseIos,
9
- SubscriptionProductIos,
10
- } from './types/ExpoIapIos.types';
7
+ ProductIOS,
8
+ ProductPurchaseIOS,
9
+ SubscriptionProductIOS,
10
+ } from './types/ExpoIapIOS.types';
11
11
  import {NATIVE_ERROR_CODES} from './ExpoIapModule';
12
12
 
13
13
  export type ChangeEventPayload = {
@@ -43,29 +43,34 @@ export type AndroidPlatform = {platform: 'android'};
43
43
  // Platform-agnostic unified product types (public API)
44
44
  export type Product =
45
45
  | (ProductAndroid & AndroidPlatform)
46
- | (ProductIos & IosPlatform);
46
+ | (ProductIOS & IosPlatform);
47
47
 
48
48
  export type SubscriptionProduct =
49
49
  | (SubscriptionProductAndroid & AndroidPlatform)
50
- | (SubscriptionProductIos & IosPlatform);
50
+ | (SubscriptionProductIOS & IosPlatform);
51
51
 
52
52
  // ============================================================================
53
- // Legacy Types (For backward compatibility with useIap hook)
53
+ // Legacy Types (For backward compatibility with useIAP hook)
54
54
  // ============================================================================
55
55
 
56
56
  // Re-export platform-specific purchase types for legacy compatibility
57
57
  export type {ProductPurchaseAndroid} from './types/ExpoIapAndroid.types';
58
- export type {ProductPurchaseIos} from './types/ExpoIapIos.types';
58
+ export type {ProductPurchaseIOS} from './types/ExpoIapIOS.types';
59
+
60
+ /**
61
+ * @deprecated Use ProductPurchaseIOS instead. This alias will be removed in v3.0.0.
62
+ */
63
+ export type ProductPurchaseIos = ProductPurchaseIOS;
59
64
 
60
65
  // Union type for platform-specific purchase types (legacy support)
61
66
  export type ProductPurchase =
62
67
  | (ProductPurchaseAndroid & AndroidPlatform)
63
- | (ProductPurchaseIos & IosPlatform);
68
+ | (ProductPurchaseIOS & IosPlatform);
64
69
 
65
70
  // Union type for platform-specific subscription purchase types (legacy support)
66
71
  export type SubscriptionPurchase =
67
72
  | (ProductPurchaseAndroid & AndroidPlatform & {autoRenewingAndroid: boolean})
68
- | (ProductPurchaseIos & IosPlatform);
73
+ | (ProductPurchaseIOS & IosPlatform);
69
74
 
70
75
  export type Purchase = ProductPurchase | SubscriptionPurchase;
71
76
 
@@ -308,7 +313,7 @@ export interface UnifiedRequestPurchaseProps {
308
313
  readonly andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
309
314
  readonly appAccountToken?: string;
310
315
  readonly quantity?: number;
311
- readonly withOffer?: import('./types/ExpoIapIos.types').PaymentDiscount;
316
+ readonly withOffer?: import('./types/ExpoIapIOS.types').PaymentDiscount;
312
317
 
313
318
  // Android-specific properties (ignored on iOS)
314
319
  readonly obfuscatedAccountIdAndroid?: string;
@@ -323,12 +328,12 @@ export interface UnifiedRequestPurchaseProps {
323
328
  /**
324
329
  * iOS-specific purchase request parameters
325
330
  */
326
- export interface RequestPurchaseIosProps {
331
+ export interface RequestPurchaseIOSProps {
327
332
  readonly sku: string;
328
333
  readonly andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
329
334
  readonly appAccountToken?: string;
330
335
  readonly quantity?: number;
331
- readonly withOffer?: import('./types/ExpoIapIos.types').PaymentDiscount;
336
+ readonly withOffer?: import('./types/ExpoIapIOS.types').PaymentDiscount;
332
337
  }
333
338
 
334
339
  /**
@@ -359,7 +364,7 @@ export interface RequestSubscriptionAndroidProps
359
364
  * Allows clear separation of iOS and Android parameters
360
365
  */
361
366
  export interface RequestPurchasePropsByPlatforms {
362
- readonly ios?: RequestPurchaseIosProps;
367
+ readonly ios?: RequestPurchaseIOSProps;
363
368
  readonly android?: RequestPurchaseAndroidProps;
364
369
  }
365
370
 
@@ -367,7 +372,7 @@ export interface RequestPurchasePropsByPlatforms {
367
372
  * Modern platform-specific subscription request structure (v2.7.0+)
368
373
  */
369
374
  export interface RequestSubscriptionPropsByPlatforms {
370
- readonly ios?: RequestPurchaseIosProps;
375
+ readonly ios?: RequestPurchaseIOSProps;
371
376
  readonly android?: RequestSubscriptionAndroidProps;
372
377
  }
373
378
 
@@ -383,4 +388,13 @@ export type RequestPurchaseProps = RequestPurchasePropsByPlatforms;
383
388
  */
384
389
  export type RequestSubscriptionProps = RequestSubscriptionPropsByPlatforms;
385
390
 
391
+ // ============================================================================
392
+ // Deprecated Aliases for Backward Compatibility
393
+ // ============================================================================
394
+
395
+ /**
396
+ * @deprecated Use RequestPurchaseIOSProps instead. This alias will be removed in v3.0.0.
397
+ */
398
+ export type RequestPurchaseIosProps = RequestPurchaseIOSProps;
399
+
386
400
  // Note: Type guard functions are exported from index.ts to avoid conflicts
@@ -28,15 +28,16 @@ export const getActiveSubscriptions = async (
28
28
  const filteredPurchases = purchases.filter((purchase) => {
29
29
  // If specific IDs provided, filter by them
30
30
  if (subscriptionIds && subscriptionIds.length > 0) {
31
- if (!subscriptionIds.includes(purchase.id)) {
31
+ if (!subscriptionIds.includes(purchase.productId)) {
32
32
  return false;
33
33
  }
34
34
  }
35
35
 
36
36
  // Check if this purchase has subscription-specific fields
37
37
  const hasSubscriptionFields =
38
- ('expirationDateIos' in purchase && purchase.expirationDateIos) ||
39
- 'autoRenewingAndroid' in purchase;
38
+ ('expirationDateIOS' in purchase && purchase.expirationDateIOS) ||
39
+ 'autoRenewingAndroid' in purchase ||
40
+ ('environmentIOS' in purchase && purchase.environmentIOS === 'Sandbox');
40
41
 
41
42
  if (!hasSubscriptionFields) {
42
43
  return false;
@@ -44,19 +45,25 @@ export const getActiveSubscriptions = async (
44
45
 
45
46
  // Check if it's actually active
46
47
  if (Platform.OS === 'ios') {
47
- if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
48
- return purchase.expirationDateIos > currentTime;
48
+ if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
49
+ return purchase.expirationDateIOS > currentTime;
49
50
  }
50
- if (
51
- 'environmentIos' in purchase &&
52
- purchase.environmentIos === 'Sandbox'
53
- ) {
51
+ // For iOS purchases without expiration date (like Sandbox), we consider them active
52
+ // if they have the environmentIOS field and were created recently
53
+ if ('environmentIOS' in purchase && purchase.environmentIOS) {
54
54
  const dayInMs = 24 * 60 * 60 * 1000;
55
+ // If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)
55
56
  if (
56
- purchase.transactionDate &&
57
- currentTime - purchase.transactionDate < dayInMs
57
+ !('expirationDateIOS' in purchase) ||
58
+ !purchase.expirationDateIOS
58
59
  ) {
59
- return true;
60
+ if (
61
+ purchase.environmentIOS === 'Sandbox' &&
62
+ purchase.transactionDate &&
63
+ currentTime - purchase.transactionDate < dayInMs
64
+ ) {
65
+ return true;
66
+ }
60
67
  }
61
68
  }
62
69
  } else if (Platform.OS === 'android') {
@@ -70,26 +77,26 @@ export const getActiveSubscriptions = async (
70
77
  // Convert to ActiveSubscription format
71
78
  for (const purchase of filteredPurchases) {
72
79
  const subscription: ActiveSubscription = {
73
- productId: purchase.id,
80
+ productId: purchase.productId,
74
81
  isActive: true,
75
82
  };
76
83
 
77
84
  // Add platform-specific details
78
85
  if (Platform.OS === 'ios') {
79
- if ('expirationDateIos' in purchase && purchase.expirationDateIos) {
80
- const expirationDate = new Date(purchase.expirationDateIos);
86
+ if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
87
+ const expirationDate = new Date(purchase.expirationDateIOS);
81
88
  subscription.expirationDateIOS = expirationDate;
82
89
 
83
- // Calculate days until expiration
84
- const daysUntilExpiration = Math.floor(
85
- (purchase.expirationDateIos - currentTime) / (1000 * 60 * 60 * 24),
90
+ // Calculate days until expiration (round to nearest day)
91
+ const daysUntilExpiration = Math.round(
92
+ (purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24),
86
93
  );
87
94
  subscription.daysUntilExpirationIOS = daysUntilExpiration;
88
95
  subscription.willExpireSoon = daysUntilExpiration <= 7;
89
96
  }
90
97
 
91
- if ('environmentIos' in purchase) {
92
- subscription.environmentIOS = purchase.environmentIos;
98
+ if ('environmentIOS' in purchase) {
99
+ subscription.environmentIOS = purchase.environmentIOS;
93
100
  }
94
101
  } else if (Platform.OS === 'android') {
95
102
  if ('autoRenewingAndroid' in purchase) {
package/src/index.ts CHANGED
@@ -5,9 +5,9 @@ import {Platform} from 'react-native';
5
5
  // Internal modules
6
6
  import ExpoIapModule from './ExpoIapModule';
7
7
  import {
8
- isProductIos,
8
+ isProductIOS,
9
9
  validateReceiptIOS,
10
- deepLinkToSubscriptionsIos,
10
+ deepLinkToSubscriptionsIOS,
11
11
  } from './modules/ios';
12
12
  import {
13
13
  isProductAndroid,
@@ -28,13 +28,13 @@ import {
28
28
  SubscriptionPurchase,
29
29
  } from './ExpoIap.types';
30
30
  import {ProductPurchaseAndroid} from './types/ExpoIapAndroid.types';
31
- import {PaymentDiscount} from './types/ExpoIapIos.types';
31
+ import {PaymentDiscount} from './types/ExpoIapIOS.types';
32
32
 
33
33
  // Export all types
34
34
  export * from './ExpoIap.types';
35
35
  export * from './modules/android';
36
36
  export * from './modules/ios';
37
- export type {AppTransactionIOS} from './types/ExpoIapIos.types';
37
+ export type {AppTransactionIOS} from './types/ExpoIapIOS.types';
38
38
 
39
39
  // Export subscription helpers
40
40
  export {
@@ -135,7 +135,7 @@ export const getProducts = async (skus: string[]): Promise<Product[]> => {
135
135
  ios: async () => {
136
136
  const rawItems = await ExpoIapModule.getItems(skus);
137
137
  return rawItems.filter((item: unknown) => {
138
- if (!isProductIos(item)) return false;
138
+ if (!isProductIOS(item)) return false;
139
139
  return (
140
140
  typeof item === 'object' &&
141
141
  item !== null &&
@@ -169,7 +169,7 @@ export const getSubscriptions = async (
169
169
  ios: async () => {
170
170
  const rawItems = await ExpoIapModule.getItems(skus);
171
171
  return rawItems.filter((item: unknown) => {
172
- if (!isProductIos(item)) return false;
172
+ if (!isProductIOS(item)) return false;
173
173
  return (
174
174
  typeof item === 'object' &&
175
175
  item !== null &&
@@ -236,7 +236,7 @@ export const requestProducts = async ({
236
236
  if (Platform.OS === 'ios') {
237
237
  const rawItems = await ExpoIapModule.getItems(skus);
238
238
  const filteredItems = rawItems.filter((item: unknown) => {
239
- if (!isProductIos(item)) return false;
239
+ if (!isProductIOS(item)) return false;
240
240
  return (
241
241
  typeof item === 'object' &&
242
242
  item !== null &&
@@ -341,7 +341,7 @@ export const getAvailablePurchases = ({
341
341
  }) || (() => Promise.resolve([]))
342
342
  )();
343
343
 
344
- const offerToRecordIos = (
344
+ const offerToRecordIOS = (
345
345
  offer: PaymentDiscount | undefined,
346
346
  ): Record<keyof PaymentDiscount, string> | undefined => {
347
347
  if (!offer) return undefined;
@@ -436,7 +436,7 @@ export const requestPurchase = (
436
436
  } = normalizedRequest;
437
437
 
438
438
  return (async () => {
439
- const offer = offerToRecordIos(withOffer);
439
+ const offer = offerToRecordIOS(withOffer);
440
440
  const purchase = await ExpoIapModule.buyProduct(
441
441
  sku,
442
442
  andDangerouslyFinishTransactionAutomaticallyIOS,
@@ -683,7 +683,7 @@ export const deepLinkToSubscriptions = (options: {
683
683
  packageNameAndroid?: string;
684
684
  }): Promise<void> => {
685
685
  if (Platform.OS === 'ios') {
686
- return deepLinkToSubscriptionsIos();
686
+ return deepLinkToSubscriptionsIOS();
687
687
  }
688
688
 
689
689
  if (Platform.OS === 'android') {
@@ -710,5 +710,5 @@ export const deepLinkToSubscriptions = (options: {
710
710
  return Promise.reject(new Error(`Unsupported platform: ${Platform.OS}`));
711
711
  };
712
712
 
713
- export * from './useIap';
713
+ export * from './useIAP';
714
714
  export * from './utils/errorMapping';