@umituz/react-native-subscription 2.27.68 → 2.27.70

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.27.68",
3
+ "version": "2.27.70",
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",
@@ -1,6 +1,6 @@
1
- import type { CreditsConfig } from "../../domain/entities/Credits";
2
- import { detectPackageType } from "../../utils/packageTypeDetector";
3
- import { getCreditAllocation } from "../../utils/creditMapper";
1
+ import type { CreditsConfig } from "../core/Credits";
2
+ import { detectPackageType } from "../../../utils/packageTypeDetector";
3
+ import { getCreditAllocation } from "../../../utils/creditMapper";
4
4
 
5
5
  export class CreditLimitCalculator {
6
6
  static calculate(productId: string | undefined, config: CreditsConfig): number {
@@ -4,10 +4,10 @@ import {
4
4
  runTransaction,
5
5
  serverTimestamp,
6
6
  Timestamp,
7
- type Firestore,
8
7
  type Transaction,
9
8
  type DocumentReference,
10
- } from "firebase/firestore";
9
+ type Firestore,
10
+ } from "@umituz/react-native-firebase";
11
11
  import type { CreditsConfig } from "../core/Credits";
12
12
  import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
13
13
  import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionStatus";
@@ -33,7 +33,7 @@ export async function initializeCreditsTransaction(
33
33
  }
34
34
 
35
35
  const creditLimit = CreditLimitCalculator.calculate(metadata?.productId, config);
36
- const { purchaseType, purchaseHistory } = PurchaseMetadataGenerator.generate({
36
+ const { purchaseHistory } = PurchaseMetadataGenerator.generate({
37
37
  productId: metadata?.productId,
38
38
  source: metadata?.source,
39
39
  type: metadata?.type,
@@ -51,19 +51,22 @@ export async function initializeCreditsTransaction(
51
51
  // Resolve credits using Strategy Pattern
52
52
  const isStatusSync = purchaseId?.startsWith("status_sync_") ?? false;
53
53
  const isSubscriptionActive = isPremium && !isExpired;
54
+ const productId = metadata?.productId;
54
55
 
55
56
  const newCredits = creditAllocationContext.allocate({
56
57
  status,
57
58
  isStatusSync,
58
59
  existingData,
59
60
  creditLimit,
60
- isSubscriptionActive
61
+ isSubscriptionActive,
62
+ productId,
61
63
  });
62
64
 
63
65
  const creditsData: Record<string, any> = {
64
66
  isPremium, status, credits: newCredits, creditLimit,
65
67
  lastUpdatedAt: now,
66
- processedPurchases: (purchaseId ? [...(existingData?.processedPurchases || []), purchaseId].slice(-10) : existingData?.processedPurchases) || [],
68
+ // Increase history window to 50 for better idempotency
69
+ processedPurchases: (purchaseId ? [...(existingData?.processedPurchases || []), purchaseId].slice(-50) : existingData?.processedPurchases) || [],
67
70
  purchaseHistory: purchaseHistory.length ? purchaseHistory : undefined
68
71
  };
69
72
 
@@ -1,7 +1,7 @@
1
1
  import { runTransaction, serverTimestamp, type Firestore, type Transaction, type DocumentReference } from "firebase/firestore";
2
2
  import { getFirestore } from "@umituz/react-native-firebase";
3
- import type { DeductCreditsResult } from "../../../domain/entities/Credits";
4
- import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../SubscriptionEventBus";
3
+ import type { DeductCreditsResult } from "../core/Credits";
4
+ import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
5
5
 
6
6
  export interface IDeductCreditsCommand {
7
7
  execute(userId: string, cost: number): Promise<DeductCreditsResult>;
@@ -1,5 +1,5 @@
1
- import type { SubscriptionStatusType } from "../../domain/entities/SubscriptionStatus";
2
- import type { UserCreditsDocumentRead } from "../models/UserCreditsDocument";
1
+ import type { SubscriptionStatusType } from "../../../subscription/core/SubscriptionStatus";
2
+ import type { UserCreditsDocumentRead } from "../../core/UserCreditsDocument";
3
3
 
4
4
  export interface CreditStrategyParams {
5
5
  status: SubscriptionStatusType;
@@ -7,6 +7,7 @@ export interface CreditStrategyParams {
7
7
  existingData: UserCreditsDocumentRead | null;
8
8
  creditLimit: number;
9
9
  isSubscriptionActive: boolean;
10
+ productId?: string;
10
11
  }
11
12
 
12
13
  export interface ICreditStrategy {
@@ -1,8 +1,9 @@
1
1
  import { ICreditStrategy, type CreditAllocationParams } from "./ICreditStrategy";
2
+ import { isCreditPackage } from "../../../../utils/packageTypeDetector";
2
3
 
3
4
  /**
4
5
  * Default strategy for new purchases, renewals, or upgrades.
5
- * Resets credits to the calculated credit limit.
6
+ * Resets credits for subscriptions, but ADDS credits for consumable packages.
6
7
  */
7
8
  export class StandardPurchaseCreditStrategy implements ICreditStrategy {
8
9
  canHandle(_params: CreditAllocationParams): boolean {
@@ -11,6 +12,13 @@ export class StandardPurchaseCreditStrategy implements ICreditStrategy {
11
12
  }
12
13
 
13
14
  execute(params: CreditAllocationParams): number {
15
+ // If it's a credit package (consumable), we add to existing balance
16
+ if (params.productId && isCreditPackage(params.productId)) {
17
+ const existing = params.existingData?.credits ?? 0;
18
+ return existing + params.creditLimit;
19
+ }
20
+
21
+ // Standard subscription behavior: Reset to the calculated limit (e.g. 100/mo)
14
22
  return params.creditLimit;
15
23
  }
16
24
  }
@@ -1,6 +1,6 @@
1
1
  import { ICreditStrategy, type CreditAllocationParams } from "./ICreditStrategy";
2
- import { SUBSCRIPTION_STATUS } from "../../../domain/entities/SubscriptionStatus";
3
- import { TRIAL_CONFIG } from "../TrialService";
2
+ import { SUBSCRIPTION_STATUS } from "../../../subscription/core/SubscriptionConstants";
3
+ import { TRIAL_CONFIG } from "../../../trial/core/TrialTypes";
4
4
 
5
5
  /**
6
6
  * Strategy for Trial and Trial Canceled users.
@@ -15,7 +15,11 @@ export class SubscriptionSyncService {
15
15
  async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) {
16
16
  try {
17
17
  const revenueCatData = extractRevenueCatData(customerInfo, this.entitlementId);
18
- await getCreditsRepository().initializeCredits(userId, `purchase_${productId}_${Date.now()}`, productId, source, revenueCatData);
18
+ const purchaseId = revenueCatData.originalTransactionId
19
+ ? `purchase_${revenueCatData.originalTransactionId}`
20
+ : `purchase_${productId}_${Date.now()}`;
21
+
22
+ await getCreditsRepository().initializeCredits(userId, purchaseId, productId, source, revenueCatData);
19
23
 
20
24
  // Notify listeners via Event Bus
21
25
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
@@ -29,7 +33,11 @@ export class SubscriptionSyncService {
29
33
  try {
30
34
  const revenueCatData = extractRevenueCatData(customerInfo, this.entitlementId);
31
35
  revenueCatData.expirationDate = newExpirationDate || revenueCatData.expirationDate;
32
- await getCreditsRepository().initializeCredits(userId, `renewal_${productId}_${Date.now()}`, productId, "renewal", revenueCatData);
36
+ const purchaseId = revenueCatData.originalTransactionId
37
+ ? `renewal_${revenueCatData.originalTransactionId}_${newExpirationDate}`
38
+ : `renewal_${productId}_${Date.now()}`;
39
+
40
+ await getCreditsRepository().initializeCredits(userId, purchaseId, productId, "renewal", revenueCatData);
33
41
 
34
42
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
35
43
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.RENEWAL_DETECTED, { userId, productId });
@@ -1,6 +1,6 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
- import type { RevenueCatData } from "../../domain/types/RevenueCatData";
3
- import { type PeriodType } from "../../domain/entities/SubscriptionConstants";
2
+ import type { RevenueCatData } from "../core/RevenueCatData";
3
+ import { type PeriodType } from "../core/SubscriptionStatus";
4
4
 
5
5
  /** Extract RevenueCat data from CustomerInfo (Single Source of Truth) */
6
6
  export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId: string): RevenueCatData => {
@@ -10,7 +10,9 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
10
10
  return {
11
11
  expirationDate: entitlement?.expirationDate ?? customerInfo.latestExpirationDate ?? null,
12
12
  willRenew: entitlement?.willRenew ?? false,
13
- originalTransactionId: entitlement?.originalPurchaseDate ?? undefined,
13
+ // Use latestPurchaseDate if originalPurchaseDate is missing, or a combine id
14
+ originalTransactionId: entitlement?.originalPurchaseDate || customerInfo.firstSeen,
14
15
  periodType: entitlement?.periodType as PeriodType | undefined,
16
+ isPremium: !!customerInfo.entitlements.active[entitlementId],
15
17
  };
16
18
  };
@@ -3,7 +3,7 @@
3
3
  * Detects subscription package type from RevenueCat package identifier
4
4
  */
5
5
 
6
- import { PACKAGE_TYPE, type PackageType } from "../domain/entities/SubscriptionConstants";
6
+ import { PACKAGE_TYPE, type PackageType } from "../domains/subscription/core/SubscriptionConstants";
7
7
 
8
8
  export type SubscriptionPackageType = PackageType;
9
9
 
@@ -15,7 +15,7 @@ export type SubscriptionPackageType = PackageType;
15
15
  * Check if identifier is a credit package (consumable purchase)
16
16
  * Credit packages use a different system and don't need type detection
17
17
  */
18
- function isCreditPackage(identifier: string): boolean {
18
+ export function isCreditPackage(identifier: string): boolean {
19
19
  // Matches "credit" as a word or part of a common naming pattern
20
20
  return /\bcredit\b|_credit_|-credit-/i.test(identifier) || identifier.toLowerCase().includes("credit");
21
21
  }