expo-iap 5.6.3 → 5.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 (68) hide show
  1. package/android/build.gradle +10 -15
  2. package/android/openiap-store.gradle +365 -0
  3. package/android/src/main/java/expo/modules/iap/ExpoIapHelper.kt +2 -5
  4. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +2 -6
  5. package/build/ExpoIapModule.d.ts +51 -6
  6. package/build/ExpoIapModule.d.ts.map +1 -1
  7. package/build/ExpoIapModule.js +12 -8
  8. package/build/ExpoIapModule.js.map +1 -1
  9. package/build/index.d.ts +22 -44
  10. package/build/index.d.ts.map +1 -1
  11. package/build/index.js +26 -54
  12. package/build/index.js.map +1 -1
  13. package/build/kit-api.d.ts +10 -10
  14. package/build/kit-api.d.ts.map +1 -1
  15. package/build/kit-api.js +11 -35
  16. package/build/kit-api.js.map +1 -1
  17. package/build/modules/android.d.ts +4 -6
  18. package/build/modules/android.d.ts.map +1 -1
  19. package/build/modules/android.js +9 -15
  20. package/build/modules/android.js.map +1 -1
  21. package/build/modules/ios.d.ts +11 -29
  22. package/build/modules/ios.d.ts.map +1 -1
  23. package/build/modules/ios.js +13 -36
  24. package/build/modules/ios.js.map +1 -1
  25. package/build/onside/index.d.ts +7 -7
  26. package/build/onside/index.d.ts.map +1 -1
  27. package/build/onside/index.js +7 -8
  28. package/build/onside/index.js.map +1 -1
  29. package/build/useIAP.d.ts +3 -6
  30. package/build/useIAP.d.ts.map +1 -1
  31. package/build/useIAP.js +11 -32
  32. package/build/useIAP.js.map +1 -1
  33. package/build/utils/debug.d.ts +3 -3
  34. package/build/utils/debug.d.ts.map +1 -1
  35. package/build/utils/debug.js +3 -13
  36. package/build/utils/debug.js.map +1 -1
  37. package/build/utils/errorMapping.d.ts +4 -12
  38. package/build/utils/errorMapping.d.ts.map +1 -1
  39. package/build/utils/errorMapping.js +4 -12
  40. package/build/utils/errorMapping.js.map +1 -1
  41. package/build/utils/restorePurchases.d.ts.map +1 -1
  42. package/build/utils/restorePurchases.js +3 -4
  43. package/build/utils/restorePurchases.js.map +1 -1
  44. package/ios/ExpoIap.podspec +4 -7
  45. package/ios/ExpoIapHelper.swift +2 -3
  46. package/ios/ExpoIapModule.swift +2 -3
  47. package/openiap-versions.json +2 -2
  48. package/package.json +2 -2
  49. package/plugin/build/withIAP.d.ts +16 -5
  50. package/plugin/build/withIAP.js +181 -162
  51. package/plugin/build/withLocalOpenIAP.d.ts +15 -12
  52. package/plugin/build/withLocalOpenIAP.js +131 -118
  53. package/plugin/build/withVega.d.ts +7 -0
  54. package/plugin/build/withVega.js +25 -5
  55. package/plugin/src/expoConfig.augmentation.d.ts +22 -11
  56. package/plugin/src/withIAP.ts +241 -222
  57. package/plugin/src/withLocalOpenIAP.ts +214 -163
  58. package/plugin/src/withVega.ts +28 -4
  59. package/src/ExpoIapModule.ts +136 -18
  60. package/src/index.ts +28 -65
  61. package/src/kit-api.ts +21 -46
  62. package/src/modules/android.ts +11 -26
  63. package/src/modules/ios.ts +21 -44
  64. package/src/onside/index.ts +7 -9
  65. package/src/useIAP.ts +14 -38
  66. package/src/utils/debug.ts +3 -14
  67. package/src/utils/errorMapping.ts +4 -12
  68. package/src/utils/restorePurchases.ts +3 -4
