[TIL] 프론트에서 PortOne(아임포트) 결제 연동하기

Leesu·2025년 12월 31일

[TIL] : Today I Learned

목록 보기
26/26

0. 개요

최근 사이드프로젝트에서 구독 서비스를 개발하면서 결제 수단 등록 기능을 구현했다.
단순해 보이는 기능이지만, 실제로는 본인인증, 결제 수단별 차별화 로직, 사용자 경험 등을 고려해야 하는 복잡한 시스템이었다.
이번 글에서는 Next.js + TypeScript 환경에서 PortOne(구 아임포트)을 활용해 구현한 useRegisterPaymentMethod 훅의 개발 과정을 공유하고자 한다.

참고로 본인인증 및 결제수단 등록 등 전체적인 방법은 🚩포트원 공식문서 여기를 보면 된다.

1. 구현해야되는 내용

  • 마이페이지에서의 카드 및 카카오페이 결제 수단 등록 기능
  • 본인인증 연동 (카드 결제만)
  • 결제 수단별 처리 로직을 가진 공용 훅

2. 핵심 아키텍처

1) 인터페이스 설계

interface RegisterPaymentMethodParams {
  paymentMethod: PaymentMethod; // 'CARD' | 'KAKAO_PAY'
  billingParams: BillingRequestParams;
  isFromMyPage?: boolean; // 마이페이지 호출 여부
}

isFromMyPage 플래그를 통해 호출 컨텍스트를 구분하여 리다이렉트 URL을 다르게 처리할 수 있게 했다.
이 훅은 마이페이지에서 뿐만 아니라 결제 페이지에서도 사용될 수 있기 때문이다.

2) 결제 플로우 및 수단별 차별화

본인인증 -> 서버 검증 -> 빌링키 생성 -> 빌링키 정보 반환으로 플로우가 진행되는데
카카오페이는 본인인증을 하지 않으므로 건너뛰었다.

// 1. 본인인증 (카드결제인 경우만)
if (paymentMethod === 'CARD') {
  const identityRes = await requestIdentityVerification(isFromMyPage);
  
  if (!identityRes) {
    throw new Error('본인인증에 실패하였습니다');
  }
  
  identityVerificationId = identityRes.identityVerificationId;
}
// 카카오페이는 본인인증 건너뜀
...

// 2. 서버 검증(카드결제인 경우만)

 if (paymentMethod === 'CARD') {
          verifyData = await verifyPayment({
            ...
          });
        }
                                           
// 3. 빌링키 생성

const billingKeyRes = await requestBillingKey(paymentMethod, {
          ...billingParams,
          ...(verifyData && {
            customer: {
              customerId:...,
              fullName: ...,
              phoneNumber: ...,
              email: ...,
            },
          }),
        });

// 4. 성공 시 빌링키 정보 반환!!
return { ... }

카드 결제: 본인인증 → 서버 검증 → 빌링키 생성
카카오페이: 본인인증 건너뜀 → 빌링키 생성

빌링키(Billing Key)란?
고객의 카드번호, 유효기간 등 민감한 결제 정보를 암호화하여 만든 고유 식별 값으로, 구독 서비스나 자동 결제 시 매번 정보를 입력하지 않고도 가맹점이 고객의 동의 하에 안전하게 재결제할 수 있도록 하는 '결제용 암호화 키'입니다.

우리는 KG이니시스 통합본인인증 방법을 사용했고, 방법은 포트원 개발자센터에 자세히 나와있다.

3) React Query + 상태 관리

const billingKeyMutation = useMutation({
  mutationFn: async ({ paymentMethod, billingParams, isFromMyPage }) => {
    setIsProcessing(true);
    
    try {
      // 결제 수단 등록 로직
    } finally {
      setIsProcessing(false);
    }
  },
  onSuccess: (data) => {
    const methodText = data.paymentMethod === 'CARD' ? '카드' : '카카오페이';
    openAlert('alert', { message: `${methodText} 결제 수단이 성공적으로 등록되었습니다.` });
  },
  onError: (error: Error) => {
    openAlert('error', { message: error.message || '결제 수단 등록에 실패했습니다.' });
  },
});

React Query의 useMutation을 활용해 비동기 처리와 로딩 상태를 자동으로 관리했따.

3. 실제 UI 연동

export const PaymentMethod = () => {
  const { registerPaymentMethod } = useRegisterPaymentMethod();
  
  const handleAddCard = () => {
    registerPaymentMethod({
      paymentMethod: 'CARD',
      billingParams: {
        finalPrice: 0,
        planName: '카드 등록',
      },
      isFromMyPage: true,
    });
  };

  return (
    <TextButton
      size='sm'
      label='추가하기'
      iconLeft={<Plus />}
      onClick={handleAddCard}
    />
  );
};

UI 컴포넌트에서는 단순히 registerPaymentMethod 함수를 호출하기만 하면 됐다.

4. 문제점과 해결책

1) 모바일 환경에서의 본인인증 리다이렉트 문제

문제 상황

모바일에서 본인인증을 진행할 때 다음과 같은 플로우가 발생하는데,
1. 사용자가 결제 수단 등록 시작
2. 본인인증 페이지로 리다이렉트 (외부 앱 또는 웹뷰)
3. 인증 완료 후 우리 서비스로 다시 돌아옴
4. 이때 기존 결제 진행 상황이 초기화되는 문제

초기 구현의 한계

제가 처음 구현한 useRegisterPaymentMethod에서는 이 재개 로직이 완벽하게 처리되지 않았다.
이 부분을 고려하지 못한 내 잘못이다..

// 초기 구현 - 재개 로직 부족
const billingKeyMutation = useMutation({
  mutationFn: async ({ paymentMethod, billingParams, isFromMyPage }) => {
    // 본인인증은 처리했지만, 리다이렉트 후 재개 로직이 부족
    if (paymentMethod === 'CARD') {
      const identityRes = await requestIdentityVerification(isFromMyPage);
      // ... 
    }
  }
});

팀원의 개선 솔루션

후에 팀원이 리팩토링하면서 이 문제를 해결하기 위해 전용 리다이렉트 핸들러를 도입했고,
성공적으로 결제 수단 등록 로직이 진행될 수 있었다!

export const PaymentRedirectHandler = () => {
  const pathname = usePathname();

  // 현재 URL이 /mypage로 시작하는지 체크하여 isFromMyPage 전달
  const isFromMyPage = pathname.startsWith('/mypage');

  usePaymentResume({ isFromMyPage });

  return null;
};

이 컴포넌트는 다음과 같은 역할을 담당한다!

  • 자동 컨텍스트 감지: 현재 경로를 통해 마이페이지 여부 자동 판단
  • 결제 재개 로직 실행: usePaymentResume 훅을 통해 중단된 결제 프로세스 자동 재개
  • URL 기반 상태 복구: 리다이렉트 파라미터를 통한 결제 상태 복구

이를 통해 모바일 환경에서도 끊김 없는 결제 경험을 제공할 수 있게 되었음.

profile
3년차 FE 개발자의 메모장

0개의 댓글