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.
@@ -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 accessToken from googleApis.
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<object>}
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,CAsBzB,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"}
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"}
@@ -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 accessToken from googleApis.
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<object>}
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}?access_token=${accessToken}`;
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,iBAAiB,WAAW,EAAE,CAAC;IAExD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,KAAK;QACb,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;SACnC;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.\n * @param {string} accessToken accessToken from googleApis.\n * @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.\n * @returns {Promise<object>}\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}?access_token=${accessToken}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\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"]}
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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AACtC,OAAO,EAAC,OAAO,EAAE,QAAQ,EAAC,MAAM,IAAI,CAAC;AAGrC,OAAO,aAAa,MAAM,kBAAkB,CAAC;AAO7C,YAAY;AACZ,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,QAA2C,EAC3C,EAAE;IACF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,qBAAqB,EAAE,QAAQ,CAAC,CAAC;AACvE,CAAC,CAAC;AAEF,cAAc;AACd,MAAM,UAAU,YAAY,CAC1B,IAAa;IAEb,OAAO,CACL,IAAI,IAAI,IAAI;QACZ,OAAO,IAAI,KAAK,QAAQ;QACxB,UAAU,IAAI,IAAI;QAClB,IAAI,CAAC,QAAQ,KAAK,KAAK,CACxB,CAAC;AACJ,CAAC;AAED,YAAY;AACZ;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,GAAkB,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;AAE9D;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,OAAe,EAAoB,EAAE,CAC3E,aAAa,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAEjD;;GAEG;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAA+B,EAAE,CAC7E,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAA4B,EAAE,CAC1E,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAA4B,EAAE,CACzE,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAMvC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAgC,EAAE,CAC9E,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;AAExC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAkB,EAAE,CACzD,aAAa,CAAC,uBAAuB,EAAE,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAoB,EAAE;IACjD,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC;AACxC,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,GAAW,EAAoB,EAAE;IACrE,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,aAAa,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAmB,EAAE;IAChE,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAC9C,CAAC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACrC,GAAW,EAMV,EAAE;IACH,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC","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"]}
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
- 'cpk.points.1000',
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
  }
@@ -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
- purchasedItemsSerialized.append(serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos))
241
- if alsoPublishToEventListener {
242
- self.sendEvent(IapEvent.PurchaseUpdated, serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos))
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
- func addError(error: Error, errorDict: [String: String]) {
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.PurchaseError, errorDict)
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
- addError(error: StoreError.failedVerification, errorDict: err)
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
- addError(error: error, errorDict: err)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.3.4",
3
+ "version": "2.3.5-rc.2",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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 accessToken from googleApis.
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<object>}
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}?access_token=${accessToken}`;
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
 
@@ -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
  */