@@ -53,12 +53,9 @@ if (!(googleVersion instanceof String) || !googleVersion.trim()) {
53
53
  def googleVersionString = googleVersion.trim()
54
54
  apply from: project.file('openiap-android-sdk.gradle')
55
55
 
56
- def horizonEnabled = project.findProperty('horizonEnabled')?.toBoolean() ?: false
57
- def fireOsEnabled = project.findProperty('fireOsEnabled')?.toBoolean() ?: false
58
- if (horizonEnabled && fireOsEnabled) {
59
- throw new GradleException("expo-iap: horizonEnabled and fireOsEnabled cannot both be true")
60
- }
61
- def openiapFlavor = fireOsEnabled ? 'amazon' : (horizonEnabled ? 'horizon' : 'play')
56
+ // Store selection: explicit property > task flavor > connected debug device > play.
57
+ apply from: project.file('openiap-store.gradle')
58
+ def openiapFlavor = openIapResolveStore('expo-iap').store
62
59
 
63
60
  // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
64
61
  // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
@@ -111,17 +108,15 @@ dependencies {
111
108
  testImplementation "junit:junit:4.13.2"
112
109
  testImplementation "org.json:json:20260719"
113
110
 
114
- // Use OpenIAP Google module only; avoid direct BillingClient dependency
111
+ // `api`, so an app's own native code can use dev.hyo.openiap through this module.
115
112
  if (findProject(":openiap-google") != null) {
116
- implementation project(":openiap-google")
117
- } else if (fireOsEnabled) {
118
- // Use openiap-google-amazon for Fire OS when fireOsEnabled is true
119
- implementation "io.github.hyochan.openiap:openiap-google-amazon:${googleVersionString}"
120
- } else if (horizonEnabled) {
121
- // Use openiap-google-horizon for Meta Quest when horizonEnabled is true
122
- implementation "io.github.hyochan.openiap:openiap-google-horizon:${googleVersionString}"
113
+ api project(":openiap-google")
114
+ } else if (openiapFlavor == 'amazon') {
115
+ api "io.github.hyochan.openiap:openiap-google-amazon:${googleVersionString}"
116
+ } else if (openiapFlavor == 'horizon') {
117
+ api "io.github.hyochan.openiap:openiap-google-horizon:${googleVersionString}"
123
118
  } else {
124
119
  // Fallback to published artifact when local project isn't linked
125
- implementation "io.github.hyochan.openiap:openiap-google:${googleVersionString}"
120
+ api "io.github.hyochan.openiap:openiap-google:${googleVersionString}"
126
121
  }
127
122
  }
@@ -0,0 +1,365 @@
1
+ // OpenIAP store resolver. Framework wrappers symlink this file into android/
2
+ // and publish it as a real file.
3
+ // Apply it, then call openIapResolveStore('<label>'), or ('<label>', [allowNone: true])
4
+ // where the library can build without a store SDK.
5
+ //
6
+ // First match wins:
7
+ // 1. explicit openiapStore=<store> (-P, ORG_GRADLE_PROJECT_openiapStore, gradle.properties).
8
+ // The legacy horizonEnabled, fireOsEnabled and openiapPlatform=none still work.
9
+ // 2. variant a requested task names a store flavor (assembleHorizonRelease).
10
+ // 3. device debug builds only: the Quest or Fire device that ANDROID_SERIAL
11
+ // names, or the only one connected.
12
+ // 4. play
13
+ // Two signals naming different stores fail the build. Only spelled-out task names
14
+ // are read; a task that builds another store (an abbreviation such as aHR) fails
15
+ // once the task graph is ready.
16
+
17
+ ext.openIapStoreAliases = [
18
+ play: 'play', google: 'play', gplay: 'play', googleplay: 'play', 'google-play': 'play', gms: 'play',
19
+ horizon: 'horizon', meta: 'horizon', quest: 'horizon',
20
+ amazon: 'amazon', fire: 'amazon', fireos: 'amazon', 'fire-os': 'amazon',
21
+ none: 'none', auto: 'auto',
22
+ ]
23
+
24
+ ext.openIapNormalizeStore = { Object value, String label ->
25
+ def key = value?.toString()?.trim()?.toLowerCase(Locale.ROOT)
26
+ if (!key) {
27
+ return null
28
+ }
29
+ def store = openIapStoreAliases[key]
30
+ if (store == null) {
31
+ throw new GradleException("${label}: unknown openiapStore '${value}'; use play, horizon, amazon, auto, or none where the library allows it")
32
+ }
33
+ return store
34
+ }
35
+
36
+ // assembleHorizonRelease -> [assemble, horizon, release]
37
+ ext.openIapTaskTokens = { String taskPath ->
38
+ def name = taskPath.substring(taskPath.lastIndexOf(':') + 1)
39
+ return name.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|[^A-Za-z0-9]+/).collect { it.toLowerCase(Locale.ROOT) }.findAll { it }
40
+ }
41
+
42
+ // The key that set the opt-out, for error messages.
43
+ ext.openIapOptOutKey = {
44
+ def explicit = project.findProperty('openiapStore')?.toString()?.trim()?.toLowerCase(Locale.ROOT)
45
+ return explicit == 'none' ? 'openiapStore=none' : 'openiapPlatform=none'
46
+ }
47
+
48
+ ext.openIapExplicitStore = { String label, boolean allowNone ->
49
+ def explicit = openIapNormalizeStore(project.findProperty('openiapStore'), label)
50
+ def horizon = project.findProperty('horizonEnabled')?.toString()?.toBoolean() ?: false
51
+ def fireOs = project.findProperty('fireOsEnabled')?.toString()?.toBoolean() ?: false
52
+ def platform = project.findProperty('openiapPlatform')?.toString()?.trim()?.toLowerCase(Locale.ROOT)
53
+ if (platform != null && platform != 'none') {
54
+ throw new GradleException("${label}: openiapPlatform only supports the opt-out value 'none'; use openiapStore to pick a store")
55
+ }
56
+ if (horizon && fireOs) {
57
+ throw new GradleException("${label}: horizonEnabled and fireOsEnabled cannot both be true")
58
+ }
59
+ if (platform == 'none' && (horizon || fireOs)) {
60
+ throw new GradleException("${label}: openiapPlatform=none conflicts with legacy store flags")
61
+ }
62
+ def (legacyKey, legacy) = platform == 'none' ? ['openiapPlatform=none', 'none']
63
+ : fireOs ? ['fireOsEnabled=true', 'amazon']
64
+ : horizon ? ['horizonEnabled=true', 'horizon'] : [null, null]
65
+ if (legacy != null) {
66
+ logger.warn("${label}: horizonEnabled, fireOsEnabled, and openiapPlatform are deprecated; use openiapStore=${legacy}")
67
+ }
68
+ def pin = explicit == 'auto' ? null : explicit
69
+ if (pin != null && legacy != null && legacy != pin) {
70
+ throw new GradleException("${label}: openiapStore=${pin} conflicts with ${legacyKey}")
71
+ }
72
+ def store = pin ?: legacy
73
+ if (store == 'none' && !allowNone) {
74
+ throw new GradleException("${label}: ${openIapOptOutKey()} is not supported by this library")
75
+ }
76
+ return store
77
+ }
78
+
79
+ // Task options that take a separate value, from Gradle's `help --task` output.
80
+ // An unlisted option's value may be read as a task; the graph check then fails
81
+ // unless the value also names a task the build runs.
82
+ ext.openIapValueOptions = [
83
+ '-b', '-c', '-g', '-I', '-p',
84
+ '--args', '--component', '--configuration', '--console', '--dependency',
85
+ '--distribution-type', '--dsl', '--format',
86
+ '--gradle-distribution-sha256-sum', '--gradle-distribution-url',
87
+ '--gradle-version', '--group', '--groups', '--include-build',
88
+ '--insecure-protocol', '--java-version', '--jvm-vendor', '--jvm-version',
89
+ '--max-workers', '--network-timeout', '--package', '--project-cache-dir',
90
+ '--project-dir', '--project-name', '--property', '--task', '--test-framework',
91
+ '--tests', '--type', '--variant',
92
+ ] as Set
93
+
94
+ // Gradle flattens an invocation into one list, options included, so drop each
95
+ // option and its value: `--tests *AmazonTest*` is not an Amazon build.
96
+ ext.openIapRequestedTasks = {
97
+ def names = []
98
+ def previous = null
99
+ def requested = gradle.startParameter.taskNames
100
+ if (requested.isEmpty()) {
101
+ requested = rootProject.defaultTasks ?: []
102
+ }
103
+ requested.each { String token ->
104
+ if (!token) {
105
+ return
106
+ }
107
+ if (token.startsWith('-')) {
108
+ previous = token
109
+ return
110
+ }
111
+ if (previous != null && openIapValueOptions.contains(previous)) {
112
+ previous = token
113
+ return
114
+ }
115
+ names << token
116
+ previous = token
117
+ }
118
+ return names
119
+ }
120
+
121
+ // Variant detection reads every requested task: keep whole-token
122
+ // play/horizon/amazon out of custom task names.
123
+ ext.openIapTaskStore = { String label ->
124
+ def named = openIapRequestedTasks().collectMany { openIapTaskTokens(it) }
125
+ .findAll { it in ['play', 'horizon', 'amazon'] } as LinkedHashSet
126
+ // Two stores in one build: refuse rather than guess.
127
+ if (named.size() > 1) {
128
+ throw new GradleException("${label}: cannot tell which store the requested tasks '${openIapRequestedTasks().join(' ')}' select (${named.join(', ')}); build one store per invocation, spell the tasks out, or set openiapStore")
129
+ }
130
+ return named ? named.first() : null
131
+ }
132
+
133
+ // `clean assembleDebug` is a debug build; any release task makes it a release
134
+ // build, which never depends on the attached device.
135
+ ext.openIapBuildTypes = { String taskPath ->
136
+ def types = [] as LinkedHashSet
137
+ openIapTaskTokens(taskPath).each { token ->
138
+ if (token in ['debug', 'release']) {
139
+ types << token
140
+ }
141
+ }
142
+ if (types) {
143
+ return types
144
+ }
145
+ // `aD` is assembleDebug, so the device rule has to see it as one.
146
+ def name = taskPath.substring(taskPath.lastIndexOf(':') + 1)
147
+ def segments = name.split(/(?=[A-Z])/).collect { it.toLowerCase(Locale.ROOT) }.findAll { it }
148
+ segments.drop(1).each { segment ->
149
+ ['debug', 'release'].findAll { it.startsWith(segment) }.each { types << it }
150
+ }
151
+ return types
152
+ }
153
+
154
+ ext.openIapDebugBuild = {
155
+ def names = openIapRequestedTasks()
156
+ if (!names) {
157
+ return false
158
+ }
159
+ def types = names.collectMany { openIapBuildTypes(it) } as LinkedHashSet
160
+ return types.contains('debug') && !types.contains('release')
161
+ }
162
+
163
+ // Gradle resolves a typed name to real tasks: an exact, case-insensitive match
164
+ // when one exists, otherwise a unique prefix or a camelCase abbreviation.
165
+ ext.openIapSelectedTasks = { graph, String request ->
166
+ def wanted = request.substring(request.lastIndexOf(':') + 1)
167
+ if (!wanted) {
168
+ return []
169
+ }
170
+ def lower = wanted.toLowerCase(Locale.ROOT)
171
+ def names = graph.allTasks.collect { it.name } as LinkedHashSet
172
+ def exact = names.findAll { it.toLowerCase(Locale.ROOT) == lower }
173
+ if (exact) {
174
+ return exact
175
+ }
176
+ def segments = wanted.split(/(?=[A-Z])/).findAll { it }
177
+ return names.findAll { String name ->
178
+ if (name.toLowerCase(Locale.ROOT).startsWith(lower)) {
179
+ return true
180
+ }
181
+ def taskSegments = name.split(/(?=[A-Z])/).findAll { it }
182
+ if (segments.size() < 2 || segments.size() > taskSegments.size()) {
183
+ return false
184
+ }
185
+ return (0..<segments.size()).every {
186
+ taskSegments[it].toLowerCase(Locale.ROOT).startsWith(segments[it].toLowerCase(Locale.ROOT))
187
+ }
188
+ }
189
+ }
190
+
191
+ // Checks the store picked at configuration against the tasks that will run.
192
+ // Only tasks the request selected count: an anchor such as assembleDebug also
193
+ // builds every flavor of a library it reaches.
194
+ ext.openIapVerifyTaskGraph = { String label, Map resolution ->
195
+ def resolved = resolution.store
196
+ if (resolved == 'none') {
197
+ return
198
+ }
199
+ gradle.taskGraph.whenReady { graph ->
200
+ def named = [] as LinkedHashSet
201
+ openIapRequestedTasks().each { String request ->
202
+ openIapSelectedTasks(graph, request).each { String taskName ->
203
+ openIapTaskTokens(taskName).each { token ->
204
+ if (token in ['play', 'horizon', 'amazon']) {
205
+ named << token
206
+ }
207
+ }
208
+ }
209
+ }
210
+ if (!named) {
211
+ // The store came from something that is not a task, such as an option's value.
212
+ if (resolution.source == 'variant') {
213
+ throw new GradleException("${label}: read ${resolved} from '${openIapRequestedTasks().join(' ')}', but no task it runs names a store; set openiapStore")
214
+ }
215
+ return
216
+ }
217
+ if (named.size() > 1) {
218
+ throw new GradleException("${label}: the requested tasks build more than one store (${named.join(', ')}) but a build links one; build one store per invocation or set openiapStore")
219
+ }
220
+ def actual = named.first()
221
+ if (actual != resolved) {
222
+ throw new GradleException("${label}: linked ${resolved} but the requested tasks build ${actual}; spell the task out or set openiapStore=${actual}")
223
+ }
224
+ }
225
+ }
226
+
227
+ ext.openIapEnv = { String name ->
228
+ // providers.environmentVariable is a tracked configuration-cache input;
229
+ // System.getenv is not.
230
+ return providers.environmentVariable(name).orNull
231
+ }
232
+
233
+ ext.openIapAdb = {
234
+ def roots = [openIapEnv('ANDROID_HOME'), openIapEnv('ANDROID_SDK_ROOT')]
235
+ // local.properties is plain I/O, not a tracked input: sdk.dir ~never
236
+ // changes, and staleness only loses adb discovery, never a store pick.
237
+ def localProperties = rootProject.file('local.properties')
238
+ if (localProperties.isFile()) {
239
+ def properties = new Properties()
240
+ localProperties.withInputStream { properties.load(it) }
241
+ roots << properties.getProperty('sdk.dir')
242
+ }
243
+ def names = ['adb', 'adb.exe']
244
+ def adb = roots.findResult { root ->
245
+ if (!root) {
246
+ return null
247
+ }
248
+ return names.findResult { name ->
249
+ def candidate = new File(root, "platform-tools/${name}")
250
+ return candidate.isFile() ? candidate.absolutePath : null
251
+ }
252
+ }
253
+ if (adb != null) {
254
+ return adb
255
+ }
256
+ // A developer with adb only on PATH still gets device selection.
257
+ return (openIapEnv('PATH') ?: '').split(File.pathSeparator).findResult { entry ->
258
+ if (!entry) {
259
+ return null
260
+ }
261
+ return names.findResult { name ->
262
+ def candidate = new File(entry, name)
263
+ return candidate.isFile() ? candidate.absolutePath : null
264
+ }
265
+ }
266
+ }
267
+
268
+ // providers.exec keeps the probe legal under the configuration cache, and Gradle
269
+ // re-runs it before reusing a cached configuration, so a new device reconfigures.
270
+ ext.openIapAdbText = { List<String> command ->
271
+ try {
272
+ return providers.exec {
273
+ commandLine(command)
274
+ ignoreExitValue = true
275
+ }.standardOutput.asText.get()
276
+ } catch (Throwable ignored) {
277
+ return null
278
+ }
279
+ }
280
+
281
+ // Like every Android tool, target the device ANDROID_SERIAL names.
282
+ ext.openIapDeviceStore = {
283
+ def adb = openIapAdb()
284
+ if (adb == null) {
285
+ return null
286
+ }
287
+ def listing = openIapAdbText([adb, 'devices'])
288
+ if (listing == null) {
289
+ return null
290
+ }
291
+ // Windows adb terminates lines with CR, which the serial would otherwise keep.
292
+ def serials = listing.readLines()
293
+ .collect { it.replace('\r', '').trim() }
294
+ .findAll { it ==~ /^\S+\s+device$/ }
295
+ .collect { it.split(/\s+/)[0] }
296
+ def requested = openIapEnv('ANDROID_SERIAL')?.trim()
297
+ def serial
298
+ if (requested) {
299
+ if (!serials.contains(requested)) {
300
+ logger.warn("openiap: ANDROID_SERIAL=${requested} is not attached; not selecting a store from a device")
301
+ return null
302
+ }
303
+ serial = requested
304
+ } else if (serials.size() == 1) {
305
+ serial = serials[0]
306
+ } else {
307
+ return null
308
+ }
309
+ def features = openIapAdbText([adb, '-s', serial, 'shell', 'pm', 'list', 'features'])
310
+ def manufacturerText = openIapAdbText([adb, '-s', serial, 'shell', 'getprop', 'ro.product.manufacturer'])
311
+ // A device that dropped after the listing answers with nothing; that is not Play.
312
+ if (!features?.trim() && !manufacturerText?.trim()) {
313
+ logger.warn("openiap: ${serial} stopped responding; not selecting a store from a device")
314
+ return null
315
+ }
316
+ features = features ?: ''
317
+ def manufacturer = (manufacturerText ?: '').trim()
318
+ def store = 'play'
319
+ if (features.contains('feature:horizonos.software.horizon_os') || features.contains('feature:oculus.hardware.standalone_vr')) {
320
+ store = 'horizon'
321
+ } else if (features.contains('feature:amazon.hardware.fire_tv') || manufacturer.equalsIgnoreCase('Amazon')) {
322
+ store = 'amazon'
323
+ }
324
+ return [store: store, reason: "device ${serial}, manufacturer ${manufacturer ?: 'unknown'}"]
325
+ }
326
+
327
+ ext.openIapResolveStore = { String label, Map options = [:] ->
328
+ def allowNone = options.allowNone == true
329
+ if (rootProject.ext.has('openIapStoreResolution')) {
330
+ def cached = rootProject.ext.openIapStoreResolution
331
+ // The opt-out is per caller: a library that needs a store SDK still refuses it.
332
+ if (cached.store == 'none' && !allowNone) {
333
+ throw new GradleException("${label}: ${openIapOptOutKey()} is not supported by this library")
334
+ }
335
+ return cached
336
+ }
337
+ def explicit = openIapExplicitStore(label, allowNone)
338
+ // Opting out links nothing, so the tasks may build several store flavors.
339
+ def variant = explicit == 'none' ? null : openIapTaskStore(label)
340
+ // A pin against the task's flavor would ship the wrong SDK. The opt-out links
341
+ // nothing, so it may sit beside any flavor.
342
+ if (explicit != null && explicit != 'none' && variant != null && explicit != variant) {
343
+ throw new GradleException(
344
+ "${label}: openiapStore=${explicit} conflicts with the ${variant} flavor in '${openIapRequestedTasks().join(' ')}'"
345
+ )
346
+ }
347
+ def resolution = null
348
+ if (explicit != null) {
349
+ resolution = [store: explicit, source: 'explicit', reason: 'openiapStore or a legacy flag']
350
+ } else if (variant != null) {
351
+ resolution = [store: variant, source: 'variant', reason: "task ${openIapRequestedTasks().join(' ')}"]
352
+ } else if (openIapDebugBuild()) {
353
+ def device = openIapDeviceStore()
354
+ if (device != null) {
355
+ resolution = [store: device.store, source: 'device', reason: device.reason]
356
+ }
357
+ }
358
+ if (resolution == null) {
359
+ resolution = [store: 'play', source: 'default', reason: 'no store signal']
360
+ }
361
+ rootProject.ext.openIapStoreResolution = resolution
362
+ logger.lifecycle("openiap: store=${resolution.store} (source=${resolution.source}; ${resolution.reason})")
363
+ openIapVerifyTaskGraph(label, resolution)
364
+ return resolution
365
+ }
@@ -272,10 +272,7 @@ object ExpoIapHelper {
272
272
  )
