expo-iap 2.3.4-rc.2 → 2.3.4-rc.3
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/ios.d.ts +48 -0
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +64 -0
- package/build/modules/ios.js.map +1 -1
- package/build/useIap.d.ts +6 -0
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +37 -8
- package/build/useIap.js.map +1 -1
- package/ios/ExpoIapModule.swift +112 -0
- package/package.json +1 -1
- package/src/modules/ios.ts +76 -0
- package/src/useIap.ts +91 -30
package/build/modules/ios.d.ts
CHANGED
|
@@ -43,5 +43,53 @@ export declare const beginRefundRequest: (sku: string) => Promise<RefundRequestS
|
|
|
43
43
|
* @returns {Promise<null>}
|
|
44
44
|
*/
|
|
45
45
|
export declare const showManageSubscriptions: () => Promise<null>;
|
|
46
|
+
/**
|
|
47
|
+
* Get the receipt data from the iOS device.
|
|
48
|
+
* This returns the base64 encoded receipt data which can be sent to your server
|
|
49
|
+
* for verification with Apple's server.
|
|
50
|
+
*
|
|
51
|
+
* NOTE: For proper security, always verify receipts on your server using
|
|
52
|
+
* Apple's verifyReceipt endpoint, not directly from the app.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Promise<string>} Base64 encoded receipt data
|
|
55
|
+
*/
|
|
56
|
+
export declare const getReceiptIos: () => Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a transaction is verified through StoreKit 2.
|
|
59
|
+
* StoreKit 2 performs local verification of transaction JWS signatures.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
62
|
+
* @returns {Promise<boolean>} True if the transaction is verified
|
|
63
|
+
*/
|
|
64
|
+
export declare const isTransactionVerified: (sku: string) => Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Get the JWS representation of a purchase for server-side verification.
|
|
67
|
+
* The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
70
|
+
* @returns {Promise<string>} JWS representation of the transaction
|
|
71
|
+
*/
|
|
72
|
+
export declare const getTransactionJws: (sku: string) => Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Validate receipt for iOS using StoreKit 2's built-in verification.
|
|
75
|
+
* Returns receipt data and verification information to help with server-side validation.
|
|
76
|
+
*
|
|
77
|
+
* NOTE: For proper security, Apple recommends verifying receipts on your server using
|
|
78
|
+
* the verifyReceipt endpoint rather than relying solely on client-side verification.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
81
|
+
* @returns {Promise<{
|
|
82
|
+
* isValid: boolean;
|
|
83
|
+
* receiptData: string;
|
|
84
|
+
* jwsRepresentation: string;
|
|
85
|
+
* latestTransaction?: ProductPurchase;
|
|
86
|
+
* }>}
|
|
87
|
+
*/
|
|
88
|
+
export declare const validateReceiptIos: (sku: string) => Promise<{
|
|
89
|
+
isValid: boolean;
|
|
90
|
+
receiptData: string;
|
|
91
|
+
jwsRepresentation: string;
|
|
92
|
+
latestTransaction?: ProductPurchase;
|
|
93
|
+
}>;
|
|
46
94
|
export {};
|
|
47
95
|
//# sourceMappingURL=ios.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAGhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAGF,eAAO,MAAM,qBAAqB,aACtB,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,6CAO5C,CAAC;AAGF,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,EACxD,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAO/B;AAGD;;;GAGG;AACH,eAAO,MAAM,IAAI,QAAO,QAAQ,IAAI,CAAyB,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,uBAAuB,YAAa,MAAM,KAAG,QAAQ,OAAO,CACzB,CAAC;AAEjD;;GAEG;AAEH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,gBAAgB,EAAE,CACpC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,eAAe,CACjC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAAS,MAAM,KAAG,QAAQ,eAAe,CACjC,CAAC;AAEvC;;GAEG;AACH,KAAK,mBAAmB,GAAG,SAAS,GAAG,eAAe,CAAC;AACvD,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,mBAAmB,CACrC,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAO,QAAQ,IAAI,CACd,CAAC"}
|
|
1
|
+
{"version":3,"file":"ios.d.ts","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAGhE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAGF,eAAO,MAAM,qBAAqB,aACtB,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,6CAO5C,CAAC;AAGF,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAC,EACxD,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,CAAC,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAC,CAO/B;AAGD;;;GAGG;AACH,eAAO,MAAM,IAAI,QAAO,QAAQ,IAAI,CAAyB,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,uBAAuB,YAAa,MAAM,KAAG,QAAQ,OAAO,CACzB,CAAC;AAEjD;;GAEG;AAEH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,gBAAgB,EAAE,CACpC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,eAAe,CACjC,CAAC;AAExC;;GAEG;AACH,eAAO,MAAM,iBAAiB,QAAS,MAAM,KAAG,QAAQ,eAAe,CACjC,CAAC;AAEvC;;GAEG;AACH,KAAK,mBAAmB,GAAG,SAAS,GAAG,eAAe,CAAC;AACvD,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,QAAQ,mBAAmB,CACrC,CAAC;AAExC;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAO,QAAQ,IAAI,CACd,CAAC;AAE1C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,QAAO,QAAQ,MAAM,CAK9C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAS,MAAM,KAAG,QAAQ,OAAO,CAKlE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAS,MAAM,KAAG,QAAQ,MAAM,CAK7D,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,QACxB,MAAM,KACV,QAAQ;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,eAAe,CAAC;CACrC,CAOA,CAAC"}
|
package/build/modules/ios.js
CHANGED
|
@@ -45,4 +45,68 @@ export const beginRefundRequest = (sku) => ExpoIapModule.beginRefundRequest(sku)
|
|
|
45
45
|
* @returns {Promise<null>}
|
|
46
46
|
*/
|
|
47
47
|
export const showManageSubscriptions = () => ExpoIapModule.showManageSubscriptions();
|
|
48
|
+
/**
|
|
49
|
+
* Get the receipt data from the iOS device.
|
|
50
|
+
* This returns the base64 encoded receipt data which can be sent to your server
|
|
51
|
+
* for verification with Apple's server.
|
|
52
|
+
*
|
|
53
|
+
* NOTE: For proper security, always verify receipts on your server using
|
|
54
|
+
* Apple's verifyReceipt endpoint, not directly from the app.
|
|
55
|
+
*
|
|
56
|
+
* @returns {Promise<string>} Base64 encoded receipt data
|
|
57
|
+
*/
|
|
58
|
+
export const getReceiptIos = () => {
|
|
59
|
+
if (Platform.OS !== 'ios') {
|
|
60
|
+
throw new Error('This method is only available on iOS');
|
|
61
|
+
}
|
|
62
|
+
return ExpoIapModule.getReceiptData();
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Check if a transaction is verified through StoreKit 2.
|
|
66
|
+
* StoreKit 2 performs local verification of transaction JWS signatures.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
69
|
+
* @returns {Promise<boolean>} True if the transaction is verified
|
|
70
|
+
*/
|
|
71
|
+
export const isTransactionVerified = (sku) => {
|
|
72
|
+
if (Platform.OS !== 'ios') {
|
|
73
|
+
throw new Error('This method is only available on iOS');
|
|
74
|
+
}
|
|
75
|
+
return ExpoIapModule.isTransactionVerified(sku);
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Get the JWS representation of a purchase for server-side verification.
|
|
79
|
+
* The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
82
|
+
* @returns {Promise<string>} JWS representation of the transaction
|
|
83
|
+
*/
|
|
84
|
+
export const getTransactionJws = (sku) => {
|
|
85
|
+
if (Platform.OS !== 'ios') {
|
|
86
|
+
throw new Error('This method is only available on iOS');
|
|
87
|
+
}
|
|
88
|
+
return ExpoIapModule.getTransactionJws(sku);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Validate receipt for iOS using StoreKit 2's built-in verification.
|
|
92
|
+
* Returns receipt data and verification information to help with server-side validation.
|
|
93
|
+
*
|
|
94
|
+
* NOTE: For proper security, Apple recommends verifying receipts on your server using
|
|
95
|
+
* the verifyReceipt endpoint rather than relying solely on client-side verification.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
98
|
+
* @returns {Promise<{
|
|
99
|
+
* isValid: boolean;
|
|
100
|
+
* receiptData: string;
|
|
101
|
+
* jwsRepresentation: string;
|
|
102
|
+
* latestTransaction?: ProductPurchase;
|
|
103
|
+
* }>}
|
|
104
|
+
*/
|
|
105
|
+
export const validateReceiptIos = async (sku) => {
|
|
106
|
+
if (Platform.OS !== 'ios') {
|
|
107
|
+
throw new Error('This method is only available on iOS');
|
|
108
|
+
}
|
|
109
|
+
const result = await ExpoIapModule.validateReceiptIos(sku);
|
|
110
|
+
return result;
|
|
111
|
+
};
|
|
48
112
|
//# sourceMappingURL=ios.js.map
|
package/build/modules/ios.js.map
CHANGED
|
@@ -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","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"]}
|
|
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/build/useIap.d.ts
CHANGED
|
@@ -20,6 +20,12 @@ type UseIap = {
|
|
|
20
20
|
getProducts: (skus: string[]) => Promise<void>;
|
|
21
21
|
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
22
22
|
requestPurchase: typeof requestPurchaseInternal;
|
|
23
|
+
validateReceipt: (sku: string, androidOptions?: {
|
|
24
|
+
packageName: string;
|
|
25
|
+
productToken: string;
|
|
26
|
+
accessToken: string;
|
|
27
|
+
isSub?: boolean;
|
|
28
|
+
}) => Promise<any>;
|
|
23
29
|
};
|
|
24
30
|
export interface UseIAPOptions {
|
|
25
31
|
onPurchaseSuccess?: (purchase: ProductPurchase | SubscriptionPurchase) => void;
|
package/build/useIap.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIap.d.ts","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,eAAe,IAAI,uBAAuB,
|
|
1
|
+
{"version":3,"file":"useIap.d.ts","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,eAAe,IAAI,uBAAuB,EAI3C,MAAM,IAAI,CAAC;AAEZ,OAAO,EACL,OAAO,EACP,eAAe,EACf,QAAQ,EACR,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,iBAAiB,CAAC;AAKzB,KAAK,MAAM,GAAG;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,mBAAmB,EAAE,eAAe,EAAE,CAAC;IACvC,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC,kBAAkB,EAAE,eAAe,EAAE,CAAC;IACtC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,oBAAoB,CAAC,EAAE,aAAa,CAAC;IACrC,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,yBAAyB,EAAE,MAAM,IAAI,CAAC;IACtC,iBAAiB,EAAE,CAAC,EAClB,QAAQ,EACR,YAAY,GACb,EAAE;QACD,QAAQ,EAAE,QAAQ,CAAC;QACnB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,KAAK,OAAO,CAAC,MAAM,GAAG,OAAO,GAAG,cAAc,GAAG,IAAI,CAAC,CAAC;IACxD,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,oBAAoB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,eAAe,EAAE,OAAO,uBAAuB,CAAC;IAChD,eAAe,EAAE,CACf,GAAG,EAAE,MAAM,EACX,cAAc,CAAC,EAAE;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,KACE,OAAO,CAAC,GAAG,CAAC,CAAC;CACnB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,iBAAiB,CAAC,EAAE,CAClB,QAAQ,EAAE,eAAe,GAAG,oBAAoB,KAC7C,IAAI,CAAC;IACV,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CA2PtD"}
|
package/build/useIap.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { endConnection, initConnection, purchaseErrorListener, purchaseUpdatedListener, transactionUpdatedIos, getProducts, getAvailablePurchases, getPurchaseHistory, finishTransaction as finishTransactionInternal, getSubscriptions, requestPurchase as requestPurchaseInternal, sync, } from './';
|
|
1
|
+
import { endConnection, initConnection, purchaseErrorListener, purchaseUpdatedListener, transactionUpdatedIos, getProducts, getAvailablePurchases, getPurchaseHistory, finishTransaction as finishTransactionInternal, getSubscriptions, requestPurchase as requestPurchaseInternal, sync, validateReceiptIos, validateReceiptAndroid, } from './';
|
|
2
2
|
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
3
3
|
import { Platform } from 'react-native';
|
|
4
4
|
export function useIAP(options) {
|
|
@@ -51,7 +51,12 @@ export function useIAP(options) {
|
|
|
51
51
|
clearCurrentPurchaseError();
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
}, [
|
|
54
|
+
}, [
|
|
55
|
+
currentPurchase?.id,
|
|
56
|
+
currentPurchaseError?.productId,
|
|
57
|
+
clearCurrentPurchase,
|
|
58
|
+
clearCurrentPurchaseError,
|
|
59
|
+
]);
|
|
55
60
|
const requestPurchaseWithReset = useCallback(async (requestObj) => {
|
|
56
61
|
clearCurrentPurchase();
|
|
57
62
|
clearCurrentPurchaseError();
|
|
@@ -76,7 +81,7 @@ export function useIAP(options) {
|
|
|
76
81
|
}
|
|
77
82
|
});
|
|
78
83
|
}
|
|
79
|
-
if (subscriptions.some(sub => sub.id === productId)) {
|
|
84
|
+
if (subscriptions.some((sub) => sub.id === productId)) {
|
|
80
85
|
await getSubscriptionsInternal([productId]);
|
|
81
86
|
await getAvailablePurchasesInternal();
|
|
82
87
|
}
|
|
@@ -85,6 +90,29 @@ export function useIAP(options) {
|
|
|
85
90
|
console.warn('Failed to refresh subscription status:', error);
|
|
86
91
|
}
|
|
87
92
|
}, [getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions]);
|
|
93
|
+
const validateReceipt = useCallback(async (sku, androidOptions) => {
|
|
94
|
+
if (Platform.OS === 'ios') {
|
|
95
|
+
return await validateReceiptIos(sku);
|
|
96
|
+
}
|
|
97
|
+
else if (Platform.OS === 'android') {
|
|
98
|
+
if (!androidOptions ||
|
|
99
|
+
!androidOptions.packageName ||
|
|
100
|
+
!androidOptions.productToken ||
|
|
101
|
+
!androidOptions.accessToken) {
|
|
102
|
+
throw new Error('Android validation requires packageName, productToken, and accessToken');
|
|
103
|
+
}
|
|
104
|
+
return await validateReceiptAndroid({
|
|
105
|
+
packageName: androidOptions.packageName,
|
|
106
|
+
productId: sku,
|
|
107
|
+
productToken: androidOptions.productToken,
|
|
108
|
+
accessToken: androidOptions.accessToken,
|
|
109
|
+
isSub: androidOptions.isSub,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw new Error('Platform not supported');
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
88
116
|
const initIapWithSubscriptions = useCallback(async () => {
|
|
89
117
|
const result = await initConnection();
|
|
90
118
|
setConnected(result);
|
|
@@ -108,11 +136,11 @@ export function useIAP(options) {
|
|
|
108
136
|
});
|
|
109
137
|
if (Platform.OS === 'ios') {
|
|
110
138
|
subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(async (event) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
139
|
+
setPromotedProductsIOS((prevProducts) => event.transaction
|
|
140
|
+
? [...prevProducts, event.transaction]
|
|
141
|
+
: prevProducts);
|
|
142
|
+
if (event.transaction && 'expirationDateIos' in event.transaction) {
|
|
143
|
+
await refreshSubscriptionStatus(event.transaction.id);
|
|
116
144
|
}
|
|
117
145
|
});
|
|
118
146
|
}
|
|
@@ -146,6 +174,7 @@ export function useIAP(options) {
|
|
|
146
174
|
getProducts: getProductsInternal,
|
|
147
175
|
getSubscriptions: getSubscriptionsInternal,
|
|
148
176
|
requestPurchase: requestPurchaseWithReset,
|
|
177
|
+
validateReceipt,
|
|
149
178
|
};
|
|
150
179
|
}
|
|
151
180
|
//# sourceMappingURL=useIap.js.map
|
package/build/useIap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n transactionUpdatedIos,\n getProducts,\n getAvailablePurchases,\n getPurchaseHistory,\n finishTransaction as finishTransactionInternal,\n getSubscriptions,\n requestPurchase as requestPurchaseInternal,\n sync,\n} from './';\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {\n Product,\n ProductPurchase,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n SubscriptionPurchase,\n} from './ExpoIap.types';\nimport {TransactionEvent} from './modules/ios';\nimport {Subscription} from 'expo-modules-core';\nimport {Platform} from 'react-native';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: ProductPurchase[];\n subscriptions: SubscriptionProduct[];\n purchaseHistories: ProductPurchase[];\n availablePurchases: ProductPurchase[];\n currentPurchase?: ProductPurchase;\n currentPurchaseError?: PurchaseError;\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<string | boolean | PurchaseResult | void>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n getPurchaseHistories: (skus: string[]) => Promise<void>;\n getProducts: (skus: string[]) => Promise<void>;\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: typeof requestPurchaseInternal;\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (purchase: ProductPurchase | SubscriptionPurchase) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n}\n\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS, setPromotedProductsIOS] = useState<\n ProductPurchase[]\n >([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(\n [],\n );\n const [availablePurchases, setAvailablePurchases] = useState<\n ProductPurchase[]\n >([]);\n const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: Subscription;\n purchaseError?: Subscription;\n promotedProductsIos?: Subscription;\n }>({});\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setProducts(await getProducts(skus));\n },\n [],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setSubscriptions(await getSubscriptions(skus));\n },\n [],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n setAvailablePurchases(await getAvailablePurchases());\n }, []);\n\n const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {\n setPurchaseHistories(await getPurchaseHistory());\n }, []);\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: ProductPurchase;\n isConsumable?: boolean;\n }): Promise<string | boolean | PurchaseResult | void> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [currentPurchase?.id, currentPurchaseError?.productId, clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: Parameters<typeof requestPurchaseInternal>[0]) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n \n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(async (productId: string) => {\n try {\n if (Platform.OS === 'ios') {\n await sync().catch((error) => {\n // Pass the error to the developer's handler if provided\n if (optionsRef.current?.onSyncError) {\n optionsRef.current.onSyncError(error);\n } else {\n // Fallback to original behavior\n console.warn('Sync error occurred. This might require user password:', error);\n }\n });\n }\n \n if (subscriptions.some(sub => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n }, [getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions]);\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n const result = await initConnection();\n setConnected(result);\n\n if (result) {\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(\n async (event: TransactionEvent) => {\n if (event.transaction) {\n setPromotedProductsIOS((prevProducts) => \n [...prevProducts, event.transaction!]\n );\n \n if ('expirationDateIos' in event.transaction) {\n await refreshSubscriptionStatus(event.transaction.id);\n }\n }\n },\n );\n }\n }\n }, [refreshSubscriptionStatus]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIos?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n subscriptions,\n purchaseHistories,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n getPurchaseHistories: getPurchaseHistoriesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n requestPurchase: requestPurchaseWithReset,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n transactionUpdatedIos,\n getProducts,\n getAvailablePurchases,\n getPurchaseHistory,\n finishTransaction as finishTransactionInternal,\n getSubscriptions,\n requestPurchase as requestPurchaseInternal,\n sync,\n validateReceiptIos,\n validateReceiptAndroid,\n} from './';\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {\n Product,\n ProductPurchase,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n SubscriptionPurchase,\n} from './ExpoIap.types';\nimport {TransactionEvent} from './modules/ios';\nimport {Subscription} from 'expo-modules-core';\nimport {Platform} from 'react-native';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: ProductPurchase[];\n subscriptions: SubscriptionProduct[];\n purchaseHistories: ProductPurchase[];\n availablePurchases: ProductPurchase[];\n currentPurchase?: ProductPurchase;\n currentPurchaseError?: PurchaseError;\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<string | boolean | PurchaseResult | void>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n getPurchaseHistories: (skus: string[]) => Promise<void>;\n getProducts: (skus: string[]) => Promise<void>;\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: typeof requestPurchaseInternal;\n validateReceipt: (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => Promise<any>;\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (\n purchase: ProductPurchase | SubscriptionPurchase,\n ) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n}\n\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS, setPromotedProductsIOS] = useState<\n ProductPurchase[]\n >([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(\n [],\n );\n const [availablePurchases, setAvailablePurchases] = useState<\n ProductPurchase[]\n >([]);\n const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: Subscription;\n purchaseError?: Subscription;\n promotedProductsIos?: Subscription;\n }>({});\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setProducts(await getProducts(skus));\n },\n [],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n setSubscriptions(await getSubscriptions(skus));\n },\n [],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n setAvailablePurchases(await getAvailablePurchases());\n }, []);\n\n const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {\n setPurchaseHistories(await getPurchaseHistory());\n }, []);\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: ProductPurchase;\n isConsumable?: boolean;\n }): Promise<string | boolean | PurchaseResult | void> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [\n currentPurchase?.id,\n currentPurchaseError?.productId,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n ],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: Parameters<typeof requestPurchaseInternal>[0]) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n\n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(\n async (productId: string) => {\n try {\n if (Platform.OS === 'ios') {\n await sync().catch((error) => {\n // Pass the error to the developer's handler if provided\n if (optionsRef.current?.onSyncError) {\n optionsRef.current.onSyncError(error);\n } else {\n // Fallback to original behavior\n console.warn(\n 'Sync error occurred. This might require user password:',\n error,\n );\n }\n });\n }\n\n if (subscriptions.some((sub) => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n },\n [getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions],\n );\n const validateReceipt = useCallback(\n async (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => {\n if (Platform.OS === 'ios') {\n return await validateReceiptIos(sku);\n } else if (Platform.OS === 'android') {\n if (\n !androidOptions ||\n !androidOptions.packageName ||\n !androidOptions.productToken ||\n !androidOptions.accessToken\n ) {\n throw new Error(\n 'Android validation requires packageName, productToken, and accessToken',\n );\n }\n return await validateReceiptAndroid({\n packageName: androidOptions.packageName,\n productId: sku,\n productToken: androidOptions.productToken,\n accessToken: androidOptions.accessToken,\n isSub: androidOptions.isSub,\n });\n } else {\n throw new Error('Platform not supported');\n }\n },\n [],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n const result = await initConnection();\n setConnected(result);\n\n if (result) {\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(\n async (event: TransactionEvent) => {\n setPromotedProductsIOS((prevProducts) =>\n event.transaction\n ? [...prevProducts, event.transaction]\n : prevProducts,\n );\n\n if (event.transaction && 'expirationDateIos' in event.transaction) {\n await refreshSubscriptionStatus(event.transaction.id);\n }\n },\n );\n }\n }\n }, [refreshSubscriptionStatus]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIos?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n subscriptions,\n purchaseHistories,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n getPurchaseHistories: getPurchaseHistoriesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n requestPurchase: requestPurchaseWithReset,\n validateReceipt,\n };\n}\n"]}
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -645,6 +645,118 @@ public class ExpoIapModule: Module {
|
|
|
645
645
|
self.removeTransactionObserver()
|
|
646
646
|
return true
|
|
647
647
|
}
|
|
648
|
+
|
|
649
|
+
AsyncFunction("getReceiptData") { () -> String? in
|
|
650
|
+
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
|
|
651
|
+
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
|
|
652
|
+
do {
|
|
653
|
+
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
|
654
|
+
return receiptData.base64EncodedString(options: [])
|
|
655
|
+
} catch {
|
|
656
|
+
throw NSError(
|
|
657
|
+
domain: "ExpoIapModule", code: 13,
|
|
658
|
+
userInfo: [
|
|
659
|
+
NSLocalizedDescriptionKey:
|
|
660
|
+
"Error reading receipt data: \(error.localizedDescription)"
|
|
661
|
+
])
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
throw NSError(
|
|
665
|
+
domain: "ExpoIapModule", code: 14,
|
|
666
|
+
userInfo: [NSLocalizedDescriptionKey: "App Store receipt not found"])
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
|
|
671
|
+
guard let productStore = self.productStore else {
|
|
672
|
+
throw NSError(
|
|
673
|
+
domain: "ExpoIapModule", code: 1,
|
|
674
|
+
userInfo: [NSLocalizedDescriptionKey: "Connection not initialized"])
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if let product = await productStore.getProduct(productID: sku),
|
|
678
|
+
let result = await product.latestTransaction {
|
|
679
|
+
do {
|
|
680
|
+
// If this doesn't throw, the transaction is verified
|
|
681
|
+
_ = try self.checkVerified(result)
|
|
682
|
+
return true
|
|
683
|
+
} catch {
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return false
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
AsyncFunction("getTransactionJws") { (sku: String) -> String? in
|
|
691
|
+
guard let productStore = self.productStore else {
|
|
692
|
+
throw NSError(
|
|
693
|
+
domain: "ExpoIapModule", code: 1,
|
|
694
|
+
userInfo: [NSLocalizedDescriptionKey: "Connection not initialized"])
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if let product = await productStore.getProduct(productID: sku),
|
|
698
|
+
let result = await product.latestTransaction {
|
|
699
|
+
return result.jwsRepresentation
|
|
700
|
+
} else {
|
|
701
|
+
throw NSError(
|
|
702
|
+
domain: "ExpoIapModule", code: 5,
|
|
703
|
+
userInfo: [NSLocalizedDescriptionKey: "Can't find transaction for sku \(sku)"])
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
AsyncFunction("validateReceiptIos") { (sku: String) -> [String: Any] in
|
|
708
|
+
guard let productStore = self.productStore else {
|
|
709
|
+
throw NSError(
|
|
710
|
+
domain: "ExpoIapModule", code: 1,
|
|
711
|
+
userInfo: [NSLocalizedDescriptionKey: "Connection not initialized"])
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Get receipt data
|
|
715
|
+
var receiptData: String? = nil
|
|
716
|
+
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
|
|
717
|
+
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
|
|
718
|
+
do {
|
|
719
|
+
let receiptDataRaw = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
|
720
|
+
receiptData = receiptDataRaw.base64EncodedString(options: [])
|
|
721
|
+
} catch {
|
|
722
|
+
throw NSError(
|
|
723
|
+
domain: "ExpoIapModule", code: 13,
|
|
724
|
+
userInfo: [NSLocalizedDescriptionKey: "Error reading receipt data: \(error.localizedDescription)"]
|
|
725
|
+
)
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
throw NSError(
|
|
729
|
+
domain: "ExpoIapModule", code: 14,
|
|
730
|
+
userInfo: [NSLocalizedDescriptionKey: "App Store receipt not found"]
|
|
731
|
+
)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
var isValid = false
|
|
735
|
+
var jwsRepresentation: String? = nil
|
|
736
|
+
var latestTransaction: [String: Any?]? = nil
|
|
737
|
+
|
|
738
|
+
// Get JWS representation and verify transaction
|
|
739
|
+
if let product = await productStore.getProduct(productID: sku),
|
|
740
|
+
let result = await product.latestTransaction {
|
|
741
|
+
jwsRepresentation = result.jwsRepresentation
|
|
742
|
+
|
|
743
|
+
do {
|
|
744
|
+
// If this doesn't throw, the transaction is verified
|
|
745
|
+
let transaction = try self.checkVerified(result)
|
|
746
|
+
isValid = true
|
|
747
|
+
latestTransaction = serializeTransaction(transaction, jwsRepresentationIos: result.jwsRepresentation)
|
|
748
|
+
} catch {
|
|
749
|
+
isValid = false
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return [
|
|
754
|
+
"isValid": isValid,
|
|
755
|
+
"receiptData": receiptData ?? "",
|
|
756
|
+
"jwsRepresentation": jwsRepresentation ?? "",
|
|
757
|
+
"latestTransaction": latestTransaction as Any
|
|
758
|
+
]
|
|
759
|
+
}
|
|
648
760
|
}
|
|
649
761
|
|
|
650
762
|
private func addTransactionObserver() {
|
package/package.json
CHANGED
package/src/modules/ios.ts
CHANGED
|
@@ -79,3 +79,79 @@ export const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>
|
|
|
79
79
|
*/
|
|
80
80
|
export const showManageSubscriptions = (): Promise<null> =>
|
|
81
81
|
ExpoIapModule.showManageSubscriptions();
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the receipt data from the iOS device.
|
|
85
|
+
* This returns the base64 encoded receipt data which can be sent to your server
|
|
86
|
+
* for verification with Apple's server.
|
|
87
|
+
*
|
|
88
|
+
* NOTE: For proper security, always verify receipts on your server using
|
|
89
|
+
* Apple's verifyReceipt endpoint, not directly from the app.
|
|
90
|
+
*
|
|
91
|
+
* @returns {Promise<string>} Base64 encoded receipt data
|
|
92
|
+
*/
|
|
93
|
+
export const getReceiptIos = (): Promise<string> => {
|
|
94
|
+
if (Platform.OS !== 'ios') {
|
|
95
|
+
throw new Error('This method is only available on iOS');
|
|
96
|
+
}
|
|
97
|
+
return ExpoIapModule.getReceiptData();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a transaction is verified through StoreKit 2.
|
|
102
|
+
* StoreKit 2 performs local verification of transaction JWS signatures.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
105
|
+
* @returns {Promise<boolean>} True if the transaction is verified
|
|
106
|
+
*/
|
|
107
|
+
export const isTransactionVerified = (sku: string): Promise<boolean> => {
|
|
108
|
+
if (Platform.OS !== 'ios') {
|
|
109
|
+
throw new Error('This method is only available on iOS');
|
|
110
|
+
}
|
|
111
|
+
return ExpoIapModule.isTransactionVerified(sku);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the JWS representation of a purchase for server-side verification.
|
|
116
|
+
* The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
119
|
+
* @returns {Promise<string>} JWS representation of the transaction
|
|
120
|
+
*/
|
|
121
|
+
export const getTransactionJws = (sku: string): Promise<string> => {
|
|
122
|
+
if (Platform.OS !== 'ios') {
|
|
123
|
+
throw new Error('This method is only available on iOS');
|
|
124
|
+
}
|
|
125
|
+
return ExpoIapModule.getTransactionJws(sku);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validate receipt for iOS using StoreKit 2's built-in verification.
|
|
130
|
+
* Returns receipt data and verification information to help with server-side validation.
|
|
131
|
+
*
|
|
132
|
+
* NOTE: For proper security, Apple recommends verifying receipts on your server using
|
|
133
|
+
* the verifyReceipt endpoint rather than relying solely on client-side verification.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} sku The product's SKU (on iOS)
|
|
136
|
+
* @returns {Promise<{
|
|
137
|
+
* isValid: boolean;
|
|
138
|
+
* receiptData: string;
|
|
139
|
+
* jwsRepresentation: string;
|
|
140
|
+
* latestTransaction?: ProductPurchase;
|
|
141
|
+
* }>}
|
|
142
|
+
*/
|
|
143
|
+
export const validateReceiptIos = async (
|
|
144
|
+
sku: string,
|
|
145
|
+
): Promise<{
|
|
146
|
+
isValid: boolean;
|
|
147
|
+
receiptData: string;
|
|
148
|
+
jwsRepresentation: string;
|
|
149
|
+
latestTransaction?: ProductPurchase;
|
|
150
|
+
}> => {
|
|
151
|
+
if (Platform.OS !== 'ios') {
|
|
152
|
+
throw new Error('This method is only available on iOS');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = await ExpoIapModule.validateReceiptIos(sku);
|
|
156
|
+
return result;
|
|
157
|
+
};
|
package/src/useIap.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
getSubscriptions,
|
|
12
12
|
requestPurchase as requestPurchaseInternal,
|
|
13
13
|
sync,
|
|
14
|
+
validateReceiptIos,
|
|
15
|
+
validateReceiptAndroid,
|
|
14
16
|
} from './';
|
|
15
17
|
import {useCallback, useEffect, useState, useRef} from 'react';
|
|
16
18
|
import {
|
|
@@ -49,10 +51,21 @@ type UseIap = {
|
|
|
49
51
|
getProducts: (skus: string[]) => Promise<void>;
|
|
50
52
|
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
51
53
|
requestPurchase: typeof requestPurchaseInternal;
|
|
54
|
+
validateReceipt: (
|
|
55
|
+
sku: string,
|
|
56
|
+
androidOptions?: {
|
|
57
|
+
packageName: string;
|
|
58
|
+
productToken: string;
|
|
59
|
+
accessToken: string;
|
|
60
|
+
isSub?: boolean;
|
|
61
|
+
},
|
|
62
|
+
) => Promise<any>;
|
|
52
63
|
};
|
|
53
64
|
|
|
54
65
|
export interface UseIAPOptions {
|
|
55
|
-
onPurchaseSuccess?: (
|
|
66
|
+
onPurchaseSuccess?: (
|
|
67
|
+
purchase: ProductPurchase | SubscriptionPurchase,
|
|
68
|
+
) => void;
|
|
56
69
|
onPurchaseError?: (error: PurchaseError) => void;
|
|
57
70
|
onSyncError?: (error: Error) => void;
|
|
58
71
|
}
|
|
@@ -140,14 +153,19 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
},
|
|
143
|
-
[
|
|
156
|
+
[
|
|
157
|
+
currentPurchase?.id,
|
|
158
|
+
currentPurchaseError?.productId,
|
|
159
|
+
clearCurrentPurchase,
|
|
160
|
+
clearCurrentPurchaseError,
|
|
161
|
+
],
|
|
144
162
|
);
|
|
145
163
|
|
|
146
164
|
const requestPurchaseWithReset = useCallback(
|
|
147
165
|
async (requestObj: Parameters<typeof requestPurchaseInternal>[0]) => {
|
|
148
166
|
clearCurrentPurchase();
|
|
149
167
|
clearCurrentPurchaseError();
|
|
150
|
-
|
|
168
|
+
|
|
151
169
|
try {
|
|
152
170
|
return await requestPurchaseInternal(requestObj);
|
|
153
171
|
} catch (error) {
|
|
@@ -157,28 +175,70 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
157
175
|
[clearCurrentPurchase, clearCurrentPurchaseError],
|
|
158
176
|
);
|
|
159
177
|
|
|
160
|
-
const refreshSubscriptionStatus = useCallback(
|
|
161
|
-
|
|
178
|
+
const refreshSubscriptionStatus = useCallback(
|
|
179
|
+
async (productId: string) => {
|
|
180
|
+
try {
|
|
181
|
+
if (Platform.OS === 'ios') {
|
|
182
|
+
await sync().catch((error) => {
|
|
183
|
+
// Pass the error to the developer's handler if provided
|
|
184
|
+
if (optionsRef.current?.onSyncError) {
|
|
185
|
+
optionsRef.current.onSyncError(error);
|
|
186
|
+
} else {
|
|
187
|
+
// Fallback to original behavior
|
|
188
|
+
console.warn(
|
|
189
|
+
'Sync error occurred. This might require user password:',
|
|
190
|
+
error,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (subscriptions.some((sub) => sub.id === productId)) {
|
|
197
|
+
await getSubscriptionsInternal([productId]);
|
|
198
|
+
await getAvailablePurchasesInternal();
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn('Failed to refresh subscription status:', error);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
[getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions],
|
|
205
|
+
);
|
|
206
|
+
const validateReceipt = useCallback(
|
|
207
|
+
async (
|
|
208
|
+
sku: string,
|
|
209
|
+
androidOptions?: {
|
|
210
|
+
packageName: string;
|
|
211
|
+
productToken: string;
|
|
212
|
+
accessToken: string;
|
|
213
|
+
isSub?: boolean;
|
|
214
|
+
},
|
|
215
|
+
) => {
|
|
162
216
|
if (Platform.OS === 'ios') {
|
|
163
|
-
await
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
217
|
+
return await validateReceiptIos(sku);
|
|
218
|
+
} else if (Platform.OS === 'android') {
|
|
219
|
+
if (
|
|
220
|
+
!androidOptions ||
|
|
221
|
+
!androidOptions.packageName ||
|
|
222
|
+
!androidOptions.productToken ||
|
|
223
|
+
!androidOptions.accessToken
|
|
224
|
+
) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'Android validation requires packageName, productToken, and accessToken',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return await validateReceiptAndroid({
|
|
230
|
+
packageName: androidOptions.packageName,
|
|
231
|
+
productId: sku,
|
|
232
|
+
productToken: androidOptions.productToken,
|
|
233
|
+
accessToken: androidOptions.accessToken,
|
|
234
|
+
isSub: androidOptions.isSub,
|
|
171
235
|
});
|
|
236
|
+
} else {
|
|
237
|
+
throw new Error('Platform not supported');
|
|
172
238
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
await getAvailablePurchasesInternal();
|
|
177
|
-
}
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.warn('Failed to refresh subscription status:', error);
|
|
180
|
-
}
|
|
181
|
-
}, [getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions]);
|
|
239
|
+
},
|
|
240
|
+
[],
|
|
241
|
+
);
|
|
182
242
|
|
|
183
243
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
184
244
|
const result = await initConnection();
|
|
@@ -214,14 +274,14 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
214
274
|
if (Platform.OS === 'ios') {
|
|
215
275
|
subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(
|
|
216
276
|
async (event: TransactionEvent) => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
[...prevProducts, event.transaction
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
277
|
+
setPromotedProductsIOS((prevProducts) =>
|
|
278
|
+
event.transaction
|
|
279
|
+
? [...prevProducts, event.transaction]
|
|
280
|
+
: prevProducts,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (event.transaction && 'expirationDateIos' in event.transaction) {
|
|
284
|
+
await refreshSubscriptionStatus(event.transaction.id);
|
|
225
285
|
}
|
|
226
286
|
},
|
|
227
287
|
);
|
|
@@ -259,5 +319,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
259
319
|
getProducts: getProductsInternal,
|
|
260
320
|
getSubscriptions: getSubscriptionsInternal,
|
|
261
321
|
requestPurchase: requestPurchaseWithReset,
|
|
322
|
+
validateReceipt,
|
|
262
323
|
};
|
|
263
324
|
}
|