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.
- package/CLAUDE.md +14 -2
- package/CONTRIBUTING.md +19 -0
- package/README.md +18 -6
- package/android/build.gradle +24 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
- package/build/index.d.ts +32 -111
- package/build/index.d.ts.map +1 -1
- package/build/index.js +198 -243
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +7 -12
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +15 -12
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +35 -36
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +101 -35
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +107 -82
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -0
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +7 -12
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +49 -23
- package/build/useIAP.js.map +1 -1
- package/build/utils/errorMapping.d.ts +32 -23
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +117 -22
- package/build/utils/errorMapping.js.map +1 -1
- package/ios/ExpoIap.podspec +3 -2
- package/ios/ExpoIapHelper.swift +96 -0
- package/ios/ExpoIapLog.swift +127 -0
- package/ios/ExpoIapModule.swift +218 -340
- package/openiap-versions.json +5 -0
- package/package.json +2 -2
- package/plugin/build/withIAP.js +6 -4
- package/plugin/src/withIAP.ts +14 -4
- package/scripts/update-types.mjs +20 -1
- package/src/index.ts +280 -356
- package/src/modules/android.ts +25 -23
- package/src/modules/ios.ts +138 -48
- package/src/types.ts +139 -91
- package/src/useIAP.ts +91 -58
- package/src/utils/errorMapping.ts +203 -23
- package/.copilot-instructions.md +0 -321
- package/.cursorrules +0 -321
- package/build/purchase-error.d.ts +0 -67
- package/build/purchase-error.d.ts.map +0 -1
- package/build/purchase-error.js +0 -166
- package/build/purchase-error.js.map +0 -1
- 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`, `
|
|
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
|
|
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
|
[](https://npmjs.org/package/expo-iap) [](https://npmjs.org/package/expo-iap) [](https://github.com/hyochan/expo-iap/actions/workflows/ci.yml) [](https://codecov.io/gh/hyochan/expo-iap) [](https://app.fossa.com/projects/git%2Bgithub.com%2Fhyochan%2Fexpo-iap?ref=badge_shield&issueType=license)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
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
|
-
<
|
|
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
|
package/android/build.gradle
CHANGED
|
@@ -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
|
|
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
|
+
}
|