expo-iap 2.3.4 → 2.3.5-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/build/modules/android.d.ts +3 -3
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +5 -4
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.js.map +1 -1
- package/iap.md +20 -38
- package/ios/ExpoIapModule.swift +44 -32
- package/package.json +1 -1
- package/src/modules/android.ts +5 -4
- package/src/modules/ios.ts +5 -5
|
@@ -19,10 +19,10 @@ export declare const deepLinkToSubscriptionsAndroid: ({ sku, }: {
|
|
|
19
19
|
* Use server side validation instead for your production builds
|
|
20
20
|
* @param {string} packageName package name of your app.
|
|
21
21
|
* @param {string} productId product id for your in app product.
|
|
22
|
-
* @param {string} productToken token for your purchase.
|
|
23
|
-
* @param {string} accessToken
|
|
22
|
+
* @param {string} productToken token for your purchase (called 'token' in the API documentation).
|
|
23
|
+
* @param {string} accessToken OAuth access token with androidpublisher scope. Required for authentication.
|
|
24
24
|
* @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.
|
|
25
|
-
* @returns {Promise<
|
|
25
|
+
* @returns {Promise<ReceiptAndroid>}
|
|
26
26
|
*/
|
|
27
27
|
export declare const validateReceiptAndroid: ({ packageName, productId, productToken, accessToken, isSub, }: {
|
|
28
28
|
packageName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../src/modules/android.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC;AAI7D,wBAAgB,gBAAgB,CAAC,CAAC,SAAS;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,EAC5D,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,CAAC,GAAG;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAC,CAOnC;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,aAExC;IACD,GAAG,EAAE,MAAM,CAAC;CACb,KAAG,QAAQ,IAAI,CAIf,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,kEAMhC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,KAAG,QAAQ,cAAc,
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../src/modules/android.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAC,cAAc,EAAC,MAAM,+BAA+B,CAAC;AAI7D,wBAAgB,gBAAgB,CAAC,CAAC,SAAS;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,EAC5D,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,CAAC,GAAG;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAC,CAOnC;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,aAExC;IACD,GAAG,EAAE,MAAM,CAAC;CACb,KAAG,QAAQ,IAAI,CAIf,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,kEAMhC;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,KAAG,QAAQ,cAAc,CAuBzB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,iCAGpC;IACD,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,KAAG,QAAQ,cAAc,GAAG,OAAO,GAAG,IAAI,CAE1C,CAAC"}
|
package/build/modules/android.js
CHANGED
|
@@ -21,20 +21,21 @@ export const deepLinkToSubscriptionsAndroid = async ({ sku, }) => {
|
|
|
21
21
|
* Use server side validation instead for your production builds
|
|
22
22
|
* @param {string} packageName package name of your app.
|
|
23
23
|
* @param {string} productId product id for your in app product.
|
|
24
|
-
* @param {string} productToken token for your purchase.
|
|
25
|
-
* @param {string} accessToken
|
|
24
|
+
* @param {string} productToken token for your purchase (called 'token' in the API documentation).
|
|
25
|
+
* @param {string} accessToken OAuth access token with androidpublisher scope. Required for authentication.
|
|
26
26
|
* @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.
|
|
27
|
-
* @returns {Promise<
|
|
27
|
+
* @returns {Promise<ReceiptAndroid>}
|
|
28
28
|
*/
|
|
29
29
|
export const validateReceiptAndroid = async ({ packageName, productId, productToken, accessToken, isSub, }) => {
|
|
30
30
|
const type = isSub ? 'subscriptions' : 'products';
|
|
31
31
|
const url = 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications' +
|
|
32
32
|
`/${packageName}/purchases/${type}/${productId}` +
|
|
33
|
-
`/tokens/${productToken}
|
|
33
|
+
`/tokens/${productToken}`;
|
|
34
34
|
const response = await fetch(url, {
|
|
35
35
|
method: 'GET',
|
|
36
36
|
headers: {
|
|
37
37
|
'Content-Type': 'application/json',
|
|
38
|
+
Authorization: `Bearer ${accessToken}`,
|
|
38
39
|
},
|
|
39
40
|
});
|
|
40
41
|
if (!response.ok) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android.js","sourceRoot":"","sources":["../../src/modules/android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAGrC,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAE7C,cAAc;AACd,MAAM,UAAU,gBAAgB,CAC9B,IAAa;IAEb,OAAO,CACL,IAAI,IAAI,IAAI;QACZ,OAAO,IAAI,KAAK,QAAQ;QACxB,UAAU,IAAI,IAAI;QAClB,IAAI,CAAC,QAAQ,KAAK,SAAS,CAC5B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,KAAK,EAAE,EACnD,GAAG,GAGJ,EAAiB,EAAE;IAClB,OAAO,OAAO,CAAC,OAAO,CACpB,+DAA+D,MAAM,aAAa,CAAC,cAAc,EAAE,QAAQ,GAAG,EAAE,CACjH,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAAE,EAC3C,WAAW,EACX,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,GAON,EAA2B,EAAE;IAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC;IAElD,MAAM,GAAG,GACP,0EAA0E;QAC1E,IAAI,WAAW,cAAc,IAAI,IAAI,SAAS,EAAE;QAChD,WAAW,YAAY,
|
|
1
|
+
{"version":3,"file":"android.js","sourceRoot":"","sources":["../../src/modules/android.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAGrC,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAE7C,cAAc;AACd,MAAM,UAAU,gBAAgB,CAC9B,IAAa;IAEb,OAAO,CACL,IAAI,IAAI,IAAI;QACZ,OAAO,IAAI,KAAK,QAAQ;QACxB,UAAU,IAAI,IAAI;QAClB,IAAI,CAAC,QAAQ,KAAK,SAAS,CAC5B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,KAAK,EAAE,EACnD,GAAG,GAGJ,EAAiB,EAAE;IAClB,OAAO,OAAO,CAAC,OAAO,CACpB,+DAA+D,MAAM,aAAa,CAAC,cAAc,EAAE,QAAQ,GAAG,EAAE,CACjH,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAAE,EAC3C,WAAW,EACX,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,GAON,EAA2B,EAAE;IAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC;IAElD,MAAM,GAAG,GACP,0EAA0E;QAC1E,IAAI,WAAW,cAAc,IAAI,IAAI,SAAS,EAAE;QAChD,WAAW,YAAY,EAAE,CAAC;IAE5B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,WAAW,EAAE;SACvC;KACF,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE;YAClD,UAAU,EAAE,QAAQ,CAAC,MAAM;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,EACzC,KAAK,EACL,gBAAgB,GAIjB,EAA4C,EAAE;IAC7C,OAAO,aAAa,CAAC,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;AACpE,CAAC,CAAC","sourcesContent":["import {Linking} from 'react-native';\nimport {PurchaseResult} from '../ExpoIap.types';\nimport {ReceiptAndroid} from '../types/ExpoIapAndroid.types';\nimport ExpoIapModule from '../ExpoIapModule';\n\n// Type guards\nexport function isProductAndroid<T extends {platform?: string}>(\n item: unknown,\n): item is T & {platform: 'android'} {\n return (\n item != null &&\n typeof item === 'object' &&\n 'platform' in item &&\n item.platform === 'android'\n );\n}\n\n/**\n * Deep link to subscriptions screen on Android.\n * @param {string} sku The product's SKU (on Android)\n * @returns {Promise<void>}\n */\nexport const deepLinkToSubscriptionsAndroid = async ({\n sku,\n}: {\n sku: string;\n}): Promise<void> => {\n return Linking.openURL(\n `https://play.google.com/store/account/subscriptions?package=${await ExpoIapModule.getPackageName()}&sku=${sku}`,\n );\n};\n\n/**\n * Validate receipt for Android. NOTE: This method is here for debugging purposes only. Including\n * your access token in the binary you ship to users is potentially dangerous.\n * Use server side validation instead for your production builds\n * @param {string} packageName package name of your app.\n * @param {string} productId product id for your in app product.\n * @param {string} productToken token for your purchase (called 'token' in the API documentation).\n * @param {string} accessToken OAuth access token with androidpublisher scope. Required for authentication.\n * @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.\n * @returns {Promise<ReceiptAndroid>}\n */\nexport const validateReceiptAndroid = async ({\n packageName,\n productId,\n productToken,\n accessToken,\n isSub,\n}: {\n packageName: string;\n productId: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n}): Promise<ReceiptAndroid> => {\n const type = isSub ? 'subscriptions' : 'products';\n\n const url =\n 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications' +\n `/${packageName}/purchases/${type}/${productId}` +\n `/tokens/${productToken}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n throw Object.assign(new Error(response.statusText), {\n statusCode: response.status,\n });\n }\n\n return response.json();\n};\n\n/**\n * Acknowledge a product (on Android.) No-op on iOS.\n * @param {string} token The product's token (on Android)\n * @returns {Promise<PurchaseResult | void>}\n */\nexport const acknowledgePurchaseAndroid = ({\n token,\n developerPayload,\n}: {\n token: string;\n developerPayload?: string;\n}): Promise<PurchaseResult | boolean | void> => {\n return ExpoIapModule.acknowledgePurchase(token, developerPayload);\n};\n"]}
|
package/build/modules/ios.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {emitter, IapEvent} from '..';\nimport {ProductPurchase, PurchaseError} from '../ExpoIap.types';\nimport type {ProductStatusIos} from '../types/ExpoIapIos.types';\nimport ExpoIapModule from '../ExpoIapModule';\n\nexport type TransactionEvent = {\n transaction?: ProductPurchase;\n error?: PurchaseError;\n};\n\n// Listeners\nexport const transactionUpdatedIos = (\n listener: (event: TransactionEvent) => void,\n) => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n return emitter.addListener(IapEvent.TransactionIapUpdated, listener);\n};\n\n// Type guards\nexport function isProductIos<T extends {platform?: string}>(\n item: unknown,\n): item is T & {platform: 'ios'} {\n return (\n item != null &&\n typeof item === 'object' &&\n 'platform' in item &&\n item.platform === 'ios'\n );\n}\n\n// Functions\n/**\n * Sync state with Appstore (iOS only)\n * https://developer.apple.com/documentation/storekit/appstore/3791906-sync\n */\nexport const sync = (): Promise<null> => ExpoIapModule.sync();\n\n/**\n *\n */\nexport const isEligibleForIntroOffer = (groupID: string): Promise<boolean> =>\n ExpoIapModule.isEligibleForIntroOffer(groupID);\n\n/**\n *\n */\n\nexport const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>\n ExpoIapModule.subscriptionStatus(sku);\n\n/**\n *\n */\nexport const currentEntitlement = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.currentEntitlement(sku);\n\n/**\n *\n */\nexport const latestTransaction = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.latestTransaction(sku);\n\n/**\n *\n */\ntype RefundRequestStatus = 'success' | 'userCancelled';\nexport const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>\n ExpoIapModule.beginRefundRequest(sku);\n\n/**\n * Shows the system UI for managing subscriptions.\n * When the user changes subscription renewal status, the system will emit events to\n * purchaseUpdatedListener and transactionUpdatedIos listeners.\n * @returns {Promise<null>}\n */\nexport const showManageSubscriptions = (): Promise<null> =>\n ExpoIapModule.showManageSubscriptions();\n\n/**\n * Get the receipt data from the iOS device.\n * This returns the base64 encoded receipt data which can be sent to your server\n * for verification with Apple's server.\n
|
|
1
|
+
{"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {emitter, IapEvent} from '..';\nimport {ProductPurchase, PurchaseError} from '../ExpoIap.types';\nimport type {ProductStatusIos} from '../types/ExpoIapIos.types';\nimport ExpoIapModule from '../ExpoIapModule';\n\nexport type TransactionEvent = {\n transaction?: ProductPurchase;\n error?: PurchaseError;\n};\n\n// Listeners\nexport const transactionUpdatedIos = (\n listener: (event: TransactionEvent) => void,\n) => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n return emitter.addListener(IapEvent.TransactionIapUpdated, listener);\n};\n\n// Type guards\nexport function isProductIos<T extends {platform?: string}>(\n item: unknown,\n): item is T & {platform: 'ios'} {\n return (\n item != null &&\n typeof item === 'object' &&\n 'platform' in item &&\n item.platform === 'ios'\n );\n}\n\n// Functions\n/**\n * Sync state with Appstore (iOS only)\n * https://developer.apple.com/documentation/storekit/appstore/3791906-sync\n */\nexport const sync = (): Promise<null> => ExpoIapModule.sync();\n\n/**\n *\n */\nexport const isEligibleForIntroOffer = (groupID: string): Promise<boolean> =>\n ExpoIapModule.isEligibleForIntroOffer(groupID);\n\n/**\n *\n */\n\nexport const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>\n ExpoIapModule.subscriptionStatus(sku);\n\n/**\n *\n */\nexport const currentEntitlement = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.currentEntitlement(sku);\n\n/**\n *\n */\nexport const latestTransaction = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.latestTransaction(sku);\n\n/**\n *\n */\ntype RefundRequestStatus = 'success' | 'userCancelled';\nexport const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>\n ExpoIapModule.beginRefundRequest(sku);\n\n/**\n * Shows the system UI for managing subscriptions.\n * When the user changes subscription renewal status, the system will emit events to \n * purchaseUpdatedListener and transactionUpdatedIos listeners.\n * @returns {Promise<null>}\n */\nexport const showManageSubscriptions = (): Promise<null> =>\n ExpoIapModule.showManageSubscriptions();\n\n/**\n * Get the receipt data from the iOS device.\n * This returns the base64 encoded receipt data which can be sent to your server\n * for verification with Apple's server.\n * \n * NOTE: For proper security, always verify receipts on your server using\n * Apple's verifyReceipt endpoint, not directly from the app.\n * \n * @returns {Promise<string>} Base64 encoded receipt data\n */\nexport const getReceiptIos = (): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getReceiptData();\n};\n\n/**\n * Check if a transaction is verified through StoreKit 2.\n * StoreKit 2 performs local verification of transaction JWS signatures.\n * \n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<boolean>} True if the transaction is verified\n */\nexport const isTransactionVerified = (sku: string): Promise<boolean> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.isTransactionVerified(sku);\n};\n\n/**\n * Get the JWS representation of a purchase for server-side verification.\n * The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.\n * \n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<string>} JWS representation of the transaction\n */\nexport const getTransactionJws = (sku: string): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getTransactionJws(sku);\n};\n\n/**\n * Validate receipt for iOS using StoreKit 2's built-in verification.\n * Returns receipt data and verification information to help with server-side validation.\n *\n * NOTE: For proper security, Apple recommends verifying receipts on your server using\n * the verifyReceipt endpoint rather than relying solely on client-side verification.\n *\n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<{\n * isValid: boolean;\n * receiptData: string;\n * jwsRepresentation: string;\n * latestTransaction?: ProductPurchase;\n * }>}\n */\nexport const validateReceiptIos = async (\n sku: string,\n): Promise<{\n isValid: boolean;\n receiptData: string;\n jwsRepresentation: string;\n latestTransaction?: ProductPurchase;\n}> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n const result = await ExpoIapModule.validateReceiptIos(sku);\n return result;\n};\n"]}
|
package/iap.md
CHANGED
|
@@ -209,8 +209,8 @@ Transactions map to `Purchase` or `SubscriptionPurchase` with platform-specific
|
|
|
209
209
|
Below is a simple example of fetching products and making a purchase with `expo-iap` in a managed workflow, updated to use the new `requestPurchase` signature:
|
|
210
210
|
|
|
211
211
|
```tsx
|
|
212
|
-
import {useEffect, useState} from 'react';
|
|
213
|
-
import {Button, Text, View} from 'react-native';
|
|
212
|
+
import { useEffect, useState } from 'react';
|
|
213
|
+
import { Button, Text, View } from 'react-native';
|
|
214
214
|
import {
|
|
215
215
|
initConnection,
|
|
216
216
|
endConnection,
|
|
@@ -237,7 +237,7 @@ export default function SimpleIAP() {
|
|
|
237
237
|
|
|
238
238
|
const purchaseListener = purchaseUpdatedListener(async (purchase) => {
|
|
239
239
|
if (purchase) {
|
|
240
|
-
await finishTransaction({purchase, isConsumable: true});
|
|
240
|
+
await finishTransaction({ purchase, isConsumable: true });
|
|
241
241
|
alert('Purchase completed!');
|
|
242
242
|
}
|
|
243
243
|
});
|
|
@@ -252,12 +252,12 @@ export default function SimpleIAP() {
|
|
|
252
252
|
const buyItem = async () => {
|
|
253
253
|
if (!product) return;
|
|
254
254
|
await requestPurchase({
|
|
255
|
-
request: {skus: [product.id]}, // Android expects 'skus'; iOS would use 'sku'
|
|
255
|
+
request: { skus: [product.id] }, // Android expects 'skus'; iOS would use 'sku'
|
|
256
256
|
});
|
|
257
257
|
};
|
|
258
258
|
|
|
259
259
|
return (
|
|
260
|
-
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
|
|
260
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
261
261
|
<Text>{isConnected ? 'Connected' : 'Connecting...'}</Text>
|
|
262
262
|
{product ? (
|
|
263
263
|
<>
|
|
@@ -277,7 +277,7 @@ export default function SimpleIAP() {
|
|
|
277
277
|
The `useIAP` hook simplifies managing in-app purchases. Below is an example updated to use the new `requestPurchase` signature:
|
|
278
278
|
|
|
279
279
|
```tsx
|
|
280
|
-
import {useEffect, useState} from 'react';
|
|
280
|
+
import { useEffect, useState } from 'react';
|
|
281
281
|
import {
|
|
282
282
|
SafeAreaView,
|
|
283
283
|
ScrollView,
|
|
@@ -289,20 +289,12 @@ import {
|
|
|
289
289
|
InteractionManager,
|
|
290
290
|
Alert,
|
|
291
291
|
} from 'react-native';
|
|
292
|
-
import {useIAP} from 'expo-iap';
|
|
293
|
-
import type {ProductPurchase, SubscriptionProduct} from 'expo-iap';
|
|
292
|
+
import { useIAP } from 'expo-iap';
|
|
293
|
+
import type { ProductPurchase, SubscriptionProduct } from 'expo-iap';
|
|
294
294
|
|
|
295
295
|
// Define SKUs
|
|
296
|
-
const productSkus = [
|
|
297
|
-
|
|
298
|
-
'cpk.points.5000',
|
|
299
|
-
'cpk.points.10000',
|
|
300
|
-
'cpk.points.30000',
|
|
301
|
-
];
|
|
302
|
-
const subscriptionSkus = [
|
|
303
|
-
'cpk.membership.monthly.bronze',
|
|
304
|
-
'cpk.membership.monthly.silver',
|
|
305
|
-
];
|
|
296
|
+
const productSkus = ['cpk.points.1000', 'cpk.points.5000', 'cpk.points.10000', 'cpk.points.30000'];
|
|
297
|
+
const subscriptionSkus = ['cpk.membership.monthly.bronze', 'cpk.membership.monthly.silver'];
|
|
306
298
|
|
|
307
299
|
// Define operations
|
|
308
300
|
const operations = ['getProducts', 'getSubscriptions'] as const;
|
|
@@ -329,10 +321,7 @@ export default function IAPWithHook() {
|
|
|
329
321
|
|
|
330
322
|
const initializeIAP = async () => {
|
|
331
323
|
try {
|
|
332
|
-
await Promise.all([
|
|
333
|
-
getProducts(productSkus),
|
|
334
|
-
getSubscriptions(subscriptionSkus),
|
|
335
|
-
]);
|
|
324
|
+
await Promise.all([getProducts(productSkus), getSubscriptions(subscriptionSkus)]);
|
|
336
325
|
setIsReady(true);
|
|
337
326
|
} catch (error) {
|
|
338
327
|
console.error('Error initializing IAP:', error);
|
|
@@ -400,10 +389,7 @@ export default function IAPWithHook() {
|
|
|
400
389
|
<View style={styles.buttons}>
|
|
401
390
|
<ScrollView contentContainerStyle={styles.buttonsWrapper} horizontal>
|
|
402
391
|
{operations.map((operation) => (
|
|
403
|
-
<Pressable
|
|
404
|
-
key={operation}
|
|
405
|
-
onPress={() => handleOperation(operation)}
|
|
406
|
-
>
|
|
392
|
+
<Pressable key={operation} onPress={() => handleOperation(operation)}>
|
|
407
393
|
<View style={styles.buttonView}>
|
|
408
394
|
<Text>{operation}</Text>
|
|
409
395
|
</View>
|
|
@@ -415,10 +401,10 @@ export default function IAPWithHook() {
|
|
|
415
401
|
{!isReady ? (
|
|
416
402
|
<Text>Loading...</Text>
|
|
417
403
|
) : (
|
|
418
|
-
<View style={{gap: 12}}>
|
|
419
|
-
<Text style={{fontSize: 20}}>Products</Text>
|
|
404
|
+
<View style={{ gap: 12 }}>
|
|
405
|
+
<Text style={{ fontSize: 20 }}>Products</Text>
|
|
420
406
|
{products.map((item) => (
|
|
421
|
-
<View key={item.id} style={{gap: 12}}>
|
|
407
|
+
<View key={item.id} style={{ gap: 12 }}>
|
|
422
408
|
<Text>
|
|
423
409
|
{item.title} -{' '}
|
|
424
410
|
{item.platform === 'android'
|
|
@@ -429,24 +415,20 @@ export default function IAPWithHook() {
|
|
|
429
415
|
title="Buy"
|
|
430
416
|
onPress={() =>
|
|
431
417
|
requestPurchase({
|
|
432
|
-
request:
|
|
433
|
-
item.platform === 'android'
|
|
434
|
-
? {skus: [item.id]}
|
|
435
|
-
: {sku: item.id},
|
|
418
|
+
request: item.platform === 'android' ? { skus: [item.id] } : { sku: item.id },
|
|
436
419
|
})
|
|
437
420
|
}
|
|
438
421
|
/>
|
|
439
422
|
</View>
|
|
440
423
|
))}
|
|
441
424
|
|
|
442
|
-
<Text style={{fontSize: 20}}>Subscriptions</Text>
|
|
425
|
+
<Text style={{ fontSize: 20 }}>Subscriptions</Text>
|
|
443
426
|
{subscriptions.map((item) => (
|
|
444
|
-
<View key={item.id} style={{gap: 12}}>
|
|
427
|
+
<View key={item.id} style={{ gap: 12 }}>
|
|
445
428
|
<Text>
|
|
446
429
|
{item.title || item.displayName} -{' '}
|
|
447
430
|
{item.platform === 'android' && item.subscriptionOfferDetails
|
|
448
|
-
? item.subscriptionOfferDetails[0]?.pricingPhases
|
|
449
|
-
.pricingPhaseList[0].formattedPrice
|
|
431
|
+
? item.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList[0].formattedPrice
|
|
450
432
|
: item.displayPrice}
|
|
451
433
|
</Text>
|
|
452
434
|
<Button
|
|
@@ -463,7 +445,7 @@ export default function IAPWithHook() {
|
|
|
463
445
|
offerToken: offer.offerToken,
|
|
464
446
|
})) || [],
|
|
465
447
|
}
|
|
466
|
-
: {sku: item.id},
|
|
448
|
+
: { sku: item.id },
|
|
467
449
|
type: 'subs',
|
|
468
450
|
})
|
|
469
451
|
}
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -9,6 +9,12 @@ func serializeDebug(_ s: String) -> String? {
|
|
|
9
9
|
#endif
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
func logDebug(_ message: String) {
|
|
13
|
+
#if DEBUG
|
|
14
|
+
print("DEBUG - \(message)")
|
|
15
|
+
#endif
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
struct IapEvent {
|
|
13
19
|
static let PurchaseUpdated = "purchase-updated"
|
|
14
20
|
static let PurchaseError = "purchase-error"
|
|
@@ -71,7 +77,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
|
|
|
71
77
|
]
|
|
72
78
|
|
|
73
79
|
if (jwsRepresentationIos != nil) {
|
|
80
|
+
logDebug("serializeTransaction adding jwsRepresentationIos with length: \(jwsRepresentationIos!.count)")
|
|
74
81
|
purchaseMap["jwsRepresentationIos"] = jwsRepresentationIos
|
|
82
|
+
} else {
|
|
83
|
+
logDebug("serializeTransaction jwsRepresentationIos is nil")
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
if #available(iOS 16.0, *) {
|
|
@@ -237,15 +246,20 @@ public class ExpoIapModule: Module {
|
|
|
237
246
|
var purchasedItemsSerialized: [[String: Any?]] = []
|
|
238
247
|
|
|
239
248
|
func addTransaction(transaction: Transaction, jwsRepresentationIos: String? = nil) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
// Debug: Log JWS representation
|
|
250
|
+
logDebug("getAvailableItems JWS: \(jwsRepresentationIos != nil ? "exists" : "nil")")
|
|
251
|
+
if let jws = jwsRepresentationIos {
|
|
252
|
+
logDebug("getAvailableItems JWS length: \(jws.count)")
|
|
243
253
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
|
|
255
|
+
let serialized = serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos)
|
|
256
|
+
purchasedItemsSerialized.append(serialized)
|
|
257
|
+
|
|
258
|
+
// Debug: Check if jwsRepresentationIos is included in serialized result
|
|
259
|
+
logDebug("getAvailableItems serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
|
|
260
|
+
|
|
247
261
|
if alsoPublishToEventListener {
|
|
248
|
-
self.sendEvent(IapEvent.
|
|
262
|
+
self.sendEvent(IapEvent.PurchaseUpdated, serialized)
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
|
|
@@ -287,7 +301,9 @@ public class ExpoIapModule: Module {
|
|
|
287
301
|
"message": StoreError.failedVerification.localizedDescription,
|
|
288
302
|
"productId": "unknown",
|
|
289
303
|
]
|
|
290
|
-
|
|
304
|
+
if alsoPublishToEventListener {
|
|
305
|
+
self.sendEvent(IapEvent.PurchaseError, err)
|
|
306
|
+
}
|
|
291
307
|
} catch {
|
|
292
308
|
let err = [
|
|
293
309
|
"responseCode": IapErrors.E_UNKNOWN.rawValue,
|
|
@@ -296,10 +312,11 @@ public class ExpoIapModule: Module {
|
|
|
296
312
|
"message": error.localizedDescription,
|
|
297
313
|
"productId": "unknown",
|
|
298
314
|
]
|
|
299
|
-
|
|
315
|
+
if alsoPublishToEventListener {
|
|
316
|
+
self.sendEvent(IapEvent.PurchaseError, err)
|
|
317
|
+
}
|
|
300
318
|
}
|
|
301
319
|
}
|
|
302
|
-
|
|
303
320
|
return purchasedItemsSerialized
|
|
304
321
|
}
|
|
305
322
|
|
|
@@ -362,12 +379,21 @@ public class ExpoIapModule: Module {
|
|
|
362
379
|
switch result {
|
|
363
380
|
case .success(let verification):
|
|
364
381
|
let transaction = try self.checkVerified(verification)
|
|
382
|
+
|
|
383
|
+
// Debug: Log JWS representation
|
|
384
|
+
logDebug("buyProduct JWS: exists")
|
|
385
|
+
logDebug("buyProduct JWS length: \(verification.jwsRepresentation.count)")
|
|
386
|
+
|
|
365
387
|
if andDangerouslyFinishTransactionAutomatically {
|
|
366
388
|
await transaction.finish()
|
|
367
389
|
return nil
|
|
368
390
|
} else {
|
|
369
391
|
self.transactions[String(transaction.id)] = transaction
|
|
370
392
|
let serialized = serializeTransaction(transaction, jwsRepresentationIos: verification.jwsRepresentation)
|
|
393
|
+
|
|
394
|
+
// Debug: Check if jwsRepresentationIos is included in serialized result
|
|
395
|
+
logDebug("buyProduct serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
|
|
396
|
+
|
|
371
397
|
self.sendEvent(IapEvent.PurchaseUpdated, serialized)
|
|
372
398
|
return serialized
|
|
373
399
|
}
|
|
@@ -458,9 +484,7 @@ public class ExpoIapModule: Module {
|
|
|
458
484
|
} else {
|
|
459
485
|
throw NSError(
|
|
460
486
|
domain: "ExpoIapModule", code: 4,
|
|
461
|
-
userInfo: [
|
|
462
|
-
NSLocalizedDescriptionKey: "Can't find entitlement for sku \(sku)"
|
|
463
|
-
])
|
|
487
|
+
userInfo: [NSLocalizedDescriptionKey: "Can't find entitlement for sku \(sku)"])
|
|
464
488
|
}
|
|
465
489
|
} else {
|
|
466
490
|
throw NSError(
|
|
@@ -499,10 +523,7 @@ public class ExpoIapModule: Module {
|
|
|
499
523
|
} else {
|
|
500
524
|
throw NSError(
|
|
501
525
|
domain: "ExpoIapModule", code: 4,
|
|
502
|
-
userInfo: [
|
|
503
|
-
NSLocalizedDescriptionKey:
|
|
504
|
-
"Can't find latest transaction for sku \(sku)"
|
|
505
|
-
])
|
|
526
|
+
userInfo: [NSLocalizedDescriptionKey: "Can't find latest transaction for sku \(sku)"])
|
|
506
527
|
}
|
|
507
528
|
} else {
|
|
508
529
|
throw NSError(
|
|
@@ -562,17 +583,13 @@ public class ExpoIapModule: Module {
|
|
|
562
583
|
"Cannot find window scene or not available on macOS"
|
|
563
584
|
])
|
|
564
585
|
}
|
|
565
|
-
|
|
566
586
|
// Get all subscription products before showing the management UI
|
|
567
587
|
let subscriptionSkus = await self.getAllSubscriptionProductIds()
|
|
568
588
|
self.pollingSkus = Set(subscriptionSkus)
|
|
569
|
-
|
|
570
589
|
// Show the management UI
|
|
571
590
|
try await AppStore.showManageSubscriptions(in: windowScene)
|
|
572
|
-
|
|
573
591
|
// Start polling for status changes
|
|
574
592
|
self.pollForSubscriptionStatusChanges()
|
|
575
|
-
|
|
576
593
|
return true
|
|
577
594
|
#else
|
|
578
595
|
throw NSError(
|
|
@@ -649,7 +666,7 @@ public class ExpoIapModule: Module {
|
|
|
649
666
|
AsyncFunction("getReceiptData") { () -> String? in
|
|
650
667
|
return try self.getReceiptDataInternal()
|
|
651
668
|
}
|
|
652
|
-
|
|
669
|
+
|
|
653
670
|
AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
|
|
654
671
|
guard let productStore = self.productStore else {
|
|
655
672
|
throw NSError(
|
|
@@ -669,7 +686,7 @@ public class ExpoIapModule: Module {
|
|
|
669
686
|
}
|
|
670
687
|
return false
|
|
671
688
|
}
|
|
672
|
-
|
|
689
|
+
|
|
673
690
|
AsyncFunction("getTransactionJws") { (sku: String) -> String? in
|
|
674
691
|
guard let productStore = self.productStore else {
|
|
675
692
|
throw NSError(
|
|
@@ -686,7 +703,7 @@ public class ExpoIapModule: Module {
|
|
|
686
703
|
userInfo: [NSLocalizedDescriptionKey: "Can't find transaction for sku \(sku)"])
|
|
687
704
|
}
|
|
688
705
|
}
|
|
689
|
-
|
|
706
|
+
|
|
690
707
|
AsyncFunction("validateReceiptIos") { (sku: String) -> [String: Any] in
|
|
691
708
|
guard let productStore = self.productStore else {
|
|
692
709
|
throw NSError(
|
|
@@ -721,7 +738,7 @@ public class ExpoIapModule: Module {
|
|
|
721
738
|
isValid = false
|
|
722
739
|
}
|
|
723
740
|
}
|
|
724
|
-
|
|
741
|
+
|
|
725
742
|
return [
|
|
726
743
|
"isValid": isValid,
|
|
727
744
|
"receiptData": receiptData,
|
|
@@ -750,7 +767,7 @@ public class ExpoIapModule: Module {
|
|
|
750
767
|
let transaction = try self.checkVerified(result)
|
|
751
768
|
self.transactions[String(transaction.id)] = transaction
|
|
752
769
|
if self.hasListeners {
|
|
753
|
-
let serialized = serializeTransaction(transaction)
|
|
770
|
+
let serialized = serializeTransaction(transaction, jwsRepresentationIos: result.jwsRepresentation)
|
|
754
771
|
self.sendEvent(IapEvent.PurchaseUpdated, serialized)
|
|
755
772
|
self.sendEvent(IapEvent.TransactionIapUpdated, ["transaction": serialized])
|
|
756
773
|
}
|
|
@@ -808,7 +825,6 @@ public class ExpoIapModule: Module {
|
|
|
808
825
|
|
|
809
826
|
private func pollForSubscriptionStatusChanges() {
|
|
810
827
|
subscriptionPollingTask?.cancel()
|
|
811
|
-
|
|
812
828
|
subscriptionPollingTask = Task {
|
|
813
829
|
try? await Task.sleep(nanoseconds: 1_500_000_000) // 1.5 seconds
|
|
814
830
|
|
|
@@ -825,10 +841,9 @@ public class ExpoIapModule: Module {
|
|
|
825
841
|
}
|
|
826
842
|
previousStatuses[sku] = willAutoRenew
|
|
827
843
|
}
|
|
828
|
-
|
|
844
|
+
|
|
829
845
|
for _ in 1...5 {
|
|
830
846
|
try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds
|
|
831
|
-
|
|
832
847
|
if Task.isCancelled {
|
|
833
848
|
return
|
|
834
849
|
}
|
|
@@ -837,7 +852,6 @@ public class ExpoIapModule: Module {
|
|
|
837
852
|
guard let product = await self.productStore?.getProduct(productID: sku),
|
|
838
853
|
let status = try? await product.subscription?.status.first,
|
|
839
854
|
let result = await product.latestTransaction else { continue }
|
|
840
|
-
|
|
841
855
|
// Try to verify the transaction
|
|
842
856
|
let transaction: Transaction
|
|
843
857
|
do {
|
|
@@ -866,12 +880,10 @@ public class ExpoIapModule: Module {
|
|
|
866
880
|
|
|
867
881
|
self.sendEvent(IapEvent.PurchaseUpdated, purchaseMap)
|
|
868
882
|
self.sendEvent(IapEvent.TransactionIapUpdated, ["transaction": purchaseMap])
|
|
869
|
-
|
|
870
883
|
previousStatuses[sku] = currentWillAutoRenew
|
|
871
884
|
}
|
|
872
885
|
}
|
|
873
886
|
}
|
|
874
|
-
|
|
875
887
|
self.pollingSkus.removeAll()
|
|
876
888
|
}
|
|
877
889
|
}
|
package/package.json
CHANGED
package/src/modules/android.ts
CHANGED
|
@@ -36,10 +36,10 @@ export const deepLinkToSubscriptionsAndroid = async ({
|
|
|
36
36
|
* Use server side validation instead for your production builds
|
|
37
37
|
* @param {string} packageName package name of your app.
|
|
38
38
|
* @param {string} productId product id for your in app product.
|
|
39
|
-
* @param {string} productToken token for your purchase.
|
|
40
|
-
* @param {string} accessToken
|
|
39
|
+
* @param {string} productToken token for your purchase (called 'token' in the API documentation).
|
|
40
|
+
* @param {string} accessToken OAuth access token with androidpublisher scope. Required for authentication.
|
|
41
41
|
* @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.
|
|
42
|
-
* @returns {Promise<
|
|
42
|
+
* @returns {Promise<ReceiptAndroid>}
|
|
43
43
|
*/
|
|
44
44
|
export const validateReceiptAndroid = async ({
|
|
45
45
|
packageName,
|
|
@@ -59,12 +59,13 @@ export const validateReceiptAndroid = async ({
|
|
|
59
59
|
const url =
|
|
60
60
|
'https://androidpublisher.googleapis.com/androidpublisher/v3/applications' +
|
|
61
61
|
`/${packageName}/purchases/${type}/${productId}` +
|
|
62
|
-
`/tokens/${productToken}
|
|
62
|
+
`/tokens/${productToken}`;
|
|
63
63
|
|
|
64
64
|
const response = await fetch(url, {
|
|
65
65
|
method: 'GET',
|
|
66
66
|
headers: {
|
|
67
67
|
'Content-Type': 'application/json',
|
|
68
|
+
Authorization: `Bearer ${accessToken}`,
|
|
68
69
|
},
|
|
69
70
|
});
|
|
70
71
|
|
package/src/modules/ios.ts
CHANGED
|
@@ -73,7 +73,7 @@ export const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Shows the system UI for managing subscriptions.
|
|
76
|
-
* When the user changes subscription renewal status, the system will emit events to
|
|
76
|
+
* When the user changes subscription renewal status, the system will emit events to
|
|
77
77
|
* purchaseUpdatedListener and transactionUpdatedIos listeners.
|
|
78
78
|
* @returns {Promise<null>}
|
|
79
79
|
*/
|
|
@@ -84,10 +84,10 @@ export const showManageSubscriptions = (): Promise<null> =>
|
|
|
84
84
|
* Get the receipt data from the iOS device.
|
|
85
85
|
* This returns the base64 encoded receipt data which can be sent to your server
|
|
86
86
|
* for verification with Apple's server.
|
|
87
|
-
*
|
|
87
|
+
*
|
|
88
88
|
* NOTE: For proper security, always verify receipts on your server using
|
|
89
89
|
* Apple's verifyReceipt endpoint, not directly from the app.
|
|
90
|
-
*
|
|
90
|
+
*
|
|
91
91
|
* @returns {Promise<string>} Base64 encoded receipt data
|
|
92
92
|
*/
|
|
93
93
|
export const getReceiptIos = (): Promise<string> => {
|
|
@@ -100,7 +100,7 @@ export const getReceiptIos = (): Promise<string> => {
|
|
|
100
100
|
/**
|
|
101
101
|
* Check if a transaction is verified through StoreKit 2.
|
|
102
102
|
* StoreKit 2 performs local verification of transaction JWS signatures.
|
|
103
|
-
*
|
|
103
|
+
*
|
|
104
104
|
* @param {string} sku The product's SKU (on iOS)
|
|
105
105
|
* @returns {Promise<boolean>} True if the transaction is verified
|
|
106
106
|
*/
|
|
@@ -114,7 +114,7 @@ export const isTransactionVerified = (sku: string): Promise<boolean> => {
|
|
|
114
114
|
/**
|
|
115
115
|
* Get the JWS representation of a purchase for server-side verification.
|
|
116
116
|
* The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.
|
|
117
|
-
*
|
|
117
|
+
*
|
|
118
118
|
* @param {string} sku The product's SKU (on iOS)
|
|
119
119
|
* @returns {Promise<string>} JWS representation of the transaction
|
|
120
120
|
*/
|