Slefhostet und postgres
This commit is contained in:
@@ -12,7 +12,14 @@ import {
|
||||
import { ImageCacheService } from '../services/imageCacheService';
|
||||
import { getTranslation } from '../utils/translations';
|
||||
import { backendApiClient } from '../services/backend/backendApiClient';
|
||||
import { BillingSummary, PurchaseProductId, RevenueCatCustomerInfo, SimulatedWebhookEvent } from '../services/backend/contracts';
|
||||
import {
|
||||
BillingSummary,
|
||||
PurchaseProductId,
|
||||
RevenueCatCustomerInfo,
|
||||
RevenueCatEntitlementInfo,
|
||||
RevenueCatSyncSource,
|
||||
SimulatedWebhookEvent,
|
||||
} from '../services/backend/contracts';
|
||||
import { createIdempotencyKey } from '../utils/idempotency';
|
||||
import { AuthService, AuthSession } from '../services/authService';
|
||||
import { PlantsDb, SettingsDb, LexiconHistoryDb, AppMetaDb } from '../services/database';
|
||||
@@ -43,7 +50,7 @@ interface AppState {
|
||||
updatePlant: (plant: Plant) => void;
|
||||
refreshPlants: () => void;
|
||||
refreshBillingSummary: () => Promise<void>;
|
||||
syncRevenueCatState: (customerInfo: RevenueCatCustomerInfo) => Promise<BillingSummary | null>;
|
||||
syncRevenueCatState: (customerInfo: RevenueCatCustomerInfo, source?: RevenueCatSyncSource) => Promise<BillingSummary | null>;
|
||||
simulatePurchase: (productId: PurchaseProductId) => Promise<void>;
|
||||
simulateWebhookEvent: (event: SimulatedWebhookEvent, payload?: { credits?: number }) => Promise<void>;
|
||||
getLexiconSearchHistory: () => string[];
|
||||
@@ -70,11 +77,47 @@ export const useApp = () => {
|
||||
return ctx;
|
||||
};
|
||||
|
||||
const isAppearanceMode = (v: string): v is AppearanceMode =>
|
||||
v === 'system' || v === 'light' || v === 'dark';
|
||||
const isColorPalette = (v: string): v is ColorPalette =>
|
||||
v === 'forest' || v === 'ocean' || v === 'sunset' || v === 'mono';
|
||||
const isLanguage = (v: string): v is Language => v === 'de' || v === 'en' || v === 'es';
|
||||
const isAppearanceMode = (v: string): v is AppearanceMode =>
|
||||
v === 'system' || v === 'light' || v === 'dark';
|
||||
const isColorPalette = (v: string): v is ColorPalette =>
|
||||
v === 'forest' || v === 'ocean' || v === 'sunset' || v === 'mono';
|
||||
const isLanguage = (v: string): v is Language => v === 'de' || v === 'en' || v === 'es';
|
||||
const REVENUECAT_PRO_ENTITLEMENT_ID = (process.env.EXPO_PUBLIC_REVENUECAT_PRO_ENTITLEMENT_ID || 'pro').trim() || 'pro';
|
||||
const SUPPORTED_REVENUECAT_SUBSCRIPTION_PRODUCTS = new Set<PurchaseProductId>(['monthly_pro', 'yearly_pro']);
|
||||
|
||||
const summarizeRevenueCatCustomerInfo = (customerInfo: RevenueCatCustomerInfo) => {
|
||||
const activeEntitlements = customerInfo?.entitlements?.active || {};
|
||||
return {
|
||||
appUserId: customerInfo?.appUserId ?? null,
|
||||
originalAppUserId: customerInfo?.originalAppUserId ?? null,
|
||||
activeEntitlements: Object.entries(activeEntitlements).map(([id, entitlement]) => ({
|
||||
id,
|
||||
productIdentifier: entitlement?.productIdentifier ?? null,
|
||||
expirationDate: entitlement?.expirationDate || entitlement?.expiresDate || null,
|
||||
})),
|
||||
allPurchasedProductIdentifiers: customerInfo?.allPurchasedProductIdentifiers ?? [],
|
||||
nonSubscriptionTransactions: Object.values(customerInfo?.nonSubscriptions || {}).flatMap((entries) =>
|
||||
(Array.isArray(entries) ? entries : []).map((transaction) => ({
|
||||
productIdentifier: transaction?.productIdentifier ?? null,
|
||||
transactionIdentifier: transaction?.transactionIdentifier || transaction?.transactionId || null,
|
||||
}))),
|
||||
};
|
||||
};
|
||||
|
||||
const getValidProEntitlement = (customerInfo: RevenueCatCustomerInfo): RevenueCatEntitlementInfo | null => {
|
||||
const activeEntitlements = customerInfo?.entitlements?.active || {};
|
||||
const proEntitlement = activeEntitlements[REVENUECAT_PRO_ENTITLEMENT_ID];
|
||||
if (!proEntitlement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (proEntitlement.productIdentifier && SUPPORTED_REVENUECAT_SUBSCRIPTION_PRODUCTS.has(proEntitlement.productIdentifier as PurchaseProductId)) {
|
||||
return proEntitlement;
|
||||
}
|
||||
|
||||
console.warn('[Billing] Ignoring unsupported RevenueCat pro entitlement during local sync', summarizeRevenueCatCustomerInfo(customerInfo));
|
||||
return null;
|
||||
};
|
||||
|
||||
const getDeviceLanguage = (): Language => {
|
||||
try {
|
||||
@@ -341,14 +384,25 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const applyRevenueCatCustomerInfoLocally = useCallback((customerInfo: RevenueCatCustomerInfo) => {
|
||||
const entitlementId = (process.env.EXPO_PUBLIC_REVENUECAT_PRO_ENTITLEMENT_ID || 'pro').trim() || 'pro';
|
||||
const applyRevenueCatCustomerInfoLocally = useCallback((
|
||||
customerInfo: RevenueCatCustomerInfo,
|
||||
source: RevenueCatSyncSource = 'app_init',
|
||||
) => {
|
||||
if (source === 'topup_purchase') {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeEntitlements = customerInfo?.entitlements?.active || {};
|
||||
const proEntitlement = activeEntitlements[entitlementId];
|
||||
const rawProEntitlement = activeEntitlements[REVENUECAT_PRO_ENTITLEMENT_ID];
|
||||
const proEntitlement = getValidProEntitlement(customerInfo);
|
||||
const isPro = Boolean(proEntitlement);
|
||||
|
||||
setBillingSummary((prev) => {
|
||||
if (!prev) return prev;
|
||||
if (!proEntitlement && rawProEntitlement) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
entitlement: {
|
||||
@@ -362,10 +416,17 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
});
|
||||
}, []);
|
||||
|
||||
const syncRevenueCatState = useCallback(async (customerInfo: RevenueCatCustomerInfo) => {
|
||||
applyRevenueCatCustomerInfoLocally(customerInfo);
|
||||
const syncRevenueCatState = useCallback(async (
|
||||
customerInfo: RevenueCatCustomerInfo,
|
||||
source: RevenueCatSyncSource = 'app_init',
|
||||
) => {
|
||||
console.log('[Billing] Syncing RevenueCat customer info', {
|
||||
source,
|
||||
customerInfo: summarizeRevenueCatCustomerInfo(customerInfo),
|
||||
});
|
||||
applyRevenueCatCustomerInfoLocally(customerInfo, source);
|
||||
try {
|
||||
const response = await backendApiClient.syncRevenueCatState({ customerInfo });
|
||||
const response = await backendApiClient.syncRevenueCatState({ customerInfo, source });
|
||||
setBillingSummary(response.billing);
|
||||
return response.billing;
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user