- 이번 주 항해 취업 리부트코스에서 내가 구현한 기능은 무엇인가요?
커머스
라는 주제를 가지고 프로젝트를 하게 되었다. 나는 그 중 festa! 같은 컨퍼런스 및 이벤트 티켓 예약 플랫폼을 만들기로 하였다.
참여자
- 이벤트 참여 내역 조회참여자
- 구매 취소 기능주최자
- 이벤트 별 주문 내역 조회 기능주최자
- 주문 상태 업데이트 기능 (예: 배송 준비, 배송 중, 배송 완료)- 이번 주 겪은 트러블 슈팅이 있다면 무엇인가요?
요구 사항
구매자가 장바구니에 담은 상품으로 구매를 진행할 수 있게 해주세요.
- 구매자가 구입을 진행하고 결제 단계로 넘어가기전 상품에 제고를 우선적으로 감소시켜주세요.
- 상품 구매 직전에 실제 DB 데이터에 수량 변경 사항을 다시 한번 확인해주세요.
- 만약 결제가 이루어지기 전에 결제가 취소된다면 상품 제고를 복구 시켜주세요.
결제 프로세스 중에 발생할 수 있는 여러 시나리오를 고려하여 트랜잭션을 적절히 관리하는 것이 중요하다.
Firestore에서 제공하는 트랜잭션 기능을 사용하여 결제 전 수량 확인 및 업데이트를 원자적으로 처리하고, 결제가 실패했을 경우 티켓 수량을 안전하게 복구할 수 있도록 할 수 있다.
트랜잭션 내에서 모든 데이터 업데이트는 원자적으로 수행되어야 한다. 즉, 트랜잭션이 성공하면 모든 변경 사항이 반영되고, 실패하면 아무것도 반영되지 않는다.
PortOne.requestPayment
는 실제 결제를 요청하는 부분으로, 결제 요청이 실패할 경우 트랜잭션이 자동으로 롤백되도록 예외를 발생시켜야 한다.
export const requestPayment = async (data: {
user: UserType | null;
tickets: CartItemType[];
payment: PaymentFormValues;
totalPrice: number;
}) => {
const { user, tickets, payment, totalPrice } = data;
try {
await runTransaction(db, async transaction => {
const ticketUpdates = [];
for (const ticket of tickets) {
const eventRef = doc(db, 'events', ticket.eventId);
const eventSnap = await transaction.get(eventRef);
if (!eventSnap.exists()) {
throw new Error(`[${ticket.eventId}] 이벤트가 존재하지 않습니다.`);
}
const eventData = eventSnap.data();
const ticketOption = eventData.ticketOptions.find(
(option: TicketType) => option.id === ticket.ticketId,
);
const availableCount =
ticketOption.scheduledCount - ticketOption.soldCount;
if (ticket.quantity > availableCount) {
throw new Error(
`[${ticket.eventName}] - [${ticket.name}] 티켓이 모두 판매되었습니다.`,
);
}
ticketUpdates.push({
eventRef,
updatedTicketOptions: eventData.ticketOptions.map(
(option: TicketType) => {
if (option.id === ticket.ticketId) {
return {
...option,
soldCount: option.soldCount + ticket.quantity,
};
}
return option;
},
),
});
}
ticketUpdates.forEach(({ eventRef, updatedTicketOptions }) => {
transaction.update(eventRef, { ticketOptions: updatedTicketOptions });
});
if (totalPrice > 0) {
const response = await PortOne.requestPayment({
storeId: import.meta.env.VITE_PORTONE_STORE_ID,
channelKey: import.meta.env.VITE_PORTONE_CHANNEL_KEY,
paymentId: `payment-${crypto.randomUUID()}`,
orderName: 'EVENTRIX',
totalAmount: totalPrice,
currency: 'CURRENCY_KRW',
payMethod: 'CARD',
});
if (response!.code) {
throw new Error(response?.message);
}
}
// 결제 성공 후 구매 정보 기록
await recordPurchase(user!, tickets, payment, totalPrice);
});
return { success: true };
} catch (error) {
if (error instanceof Error) {
return { success: false, error: error.message };
} else {
return { success: false, error: '오류가 발생했습니다.' };
}
}
};
열심히 결제 테스트 한 흔적 ㅋㅋ
항해99 취업 리부트 코스를 수강하고 작성한 콘텐츠 입니다.
#개발자포트폴리오 #개발자이력서 #개발자취업 #개발자취준 #코딩테스트 #항해99 #취리코 #취업리부트코스