expo-iap 3.0.7 → 3.1.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 (52) hide show
  1. package/CLAUDE.md +14 -2
  2. package/CONTRIBUTING.md +19 -0
  3. package/README.md +18 -6
  4. package/android/build.gradle +24 -1
  5. package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
  6. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
  7. package/build/index.d.ts +32 -111
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +198 -243
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +7 -12
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +15 -12
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +35 -36
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +101 -35
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/types.d.ts +107 -82
  20. package/build/types.d.ts.map +1 -1
  21. package/build/types.js +1 -0
  22. package/build/types.js.map +1 -1
  23. package/build/useIAP.d.ts +7 -12
  24. package/build/useIAP.d.ts.map +1 -1
  25. package/build/useIAP.js +49 -23
  26. package/build/useIAP.js.map +1 -1
  27. package/build/utils/errorMapping.d.ts +32 -23
  28. package/build/utils/errorMapping.d.ts.map +1 -1
  29. package/build/utils/errorMapping.js +117 -22
  30. package/build/utils/errorMapping.js.map +1 -1
  31. package/ios/ExpoIap.podspec +3 -2
  32. package/ios/ExpoIapHelper.swift +96 -0
  33. package/ios/ExpoIapLog.swift +127 -0
  34. package/ios/ExpoIapModule.swift +218 -340
  35. package/openiap-versions.json +5 -0
  36. package/package.json +2 -2
  37. package/plugin/build/withIAP.js +6 -4
  38. package/plugin/src/withIAP.ts +14 -4
  39. package/scripts/update-types.mjs +20 -1
  40. package/src/index.ts +280 -356
  41. package/src/modules/android.ts +25 -23
  42. package/src/modules/ios.ts +138 -48
  43. package/src/types.ts +139 -91
  44. package/src/useIAP.ts +91 -58
  45. package/src/utils/errorMapping.ts +203 -23
  46. package/.copilot-instructions.md +0 -321
  47. package/.cursorrules +0 -321
  48. package/build/purchase-error.d.ts +0 -67
  49. package/build/purchase-error.d.ts.map +0 -1
  50. package/build/purchase-error.js +0 -166
  51. package/build/purchase-error.js.map +0 -1
  52. package/src/purchase-error.ts +0 -265
package/CLAUDE.md CHANGED
@@ -8,6 +8,11 @@
8
8
  - Subject must be imperative, lowercase, without a trailing period, and roughly 50 characters
9
9
  - Wrap commit body lines near 72 characters and include footers such as `BREAKING CHANGE:` or `Closes #123` when needed
10
10
 
11
+ ## Tooling & Package Management
12
+
13
+ - **Use Bun exclusively.** Run installs with `bun install`, scripts with `bun run <script>`, add deps via `bun add` / `bun add -d`.
14
+ - Do **not** suggest or create `package-lock.json` or `yarn.lock`; `bun.lock` is the single source of truth.
15
+
11
16
  ## Expo-Specific Guidelines
12
17
 
13
18
  ### iOS Pod Configuration
@@ -38,6 +43,11 @@ Before committing any changes:
38
43
 
39
44
  ### Platform-Specific Naming Conventions
40
45
 
46
+ #### Function Naming
47
+
48
+ - Functions that only operate on one platform must carry the suffix: `nameIOS` or `nameAndroid` (e.g. `getStorefrontIOS`, `deepLinkToSubscriptionsAndroid`).
49
+ - Cross-platform helpers should expose a single name and branch internally via `Platform.select` or equivalent.
50
+
41
51
  #### Field Naming
42
52
 
43
53
  - **iOS-related fields**: Use `IOS` suffix (e.g., `displayNameIOS`, `discountsIOS`, `introductoryPriceIOS`)
@@ -72,6 +82,8 @@ The library follows the OpenIAP type specifications with platform-specific exten
72
82
 
73
83
  > **Important:** `src/types.ts` is generated from the OpenIAP schema. Never edit this file manually or commit hand-written changes. After updating any `*.graphql` schema, run `bun run generate:types` (or the equivalent script in your package manager) to refresh the file.
74
84
 
85
+ - Whenever you need Request/Params/Result types in the JS API surface (`src/index.ts`, hooks, modules, examples), import them directly from the generated `src/types.ts` (e.g., `MutationRequestPurchaseArgs`, `QueryFetchProductsArgs`). Bind exported functions with the generated `QueryField` / `MutationField` helpers so their signatures stay in lockstep with `types.ts` instead of redefining ad-hoc unions like `ProductTypeInput`.
86
+
75
87
  ### React/JSX Conventions
