@umituz/react-native-subscription 2.37.76 → 2.37.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.37.76",
3
+ "version": "2.37.77",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -96,22 +96,14 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
96
96
  }
97
97
  }
98
98
 
99
+ // Throw so the error propagates to callers (PurchaseExecutor, SubscriptionSyncProcessor).
100
+ // Previously this returned { success: false } which was silently ignored by callers,
101
+ // causing purchases to succeed in RevenueCat but credits to never be written to Firestore.
99
102
  const errorMessage = lastError instanceof Error
100
103
  ? lastError.message
101
104
  : typeof lastError === 'string'
102
105
  ? lastError
103
106
  : 'Unknown error during credit initialization';
104
107
 
105
- const errorCode = lastError instanceof Error
106
- ? (lastError as Error & { code?: string }).code ?? 'UNKNOWN_ERROR'
107
- : 'UNKNOWN_ERROR';
108
-
109
- return {
110
- success: false,
111
- data: null,
112
- error: {
113
- message: errorMessage,
114
- code: errorCode,
115
- },
116
- };
108
+ throw new Error(`[CreditsInitializer] Credit initialization failed after ${maxRetries} retries: ${errorMessage}`);
117
109
  }
@@ -51,7 +51,7 @@ export class SubscriptionSyncProcessor {
51
51
 
52
52
  const creditsUserId = await this.getCreditsUserId(userId);
53
53
 
54
- await getCreditsRepository().initializeCredits(
54
+ const result = await getCreditsRepository().initializeCredits(
55
55
  creditsUserId,
56
56
  purchaseId,
57
57
  productId,
@@ -60,6 +60,10 @@ export class SubscriptionSyncProcessor {
60
60
  PURCHASE_TYPE.INITIAL
61
61
  );
62
62
 
63
+ if (!result.success) {
64
+ throw new Error(`[SubscriptionSyncProcessor] Credit initialization failed for purchase: ${result.error?.message ?? 'unknown'}`);
65
+ }
66
+
63
67
  emitCreditsUpdated(creditsUserId);
64
68
  } finally {
65
69
  this.purchaseInProgress = false;
@@ -77,7 +81,7 @@ export class SubscriptionSyncProcessor {
77
81
 
78
82
  const creditsUserId = await this.getCreditsUserId(userId);
79
83
 
80
- await getCreditsRepository().initializeCredits(
84
+ const result = await getCreditsRepository().initializeCredits(
81
85
  creditsUserId,
82
86
  purchaseId,
83
87
  productId,
@@ -86,6 +90,10 @@ export class SubscriptionSyncProcessor {
86
90
  PURCHASE_TYPE.RENEWAL
87
91
  );
88
92
 
93
+ if (!result.success) {
94
+ throw new Error(`[SubscriptionSyncProcessor] Credit initialization failed for renewal: ${result.error?.message ?? 'unknown'}`);
95
+ }
96
+
89
97
  emitCreditsUpdated(creditsUserId);
90
98
  } finally {
91
99
  this.purchaseInProgress = false;
@@ -100,12 +108,23 @@ export class SubscriptionSyncProcessor {
100
108
  willRenew?: boolean,
101
109
  periodType?: PeriodType
102
110
  ) {
103
- // Skip if a purchase is already handling the credits document.
104
- // Both PurchaseExecutor and CustomerInfoListener fire after a purchase
105
- // the purchase handler writes credits + metadata, so the status handler can skip.
111
+ // If a purchase is in progress, skip metadata sync (purchase handler does it)
112
+ // but still allow recovery to run the purchase handler's credit initialization
113
+ // might have failed, and this is the safety net.
106
114
  if (this.purchaseInProgress) {
107
115
  if (typeof __DEV__ !== "undefined" && __DEV__) {
108
- console.log("[SubscriptionSyncProcessor] Skipping status change - purchase in progress");
116
+ console.log("[SubscriptionSyncProcessor] Purchase in progress - running recovery only");
117
+ }
118
+ if (isPremium && productId) {
119
+ const creditsUserId = await this.getCreditsUserId(userId);
120
+ await handlePremiumStatusSync(
121
+ creditsUserId,
122
+ isPremium,
123
+ productId,
124
+ expiresAt ?? null,
125
+ willRenew ?? false,
126
+ periodType ?? null
127
+ );
109
128
  }
110
129
  return;
111
130
  }
@@ -1,9 +1,18 @@
1
1
  import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
2
2
  import type { PurchaseResult } from "../../../../../shared/application/ports/IRevenueCatService";
3
3
  import type { RevenueCatConfig, PackageType } from "../../../../revenuecat/core/types";
4
- import { notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
4
+ import { notifyPurchaseCompleted, syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
5
5
  import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
6
6
 
7
+ async function attemptRecovery(config: RevenueCatConfig, userId: string, customerInfo: CustomerInfo): Promise<void> {
8
+ try {
9
+ console.warn('[PurchaseExecutor] Attempting recovery via syncPremiumStatus...');
10
+ await syncPremiumStatus(config, userId, customerInfo);
11
+ } catch (recoveryError) {
12
+ console.error('[PurchaseExecutor] Recovery also failed:', recoveryError);
13
+ }
14
+ }
15
+
7
16
  async function executeConsumablePurchase(
8
17
  config: RevenueCatConfig,
9
18
  userId: string,
@@ -17,8 +26,8 @@ async function executeConsumablePurchase(
17
26
  try {
18
27
  await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
19
28
  } catch (syncError) {
20
- // Non-fatal: RevenueCat purchase succeeded, credits sync can be recovered on next session
21
- console.error('[PurchaseExecutor] Post-purchase sync failed (purchase was successful):', syncError);
29
+ console.error('[PurchaseExecutor] Post-purchase sync failed, attempting recovery:', syncError);
30
+ await attemptRecovery(config, userId, customerInfo);
22
31
  } finally {
23
32
  if (savedPurchase) {
24
33
  clearSavedPurchase();
@@ -61,8 +70,8 @@ async function executeSubscriptionPurchase(
61
70
  try {
62
71
  await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
63
72
  } catch (syncError) {
64
- // Non-fatal: RevenueCat purchase succeeded, credits sync can be recovered on next session
65
- console.error('[PurchaseExecutor] Post-purchase sync failed (purchase was successful):', syncError);
73
+ console.error('[PurchaseExecutor] Post-purchase sync failed, attempting recovery:', syncError);
74
+ await attemptRecovery(config, userId, customerInfo);
66
75
  } finally {
67
76
  if (savedPurchase) {
68
77
  clearSavedPurchase();