expo-iap 3.0.7 → 3.1.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/CLAUDE.md +14 -2
- package/CONTRIBUTING.md +19 -0
- package/README.md +18 -6
- package/android/build.gradle +24 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
- package/build/index.d.ts +32 -111
- package/build/index.d.ts.map +1 -1
- package/build/index.js +198 -243
- 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 +15 -12
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +35 -36
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +101 -35
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +107 -82
- 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 +7 -12
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +49 -23
- package/build/useIAP.js.map +1 -1
- package/build/utils/errorMapping.d.ts +32 -23
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +117 -22
- package/build/utils/errorMapping.js.map +1 -1
- package/ios/ExpoIap.podspec +3 -2
- package/ios/ExpoIapHelper.swift +96 -0
- package/ios/ExpoIapLog.swift +127 -0
- package/ios/ExpoIapModule.swift +218 -340
- package/openiap-versions.json +5 -0
- package/package.json +2 -2
- package/plugin/build/withIAP.js +6 -4
- package/plugin/src/withIAP.ts +14 -4
- package/scripts/update-types.mjs +20 -1
- package/src/index.ts +280 -356
- package/src/modules/android.ts +25 -23
- package/src/modules/ios.ts +138 -48
- package/src/types.ts +139 -91
- package/src/useIAP.ts +91 -58
- package/src/utils/errorMapping.ts +203 -23
- package/.copilot-instructions.md +0 -321
- package/.cursorrules +0 -321
- package/build/purchase-error.d.ts +0 -67
- package/build/purchase-error.d.ts.map +0 -1
- package/build/purchase-error.js +0 -166
- package/build/purchase-error.js.map +0 -1
- package/src/purchase-error.ts +0 -265
package/src/types.ts
CHANGED
|
@@ -126,68 +126,54 @@ 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
|
-
export type IapEvent = '
|
|
131
|
+
export type IapEvent = 'purchase-updated' | 'purchase-error' | 'promoted-product-ios';
|
|
135
132
|
|
|
136
|
-
export type IapPlatform = '
|
|
133
|
+
export type IapPlatform = 'ios' | 'android';
|
|
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
|
-
|
|
177
|
-
export interface MutationBeginRefundRequestIosArgs {
|
|
178
|
-
sku: string;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
169
|
|
|
182
|
-
export
|
|
183
|
-
purchaseToken: string;
|
|
184
|
-
}
|
|
170
|
+
export type MutationAcknowledgePurchaseAndroidArgs = string;
|
|
185
171
|
|
|
172
|
+
export type MutationBeginRefundRequestIosArgs = string;
|
|
186
173
|
|
|
187
|
-
export
|
|
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
|
|
|
@@ -274,7 +266,7 @@ export interface ProductIOS extends ProductCommon {
|
|
|
274
266
|
typeIOS: ProductTypeIOS;
|
|
275
267
|
}
|
|
276
268
|
|
|
277
|
-
export type ProductQueryType = '
|
|
269
|
+
export type ProductQueryType = 'in-app' | 'subs' | 'all';
|
|
278
270
|
|
|
279
271
|
export interface ProductRequest {
|
|
280
272
|
skus: string[];
|
|
@@ -335,7 +327,7 @@ export interface ProductSubscriptionIOS extends ProductCommon {
|
|
|
335
327
|
|
|
336
328
|
export type ProductType = 'in-app' | 'subs';
|
|
337
329
|
|
|
338
|
-
export type ProductTypeIOS = '
|
|
330
|
+
export type ProductTypeIOS = 'consumable' | 'non-consumable' | 'auto-renewable-subscription' | 'non-renewing-subscription';
|
|
339
331
|
|
|
340
332
|
export type Purchase = PurchaseAndroid | PurchaseIOS;
|
|
341
333
|
|
|
@@ -357,6 +349,7 @@ export interface PurchaseAndroid extends PurchaseCommon {
|
|
|
357
349
|
quantity: number;
|
|
358
350
|
signatureAndroid?: (string | null);
|
|
359
351
|
transactionDate: number;
|
|
352
|
+
transactionId?: (string | null);
|
|
360
353
|
}
|
|
361
354
|
|
|
362
355
|
export interface PurchaseCommon {
|
|
@@ -407,6 +400,7 @@ export interface PurchaseIOS extends PurchaseCommon {
|
|
|
407
400
|
storefrontCountryCodeIOS?: (string | null);
|
|
408
401
|
subscriptionGroupIdIOS?: (string | null);
|
|
409
402
|
transactionDate: number;
|
|
403
|
+
transactionId: string;
|
|
410
404
|
transactionReasonIOS?: (string | null);
|
|
411
405
|
webOrderLineItemIdIOS?: (string | null);
|
|
412
406
|
}
|
|
@@ -436,13 +430,13 @@ export interface PurchaseOptions {
|
|
|
436
430
|
onlyIncludeActiveItemsIOS?: (boolean | null);
|
|
437
431
|
}
|
|
438
432
|
|
|
439
|
-
export type PurchaseState = '
|
|
433
|
+
export type PurchaseState = 'pending' | 'purchased' | 'failed' | 'restored' | 'deferred' | 'unknown';
|
|
440
434
|
|
|
441
435
|
export interface Query {
|
|
442
436
|
/** Get current StoreKit 2 entitlements (iOS 15+) */
|
|
443
|
-
currentEntitlementIOS
|
|
437
|
+
currentEntitlementIOS?: Promise<(PurchaseIOS | null)>;
|
|
444
438
|
/** Retrieve products or subscriptions from the store */
|
|
445
|
-
fetchProducts: Promise<
|
|
439
|
+
fetchProducts: Promise<(Product[] | ProductSubscription[] | null)>;
|
|
446
440
|
/** Get active subscriptions (filters by subscriptionIds when provided) */
|
|
447
441
|
getActiveSubscriptions: Promise<ActiveSubscription[]>;
|
|
448
442
|
/** Fetch the current app transaction (iOS 16+) */
|
|
@@ -454,14 +448,14 @@ export interface Query {
|
|
|
454
448
|
/** Get the currently promoted product (iOS 11+) */
|
|
455
449
|
getPromotedProductIOS?: Promise<(ProductIOS | null)>;
|
|
456
450
|
/** Get base64-encoded receipt data for validation */
|
|
457
|
-
getReceiptDataIOS
|
|
451
|
+
getReceiptDataIOS?: Promise<(string | null)>;
|
|
458
452
|
/** Get the current App Store storefront country code */
|
|
459
453
|
getStorefrontIOS: Promise<string>;
|
|
460
454
|
/** Get the transaction JWS (StoreKit 2) */
|
|
461
|
-
getTransactionJwsIOS
|
|
455
|
+
getTransactionJwsIOS?: Promise<(string | null)>;
|
|
462
456
|
/** Check whether the user has active subscriptions */
|
|
463
457
|
hasActiveSubscriptions: Promise<boolean>;
|
|
464
|
-
/** Check introductory offer eligibility for
|
|
458
|
+
/** Check introductory offer eligibility for a subscription group */
|
|
465
459
|
isEligibleForIntroOfferIOS: Promise<boolean>;
|
|
466
460
|
/** Verify a StoreKit 2 transaction signature */
|
|
467
461
|
isTransactionVerifiedIOS: Promise<boolean>;
|
|
@@ -469,57 +463,33 @@ export interface Query {
|
|
|
469
463
|
latestTransactionIOS?: Promise<(PurchaseIOS | null)>;
|
|
470
464
|
/** Get StoreKit 2 subscription status details (iOS 15+) */
|
|
471
465
|
subscriptionStatusIOS: Promise<SubscriptionStatusIOS[]>;
|
|
466
|
+
/** Validate a receipt for a specific product */
|
|
467
|
+
validateReceiptIOS: Promise<ReceiptValidationResultIOS>;
|
|
472
468
|
}
|
|
473
469
|
|
|
474
470
|
|
|
475
|
-
export interface QueryCurrentEntitlementIosArgs {
|
|
476
|
-
skus?: (string[] | null);
|
|
477
|
-
}
|
|
478
471
|
|
|
472
|
+
export type QueryCurrentEntitlementIosArgs = string;
|
|
479
473
|
|
|
480
|
-
export
|
|
481
|
-
params: ProductRequest;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
export interface QueryGetActiveSubscriptionsArgs {
|
|
486
|
-
subscriptionIds?: (string[] | null);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
export interface QueryGetAvailablePurchasesArgs {
|
|
491
|
-
options?: (PurchaseOptions | null);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
export interface QueryGetTransactionJwsIosArgs {
|
|
496
|
-
transactionId: string;
|
|
497
|
-
}
|
|
474
|
+
export type QueryFetchProductsArgs = ProductRequest;
|
|
498
475
|
|
|
476
|
+
export type QueryGetActiveSubscriptionsArgs = (string[] | null) | undefined;
|
|
499
477
|
|
|
500
|
-
export
|
|
501
|
-
subscriptionIds?: (string[] | null);
|
|
502
|
-
}
|
|
478
|
+
export type QueryGetAvailablePurchasesArgs = (PurchaseOptions | null) | undefined;
|
|
503
479
|
|
|
480
|
+
export type QueryGetTransactionJwsIosArgs = string;
|
|
504
481
|
|
|
505
|
-
export
|
|
506
|
-
productIds: string[];
|
|
507
|
-
}
|
|
482
|
+
export type QueryHasActiveSubscriptionsArgs = (string[] | null) | undefined;
|
|
508
483
|
|
|
484
|
+
export type QueryIsEligibleForIntroOfferIosArgs = string;
|
|
509
485
|
|
|
510
|
-
export
|
|
511
|
-
transactionId: string;
|
|
512
|
-
}
|
|
486
|
+
export type QueryIsTransactionVerifiedIosArgs = string;
|
|
513
487
|
|
|
488
|
+
export type QueryLatestTransactionIosArgs = string;
|
|
514
489
|
|
|
515
|
-
export
|
|
516
|
-
sku: string;
|
|
517
|
-
}
|
|
490
|
+
export type QuerySubscriptionStatusIosArgs = string;
|
|
518
491
|
|
|
519
|
-
|
|
520
|
-
export interface QuerySubscriptionStatusIosArgs {
|
|
521
|
-
skus?: (string[] | null);
|
|
522
|
-
}
|
|
492
|
+
export type QueryValidateReceiptIosArgs = ReceiptValidationProps;
|
|
523
493
|
|
|
524
494
|
export interface ReceiptValidationAndroidOptions {
|
|
525
495
|
accessToken: string;
|
|
@@ -623,10 +593,7 @@ export interface RequestPurchasePropsByPlatforms {
|
|
|
623
593
|
ios?: (RequestPurchaseIosProps | null);
|
|
624
594
|
}
|
|
625
595
|
|
|
626
|
-
export
|
|
627
|
-
purchase?: (Purchase | null);
|
|
628
|
-
purchases?: (Purchase[] | null);
|
|
629
|
-
}
|
|
596
|
+
export type RequestPurchaseResult = Purchase | Purchase[] | null;
|
|
630
597
|
|
|
631
598
|
export interface RequestSubscriptionAndroidProps {
|
|
632
599
|
/** Personalized offer flag */
|
|
@@ -669,6 +636,7 @@ export interface Subscription {
|
|
|
669
636
|
purchaseUpdated: Purchase;
|
|
670
637
|
}
|
|
671
638
|
|
|
639
|
+
|
|
672
640
|
export interface SubscriptionInfoIOS {
|
|
673
641
|
introductoryOffer?: (SubscriptionOfferIOS | null);
|
|
674
642
|
promotionalOffers?: (SubscriptionOfferIOS[] | null);
|
|
@@ -688,7 +656,7 @@ export interface SubscriptionOfferIOS {
|
|
|
688
656
|
|
|
689
657
|
export type SubscriptionOfferTypeIOS = 'introductory' | 'promotional';
|
|
690
658
|
|
|
691
|
-
export type SubscriptionPeriodIOS = 'day' | '
|
|
659
|
+
export type SubscriptionPeriodIOS = 'day' | 'week' | 'month' | 'year' | 'empty';
|
|
692
660
|
|
|
693
661
|
export interface SubscriptionPeriodValueIOS {
|
|
694
662
|
unit: SubscriptionPeriodIOS;
|
|
@@ -700,6 +668,86 @@ export interface SubscriptionStatusIOS {
|
|
|
700
668
|
state: string;
|
|
701
669
|
}
|
|
702
670
|
|
|
703
|
-
export
|
|
704
|
-
|
|
705
|
-
|
|
671
|
+
export type VoidResult = void;
|
|
672
|
+
|
|
673
|
+
// -- Query helper types (auto-generated)
|
|
674
|
+
export type QueryArgsMap = {
|
|
675
|
+
currentEntitlementIOS: QueryCurrentEntitlementIosArgs;
|
|
676
|
+
fetchProducts: QueryFetchProductsArgs;
|
|
677
|
+
getActiveSubscriptions: QueryGetActiveSubscriptionsArgs;
|
|
678
|
+
getAppTransactionIOS: never;
|
|
679
|
+
getAvailablePurchases: QueryGetAvailablePurchasesArgs;
|
|
680
|
+
getPendingTransactionsIOS: never;
|
|
681
|
+
getPromotedProductIOS: never;
|
|
682
|
+
getReceiptDataIOS: never;
|
|
683
|
+
getStorefrontIOS: never;
|
|
684
|
+
getTransactionJwsIOS: QueryGetTransactionJwsIosArgs;
|
|
685
|
+
hasActiveSubscriptions: QueryHasActiveSubscriptionsArgs;
|
|
686
|
+
isEligibleForIntroOfferIOS: QueryIsEligibleForIntroOfferIosArgs;
|
|
687
|
+
isTransactionVerifiedIOS: QueryIsTransactionVerifiedIosArgs;
|
|
688
|
+
latestTransactionIOS: QueryLatestTransactionIosArgs;
|
|
689
|
+
subscriptionStatusIOS: QuerySubscriptionStatusIosArgs;
|
|
690
|
+
validateReceiptIOS: QueryValidateReceiptIosArgs;
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
export type QueryField<K extends keyof Query> =
|
|
694
|
+
QueryArgsMap[K] extends never
|
|
695
|
+
? () => NonNullable<Query[K]>
|
|
696
|
+
: undefined extends QueryArgsMap[K]
|
|
697
|
+
? (args?: QueryArgsMap[K]) => NonNullable<Query[K]>
|
|
698
|
+
: (args: QueryArgsMap[K]) => NonNullable<Query[K]>;
|
|
699
|
+
|
|
700
|
+
export type QueryFieldMap = {
|
|
701
|
+
[K in keyof Query]?: QueryField<K>;
|
|
702
|
+
};
|
|
703
|
+
// -- End query helper types
|
|
704
|
+
|
|
705
|
+
// -- Mutation helper types (auto-generated)
|
|
706
|
+
export type MutationArgsMap = {
|
|
707
|
+
acknowledgePurchaseAndroid: MutationAcknowledgePurchaseAndroidArgs;
|
|
708
|
+
beginRefundRequestIOS: MutationBeginRefundRequestIosArgs;
|
|
709
|
+
clearTransactionIOS: never;
|
|
710
|
+
consumePurchaseAndroid: MutationConsumePurchaseAndroidArgs;
|
|
711
|
+
deepLinkToSubscriptions: MutationDeepLinkToSubscriptionsArgs;
|
|
712
|
+
endConnection: never;
|
|
713
|
+
finishTransaction: MutationFinishTransactionArgs;
|
|
714
|
+
initConnection: never;
|
|
715
|
+
presentCodeRedemptionSheetIOS: never;
|
|
716
|
+
requestPurchase: MutationRequestPurchaseArgs;
|
|
717
|
+
requestPurchaseOnPromotedProductIOS: never;
|
|
718
|
+
restorePurchases: never;
|
|
719
|
+
showManageSubscriptionsIOS: never;
|
|
720
|
+
syncIOS: never;
|
|
721
|
+
validateReceipt: MutationValidateReceiptArgs;
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
export type MutationField<K extends keyof Mutation> =
|
|
725
|
+
MutationArgsMap[K] extends never
|
|
726
|
+
? () => NonNullable<Mutation[K]>
|
|
727
|
+
: undefined extends MutationArgsMap[K]
|
|
728
|
+
? (args?: MutationArgsMap[K]) => NonNullable<Mutation[K]>
|
|
729
|
+
: (args: MutationArgsMap[K]) => NonNullable<Mutation[K]>;
|
|
730
|
+
|
|
731
|
+
export type MutationFieldMap = {
|
|
732
|
+
[K in keyof Mutation]?: MutationField<K>;
|
|
733
|
+
};
|
|
734
|
+
// -- End mutation helper types
|
|
735
|
+
|
|
736
|
+
// -- Subscription helper types (auto-generated)
|
|
737
|
+
export type SubscriptionArgsMap = {
|
|
738
|
+
promotedProductIOS: never;
|
|
739
|
+
purchaseError: never;
|
|
740
|
+
purchaseUpdated: never;
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
export type SubscriptionField<K extends keyof Subscription> =
|
|
744
|
+
SubscriptionArgsMap[K] extends never
|
|
745
|
+
? () => NonNullable<Subscription[K]>
|
|
746
|
+
: undefined extends SubscriptionArgsMap[K]
|
|
747
|
+
? (args?: SubscriptionArgsMap[K]) => NonNullable<Subscription[K]>
|
|
748
|
+
: (args: SubscriptionArgsMap[K]) => NonNullable<Subscription[K]>;
|
|
749
|
+
|
|
750
|
+
export type SubscriptionFieldMap = {
|
|
751
|
+
[K in keyof Subscription]?: SubscriptionField<K>;
|
|
752
|
+
};
|
|
753
|
+
// -- 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,15 +27,19 @@ 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';
|
|
39
|
-
import {
|
|
41
|
+
import {ErrorCode} from './types';
|
|
42
|
+
import type {PurchaseError} from './utils/errorMapping';
|
|
40
43
|
import {
|
|
41
44
|
getUserFriendlyErrorMessage,
|
|
42
45
|
isUserCancelledError,
|
|
@@ -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,22 +151,40 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
154
151
|
subscriptionsRefState.current = subscriptions;
|
|
155
152
|
}, [subscriptions]);
|
|
156
153
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
setSubscriptions((prevSubscriptions) =>
|
|
162
|
-
mergeWithDuplicateCheck(
|
|
163
|
-
prevSubscriptions,
|
|
164
|
-
result as ProductSubscription[],
|
|
165
|
-
(subscription) => subscription.id,
|
|
166
|
-
),
|
|
167
|
-
);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error('Error fetching subscriptions:', error);
|
|
154
|
+
const normalizeProductQueryType = useCallback(
|
|
155
|
+
(type?: ProductTypeInput): ProductQueryType => {
|
|
156
|
+
if (!type || type === 'inapp' || type === 'in-app') {
|
|
157
|
+
return 'in-app';
|
|
170
158
|
}
|
|
159
|
+
return type;
|
|
171
160
|
},
|
|
172
|
-
[
|
|
161
|
+
[],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const canonicalProductType = useCallback(
|
|
165
|
+
(value?: string): ProductQueryType => {
|
|
166
|
+
if (!value) {
|
|
167
|
+
return 'in-app';
|
|
168
|
+
}
|
|
169
|
+
const normalized = value.trim().toLowerCase().replace(/[_-]/g, '');
|
|
170
|
+
return normalized === 'subs' ? 'subs' : 'in-app';
|
|
171
|
+
},
|
|
172
|
+
[],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const toPurchaseInput = useCallback(
|
|
176
|
+
(purchase: Purchase): PurchaseInput => ({
|
|
177
|
+
id: purchase.id,
|
|
178
|
+
ids: purchase.ids ?? undefined,
|
|
179
|
+
isAutoRenewing: purchase.isAutoRenewing,
|
|
180
|
+
platform: purchase.platform,
|
|
181
|
+
productId: purchase.productId,
|
|
182
|
+
purchaseState: purchase.purchaseState,
|
|
183
|
+
purchaseToken: purchase.purchaseToken ?? null,
|
|
184
|
+
quantity: purchase.quantity,
|
|
185
|
+
transactionDate: purchase.transactionDate,
|
|
186
|
+
}),
|
|
187
|
+
[],
|
|
173
188
|
);
|
|
174
189
|
|
|
175
190
|
const fetchProductsInternal = useCallback(
|
|
@@ -178,30 +193,58 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
178
193
|
type?: ProductTypeInput;
|
|
179
194
|
}): Promise<void> => {
|
|
180
195
|
try {
|
|
181
|
-
const
|
|
196
|
+
const queryType = normalizeProductQueryType(params.type);
|
|
197
|
+
const request: ProductRequest = {skus: params.skus, type: queryType};
|
|
198
|
+
const result = await fetchProducts(request);
|
|
199
|
+
const items = (result ?? []) as (Product | ProductSubscription)[];
|
|
182
200
|
|
|
183
|
-
if (
|
|
201
|
+
if (queryType === 'subs') {
|
|
202
|
+
const subscriptionsResult = items as ProductSubscription[];
|
|
184
203
|
setSubscriptions((prevSubscriptions) =>
|
|
185
204
|
mergeWithDuplicateCheck(
|
|
186
205
|
prevSubscriptions,
|
|
187
|
-
|
|
206
|
+
subscriptionsResult,
|
|
188
207
|
(subscription) => subscription.id,
|
|
189
208
|
),
|
|
190
209
|
);
|
|
210
|
+
} else if (queryType === 'in-app') {
|
|
211
|
+
const productsResult = items as Product[];
|
|
212
|
+
setProducts((prevProducts) =>
|
|
213
|
+
mergeWithDuplicateCheck(
|
|
214
|
+
prevProducts,
|
|
215
|
+
productsResult,
|
|
216
|
+
(product) => product.id,
|
|
217
|
+
),
|
|
218
|
+
);
|
|
191
219
|
} else {
|
|
220
|
+
const productItems = items.filter(
|
|
221
|
+
(item) => canonicalProductType(item.type as string) === 'in-app',
|
|
222
|
+
) as Product[];
|
|
223
|
+
const subscriptionItems = items.filter(
|
|
224
|
+
(item) => canonicalProductType(item.type as string) === 'subs',
|
|
225
|
+
) as ProductSubscription[];
|
|
226
|
+
|
|
192
227
|
setProducts((prevProducts) =>
|
|
193
228
|
mergeWithDuplicateCheck(
|
|
194
229
|
prevProducts,
|
|
195
|
-
|
|
230
|
+
productItems,
|
|
196
231
|
(product) => product.id,
|
|
197
232
|
),
|
|
198
233
|
);
|
|
234
|
+
|
|
235
|
+
setSubscriptions((prevSubscriptions) =>
|
|
236
|
+
mergeWithDuplicateCheck(
|
|
237
|
+
prevSubscriptions,
|
|
238
|
+
subscriptionItems,
|
|
239
|
+
(subscription) => subscription.id,
|
|
240
|
+
),
|
|
241
|
+
);
|
|
199
242
|
}
|
|
200
243
|
} catch (error) {
|
|
201
244
|
console.error('Error fetching products:', error);
|
|
202
245
|
}
|
|
203
246
|
},
|
|
204
|
-
[mergeWithDuplicateCheck],
|
|
247
|
+
[canonicalProductType, mergeWithDuplicateCheck, normalizeProductQueryType],
|
|
205
248
|
);
|
|
206
249
|
|
|
207
250
|
const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
@@ -242,23 +285,23 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
242
285
|
);
|
|
243
286
|
|
|
244
287
|
const finishTransaction = useCallback(
|
|
245
|
-
({
|
|
288
|
+
async ({
|
|
246
289
|
purchase,
|
|
247
290
|
isConsumable,
|
|
248
291
|
}: {
|
|
249
292
|
purchase: Purchase;
|
|
250
293
|
isConsumable?: boolean;
|
|
251
|
-
}): Promise<
|
|
252
|
-
|
|
253
|
-
purchase,
|
|
294
|
+
}): Promise<void> => {
|
|
295
|
+
await finishTransactionInternal({
|
|
296
|
+
purchase: toPurchaseInput(purchase),
|
|
254
297
|
isConsumable,
|
|
255
298
|
});
|
|
256
299
|
},
|
|
257
|
-
[],
|
|
300
|
+
[toPurchaseInput],
|
|
258
301
|
);
|
|
259
302
|
|
|
260
303
|
const requestPurchaseWithReset = useCallback(
|
|
261
|
-
(requestObj:
|
|
304
|
+
(requestObj: MutationRequestPurchaseArgs) => {
|
|
262
305
|
return requestPurchaseInternal(requestObj);
|
|
263
306
|
},
|
|
264
307
|
[],
|
|
@@ -268,14 +311,14 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
268
311
|
async (productId: string) => {
|
|
269
312
|
try {
|
|
270
313
|
if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {
|
|
271
|
-
await
|
|
314
|
+
await fetchProductsInternal({skus: [productId], type: 'subs'});
|
|
272
315
|
await getAvailablePurchasesInternal();
|
|
273
316
|
}
|
|
274
317
|
} catch (error) {
|
|
275
318
|
console.warn('Failed to refresh subscription status:', error);
|
|
276
319
|
}
|
|
277
320
|
},
|
|
278
|
-
[
|
|
321
|
+
[fetchProductsInternal, getAvailablePurchasesInternal],
|
|
279
322
|
);
|
|
280
323
|
|
|
281
324
|
// Restore completed transactions with cross-platform behavior.
|
|
@@ -283,7 +326,8 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
283
326
|
// Android: fetch available purchases directly.
|
|
284
327
|
const restorePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
285
328
|
try {
|
|
286
|
-
|
|
329
|
+
await restorePurchases();
|
|
330
|
+
const purchases = await getAvailablePurchases({
|
|
287
331
|
alsoPublishToEventListenerIOS: false,
|
|
288
332
|
onlyIncludeActiveItemsIOS: true,
|
|
289
333
|
});
|
|
@@ -293,20 +337,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
293
337
|
}
|
|
294
338
|
}, []);
|
|
295
339
|
|
|
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
|
-
);
|
|
340
|
+
const validateReceipt = useCallback(async (props: ReceiptValidationProps) => {
|
|
341
|
+
return validateReceiptInternal(props);
|
|
342
|
+
}, []);
|
|
310
343
|
|
|
311
344
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
312
345
|
// CRITICAL: Register listeners BEFORE initConnection to avoid race condition
|