expo-iap 2.7.6 → 2.7.7-rc.1
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/.copilot-instructions.md +8 -26
- package/.markdownlint.json +11 -0
- package/CHANGELOG.md +56 -34
- package/CONTRIBUTING.md +2 -25
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -3
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js.map +1 -1
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +1 -1
- package/build/useIap.js.map +1 -1
- package/ios/ExpoIapModule.swift +92 -52
- package/ios/expoiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/expoiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/package.json +1 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIap.types.ts +8 -17
- package/src/index.ts +34 -31
- package/src/modules/android.ts +4 -2
- package/src/modules/ios.ts +47 -87
- package/src/useIap.ts +5 -8
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -229,6 +229,9 @@ public class ExpoIapModule: Module {
|
|
|
229
229
|
private var paymentObserver: PaymentObserver?
|
|
230
230
|
private var promotedPayment: SKPayment?
|
|
231
231
|
private var promotedProduct: SKProduct?
|
|
232
|
+
|
|
233
|
+
// Add a flag to track initialization state
|
|
234
|
+
private var isInitialized = false
|
|
232
235
|
|
|
233
236
|
public func definition() -> ModuleDefinition {
|
|
234
237
|
Name("ExpoIap")
|
|
@@ -250,6 +253,10 @@ public class ExpoIapModule: Module {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
Function("initConnection") { () -> Bool in
|
|
256
|
+
// Clean up any existing state first (important for hot reload)
|
|
257
|
+
self.cleanupExistingState()
|
|
258
|
+
|
|
259
|
+
// Initialize fresh state
|
|
253
260
|
self.productStore = ProductStore()
|
|
254
261
|
|
|
255
262
|
// Set up PaymentObserver for promoted products
|
|
@@ -258,6 +265,7 @@ public class ExpoIapModule: Module {
|
|
|
258
265
|
SKPaymentQueue.default().add(self.paymentObserver!)
|
|
259
266
|
}
|
|
260
267
|
|
|
268
|
+
self.isInitialized = true
|
|
261
269
|
return AppStore.canMakePayments
|
|
262
270
|
}
|
|
263
271
|
|
|
@@ -352,10 +360,10 @@ public class ExpoIapModule: Module {
|
|
|
352
360
|
}
|
|
353
361
|
|
|
354
362
|
AsyncFunction("getItems") { (skus: [String]) -> [[String: Any?]?] in
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
363
|
+
try self.ensureConnection()
|
|
364
|
+
|
|
365
|
+
let productStore = self.productStore!
|
|
366
|
+
|
|
359
367
|
do {
|
|
360
368
|
let fetchedProducts = try await Product.products(for: skus)
|
|
361
369
|
await productStore.performOnActor { isolatedStore in
|
|
@@ -367,48 +375,26 @@ public class ExpoIapModule: Module {
|
|
|
367
375
|
return products.map { serializeProduct($0) }.compactMap { $0 }
|
|
368
376
|
} catch {
|
|
369
377
|
print("Error fetching items: \(error)")
|
|
370
|
-
if error is Exception {
|
|
371
|
-
throw error
|
|
372
|
-
}
|
|
373
378
|
throw error
|
|
374
379
|
}
|
|
375
380
|
}
|
|
376
381
|
|
|
377
382
|
AsyncFunction("endConnection") { () -> Bool in
|
|
378
|
-
|
|
379
|
-
return false
|
|
380
|
-
}
|
|
381
|
-
await productStore.removeAll()
|
|
382
|
-
self.transactions.removeAll()
|
|
383
|
-
self.productStore = nil
|
|
384
|
-
self.removeTransactionObserver()
|
|
385
|
-
|
|
386
|
-
// Remove PaymentObserver
|
|
387
|
-
if let observer = self.paymentObserver {
|
|
388
|
-
SKPaymentQueue.default().remove(observer)
|
|
389
|
-
self.paymentObserver = nil
|
|
390
|
-
}
|
|
391
|
-
|
|
383
|
+
self.cleanupExistingState()
|
|
392
384
|
return true
|
|
393
385
|
}
|
|
394
386
|
|
|
395
387
|
AsyncFunction("getAvailableItems") {
|
|
396
388
|
(alsoPublishToEventListener: Bool, onlyIncludeActiveItems: Bool) -> [[String: Any?]?] in
|
|
389
|
+
|
|
390
|
+
try self.ensureConnection()
|
|
391
|
+
|
|
397
392
|
var purchasedItemsSerialized: [[String: Any?]] = []
|
|
398
393
|
|
|
399
394
|
func addTransaction(transaction: Transaction, jwsRepresentationIos: String? = nil) {
|
|
400
|
-
// Debug: Log JWS representation
|
|
401
|
-
logDebug("getAvailableItems JWS: \(jwsRepresentationIos != nil ? "exists" : "nil")")
|
|
402
|
-
if let jws = jwsRepresentationIos {
|
|
403
|
-
logDebug("getAvailableItems JWS length: \(jws.count)")
|
|
404
|
-
}
|
|
405
|
-
|
|
406
395
|
let serialized = serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos)
|
|
407
396
|
purchasedItemsSerialized.append(serialized)
|
|
408
397
|
|
|
409
|
-
// Debug: Check if jwsRepresentationIos is included in serialized result
|
|
410
|
-
logDebug("getAvailableItems serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
|
|
411
|
-
|
|
412
398
|
if alsoPublishToEventListener {
|
|
413
399
|
self.sendEvent(IapEvent.PurchaseUpdated, serialized)
|
|
414
400
|
}
|
|
@@ -476,9 +462,9 @@ public class ExpoIapModule: Module {
|
|
|
476
462
|
sku: String, andDangerouslyFinishTransactionAutomatically: Bool,
|
|
477
463
|
appAccountToken: String?, quantity: Int, discountOffer: [String: String]?
|
|
478
464
|
) -> [String: Any?]? in
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
465
|
+
|
|
466
|
+
try self.ensureConnection()
|
|
467
|
+
let productStore = self.productStore!
|
|
482
468
|
|
|
483
469
|
let product: Product? = await productStore.getProduct(productID: sku)
|
|
484
470
|
if let product = product {
|
|
@@ -572,9 +558,8 @@ public class ExpoIapModule: Module {
|
|
|
572
558
|
}
|
|
573
559
|
|
|
574
560
|
AsyncFunction("subscriptionStatus") { (sku: String) -> [[String: Any?]?]? in
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
561
|
+
try self.ensureConnection()
|
|
562
|
+
let productStore = self.productStore!
|
|
578
563
|
|
|
579
564
|
do {
|
|
580
565
|
let product = await productStore.getProduct(productID: sku)
|
|
@@ -593,9 +578,8 @@ public class ExpoIapModule: Module {
|
|
|
593
578
|
}
|
|
594
579
|
|
|
595
580
|
AsyncFunction("currentEntitlement") { (sku: String) -> [String: Any?]? in
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
581
|
+
try self.ensureConnection()
|
|
582
|
+
let productStore = self.productStore!
|
|
599
583
|
|
|
600
584
|
if let product = await productStore.getProduct(productID: sku) {
|
|
601
585
|
if let result = await product.currentEntitlement {
|
|
@@ -619,9 +603,8 @@ public class ExpoIapModule: Module {
|
|
|
619
603
|
}
|
|
620
604
|
|
|
621
605
|
AsyncFunction("latestTransaction") { (sku: String) -> [String: Any?]? in
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
}
|
|
606
|
+
try self.ensureConnection()
|
|
607
|
+
let productStore = self.productStore!
|
|
625
608
|
|
|
626
609
|
if let product = await productStore.getProduct(productID: sku) {
|
|
627
610
|
if let result = await product.latestTransaction {
|
|
@@ -716,7 +699,10 @@ public class ExpoIapModule: Module {
|
|
|
716
699
|
|
|
717
700
|
AsyncFunction("beginRefundRequest") { (sku: String) -> String? in
|
|
718
701
|
#if !os(tvOS)
|
|
719
|
-
|
|
702
|
+
try self.ensureConnection()
|
|
703
|
+
let productStore = self.productStore!
|
|
704
|
+
|
|
705
|
+
guard let product = await productStore.getProduct(productID: sku),
|
|
720
706
|
let result = await product.latestTransaction
|
|
721
707
|
else {
|
|
722
708
|
throw Exception(name: "ExpoIapModule", description: "Can't find product or transaction for sku \(sku)", code: IapErrorCode.itemUnavailable)
|
|
@@ -752,9 +738,8 @@ public class ExpoIapModule: Module {
|
|
|
752
738
|
}
|
|
753
739
|
|
|
754
740
|
AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
741
|
+
try self.ensureConnection()
|
|
742
|
+
let productStore = self.productStore!
|
|
758
743
|
|
|
759
744
|
if let product = await productStore.getProduct(productID: sku),
|
|
760
745
|
let result = await product.latestTransaction {
|
|
@@ -770,9 +755,8 @@ public class ExpoIapModule: Module {
|
|
|
770
755
|
}
|
|
771
756
|
|
|
772
757
|
AsyncFunction("getTransactionJws") { (sku: String) -> String? in
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
}
|
|
758
|
+
try self.ensureConnection()
|
|
759
|
+
let productStore = self.productStore!
|
|
776
760
|
|
|
777
761
|
if let product = await productStore.getProduct(productID: sku),
|
|
778
762
|
let result = await product.latestTransaction {
|
|
@@ -783,9 +767,8 @@ public class ExpoIapModule: Module {
|
|
|
783
767
|
}
|
|
784
768
|
|
|
785
769
|
AsyncFunction("validateReceiptIOS") { (sku: String) -> [String: Any] in
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
770
|
+
try self.ensureConnection()
|
|
771
|
+
let productStore = self.productStore!
|
|
789
772
|
|
|
790
773
|
// Get receipt data
|
|
791
774
|
var receiptData: String = ""
|
|
@@ -824,6 +807,58 @@ public class ExpoIapModule: Module {
|
|
|
824
807
|
}
|
|
825
808
|
}
|
|
826
809
|
|
|
810
|
+
// Similar to Android's ensureConnection pattern
|
|
811
|
+
private func ensureConnection() throws {
|
|
812
|
+
guard isInitialized else {
|
|
813
|
+
throw Exception(
|
|
814
|
+
name: "ExpoIapModule",
|
|
815
|
+
description: "Connection not initialized. Call initConnection() first.",
|
|
816
|
+
code: IapErrorCode.notPrepared
|
|
817
|
+
)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
guard productStore != nil else {
|
|
821
|
+
throw Exception(
|
|
822
|
+
name: "ExpoIapModule",
|
|
823
|
+
description: "Product store not available",
|
|
824
|
+
code: IapErrorCode.notPrepared
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private func cleanupExistingState() {
|
|
830
|
+
// Cancel any existing tasks
|
|
831
|
+
updateListenerTask?.cancel()
|
|
832
|
+
updateListenerTask = nil
|
|
833
|
+
|
|
834
|
+
subscriptionPollingTask?.cancel()
|
|
835
|
+
subscriptionPollingTask = nil
|
|
836
|
+
|
|
837
|
+
// Clear collections
|
|
838
|
+
transactions.removeAll()
|
|
839
|
+
pollingSkus.removeAll()
|
|
840
|
+
|
|
841
|
+
// Reset promoted products
|
|
842
|
+
promotedPayment = nil
|
|
843
|
+
promotedProduct = nil
|
|
844
|
+
|
|
845
|
+
// Remove existing payment observer if any
|
|
846
|
+
if let observer = paymentObserver {
|
|
847
|
+
SKPaymentQueue.default().remove(observer)
|
|
848
|
+
paymentObserver = nil
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// Clear product store
|
|
852
|
+
if let store = productStore {
|
|
853
|
+
Task {
|
|
854
|
+
await store.removeAll()
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
productStore = nil
|
|
858
|
+
|
|
859
|
+
isInitialized = false
|
|
860
|
+
}
|
|
861
|
+
|
|
827
862
|
private func addTransactionObserver() {
|
|
828
863
|
if updateListenerTask == nil {
|
|
829
864
|
updateListenerTask = listenForTransactions()
|
|
@@ -996,6 +1031,11 @@ public class ExpoIapModule: Module {
|
|
|
996
1031
|
sendEvent(IapEvent.PromotedProductIOS, productData)
|
|
997
1032
|
}
|
|
998
1033
|
}
|
|
1034
|
+
|
|
1035
|
+
// Ensure cleanup when module is deallocated
|
|
1036
|
+
deinit {
|
|
1037
|
+
cleanupExistingState()
|
|
1038
|
+
}
|
|
999
1039
|
}
|
|
1000
1040
|
|
|
1001
1041
|
// PaymentObserver for handling promoted products
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/
|
|
1
|
+
{"root":["./src/withiap.ts"],"version":"5.8.3"}
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -365,8 +365,7 @@ export interface AndroidRequestPurchaseProps {
|
|
|
365
365
|
/**
|
|
366
366
|
* Android-specific subscription request parameters
|
|
367
367
|
*/
|
|
368
|
-
export interface AndroidRequestSubscriptionProps
|
|
369
|
-
extends AndroidRequestPurchaseProps {
|
|
368
|
+
export interface AndroidRequestSubscriptionProps extends AndroidRequestPurchaseProps {
|
|
370
369
|
readonly purchaseTokenAndroid?: string;
|
|
371
370
|
readonly replacementModeAndroid?: number;
|
|
372
371
|
readonly subscriptionOffers: {
|
|
@@ -409,36 +408,28 @@ export type RequestSubscriptionProps = PlatformRequestSubscriptionProps;
|
|
|
409
408
|
* Includes both unified and old platform-specific formats
|
|
410
409
|
* @deprecated Use RequestPurchaseProps with platform-specific structure instead
|
|
411
410
|
*/
|
|
412
|
-
export type LegacyRequestPurchasePropsAll =
|
|
413
|
-
| UnifiedRequestPurchaseProps
|
|
414
|
-
| LegacyRequestPurchaseProps;
|
|
411
|
+
export type LegacyRequestPurchasePropsAll = UnifiedRequestPurchaseProps | LegacyRequestPurchaseProps;
|
|
415
412
|
|
|
416
413
|
/**
|
|
417
414
|
* Legacy request subscription parameters (deprecated)
|
|
418
415
|
* Includes both unified and old platform-specific formats
|
|
419
416
|
* @deprecated Use RequestSubscriptionProps with platform-specific structure instead
|
|
420
417
|
*/
|
|
421
|
-
export type LegacyRequestSubscriptionPropsAll =
|
|
422
|
-
| UnifiedRequestSubscriptionProps
|
|
423
|
-
| LegacyRequestSubscriptionProps;
|
|
418
|
+
export type LegacyRequestSubscriptionPropsAll = UnifiedRequestSubscriptionProps | LegacyRequestSubscriptionProps;
|
|
424
419
|
|
|
425
420
|
/**
|
|
426
421
|
* All supported request purchase parameters
|
|
427
422
|
* Used internally for backward compatibility
|
|
428
423
|
* @internal
|
|
429
424
|
*/
|
|
430
|
-
export type RequestPurchasePropsWithLegacy =
|
|
431
|
-
| RequestPurchaseProps
|
|
432
|
-
| LegacyRequestPurchasePropsAll;
|
|
425
|
+
export type RequestPurchasePropsWithLegacy = RequestPurchaseProps | LegacyRequestPurchasePropsAll;
|
|
433
426
|
|
|
434
427
|
/**
|
|
435
428
|
* All supported request subscription parameters
|
|
436
429
|
* Used internally for backward compatibility
|
|
437
430
|
* @internal
|
|
438
431
|
*/
|
|
439
|
-
export type RequestSubscriptionPropsWithLegacy =
|
|
440
|
-
| RequestSubscriptionProps
|
|
441
|
-
| LegacyRequestSubscriptionPropsAll;
|
|
432
|
+
export type RequestSubscriptionPropsWithLegacy = RequestSubscriptionProps | LegacyRequestSubscriptionPropsAll;
|
|
442
433
|
|
|
443
434
|
// ============================================================================
|
|
444
435
|
// Type Guards and Utility Functions
|
|
@@ -446,19 +437,19 @@ export type RequestSubscriptionPropsWithLegacy =
|
|
|
446
437
|
|
|
447
438
|
// Type guards to check which API style is being used
|
|
448
439
|
export function isPlatformRequestProps(
|
|
449
|
-
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
440
|
+
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
450
441
|
): props is PlatformRequestPurchaseProps | PlatformRequestSubscriptionProps {
|
|
451
442
|
return 'ios' in props || 'android' in props;
|
|
452
443
|
}
|
|
453
444
|
|
|
454
445
|
export function isUnifiedRequestProps(
|
|
455
|
-
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
446
|
+
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
456
447
|
): props is UnifiedRequestPurchaseProps | UnifiedRequestSubscriptionProps {
|
|
457
448
|
return 'sku' in props || 'skus' in props;
|
|
458
449
|
}
|
|
459
450
|
|
|
460
451
|
export function isLegacyRequestProps(
|
|
461
|
-
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
452
|
+
props: RequestPurchasePropsWithLegacy | RequestSubscriptionPropsWithLegacy
|
|
462
453
|
): props is LegacyRequestPurchaseProps | LegacyRequestSubscriptionProps {
|
|
463
454
|
return 'productId' in props || 'productIds' in props;
|
|
464
455
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,8 +21,12 @@ import {
|
|
|
21
21
|
isPlatformRequestProps,
|
|
22
22
|
isUnifiedRequestProps,
|
|
23
23
|
} from './ExpoIap.types';
|
|
24
|
-
import {
|
|
25
|
-
|
|
24
|
+
import {
|
|
25
|
+
ProductPurchaseAndroid,
|
|
26
|
+
} from './types/ExpoIapAndroid.types';
|
|
27
|
+
import {
|
|
28
|
+
PaymentDiscount,
|
|
29
|
+
} from './types/ExpoIapIos.types';
|
|
26
30
|
|
|
27
31
|
// Export all types
|
|
28
32
|
export * from './ExpoIap.types';
|
|
@@ -76,31 +80,29 @@ export const purchaseErrorListener = (
|
|
|
76
80
|
/**
|
|
77
81
|
* iOS-only listener for App Store promoted product events.
|
|
78
82
|
* This fires when a user taps on a promoted product in the App Store.
|
|
79
|
-
*
|
|
83
|
+
*
|
|
80
84
|
* @param listener - Callback function that receives the promoted product details
|
|
81
85
|
* @returns EventSubscription that can be used to unsubscribe
|
|
82
|
-
*
|
|
86
|
+
*
|
|
83
87
|
* @example
|
|
84
88
|
* ```typescript
|
|
85
89
|
* const subscription = promotedProductListenerIOS((product) => {
|
|
86
90
|
* console.log('Promoted product:', product);
|
|
87
91
|
* // Handle the promoted product
|
|
88
92
|
* });
|
|
89
|
-
*
|
|
93
|
+
*
|
|
90
94
|
* // Later, clean up
|
|
91
95
|
* subscription.remove();
|
|
92
96
|
* ```
|
|
93
|
-
*
|
|
97
|
+
*
|
|
94
98
|
* @platform iOS
|
|
95
99
|
*/
|
|
96
100
|
export const promotedProductListenerIOS = (
|
|
97
101
|
listener: (product: Product) => void,
|
|
98
102
|
) => {
|
|
99
103
|
if (Platform.OS !== 'ios') {
|
|
100
|
-
console.warn(
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
return {remove: () => {}};
|
|
104
|
+
console.warn('promotedProductListenerIOS: This listener is only available on iOS');
|
|
105
|
+
return { remove: () => {} };
|
|
104
106
|
}
|
|
105
107
|
return emitter.addListener(IapEvent.PromotedProductIOS, listener);
|
|
106
108
|
};
|
|
@@ -112,7 +114,7 @@ export function initConnection(): Promise<boolean> {
|
|
|
112
114
|
|
|
113
115
|
export const getProducts = async (skus: string[]): Promise<Product[]> => {
|
|
114
116
|
console.warn(
|
|
115
|
-
|
|
117
|
+
'`getProducts` is deprecated. Use `requestProducts({ skus, type: \'inapp\' })` instead. This function will be removed in version 3.0.0.',
|
|
116
118
|
);
|
|
117
119
|
if (!skus?.length) {
|
|
118
120
|
return Promise.reject(new Error('"skus" is required'));
|
|
@@ -146,7 +148,7 @@ export const getSubscriptions = async (
|
|
|
146
148
|
skus: string[],
|
|
147
149
|
): Promise<SubscriptionProduct[]> => {
|
|
148
150
|
console.warn(
|
|
149
|
-
|
|
151
|
+
'`getSubscriptions` is deprecated. Use `requestProducts({ skus, type: \'subs\' })` instead. This function will be removed in version 3.0.0.',
|
|
150
152
|
);
|
|
151
153
|
if (!skus?.length) {
|
|
152
154
|
return Promise.reject(new Error('"skus" is required'));
|
|
@@ -270,7 +272,7 @@ export const getPurchaseHistory = ({
|
|
|
270
272
|
onlyIncludeActiveItems?: boolean;
|
|
271
273
|
} = {}): Promise<ProductPurchase[]> => {
|
|
272
274
|
console.warn(
|
|
273
|
-
|
|
275
|
+
"`getPurchaseHistory` is deprecated. Use `getPurchaseHistories` instead. This function will be removed in version 3.0.0.",
|
|
274
276
|
);
|
|
275
277
|
return getPurchaseHistories({
|
|
276
278
|
alsoPublishToEventListener,
|
|
@@ -320,8 +322,9 @@ export const getAvailablePurchases = ({
|
|
|
320
322
|
),
|
|
321
323
|
android: async () => {
|
|
322
324
|
const products = await ExpoIapModule.getAvailableItemsByType('inapp');
|
|
323
|
-
const subscriptions =
|
|
324
|
-
|
|
325
|
+
const subscriptions = await ExpoIapModule.getAvailableItemsByType(
|
|
326
|
+
'subs',
|
|
327
|
+
);
|
|
325
328
|
return products.concat(subscriptions);
|
|
326
329
|
},
|
|
327
330
|
}) || (() => Promise.resolve([]))
|
|
@@ -340,6 +343,7 @@ const offerToRecordIos = (
|
|
|
340
343
|
};
|
|
341
344
|
};
|
|
342
345
|
|
|
346
|
+
|
|
343
347
|
// Define discriminated union with explicit type parameter
|
|
344
348
|
// Using legacy types internally for backward compatibility
|
|
345
349
|
type PurchaseRequest =
|
|
@@ -369,8 +373,7 @@ const normalizeRequestProps = (
|
|
|
369
373
|
if (platform === 'ios') {
|
|
370
374
|
return {
|
|
371
375
|
sku: request.sku || (request.skus?.[0] ?? ''),
|
|
372
|
-
andDangerouslyFinishTransactionAutomaticallyIOS:
|
|
373
|
-
request.andDangerouslyFinishTransactionAutomaticallyIOS,
|
|
376
|
+
andDangerouslyFinishTransactionAutomaticallyIOS: request.andDangerouslyFinishTransactionAutomaticallyIOS,
|
|
374
377
|
appAccountToken: request.appAccountToken,
|
|
375
378
|
quantity: request.quantity,
|
|
376
379
|
withOffer: request.withOffer,
|
|
@@ -382,7 +385,7 @@ const normalizeRequestProps = (
|
|
|
382
385
|
obfuscatedProfileIdAndroid: request.obfuscatedProfileIdAndroid,
|
|
383
386
|
isOfferPersonalized: request.isOfferPersonalized,
|
|
384
387
|
};
|
|
385
|
-
|
|
388
|
+
|
|
386
389
|
// Add subscription-specific fields if present
|
|
387
390
|
if ('subscriptionOffers' in request && request.subscriptionOffers) {
|
|
388
391
|
androidRequest.subscriptionOffers = request.subscriptionOffers;
|
|
@@ -393,7 +396,7 @@ const normalizeRequestProps = (
|
|
|
393
396
|
if ('replacementModeAndroid' in request) {
|
|
394
397
|
androidRequest.replacementModeAndroid = request.replacementModeAndroid;
|
|
395
398
|
}
|
|
396
|
-
|
|
399
|
+
|
|
397
400
|
return androidRequest;
|
|
398
401
|
}
|
|
399
402
|
}
|
|
@@ -404,11 +407,11 @@ const normalizeRequestProps = (
|
|
|
404
407
|
|
|
405
408
|
/**
|
|
406
409
|
* Request a purchase for products or subscriptions.
|
|
407
|
-
*
|
|
410
|
+
*
|
|
408
411
|
* @param requestObj - Purchase request configuration
|
|
409
412
|
* @param requestObj.request - Platform-specific purchase parameters
|
|
410
413
|
* @param requestObj.type - Type of purchase: 'inapp' for products (default) or 'subs' for subscriptions
|
|
411
|
-
*
|
|
414
|
+
*
|
|
412
415
|
* @example
|
|
413
416
|
* ```typescript
|
|
414
417
|
* // Product purchase
|
|
@@ -419,12 +422,12 @@ const normalizeRequestProps = (
|
|
|
419
422
|
* },
|
|
420
423
|
* type: 'inapp'
|
|
421
424
|
* });
|
|
422
|
-
*
|
|
425
|
+
*
|
|
423
426
|
* // Subscription purchase
|
|
424
427
|
* await requestPurchase({
|
|
425
428
|
* request: {
|
|
426
429
|
* ios: { sku: subscriptionId },
|
|
427
|
-
* android: {
|
|
430
|
+
* android: {
|
|
428
431
|
* skus: [subscriptionId],
|
|
429
432
|
* subscriptionOffers: [{ sku: subscriptionId, offerToken: 'token' }]
|
|
430
433
|
* }
|
|
@@ -446,7 +449,7 @@ export const requestPurchase = (
|
|
|
446
449
|
|
|
447
450
|
if (Platform.OS === 'ios') {
|
|
448
451
|
const normalizedRequest = normalizeRequestProps(request, 'ios');
|
|
449
|
-
|
|
452
|
+
|
|
450
453
|
if (!normalizedRequest?.sku) {
|
|
451
454
|
throw new Error(
|
|
452
455
|
'Invalid request for iOS. The `sku` property is required and must be a string.',
|
|
@@ -479,7 +482,7 @@ export const requestPurchase = (
|
|
|
479
482
|
|
|
480
483
|
if (Platform.OS === 'android') {
|
|
481
484
|
const normalizedRequest = normalizeRequestProps(request, 'android');
|
|
482
|
-
|
|
485
|
+
|
|
483
486
|
if (!normalizedRequest?.skus?.length) {
|
|
484
487
|
throw new Error(
|
|
485
488
|
'Invalid request for Android. The `skus` property is required and must be a non-empty array.',
|
|
@@ -543,7 +546,7 @@ export const requestPurchase = (
|
|
|
543
546
|
|
|
544
547
|
/**
|
|
545
548
|
* @deprecated Use `requestPurchase({ request, type: 'subs' })` instead. This method will be removed in version 3.0.0.
|
|
546
|
-
*
|
|
549
|
+
*
|
|
547
550
|
* @example
|
|
548
551
|
* ```typescript
|
|
549
552
|
* // Old way (deprecated)
|
|
@@ -552,12 +555,12 @@ export const requestPurchase = (
|
|
|
552
555
|
* // or for Android
|
|
553
556
|
* skus: [subscriptionId],
|
|
554
557
|
* });
|
|
555
|
-
*
|
|
558
|
+
*
|
|
556
559
|
* // New way (recommended)
|
|
557
560
|
* await requestPurchase({
|
|
558
561
|
* request: {
|
|
559
562
|
* ios: { sku: subscriptionId },
|
|
560
|
-
* android: {
|
|
563
|
+
* android: {
|
|
561
564
|
* skus: [subscriptionId],
|
|
562
565
|
* subscriptionOffers: [{ sku: subscriptionId, offerToken: 'token' }]
|
|
563
566
|
* }
|
|
@@ -622,16 +625,16 @@ export const finishTransaction = ({
|
|
|
622
625
|
|
|
623
626
|
/**
|
|
624
627
|
* Retrieves the current storefront information from iOS App Store
|
|
625
|
-
*
|
|
628
|
+
*
|
|
626
629
|
* @returns Promise resolving to the storefront country code
|
|
627
630
|
* @throws Error if called on non-iOS platform
|
|
628
|
-
*
|
|
631
|
+
*
|
|
629
632
|
* @example
|
|
630
633
|
* ```typescript
|
|
631
634
|
* const storefront = await getStorefrontIOS();
|
|
632
635
|
* console.log(storefront); // 'US'
|
|
633
636
|
* ```
|
|
634
|
-
*
|
|
637
|
+
*
|
|
635
638
|
* @platform iOS
|
|
636
639
|
*/
|
|
637
640
|
export const getStorefrontIOS = (): Promise<string> => {
|
package/src/modules/android.ts
CHANGED
|
@@ -102,9 +102,11 @@ export const acknowledgePurchaseAndroid = ({
|
|
|
102
102
|
* Open the Google Play Store to redeem offer codes (Android only).
|
|
103
103
|
* Note: Google Play does not provide a direct API to redeem codes within the app.
|
|
104
104
|
* This function opens the Play Store where users can manually enter their codes.
|
|
105
|
-
*
|
|
105
|
+
*
|
|
106
106
|
* @returns {Promise<void>}
|
|
107
107
|
*/
|
|
108
108
|
export const openRedeemOfferCodeAndroid = async (): Promise<void> => {
|
|
109
|
-
return Linking.openURL(
|
|
109
|
+
return Linking.openURL(
|
|
110
|
+
`https://play.google.com/redeem?code=`
|
|
111
|
+
);
|
|
110
112
|
};
|