76
88
 
77
89
  - **Conditional Rendering**: Use ternary operator with null instead of logical AND
@@ -81,7 +93,7 @@ The library follows the OpenIAP type specifications with platform-specific exten
81
93
  ### Hook API Semantics (useIAP)
82
94
 
83
95
  - Inside the `useIAP` hook, most methods return `Promise<void>` and update internal state. Do not design examples or implementations that expect data from these methods.
84
- - Examples: `fetchProducts`, `requestProducts`, `getProducts`/`getSubscriptions` (deprecated helpers), `requestPurchase`, `getAvailablePurchases`.
96
+ - Examples: `fetchProducts`, `requestPurchase`, `getAvailablePurchases`.
85
97
  - After calling, consume state from the hook: `products`, `subscriptions`, `availablePurchases`, etc.
86
98
  - Defined exceptions that DO return values in the hook:
87
99
  - `getActiveSubscriptions(subscriptionIds?) => Promise<ActiveSubscription[]>` (also updates `activeSubscriptions` state)
@@ -90,7 +102,7 @@ The library follows the OpenIAP type specifications with platform-specific exten
90
102
 
91
103
  ### API Method Naming
92
104
 
93
- - Functions that depend on event results should use `request` prefix (e.g., `requestPurchase`, `requestSubscription`)
105
+ - Functions that depend on event results should use `request` prefix (e.g., `requestPurchase`)
94
106
  - Follow OpenIAP terminology: <https://www.openiap.dev/docs/apis#terminology>
95
107
  - Do not use generic prefixes like `get`, `find` - refer to the official terminology
96
108
 
