TIL
좌석 예약 하기
용찬
2023. 9. 13. 22:30
전 글에서 좌석 예약을 받기 위해서 시간표를 만들어주었다.
이제 본격적으로 좌석 예약 및 결제를 위한 로직을 작성 할 시간이다.
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { TimeTable } from './timeTable.entity';
import { Seat } from './seat.entity';
import { Room } from './room.entity';
@Entity()
export class Reservation {
@PrimaryGeneratedColumn()
id: number;
@Column()
timeTableId: number;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
reservationTime: Date;
@Column({ type: 'boolean', default: false })
stats: boolean;
@Column()
seatId: number;
@Column()
userId: number;
@ManyToOne(() => TimeTable, (timeTable) => timeTable.reservations)
timeTable: TimeTable;
@ManyToOne(() => Seat, (seat) => seat.reservation)
seat: Seat;
@ManyToOne(() => Room, (room) => room.reservations) // Room 엔터티와 관계 설정
room: Room;
}
예약을 확인하기 위한 entity
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
OneToMany,
} from 'typeorm';
import { TimeTable } from './timeTable.entity';
import { User } from './user.entity';
@Entity({ schema: 'apple', name: 'payment' })
export class Payment {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'integer' })
points: number;
@CreateDateColumn()
paymentTime: Date;
@Column()
userId: number;
@OneToMany(() => TimeTable, (timeTable) => timeTable.payments)
timeTable: TimeTable;
@OneToMany(() => User, (user) => user.payments)
user: User;
}
결제 내용을 저장하기 위한 entity
async makeReservation(
timeTableIds: number[],
seatIds: number[],
userId : number
): Promise<void> {
const reservations: Reservation[] = [];
for (const timeTableId of timeTableIds) {
const timeTable = await this.timeTableRepository.findOne({
where: { timeTableId },
select: ['timeSlot'],
});
if (!timeTable) {
throw new Error(
`해당 시간표를 찾을 수 없습니다. (timeTableId: ${timeTableId})`,
);
}
for (const seatId of seatIds) {
const seat = await this.seatRepository.findOne({ where: { seatId } });
if (!seat) {
throw new Error(`해당 좌석을 찾을 수 없습니다. (seatId: ${seatId})`);
}
console.log(seat)
const existingReservation = await this.reservationRepository.findOne({
where: { timeTableId, seatId },
});
if (!existingReservation) {
const reservation = new Reservation();
reservation.timeTableId = timeTableId;
reservation.seatId = seatId;
await this.reservationRepository.save(reservation);
const paymentSuccessful = await this.processPayment(seat.price,userId); // 결제 처리 함수
if (paymentSuccessful) {
await this.reservationRepository.save(reservation);
// 예약 취소 타이머 설정 (예: 30분 후에 취소)
setTimeout(async () => {
// 예약 상태 확인
const updatedReservation =
await this.reservationRepository.findOne({
where: { timeTableId, seatId },
select: ['stats'], // state를 조회하도록 변경
});
if (updatedReservation && !updatedReservation.stats) {
// 이미 결제가 완료되었으면 취소하지 않음
return;
}
// 예약 취소 처리
updatedReservation.stats = true;
await this.deleteReservation(reservation.id); // 예시: 예약 정보 삭제 함수
}, 10 * 60 * 1000); // 10분 후에 실행 (밀리초 단위)
} else {
// 결제가 실패한 경우, 상태를 true로 변경하여 예약 취소
await this.reservationRepository.update(timeTableId, {
stats: true,
}); // state를 true로 변경
throw new Error(
'결제가 실패하여 예약이 취소되었습니다. 남은 포인트를 확인해주세요',
);
}
reservations.push(reservation);
} else {
throw new Error('해당 시간대에 예약할 수 없습니다.');
}
}
}
}
위 로직은 reservation에서 seatId와 timeTableId를 조회 후 두 가지 모두 해당하는 예약이 없으면 예약이 없음으로
예약이 있는지 확인 후 예약을 받고 혹시나 예약이 취소되는 경우 10분의 타임아웃을 설정해주었다.
private async processPayment(amount: number, userId:number): Promise<boolean> {
// const userId = 2; // userId 주입
console.log(userId)
const point = await this.pointRepository.findOne({ where: { userId } });
if (!point) {
throw new Error('포인트를 조회 할 수 없습니다.');
}
if (point.point < amount) {
return false;
}
const payment = new Payment();
payment.userId = userId;
payment.points = amount;
point.point -= amount;
await this.paymentRepository.save(payment);
await this.pointRepository.save(point);
return true;
}
// 스케줄러 메서드: 매일 자정에 실행
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async handleScheduledTask() {
try {
console.log('자정에 예약 정보가 모두 삭제됩니다.');
// 예약 정보 삭제
await this.deleteSchedule();
console.log('예약 정보 삭제가 완료되었습니다/');
} catch (error) {
console.error('예약 정보 삭제 중 error', error);
}
}
private async deleteSchedule(): Promise<void> {
// 예약 정보를 데이터베이스에서 삭제
const reservation = await this.reservationRepository.find();
if (!reservation) {
throw new Error('예약을 찾을 수 없습니다.');
}
await this.reservationRepository.remove(reservation);
}
}
위 로직은 makeReservation ( 예약 및 결제 ) 를 위해 호출하는 함수들이다.
예약은 db 및 여러가지 문제로 다음 날 예약만 받기로 했고
다음 날 예약을 받기 위해 db내 예약 정보를 삭제해주는 handelScheduledTask + deleteSchdule 과
선택한 예약 정보의 가격을 결제하기 위해 포인트를 조회하고 연산을 해주는 processPayment로 구성되어있다.