mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
fix(server): should redeem onetime invoice only once (#9927)
fix CLOUD-115
This commit is contained in:
@@ -8,9 +8,11 @@ import {
|
||||
EventBus,
|
||||
InternalServerError,
|
||||
InvalidCheckoutParameters,
|
||||
Mutex,
|
||||
Runtime,
|
||||
SubscriptionAlreadyExists,
|
||||
SubscriptionPlanNotFound,
|
||||
TooManyRequest,
|
||||
URLHelper,
|
||||
} from '../../../base';
|
||||
import {
|
||||
@@ -59,7 +61,8 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
private readonly runtime: Runtime,
|
||||
private readonly feature: FeatureManagementService,
|
||||
private readonly event: EventBus,
|
||||
private readonly url: URLHelper
|
||||
private readonly url: URLHelper,
|
||||
private readonly mutex: Mutex
|
||||
) {
|
||||
super(stripe, db);
|
||||
}
|
||||
@@ -405,7 +408,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
},
|
||||
});
|
||||
|
||||
const invoice = this.db.invoice.upsert({
|
||||
const invoice = await this.db.invoice.upsert({
|
||||
where: {
|
||||
stripeInvoiceId: stripeInvoice.id,
|
||||
},
|
||||
@@ -419,6 +422,14 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
// onetime and lifetime subscription is a special "subscription" that doesn't get involved with stripe subscription system
|
||||
// we track the deals by invoice only.
|
||||
if (stripeInvoice.status === 'paid') {
|
||||
await using lock = await this.mutex.acquire(
|
||||
`redeem-onetime-subscription:${stripeInvoice.id}`
|
||||
);
|
||||
|
||||
if (!lock) {
|
||||
throw new TooManyRequest();
|
||||
}
|
||||
|
||||
if (lookupKey.recurring === SubscriptionRecurring.Lifetime) {
|
||||
await this.saveLifetimeSubscription(knownInvoice);
|
||||
} else if (lookupKey.variant === SubscriptionVariant.Onetime) {
|
||||
@@ -429,9 +440,7 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
return invoice;
|
||||
}
|
||||
|
||||
async saveLifetimeSubscription(
|
||||
knownInvoice: KnownStripeInvoice
|
||||
): Promise<Subscription> {
|
||||
async saveLifetimeSubscription(knownInvoice: KnownStripeInvoice) {
|
||||
this.assertUserIdExists(knownInvoice.userId);
|
||||
|
||||
// cancel previous non-lifetime subscription
|
||||
@@ -444,10 +453,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
},
|
||||
});
|
||||
|
||||
let subscription: Subscription;
|
||||
if (prevSubscription) {
|
||||
if (prevSubscription.stripeSubscriptionId) {
|
||||
subscription = await this.db.subscription.update({
|
||||
await this.db.subscription.update({
|
||||
where: {
|
||||
id: prevSubscription.id,
|
||||
},
|
||||
@@ -469,11 +477,9 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
prorate: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
subscription = prevSubscription;
|
||||
}
|
||||
} else {
|
||||
subscription = await this.db.subscription.create({
|
||||
await this.db.subscription.create({
|
||||
data: {
|
||||
targetId: knownInvoice.userId,
|
||||
stripeSubscriptionId: null,
|
||||
@@ -492,17 +498,37 @@ export class UserSubscriptionManager extends SubscriptionManager {
|
||||
plan: knownInvoice.lookupKey.plan,
|
||||
recurring: SubscriptionRecurring.Lifetime,
|
||||
});
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
async saveOnetimePaymentSubscription(
|
||||
knownInvoice: KnownStripeInvoice
|
||||
): Promise<Subscription> {
|
||||
async saveOnetimePaymentSubscription(knownInvoice: KnownStripeInvoice) {
|
||||
this.assertUserIdExists(knownInvoice.userId);
|
||||
const { userId, lookupKey, stripeInvoice } = knownInvoice;
|
||||
|
||||
const invoice = await this.db.invoice.findUnique({
|
||||
where: {
|
||||
stripeInvoiceId: stripeInvoice.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!invoice) {
|
||||
// never happens
|
||||
throw new InternalServerError('Invoice not found');
|
||||
}
|
||||
|
||||
if (invoice.onetimeSubscriptionRedeemed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.db.invoice.update({
|
||||
select: {
|
||||
onetimeSubscriptionRedeemed: true,
|
||||
},
|
||||
where: {
|
||||
stripeInvoiceId: stripeInvoice.id,
|
||||
},
|
||||
data: { onetimeSubscriptionRedeemed: true },
|
||||
});
|
||||
|
||||
// TODO(@forehalo): identify whether the invoice has already been redeemed.
|
||||
const { userId, lookupKey } = knownInvoice;
|
||||
const existingSubscription = await this.db.subscription.findUnique({
|
||||
where: {
|
||||
targetId_plan: {
|
||||
|
||||
Reference in New Issue
Block a user