연락처 송금 비회원 주문 누락
목차
연락처 송금은 구조 자체가 좀 특이하다. 은행 앱 화면을 Playwright로 직접 제어해서 입금 완료까지 자동으로 처리하는 방식인데, 이게 외부 API를 쓰는 게 아니라 실제 앱 UI를 긁어서 동작하는 구조다 보니 은행 앱 업데이트 한 번에 셀렉터가 통째로 날아가는 일이 종종 생긴다. 거기에 비회원 주문 매칭 누락까지 겹쳐서 이번에 한꺼번에 손을 봤다.
이번에 수정한 것들
수정 포인트는 네 군데였다.
- 은행 셀렉터 감지 로직 오류
- 계좌 로테이션 순서 문제
- 파싱 실패 후 재시도 필터 조건 누락
- 비회원 주문 매칭 누락
숫자로 보면 작아 보이는데, 각각 독립적인 버그라서 재현 흐름이 다 달랐다. 셀렉터 감지는 특정 은행 핸들러에서 DOM 구조가 바뀐 이후로 매칭이 안 됐고, 계좌 로테이션은 처리 순서가 뒤집혀서 한 계좌에 트래픽이 쏠리는 현상이 있었다. 파싱 실패 재시도 필터는 이미 MANUAL_REQUIRED로 빠진 건들이 재시도 큐에 다시 올라오는 문제였는데, 필터 조건에 상태값 체크가 빠져 있었다.
비회원 주문 누락이 제일 임팩트가 컸다. 사전 매칭 단계에서 회원 주문만 조회 대상에 포함돼 있었고, 비회원 주문은 아예 후보 풀에서 빠져 있었다. 입금은 처리됐는데 매칭이 안 되면 NO_MATCH로 빠지고 수동으로 올라오는데, 이게 비회원 주문 건마다 반복되고 있었던 거다.
완료 감지 쪽 정규식은 이번 기회에 통합 패턴으로 정리했다.
// 완료 감지 regex 통합 패턴
private static final Pattern COMPLETE_PATTERN =
Pattern.compile("(이체|송금|완료|처리됐|성공)");
근데 이 regex만으로 모든 케이스를 잡기는 어렵다. 은행마다 완료 메시지 문구가 제각각이고, 같은 은행이라도 버전에 따라 "거래가 완료됐어요", "이체 처리됐습니다", "성공적으로 송금됐어요" 처럼 표현이 조금씩 달라진다. 그래서 regex 매칭 실패 케이스는 Claude API에 화면 텍스트를 넘겨서 파싱 결과를 받는 AI 폴백 로직이 붙어 있다. 정규식은 빠르고 저렴하게 대부분을 처리하고, 애매한 건 LLM이 받아서 판단하는 구조다.
셀렉터 안정성을 어떻게 확보하나
은행 앱 UI는 앱 업데이트마다 바뀐다. 이건 어쩔 수 없는 전제다. 하드코딩된 셀렉터로만 버티면 앱 버전 하나 올라갈 때마다 핸들러가 죽는다. 현재 구조에서는 자가학습 캐시로 이 문제를 완화하고 있다.
동작 방식은 단순하다. 셀렉터가 성공하면 DB에 저장하고, 다음 요청에서 저장된 셀렉터를 먼저 시도한다. 실패하면 전체 탐색으로 폴백하고, 새로 찾아낸 셀렉터를 다시 저장한다. 이렇게 하면 앱이 업데이트돼도 첫 요청 한 번만 느리게 탐색하고, 이후부터는 캐시 히트로 빠르게 처리된다.
트레이드오프도 있다. 캐시에 저장된 셀렉터가 특정 버전에만 유효하고, 이전 버전으로 롤백하면 다시 탐색이 필요해진다. 캐시 유효성 검증 로직이 없으면 실패율이 올라가도 한동안 캐시를 계속 신뢰하는 문제가 생길 수 있다.
처리 상태는 아래 네 가지로 관리한다.
| 상태 | 의미 |
|---|---|
| PENDING | 처리 중 |
| SUCCESS | 완료 |
| NO_MATCH | 주문 매칭 실패 → 재시도 큐 |
| MANUAL_REQUIRED | 자동 처리 불가 → 수동 처리 |
NO_MATCH와 MANUAL_REQUIRED를 구분하는 게 중요하다. 재시도 필터 조건 누락 버그가 MANUAL_REQUIRED 건을 NO_MATCH처럼 다시 큐에 올리고 있었던 거라서, 이번 수정에서 상태값 기반 필터를 제대로 달았다. 이 두 상태를 같은 선상에 놓으면 수동 처리 대기 중인 건들이 계속 재시도를 타면서 중복 처리 위험이 생긴다.
이번 작업을 돌아보면
작업 규모 자체는 크지 않았다. 근데 비회원 주문 매칭 누락은 입금이 완료됐는데 처리로 이어지지 않는 케이스라서, 운영 임팩트는 규모 대비 꽤 있는 버그였다. 사전 매칭 쿼리를 짤 때 암묵적으로 회원 주문만 대상으로 가정한 게 원인인데, 이런 암묵적 가정이 코드 곳곳에 숨어 있으면 나중에 비슷한 케이스가 또 나온다.
Playwright로 실제 앱 UI를 제어하는 구조는 외부 의존성에 취약하다는 걸 다시 한번 체감했다. 같은 기능이라도 은행마다, 버전마다, 화면마다 제각각이라 핸들러 관리 비용이 계속 올라간다. 자가학습 캐시와 AI 폴백이 이 비용을 어느 정도 흡수해주고 있지만, 근본적으로 외부 앱 UI에 의존하는 구조의 한계는 남아 있다. 지금은 그 트레이드오프를 인지하면서 운영하는 상태다.
댓글 0
첫 댓글 달아줘.