init
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, type EntityManager } from 'typeorm';
|
||||
import { deriveInvoiceState } from '../../../utils/invoice/deriveInvoiceState';
|
||||
import { NotificationService } from '../../notifications/services/NotificationService';
|
||||
import { DeliveryMode } from '../../product/types/DeliveryMode';
|
||||
import { CheckoutSession } from '../../storefrontCheckout/entities/CheckoutSession';
|
||||
import { CheckoutSessionLine } from '../../storefrontCheckout/entities/CheckoutSessionLine';
|
||||
import { Order } from '../entities/Order';
|
||||
import { ManualLineFulfillmentStatus } from '../types/ManualLineFulfillmentStatus';
|
||||
import { OrderFailureReason } from '../types/OrderFailureReason';
|
||||
import type { PreparedStockClaim } from '../types/PreparedStockClaim';
|
||||
import { isPreparedDigitalStockClaim, isPreparedManualStockClaim } from '../utils/isPreparedStockClaim';
|
||||
import { OrderAccessTokenService } from './OrderAccessTokenService';
|
||||
import { OrderClaimService } from './OrderClaimService';
|
||||
|
||||
@Injectable()
|
||||
export class OrderCreationService {
|
||||
constructor(
|
||||
private readonly dataSource: DataSource,
|
||||
private readonly orderClaimService: OrderClaimService,
|
||||
private readonly accessTokenService: OrderAccessTokenService,
|
||||
private readonly notificationService: NotificationService
|
||||
) {}
|
||||
|
||||
async createFromPaidSession(sessionId: string): Promise<void> {
|
||||
const now = new Date();
|
||||
let createdOrderId: string | null = null;
|
||||
|
||||
await this.dataSource.transaction(async manager => {
|
||||
const sessionRepo = manager.getRepository(CheckoutSession);
|
||||
const orderRepo = manager.getRepository(Order);
|
||||
|
||||
const session = await sessionRepo
|
||||
.createQueryBuilder('session')
|
||||
.leftJoinAndSelect('session.lines', 'line')
|
||||
.leftJoinAndSelect('session.discounts', 'discount')
|
||||
.leftJoinAndSelect('session.invoice', 'invoice')
|
||||
.leftJoinAndSelect('invoice.moneroDetails', 'moneroDetails')
|
||||
.leftJoinAndSelect('invoice.payments', 'payment')
|
||||
.leftJoin('session.order', 'order')
|
||||
.where('session.id = :sessionId', { sessionId })
|
||||
.andWhere('session.cancelledAt IS NULL')
|
||||
.andWhere('order.id IS NULL')
|
||||
.andWhere('invoice.expiresAt > :now', { now })
|
||||
.setLock('pessimistic_write', undefined, ['session'])
|
||||
.getOne();
|
||||
|
||||
if (!session || !session.invoice) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isPaidSufficient } = deriveInvoiceState(session.invoice);
|
||||
|
||||
if (!isPaidSufficient) {
|
||||
return;
|
||||
}
|
||||
|
||||
const claimResult = await this.orderClaimService.claimFromSession(manager, session);
|
||||
|
||||
const failureReason = claimResult.success ? null : claimResult.failureReason;
|
||||
const stockClaims = claimResult.success ? claimResult.stockClaims : [];
|
||||
|
||||
const { lookup, encrypted } = this.accessTokenService.generate();
|
||||
|
||||
const order = this.buildOrderFromSession(
|
||||
manager,
|
||||
session,
|
||||
{
|
||||
accessTokenLookup: lookup,
|
||||
accessToken: encrypted,
|
||||
failureReason
|
||||
},
|
||||
stockClaims
|
||||
);
|
||||
|
||||
await orderRepo.save(order);
|
||||
|
||||
createdOrderId = order.id;
|
||||
});
|
||||
|
||||
if (createdOrderId) {
|
||||
this.notificationService.sendNotification(createdOrderId, 'newOrder');
|
||||
}
|
||||
}
|
||||
|
||||
private buildOrderFromSession(
|
||||
manager: EntityManager,
|
||||
session: CheckoutSession,
|
||||
{
|
||||
accessTokenLookup,
|
||||
accessToken,
|
||||
failureReason
|
||||
}: {
|
||||
accessTokenLookup: string;
|
||||
accessToken: string;
|
||||
failureReason: OrderFailureReason | null;
|
||||
},
|
||||
stockClaims: PreparedStockClaim[] = []
|
||||
): Order {
|
||||
const orderRepo = manager.getRepository(Order);
|
||||
const stockClaimsByLineId = new Map(stockClaims.map(claim => [claim.checkoutSessionLineId, claim]));
|
||||
|
||||
return orderRepo.create({
|
||||
accessTokenLookup,
|
||||
accessToken,
|
||||
failureReason,
|
||||
checkoutSession: { id: session.id },
|
||||
checkoutInvoice: { id: session.invoice.id },
|
||||
discounts: (session.discounts ?? []).map(discount => ({
|
||||
code: discount.code,
|
||||
amountFiat: discount.amountFiat
|
||||
})),
|
||||
lines: (session.lines ?? []).map(line => this.mapCheckoutLine(line, stockClaimsByLineId))
|
||||
});
|
||||
}
|
||||
|
||||
private mapCheckoutLine(source: CheckoutSessionLine, stockClaimsByLineId: Map<string, PreparedStockClaim>) {
|
||||
const claim = stockClaimsByLineId.get(source.id);
|
||||
|
||||
const isManualStockClaim =
|
||||
source.deliveryMode === DeliveryMode.Manual && claim && isPreparedManualStockClaim(claim);
|
||||
|
||||
const isDigitalStockClaim =
|
||||
source.deliveryMode === DeliveryMode.Auto && claim && isPreparedDigitalStockClaim(claim);
|
||||
|
||||
return {
|
||||
variantId: source.variantId,
|
||||
productId: source.productId,
|
||||
productTitle: source.productTitle,
|
||||
variantTitle: source.variantTitle,
|
||||
thumbnailUrl: source.thumbnailUrl,
|
||||
qty: source.qty,
|
||||
unitPriceFiat: source.unitPriceFiat,
|
||||
lineSubtotalFiat: source.lineSubtotalFiat,
|
||||
deliveryMode: source.deliveryMode,
|
||||
...(isManualStockClaim
|
||||
? {
|
||||
manualFulfillment: {
|
||||
status: ManualLineFulfillmentStatus.Pending
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(isDigitalStockClaim
|
||||
? {
|
||||
autoFulfillmentItems: claim.items.map((item, index) => ({
|
||||
sortOrder: index,
|
||||
contentSnapshot: item.content,
|
||||
sourceDigitalStockItemId: item.id,
|
||||
attachments: (item.attachments ?? []).map(attachment => ({
|
||||
storageKey: attachment.storageKey,
|
||||
sourceDigitalStockAttachmentId: attachment.id,
|
||||
originalFilename: attachment.originalFilename,
|
||||
mimeType: attachment.mimeType,
|
||||
sizeBytes: attachment.sizeBytes
|
||||
}))
|
||||
}))
|
||||
}
|
||||
: {})
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user