@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 +1 -1
- package/src/domains/credits/application/CreditLimitCalculator.ts +3 -3
- package/src/domains/credits/application/CreditsInitializer.ts +8 -5
- package/src/domains/credits/application/DeductCreditsCommand.ts +2 -2
- package/src/domains/credits/application/credit-strategies/ICreditStrategy.ts +3 -2
- package/src/domains/credits/application/credit-strategies/StandardPurchaseCreditStrategy.ts +9 -1
- package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +2 -2
- package/src/domains/subscription/application/SubscriptionSyncService.ts +10 -2
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +5 -3
- package/src/utils/packageTypeDetector.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.27.
|
|
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 "
|
|
2
|
-
import { detectPackageType } from "
|
|
3
|
-
import { getCreditAllocation } from "
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 "
|
|
4
|
-
import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "
|
|
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 "
|
|
2
|
-
import type { UserCreditsDocumentRead } from "
|
|
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
|
|
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 "../../../
|
|
3
|
-
import { TRIAL_CONFIG } from "
|
|
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
|
-
|
|
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
|
-
|
|
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 "
|
|
3
|
-
import { type PeriodType } from "
|
|
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
|
-
|
|
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 "../
|
|
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
|
}
|