expo-iap 3.0.7 → 3.0.8
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/CLAUDE.md +12 -0
- package/android/build.gradle +1 -1
- package/build/index.d.ts +14 -66
- package/build/index.d.ts.map +1 -1
- package/build/index.js +149 -151
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +7 -12
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +13 -11
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +19 -35
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +86 -33
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +99 -76
- package/build/types.d.ts.map +1 -1
- package/build/types.js +1 -0
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +6 -11
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +44 -15
- package/build/useIAP.js.map +1 -1
- package/build/utils/purchase.d.ts +9 -0
- package/build/utils/purchase.d.ts.map +1 -0
- package/build/utils/purchase.js +34 -0
- package/build/utils/purchase.js.map +1 -0
- package/package.json +2 -2
- package/plugin/build/withIAP.js +3 -3
- package/plugin/src/withIAP.ts +3 -3
- package/src/index.ts +217 -250
- package/src/modules/android.ts +23 -22
- package/src/modules/ios.ts +123 -45
- package/src/types.ts +131 -85
- package/src/useIAP.ts +83 -42
- package/src/utils/purchase.ts +52 -0
- package/.copilot-instructions.md +0 -321
- package/.cursorrules +0 -321
package/src/types.ts
CHANGED
|
@@ -126,10 +126,7 @@ export enum ErrorCode {
|
|
|
126
126
|
UserError = 'USER_ERROR'
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
export
|
|
130
|
-
products?: (Product[] | null);
|
|
131
|
-
subscriptions?: (ProductSubscription[] | null);
|
|
132
|
-
}
|
|
129
|
+
export type FetchProductsResult = Product[] | ProductSubscription[] | null;
|
|
133
130
|
|
|
134
131
|
export type IapEvent = 'promoted-product-ios' | 'purchase-error' | 'purchase-updated';
|
|
135
132
|
|
|
@@ -137,57 +134,46 @@ export type IapPlatform = 'android' | 'ios';
|
|
|
137
134
|
|
|
138
135
|
export interface Mutation {
|
|
139
136
|
/** Acknowledge a non-consumable purchase or subscription */
|
|
140
|
-
acknowledgePurchaseAndroid: Promise<
|
|
137
|
+
acknowledgePurchaseAndroid: Promise<boolean>;
|
|
141
138
|
/** Initiate a refund request for a product (iOS 15+) */
|
|
142
|
-
beginRefundRequestIOS
|
|
139
|
+
beginRefundRequestIOS?: Promise<(string | null)>;
|
|
143
140
|
/** Clear pending transactions from the StoreKit payment queue */
|
|
144
|
-
clearTransactionIOS: Promise<
|
|
141
|
+
clearTransactionIOS: Promise<boolean>;
|
|
145
142
|
/** Consume a purchase token so it can be repurchased */
|
|
146
|
-
consumePurchaseAndroid: Promise<
|
|
143
|
+
consumePurchaseAndroid: Promise<boolean>;
|
|
147
144
|
/** Open the native subscription management surface */
|
|
148
|
-
deepLinkToSubscriptions: Promise<
|
|
145
|
+
deepLinkToSubscriptions: Promise<void>;
|
|
149
146
|
/** Close the platform billing connection */
|
|
150
147
|
endConnection: Promise<boolean>;
|
|
151
148
|
/** Finish a transaction after validating receipts */
|
|
152
|
-
finishTransaction: Promise<
|
|
149
|
+
finishTransaction: Promise<void>;
|
|
153
150
|
/** Establish the platform billing connection */
|
|
154
151
|
initConnection: Promise<boolean>;
|
|
155
152
|
/** Present the App Store code redemption sheet */
|
|
156
|
-
presentCodeRedemptionSheetIOS: Promise<
|
|
153
|
+
presentCodeRedemptionSheetIOS: Promise<boolean>;
|
|
157
154
|
/** Initiate a purchase flow; rely on events for final state */
|
|
158
|
-
requestPurchase?: Promise<(
|
|
155
|
+
requestPurchase?: Promise<(Purchase | Purchase[] | null)>;
|
|
159
156
|
/** Purchase the promoted product surfaced by the App Store */
|
|
160
|
-
requestPurchaseOnPromotedProductIOS: Promise<
|
|
157
|
+
requestPurchaseOnPromotedProductIOS: Promise<boolean>;
|
|
161
158
|
/** Restore completed purchases across platforms */
|
|
162
|
-
restorePurchases: Promise<
|
|
159
|
+
restorePurchases: Promise<void>;
|
|
163
160
|
/** Open subscription management UI and return changed purchases (iOS 15+) */
|
|
164
161
|
showManageSubscriptionsIOS: Promise<PurchaseIOS[]>;
|
|
165
162
|
/** Force a StoreKit sync for transactions (iOS 15+) */
|
|
166
|
-
syncIOS: Promise<
|
|
163
|
+
syncIOS: Promise<boolean>;
|
|
167
164
|
/** Validate purchase receipts with the configured providers */
|
|
168
165
|
validateReceipt: Promise<ReceiptValidationResult>;
|
|
169
166
|
}
|
|
170
167
|
|
|
171
168
|
|
|
172
|
-
export interface MutationAcknowledgePurchaseAndroidArgs {
|
|
173
|
-
purchaseToken: string;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
169
|
|
|
177
|
-
export
|
|
178
|
-
sku: string;
|
|
179
|
-
}
|
|
170
|
+
export type MutationAcknowledgePurchaseAndroidArgs = string;
|
|
180
171
|
|
|
172
|
+
export type MutationBeginRefundRequestIosArgs = string;
|
|
181
173
|
|
|
182
|
-
export
|
|
183
|
-
purchaseToken: string;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
export interface MutationDeepLinkToSubscriptionsArgs {
|
|
188
|
-
options?: (DeepLinkOptions | null);
|
|
189
|
-
}
|
|
174
|
+
export type MutationConsumePurchaseAndroidArgs = string;
|
|
190
175
|
|
|
176
|
+
export type MutationDeepLinkToSubscriptionsArgs = (DeepLinkOptions | null) | undefined;
|
|
191
177
|
|
|
192
178
|
export interface MutationFinishTransactionArgs {
|
|
193
179
|
isConsumable?: (boolean | null);
|
|
@@ -195,14 +181,20 @@ export interface MutationFinishTransactionArgs {
|
|
|
195
181
|
}
|
|
196
182
|
|
|
197
183
|
|
|
198
|
-
export
|
|
199
|
-
|
|
200
|
-
|
|
184
|
+
export type MutationRequestPurchaseArgs =
|
|
185
|
+
| {
|
|
186
|
+
/** Per-platform purchase request props */
|
|
187
|
+
request: RequestPurchasePropsByPlatforms;
|
|
188
|
+
type: 'in-app';
|
|
189
|
+
}
|
|
190
|
+
| {
|
|
191
|
+
/** Per-platform subscription request props */
|
|
192
|
+
request: RequestSubscriptionPropsByPlatforms;
|
|
193
|
+
type: 'subs';
|
|
194
|
+
};
|
|
201
195
|
|
|
202
196
|
|
|
203
|
-
export
|
|
204
|
-
options: ReceiptValidationProps;
|
|
205
|
-
}
|
|
197
|
+
export type MutationValidateReceiptArgs = ReceiptValidationProps;
|
|
206
198
|
|
|
207
199
|
export type PaymentModeIOS = 'empty' | 'free-trial' | 'pay-as-you-go' | 'pay-up-front';
|
|
208
200
|
|
|
@@ -440,9 +432,9 @@ export type PurchaseState = 'deferred' | 'failed' | 'pending' | 'purchased' | 'r
|
|
|
440
432
|
|
|
441
433
|
export interface Query {
|
|
442
434
|
/** Get current StoreKit 2 entitlements (iOS 15+) */
|
|
443
|
-
currentEntitlementIOS
|
|
435
|
+
currentEntitlementIOS?: Promise<(PurchaseIOS | null)>;
|
|
444
436
|
/** Retrieve products or subscriptions from the store */
|
|
445
|
-
fetchProducts: Promise<
|
|
437
|
+
fetchProducts: Promise<(Product[] | ProductSubscription[] | null)>;
|
|
446
438
|
/** Get active subscriptions (filters by subscriptionIds when provided) */
|
|
447
439
|
getActiveSubscriptions: Promise<ActiveSubscription[]>;
|
|
448
440
|
/** Fetch the current app transaction (iOS 16+) */
|
|
@@ -454,14 +446,14 @@ export interface Query {
|
|
|
454
446
|
/** Get the currently promoted product (iOS 11+) */
|
|
455
447
|
getPromotedProductIOS?: Promise<(ProductIOS | null)>;
|
|
456
448
|
/** Get base64-encoded receipt data for validation */
|
|
457
|
-
getReceiptDataIOS
|
|
449
|
+
getReceiptDataIOS?: Promise<(string | null)>;
|
|
458
450
|
/** Get the current App Store storefront country code */
|
|
459
451
|
getStorefrontIOS: Promise<string>;
|
|
460
452
|
/** Get the transaction JWS (StoreKit 2) */
|
|
461
|
-
getTransactionJwsIOS
|
|
453
|
+
getTransactionJwsIOS?: Promise<(string | null)>;
|
|
462
454
|
/** Check whether the user has active subscriptions */
|
|
463
455
|
hasActiveSubscriptions: Promise<boolean>;
|
|
464
|
-
/** Check introductory offer eligibility for
|
|
456
|
+
/** Check introductory offer eligibility for a subscription group */
|
|
465
457
|
isEligibleForIntroOfferIOS: Promise<boolean>;
|
|
466
458
|
/** Verify a StoreKit 2 transaction signature */
|
|
467
459
|
isTransactionVerifiedIOS: Promise<boolean>;
|
|
@@ -469,57 +461,33 @@ export interface Query {
|
|
|
469
461
|
latestTransactionIOS?: Promise<(PurchaseIOS | null)>;
|
|
470
462
|
/** Get StoreKit 2 subscription status details (iOS 15+) */
|
|
471
463
|
subscriptionStatusIOS: Promise<SubscriptionStatusIOS[]>;
|
|
464
|
+
/** Validate a receipt for a specific product */
|
|
465
|
+
validateReceiptIOS: Promise<ReceiptValidationResultIOS>;
|
|
472
466
|
}
|
|
473
467
|
|
|
474
468
|
|
|
475
|
-
export interface QueryCurrentEntitlementIosArgs {
|
|
476
|
-
skus?: (string[] | null);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
export interface QueryFetchProductsArgs {
|
|
481
|
-
params: ProductRequest;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
export interface QueryGetActiveSubscriptionsArgs {
|
|
486
|
-
subscriptionIds?: (string[] | null);
|
|
487
|
-
}
|
|
488
469
|
|
|
470
|
+
export type QueryCurrentEntitlementIosArgs = string;
|
|
489
471
|
|
|
490
|
-
export
|
|
491
|
-
options?: (PurchaseOptions | null);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
export interface QueryGetTransactionJwsIosArgs {
|
|
496
|
-
transactionId: string;
|
|
497
|
-
}
|
|
498
|
-
|
|
472
|
+
export type QueryFetchProductsArgs = ProductRequest;
|
|
499
473
|
|
|
500
|
-
export
|
|
501
|
-
subscriptionIds?: (string[] | null);
|
|
502
|
-
}
|
|
474
|
+
export type QueryGetActiveSubscriptionsArgs = (string[] | null) | undefined;
|
|
503
475
|
|
|
476
|
+
export type QueryGetAvailablePurchasesArgs = (PurchaseOptions | null) | undefined;
|
|
504
477
|
|
|
505
|
-
export
|
|
506
|
-
productIds: string[];
|
|
507
|
-
}
|
|
478
|
+
export type QueryGetTransactionJwsIosArgs = string;
|
|
508
479
|
|
|
480
|
+
export type QueryHasActiveSubscriptionsArgs = (string[] | null) | undefined;
|
|
509
481
|
|
|
510
|
-
export
|
|
511
|
-
transactionId: string;
|
|
512
|
-
}
|
|
482
|
+
export type QueryIsEligibleForIntroOfferIosArgs = string;
|
|
513
483
|
|
|
484
|
+
export type QueryIsTransactionVerifiedIosArgs = string;
|
|
514
485
|
|
|
515
|
-
export
|
|
516
|
-
sku: string;
|
|
517
|
-
}
|
|
486
|
+
export type QueryLatestTransactionIosArgs = string;
|
|
518
487
|
|
|
488
|
+
export type QuerySubscriptionStatusIosArgs = string;
|
|
519
489
|
|
|
520
|
-
export
|
|
521
|
-
skus?: (string[] | null);
|
|
522
|
-
}
|
|
490
|
+
export type QueryValidateReceiptIosArgs = ReceiptValidationProps;
|
|
523
491
|
|
|
524
492
|
export interface ReceiptValidationAndroidOptions {
|
|
525
493
|
accessToken: string;
|
|
@@ -623,10 +591,7 @@ export interface RequestPurchasePropsByPlatforms {
|
|
|
623
591
|
ios?: (RequestPurchaseIosProps | null);
|
|
624
592
|
}
|
|
625
593
|
|
|
626
|
-
export
|
|
627
|
-
purchase?: (Purchase | null);
|
|
628
|
-
purchases?: (Purchase[] | null);
|
|
629
|
-
}
|
|
594
|
+
export type RequestPurchaseResult = Purchase | Purchase[] | null;
|
|
630
595
|
|
|
631
596
|
export interface RequestSubscriptionAndroidProps {
|
|
632
597
|
/** Personalized offer flag */
|
|
@@ -669,6 +634,7 @@ export interface Subscription {
|
|
|
669
634
|
purchaseUpdated: Purchase;
|
|
670
635
|
}
|
|
671
636
|
|
|
637
|
+
|
|
672
638
|
export interface SubscriptionInfoIOS {
|
|
673
639
|
introductoryOffer?: (SubscriptionOfferIOS | null);
|
|
674
640
|
promotionalOffers?: (SubscriptionOfferIOS[] | null);
|
|
@@ -700,6 +666,86 @@ export interface SubscriptionStatusIOS {
|
|
|
700
666
|
state: string;
|
|
701
667
|
}
|
|
702
668
|
|
|
703
|
-
export
|
|
704
|
-
|
|
705
|
-
|
|
669
|
+
export type VoidResult = void;
|
|
670
|
+
|
|
671
|
+
// -- Query helper types (auto-generated)
|
|
672
|
+
export type QueryArgsMap = {
|
|
673
|
+
currentEntitlementIOS: QueryCurrentEntitlementIosArgs;
|
|
674
|
+
fetchProducts: QueryFetchProductsArgs;
|
|
675
|
+
getActiveSubscriptions: QueryGetActiveSubscriptionsArgs;
|
|
676
|
+
getAppTransactionIOS: never;
|
|
677
|
+
getAvailablePurchases: QueryGetAvailablePurchasesArgs;
|
|
678
|
+
getPendingTransactionsIOS: never;
|
|
679
|
+
getPromotedProductIOS: never;
|
|
680
|
+
getReceiptDataIOS: never;
|
|
681
|
+
getStorefrontIOS: never;
|
|
682
|
+
getTransactionJwsIOS: QueryGetTransactionJwsIosArgs;
|
|
683
|
+
hasActiveSubscriptions: QueryHasActiveSubscriptionsArgs;
|
|
684
|
+
isEligibleForIntroOfferIOS: QueryIsEligibleForIntroOfferIosArgs;
|
|
685
|
+
isTransactionVerifiedIOS: QueryIsTransactionVerifiedIosArgs;
|
|
686
|
+
latestTransactionIOS: QueryLatestTransactionIosArgs;
|
|
687
|
+
subscriptionStatusIOS: QuerySubscriptionStatusIosArgs;
|
|
688
|
+
validateReceiptIOS: QueryValidateReceiptIosArgs;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
export type QueryField<K extends keyof Query> =
|
|
692
|
+
QueryArgsMap[K] extends never
|
|
693
|
+
? () => NonNullable<Query[K]>
|
|
694
|
+
: undefined extends QueryArgsMap[K]
|
|
695
|
+
? (args?: QueryArgsMap[K]) => NonNullable<Query[K]>
|
|
696
|
+
: (args: QueryArgsMap[K]) => NonNullable<Query[K]>;
|
|
697
|
+
|
|
698
|
+
export type QueryFieldMap = {
|
|
699
|
+
[K in keyof Query]?: QueryField<K>;
|
|
700
|
+
};
|
|
701
|
+
// -- End query helper types
|
|
702
|
+
|
|
703
|
+
// -- Mutation helper types (auto-generated)
|
|
704
|
+
export type MutationArgsMap = {
|
|
705
|
+
acknowledgePurchaseAndroid: MutationAcknowledgePurchaseAndroidArgs;
|
|
706
|
+
beginRefundRequestIOS: MutationBeginRefundRequestIosArgs;
|
|
707
|
+
clearTransactionIOS: never;
|
|
708
|
+
consumePurchaseAndroid: MutationConsumePurchaseAndroidArgs;
|
|
709
|
+
deepLinkToSubscriptions: MutationDeepLinkToSubscriptionsArgs;
|
|
710
|
+
endConnection: never;
|
|
711
|
+
finishTransaction: MutationFinishTransactionArgs;
|
|
712
|
+
initConnection: never;
|
|
713
|
+
presentCodeRedemptionSheetIOS: never;
|
|
714
|
+
requestPurchase: MutationRequestPurchaseArgs;
|
|
715
|
+
requestPurchaseOnPromotedProductIOS: never;
|
|
716
|
+
restorePurchases: never;
|
|
717
|
+
showManageSubscriptionsIOS: never;
|
|
718
|
+
syncIOS: never;
|
|
719
|
+
validateReceipt: MutationValidateReceiptArgs;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
export type MutationField<K extends keyof Mutation> =
|
|
723
|
+
MutationArgsMap[K] extends never
|
|
724
|
+
? () => NonNullable<Mutation[K]>
|
|
725
|
+
: undefined extends MutationArgsMap[K]
|
|
726
|
+
? (args?: MutationArgsMap[K]) => NonNullable<Mutation[K]>
|
|
727
|
+
: (args: MutationArgsMap[K]) => NonNullable<Mutation[K]>;
|
|
728
|
+
|
|
729
|
+
export type MutationFieldMap = {
|
|
730
|
+
[K in keyof Mutation]?: MutationField<K>;
|
|
731
|
+
};
|
|
732
|
+
// -- End mutation helper types
|
|
733
|
+
|
|
734
|
+
// -- Subscription helper types (auto-generated)
|
|
735
|
+
export type SubscriptionArgsMap = {
|
|
736
|
+
promotedProductIOS: never;
|
|
737
|
+
purchaseError: never;
|
|
738
|
+
purchaseUpdated: never;
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
export type SubscriptionField<K extends keyof Subscription> =
|
|
742
|
+
SubscriptionArgsMap[K] extends never
|
|
743
|
+
? () => NonNullable<Subscription[K]>
|
|
744
|
+
: undefined extends SubscriptionArgsMap[K]
|
|
745
|
+
? (args?: SubscriptionArgsMap[K]) => NonNullable<Subscription[K]>
|
|
746
|
+
: (args: SubscriptionArgsMap[K]) => NonNullable<Subscription[K]>;
|
|
747
|
+
|
|
748
|
+
export type SubscriptionFieldMap = {
|
|
749
|
+
[K in keyof Subscription]?: SubscriptionField<K>;
|
|
750
|
+
};
|
|
751
|
+
// -- End subscription helper types
|
package/src/useIAP.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
hasActiveSubscriptions,
|
|
20
20
|
type ActiveSubscription,
|
|
21
21
|
type ProductTypeInput,
|
|
22
|
-
type PurchaseRequestInput,
|
|
23
22
|
restorePurchases,
|
|
24
23
|
} from './index';
|
|
25
24
|
import {
|
|
@@ -28,14 +27,18 @@ import {
|
|
|
28
27
|
} from './modules/ios';
|
|
29
28
|
|
|
30
29
|
// Types
|
|
31
|
-
import {
|
|
30
|
+
import type {
|
|
32
31
|
Product,
|
|
33
|
-
Purchase,
|
|
34
32
|
ProductSubscription,
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
ProductQueryType,
|
|
34
|
+
ProductRequest,
|
|
35
|
+
Purchase,
|
|
36
|
+
MutationRequestPurchaseArgs,
|
|
37
|
+
PurchaseInput,
|
|
38
|
+
ReceiptValidationProps,
|
|
37
39
|
ReceiptValidationResult,
|
|
38
40
|
} from './types';
|
|
41
|
+
import {ErrorCode} from './types';
|
|
39
42
|
import {PurchaseError} from './purchase-error';
|
|
40
43
|
import {
|
|
41
44
|
getUserFriendlyErrorMessage,
|
|
@@ -58,7 +61,7 @@ type UseIap = {
|
|
|
58
61
|
}: {
|
|
59
62
|
purchase: Purchase;
|
|
60
63
|
isConsumable?: boolean;
|
|
61
|
-
}) => Promise<
|
|
64
|
+
}) => Promise<void>;
|
|
62
65
|
getAvailablePurchases: () => Promise<void>;
|
|
63
66
|
fetchProducts: (params: {
|
|
64
67
|
skus: string[];
|
|
@@ -66,20 +69,14 @@ type UseIap = {
|
|
|
66
69
|
}) => Promise<void>;
|
|
67
70
|
|
|
68
71
|
requestPurchase: (
|
|
69
|
-
params:
|
|
72
|
+
params: MutationRequestPurchaseArgs,
|
|
70
73
|
) => ReturnType<typeof requestPurchaseInternal>;
|
|
71
74
|
validateReceipt: (
|
|
72
|
-
|
|
73
|
-
androidOptions?: {
|
|
74
|
-
packageName: string;
|
|
75
|
-
productToken: string;
|
|
76
|
-
accessToken: string;
|
|
77
|
-
isSub?: boolean;
|
|
78
|
-
},
|
|
75
|
+
props: ReceiptValidationProps,
|
|
79
76
|
) => Promise<ReceiptValidationResult>;
|
|
80
77
|
restorePurchases: () => Promise<void>;
|
|
81
78
|
getPromotedProductIOS: () => Promise<Product | null>;
|
|
82
|
-
requestPurchaseOnPromotedProductIOS: () => Promise<
|
|
79
|
+
requestPurchaseOnPromotedProductIOS: () => Promise<boolean>;
|
|
83
80
|
getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;
|
|
84
81
|
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
85
82
|
};
|
|
@@ -154,14 +151,40 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
154
151
|
subscriptionsRefState.current = subscriptions;
|
|
155
152
|
}, [subscriptions]);
|
|
156
153
|
|
|
154
|
+
const normalizeProductQueryType = useCallback(
|
|
155
|
+
(type?: ProductTypeInput): ProductQueryType => {
|
|
156
|
+
if (!type || type === 'inapp' || type === 'in-app') {
|
|
157
|
+
return 'in-app';
|
|
158
|
+
}
|
|
159
|
+
return type;
|
|
160
|
+
},
|
|
161
|
+
[],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const toPurchaseInput = useCallback(
|
|
165
|
+
(purchase: Purchase): PurchaseInput => ({
|
|
166
|
+
id: purchase.id,
|
|
167
|
+
ids: purchase.ids ?? undefined,
|
|
168
|
+
isAutoRenewing: purchase.isAutoRenewing,
|
|
169
|
+
platform: purchase.platform,
|
|
170
|
+
productId: purchase.productId,
|
|
171
|
+
purchaseState: purchase.purchaseState,
|
|
172
|
+
purchaseToken: purchase.purchaseToken ?? null,
|
|
173
|
+
quantity: purchase.quantity,
|
|
174
|
+
transactionDate: purchase.transactionDate,
|
|
175
|
+
}),
|
|
176
|
+
[],
|
|
177
|
+
);
|
|
178
|
+
|
|
157
179
|
const getSubscriptionsInternal = useCallback(
|
|
158
180
|
async (skus: string[]): Promise<void> => {
|
|
159
181
|
try {
|
|
160
182
|
const result = await fetchProducts({skus, type: 'subs'});
|
|
183
|
+
const subscriptionsResult = (result ?? []) as ProductSubscription[];
|
|
161
184
|
setSubscriptions((prevSubscriptions) =>
|
|
162
185
|
mergeWithDuplicateCheck(
|
|
163
186
|
prevSubscriptions,
|
|
164
|
-
|
|
187
|
+
subscriptionsResult,
|
|
165
188
|
(subscription) => subscription.id,
|
|
166
189
|
),
|
|
167
190
|
);
|
|
@@ -178,30 +201,58 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
178
201
|
type?: ProductTypeInput;
|
|
179
202
|
}): Promise<void> => {
|
|
180
203
|
try {
|
|
181
|
-
const
|
|
204
|
+
const queryType = normalizeProductQueryType(params.type);
|
|
205
|
+
const request: ProductRequest = {skus: params.skus, type: queryType};
|
|
206
|
+
const result = await fetchProducts(request);
|
|
207
|
+
const items = (result ?? []) as (Product | ProductSubscription)[];
|
|
182
208
|
|
|
183
|
-
if (
|
|
209
|
+
if (queryType === 'subs') {
|
|
210
|
+
const subscriptionsResult = items as ProductSubscription[];
|
|
184
211
|
setSubscriptions((prevSubscriptions) =>
|
|
185
212
|
mergeWithDuplicateCheck(
|
|
186
213
|
prevSubscriptions,
|
|
187
|
-
|
|
214
|
+
subscriptionsResult,
|
|
188
215
|
(subscription) => subscription.id,
|
|
189
216
|
),
|
|
190
217
|
);
|
|
218
|
+
} else if (queryType === 'in-app') {
|
|
219
|
+
const productsResult = items as Product[];
|
|
220
|
+
setProducts((prevProducts) =>
|
|
221
|
+
mergeWithDuplicateCheck(
|
|
222
|
+
prevProducts,
|
|
223
|
+
productsResult,
|
|
224
|
+
(product) => product.id,
|
|
225
|
+
),
|
|
226
|
+
);
|
|
191
227
|
} else {
|
|
228
|
+
const productItems = items.filter(
|
|
229
|
+
(item) => item.type === 'in-app',
|
|
230
|
+
) as Product[];
|
|
231
|
+
const subscriptionItems = items.filter(
|
|
232
|
+
(item) => item.type === 'subs',
|
|
233
|
+
) as ProductSubscription[];
|
|
234
|
+
|
|
192
235
|
setProducts((prevProducts) =>
|
|
193
236
|
mergeWithDuplicateCheck(
|
|
194
237
|
prevProducts,
|
|
195
|
-
|
|
238
|
+
productItems,
|
|
196
239
|
(product) => product.id,
|
|
197
240
|
),
|
|
198
241
|
);
|
|
242
|
+
|
|
243
|
+
setSubscriptions((prevSubscriptions) =>
|
|
244
|
+
mergeWithDuplicateCheck(
|
|
245
|
+
prevSubscriptions,
|
|
246
|
+
subscriptionItems,
|
|
247
|
+
(subscription) => subscription.id,
|
|
248
|
+
),
|
|
249
|
+
);
|
|
199
250
|
}
|
|
200
251
|
} catch (error) {
|
|
201
252
|
console.error('Error fetching products:', error);
|
|
202
253
|
}
|
|
203
254
|
},
|
|
204
|
-
[mergeWithDuplicateCheck],
|
|
255
|
+
[mergeWithDuplicateCheck, normalizeProductQueryType],
|
|
205
256
|
);
|
|
206
257
|
|
|
207
258
|
const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
@@ -242,23 +293,23 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
242
293
|
);
|
|
243
294
|
|
|
244
295
|
const finishTransaction = useCallback(
|
|
245
|
-
({
|
|
296
|
+
async ({
|
|
246
297
|
purchase,
|
|
247
298
|
isConsumable,
|
|
248
299
|
}: {
|
|
249
300
|
purchase: Purchase;
|
|
250
301
|
isConsumable?: boolean;
|
|
251
|
-
}): Promise<
|
|
252
|
-
|
|
253
|
-
purchase,
|
|
302
|
+
}): Promise<void> => {
|
|
303
|
+
await finishTransactionInternal({
|
|
304
|
+
purchase: toPurchaseInput(purchase),
|
|
254
305
|
isConsumable,
|
|
255
306
|
});
|
|
256
307
|
},
|
|
257
|
-
[],
|
|
308
|
+
[toPurchaseInput],
|
|
258
309
|
);
|
|
259
310
|
|
|
260
311
|
const requestPurchaseWithReset = useCallback(
|
|
261
|
-
(requestObj:
|
|
312
|
+
(requestObj: MutationRequestPurchaseArgs) => {
|
|
262
313
|
return requestPurchaseInternal(requestObj);
|
|
263
314
|
},
|
|
264
315
|
[],
|
|
@@ -283,7 +334,8 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
283
334
|
// Android: fetch available purchases directly.
|
|
284
335
|
const restorePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
285
336
|
try {
|
|
286
|
-
|
|
337
|
+
await restorePurchases();
|
|
338
|
+
const purchases = await getAvailablePurchases({
|
|
287
339
|
alsoPublishToEventListenerIOS: false,
|
|
288
340
|
onlyIncludeActiveItemsIOS: true,
|
|
289
341
|
});
|
|
@@ -293,20 +345,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
293
345
|
}
|
|
294
346
|
}, []);
|
|
295
347
|
|
|
296
|
-
const validateReceipt = useCallback(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
androidOptions?: {
|
|
300
|
-
packageName: string;
|
|
301
|
-
productToken: string;
|
|
302
|
-
accessToken: string;
|
|
303
|
-
isSub?: boolean;
|
|
304
|
-
},
|
|
305
|
-
) => {
|
|
306
|
-
return validateReceiptInternal(sku, androidOptions);
|
|
307
|
-
},
|
|
308
|
-
[],
|
|
309
|
-
);
|
|
348
|
+
const validateReceipt = useCallback(async (props: ReceiptValidationProps) => {
|
|
349
|
+
return validateReceiptInternal(props);
|
|
350
|
+
}, []);
|
|
310
351
|
|
|
311
352
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
312
353
|
// CRITICAL: Register listeners BEFORE initConnection to avoid race condition
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {Purchase} from '../types';
|
|
2
|
+
|
|
3
|
+
const isPurchaseObject = (value: unknown): value is Record<string, unknown> =>
|
|
4
|
+
Boolean(value) && typeof value === 'object';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes purchase identifiers so `id` reflects the transaction identifier (if present).
|
|
8
|
+
* Falls back to the existing `id` when no transaction identifier is available.
|
|
9
|
+
*/
|
|
10
|
+
export const normalizePurchaseId = <T extends Purchase | null | undefined>(
|
|
11
|
+
purchase: T,
|
|
12
|
+
): T => {
|
|
13
|
+
if (!isPurchaseObject(purchase)) {
|
|
14
|
+
return purchase;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const transactionId = (purchase as Record<string, unknown>).transactionId;
|
|
18
|
+
if (typeof transactionId !== 'string' || transactionId.length === 0) {
|
|
19
|
+
return purchase;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (purchase.id === transactionId) {
|
|
23
|
+
return purchase;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {...purchase, id: transactionId} as T;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const normalizePurchaseList = <T extends Purchase>(
|
|
30
|
+
purchases: T[] | null | undefined,
|
|
31
|
+
): T[] => {
|
|
32
|
+
if (!Array.isArray(purchases)) {
|
|
33
|
+
return purchases ?? [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (purchases.length === 0) {
|
|
37
|
+
return purchases;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return purchases.map((purchase) => normalizePurchaseId(purchase)) as T[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const normalizePurchasePayload = <T extends Purchase | null | undefined>(
|
|
44
|
+
payload: T | T[] | null | undefined,
|
|
45
|
+
): typeof payload => {
|
|
46
|
+
if (Array.isArray(payload)) {
|
|
47
|
+
return payload.map((purchase) =>
|
|
48
|
+
normalizePurchaseId(purchase),
|
|
49
|
+
) as typeof payload;
|
|
50
|
+
}
|
|
51
|
+
return normalizePurchaseId(payload as T) as typeof payload;
|
|
52
|
+
};
|