expo-iap 2.8.7 → 2.9.0-rc.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/CHANGELOG.md +28 -0
- package/CLAUDE.md +7 -0
- package/CONTRIBUTING.md +3 -4
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +7 -7
- package/android/src/main/java/expo/modules/iap/Types.kt +1 -1
- package/build/ExpoIap.types.d.ts +4 -4
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +3 -0
- package/build/ExpoIap.types.js.map +1 -1
- package/build/helpers/subscription.d.ts.map +1 -1
- package/build/helpers/subscription.js +3 -6
- package/build/helpers/subscription.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -12
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +2 -2
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIOS.types.d.ts +3 -3
- package/build/types/ExpoIapIOS.types.d.ts.map +1 -1
- package/build/types/ExpoIapIOS.types.js.map +1 -1
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js.map +1 -1
- package/ios/ExpoIap.podspec +1 -0
- package/ios/ExpoIapModule.swift +353 -1180
- package/jest.config.js +14 -17
- package/package.json +5 -3
- package/plugin/build/withIAP.d.ts +7 -1
- package/plugin/build/withIAP.js +16 -2
- package/plugin/build/withLocalOpenIAP.d.ts +9 -0
- package/plugin/build/withLocalOpenIAP.js +85 -0
- package/plugin/src/withIAP.ts +21 -2
- package/plugin/src/withLocalOpenIAP.ts +66 -0
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIap.types.ts +5 -11
- package/src/helpers/subscription.ts +21 -28
- package/src/index.ts +27 -25
- package/src/modules/android.ts +7 -7
- package/src/modules/ios.ts +11 -5
- package/src/types/ExpoIapAndroid.types.ts +3 -4
- package/src/types/ExpoIapIOS.types.ts +4 -3
- package/src/useIAP.ts +10 -4
package/jest.config.js
CHANGED
|
@@ -4,21 +4,18 @@ module.exports = {
|
|
|
4
4
|
roots: ['<rootDir>/src'],
|
|
5
5
|
testMatch: [
|
|
6
6
|
'**/__tests__/**/*.+(ts|tsx|js)',
|
|
7
|
-
'**/?(*.)+(spec|test).+(ts|tsx|js)'
|
|
7
|
+
'**/?(*.)+(spec|test).+(ts|tsx|js)'
|
|
8
8
|
],
|
|
9
9
|
transform: {
|
|
10
|
-
'^.+\\.(ts|tsx)$': [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
],
|
|
10
|
+
'^.+\\.(ts|tsx)$': ['ts-jest', {
|
|
11
|
+
tsconfig: {
|
|
12
|
+
jsx: 'react',
|
|
13
|
+
esModuleInterop: true,
|
|
14
|
+
allowSyntheticDefaultImports: true,
|
|
15
|
+
moduleResolution: 'node',
|
|
16
|
+
skipLibCheck: true,
|
|
17
|
+
}
|
|
18
|
+
}]
|
|
22
19
|
},
|
|
23
20
|
moduleNameMapper: {
|
|
24
21
|
'^react-native$': '<rootDir>/src/__mocks__/react-native.js',
|
|
@@ -37,7 +34,7 @@ module.exports = {
|
|
|
37
34
|
branches: 15,
|
|
38
35
|
functions: 15,
|
|
39
36
|
lines: 15,
|
|
40
|
-
statements: 15
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
};
|
|
37
|
+
statements: 15
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-iap",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0-rc.2",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"docs:start": "cd docs && bun run start",
|
|
26
26
|
"docs:build": "cd docs && bun run build",
|
|
27
27
|
"docs:serve": "cd docs && bun run serve",
|
|
28
|
-
"docs:install": "cd docs && bun install"
|
|
28
|
+
"docs:install": "cd docs && bun install",
|
|
29
|
+
"generate:icon": "npx sharp-cli resize 32 32 -i docs/static/img/icon.png -o docs/static/img/favicon-32x32.png && npx sharp-cli resize 16 16 -i docs/static/img/icon.png -o docs/static/img/favicon-16x16.png && npx sharp-cli resize 180 180 -i docs/static/img/icon.png -o docs/static/img/apple-touch-icon.png && npx sharp-cli resize 192 192 -i docs/static/img/icon.png -o docs/static/img/android-chrome-192x192.png && npx sharp-cli resize 512 512 -i docs/static/img/icon.png -o docs/static/img/android-chrome-512x512.png && npx sharp-cli resize 150 150 -i docs/static/img/icon.png -o docs/static/img/mstile-150x150.png && npx sharp-cli resize 1200 630 -i docs/static/img/icon.png -o docs/static/img/og-image.png && npx sharp-cli resize 1200 600 -i docs/static/img/icon.png -o docs/static/img/twitter-card.png && npx sharp-cli resize 16 16 -i docs/static/img/icon.png -o docs/static/img/favicon.png && cp docs/static/img/favicon-16x16.png docs/static/img/favicon.ico"
|
|
29
30
|
},
|
|
30
31
|
"keywords": [
|
|
31
32
|
"react-native",
|
|
@@ -60,5 +61,6 @@
|
|
|
60
61
|
},
|
|
61
62
|
"expo": {
|
|
62
63
|
"plugin": "./app.plugin.js"
|
|
63
|
-
}
|
|
64
|
+
},
|
|
65
|
+
"packageManager": "yarn@3.6.1+sha512.de524adec81a6c3d7a26d936d439d2832e351cdfc5728f9d91f3fc85dd20b04391c038e9b4ecab11cae2b0dd9f0d55fd355af766bc5c1a7f8d25d96bb2a0b2ca"
|
|
64
66
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
|
|
2
|
+
export interface ExpoIapPluginOptions {
|
|
3
|
+
/** Local development path for OpenIAP library */
|
|
4
|
+
localPath?: string;
|
|
5
|
+
/** Enable local development mode */
|
|
6
|
+
enableLocalDev?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const _default: ConfigPlugin<void | ExpoIapPluginOptions>;
|
|
3
9
|
export default _default;
|
package/plugin/build/withIAP.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
const config_plugins_1 = require("expo/config-plugins");
|
|
7
|
+
const withLocalOpenIAP_1 = __importDefault(require("./withLocalOpenIAP"));
|
|
4
8
|
const pkg = require('../../package.json');
|
|
5
9
|
// Global flag to prevent duplicate logs
|
|
6
10
|
let hasLoggedPluginExecution = false;
|
|
@@ -65,9 +69,19 @@ const withIapAndroid = (config) => {
|
|
|
65
69
|
});
|
|
66
70
|
return config;
|
|
67
71
|
};
|
|
68
|
-
const withIap = (config,
|
|
72
|
+
const withIap = (config, options) => {
|
|
69
73
|
try {
|
|
70
|
-
|
|
74
|
+
// Apply Android modifications
|
|
75
|
+
let result = withIapAndroid(config);
|
|
76
|
+
// Apply iOS local development if enabled
|
|
77
|
+
if (options?.enableLocalDev || options?.localPath) {
|
|
78
|
+
const localPath = options.localPath || '/Users/crossplatformkorea/Github/hyodotdev/openiap-apple';
|
|
79
|
+
console.log(`🔧 [expo-iap] Enabling local OpenIAP development at: ${localPath}`);
|
|
80
|
+
result = (0, withLocalOpenIAP_1.default)(result, { localPath });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log('📦 [expo-iap] Using OpenIAP from CocoaPods');
|
|
84
|
+
}
|
|
71
85
|
// Set flag after first execution to prevent duplicate logs
|
|
72
86
|
hasLoggedPluginExecution = true;
|
|
73
87
|
return result;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ConfigPlugin } from '@expo/config-plugins';
|
|
2
|
+
/**
|
|
3
|
+
* Plugin to add local OpenIAP pod dependency for development
|
|
4
|
+
* This is only for local development with openiap-apple library
|
|
5
|
+
*/
|
|
6
|
+
declare const withLocalOpenIAP: ConfigPlugin<{
|
|
7
|
+
localPath?: string;
|
|
8
|
+
} | void>;
|
|
9
|
+
export default withLocalOpenIAP;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Plugin to add local OpenIAP pod dependency for development
|
|
41
|
+
* This is only for local development with openiap-apple library
|
|
42
|
+
*/
|
|
43
|
+
const withLocalOpenIAP = (config, props) => {
|
|
44
|
+
return (0, config_plugins_1.withDangerousMod)(config, [
|
|
45
|
+
'ios',
|
|
46
|
+
async (config) => {
|
|
47
|
+
const { platformProjectRoot } = config.modRequest;
|
|
48
|
+
const podfilePath = path.join(platformProjectRoot, 'Podfile');
|
|
49
|
+
// Default local path or use provided one
|
|
50
|
+
const localOpenIapPath = props?.localPath ||
|
|
51
|
+
'/Users/crossplatformkorea/Github/hyodotdev/openiap-apple';
|
|
52
|
+
// Check if local path exists
|
|
53
|
+
if (!fs.existsSync(localOpenIapPath)) {
|
|
54
|
+
console.warn(`⚠️ Local openiap-apple path not found: ${localOpenIapPath}`);
|
|
55
|
+
console.warn(' Skipping local pod injection. Using default pod resolution.');
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
// Read Podfile
|
|
59
|
+
let podfileContent = fs.readFileSync(podfilePath, 'utf8');
|
|
60
|
+
// Check if already has the local pod reference
|
|
61
|
+
if (podfileContent.includes("pod 'openiap',")) {
|
|
62
|
+
console.log('✅ Local OpenIAP pod already configured');
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
// Find the target block and inject the local pod
|
|
66
|
+
const targetRegex = /target\s+['"][\w]+['"]\s+do\s*\n\s*use_expo_modules!/;
|
|
67
|
+
if (targetRegex.test(podfileContent)) {
|
|
68
|
+
podfileContent = podfileContent.replace(targetRegex, (match) => {
|
|
69
|
+
return `${match}
|
|
70
|
+
|
|
71
|
+
# Local OpenIAP pod for development (added by expo-iap plugin)
|
|
72
|
+
pod 'openiap', :path => '${localOpenIapPath}'`;
|
|
73
|
+
});
|
|
74
|
+
// Write back to Podfile
|
|
75
|
+
fs.writeFileSync(podfilePath, podfileContent);
|
|
76
|
+
console.log(`✅ Added local OpenIAP pod at: ${localOpenIapPath}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.warn('⚠️ Could not find target block in Podfile');
|
|
80
|
+
}
|
|
81
|
+
return config;
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
};
|
|
85
|
+
exports.default = withLocalOpenIAP;
|
package/plugin/src/withIAP.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
withAndroidManifest,
|
|
6
6
|
withAppBuildGradle,
|
|
7
7
|
} from 'expo/config-plugins';
|
|
8
|
+
import withLocalOpenIAP from './withLocalOpenIAP';
|
|
8
9
|
|
|
9
10
|
const pkg = require('../../package.json');
|
|
10
11
|
|
|
@@ -98,9 +99,27 @@ const withIapAndroid: ConfigPlugin = (config) => {
|
|
|
98
99
|
return config;
|
|
99
100
|
};
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
export interface ExpoIapPluginOptions {
|
|
103
|
+
/** Local development path for OpenIAP library */
|
|
104
|
+
localPath?: string;
|
|
105
|
+
/** Enable local development mode */
|
|
106
|
+
enableLocalDev?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (config, options) => {
|
|
102
110
|
try {
|
|
103
|
-
|
|
111
|
+
// Apply Android modifications
|
|
112
|
+
let result = withIapAndroid(config);
|
|
113
|
+
|
|
114
|
+
// Apply iOS local development if enabled
|
|
115
|
+
if (options?.enableLocalDev || options?.localPath) {
|
|
116
|
+
const localPath = options.localPath || '/Users/crossplatformkorea/Github/hyodotdev/openiap-apple';
|
|
117
|
+
console.log(`🔧 [expo-iap] Enabling local OpenIAP development at: ${localPath}`);
|
|
118
|
+
result = withLocalOpenIAP(result, { localPath });
|
|
119
|
+
} else {
|
|
120
|
+
console.log('📦 [expo-iap] Using OpenIAP from CocoaPods');
|
|
121
|
+
}
|
|
122
|
+
|
|
104
123
|
// Set flag after first execution to prevent duplicate logs
|
|
105
124
|
hasLoggedPluginExecution = true;
|
|
106
125
|
return result;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {ConfigPlugin, withDangerousMod} from '@expo/config-plugins';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin to add local OpenIAP pod dependency for development
|
|
7
|
+
* This is only for local development with openiap-apple library
|
|
8
|
+
*/
|
|
9
|
+
const withLocalOpenIAP: ConfigPlugin<{localPath?: string} | void> = (
|
|
10
|
+
config,
|
|
11
|
+
props,
|
|
12
|
+
) => {
|
|
13
|
+
return withDangerousMod(config, [
|
|
14
|
+
'ios',
|
|
15
|
+
async (config) => {
|
|
16
|
+
const {platformProjectRoot} = config.modRequest;
|
|
17
|
+
const podfilePath = path.join(platformProjectRoot, 'Podfile');
|
|
18
|
+
|
|
19
|
+
// Default local path or use provided one
|
|
20
|
+
const localOpenIapPath =
|
|
21
|
+
props?.localPath ||
|
|
22
|
+
'/Users/crossplatformkorea/Github/hyodotdev/openiap-apple';
|
|
23
|
+
|
|
24
|
+
// Check if local path exists
|
|
25
|
+
if (!fs.existsSync(localOpenIapPath)) {
|
|
26
|
+
console.warn(`⚠️ Local openiap-apple path not found: ${localOpenIapPath}`);
|
|
27
|
+
console.warn(
|
|
28
|
+
' Skipping local pod injection. Using default pod resolution.',
|
|
29
|
+
);
|
|
30
|
+
return config;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Read Podfile
|
|
34
|
+
let podfileContent = fs.readFileSync(podfilePath, 'utf8');
|
|
35
|
+
|
|
36
|
+
// Check if already has the local pod reference
|
|
37
|
+
if (podfileContent.includes("pod 'openiap',")) {
|
|
38
|
+
console.log('✅ Local OpenIAP pod already configured');
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Find the target block and inject the local pod
|
|
43
|
+
const targetRegex =
|
|
44
|
+
/target\s+['"][\w]+['"]\s+do\s*\n\s*use_expo_modules!/;
|
|
45
|
+
|
|
46
|
+
if (targetRegex.test(podfileContent)) {
|
|
47
|
+
podfileContent = podfileContent.replace(targetRegex, (match) => {
|
|
48
|
+
return `${match}
|
|
49
|
+
|
|
50
|
+
# Local OpenIAP pod for development (added by expo-iap plugin)
|
|
51
|
+
pod 'openiap', :path => '${localOpenIapPath}'`;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Write back to Podfile
|
|
55
|
+
fs.writeFileSync(podfilePath, podfileContent);
|
|
56
|
+
console.log(`✅ Added local OpenIAP pod at: ${localOpenIapPath}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.warn('⚠️ Could not find target block in Podfile');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return config;
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default withLocalOpenIAP;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/
|
|
1
|
+
{"root":["./src/withiap.ts","./src/withlocalopeniap.ts"],"version":"5.9.2"}
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
ProductAndroid,
|
|
3
3
|
PurchaseAndroid,
|
|
4
4
|
ProductSubscriptionAndroid,
|
|
5
5
|
} from './types/ExpoIapAndroid.types';
|
|
6
|
-
import {
|
|
6
|
+
import type {
|
|
7
7
|
ProductIOS,
|
|
8
8
|
PurchaseIOS,
|
|
9
9
|
ProductSubscriptionIOS,
|
|
@@ -61,15 +61,9 @@ export type SubscriptionProduct =
|
|
|
61
61
|
| (ProductSubscriptionAndroid & AndroidPlatform)
|
|
62
62
|
| (ProductSubscriptionIOS & IosPlatform);
|
|
63
63
|
|
|
64
|
-
// Re-export platform-specific types
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
ProductSubscriptionAndroid,
|
|
68
|
-
} from './types/ExpoIapAndroid.types';
|
|
69
|
-
export type {
|
|
70
|
-
PurchaseIOS,
|
|
71
|
-
ProductSubscriptionIOS,
|
|
72
|
-
} from './types/ExpoIapIOS.types';
|
|
64
|
+
// Re-export all platform-specific types to avoid deep imports
|
|
65
|
+
export * from './types/ExpoIapAndroid.types';
|
|
66
|
+
export * from './types/ExpoIapIOS.types';
|
|
73
67
|
|
|
74
68
|
// Unified purchase type for both products and subscriptions
|
|
75
69
|
export type Purchase =
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {Platform} from 'react-native';
|
|
2
|
-
import {getAvailablePurchases} from '../index';
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { getAvailablePurchases } from '../index';
|
|
3
3
|
|
|
4
4
|
export interface ActiveSubscription {
|
|
5
5
|
productId: string;
|
|
@@ -17,13 +17,13 @@ export interface ActiveSubscription {
|
|
|
17
17
|
* @returns Promise<ActiveSubscription[]> array of active subscriptions with details
|
|
18
18
|
*/
|
|
19
19
|
export const getActiveSubscriptions = async (
|
|
20
|
-
subscriptionIds?: string[]
|
|
20
|
+
subscriptionIds?: string[]
|
|
21
21
|
): Promise<ActiveSubscription[]> => {
|
|
22
22
|
try {
|
|
23
23
|
const purchases = await getAvailablePurchases();
|
|
24
24
|
const currentTime = Date.now();
|
|
25
25
|
const activeSubscriptions: ActiveSubscription[] = [];
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
// Filter purchases to find active subscriptions
|
|
28
28
|
const filteredPurchases = purchases.filter((purchase) => {
|
|
29
29
|
// If specific IDs provided, filter by them
|
|
@@ -32,17 +32,17 @@ export const getActiveSubscriptions = async (
|
|
|
32
32
|
return false;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
// Check if this purchase has subscription-specific fields
|
|
37
|
-
const hasSubscriptionFields =
|
|
37
|
+
const hasSubscriptionFields =
|
|
38
38
|
('expirationDateIOS' in purchase && purchase.expirationDateIOS) ||
|
|
39
|
-
'autoRenewingAndroid' in purchase ||
|
|
39
|
+
('autoRenewingAndroid' in purchase) ||
|
|
40
40
|
('environmentIOS' in purchase && purchase.environmentIOS === 'Sandbox');
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
if (!hasSubscriptionFields) {
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Check if it's actually active
|
|
47
47
|
if (Platform.OS === 'ios') {
|
|
48
48
|
if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
|
|
@@ -53,15 +53,8 @@ export const getActiveSubscriptions = async (
|
|
|
53
53
|
if ('environmentIOS' in purchase && purchase.environmentIOS) {
|
|
54
54
|
const dayInMs = 24 * 60 * 60 * 1000;
|
|
55
55
|
// If no expiration date, consider active if transaction is recent (within 24 hours for Sandbox)
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
!purchase.expirationDateIOS
|
|
59
|
-
) {
|
|
60
|
-
if (
|
|
61
|
-
purchase.environmentIOS === 'Sandbox' &&
|
|
62
|
-
purchase.transactionDate &&
|
|
63
|
-
currentTime - purchase.transactionDate < dayInMs
|
|
64
|
-
) {
|
|
56
|
+
if (!('expirationDateIOS' in purchase) || !purchase.expirationDateIOS) {
|
|
57
|
+
if (purchase.environmentIOS === 'Sandbox' && purchase.transactionDate && (currentTime - purchase.transactionDate) < dayInMs) {
|
|
65
58
|
return true;
|
|
66
59
|
}
|
|
67
60
|
}
|
|
@@ -70,31 +63,31 @@ export const getActiveSubscriptions = async (
|
|
|
70
63
|
// For Android, if it's in the purchases list, it's active
|
|
71
64
|
return true;
|
|
72
65
|
}
|
|
73
|
-
|
|
66
|
+
|
|
74
67
|
return false;
|
|
75
68
|
});
|
|
76
|
-
|
|
69
|
+
|
|
77
70
|
// Convert to ActiveSubscription format
|
|
78
71
|
for (const purchase of filteredPurchases) {
|
|
79
72
|
const subscription: ActiveSubscription = {
|
|
80
73
|
productId: purchase.productId,
|
|
81
74
|
isActive: true,
|
|
82
75
|
};
|
|
83
|
-
|
|
76
|
+
|
|
84
77
|
// Add platform-specific details
|
|
85
78
|
if (Platform.OS === 'ios') {
|
|
86
79
|
if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
|
|
87
80
|
const expirationDate = new Date(purchase.expirationDateIOS);
|
|
88
81
|
subscription.expirationDateIOS = expirationDate;
|
|
89
|
-
|
|
82
|
+
|
|
90
83
|
// Calculate days until expiration (round to nearest day)
|
|
91
84
|
const daysUntilExpiration = Math.round(
|
|
92
|
-
(purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24)
|
|
85
|
+
(purchase.expirationDateIOS - currentTime) / (1000 * 60 * 60 * 24)
|
|
93
86
|
);
|
|
94
87
|
subscription.daysUntilExpirationIOS = daysUntilExpiration;
|
|
95
88
|
subscription.willExpireSoon = daysUntilExpiration <= 7;
|
|
96
89
|
}
|
|
97
|
-
|
|
90
|
+
|
|
98
91
|
if ('environmentIOS' in purchase) {
|
|
99
92
|
subscription.environmentIOS = purchase.environmentIOS;
|
|
100
93
|
}
|
|
@@ -105,10 +98,10 @@ export const getActiveSubscriptions = async (
|
|
|
105
98
|
subscription.willExpireSoon = !purchase.autoRenewingAndroid;
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
|
-
|
|
101
|
+
|
|
109
102
|
activeSubscriptions.push(subscription);
|
|
110
103
|
}
|
|
111
|
-
|
|
104
|
+
|
|
112
105
|
return activeSubscriptions;
|
|
113
106
|
} catch (error) {
|
|
114
107
|
console.error('Error getting active subscriptions:', error);
|
|
@@ -122,8 +115,8 @@ export const getActiveSubscriptions = async (
|
|
|
122
115
|
* @returns Promise<boolean> true if user has at least one active subscription
|
|
123
116
|
*/
|
|
124
117
|
export const hasActiveSubscriptions = async (
|
|
125
|
-
subscriptionIds?: string[]
|
|
118
|
+
subscriptionIds?: string[]
|
|
126
119
|
): Promise<boolean> => {
|
|
127
120
|
const subscriptions = await getActiveSubscriptions(subscriptionIds);
|
|
128
121
|
return subscriptions.length > 0;
|
|
129
|
-
};
|
|
122
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ export {
|
|
|
45
45
|
// Get the native constant value
|
|
46
46
|
export const PI = ExpoIapModule.PI;
|
|
47
47
|
|
|
48
|
-
export enum
|
|
48
|
+
export enum OpenIapEvent {
|
|
49
49
|
PurchaseUpdated = 'purchase-updated',
|
|
50
50
|
PurchaseError = 'purchase-error',
|
|
51
51
|
/** @deprecated Use PurchaseUpdated instead. This will be removed in a future version. */
|
|
@@ -73,7 +73,7 @@ export const purchaseUpdatedListener = (
|
|
|
73
73
|
listener: (event: Purchase) => void,
|
|
74
74
|
) => {
|
|
75
75
|
const emitterSubscription = emitter.addListener(
|
|
76
|
-
|
|
76
|
+
OpenIapEvent.PurchaseUpdated,
|
|
77
77
|
listener,
|
|
78
78
|
);
|
|
79
79
|
return emitterSubscription;
|
|
@@ -82,7 +82,7 @@ export const purchaseUpdatedListener = (
|
|
|
82
82
|
export const purchaseErrorListener = (
|
|
83
83
|
listener: (error: PurchaseError) => void,
|
|
84
84
|
) => {
|
|
85
|
-
return emitter.addListener(
|
|
85
|
+
return emitter.addListener(OpenIapEvent.PurchaseError, listener);
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
/**
|
|
@@ -114,7 +114,7 @@ export const promotedProductListenerIOS = (
|
|
|
114
114
|
);
|
|
115
115
|
return {remove: () => {}};
|
|
116
116
|
}
|
|
117
|
-
return emitter.addListener(
|
|
117
|
+
return emitter.addListener(OpenIapEvent.PromotedProductIOS, listener);
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
export function initConnection(): Promise<boolean> {
|
|
@@ -273,11 +273,11 @@ export const fetchProducts = async ({
|
|
|
273
273
|
|
|
274
274
|
/**
|
|
275
275
|
* @deprecated Use `fetchProducts` instead. This method will be removed in version 3.0.0.
|
|
276
|
-
*
|
|
276
|
+
*
|
|
277
277
|
* The 'request' prefix should only be used for event-based operations that trigger
|
|
278
278
|
* purchase flows. Since this function simply fetches product information, it has been
|
|
279
279
|
* renamed to `fetchProducts` to follow OpenIAP terminology guidelines.
|
|
280
|
-
*
|
|
280
|
+
*
|
|
281
281
|
* @example
|
|
282
282
|
* ```typescript
|
|
283
283
|
* // Old way (deprecated)
|
|
@@ -285,7 +285,7 @@ export const fetchProducts = async ({
|
|
|
285
285
|
* skus: ['com.example.product1'],
|
|
286
286
|
* type: 'inapp'
|
|
287
287
|
* });
|
|
288
|
-
*
|
|
288
|
+
*
|
|
289
289
|
* // New way (recommended)
|
|
290
290
|
* const products = await fetchProducts({
|
|
291
291
|
* skus: ['com.example.product1'],
|
|
@@ -301,9 +301,9 @@ export const requestProducts = async ({
|
|
|
301
301
|
type?: 'inapp' | 'subs';
|
|
302
302
|
}): Promise<Product[] | SubscriptionProduct[]> => {
|
|
303
303
|
console.warn(
|
|
304
|
-
"`requestProducts` is deprecated. Use `fetchProducts` instead. The 'request' prefix should only be used for event-based operations. This method will be removed in version 3.0.0."
|
|
304
|
+
"`requestProducts` is deprecated. Use `fetchProducts` instead. The 'request' prefix should only be used for event-based operations. This method will be removed in version 3.0.0."
|
|
305
305
|
);
|
|
306
|
-
return fetchProducts({skus, type});
|
|
306
|
+
return fetchProducts({ skus, type });
|
|
307
307
|
};
|
|
308
308
|
|
|
309
309
|
/**
|
|
@@ -326,10 +326,8 @@ export const getPurchaseHistory = ({
|
|
|
326
326
|
'`getPurchaseHistory` is deprecated. Use `getPurchaseHistories` instead. This function will be removed in version 3.0.0.',
|
|
327
327
|
);
|
|
328
328
|
return getPurchaseHistories({
|
|
329
|
-
alsoPublishToEventListenerIOS:
|
|
330
|
-
|
|
331
|
-
onlyIncludeActiveItemsIOS:
|
|
332
|
-
onlyIncludeActiveItemsIOS ?? onlyIncludeActiveItems,
|
|
329
|
+
alsoPublishToEventListenerIOS: alsoPublishToEventListenerIOS ?? alsoPublishToEventListener,
|
|
330
|
+
onlyIncludeActiveItemsIOS: onlyIncludeActiveItemsIOS ?? onlyIncludeActiveItems,
|
|
333
331
|
});
|
|
334
332
|
};
|
|
335
333
|
|
|
@@ -392,9 +390,8 @@ export const getAvailablePurchases = ({
|
|
|
392
390
|
),
|
|
393
391
|
android: async () => {
|
|
394
392
|
const products = await ExpoIapModule.getAvailableItemsByType('inapp');
|
|
395
|
-
const subscriptions =
|
|
396
|
-
'subs'
|
|
397
|
-
);
|
|
393
|
+
const subscriptions =
|
|
394
|
+
await ExpoIapModule.getAvailableItemsByType('subs');
|
|
398
395
|
return products.concat(subscriptions);
|
|
399
396
|
},
|
|
400
397
|
}) || (() => Promise.resolve([]))
|
|
@@ -468,7 +465,11 @@ const normalizeRequestProps = (
|
|
|
468
465
|
*/
|
|
469
466
|
export const requestPurchase = (
|
|
470
467
|
requestObj: PurchaseRequest,
|
|
471
|
-
): Promise<
|
|
468
|
+
): Promise<
|
|
469
|
+
| Purchase
|
|
470
|
+
| Purchase[]
|
|
471
|
+
| void
|
|
472
|
+
> => {
|
|
472
473
|
const {request, type = 'inapp'} = requestObj;
|
|
473
474
|
|
|
474
475
|
if (Platform.OS === 'ios') {
|
|
@@ -498,7 +499,9 @@ export const requestPurchase = (
|
|
|
498
499
|
offer,
|
|
499
500
|
);
|
|
500
501
|
|
|
501
|
-
return type === 'inapp'
|
|
502
|
+
return type === 'inapp'
|
|
503
|
+
? (purchase as Purchase)
|
|
504
|
+
: (purchase as Purchase);
|
|
502
505
|
})();
|
|
503
506
|
}
|
|
504
507
|
|
|
@@ -626,11 +629,10 @@ export const finishTransaction = ({
|
|
|
626
629
|
},
|
|
627
630
|
android: async () => {
|
|
628
631
|
const androidPurchase = purchase as PurchaseAndroid;
|
|
629
|
-
|
|
632
|
+
|
|
630
633
|
// Use purchaseToken if available, fallback to purchaseTokenAndroid for backward compatibility
|
|
631
|
-
const token =
|
|
632
|
-
|
|
633
|
-
|
|
634
|
+
const token = androidPurchase.purchaseToken || androidPurchase.purchaseTokenAndroid;
|
|
635
|
+
|
|
634
636
|
if (!token) {
|
|
635
637
|
return Promise.reject(
|
|
636
638
|
new PurchaseError(
|
|
@@ -640,8 +642,8 @@ export const finishTransaction = ({
|
|
|
640
642
|
undefined,
|
|
641
643
|
'E_DEVELOPER_ERROR' as ErrorCode,
|
|
642
644
|
androidPurchase.productId,
|
|
643
|
-
'android'
|
|
644
|
-
)
|
|
645
|
+
'android'
|
|
646
|
+
)
|
|
645
647
|
);
|
|
646
648
|
}
|
|
647
649
|
|
|
@@ -674,7 +676,7 @@ export const getStorefrontIOS = (): Promise<string> => {
|
|
|
674
676
|
console.warn('getStorefrontIOS: This method is only available on iOS');
|
|
675
677
|
return Promise.resolve('');
|
|
676
678
|
}
|
|
677
|
-
return ExpoIapModule.
|
|
679
|
+
return ExpoIapModule.getStorefrontIOS();
|
|
678
680
|
};
|
|
679
681
|
|
|
680
682
|
/**
|
package/src/modules/android.ts
CHANGED
|
@@ -26,7 +26,7 @@ export function isProductAndroid<T extends {platform?: string}>(
|
|
|
26
26
|
* @param {string} params.sku - The product's SKU (on Android)
|
|
27
27
|
* @param {string} params.packageName - The package name of your Android app (e.g., 'com.example.app')
|
|
28
28
|
* @returns {Promise<void>}
|
|
29
|
-
*
|
|
29
|
+
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```typescript
|
|
32
32
|
* await deepLinkToSubscriptionsAndroid({
|
|
@@ -43,11 +43,9 @@ export const deepLinkToSubscriptionsAndroid = async ({
|
|
|
43
43
|
packageName: string;
|
|
44
44
|
}): Promise<void> => {
|
|
45
45
|
if (!packageName) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
'packageName is required for deepLinkToSubscriptionsAndroid',
|
|
48
|
-
);
|
|
46
|
+
throw new Error('packageName is required for deepLinkToSubscriptionsAndroid');
|
|
49
47
|
}
|
|
50
|
-
|
|
48
|
+
|
|
51
49
|
return Linking.openURL(
|
|
52
50
|
`https://play.google.com/store/account/subscriptions?package=${packageName}&sku=${sku}`,
|
|
53
51
|
);
|
|
@@ -120,9 +118,11 @@ export const acknowledgePurchaseAndroid = ({
|
|
|
120
118
|
* Open the Google Play Store to redeem offer codes (Android only).
|
|
121
119
|
* Note: Google Play does not provide a direct API to redeem codes within the app.
|
|
122
120
|
* This function opens the Play Store where users can manually enter their codes.
|
|
123
|
-
*
|
|
121
|
+
*
|
|
124
122
|
* @returns {Promise<void>}
|
|
125
123
|
*/
|
|
126
124
|
export const openRedeemOfferCodeAndroid = async (): Promise<void> => {
|
|
127
|
-
return Linking.openURL(
|
|
125
|
+
return Linking.openURL(
|
|
126
|
+
`https://play.google.com/redeem?code=`
|
|
127
|
+
);
|
|
128
128
|
};
|