본문 바로가기
회사

결제창 코드 리팩토링 후기

by 개발 어렵다. 2025. 7. 26.

문제 상황

현재 PG 회사에서 결제창 서비스를 담당하고 있다. 결제창은 쿠팡, 네이버 스토어 같은 온라인 쇼핑몰에서 카드결제, 간편결제 등을 통해 고객이 온라인 결제를 할 수 있도록 지원하는 핵심 서비스다. 돈과 직결된 서비스인 만큼 가맹점 서버와의 결제 데이터 정합성, 성능 최적화, 중복결제 방지 등을 매우 신중하게 고려해야 한다.

 

초기 결제창 코드는 극도로 복잡한 구조를 가지고 있었다. 결제수단마다 if~else 구문을 남발했고, 간편결제를 선택했을 때도 토스페이, 네이버페이를 구분하는 부분에서 또다시 if~else를 사용했다. 여기에 PC와 모바일 환경까지 구분하다 보니 3중, 4중 중첩된 if~else 문이 곳곳에 자리잡게 되었다.

 

이런 코드 구조는 심각한 문제를 야기했다. 새로운 팀원이 투입되면 코드 파악만으로도 상당한 시간이 소요되었고, 특정 결제수단에서 버그가 발생해 수정하려고 하면 다른 결제수단들과 강하게 결합되어 있어 연쇄적으로 추가 버그가 발생하는 악순환이 반복되었다.

 

이런 문제점들이 누적되면서 회사 내에서 결제창 리팩토링이 점차 화두로 떠올랐다. 그리고 수수료 절감을 위해 직계약 결제수단이 늘어나면서, 그동안 말로만 하던 리팩토링 작업이 드디어 정식 프로젝트로 승인받게 되었다.

 

즉 정리하면 아래와 같은 문제가 발생했다.

1. 너무 강한 커플링으로 인해 한 곳에서 버그를 수정하면 다른 부분에서 새로운 버그가 발생할 수 있는 상황

2. 새로운 결제수단을 추가할 때 기존 코드를 건드려야 해서 예상치 못한 문제가 생기는 상황

3. 결제수단을 하나 추가하더라도 기존의 모든 결제수단을 다시 테스트해야 하는 상황

4. 점점 늘어나는 직계약 결제수단으로 인해 위의 모든 문제들이 반복적으로 심화되는 상황


결제창 프로세스


리팩토링

추상화를 진행하기 전에 먼저 공통된 행동을 정의하고, 결제수단별로 구현체를 추가할 수 있도록 추상화를 진행했다. 추가적으로 BC, KB, 토스페이, 카카오페이 등의 서비스 코드를 ENUM으로 정의했다.

 

 

 

PaymentService 인터페이스를 만들고 각각의 결제수단인 카드결제(CardPaymentService), 간편결제(EasyPayPaymentService) 클래스에서 이를 상속받아 구현하도록 설계했다.

 

결제수단에 대한 클래스는 정의했지만, 여전히 해결해야 할 문제가 있었다. 카드사 앱, 토스페이, 카카오페이 등 각 서비스별로 파라미터를 세팅하는 구현체가 필요했던 것이다. 이러한 구현체들은 서비스마다 서로 다른 행동 전략을 가져야 하므로, Strategy 패턴과 Factory 패턴을 적용하여 해결했다.

 

CardPaymentFactory

public class CardPaymentFactory {

    private final Map<CardPayment, CardPaymentStrategy> strategyMap = new HashMap<>();

    @AutoWired
    public CardPaymentFactory(BcCardPaymentStrategy bcCardPaymentStrategy, KbCardPaymentStrategy kbCardPaymentStrategy, ...) {
        strategyMap.put(CardPayment.BC, bcCardPaymentStrategy);
        strategyMap.put(CardPayment.KB, kbCardPaymentStrategy);
        ...
    }

    public CardPaymentStrategy getStrategy(CardPayment cardPayment) {
        final CardPaymentStrategy cardPaymentStrategy = strategyMap.get(cardPayment);
        if (ObjectUtil.isNull(cardPaymentStrategy)) {
            throw new NotFoundClassException("...");
        } else {
            return cardPaymentStrategy;
        }
    }
}

 

CardPaymentService

public class CardPaymentService implements PaymentService {

    private final CardPaymentFactory cardPaymentFactory;

    public ModelAndView requestAuthentication(Authentication authentication) {
        final UserInput userInput = parseUserInput(authentication);
        // 결제수단 검증

        final CardPaymentStrategy cardPaymentStrategy = cardPaymentFactory.getStrategy(authentication.cardPaymentType());
        return cardPaymentStrategy.doRequestAuthentication(authentication);
    }
}

 

편의상 카드결제 쪽만 예제 코드를 작성했다. 클라이언트 코드를 보면 각 카드사의 구현체가 아닌 추상화된 클래스에 의존하게 되고, 이 추상화 클래스는 구체적인 구현체가 아니므로 새로운 결제수단이 추가되더라도 클라이언트 코드는 수정할 필요가 없다.


결과

결국 객체지향 원칙 중 OCP(Open-Closed Principle) 원칙을 적용한 결과, 개발 기간 단축과 테스트 비용 절감이라는 실질적인 효과를 얻을 수 있었다.

 

리팩토링을 진행한 후 새로운 직계약 간편결제 수단을 추가하면서 그 효과를 톡톡히 체감할 수 있었다. 기존 코드에서는 결제수단을 하나 추가할 때마다 개발과 테스트 기간을 합쳐 2~3개월이 소요되었다. 하지만 리팩토링 이후에는 개발 기간이 1주일로 대폭 단축되었고, 테스트 역시 새로 추가된 결제수단만 검증하면 되어 비용 절감 효과를 크게 느낄 수 있었다.

 

정리해보면 다음과 같은 장점을 얻었다.

  • 개발 기간 단축: 2~3개월 → 1주일로 획기적 단축
  • 버그 발생 가능성 감소: 기존 코드 수정 없이 독립적인 구현체 추가
  • 테스트 비용 절감: 전체 결제수단 재테스트 불필요, 신규 수단만 테스트
  • 버그 수정 시간 단축: 격리된 구조로 인한 빠른 문제 해결