[항해99 취업 리부트 코스] 프로젝트 3주차 WIL

김가희·2024년 5월 9일
0

- 이번 주 항해 취업 리부트코스에서 내가 구현한 기능은 무엇인가요?
커머스라는 주제를 가지고 프로젝트를 하게 되었다. 나는 그 중 festa! 같은 컨퍼런스 및 이벤트 티켓 예약 플랫폼을 만들기로 하였다.

  • 이벤트 결제 (포트원 SDK 활용)
  • 프로필 편집
  • 참여자 - 이벤트 참여 내역 조회
  • 참여자 - 구매 취소 기능
  • 주최자 - 이벤트 별 주문 내역 조회 기능
  • 주최자 - 주문 상태 업데이트 기능 (예: 배송 준비, 배송 중, 배송 완료)

- 이번 주 겪은 트러블 슈팅이 있다면 무엇인가요?

  • 포트원 결제 SDK, 트랜잭션 처리

요구 사항
구매자가 장바구니에 담은 상품으로 구매를 진행할 수 있게 해주세요.

  • 구매자가 구입을 진행하고 결제 단계로 넘어가기전 상품에 제고를 우선적으로 감소시켜주세요.
  • 상품 구매 직전에 실제 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 #취리코 #취업리부트코스

0개의 댓글