273
273
  }
274
274
 
275
- /**
276
- * Helper to safely emit an event with error fallback.
277
- * Reduces code duplication across listener handlers.
278
- */
275
+ /** Emits an event, falling back to a purchase error if emitting fails. */
279
276
  private fun safeEmitEvent(
280
277
  module: Module,
281
278
  scope: CoroutineScope,
@@ -362,7 +359,7 @@ object ExpoIapHelper {
362
359
  )
363
360
  }.onFailure { error ->
364
361
  ExpoIapLog.failure("buffer/send PURCHASE_ERROR", error)
365
- // Critical: if we can't emit the original error, at least try to emit a generic one
362
+ // If the original error cannot be emitted, try a generic one.
366
363
  val fallbackPayload =
367
364
  mapOf(
368
365
  "code" to OpenIapError.UnknownError.CODE,
@@ -148,8 +148,8 @@ class ExpoIapModule : Module() {
148
148
  scope.launch {
149
149
  connectionMutex.withLock {
150
150
  try {
151
- // CRITICAL: Set Activity BEFORE calling initConnection
152
- // Horizon SDK needs Activity to initialize OVRPlatform with proper returnComponent
151
+ // Set the Activity before initConnection: the Horizon SDK needs it to
152
+ // initialize OVRPlatform with the proper returnComponent.
153
153
  // https://github.com/meta-quest/Meta-Spatial-SDK-Samples/issues/82#issuecomment-3452577530
154
154
  runCatching { currentActivity }
155
155
  .onSuccess {
@@ -501,7 +501,6 @@ class ExpoIapModule : Module() {
501
501
  }
502
502
  }
503
503
 
504
- // New name: consumePurchaseAndroid
505
504
  AsyncFunction("consumePurchaseAndroid") { token: String, promise: Promise ->
506
505
  ExpoIapLog.payload("consumePurchaseAndroid", mapOf("token" to token))
507
506
  scope.launch {
@@ -610,9 +609,6 @@ class ExpoIapModule : Module() {
610
609
  scope.launch {
611
610
  try {
612
611
  val openIapProgram = mapBillingProgram(program)
613
- // Note: enableBillingProgram should be called before initConnection
614
- // for proper BillingClient configuration. Here it's called as a fallback
615
- // but may have no effect if BillingClient is already initialized.
616
612
  val result = openIapStore.isBillingProgramAvailable(openIapProgram)
617
613
  val response =
618
614
  mapOf(
@@ -1,11 +1,56 @@
1
+ import { type EventSubscription } from 'expo-modules-core';
2
+ import type { BillingProgramAndroid, BillingProgramReportingDetailsAndroid, DeveloperBillingTypeAndroid, Mutation, MutationField, ProductQueryType, PurchaseInput, PurchaseOptions, PurchaseUpdatedListenerOptions, Query, QueryField, SubscriptionStatusIOS } from './types';
3
+ /** Members read from the raw module; every store module provides them. */
4
+ type NativeEventModule = {
5
+ ERROR_CODES?: Record<string, unknown>;
6
+ addListener<T>(eventName: string, listener: (payload: T) => void): EventSubscription | undefined;
7
+ removeListener?<T>(eventName: string, listener: (payload: T) => void): void;
8
+ setPurchaseUpdatedListenerOptions?(options?: PurchaseUpdatedListenerOptions | null): Promise<void>;
9
+ };
10
+ type QueryFields<K extends keyof Query> = {
11
+ [P in K]: QueryField<P>;
12
+ };
13
+ type MutationFields<K extends keyof Mutation> = {
14
+ [P in K]: MutationField<P>;
15
+ };
16
+ /**
17
+ * Native surface behind the default export. Results the wrappers decode stay
18
+ * `unknown`; the rest match the generated operation signatures.
19
+ */
20
+ export type ExpoIapNativeModule = NativeEventModule & QueryFields<'canPresentExternalPurchaseNoticeIOS' | 'currentEntitlementIOS' | 'getActiveSubscriptions' | 'getAppTransactionIOS' | 'getBillingChoiceInfoAndroid' | 'getExternalPurchaseCustomLinkTokenIOS' | 'getPromotedProductIOS' | 'getReceiptDataIOS' | 'getTransactionJwsIOS' | 'hasActiveSubscriptions' | 'isEligibleForExternalPurchaseCustomLinkIOS' | 'isEligibleForIntroOfferIOS' | 'isTransactionVerifiedIOS' | 'latestTransactionIOS'> & MutationFields<'beginRefundRequestIOS' | 'clearTransactionIOS' | 'endConnection' | 'initConnection' | 'isBillingProgramAvailableAndroid' | 'launchExternalLinkAndroid' | 'openRedeemOfferCodeAndroid' | 'presentCodeRedemptionSheetIOS' | 'presentExternalPurchaseLinkIOS' | 'presentExternalPurchaseNoticeSheetIOS' | 'showBillingProgramInformationDialogAndroid' | 'showExternalPurchaseCustomLinkNoticeIOS' | 'showInAppMessagesAndroid' | 'syncIOS' | 'verifyPurchase' | 'verifyPurchaseWithProvider'> & {
21
+ USING_ONSIDE_SDK: boolean;
22
+ USING_VEGA_SDK: boolean;
23
+ fetchProducts(request: {
24
+ skus: string[];
25
+ type: ProductQueryType;
26
+ }): Promise<unknown[]>;
27
+ fetchProducts(type: ProductQueryType, skus: string[]): Promise<unknown[]>;
28
+ getAvailableItems(alsoPublishToEventListenerIOS: boolean, onlyIncludeActiveItemsIOS: boolean): Promise<unknown>;
29
+ getAvailableItems(options: PurchaseOptions): Promise<unknown>;
30
+ getAllTransactionsIOS(): Promise<unknown>;
31
+ getPendingTransactionsIOS(): Promise<unknown>;
32
+ showManageSubscriptionsIOS(): Promise<unknown>;
33
+ requestPurchase(request: object): Promise<unknown>;
34
+ finishTransaction(purchase: PurchaseInput, isConsumable: boolean | null): Promise<boolean>;
35
+ acknowledgePurchaseAndroid(purchaseToken: string): Promise<unknown>;
36
+ consumePurchaseAndroid(purchaseToken: string): Promise<unknown>;
37
+ createBillingProgramReportingDetailsAndroid(program: BillingProgramAndroid, developerBillingType: DeveloperBillingTypeAndroid | null): Promise<BillingProgramReportingDetailsAndroid>;
38
+ subscriptionStatusIOS(sku: string): Promise<SubscriptionStatusIOS[] | null>;
39
+ requestReceiptRefreshIOS(): Promise<string>;
40
+ deepLinkToSubscriptionsAndroid?(options: {
41
+ skuAndroid?: string;
42
+ packageNameAndroid?: string;
43
+ }): Promise<void> | void;
44
+ getStorefront?(): Promise<string> | string;
45
+ restorePurchases?(): Promise<boolean>;
46
+ };
1
47
  export declare const NATIVE_ERROR_CODES: Record<string, unknown>;
2
48
  /**
3
- * Returns the raw native module (not wrapped in a Proxy).
4
- * Use this for EventEmitter / addListener calls — JSI HostObjects
5
- * require the real native module as `this`; a Proxy triggers
6
- * "native state unsupported on Proxy" on New Architecture / Hermes.
49
+ * Returns the raw native module, not the Proxy. Use it for addListener: a JSI
50
+ * HostObject needs the real module as `this`, and a Proxy throws "native state
51
+ * unsupported on Proxy" on New Architecture / Hermes.
7
52
  */
8
- export declare function getNativeModule(): any;
9
- declare const _default: any;
53
+ export declare function getNativeModule(): NativeEventModule;
54
+ declare const _default: ExpoIapNativeModule;
10
55
  export default _default;
11
56
  //# sourceMappingURL=ExpoIapModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoIapModule.d.ts","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"AA6FA,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAQtD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,QAE9B;;AAED,wBAuBG"}
1
+ {"version":3,"file":"ExpoIapModule.d.ts","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { requireNativeModule, UnavailabilityError } from 'expo-modules-core';
1
+ import { requireNativeModule, UnavailabilityError, } from 'expo-modules-core';
2
2
  import { installedFromOnside } from './onside';
3
3
  import { getVegaIapModule, isVegaOS } from './vega';
4
4
  const ONSIDE_MARKETPLACE_ID = 'com.onside.marketplace-app';
@@ -47,7 +47,10 @@ function getResolved() {
47
47
  throw new UnavailabilityError('expo-iap', 'The Onside marketplace build does not contain ExpoIapOnside. Rebuild with ios.onside.enabled instead of routing purchases through Apple StoreKit.');
48
48
  }
49
49
  }
50
- return { module: requireNativeModule('ExpoIap'), name: 'ExpoIap' };
50
+ return {
51
+ module: requireNativeModule('ExpoIap'),
52
+ name: 'ExpoIap',
53
+ };
51
54
  }
52
55
  const expectedName = getExpectedModuleName();
53
56
  if (!cached || cached.name !== expectedName) {
@@ -68,18 +71,19 @@ export const NATIVE_ERROR_CODES = new Proxy({}, {
68
71
  get(target, prop) {
69
72
  if (typeof prop === 'symbol')
70
73
  return Reflect.get(target, prop);
71
- return (getResolved().module.ERROR_CODES || {})[prop];
74
+ const errorCodes = getResolved().module.ERROR_CODES || {};
75
+ return errorCodes[prop];
72
76
  },
73
77
  });
74
78
  /**
75
- * Returns the raw native module (not wrapped in a Proxy).
76
- * Use this for EventEmitter / addListener calls — JSI HostObjects
77
- * require the real native module as `this`; a Proxy triggers
78
- * "native state unsupported on Proxy" on New Architecture / Hermes.
79
+ * Returns the raw native module, not the Proxy. Use it for addListener: a JSI
80
+ * HostObject needs the real module as `this`, and a Proxy throws "native state
81
+ * unsupported on Proxy" on New Architecture / Hermes.
79
82
  */
80
83
  export function getNativeModule() {
81
84
  return getResolved().module;
82
85
  }
86
+ // The Proxy forwards every member to the lazily resolved store module.
83
87
  export default new Proxy({}, {
84
88
  get(target, prop) {
85
89
  if (typeof prop === 'symbol')
@@ -91,7 +95,7 @@ export default new Proxy({}, {
91
95
  if (prop === 'USING_VEGA_SDK') {
92
96
  return resolved.name === 'ExpoIapVega';
93
97
  }
94
- const value = resolved.module[prop];
98
+ const value = Reflect.get(resolved.module, prop);
95
99
  if (value !== undefined || resolved.name !== 'ExpoIapOnside') {
96
100
  return value;
97
101
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoIapModule.js","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"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","sourcesContent":["import {requireNativeModule, UnavailabilityError} from 'expo-modules-core';\nimport {installedFromOnside} from './onside';\nimport {getVegaIapModule, isVegaOS} from './vega';\n\ntype NativeIapModuleName = 'ExpoIapVega' | 'ExpoIapOnside' | 'ExpoIap';\nconst ONSIDE_MARKETPLACE_ID = 'com.onside.marketplace-app';\n\nlet cached: {module: any; name: NativeIapModuleName} | null = null;\nlet onsideModuleUnavailable = false;\n\nfunction getResolved(): {module: any; name: NativeIapModuleName} {\n function shouldUseOnsideModule(): boolean {\n if (installedFromOnside === true) {\n return true;\n }\n\n if (typeof installedFromOnside !== 'string') {\n return false;\n }\n\n const normalized = installedFromOnside.trim().toLowerCase();\n return normalized === 'true' || normalized === ONSIDE_MARKETPLACE_ID;\n }\n\n function getExpectedModuleName(): NativeIapModuleName {\n if (isVegaOS()) {\n return 'ExpoIapVega';\n }\n\n return shouldUseOnsideModule() ? 'ExpoIapOnside' : 'ExpoIap';\n }\n\n function resolveNativeModule(): {\n module: any;\n name: NativeIapModuleName;\n } {\n if (isVegaOS()) {\n const vegaModule = getVegaIapModule();\n if (!vegaModule) {\n throw new UnavailabilityError(\n 'expo-iap',\n 'Amazon Vega IAP module is unavailable. Install @amazon-devices/keplerscript-appstore-iap-lib in the Vega app target and build with the React Native for Vega kepler platform.',\n );\n }\n return {module: vegaModule, name: 'ExpoIapVega'};\n }\n\n if (shouldUseOnsideModule()) {\n if (onsideModuleUnavailable) {\n throw new UnavailabilityError(\n 'expo-iap',\n 'The Onside marketplace build does not contain ExpoIapOnside. Rebuild with ios.onside.enabled instead of routing purchases through Apple StoreKit.',\n );\n }\n try {\n return {\n module: requireNativeModule('ExpoIapOnside'),\n name: 'ExpoIapOnside',\n };\n } catch (error) {\n if (!isMissingModuleError(error, 'ExpoIapOnside')) {\n throw error;\n }\n onsideModuleUnavailable = true;\n throw new UnavailabilityError(\n 'expo-iap',\n 'The Onside marketplace build does not contain ExpoIapOnside. Rebuild with ios.onside.enabled instead of routing purchases through Apple StoreKit.',\n );\n }\n }\n\n return {module: requireNativeModule('ExpoIap'), name: 'ExpoIap'};\n }\n\n const expectedName = getExpectedModuleName();\n if (!cached || cached.name !== expectedName) {\n cached = resolveNativeModule();\n }\n return cached;\n}\n\nfunction isMissingModuleError(error: unknown, moduleName: string): boolean {\n if (error instanceof UnavailabilityError) {\n return true;\n }\n\n if (error instanceof Error) {\n return error.message.includes(`Cannot find native module '${moduleName}'`);\n }\n\n return false;\n}\n\nexport const NATIVE_ERROR_CODES: Record<string, unknown> = new Proxy(\n {} as Record<string, unknown>,\n {\n get(target, prop) {\n if (typeof prop === 'symbol') return Reflect.get(target, prop);\n return (getResolved().module.ERROR_CODES || {})[prop as string];\n },\n },\n);\n\n/**\n * Returns the raw native module (not wrapped in a Proxy).\n * Use this for EventEmitter / addListener calls — JSI HostObjects\n * require the real native module as `this`; a Proxy triggers\n * \"native state unsupported on Proxy\" on New Architecture / Hermes.\n */\nexport function getNativeModule() {\n return getResolved().module;\n}\n\nexport default new Proxy({} as any, {\n get(target, prop) {\n if (typeof prop === 'symbol') return Reflect.get(target, prop);\n const resolved = getResolved();\n if (prop === 'USING_ONSIDE_SDK') {\n return resolved.name === 'ExpoIapOnside';\n }\n if (prop === 'USING_VEGA_SDK') {\n return resolved.name === 'ExpoIapVega';\n }\n\n const value = resolved.module[prop];\n if (value !== undefined || resolved.name !== 'ExpoIapOnside') {\n return value;\n }\n\n return () => {\n throw new UnavailabilityError(\n 'expo-iap',\n `The Onside marketplace does not support ${String(prop)}. The call was not routed through Apple StoreKit.`,\n );\n };\n },\n});\n"]}
1
+ {"version":3,"file":"ExpoIapModule.js","sourceRoot":"","sources":["../src/ExpoIapModule.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n requireNativeModule,\n UnavailabilityError,\n type EventSubscription,\n} from 'expo-modules-core';\nimport {installedFromOnside} from './onside';\nimport type {\n BillingProgramAndroid,\n BillingProgramReportingDetailsAndroid,\n DeveloperBillingTypeAndroid,\n Mutation,\n MutationField,\n ProductQueryType,\n PurchaseInput,\n PurchaseOptions,\n PurchaseUpdatedListenerOptions,\n Query,\n QueryField,\n SubscriptionStatusIOS,\n} from './types';\nimport {getVegaIapModule, isVegaOS} from './vega';\n\ntype NativeIapModuleName = 'ExpoIapVega' | 'ExpoIapOnside' | 'ExpoIap';\nconst ONSIDE_MARKETPLACE_ID = 'com.onside.marketplace-app';\n\n/** Members read from the raw module; every store module provides them. */\ntype NativeEventModule = {\n ERROR_CODES?: Record<string, unknown>;\n addListener<T>(\n eventName: string,\n listener: (payload: T) => void,\n ): EventSubscription | undefined;\n removeListener?<T>(eventName: string, listener: (payload: T) => void): void;\n setPurchaseUpdatedListenerOptions?(\n options?: PurchaseUpdatedListenerOptions | null,\n ): Promise<void>;\n};\n\ntype QueryFields<K extends keyof Query> = {[P in K]: QueryField<P>};\ntype MutationFields<K extends keyof Mutation> = {[P in K]: MutationField<P>};\n\n/**\n * Native surface behind the default export. Results the wrappers decode stay\n * `unknown`; the rest match the generated operation signatures.\n */\nexport type ExpoIapNativeModule = NativeEventModule &\n QueryFields<\n | 'canPresentExternalPurchaseNoticeIOS'\n | 'currentEntitlementIOS'\n | 'getActiveSubscriptions'\n | 'getAppTransactionIOS'\n | 'getBillingChoiceInfoAndroid'\n | 'getExternalPurchaseCustomLinkTokenIOS'\n | 'getPromotedProductIOS'\n | 'getReceiptDataIOS'\n | 'getTransactionJwsIOS'\n | 'hasActiveSubscriptions'\n | 'isEligibleForExternalPurchaseCustomLinkIOS'\n | 'isEligibleForIntroOfferIOS'\n | 'isTransactionVerifiedIOS'\n | 'latestTransactionIOS'\n > &\n MutationFields<\n | 'beginRefundRequestIOS'\n | 'clearTransactionIOS'\n | 'endConnection'\n | 'initConnection'\n | 'isBillingProgramAvailableAndroid'\n | 'launchExternalLinkAndroid'\n | 'openRedeemOfferCodeAndroid'\n | 'presentCodeRedemptionSheetIOS'\n | 'presentExternalPurchaseLinkIOS'\n | 'presentExternalPurchaseNoticeSheetIOS'\n | 'showBillingProgramInformationDialogAndroid'\n | 'showExternalPurchaseCustomLinkNoticeIOS'\n | 'showInAppMessagesAndroid'\n | 'syncIOS'\n | 'verifyPurchase'\n | 'verifyPurchaseWithProvider'\n > & {\n USING_ONSIDE_SDK: boolean;\n USING_VEGA_SDK: boolean;\n fetchProducts(request: {\n skus: string[];\n type: ProductQueryType;\n }): Promise<unknown[]>;\n fetchProducts(type: ProductQueryType, skus: string[]): Promise<unknown[]>;\n getAvailableItems(\n alsoPublishToEventListenerIOS: boolean,\n onlyIncludeActiveItemsIOS: boolean,\n ): Promise<unknown>;\n getAvailableItems(options: PurchaseOptions): Promise<unknown>;\n getAllTransactionsIOS(): Promise<unknown>;\n getPendingTransactionsIOS(): Promise<unknown>;\n showManageSubscriptionsIOS(): Promise<unknown>;\n requestPurchase(request: object): Promise<unknown>;\n finishTransaction(\n purchase: PurchaseInput,\n isConsumable: boolean | null,\n ): Promise<boolean>;\n acknowledgePurchaseAndroid(purchaseToken: string): Promise<unknown>;\n consumePurchaseAndroid(purchaseToken: string): Promise<unknown>;\n createBillingProgramReportingDetailsAndroid(\n program: BillingProgramAndroid,\n developerBillingType: DeveloperBillingTypeAndroid | null,\n ): Promise<BillingProgramReportingDetailsAndroid>;\n subscriptionStatusIOS(sku: string): Promise<SubscriptionStatusIOS[] | null>;\n requestReceiptRefreshIOS(): Promise<string>;\n deepLinkToSubscriptionsAndroid?(options: {\n skuAndroid?: string;\n packageNameAndroid?: string;\n }): Promise<void> | void;\n getStorefront?(): Promise<string> | string;\n restorePurchases?(): Promise<boolean>;\n };\n\ntype ResolvedNativeModule = {\n module: NativeEventModule;\n name: NativeIapModuleName;\n};\n\nlet cached: ResolvedNativeModule | null = null;\nlet onsideModuleUnavailable = false;\n\nfunction getResolved(): ResolvedNativeModule {\n function shouldUseOnsideModule(): boolean {\n if (installedFromOnside === true) {\n return true;\n }\n\n if (typeof installedFromOnside !== 'string') {\n return false;\n }\n\n const normalized = installedFromOnside.trim().toLowerCase();\n return normalized === 'true' || normalized === ONSIDE_MARKETPLACE_ID;\n }\n\n function getExpectedModuleName(): NativeIapModuleName {\n if (isVegaOS()) {\n return 'ExpoIapVega';\n }\n\n return shouldUseOnsideModule() ? 'ExpoIapOnside' : 'ExpoIap';\n }\n\n function resolveNativeModule(): ResolvedNativeModule {\n if (isVegaOS()) {\n const vegaModule = getVegaIapModule();\n if (!vegaModule) {\n throw new UnavailabilityError(\n 'expo-iap',\n 'Amazon Vega IAP module is unavailable. Install @amazon-devices/keplerscript-appstore-iap-lib in the Vega app target and build with the React Native for Vega kepler platform.',\n );\n }\n return {module: vegaModule, name: 'ExpoIapVega'};\n }\n\n if (shouldUseOnsideModule()) {\n if (onsideModuleUnavailable) {\n throw new UnavailabilityError(\n 'expo-iap',\n 'The Onside marketplace build does not contain ExpoIapOnside. Rebuild with ios.onside.enabled instead of routing purchases through Apple StoreKit.',\n );\n }\n try {\n return {\n module: requireNativeModule<NativeEventModule>('ExpoIapOnside'),\n name: 'ExpoIapOnside',\n };\n } catch (error) {\n if (!isMissingModuleError(error, 'ExpoIapOnside')) {\n throw error;\n }\n onsideModuleUnavailable = true;\n throw new UnavailabilityError(\n 'expo-iap',\n 'The Onside marketplace build does not contain ExpoIapOnside. Rebuild with ios.onside.enabled instead of routing purchases through Apple StoreKit.',\n );\n }\n }\n\n return {\n module: requireNativeModule<NativeEventModule>('ExpoIap'),\n name: 'ExpoIap',\n };\n }\n\n const expectedName = getExpectedModuleName();\n if (!cached || cached.name !== expectedName) {\n cached = resolveNativeModule();\n }\n return cached;\n}\n\nfunction isMissingModuleError(error: unknown, moduleName: string): boolean {\n if (error instanceof UnavailabilityError) {\n return true;\n }\n\n if (error instanceof Error) {\n return error.message.includes(`Cannot find native module '${moduleName}'`);\n }\n\n return false;\n}\n\nexport const NATIVE_ERROR_CODES: Record<string, unknown> = new Proxy(\n {} as Record<string, unknown>,\n {\n get(target, prop) {\n if (typeof prop === 'symbol') return Reflect.get(target, prop);\n const errorCodes: Record<string, unknown> =\n getResolved().module.ERROR_CODES || {};\n return errorCodes[prop];\n },\n },\n);\n\n/**\n * Returns the raw native module, not the Proxy. Use it for addListener: a JSI\n * HostObject needs the real module as `this`, and a Proxy throws \"native state\n * unsupported on Proxy\" on New Architecture / Hermes.\n */\nexport function getNativeModule(): NativeEventModule {\n return getResolved().module;\n}\n\n// The Proxy forwards every member to the lazily resolved store module.\nexport default new Proxy({} as ExpoIapNativeModule, {\n get(target, prop) {\n if (typeof prop === 'symbol') return Reflect.get(target, prop);\n const resolved = getResolved();\n if (prop === 'USING_ONSIDE_SDK') {\n return resolved.name === 'ExpoIapOnside';\n }\n if (prop === 'USING_VEGA_SDK') {\n return resolved.name === 'ExpoIapVega';\n }\n\n const value: unknown = Reflect.get(resolved.module, prop);\n if (value !== undefined || resolved.name !== 'ExpoIapOnside') {\n return value;\n }\n\n return () => {\n throw new UnavailabilityError(\n 'expo-iap',\n `The Onside marketplace does not support ${String(\n prop,\n )}. The call was not routed through Apple StoreKit.`,\n );\n };\n },\n});\n"]}