10월 쯤 부터 대략 2개월 정도 작업 했던 부분을 간략히 정리 해보고자 한다.
결제 연동 ?
대부분 결제 연동을 모르셨다.
나도 몰랐다
주문과 결제는 분리 되어 있다.
그리고 토스 페이먼츠의 위젯은 기존 보다 1단계를 더 추가 했다.
- 결제 처리 전 필요한 인증과 결제 승인 처리가 분리 되었다.
- 카드 결제의 경우 입력된 정보를 카드사에 요청해서 인증을 먼저 거친다.
- 분리가 안 되어 있다면, 카드 인증 단계에서 에러 발생시 결제 후 연결되는 주문 처리, 상품 지급 처리, 회원 관련 처리 등이 함께 묶여 있게 된다.
- 분리로 인한 이점은 여러 try 가 가능 해진다.
주문 → 결제 요청 → 결제
결제 연동은 뭘까 ?
주문을 했다고 해서 모든 주문들이 바로 결제로 연결 되지 않는다.
결제 시점에 사용자는 주문 정보를 삭제하거나 변경 할 수 있다.
무언가 기본적으로 제공되지 않을까 싶은 주문의 삭제, 변경은 회사 주문 서비스별로 달라질 수 있다.
- 주문 정보를 일회성으로 둔다면 ?
- 주문과 결제 처리가 정말 일관된 작업 프로세스로 있는 서비스는 의외로 없을 확률이 높다.
주제, 목표 보다 분석이 중요하다.
(이건 내 개인적인 생각이다. -_-)
끝까지 혼란이 많았다.
지나고 보니 분석해 두라고 봤던 코드는, 의미가 없었고,
중요하다 생각해서 분석 시작한 코드는 안 봐도 된다거나 등 (정말 중요해보여서 걍 봤다)
그리고 토스 결제 연동과 서비스 비즈니스 로직, 시간 내 해결하기 위한 전체 로직의 변경 등
새롭게 추가하고자 했던 프로토콜 관련 검증 로직은 쓰이지 않게 되었다.
- 설정에 지정된 값들을 정규식으로 그룹핑으로 묶어 스프링 초기화 단계에 검증 패턴을 생성시켜서, 호출 시점마다 검증되게 했다.
- 안 쓰이는 코드 여기 올려도 될까요, 안 될까요
- 쨌든, 당시 작업에만 집중했는데, 돌아보고 싶던 내용들을 복습하면서 개인적으로 개선점을 찾으면 좋겠다
분석의 중요점은 작업하면서 계속 느꼈던 점 이다.
정리한 글은 보편적으로 고려할 점들을 적어 본다.
결제 처리 하기 전, 결제 연동 작업에서 중요한 것
검증
- 주문 정보 검증
- 회사 상품 가격이 시세정보가 반영 됐는지 나중에 알면서 해당 부분을 추가 작업 했다.
- 이미 결제 처리가 안 된 주문 이어야 한다
- 주문의 상태값이 어떻게 정의 되어 있는가
- 결제 상태값이 어떻게 정의 되어 있는가
- 가격 검증
- 서비스 별 상품에 대한 가격 정의는 어떻게 되어 있는가
- 할인 정책은 어떻게 지원 되는가
가격 검증이 레거시 코드로 인 해, 하나의 역할로 묶어내기 위한 지구력이 필요 했었다.
레거시가 왜 불편 했을까?
- 할인 정책에 대한 고유값을 공통으로 쓰지 않는다
- 이건 할인 계산 인가? 아니었다 -_-
- 가격이 맞지 않습니다. 나중에 알 거예요. 일단 작업 하세요.
- 정말 맞지 않습니다.... 3주 지연 됐다 ㅠ (먼저 정리해서 보여드렸어야 했는데, 나도 혼동되는 점이 많다보니 아쉬웠다)
여러 할인 계산 로직들이 있었고, 하나의 메서드 로직이 답이었다.
하지만 이는 나중에 결제 처리시 할인 검증 로직에서 또 다른 부분이 있으면서, 끝까지 신경 쓰이게 했다.
- 틀리더라도 일단 하나로 개념화 하고자 했다.
- 고치기 쉽다 : 잘못된 결과 확률이 높아지면서, ...이런 이유로 추상화 작업을 ㅎㅎㅎ
- 결제 요청 전 가격 검증과 결제 처리시 가격 검증 로직이 다른점이 생겼는데, abstract factory pattern ? 생각하면서 작업 했다.
- 이는 전체 로직이 파악 되고, 토스 페이먼츠 결제 처리 로직이 정리 되고서야 작업 할 수 있었다.
- 작은 Bigdecimal 반환 처리 코드 실수로 에러가 있었지만, 로직 아닌 포인트 에러사항이라는 문제점에 대해, 빠르게 찾고 수정 가능 했던 점에서 괜찮게 한 거 같단 생각이 들긴 했다.
- 중요한 건 각 호출 시점 별 필요한 추상체 calculator 와 validator 각각을 연결하고 불러오는 것이 었는데, 이는 내가 아니라 다른 개발자 분이 같은 추상 타입이어도 다른 로직이라는 점을 알 수 있게 해야 한다는 생각 이었다.
- 별도의 서비스 layer 추가하여 DI 로 작업 했다
... 쨌든 레거시 코드의 절대적이었던, 모순
자세히 보아야 예쁘다
오래 보아야 사랑 스럽다
너도 그렇다
"자세히 보아야 예쁘다", 나태주
(끝내 슬픈 건 나도 레거시)
결제 요청
다시 적어 본다
주제, 목표 보다 분석이 중요하다.
결제란?
가격만 결제 처리 하면 되는가?
- 회사에서 제공 되는 모든 서비스가 지급 적용 된다.
- 회사에서 제공하는 회원 서비스도 지급 적용 된다.
- 회사에서 필요한 수익, 세금, 정산과 연결 된다.
- 결제 처리는 회사에서 제공하는 서비스/상품에 대한 로직으로 동작 해야 한다.
- etc...
나는 토스 결제 처리 부분만 작업 했다.
어떤 서비스 단위 결제작업만 하면 된다 하셨지만,
해당 서비스는 고객 관점에서만 정의된 개념이고, 회사 도메인이 정의되거나 로직에 정의된 서비스가 아니었다.
또 다시 적어본다
레거시가 왜 불편 했을까?
왜 고객에게 제공되는 해당 서비스만 작업 할 수가 없었을까 ?
정의된 도메인이란 무엇인가 ?
먼저, 회사 내에서도 상품 서비스 정의에 대한 문제점이 있어 개선 작업 중이었다.
나는 작은 부분이었지만, 상품의 일부를 고객 서비스화 했달까?
이게 레거시 코드에서는 오래전에 사라진 할인관련 정책인지, 상품 정의상 기본으로 제공되는 정책인지 모르는 절차지향적 코드와 기존 이용중이던 PG 처리 파라미터 의도된 절차지향적 코드는 ....
- 의미를 아는 사람이 없었다 -_- (작업자는 퇴사, 프로시저는 진짜 ... ㅎㅎ)
절차지향적 코드와 결제?
예를 들어 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태라면, 결제 처리에서 어떻게 동작할까?
-
아무것도 작업 할 게 없다? ㅎㅎㅎ
- 일정 기간 동안 일정양의 서비스를 사용할 수 있는 이용권을 구입 했다면, 해당 이용권 이용한 주문 상품에 대한 지급 적용이 있다면?
- 이게... 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태로 연결 되어 있었다.
- 절차지향적 코드는 뒤에 이어지는 할인이나 포인트로 완전 결제 처리 되는 경우와 실제 결제 처리지 지급/적용 되는 경우, 포인트 지급까지 묶여 있어서 무통장 입금하지 않은 가상계좌 번호를 할당 받은 상태와의 연관성은 .....
-
디버깅이 안 됐다 ㅎㅎㅎ
-
내가 임의로 넣고 빼고 할 로직이 아니었다
-
테스트도 안 되고 디버깅도 안 되고
-
기존 결제 방식의 파라미터 값 의존한 로직은 토스와도 달랐고, 의미 확신은 더욱...
-
결제 파라미터가 앞단에서 별도로 무통장, 카드 결제처리 후 전달 되는 경우란 걸 나중에야 파악 됐었다 ㅠ
기존 코드는 디버깅이나 테스트가 불가 했지만,
내가 작업한 API로 되면서 테스트와 디버깅이 가능 해졌으나
나는 ...지금 생각하면 그냥 복붙하지 왜 그랬는가.....
- 토스 결제 승인 처리
- 토스 결제 결과(결제방식)에 따른 결제 처리
- 서비스 지급/적용
- 회원 관련 포인트 등 지급/적용
위 로직을 분리 했다.
그리고 될까 싶은 추상화가 가능 해졌다
마지막 날 내 지저분한 코드로 마무리를 지었지만 ㅠ ㅠ (너무 싫다)
처음에는 디자인 패턴을 생각하고 작업 했었는데,
점차 조용호님의 오브젝트 뒷 챕터 코드와 유사하게 갔었다 (CH. 14 ?)
- condition
- 크게 2가지로 분리된다.
- 토스 결제 방식에 따른 조건 아니면, 회사 제공 서비스 지원 조건 (레거시 기반이다 보니 .. 이는 수정 되더라도 별도의 클래스로 분리되어서 수월 해질 수 있게 하는게 중요해졌다)
- process
- 이게 좀 생각처럼 안 됐었는 데, 예시로 정리 해 본다
예로, 무통장 가상계좌 할당 condition, 무통장 입급 condition, 카드결제 condition 일 경우
| 결제 처리 | 서비스 지급/적용 |
---|
무통장 가상계좌 할당 condition | ❌ | ⭕ |
무통장 입급 condition | ⭕ | ⭕ |
카드결제 condition | ⭕ | ⭕ |
-
결제 처리는 모두 동일 한가 ? 아뇽
- 무통장 입금은 PG 사 저장, 별도 무통장 관리 테이블 저장, 주문과 결제 상태 처리 등
- 카드 결제 처리는 PG 사 저장 (무통장 처럼 별도 테이블이 없을 수 있다), 주문과 결제 상태 처리 등
처리 로직이 겹치는 게 있으면서 달라진다.
설계된 하나의 process로 어떻게 각각 다른 필요 인자값들을 전달할 것인가가 난관 이었는데 ,
- Process 구현 타입들을 각각 DI
- 정보 전문가
- command
처음에는 이렇게 가야 할 거 같은데, 구체적으로 정리되지 않은 것들이 점점 그 방향으로 가고 있어서 오히려 걱정이 됐었다.
사실 난 여기서 지급/적용 로직까지 연결 시켜서
결제 실패나 중간 API 호출시 실패 등에 대한 트랜잭션 롤백 처리에 대해 각 서비스별로 동작하게 하자는 생각 이었다. (독립 보단....)
이를 위해서 2가지 준비작업이 필요 했다.
- API 호출시 에러 정의와 추상화
- 에러 타입과 파라미터에 의한 원복
- 아니면, 2PC, 사가 패턴 등 있다
안정성, 정확성
재시도
결제시 에러 등으로 인한 고객 이탈률을 고려하게 되었다.
처음에는 모듈화 등 외부 호출을 재시도 하는 것에만 초점을 맞췄는데,
- 호출하는 모듈의 지속적인 에러 발생으로 인한 번복되는 재시도 호출
- 외부 서비스 장애로 인한 지연 현상과 지속된 호출
재시도 호출은 위험했다.
- 실패 케이스 세분화가 중요하다
- 시스템 전체 설정을 손대기 버거울 때 메서드 별로 재시도 해주는 Spring-retry 가 좋아 보였다.
- 예외 클래스와 전달 매개변수 기반 동작
- 에러 발생 건에 대한 세부적인 예외 클래스 정의로 retry 케이스를 구축 할 수 있다
- 매개변수를 공통화 하면 좋다
- try-catch 문을 공통화
- Function 과 generic 기반으로 호출하는 여러 adapter 들을 공통으로 이용할 수 있게 했다.
- 특정 예외 발생 클래스만 이용하게 해서 Spring-retry 와 연결하고, try-catch 문 코드 간편성도 높이고자 했다.
가독성, 협업
코드 리뷰로 토스 구현체를 넣은 점을 추상화 해서 변경 영향 줄이는게 어떻냐는 의견이 있었다.
- 현 시스템은 여러 서비스 들 중 1개씩 단계별로 결제사 변경 중이다.
- 다른 팀의 개발자들은 어떤 서비스가 새 결제 방식을 이용하는지 모른다.
- 혹시 코드 타고 오더라도 빠르게 판단하게 하는게 좋다고 생각했다.
트레이드 오프로 인한 장점, 전략 등의 차이점을 느꼈었다.
나는 성능보다는 가독성, 협업을 중시 한다는 생각 이었다.
- 성능은 개인적으로 시도할 수 있는 영역내에서만 좋다고 생각된다. (역할과 책임 분리로 SRP 내에서 동작 등)
p.s 토스페이먼츠
토스페이먼츠
PG 사가 달라지면 ?
이런 부분은 미리 알면 먼저 정리해서 빠른 작업이 가능 했을 듯 싶다
- 은행/증권사 코드가 달라진다.
- 금융결제원 공식 코드가 있는데, 토스는 토스의 코드로 전달
- 다른 PG 사와 다르게 결제 상태값이 달라진다
- 기존 PG 사 연관된 코드, 타입 등 정리 필요
- 결제 상태 값에 따른 로직으로 연결되니, 정의가 명확하면 검증, 로직이 수월 해진다.
좀 씁쓸하다. 지루하고 ...
일단, 개인적으로 잘 채워나가며 한 해 마무리 하고자 한다
새로운 방향을 본 거면 된 거겠지.