mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(server): refresh subscription (#13670)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an on-demand mutation to refresh the current user's subscriptions, syncing with RevenueCat when applicable and handling Stripe-only cases. * Subscription variant normalization for clearer plan information and consistent results. * **Tests** * Added tests for refresh behavior: empty state, RevenueCat-backed multi-step sync, and Stripe-only scenarios. * **Client** * New client operation to invoke the refresh mutation and retrieve updated subscription fields. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -12,8 +12,7 @@ import {
|
||||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
import type { User } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaClient, Provider, type User } from '@prisma/client';
|
||||
import { GraphQLJSONObject } from 'graphql-scalars';
|
||||
import { groupBy } from 'lodash-es';
|
||||
import Stripe from 'stripe';
|
||||
@@ -31,6 +30,7 @@ import { AccessController } from '../../core/permission';
|
||||
import { UserType } from '../../core/user';
|
||||
import { WorkspaceType } from '../../core/workspaces';
|
||||
import { Invoice, Subscription, WorkspaceSubscriptionManager } from './manager';
|
||||
import { RevenueCatWebhookHandler } from './revenuecat';
|
||||
import { CheckoutParams, SubscriptionService } from './service';
|
||||
import {
|
||||
InvoiceStatus,
|
||||
@@ -463,7 +463,22 @@ export class SubscriptionResolver {
|
||||
|
||||
@Resolver(() => UserType)
|
||||
export class UserSubscriptionResolver {
|
||||
constructor(private readonly db: PrismaClient) {}
|
||||
constructor(
|
||||
private readonly db: PrismaClient,
|
||||
private readonly rcHandler: RevenueCatWebhookHandler
|
||||
) {}
|
||||
|
||||
private normalizeSubscription(s: Subscription) {
|
||||
if (
|
||||
s.variant &&
|
||||
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
|
||||
s.variant as SubscriptionVariant
|
||||
)
|
||||
) {
|
||||
s.variant = null;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@ResolveField(() => [SubscriptionType])
|
||||
async subscriptions(
|
||||
@@ -487,16 +502,9 @@ export class UserSubscriptionResolver {
|
||||
},
|
||||
});
|
||||
|
||||
subscriptions.forEach(subscription => {
|
||||
if (
|
||||
subscription.variant &&
|
||||
![SubscriptionVariant.EA, SubscriptionVariant.Onetime].includes(
|
||||
subscription.variant as SubscriptionVariant
|
||||
)
|
||||
) {
|
||||
subscription.variant = null;
|
||||
}
|
||||
});
|
||||
subscriptions.forEach(subscription =>
|
||||
this.normalizeSubscription(subscription)
|
||||
);
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
@@ -534,6 +542,71 @@ export class UserSubscriptionResolver {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@Throttle('strict')
|
||||
@Mutation(() => [SubscriptionType], {
|
||||
description: 'Refresh current user subscriptions and return latest.',
|
||||
})
|
||||
async refreshUserSubscriptions(
|
||||
@CurrentUser() user: CurrentUser
|
||||
): Promise<Subscription[]> {
|
||||
if (!user) {
|
||||
throw new AuthenticationRequired();
|
||||
}
|
||||
|
||||
let current = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const existsPlans = Object.values(SubscriptionPlan);
|
||||
const subscriptions = current.reduce(
|
||||
(r, s) => {
|
||||
if (existsPlans.includes(s.plan as SubscriptionPlan)) {
|
||||
r[s.plan as SubscriptionPlan] = s.provider;
|
||||
}
|
||||
return r;
|
||||
},
|
||||
{} as Record<SubscriptionPlan, Provider>
|
||||
);
|
||||
|
||||
// has revenuecat subscription or no subscription at all
|
||||
const shouldSync =
|
||||
current.length === 0 ||
|
||||
subscriptions.pro === Provider.revenuecat ||
|
||||
subscriptions.ai === Provider.revenuecat;
|
||||
|
||||
if (shouldSync) {
|
||||
try {
|
||||
await this.rcHandler.syncAppUser(user.id);
|
||||
current = await this.db.subscription.findMany({
|
||||
where: {
|
||||
targetId: user.id,
|
||||
status: {
|
||||
in: [
|
||||
SubscriptionStatus.Active,
|
||||
SubscriptionStatus.Trialing,
|
||||
SubscriptionStatus.PastDue,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
// ignore errors
|
||||
} catch {}
|
||||
}
|
||||
|
||||
current.forEach(subscription => this.normalizeSubscription(subscription));
|
||||
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
@Resolver(() => WorkspaceType)
|
||||
|
||||
Reference in New Issue
Block a user