expo-iap 3.2.0 → 3.2.2
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 +10 -4
- package/README.md +2 -23
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +141 -13
- package/build/index.d.ts.map +1 -1
- package/build/index.js +40 -12
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +53 -1
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +61 -0
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +4 -2
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +321 -24
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/coverage/clover.xml +115 -100
- package/coverage/coverage-final.json +3 -3
- package/coverage/lcov-report/index.html +26 -26
- package/coverage/lcov-report/src/index.html +19 -19
- package/coverage/lcov-report/src/index.ts.html +117 -24
- package/coverage/lcov-report/src/modules/android.ts.html +233 -8
- package/coverage/lcov-report/src/modules/index.html +15 -15
- package/coverage/lcov-report/src/modules/ios.ts.html +13 -7
- package/coverage/lcov-report/src/utils/debug.ts.html +1 -1
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1 -1
- package/coverage/lcov-report/src/utils/index.html +1 -1
- package/coverage/lcov.info +274 -244
- package/ios/ExpoIap.podspec +7 -5
- package/ios/ExpoIapHelper.swift +1 -1
- package/ios/ExpoIapModule.swift +1 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/plugin/build/withIAP.d.ts +8 -5
- package/plugin/build/withIAP.js +12 -3
- package/plugin/src/withIAP.ts +18 -9
- package/src/index.ts +43 -12
- package/src/modules/android.ts +75 -0
- package/src/modules/ios.ts +4 -2
- package/src/types.ts +340 -24
package/plugin/build/withIAP.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.withIosAlternativeBilling = void 0;
|
|
39
|
+
exports.withIap = exports.withIosAlternativeBilling = void 0;
|
|
40
40
|
const config_plugins_1 = require("expo/config-plugins");
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
@@ -219,10 +219,18 @@ const withIapIOS = (config, options) => {
|
|
|
219
219
|
};
|
|
220
220
|
const withIap = (config, options) => {
|
|
221
221
|
try {
|
|
222
|
+
// Add iapkitApiKey to extra if provided
|
|
223
|
+
if (options?.iapkitApiKey) {
|
|
224
|
+
config.extra = {
|
|
225
|
+
...config.extra,
|
|
226
|
+
iapkitApiKey: options.iapkitApiKey,
|
|
227
|
+
};
|
|
228
|
+
logOnce('🔑 [expo-iap] Added iapkitApiKey to config.extra');
|
|
229
|
+
}
|
|
222
230
|
// Read Horizon configuration from modules
|
|
223
231
|
const isHorizonEnabled = options?.modules?.horizon ?? false;
|
|
224
|
-
const horizonAppId = options?.android?.horizonAppId
|
|
225
|
-
const iosAlternativeBilling = options?.ios?.alternativeBilling
|
|
232
|
+
const horizonAppId = options?.android?.horizonAppId;
|
|
233
|
+
const iosAlternativeBilling = options?.ios?.alternativeBilling;
|
|
226
234
|
logOnce(`🔍 [expo-iap] Config values: horizonAppId=${horizonAppId}, isHorizonEnabled=${isHorizonEnabled}`);
|
|
227
235
|
// Respect explicit flag; fall back to presence of localPath only when flag is unset
|
|
228
236
|
const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
|
|
@@ -270,4 +278,5 @@ const withIap = (config, options) => {
|
|
|
270
278
|
return config;
|
|
271
279
|
}
|
|
272
280
|
};
|
|
281
|
+
exports.withIap = withIap;
|
|
273
282
|
exports.default = (0, config_plugins_1.createRunOncePlugin)(withIap, pkg.name, pkg.version);
|
package/plugin/src/withIAP.ts
CHANGED
|
@@ -289,6 +289,12 @@ const withIapIOS: ConfigPlugin<IOSAlternativeBillingConfig | undefined> = (
|
|
|
289
289
|
};
|
|
290
290
|
|
|
291
291
|
export interface ExpoIapPluginOptions {
|
|
292
|
+
/**
|
|
293
|
+
* IAPKit API key for server-side receipt verification.
|
|
294
|
+
* Get your API key from https://iapkit.com
|
|
295
|
+
* This will be available via `Constants.expoConfig?.extra?.iapkitApiKey`
|
|
296
|
+
*/
|
|
297
|
+
iapkitApiKey?: string;
|
|
292
298
|
/** Local development path for OpenIAP library */
|
|
293
299
|
localPath?:
|
|
294
300
|
| string
|
|
@@ -336,10 +342,6 @@ export interface ExpoIapPluginOptions {
|
|
|
336
342
|
*/
|
|
337
343
|
horizonAppId?: string;
|
|
338
344
|
};
|
|
339
|
-
/** @deprecated Use ios.alternativeBilling instead */
|
|
340
|
-
iosAlternativeBilling?: IOSAlternativeBillingConfig;
|
|
341
|
-
/** @deprecated Use android.horizonAppId instead */
|
|
342
|
-
horizonAppId?: string;
|
|
343
345
|
}
|
|
344
346
|
|
|
345
347
|
const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
@@ -347,13 +349,20 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
347
349
|
options,
|
|
348
350
|
) => {
|
|
349
351
|
try {
|
|
352
|
+
// Add iapkitApiKey to extra if provided
|
|
353
|
+
if (options?.iapkitApiKey) {
|
|
354
|
+
config.extra = {
|
|
355
|
+
...config.extra,
|
|
356
|
+
iapkitApiKey: options.iapkitApiKey,
|
|
357
|
+
};
|
|
358
|
+
logOnce('🔑 [expo-iap] Added iapkitApiKey to config.extra');
|
|
359
|
+
}
|
|
360
|
+
|
|
350
361
|
// Read Horizon configuration from modules
|
|
351
362
|
const isHorizonEnabled = options?.modules?.horizon ?? false;
|
|
352
363
|
|
|
353
|
-
const horizonAppId =
|
|
354
|
-
|
|
355
|
-
const iosAlternativeBilling =
|
|
356
|
-
options?.ios?.alternativeBilling ?? options?.iosAlternativeBilling;
|
|
364
|
+
const horizonAppId = options?.android?.horizonAppId;
|
|
365
|
+
const iosAlternativeBilling = options?.ios?.alternativeBilling;
|
|
357
366
|
|
|
358
367
|
logOnce(
|
|
359
368
|
`🔍 [expo-iap] Config values: horizonAppId=${horizonAppId}, isHorizonEnabled=${isHorizonEnabled}`,
|
|
@@ -416,5 +425,5 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
416
425
|
}
|
|
417
426
|
};
|
|
418
427
|
|
|
419
|
-
export {withIosAlternativeBilling};
|
|
428
|
+
export {withIosAlternativeBilling, withIap};
|
|
420
429
|
export default createRunOncePlugin(withIap, pkg.name, pkg.version);
|
package/src/index.ts
CHANGED
|
@@ -727,29 +727,33 @@ export const deepLinkToSubscriptions: MutationField<
|
|
|
727
727
|
export const validateReceipt: MutationField<'validateReceipt'> = async (
|
|
728
728
|
options,
|
|
729
729
|
) => {
|
|
730
|
-
const {
|
|
730
|
+
const {apple, google} = options as MutationValidateReceiptArgs;
|
|
731
731
|
|
|
732
732
|
if (Platform.OS === 'ios') {
|
|
733
|
-
|
|
733
|
+
if (!apple?.sku) {
|
|
734
|
+
throw new Error('iOS validation requires apple.sku');
|
|
735
|
+
}
|
|
736
|
+
return validateReceiptIOS({apple: {sku: apple.sku}});
|
|
734
737
|
}
|
|
735
738
|
|
|
736
739
|
if (Platform.OS === 'android') {
|
|
737
740
|
if (
|
|
738
|
-
!
|
|
739
|
-
!
|
|
740
|
-
!
|
|
741
|
-
!
|
|
741
|
+
!google ||
|
|
742
|
+
!google.sku ||
|
|
743
|
+
!google.packageName ||
|
|
744
|
+
!google.purchaseToken ||
|
|
745
|
+
!google.accessToken
|
|
742
746
|
) {
|
|
743
747
|
throw new Error(
|
|
744
|
-
'Android validation requires packageName,
|
|
748
|
+
'Android validation requires google.sku, google.packageName, google.purchaseToken, and google.accessToken',
|
|
745
749
|
);
|
|
746
750
|
}
|
|
747
751
|
return validateReceiptAndroid({
|
|
748
|
-
packageName:
|
|
749
|
-
productId: sku,
|
|
750
|
-
productToken:
|
|
751
|
-
accessToken:
|
|
752
|
-
isSub:
|
|
752
|
+
packageName: google.packageName,
|
|
753
|
+
productId: google.sku,
|
|
754
|
+
productToken: google.purchaseToken,
|
|
755
|
+
accessToken: google.accessToken,
|
|
756
|
+
isSub: google.isSub ?? undefined,
|
|
753
757
|
});
|
|
754
758
|
}
|
|
755
759
|
|
|
@@ -804,6 +808,33 @@ export const verifyPurchaseWithProvider: MutationField<
|
|
|
804
808
|
'verifyPurchaseWithProvider'
|
|
805
809
|
> = async (options) => {
|
|
806
810
|
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
811
|
+
// Auto-fill apiKey from config if not provided and provider is iapkit
|
|
812
|
+
if (
|
|
813
|
+
options.provider === 'iapkit' &&
|
|
814
|
+
options.iapkit &&
|
|
815
|
+
!options.iapkit.apiKey
|
|
816
|
+
) {
|
|
817
|
+
try {
|
|
818
|
+
// Dynamically import expo-constants to avoid hard dependency
|
|
819
|
+
const {default: Constants} = await import('expo-constants');
|
|
820
|
+
const configApiKey = Constants.expoConfig?.extra?.iapkitApiKey;
|
|
821
|
+
if (configApiKey) {
|
|
822
|
+
options = {
|
|
823
|
+
...options,
|
|
824
|
+
iapkit: {
|
|
825
|
+
...options.iapkit,
|
|
826
|
+
apiKey: configApiKey,
|
|
827
|
+
},
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
} catch {
|
|
831
|
+
throw new Error(
|
|
832
|
+
'expo-constants is required for auto-filling iapkitApiKey from config. ' +
|
|
833
|
+
'Please install it: npx expo install expo-constants\n' +
|
|
834
|
+
'Or provide apiKey directly in verifyPurchaseWithProvider options.',
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
807
838
|
return ExpoIapModule.verifyPurchaseWithProvider(options);
|
|
808
839
|
}
|
|
809
840
|
|
package/src/modules/android.ts
CHANGED
|
@@ -6,7 +6,11 @@ import ExpoIapModule from '../ExpoIapModule';
|
|
|
6
6
|
|
|
7
7
|
// Types
|
|
8
8
|
import type {
|
|
9
|
+
BillingProgramAndroid,
|
|
10
|
+
BillingProgramAvailabilityResultAndroid,
|
|
11
|
+
BillingProgramReportingDetailsAndroid,
|
|
9
12
|
DeepLinkOptions,
|
|
13
|
+
LaunchExternalLinkParamsAndroid,
|
|
10
14
|
MutationField,
|
|
11
15
|
VerifyPurchaseResultAndroid,
|
|
12
16
|
} from '../types';
|
|
@@ -248,3 +252,74 @@ export const createAlternativeBillingTokenAndroid: MutationField<
|
|
|
248
252
|
> = async (sku?: string) => {
|
|
249
253
|
return ExpoIapModule.createAlternativeBillingTokenAndroid(sku);
|
|
250
254
|
};
|
|
255
|
+
|
|
256
|
+
// ============================================================================
|
|
257
|
+
// Billing Programs API (Google Play Billing Library 8.2.0+)
|
|
258
|
+
// ============================================================================
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if a specific billing program is available for this user/device (Android only).
|
|
262
|
+
* Available in Google Play Billing Library 8.2.0+.
|
|
263
|
+
*
|
|
264
|
+
* @param program - The billing program to check ('external-offer' or 'external-content-link')
|
|
265
|
+
* @returns Promise resolving to availability result
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```typescript
|
|
269
|
+
* const result = await isBillingProgramAvailableAndroid('external-offer');
|
|
270
|
+
* if (result.isAvailable) {
|
|
271
|
+
* // Proceed with billing program flow
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
export const isBillingProgramAvailableAndroid = async (
|
|
276
|
+
program: BillingProgramAndroid,
|
|
277
|
+
): Promise<BillingProgramAvailabilityResultAndroid> => {
|
|
278
|
+
return ExpoIapModule.isBillingProgramAvailableAndroid(program);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Launch an external link for the specified billing program (Android only).
|
|
283
|
+
* Available in Google Play Billing Library 8.2.0+.
|
|
284
|
+
*
|
|
285
|
+
* @param params - The external link parameters
|
|
286
|
+
* @returns Promise resolving when the link is launched
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* await launchExternalLinkAndroid({
|
|
291
|
+
* billingProgram: 'external-offer',
|
|
292
|
+
* launchMode: 'launch-in-external-browser-or-app',
|
|
293
|
+
* linkType: 'link-to-digital-content-offer',
|
|
294
|
+
* linkUri: 'https://your-payment-site.com',
|
|
295
|
+
* });
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
export const launchExternalLinkAndroid = async (
|
|
299
|
+
params: LaunchExternalLinkParamsAndroid,
|
|
300
|
+
): Promise<void> => {
|
|
301
|
+
return ExpoIapModule.launchExternalLinkAndroid(params);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create billing program reporting details for Google Play reporting (Android only).
|
|
306
|
+
* Available in Google Play Billing Library 8.2.0+.
|
|
307
|
+
*
|
|
308
|
+
* Must be called AFTER successful payment in your payment system.
|
|
309
|
+
* Token must be reported to Google Play backend within 24 hours.
|
|
310
|
+
*
|
|
311
|
+
* @param program - The billing program type
|
|
312
|
+
* @returns Promise resolving to reporting details including the external transaction token
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```typescript
|
|
316
|
+
* const details = await createBillingProgramReportingDetailsAndroid('external-offer');
|
|
317
|
+
* // Report details.externalTransactionToken to Google Play within 24 hours
|
|
318
|
+
* await reportToGooglePlay(details.externalTransactionToken);
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export const createBillingProgramReportingDetailsAndroid = async (
|
|
322
|
+
program: BillingProgramAndroid,
|
|
323
|
+
): Promise<BillingProgramReportingDetailsAndroid> => {
|
|
324
|
+
return ExpoIapModule.createBillingProgramReportingDetailsAndroid(program);
|
|
325
|
+
};
|
package/src/modules/ios.ts
CHANGED
|
@@ -237,10 +237,12 @@ export const getTransactionJwsIOS: QueryField<'getTransactionJwsIOS'> = async (
|
|
|
237
237
|
*/
|
|
238
238
|
const validateReceiptIOSImpl = async (props: VerifyPurchaseProps | string) => {
|
|
239
239
|
const sku =
|
|
240
|
-
typeof props === 'string'
|
|
240
|
+
typeof props === 'string'
|
|
241
|
+
? props
|
|
242
|
+
: (props as VerifyPurchaseProps)?.apple?.sku;
|
|
241
243
|
|
|
242
244
|
if (!sku) {
|
|
243
|
-
throw new Error('validateReceiptIOS requires a SKU');
|
|
245
|
+
throw new Error('validateReceiptIOS requires a SKU (via apple.sku)');
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
return (await ExpoIapModule.validateReceiptIOS(
|