package/CONTRIBUTING.md CHANGED
@@ -8,6 +8,7 @@ Thank you for your interest in contributing to expo-iap! This guide will help yo
8
8
  - [Package Manager](#-package-manager)
9
9
  - [Running the Example App](#-running-the-example-app)
10
10
  - [Development Guidelines](#-development-guidelines)
11
+ - [OpenIAP Version Management](#-openiap-version-management)
11
12
  - [Testing](#-testing)
12
13
  - [Code Style](#-code-style)
13
14
  - [Submitting Changes](#-submitting-changes)
@@ -223,6 +224,24 @@ The generated TypeScript definitions in `src/types.ts` come from the [`openiap-g
223
224
 
224
225
  Always ensure the repository builds and tests succeed after regenerating the types.
225
226
 
227
+ ## 🔢 OpenIAP Version Management
228
+
229
+ All native and type-generation version numbers are sourced from `openiap-versions.json` at the repository root:
230
+
231
+ - `apple` → iOS Pod dependency (`ios/ExpoIap.podspec`).
232
+ - `google` → Android artifact (`android/build.gradle`, Expo config plugin).
233
+ - `gql` → GraphQL type generator (`scripts/update-types.mjs`).
234
+
235
+ When bumping dependencies:
236
+
237
+ 1. Update the relevant fields in `openiap-versions.json`.
238
+ 2. For iOS changes, run `cd ios && pod install` (and commit the Pod.lock if required by the workflow).
239
+ 3. For Android, re-run Gradle (`bun run android`) so the new artifact is pulled down.
240
+ 4. When the `gql` value changes, run `bun run generate:types` to refresh `src/types.ts`.
241
+ 5. Commit the updated JSON, regenerated files, and any resulting lockfile changes together.
242
+
243
+ If the JSON file is missing or malformed, build scripts (Gradle, Podspec, the type generator) will fail fast — fix the JSON rather than hard-coding version strings in multiple locations.
244
+
226
245
  ### Development Workflow
227
246
 
228
247
  1. **Before starting work**:
package/README.md CHANGED
@@ -5,9 +5,12 @@
5
5
 
6
6
  [![Version](http://img.shields.io/npm/v/expo-iap.svg?style=flat-square)](https://npmjs.org/package/expo-iap) [![Download](http://img.shields.io/npm/dm/expo-iap.svg?style=flat-square)](https://npmjs.org/package/expo-iap) [![CI](https://github.com/hyochan/expo-iap/actions/workflows/ci.yml/badge.svg)](https://github.com/hyochan/expo-iap/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/hyochan/expo-iap/graph/badge.svg?token=47VMTY5NyM)](https://codecov.io/gh/hyochan/expo-iap) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhyochan%2Fexpo-iap.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhyochan%2Fexpo-iap?ref=badge_shield&issueType=license)
7
7
 
8
- In app purchase module in [Expo](https://docs.expo.dev/guides/in-app-purchases) that conforms to the [Open IAP specification](https://openiap.dev)
9
-
10
- <a href="https://openiap.dev"><img src="https://openiap.dev/logo.png" alt="Open IAP" height="40" /></a>
8
+ Expo IAP is a powerful in-app purchase solution for Expo and React Native applications that conforms to the Open IAP specification. It provides a unified API for handling in-app purchases across iOS and Android platforms with comprehensive error handling and modern TypeScript support.
9
+
10
+ If you're shipping an app with expo-iap, we’d love to hear about it—please share your product and feedback in [Who's using Expo IAP?](https://github.com/hyochan/expo-iap/discussions/143). Community stories help us keep improving the ecosystem.
11
+
12
+ <a href="https://openiap.dev"><img src="https://openiap.dev/logo.png" alt="Open IAP" height="40" /></a>
13
+
11
14
  </div>
12
15
 
13
16
  ## 📚 Documentation
@@ -44,7 +47,7 @@ npx expo install expo-iap
44
47
  "expo-build-properties",
45
48
  {
46
49
  "android": {
47
- "kotlinVersion": "2.0.21"
50
+ "kotlinVersion": "2.1.20"
48
51
  }
49
52
  }
50
53
  ]
@@ -53,6 +56,8 @@ npx expo install expo-iap
53
56
  }
54
57
  ```
55
58
 
59
+ If you're targeting Expo SDK 54 or newer, please confirm whether this manual override is still required and share findings with the community at [github.com/hyochan/expo-iap/discussions](https://github.com/hyochan/expo-iap/discussions).
60
+
56
61
  ## Contributing
57
62
 
58
63
  We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details on:
@@ -67,14 +72,21 @@ For detailed usage examples and error handling, see the [documentation](https://
67
72
 
68
73
  > Sharing your thoughts—any feedback would be greatly appreciated!
69
74
 
70
- ## Sponsors
75
+ ## Our Sponsors
71
76
 
72
77
  💼 **[View Our Sponsors](https://openiap.dev/sponsors)**
73
78
 
79
+ We're building the OpenIAP ecosystem—defining the spec at [openiap.dev](https://www.openiap.dev), maintaining [openiap-gql](https://github.com/hyodotdev/openiap-gql) for the shared type system, and shipping platform SDKs like [openiap-apple](https://github.com/hyodotdev/openiap-apple) and [openiap-google](https://github.com/hyodotdev/openiap-google) that power [expo-iap](https://github.com/hyochan/expo-iap), [flutter_inapp_purchase](https://github.com/hyochan/flutter_inapp_purchase), React Native, and [kmp-iap](https://github.com/hyochan/kmp-iap). The work so far has focused on untangling fragmented APIs; the next milestone is a streamlined purchase flow: `initConnection → fetchProducts → requestPurchase → (server receipt validation) → finishTransaction`.
80
+
81
+ Your sponsorship helps ensure developers across platforms, OS, and frameworks can implement in-app purchases without headaches. It also fuels new plugins, payment systems, and partner integrations already being explored in the OpenIAP community. Sponsors receive shout-outs in every release and can request tailored support depending on tier. If you’re interested—or have rollout feedback to share—you can view sponsorship options at [openiap.dev/sponsors](https://openiap.dev/sponsors).
82
+
74
83
  ### <p style="color: rgb(255, 182, 193);">Angel</p>
75
84
 
76
85
  <a href="https://meta.com">
77
- <img width="600" alt="courier_dot_com" src="https://static.xx.fbcdn.net/rsrc.php/y3/r/y6QsbGgc866.svg" />
86
+ <div style="display: inline-flex; flex-direction: column; align-items: center; gap: 0.25rem; padding: 0.75rem 1rem; border-radius: 12px; background: rgba(212, 165, 116, 0.12);">
87
+ <img alt="Meta" src="https://www.openiap.dev/meta.svg" style="width: 120px;" />
88
+ <span style="font-size: 0.85rem; font-weight: 600; color: rgb(107, 78, 61); text-align: center; width: 100%;">Meta</span>
89
+ </div>
78
90
  </a>
79
91
 
80
92
  ## Past Supporters
@@ -1,3 +1,5 @@
1
+ import groovy.json.JsonSlurper
2
+
1
3
  apply plugin: 'com.android.library'
2
4
  apply plugin: 'kotlin-android'
3
5
 
@@ -10,6 +12,27 @@ applyKotlinExpoModulesCorePlugin()
10
12
  useCoreDependencies()
11
13
  useExpoPublishing()
12
14
 
15
+ def resolveOpenIapVersionsFile() {
16
+ def candidates = [
17
+ new File(projectDir.parentFile, 'openiap-versions.json'),
18
+ new File(rootDir.parentFile ?: rootDir, 'openiap-versions.json'),
19
+ new File(rootProject.projectDir.parentFile ?: rootProject.projectDir, 'openiap-versions.json')
20
+ ]
21
+ return candidates.find { it.exists() }
22
+ }
23
+
24
+ def openiapVersionsFile = resolveOpenIapVersionsFile()
25
+ if (openiapVersionsFile == null) {
26
+ throw new GradleException("expo-iap: Unable to locate openiap-versions.json")
27
+ }
28
+
29
+ def openiapVersions = new JsonSlurper().parse(openiapVersionsFile)
30
+ def googleVersion = (openiapVersions instanceof Map) ? openiapVersions.google : null
31
+ if (!(googleVersion instanceof String) || !googleVersion.trim()) {
32
+ throw new GradleException("expo-iap: 'google' version missing or invalid in openiap-versions.json")
33
+ }
34
+ def googleVersionString = googleVersion.trim()
35
+
13
36
  // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
14
37
  // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
15
38
  // Most of the time, you may like to manage the Android SDK versions yourself.
@@ -58,6 +81,6 @@ dependencies {
58
81
  implementation project(":openiap-google")
59
82
  } else {
60
83
  // Fallback to published artifact when local project isn't linked
61
- implementation "io.github.hyochan.openiap:openiap-google:1.1.11"
84
+ implementation "io.github.hyochan.openiap:openiap-google:${googleVersionString}"
62
85
  }
63
86
  }
@@ -0,0 +1,69 @@
1
+ package expo.modules.iap
2
+
3
+ import android.util.Log
4
+ import org.json.JSONArray
5
+ import org.json.JSONObject
6
+
7
+ internal object ExpoIapLog {
8
+ private const val TAG = "ExpoIap"
9
+
10
+ fun payload(
11
+ name: String,
12
+ payload: Any?,
13
+ ) {
14
+ debug("$name payload: ${stringify(payload)}")
15
+ }
16
+
17
+ fun result(
18
+ name: String,
19
+ value: Any?,
20
+ ) {
21
+ debug("$name result: ${stringify(value)}")
22
+ }
23
+
24
+ fun failure(
25
+ name: String,
26
+ error: Throwable,
27
+ ) {
28
+ Log.e(TAG, "$name failed: ${error.localizedMessage}", error)
29
+ }
30
+
31
+ fun debug(message: String) {
32
+ Log.d(TAG, message)
33
+ }
34
+
35
+ private fun stringify(value: Any?): String {
36
+ val sanitized = sanitize(value) ?: return "null"
37
+ return when (sanitized) {
38
+ is String -> sanitized
39
+ is Number, is Boolean -> sanitized.toString()
40
+ is Map<*, *> -> JSONObject(sanitized).toString()
41
+ is List<*> -> JSONArray(sanitized).toString()
42
+ else -> sanitized.toString()
43
+ }
44
+ }
45
+
46
+ private fun sanitize(value: Any?): Any? {
47
+ if (value == null) return null
48
+
49
+ return when (value) {
50
+ is Map<*, *> -> sanitizeMap(value)
51
+ is List<*> -> value.mapNotNull { sanitize(it) }
52
+ is Array<*> -> value.mapNotNull { sanitize(it) }
53
+ else -> value
54
+ }
55
+ }
56
+
57
+ private fun sanitizeMap(source: Map<*, *>): Map<String, Any?> {
58
+ val sanitized = linkedMapOf<String, Any?>()
59
+ for ((rawKey, rawValue) in source) {
60
+ val key = rawKey as? String ?: continue
61
+ if (key.lowercase().contains("token")) {
62
+ sanitized[key] = "hidden"
63
+ continue
64
+ }
65
+ sanitized[key] = sanitize(rawValue)
66
+ }
67
+ return sanitized
68
+ }
69
+ }