expo-iap 2.3.4-rc.1 → 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 +7 -0
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +46 -10
- 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 +92 -24
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,10 +20,17 @@ 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;
|
|
26
32
|
onPurchaseError?: (error: PurchaseError) => void;
|
|
33
|
+
onSyncError?: (error: Error) => void;
|
|
27
34
|
}
|
|
28
35
|
export declare function useIAP(options?: UseIAPOptions): UseIap;
|
|
29
36
|
export {};
|
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();
|
|
@@ -65,11 +70,18 @@ export function useIAP(options) {
|
|
|
65
70
|
const refreshSubscriptionStatus = useCallback(async (productId) => {
|
|
66
71
|
try {
|
|
67
72
|
if (Platform.OS === 'ios') {
|
|
68
|
-
await sync().catch(() => {
|
|
69
|
-
//
|
|
73
|
+
await sync().catch((error) => {
|
|
74
|
+
// Pass the error to the developer's handler if provided
|
|
75
|
+
if (optionsRef.current?.onSyncError) {
|
|
76
|
+
optionsRef.current.onSyncError(error);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Fallback to original behavior
|
|
80
|
+
console.warn('Sync error occurred. This might require user password:', error);
|
|
81
|
+
}
|
|
70
82
|
});
|
|
71
83
|
}
|
|
72
|
-
if (subscriptions.some(sub => sub.id === productId)) {
|
|
84
|
+
if (subscriptions.some((sub) => sub.id === productId)) {
|
|
73
85
|
await getSubscriptionsInternal([productId]);
|
|
74
86
|
await getAvailablePurchasesInternal();
|
|
75
87
|
}
|
|
@@ -78,6 +90,29 @@ export function useIAP(options) {
|
|
|
78
90
|
console.warn('Failed to refresh subscription status:', error);
|
|
79
91
|
}
|
|
80
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
|
+
}, []);
|
|
81
116
|
const initIapWithSubscriptions = useCallback(async () => {
|
|
82
117
|
const result = await initConnection();
|
|
83
118
|
setConnected(result);
|
|
@@ -101,11 +136,11 @@ export function useIAP(options) {
|
|
|
101
136
|
});
|
|
102
137
|
if (Platform.OS === 'ios') {
|
|
103
138
|
subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(async (event) => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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);
|
|
109
144
|
}
|
|
110
145
|
});
|
|
111
146
|
}
|
|
@@ -139,6 +174,7 @@ export function useIAP(options) {
|
|
|
139
174
|
getProducts: getProductsInternal,
|
|
140
175
|
getSubscriptions: getSubscriptionsInternal,
|
|
141
176
|
requestPurchase: requestPurchaseWithReset,
|
|
177
|
+
validateReceipt,
|
|
142
178
|
};
|
|
143
179
|
}
|
|
144
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}\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(() => {\n // Ignore errors as sync might require user password\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":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,qBAAqB,EACrB,WAAW,EACX,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,IAAI,yBAAyB,EAC9C,gBAAgB,EAChB,eAAe,IAAI,uBAAuB,EAC1C,IAAI,EACJ,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,IAAI,CAAC;AACZ,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAC,MAAM,OAAO,CAAC;AAY/D,OAAO,EAAC,QAAQ,EAAC,MAAM,cAAc,CAAC;AA4CtC,MAAM,UAAU,MAAM,CAAC,OAAuB;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,QAAQ,CAE5D,EAAE,CAAC,CAAC;IACN,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAwB,EAAE,CAAC,CAAC;IAC9E,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,CACxD,EAAE,CACH,CAAC;IACF,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,QAAQ,CAE1D,EAAE,CAAC,CAAC;IACN,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,EAAmB,CAAC;IAC1E,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GACnD,QAAQ,EAAiB,CAAC;IAE5B,MAAM,UAAU,GAAG,MAAM,CAA4B,OAAO,CAAC,CAAC;IAE9D,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAEd,MAAM,gBAAgB,GAAG,MAAM,CAI5B,EAAE,CAAC,CAAC;IAEP,MAAM,oBAAoB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5C,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,yBAAyB,GAAG,WAAW,CAAC,GAAG,EAAE;QACjD,uBAAuB,CAAC,SAAS,CAAC,CAAC;IACrC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,mBAAmB,GAAG,WAAW,CACrC,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,WAAW,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAC1C,KAAK,EAAE,IAAc,EAAiB,EAAE;QACtC,gBAAgB,CAAC,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,6BAA6B,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QAC1E,qBAAqB,CAAC,MAAM,qBAAqB,EAAE,CAAC,CAAC;IACvD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,4BAA4B,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACzE,oBAAoB,CAAC,MAAM,kBAAkB,EAAE,CAAC,CAAC;IACnD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,iBAAiB,GAAG,WAAW,CACnC,KAAK,EAAE,EACL,QAAQ,EACR,YAAY,GAIb,EAAqD,EAAE;QACtD,IAAI,CAAC;YACH,OAAO,MAAM,yBAAyB,CAAC;gBACrC,QAAQ;gBACR,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,QAAQ,CAAC,EAAE,KAAK,eAAe,EAAE,EAAE,EAAE,CAAC;gBACxC,oBAAoB,EAAE,CAAC;YACzB,CAAC;YACD,IAAI,QAAQ,CAAC,EAAE,KAAK,oBAAoB,EAAE,SAAS,EAAE,CAAC;gBACpD,yBAAyB,EAAE,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC,EACD;QACE,eAAe,EAAE,EAAE;QACnB,oBAAoB,EAAE,SAAS;QAC/B,oBAAoB;QACpB,yBAAyB;KAC1B,CACF,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAC1C,KAAK,EAAE,UAAyD,EAAE,EAAE;QAClE,oBAAoB,EAAE,CAAC;QACvB,yBAAyB,EAAE,CAAC;QAE5B,IAAI,CAAC;YACH,OAAO,MAAM,uBAAuB,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,EACD,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAClD,CAAC;IAEF,MAAM,yBAAyB,GAAG,WAAW,CAC3C,KAAK,EAAE,SAAiB,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;oBAC3B,wDAAwD;oBACxD,IAAI,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;wBACpC,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;oBACxC,CAAC;yBAAM,CAAC;wBACN,gCAAgC;wBAChC,OAAO,CAAC,IAAI,CACV,wDAAwD,EACxD,KAAK,CACN,CAAC;oBACJ,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;YAED,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC;gBACtD,MAAM,wBAAwB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC5C,MAAM,6BAA6B,EAAE,CAAC;YACxC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,EACD,CAAC,wBAAwB,EAAE,6BAA6B,EAAE,aAAa,CAAC,CACzE,CAAC;IACF,MAAM,eAAe,GAAG,WAAW,CACjC,KAAK,EACH,GAAW,EACX,cAKC,EACD,EAAE;QACF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;YAC1B,OAAO,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YACrC,IACE,CAAC,cAAc;gBACf,CAAC,cAAc,CAAC,WAAW;gBAC3B,CAAC,cAAc,CAAC,YAAY;gBAC5B,CAAC,cAAc,CAAC,WAAW,EAC3B,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,sBAAsB,CAAC;gBAClC,WAAW,EAAE,cAAc,CAAC,WAAW;gBACvC,SAAS,EAAE,GAAG;gBACd,YAAY,EAAE,cAAc,CAAC,YAAY;gBACzC,WAAW,EAAE,cAAc,CAAC,WAAW;gBACvC,KAAK,EAAE,cAAc,CAAC,KAAK;aAC5B,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,wBAAwB,GAAG,WAAW,CAAC,KAAK,IAAmB,EAAE;QACrE,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;QACtC,YAAY,CAAC,MAAM,CAAC,CAAC;QAErB,IAAI,MAAM,EAAE,CAAC;YACX,gBAAgB,CAAC,OAAO,CAAC,cAAc,GAAG,uBAAuB,CAC/D,KAAK,EAAE,QAAyC,EAAE,EAAE;gBAClD,uBAAuB,CAAC,SAAS,CAAC,CAAC;gBACnC,kBAAkB,CAAC,QAAQ,CAAC,CAAC;gBAE7B,IAAI,mBAAmB,IAAI,QAAQ,EAAE,CAAC;oBACpC,MAAM,yBAAyB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAC/C,CAAC;gBAED,IAAI,UAAU,CAAC,OAAO,EAAE,iBAAiB,EAAE,CAAC;oBAC1C,UAAU,CAAC,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC,CACF,CAAC;YAEF,gBAAgB,CAAC,OAAO,CAAC,aAAa,GAAG,qBAAqB,CAC5D,CAAC,KAAoB,EAAE,EAAE;gBACvB,kBAAkB,CAAC,SAAS,CAAC,CAAC;gBAC9B,uBAAuB,CAAC,KAAK,CAAC,CAAC;gBAE/B,IAAI,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;oBACxC,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;gBAC5C,CAAC;YACH,CAAC,CACF,CAAC;YAEF,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBAC1B,gBAAgB,CAAC,OAAO,CAAC,mBAAmB,GAAG,qBAAqB,CAClE,KAAK,EAAE,KAAuB,EAAE,EAAE;oBAChC,sBAAsB,CAAC,CAAC,YAAY,EAAE,EAAE,CACtC,KAAK,CAAC,WAAW;wBACf,CAAC,CAAC,CAAC,GAAG,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC;wBACtC,CAAC,CAAC,YAAY,CACjB,CAAC;oBAEF,IAAI,KAAK,CAAC,WAAW,IAAI,mBAAmB,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;wBAClE,MAAM,yBAAyB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;oBACxD,CAAC;gBACH,CAAC,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAEhC,SAAS,CAAC,GAAG,EAAE;QACb,wBAAwB,EAAE,CAAC;QAC3B,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,OAAO,CAAC;QAEtD,OAAO,GAAG,EAAE;YACV,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAC9C,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;YAC7C,oBAAoB,CAAC,mBAAmB,EAAE,MAAM,EAAE,CAAC;YACnD,aAAa,EAAE,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAE/B,OAAO;QACL,SAAS;QACT,QAAQ;QACR,mBAAmB;QACnB,aAAa;QACb,iBAAiB;QACjB,iBAAiB;QACjB,kBAAkB;QAClB,eAAe;QACf,oBAAoB;QACpB,oBAAoB;QACpB,yBAAyB;QACzB,qBAAqB,EAAE,6BAA6B;QACpD,oBAAoB,EAAE,4BAA4B;QAClD,WAAW,EAAE,mBAAmB;QAChC,gBAAgB,EAAE,wBAAwB;QAC1C,eAAe,EAAE,wBAAwB;QACzC,eAAe;KAChB,CAAC;AACJ,CAAC","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,11 +51,23 @@ 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;
|
|
70
|
+
onSyncError?: (error: Error) => void;
|
|
57
71
|
}
|
|
58
72
|
|
|
59
73
|
export function useIAP(options?: UseIAPOptions): UseIap {
|
|
@@ -139,14 +153,19 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
139
153
|
}
|
|
140
154
|
}
|
|
141
155
|
},
|
|
142
|
-
[
|
|
156
|
+
[
|
|
157
|
+
currentPurchase?.id,
|
|
158
|
+
currentPurchaseError?.productId,
|
|
159
|
+
clearCurrentPurchase,
|
|
160
|
+
clearCurrentPurchaseError,
|
|
161
|
+
],
|
|
143
162
|
);
|
|
144
163
|
|
|
145
164
|
const requestPurchaseWithReset = useCallback(
|
|
146
165
|
async (requestObj: Parameters<typeof requestPurchaseInternal>[0]) => {
|
|
147
166
|
clearCurrentPurchase();
|
|
148
167
|
clearCurrentPurchaseError();
|
|
149
|
-
|
|
168
|
+
|
|
150
169
|
try {
|
|
151
170
|
return await requestPurchaseInternal(requestObj);
|
|
152
171
|
} catch (error) {
|
|
@@ -156,22 +175,70 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
156
175
|
[clearCurrentPurchase, clearCurrentPurchaseError],
|
|
157
176
|
);
|
|
158
177
|
|
|
159
|
-
const refreshSubscriptionStatus = useCallback(
|
|
160
|
-
|
|
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
|
+
) => {
|
|
161
216
|
if (Platform.OS === 'ios') {
|
|
162
|
-
await
|
|
163
|
-
|
|
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,
|
|
164
235
|
});
|
|
236
|
+
} else {
|
|
237
|
+
throw new Error('Platform not supported');
|
|
165
238
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
await getAvailablePurchasesInternal();
|
|
170
|
-
}
|
|
171
|
-
} catch (error) {
|
|
172
|
-
console.warn('Failed to refresh subscription status:', error);
|
|
173
|
-
}
|
|
174
|
-
}, [getSubscriptionsInternal, getAvailablePurchasesInternal, subscriptions]);
|
|
239
|
+
},
|
|
240
|
+
[],
|
|
241
|
+
);
|
|
175
242
|
|
|
176
243
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
177
244
|
const result = await initConnection();
|
|
@@ -207,14 +274,14 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
207
274
|
if (Platform.OS === 'ios') {
|
|
208
275
|
subscriptionsRef.current.promotedProductsIos = transactionUpdatedIos(
|
|
209
276
|
async (event: TransactionEvent) => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
[...prevProducts, event.transaction
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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);
|
|
218
285
|
}
|
|
219
286
|
},
|
|
220
287
|
);
|
|
@@ -252,5 +319,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
252
319
|
getProducts: getProductsInternal,
|
|
253
320
|
getSubscriptions: getSubscriptionsInternal,
|
|
254
321
|
requestPurchase: requestPurchaseWithReset,
|
|
322
|
+
validateReceipt,
|
|
255
323
|
};
|
|
256
324
|
}
|