Introduce mutex for purchase fulfillment locking
parent
4a763acb48
commit
1fd8bfec89
|
|
@ -14,6 +14,7 @@
|
|||
"@nestjs/jwt": "^11.0.2",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"async-lock": "^1.4.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"pg": "^8.16.3",
|
||||
|
|
@ -4025,6 +4026,12 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-lock": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
|
||||
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"@nestjs/jwt": "^11.0.2",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"async-lock": "^1.4.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"pg": "^8.16.3",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
import { Body, Controller, Logger, Post, Req } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
Post,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import { PurchaseService } from '../service/purchase.service';
|
||||
import { UserService } from '../service/user.service';
|
||||
import { PurchaseItem } from '../dto/purchase-item';
|
||||
import { StripeService } from '../service/stripe.service';
|
||||
import { PurchaseStatus } from 'src/dto/purchase-status';
|
||||
import { TokenService } from 'src/service/token.service';
|
||||
import AsyncLock from 'async-lock';
|
||||
|
||||
@Controller('/buy')
|
||||
export class BuyController {
|
||||
|
|
@ -13,6 +21,7 @@ export class BuyController {
|
|||
PurchaseStatus.IN_PROGRESS,
|
||||
];
|
||||
private readonly logger = new Logger(BuyController.name);
|
||||
private readonly purchaseLock = new AsyncLock();
|
||||
|
||||
constructor(
|
||||
private readonly purchaseService: PurchaseService,
|
||||
|
|
@ -42,26 +51,34 @@ export class BuyController {
|
|||
}
|
||||
|
||||
@Post('/complete')
|
||||
public async complete(@Req() request) {
|
||||
public async complete(
|
||||
@Req() request,
|
||||
@Body('purchaseId') purchaseId: string,
|
||||
) {
|
||||
if (!purchaseId) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
const userId = request.userId;
|
||||
const user = await this.userService.getUserById(userId);
|
||||
const purchases = await this.purchaseService.getPurchasesForUser(user);
|
||||
purchases
|
||||
.filter((purchase) =>
|
||||
BuyController.PURCHASE_STATUS_TO_REEVALUATE.includes(purchase.status),
|
||||
)
|
||||
.forEach(async (purchase) => {
|
||||
const { isPaid } = await this.stripeService.getStripeSessionFromId(
|
||||
purchase.stripeSessionId!,
|
||||
);
|
||||
purchase.status = isPaid
|
||||
? PurchaseStatus.COMPLETED
|
||||
: PurchaseStatus.IN_PROGRESS;
|
||||
this.purchaseService.recordPurchase(purchase);
|
||||
if (purchase.status === PurchaseStatus.COMPLETED) {
|
||||
this.tokenService.addTokens(user, purchase);
|
||||
}
|
||||
});
|
||||
await this.purchaseLock.acquire(purchaseId, async () => {
|
||||
const purchase = await this.purchaseService.getPurchaseById(purchaseId);
|
||||
if (
|
||||
!BuyController.PURCHASE_STATUS_TO_REEVALUATE.includes(purchase.status)
|
||||
) {
|
||||
this.logger.debug('Purchase is not open to re-evaluate');
|
||||
return;
|
||||
}
|
||||
const { isPaid } = await this.stripeService.getStripeSessionFromId(
|
||||
purchase.stripeSessionId!,
|
||||
);
|
||||
purchase.status = isPaid
|
||||
? PurchaseStatus.COMPLETED
|
||||
: PurchaseStatus.IN_PROGRESS;
|
||||
await this.purchaseService.recordPurchase(purchase);
|
||||
if (purchase.status === PurchaseStatus.COMPLETED) {
|
||||
await this.tokenService.addTokens(user, purchase);
|
||||
}
|
||||
});
|
||||
|
||||
return { url: '/signup' };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,17 @@ export class PurchaseService {
|
|||
purchasedProduct: purchasedItem,
|
||||
purchasedUnits: units,
|
||||
status: PurchaseStatus.CREATED,
|
||||
purchaseDate: Date.now(),
|
||||
purchaseDate: new Date(),
|
||||
});
|
||||
return this.purchaseRepo.save(purchase);
|
||||
}
|
||||
|
||||
public recordPurchase(purchase: Purchase) {
|
||||
this.purchaseRepo.save(purchase);
|
||||
public async recordPurchase(purchase: Purchase) {
|
||||
await this.purchaseRepo.save(purchase);
|
||||
}
|
||||
|
||||
public async getPurchaseById(purchaseId: string) {
|
||||
return this.purchaseRepo.findOneOrFail({ where: { id: purchaseId } });
|
||||
}
|
||||
|
||||
public getPurchasesForUser(user: User) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue