mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
fix(server): rcat event parse (#13781)
This commit is contained in:
@@ -3,6 +3,8 @@ import { z } from 'zod';
|
||||
|
||||
import { Config, EventBus } from '../../../base';
|
||||
import { Public } from '../../../core/auth';
|
||||
import { FeatureService } from '../../../core/features';
|
||||
import { Models } from '../../../models';
|
||||
|
||||
const RcEventSchema = z
|
||||
.object({
|
||||
@@ -52,7 +54,9 @@ export class RevenueCatWebhookController {
|
||||
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
private readonly event: EventBus
|
||||
private readonly event: EventBus,
|
||||
private readonly models: Models,
|
||||
private readonly feature: FeatureService
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@@ -70,28 +74,49 @@ export class RevenueCatWebhookController {
|
||||
if (parsed.success) {
|
||||
const event = parsed.data.event;
|
||||
const { id, app_user_id: appUserId, type } = event;
|
||||
|
||||
if (
|
||||
event.environment.toLowerCase() === environment?.toLowerCase()
|
||||
) {
|
||||
const logParams = {
|
||||
appUserId,
|
||||
familyShare: event.is_family_share,
|
||||
environment: event.environment,
|
||||
};
|
||||
this.logger.log(
|
||||
`[${id}] RevenueCat Webhook {${type}} received for appUserId=${appUserId}.`
|
||||
);
|
||||
|
||||
if (
|
||||
appUserId &&
|
||||
(typeof event.is_family_share !== 'boolean' ||
|
||||
!event.is_family_share)
|
||||
) {
|
||||
// immediately ack and process asynchronously
|
||||
this.event
|
||||
.emitAsync('revenuecat.webhook', { appUserId, event })
|
||||
.catch((e: Error) => {
|
||||
this.logger.error(
|
||||
'Failed to handle RevenueCat Webhook event.',
|
||||
e
|
||||
if (appUserId) {
|
||||
const user = await this.models.user.get(appUserId);
|
||||
if (user) {
|
||||
if (
|
||||
(typeof event.is_family_share !== 'boolean' ||
|
||||
!event.is_family_share) &&
|
||||
(environment.toLowerCase() === 'production' ||
|
||||
this.feature.isStaff(user.email))
|
||||
) {
|
||||
// immediately ack and process asynchronously
|
||||
this.event
|
||||
.emitAsync('revenuecat.webhook', { appUserId, event })
|
||||
.catch((e: Error) => {
|
||||
this.logger.error(
|
||||
'Failed to handle RevenueCat Webhook event.',
|
||||
e
|
||||
);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`[${id}] RevenueCat Webhook received for non-acceptable params.`,
|
||||
logParams
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.warn(
|
||||
`RevenueCat Webhook received for unknown user`,
|
||||
logParams
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.logger.warn(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Config } from '../../../base';
|
||||
@@ -34,7 +34,7 @@ const zRcV2RawEntitlementItem = z
|
||||
products: z
|
||||
.object({ items: z.array(zRcV2RawProduct).default([]) })
|
||||
.partial()
|
||||
.nullable(),
|
||||
.nullish(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -75,7 +75,7 @@ const zRcV2RawEnvelope = z
|
||||
.object({
|
||||
app_user_id: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
subscriptions: z.array(zRcV2RawSubscription).default([]),
|
||||
items: z.array(zRcV2RawSubscription).default([]),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -96,6 +96,8 @@ export type Subscription = z.infer<typeof Subscription>;
|
||||
|
||||
@Injectable()
|
||||
export class RevenueCatService {
|
||||
private readonly logger = new Logger(RevenueCatService.name);
|
||||
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
private get apiKey(): string {
|
||||
@@ -132,17 +134,15 @@ export class RevenueCatService {
|
||||
);
|
||||
}
|
||||
|
||||
const envParsed = zRcV2RawEnvelope.safeParse(await res.json());
|
||||
const json = await res.json();
|
||||
const envParsed = zRcV2RawEnvelope.safeParse(json);
|
||||
|
||||
if (envParsed.success) {
|
||||
return envParsed.data.subscriptions
|
||||
return envParsed.data.items
|
||||
.flatMap(sub => {
|
||||
const items = sub.entitlements.items ?? [];
|
||||
return items.map(ent => {
|
||||
const product = ent.products?.items?.[0];
|
||||
if (!product) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
identifier: ent.lookup_key,
|
||||
isTrial: sub.status === 'trialing',
|
||||
@@ -151,20 +151,25 @@ export class RevenueCatService {
|
||||
sub.status === 'active' ||
|
||||
sub.status === 'trialing',
|
||||
latestPurchaseDate: sub.starts_at
|
||||
? new Date(sub.starts_at * 1000)
|
||||
? new Date(sub.starts_at)
|
||||
: null,
|
||||
expirationDate: sub.current_period_ends_at
|
||||
? new Date(sub.current_period_ends_at * 1000)
|
||||
? new Date(sub.current_period_ends_at)
|
||||
: null,
|
||||
productId: product.store_identifier,
|
||||
store: sub.store ?? product.app.type,
|
||||
productId: product?.store_identifier,
|
||||
store: sub.store ?? product?.app.type,
|
||||
willRenew: sub.auto_renewal_status === 'will_renew',
|
||||
duration: product.subscription?.duration ?? null,
|
||||
duration: product?.subscription?.duration ?? null,
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter((s): s is Subscription => s !== null);
|
||||
}
|
||||
this.logger.error(
|
||||
`RevenueCat subscription parse failed: ${JSON.stringify(
|
||||
envParsed.error.format()
|
||||
)}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user