expo-iap 2.9.0-rc.3 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -6
- package/CLAUDE.md +40 -0
- package/CONTRIBUTING.md +4 -3
- package/build/helpers/subscription.d.ts +3 -0
- package/build/helpers/subscription.d.ts.map +1 -1
- package/build/helpers/subscription.js +10 -5
- package/build/helpers/subscription.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +29 -10
- package/build/index.js.map +1 -1
- package/build/modules/ios.d.ts +4 -5
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +2 -3
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +2 -2
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIOS.types.d.ts +3 -3
- package/build/types/ExpoIapIOS.types.d.ts.map +1 -1
- package/build/types/ExpoIapIOS.types.js.map +1 -1
- package/build/useIAP.d.ts +1 -1
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +54 -33
- package/build/useIAP.js.map +1 -1
- package/build/utils/constants.d.ts +4 -0
- package/build/utils/constants.d.ts.map +1 -0
- package/build/utils/constants.js +12 -0
- package/build/utils/constants.js.map +1 -0
- package/ios/ExpoIap.podspec +1 -1
- package/ios/ExpoIapModule.swift +341 -334
- package/jest.config.js +19 -14
- package/package.json +2 -3
- package/plugin/build/withIAP.js +25 -23
- package/plugin/build/withLocalOpenIAP.js +5 -1
- package/plugin/src/withIAP.ts +39 -31
- package/plugin/src/withLocalOpenIAP.ts +8 -2
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/helpers/subscription.ts +35 -23
- package/src/index.ts +50 -33
- package/src/modules/ios.ts +4 -5
- package/src/types/ExpoIapAndroid.types.ts +4 -3
- package/src/types/ExpoIapIOS.types.ts +3 -4
- package/src/useIAP.ts +73 -52
- package/src/utils/constants.ts +14 -0
- package/ios/ProductStore.swift +0 -27
- package/ios/Types.swift +0 -96
package/src/modules/ios.ts
CHANGED
|
@@ -170,15 +170,14 @@ export const beginRefundRequestIOS = (
|
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Shows the system UI for managing subscriptions.
|
|
173
|
-
*
|
|
174
|
-
* purchaseUpdatedListener and transactionUpdatedIOS listeners.
|
|
173
|
+
* Returns an array of subscriptions that had status changes after the UI is closed.
|
|
175
174
|
*
|
|
176
|
-
* @returns Promise
|
|
175
|
+
* @returns Promise<Purchase[]> - Array of subscriptions with status changes (e.g., auto-renewal toggled)
|
|
177
176
|
* @throws Error if called on non-iOS platform
|
|
178
177
|
*
|
|
179
178
|
* @platform iOS
|
|
180
179
|
*/
|
|
181
|
-
export const showManageSubscriptionsIOS = (): Promise<
|
|
180
|
+
export const showManageSubscriptionsIOS = (): Promise<Purchase[]> => {
|
|
182
181
|
return ExpoIapModule.showManageSubscriptionsIOS();
|
|
183
182
|
};
|
|
184
183
|
|
|
@@ -417,7 +416,7 @@ export const beginRefundRequest = (
|
|
|
417
416
|
/**
|
|
418
417
|
* @deprecated Use `showManageSubscriptionsIOS` instead. This function will be removed in version 3.0.0.
|
|
419
418
|
*/
|
|
420
|
-
export const showManageSubscriptions = (): Promise<
|
|
419
|
+
export const showManageSubscriptions = (): Promise<Purchase[]> => {
|
|
421
420
|
console.warn(
|
|
422
421
|
'`showManageSubscriptions` is deprecated. Use `showManageSubscriptionsIOS` instead. This function will be removed in version 3.0.0.',
|
|
423
422
|
);
|
|
@@ -31,7 +31,7 @@ type ProductSubscriptionAndroidOfferDetail = {
|
|
|
31
31
|
export type ProductAndroid = ProductCommon & {
|
|
32
32
|
nameAndroid: string;
|
|
33
33
|
oneTimePurchaseOfferDetailsAndroid?: ProductAndroidOneTimePurchaseOfferDetail;
|
|
34
|
-
platform:
|
|
34
|
+
platform: 'android';
|
|
35
35
|
subscriptionOfferDetailsAndroid?: ProductSubscriptionAndroidOfferDetail[];
|
|
36
36
|
/**
|
|
37
37
|
* @deprecated Use `nameAndroid` instead. This field will be removed in v2.9.0.
|
|
@@ -144,7 +144,7 @@ export const PurchaseStateAndroid = PurchaseAndroidState;
|
|
|
144
144
|
|
|
145
145
|
// Legacy naming for backward compatibility
|
|
146
146
|
export type ProductPurchaseAndroid = PurchaseCommon & {
|
|
147
|
-
platform:
|
|
147
|
+
platform: 'android';
|
|
148
148
|
/**
|
|
149
149
|
* @deprecated Use `purchaseToken` instead. This field will be removed in a future version.
|
|
150
150
|
*/
|
|
@@ -167,7 +167,8 @@ export type PurchaseAndroid = ProductPurchaseAndroid;
|
|
|
167
167
|
/**
|
|
168
168
|
* @deprecated Use `ProductAndroidOneTimePurchaseOfferDetail` instead. This type will be removed in v2.9.0.
|
|
169
169
|
*/
|
|
170
|
-
export type OneTimePurchaseOfferDetails =
|
|
170
|
+
export type OneTimePurchaseOfferDetails =
|
|
171
|
+
ProductAndroidOneTimePurchaseOfferDetail;
|
|
171
172
|
|
|
172
173
|
/**
|
|
173
174
|
* @deprecated Use `ProductSubscriptionAndroidOfferDetail` instead. This type will be removed in v2.9.0.
|
|
@@ -30,7 +30,7 @@ export type ProductIOS = ProductCommon & {
|
|
|
30
30
|
displayNameIOS: string;
|
|
31
31
|
isFamilyShareableIOS: boolean;
|
|
32
32
|
jsonRepresentationIOS: string;
|
|
33
|
-
platform:
|
|
33
|
+
platform: 'ios';
|
|
34
34
|
subscriptionInfoIOS?: SubscriptionInfo;
|
|
35
35
|
/**
|
|
36
36
|
* @deprecated Use `displayNameIOS` instead. This field will be removed in v2.9.0.
|
|
@@ -69,7 +69,7 @@ export type ProductSubscriptionIOS = ProductIOS & {
|
|
|
69
69
|
introductoryPricePaymentModeIOS?: PaymentMode;
|
|
70
70
|
introductoryPriceNumberOfPeriodsIOS?: string;
|
|
71
71
|
introductoryPriceSubscriptionPeriodIOS?: SubscriptionIosPeriod;
|
|
72
|
-
platform:
|
|
72
|
+
platform: 'ios';
|
|
73
73
|
subscriptionPeriodNumberIOS?: string;
|
|
74
74
|
subscriptionPeriodUnitIOS?: SubscriptionIosPeriod;
|
|
75
75
|
/**
|
|
@@ -140,7 +140,7 @@ export type ProductStatusIOS = {
|
|
|
140
140
|
// Legacy naming for backward compatibility
|
|
141
141
|
export type ProductPurchaseIOS = PurchaseCommon & {
|
|
142
142
|
// iOS basic fields
|
|
143
|
-
platform:
|
|
143
|
+
platform: 'ios';
|
|
144
144
|
quantityIOS?: number;
|
|
145
145
|
originalTransactionDateIOS?: number;
|
|
146
146
|
originalTransactionIdentifierIOS?: string;
|
|
@@ -179,7 +179,6 @@ export type ProductPurchaseIOS = PurchaseCommon & {
|
|
|
179
179
|
// Preferred naming
|
|
180
180
|
export type PurchaseIOS = ProductPurchaseIOS;
|
|
181
181
|
|
|
182
|
-
|
|
183
182
|
export type AppTransactionIOS = {
|
|
184
183
|
appTransactionId?: string; // Only available in iOS 18.4+
|
|
185
184
|
originalPlatform?: string; // Only available in iOS 18.4+
|
package/src/useIAP.ts
CHANGED
|
@@ -100,16 +100,12 @@ type UseIap = {
|
|
|
100
100
|
requestPurchaseOnPromotedProductIOS: () => Promise<void>;
|
|
101
101
|
/** @deprecated Use requestPurchaseOnPromotedProductIOS instead */
|
|
102
102
|
buyPromotedProductIOS: () => Promise<void>;
|
|
103
|
-
getActiveSubscriptions: (
|
|
104
|
-
subscriptionIds?: string[],
|
|
105
|
-
) => Promise<ActiveSubscription[]>;
|
|
103
|
+
getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;
|
|
106
104
|
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
107
105
|
};
|
|
108
106
|
|
|
109
107
|
export interface UseIAPOptions {
|
|
110
|
-
onPurchaseSuccess?: (
|
|
111
|
-
purchase: Purchase,
|
|
112
|
-
) => void;
|
|
108
|
+
onPurchaseSuccess?: (purchase: Purchase) => void;
|
|
113
109
|
onPurchaseError?: (error: PurchaseError) => void;
|
|
114
110
|
onSyncError?: (error: Error) => void;
|
|
115
111
|
shouldAutoSyncPurchases?: boolean; // New option to control auto-syncing
|
|
@@ -125,12 +121,8 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
125
121
|
const [products, setProducts] = useState<Product[]>([]);
|
|
126
122
|
const [promotedProductsIOS] = useState<Purchase[]>([]);
|
|
127
123
|
const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);
|
|
128
|
-
const [purchaseHistories, setPurchaseHistories] = useState<Purchase[]>(
|
|
129
|
-
|
|
130
|
-
);
|
|
131
|
-
const [availablePurchases, setAvailablePurchases] = useState<
|
|
132
|
-
Purchase[]
|
|
133
|
-
>([]);
|
|
124
|
+
const [purchaseHistories, setPurchaseHistories] = useState<Purchase[]>([]);
|
|
125
|
+
const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);
|
|
134
126
|
const [currentPurchase, setCurrentPurchase] = useState<Purchase>();
|
|
135
127
|
const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();
|
|
136
128
|
const [currentPurchaseError, setCurrentPurchaseError] =
|
|
@@ -231,6 +223,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
231
223
|
}): Promise<void> => {
|
|
232
224
|
try {
|
|
233
225
|
const result = await fetchProducts(params);
|
|
226
|
+
|
|
234
227
|
if (params.type === 'subs') {
|
|
235
228
|
setSubscriptions((prevSubscriptions) =>
|
|
236
229
|
mergeWithDuplicateCheck(
|
|
@@ -261,7 +254,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
261
254
|
type?: 'inapp' | 'subs';
|
|
262
255
|
}): Promise<void> => {
|
|
263
256
|
console.warn(
|
|
264
|
-
"`requestProducts` is deprecated in useIAP hook. Use the new `fetchProducts` method instead. The 'request' prefix should only be used for event-based operations."
|
|
257
|
+
"`requestProducts` is deprecated in useIAP hook. Use the new `fetchProducts` method instead. The 'request' prefix should only be used for event-based operations.",
|
|
265
258
|
);
|
|
266
259
|
return fetchProductsInternal(params);
|
|
267
260
|
},
|
|
@@ -270,7 +263,10 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
270
263
|
|
|
271
264
|
const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
272
265
|
try {
|
|
273
|
-
const result = await getAvailablePurchases(
|
|
266
|
+
const result = await getAvailablePurchases({
|
|
267
|
+
alsoPublishToEventListenerIOS: false,
|
|
268
|
+
onlyIncludeActiveItemsIOS: true,
|
|
269
|
+
});
|
|
274
270
|
setAvailablePurchases(result);
|
|
275
271
|
} catch (error) {
|
|
276
272
|
console.error('Error fetching available purchases:', error);
|
|
@@ -278,16 +274,13 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
278
274
|
}, []);
|
|
279
275
|
|
|
280
276
|
const getActiveSubscriptionsInternal = useCallback(
|
|
281
|
-
async (subscriptionIds?: string[]): Promise<
|
|
277
|
+
async (subscriptionIds?: string[]): Promise<void> => {
|
|
282
278
|
try {
|
|
283
279
|
const result = await getActiveSubscriptions(subscriptionIds);
|
|
284
280
|
setActiveSubscriptions(result);
|
|
285
|
-
return result;
|
|
286
281
|
} catch (error) {
|
|
287
282
|
console.error('Error getting active subscriptions:', error);
|
|
288
|
-
//
|
|
289
|
-
// This prevents the UI from showing empty state when there are temporary network issues
|
|
290
|
-
return [];
|
|
283
|
+
// Preserve existing state on error
|
|
291
284
|
}
|
|
292
285
|
},
|
|
293
286
|
[],
|
|
@@ -406,48 +399,76 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
406
399
|
);
|
|
407
400
|
|
|
408
401
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
409
|
-
|
|
410
|
-
|
|
402
|
+
// CRITICAL: Register listeners BEFORE initConnection to avoid race condition
|
|
403
|
+
// Events might fire immediately after initConnection, so listeners must be ready
|
|
404
|
+
console.log('[useIAP] Setting up event listeners BEFORE initConnection...');
|
|
405
|
+
|
|
406
|
+
// Register purchase update listener BEFORE initConnection to avoid race conditions.
|
|
407
|
+
subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(
|
|
408
|
+
async (purchase: Purchase) => {
|
|
409
|
+
console.log('[useIAP] Purchase success callback triggered:', purchase);
|
|
410
|
+
setCurrentPurchaseError(undefined);
|
|
411
|
+
setCurrentPurchase(purchase);
|
|
412
|
+
|
|
413
|
+
if ('expirationDateIOS' in purchase) {
|
|
414
|
+
await refreshSubscriptionStatus(purchase.id);
|
|
415
|
+
}
|
|
411
416
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
+
if (optionsRef.current?.onPurchaseSuccess) {
|
|
418
|
+
optionsRef.current.onPurchaseSuccess(purchase);
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
);
|
|
417
422
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
423
|
+
// IMPORTANT: Do NOT register the purchase error listener until after initConnection succeeds.
|
|
424
|
+
// Some platforms may emit an initialization error event (E_INIT_CONNECTION) during startup.
|
|
425
|
+
// Delaying registration prevents noisy, misleading errors before the connection is ready.
|
|
421
426
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
subscriptionsRef.current.purchaseError = purchaseErrorListener(
|
|
429
|
-
(error: PurchaseError) => {
|
|
430
|
-
setCurrentPurchase(undefined);
|
|
431
|
-
setCurrentPurchaseError(error);
|
|
427
|
+
if (Platform.OS === 'ios') {
|
|
428
|
+
// iOS promoted products listener
|
|
429
|
+
subscriptionsRef.current.promotedProductsIOS = promotedProductListenerIOS(
|
|
430
|
+
(product: Product) => {
|
|
431
|
+
console.log('[useIAP] Promoted product callback triggered:', product);
|
|
432
|
+
setPromotedProductIOS(product);
|
|
432
433
|
|
|
433
|
-
if (optionsRef.current?.
|
|
434
|
-
optionsRef.current.
|
|
434
|
+
if (optionsRef.current?.onPromotedProductIOS) {
|
|
435
|
+
optionsRef.current.onPromotedProductIOS(product);
|
|
435
436
|
}
|
|
436
437
|
},
|
|
437
438
|
);
|
|
439
|
+
}
|
|
438
440
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
441
|
+
console.log(
|
|
442
|
+
'[useIAP] Event listeners registered, now calling initConnection...',
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
// NOW call initConnection after listeners are ready
|
|
446
|
+
const result = await initConnection();
|
|
447
|
+
setConnected(result);
|
|
448
|
+
console.log('[useIAP] initConnection result:', result);
|
|
449
|
+
if (!result) {
|
|
450
|
+
// If connection failed, clean up listeners
|
|
451
|
+
console.warn('[useIAP] Connection failed, cleaning up listeners...');
|
|
452
|
+
subscriptionsRef.current.purchaseUpdate?.remove();
|
|
453
|
+
subscriptionsRef.current.promotedProductsIOS?.remove();
|
|
454
|
+
subscriptionsRef.current.purchaseUpdate = undefined;
|
|
455
|
+
subscriptionsRef.current.promotedProductsIOS = undefined;
|
|
456
|
+
// Do not register error listener when connection fails
|
|
457
|
+
return;
|
|
450
458
|
}
|
|
459
|
+
|
|
460
|
+
// Now that the connection is established, register the purchase error listener.
|
|
461
|
+
subscriptionsRef.current.purchaseError = purchaseErrorListener(
|
|
462
|
+
(error: PurchaseError) => {
|
|
463
|
+
console.log('[useIAP] Purchase error callback triggered:', error);
|
|
464
|
+
setCurrentPurchase(undefined);
|
|
465
|
+
setCurrentPurchaseError(error);
|
|
466
|
+
|
|
467
|
+
if (optionsRef.current?.onPurchaseError) {
|
|
468
|
+
optionsRef.current.onPurchaseError(error);
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
);
|
|
451
472
|
}, [refreshSubscriptionStatus]);
|
|
452
473
|
|
|
453
474
|
useEffect(() => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Centralized product ID constants for examples and internal usage
|
|
2
|
+
// Rename guide: subscriptionIds -> SUBSCRIPTION_PRODUCT_IDS, PRODUCT_IDS remains the same name
|
|
3
|
+
|
|
4
|
+
// One-time purchase product IDs (consumables/non-consumables)
|
|
5
|
+
export const PRODUCT_IDS: string[] = [
|
|
6
|
+
'dev.hyo.martie.10bulbs',
|
|
7
|
+
'dev.hyo.martie.30bulbs',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
// Subscription product IDs
|
|
11
|
+
export const SUBSCRIPTION_PRODUCT_IDS: string[] = ['dev.hyo.martie.premium'];
|
|
12
|
+
|
|
13
|
+
// Optionally export a single default subscription for convenience
|
|
14
|
+
export const DEFAULT_SUBSCRIPTION_PRODUCT_ID = SUBSCRIPTION_PRODUCT_IDS[0];
|
package/ios/ProductStore.swift
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import StoreKit
|
|
3
|
-
|
|
4
|
-
@available(iOS 15.0, *)
|
|
5
|
-
actor ProductStore {
|
|
6
|
-
private(set) var products: [String: Product] = [:]
|
|
7
|
-
|
|
8
|
-
func addProduct(_ product: Product) {
|
|
9
|
-
self.products[product.id] = product
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
func getAllProducts() -> [Product] {
|
|
13
|
-
return Array(self.products.values)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
func getProduct(productID: String) -> Product? {
|
|
17
|
-
return self.products[productID]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
func removeAll() {
|
|
21
|
-
products.removeAll()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func performOnActor(_ action: @escaping (isolated ProductStore) -> Void) async {
|
|
25
|
-
action(self)
|
|
26
|
-
}
|
|
27
|
-
}
|
package/ios/Types.swift
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// IapTypes.swift
|
|
3
|
-
// RNIap
|
|
4
|
-
//
|
|
5
|
-
// Created by Andres Aguilar on 8/18/22.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import Foundation
|
|
9
|
-
import StoreKit
|
|
10
|
-
|
|
11
|
-
public enum StoreError: Error {
|
|
12
|
-
case failedVerification
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Error codes for IAP operations - centralized error code management
|
|
16
|
-
struct IapErrorCode {
|
|
17
|
-
// Constants for code usage - safe pattern without force unwrapping
|
|
18
|
-
static let unknown = "E_UNKNOWN"
|
|
19
|
-
static let serviceError = "E_SERVICE_ERROR"
|
|
20
|
-
static let userCancelled = "E_USER_CANCELLED"
|
|
21
|
-
static let userError = "E_USER_ERROR"
|
|
22
|
-
static let itemUnavailable = "E_ITEM_UNAVAILABLE"
|
|
23
|
-
static let remoteError = "E_REMOTE_ERROR"
|
|
24
|
-
static let networkError = "E_NETWORK_ERROR"
|
|
25
|
-
static let receiptFailed = "E_RECEIPT_FAILED"
|
|
26
|
-
static let receiptFinishedFailed = "E_RECEIPT_FINISHED_FAILED"
|
|
27
|
-
static let notPrepared = "E_NOT_PREPARED"
|
|
28
|
-
static let notEnded = "E_NOT_ENDED"
|
|
29
|
-
static let alreadyOwned = "E_ALREADY_OWNED"
|
|
30
|
-
static let developerError = "E_DEVELOPER_ERROR"
|
|
31
|
-
static let purchaseError = "E_PURCHASE_ERROR"
|
|
32
|
-
static let syncError = "E_SYNC_ERROR"
|
|
33
|
-
static let deferredPayment = "E_DEFERRED_PAYMENT"
|
|
34
|
-
static let transactionValidationFailed = "E_TRANSACTION_VALIDATION_FAILED"
|
|
35
|
-
static let billingResponseJsonParseError = "E_BILLING_RESPONSE_JSON_PARSE_ERROR"
|
|
36
|
-
static let interrupted = "E_INTERRUPTED"
|
|
37
|
-
static let iapNotAvailable = "E_IAP_NOT_AVAILABLE"
|
|
38
|
-
static let activityUnavailable = "E_ACTIVITY_UNAVAILABLE"
|
|
39
|
-
static let alreadyPrepared = "E_ALREADY_PREPARED"
|
|
40
|
-
static let pending = "E_PENDING"
|
|
41
|
-
static let connectionClosed = "E_CONNECTION_CLOSED"
|
|
42
|
-
|
|
43
|
-
// Cached dictionary for Constants export - using constants as keys to avoid duplication
|
|
44
|
-
private static let _cachedDictionary: [String: String] = [
|
|
45
|
-
unknown: unknown,
|
|
46
|
-
serviceError: serviceError,
|
|
47
|
-
userCancelled: userCancelled,
|
|
48
|
-
userError: userError,
|
|
49
|
-
itemUnavailable: itemUnavailable,
|
|
50
|
-
remoteError: remoteError,
|
|
51
|
-
networkError: networkError,
|
|
52
|
-
receiptFailed: receiptFailed,
|
|
53
|
-
receiptFinishedFailed: receiptFinishedFailed,
|
|
54
|
-
notPrepared: notPrepared,
|
|
55
|
-
notEnded: notEnded,
|
|
56
|
-
alreadyOwned: alreadyOwned,
|
|
57
|
-
developerError: developerError,
|
|
58
|
-
purchaseError: purchaseError,
|
|
59
|
-
syncError: syncError,
|
|
60
|
-
deferredPayment: deferredPayment,
|
|
61
|
-
transactionValidationFailed: transactionValidationFailed,
|
|
62
|
-
billingResponseJsonParseError: billingResponseJsonParseError,
|
|
63
|
-
interrupted: interrupted,
|
|
64
|
-
iapNotAvailable: iapNotAvailable,
|
|
65
|
-
activityUnavailable: activityUnavailable,
|
|
66
|
-
alreadyPrepared: alreadyPrepared,
|
|
67
|
-
pending: pending,
|
|
68
|
-
connectionClosed: connectionClosed
|
|
69
|
-
]
|
|
70
|
-
|
|
71
|
-
// Return cached dictionary - no allocation on repeated calls
|
|
72
|
-
static func toDictionary() -> [String: String] {
|
|
73
|
-
return _cachedDictionary
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Based on https://stackoverflow.com/a/40135192/570612
|
|
78
|
-
extension Date {
|
|
79
|
-
var millisecondsSince1970: Int64 {
|
|
80
|
-
return Int64((self.timeIntervalSince1970 * 1000.0).rounded())
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
var millisecondsSince1970String: String {
|
|
84
|
-
return String(self.millisecondsSince1970)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
init(milliseconds: Int64) {
|
|
88
|
-
self = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
extension SKProductsRequest {
|
|
93
|
-
var key: String {
|
|
94
|
-
return String(self.hashValue)
|
|
95
|
-
}
|
|
96
|
-
}
|