입금 확인을 컴퓨터에게 맡기기 — 틀리면 안 되는 자동화를 만든 기록
예약금 입금이 들어오면 홈페이지 예약을 자동으로 승인해주는 프로그램을 만들었어요. 돈이 걸린 자동화라 '빠른 것'보다 '안 틀리는 것'이 훨씬 중요했습니다. 알림을 읽고, 파싱하고, 매칭하는 세 단계를 어떻게 안전하게 짰는지 적어둡니다.
title: "입금 확인을 컴퓨터에게 맡기기 — 틀리면 안 되는 자동화를 만든 기록" date: "2026-06-22" description: "예약금 입금이 들어오면 홈페이지 예약을 자동으로 승인해주는 프로그램을 만들었어요. 돈이 걸린 자동화라 '빠른 것'보다 '안 틀리는 것'이 훨씬 중요했습니다. 알림을 읽고, 파싱하고, 매칭하는 세 단계를 어떻게 안전하게 짰는지 적어둡니다." tags: ["dev", "automation", "python", "자영업"] category: "dev"
제가 일하는 매장은 손님이 예약하실 때 예약금을 미리 받아요. 그리고 그 입금이 확인되면 홈페이지에서 예약을 '승인' 상태로 바꿔드려야 하고요. 지금까진 이걸 사람이 했어요. 입금 알림이 오면 보고, 이름·금액 맞춰보고, 홈페이지 들어가서 승인 버튼을 누르고.
별일 아닌 것 같지만 이게 하루에도 몇 번씩, 그것도 보통 입금 후 빨리 처리해야 하는 일이라 손이 꽤 갔어요. 그래서 자동화를 마음먹었는데, 시작하자마자 든 생각은 "이건 빠른 것보다 틀리지 않는 게 훨씬 중요하겠다"였어요. 돈과 예약이 걸린 일이라 엉뚱한 사람 예약을 승인해버리면 그게 더 큰 사고니까요.
오늘은 이 프로그램을 **세 단계(읽기 → 파싱 → 매칭)**로 나눠서, 각 단계마다 어떻게 "틀리지 않게" 짰는지 적어둘게요.
1단계 — 알림을 어떻게 '읽을' 것인가
자동화의 출발점은 "입금 알림 내용을 컴퓨터가 알아야 한다"였어요. 처음엔 화면을 캡처해서 글자를 인식(OCR)하는 방법을 떠올렸는데, OCR은 잘못 읽을 여지가 너무 많아요. 숫자 0을 O로 본다거나 하는 식으로요. 돈 다루는 일에 그건 위험하죠.
그러다 찾은 게 윈도우 알림 센터였어요. PC에 카카오톡 알림이 뜨면 그 텍스트가
윈도우 알림 센터에 그대로 쌓이거든요. 이걸 읽는 공식 API(UserNotificationListener)가
있어서, 그림이 아니라 글자 원본을 그대로 가져올 수 있었어요. OCR이 아예 필요 없어진
거죠.
# 알림 센터에 쌓인 토스트 알림을 텍스트 그대로 읽어온다 (OCR 불필요)
listener = UserNotificationListener.current
notes = await listener.get_notifications_async(NotificationKinds.TOAST)
for n in notes:
app = n.app_info.display_info.display_name # 어느 앱이 띄운 알림인지
text = extract_text(n.notification) # 제목 + 본문 텍스트
여기서 안전장치를 두 개 걸었어요.
- 앱 필터: 카카오톡에서 온 알림만 본다. 다른 앱 알림은 쳐다보지도 않아요.
- 키워드 필터: "입금" / "송금" 같은 단어가 든 알림만 처리한다. 개인 대화는 애초에 서버로 보내지 않아요. (프라이버시 + 오작동 방지, 둘 다요.)
내 개인 카톡 내용이 새어나갈 걱정 없이, 딱 입금 알림만 걸러내게 한 거예요.
2단계 — 파싱: 제일 애먹은 부분
읽어온 알림 텍스트에서 누가 얼마를 보냈는지를 뽑아내는 단계예요. 그리고 솔직히 이 프로그램에서 제일 고생한 게 여기였어요.
문제는 알림 형식이 한 가지가 아니라는 거예요. 예를 들면 이런 식으로 조금씩 달라요.
입금 50,000원 홍길동 → 입출금통장(5207) 잔액 123,456원
홍길동님이 50,000원을 입금했습니다
홍길동 50,000원 입금
사람 눈엔 다 똑같이 "홍길동, 5만원"이지만, 컴퓨터한테는 어디까지가 이름이고 어디부터가 금액인지 일일이 알려줘야 해요. 그래서 여러 형식에 맞는 패턴을 순서대로 시도하게 만들었어요.
# 형식이 다른 경우들을 순서대로 시도. 위에서부터 맞는 게 나오면 사용.
PATTERNS = [
r"(?:입금|송금)\s*([\d,]+)\s*원.*?([가-힣]{2,10})\s*[→▶>]", # "입금 N원 ... 이름 →"
r"([가-힣]{2,10})\s*님(?:이)?\s*([\d,]+)\s*원.*?입금", # "이름님이 N원 입금"
r"([가-힣]{2,10})\s+([\d,]+)\s*원\s*입금", # "이름 N원 입금"
]
그런데 여기서 제일 중요한 결정이 하나 있었어요. 확실하지 않으면 차라리 처리하지 않는다는 거예요. 패턴이 하나도 안 맞으면 억지로 추측해서 뽑는 게 아니라, 그냥 "못 읽었다"하고 아무것도 하지 않게 했어요.
def parse_deposit(text):
for pattern in PATTERNS:
m = re.search(pattern, text)
if m:
return {"name": m.group(name_group), "amount": ...}
return None # ← 애매하면 추측하지 말고 그냥 포기. 이게 핵심.
자동 승인 흐름에서 잘못 읽은 이름·금액으로 매칭이 일어나면 그게 사고예요. 그래서 "읽을 수 있는 것만 확실하게, 나머지는 사람이 보게" 쪽으로 정했어요. 못 읽은 건 어차피 화면에 '매칭 실패'로 남아서 제가 직접 확인하면 되니까요.
3단계 — 매칭: 정확히 같을 때만 승인
이름·금액을 뽑았으면, 이제 홈페이지에 들어온 예약 중에 이 사람의 예약을 찾아야 해요. 여기서도 원칙은 똑같았어요. 정확히 일치할 때만 승인.
- 이름은 공백 제거하고 똑같은지 비교 (비슷한 이름 매칭 ❌)
- 금액도 정확히 같은 예약만
- 그런데 같은 이름·금액 예약이 두 건 이상이면? → 자동 승인 안 함
마지막 게 제일 중요했어요. 동명이인이 같은 금액을 보내는 경우가 드물지만 분명히 있을 수 있거든요. 이럴 때 둘 중 하나를 프로그램이 멋대로 골라서 승인하면 안 돼요. 모호하면 무조건 사람한테 넘기게 했어요.
candidates = [r for r in reservations
if same_name(r.name, deposit.name) and r.amount == deposit.amount]
if len(candidates) == 1:
approve(candidates[0]) # 딱 한 건일 때만 자동 승인
else:
pass # 0건이거나 2건 이상이면 사람이 처리
"빠르게 많이 처리"가 아니라 "확실한 것만 처리, 애매하면 멈춤"이 이 프로그램 전체를 관통하는 규칙이에요.
같은 알림이 두 번 와도 한 번만
하나 더. 알림 센터를 주기적으로 들여다보는 방식이라, 같은 입금 알림을 두 번 볼 수가 있어요. 그대로 두면 한 건을 두 번 승인하려 들겠죠. 그래서 알림마다 고유한 키를 만들어 "이미 본 알림"으로 기록해두고, 같은 게 또 보이면 건너뛰게 했어요. 같은 입금을 두 번 처리하는 일은 없어요.
실제로 돌아가는 화면
지금은 이렇게 돌아가고 있어요. 입금 알림이 뜨면 1초 안에 자동으로 승인까지 끝나요. (개인정보라 이름·금액·주소는 가렸어요.)

'승인됨'과 '매칭 실패'가 섞여 있는 게 보이실 거예요. 매칭 실패는 위에서 말한 "애매해서 사람한테 넘긴" 경우들이에요. 프로그램이 무리해서 처리하지 않고 솔직하게 손을 든 거라, 저는 저것들만 가끔 확인하면 돼요.
남는 생각
이번 작업으로 제일 크게 배운 건, 자동화에서 어떤 건 일부러 '안 하게' 만드는 게 더 중요하다는 거였어요. 처음엔 "전부 다 자동으로 척척" 되는 걸 상상했는데, 막상 돈이 걸리니까 "이건 자신 없으면 멈추고 사람한테 넘겨라"를 곳곳에 심는 게 핵심이 되더라고요.
빠른 자동화는 만들기 쉽지만, 틀리지 않는 자동화는 멈출 줄 알아야 만들어지는 것 같아요. 덕분에 저는 이제 알림 올 때마다 폰 들여다보던 일에서 거의 해방됐고요. 그게 제일 좋습니다.