This commit is contained in:
2026-08-28 17:31:02 +02:00
commit 2b30e8bd39
694 changed files with 49243 additions and 0 deletions
@@ -0,0 +1,102 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { randomUUID } from 'node:crypto';
import { Repository } from 'typeorm';
import type { CookieCartSummary } from '../../storefrontCart/types/CookieCartSummary';
import { deriveCheckoutSessionState } from '../../../utils/checkout/deriveCheckoutSessionState';
import { InvoiceReason } from '../../payment/types/InvoiceReason';
import { InvoiceService } from '../../payment/services/InvoiceService';
import { PaymentMethod } from '../../payment/types/PaymentMethod';
import { CheckoutSessionDiscount } from '../entities/CheckoutSessionDiscount';
import { CheckoutSessionLine } from '../entities/CheckoutSessionLine';
import { CheckoutSession } from '../entities/CheckoutSession';
@Injectable()
export class CheckoutSessionService {
constructor(
@InjectRepository(CheckoutSession)
private readonly sessionRepo: Repository<CheckoutSession>,
@InjectRepository(CheckoutSessionLine)
private readonly lineRepo: Repository<CheckoutSessionLine>,
@InjectRepository(CheckoutSessionDiscount)
private readonly discountRepo: Repository<CheckoutSessionDiscount>,
private readonly invoiceService: InvoiceService
) {}
async findById(id: string): Promise<CheckoutSession | null> {
return this.sessionRepo.findOne({
where: { id },
relations: ['lines', 'discounts', 'invoice', 'invoice.moneroDetails', 'invoice.payments']
});
}
async createFromCartSummary(summary: CookieCartSummary): Promise<CheckoutSession> {
if (summary.cartExtended.length === 0) {
throw new BadRequestException('Your cart is empty');
}
if (summary.hasIssues) {
throw new BadRequestException('Resolve cart issues before paying');
}
const sessionId = randomUUID();
const invoice = await this.invoiceService.issueInvoice({
paymentMethod: PaymentMethod.Xmr,
reason: InvoiceReason.Checkout,
contextId: sessionId,
amountFiat: summary.cartTotalPrice
});
const lines = summary.cartExtended.map(line =>
this.lineRepo.create({
variantId: line.id,
productId: line.productId,
productTitle: line.productTitle,
variantTitle: line.title,
thumbnailUrl: line.thumbnailUrl,
qty: line.qty,
unitPriceFiat: line.price,
lineSubtotalFiat: line.lineSubtotal,
deliveryMode: line.deliveryMode
})
);
const discounts = summary.discounts
.filter(d => d.amount !== null && d.amount > 0 && !d.issueMessage)
.map(d =>
this.discountRepo.create({
code: d.code,
amountFiat: d.amount!
})
);
const session = this.sessionRepo.create({
id: sessionId,
invoice,
lines,
discounts
});
return this.sessionRepo.save(session);
}
async cancelSession(sessionId: string): Promise<void> {
const session = await this.sessionRepo.findOne({
where: { id: sessionId },
relations: ['invoice', 'invoice.moneroDetails', 'invoice.payments']
});
if (!session) {
return;
}
const { isOpenForPayment } = deriveCheckoutSessionState(session);
if (!isOpenForPayment) {
return;
}
await this.sessionRepo.update(session.id, { cancelledAt: new Date() });
}
}