🌟개발기

1인 개발자가 결제를 붙이기까지 — 웹훅 서명검증·SMS 파싱을 거쳐 Polar(Merchant of Record)로

1인 개발 서비스에 결제를 붙이기까지의 엔지니어링 기록. '입금 확인=결제 확인'이라는 뼈대를 후원 웹훅·입금 SMS 파싱으로 만들고, 최종적으로 Polar(Merchant of Record)에 안착했다 — 사업자 등록 없이 글로벌 카드 결제, 서명검증·멱등·환율·매칭, 그리고 정직한 결과(인프라는 완성, 매출은 아직 0)까지.

📅 2026년 7월 12일·📖 7분 읽기·👁 8
1인 개발자가 결제를 붙이기까지 — 웹훅 서명검증·SMS 파싱을 거쳐 Polar(Merchant of Record)로

1인 개발자가 결제를 붙이기까지 — 웹훅 서명검증·SMS 파싱을 거쳐 Polar(Merchant of Record)로

리엘(Riel)에는 크레딧(포인트) 충전 기능이 있다. 사용자가 결제하면 잔액이 오르고, 그 잔액으로 유료 기능을 쓴다. 흔한 구조다. 그런데 1인 개발 서비스에서 이걸 붙일 때 진짜 어려운 부분은 결제창 UI가 아니라 "외부에서 돈이 들어온 걸 어떻게 자동으로 확인해서 크레딧에 반영하느냐", 그리고 "정식 결제대행(PG)은 대부분 사업자 등록을 요구한다"는 두 가지였다.

이 글은 그 두 문제를 풀어간 엔지니어링 기록이다. 결론부터 말하면 — 후원 웹훅과 입금 SMS 파싱으로 "입금 확인 자동화"의 뼈대를 먼저 만들었고, 최종적으로는 Polar(Merchant of Record)에 정식으로 안착했다. 중요한 건, 앞의 실험들이 헛되지 않고 그 패턴이 그대로 Polar로 이식됐다는 점이다.

핵심 아이디어: "입금 확인 = 결제 확인"

돈을 받는 방법은 이미 세상에 많다. 후원 플랫폼, 계좌이체, 간편송금, 그리고 정식 결제 플랫폼. 문제는 그게 내 서버와 연결이 안 되어 있다는 것뿐이었다. 그렇다면 "돈이 들어왔다"는 신호만 어떻게든 서버로 가져오면, 그 뒤(누구에게 얼마를 적립할지)는 내가 코드로 처리할 수 있다. 이 한 줄이 전체 설계의 출발점이었고, 마지막 Polar까지 관통하는 뼈대가 됐다.

실험 1 — 후원 플랫폼 웹훅으로 뼈대를 만들다

처음 만든 통로는 후원 플랫폼(Buy Me a Coffee)의 웹훅이었다. 후원이 들어오면 플랫폼이 내 서버 엔드포인트로 이벤트를 POST 해준다. 이걸 "입금 확인 신호"로 쓰면 결제창 없이도 자동 적립이 된다. 외부에서 들어오는 공개 엔드포인트라, 신뢰하려면 방어를 겹겹이 쌓아야 했다. 여기서 만든 방어 패턴이 나중에 그대로 재사용된다.

서명 검증 (HMAC). 아무나 이 URL로 가짜 이벤트를 쏠 수 있으면 안 된다. 플랫폼이 payload를 비밀키로 서명해 헤더에 넣어주는데, 서버에서 같은 키로 다시 계산해 일치할 때만 처리한다.

멱등성. 웹훅은 네트워크 사정에 따라 같은 이벤트를 여러 번 보낼 수 있다. 그대로 처리하면 한 번 결제에 크레딧이 두 번 적립된다. 이벤트마다 고유 키를 뽑아 기록하고, 이미 본 키면 무시한다.

항상 200을 반환. 오류 상황에서 4xx/5xx를 주면 플랫폼이 "실패"로 보고 같은 이벤트를 계속 재시도한다. 그래서 내부에서 무슨 일이 있어도 응답은 200으로 주고(테스트·미매칭·중복 전부 200), 실제 처리 여부는 body로 구분한다.

테스트 이벤트 가드. 대시보드의 "Send Test"는 live_mode: false로 온다. 실제 결제로 착각해 적립하면 안 되니, live가 아니면 기록만 하고 적립은 건너뛴다.

사용자 매칭 — 이메일이 다르다. 가장 현실적인 함정이었다. 후원한 사람의 이메일과 서비스 로그인 이메일이 다를 수 있다. 그래서 이메일 매칭만 믿으면 안 됐다. 사용자별 짧은 코드를 발급해 "메시지에 이 코드를 붙여달라"고 안내하고, 웹훅이 오면 메시지 속 코드로 먼저 매칭, 없으면 이메일 폴백. 실제 첫 테스트($1)에서 이메일이 서로 달랐는데도 코드로 정확한 계정에 자동 적립됐다.

실험 2 — 입금 SMS 파싱으로 계좌이체를 반자동으로

한국 사용자에겐 카드보다 계좌이체·간편송금이 익숙하다. 그런데 개인 계좌 입금은 웹훅을 주지 않는다. 입금을 알 방법이 딱 하나 있었다 — 은행이 보내주는 입금 알림 문자.

그래서 폰의 SMS 전달 앱이 은행 입금 문자를 내 서버로 POST 하게 하고, 서버는 문자에서 금액을 파싱(잔액 숫자 오매칭 회피)하고 코드로 사용자를 매칭해 적립한다. 후원 웹훅과 완전히 같은 패턴 — 소스가 플랫폼 이벤트냐 은행 문자냐만 다르다. 여기도 문자 해시로 멱등 처리 + 시크릿 헤더 + 미매칭 기록을 붙였다.

그리고 최종 — Polar(Merchant of Record)

후원·SMS로 "입금 확인 자동화"의 뼈대는 잘 돌았지만, 그건 결국 후원 명목이었다. 진짜 필요한 건 "상품(크레딧 팩)을 파는 정식 결제 + 영수증 + 세금 처리"였고, 여기서 사업자 등록이라는 벽이 다시 등장했다. 정식 PG는 사업자를 요구하니까.

답은 Merchant of Record(MoR) 모델이었다. Polar 같은 MoR은 플랫폼이 '판매자'가 되어 결제·부가세·세금 신고를 대신 처리한다. 즉 개발자는 개인 사업자 등록 없이 전 세계 카드 결제를 받을 수 있다. 게다가 Stripe 기반이라 한국 개인 계정으로도 정산(payout)이 열렸다 — "글로벌 + 개인 + 한국 정산은 불가능"이라던 통념을 뒤집은 지점이다.

구조는 앞의 실험과 놀랄 만큼 똑같았다:

  • 체크아웃 생성: 사용자가 크레딧 팩을 고르면, 백엔드가 결제 세션을 만들면서 체크아웃 metadata에 유저 식별자를 심는다.
  • order 웹훅: 결제가 끝나면 Polar가 order 이벤트를 POST → Standard Webhooks 규격으로 서명 검증metadata에서 유저를 정확히 매칭(후원 실험의 '메시지 속 코드'보다 깔끔하다) → USD를 라이브 환율로 원화 환산 → 포인트 적립.
  • 멱등·서명검증·환율·적립 로직은 후원 웹훅에서 만든 걸 거의 그대로 재사용했다. 실험이 헛되지 않았다는 게 여기서 증명된다.

정리하면 역할이 이렇게 나뉜다: 카드/글로벌 결제 = Polar(정식, MoR), 한국 계좌이체 = SMS 파싱(병행), 애매한 건 어드민 수동 승인(안전망).

배우면서 밟은 지뢰들

  • nginx 라우팅 — =(정확) vs prefix. 공개 웹훅 경로와 로그인 필요한 하위 경로가 섞이면서 prefix 매칭이 하위 경로까지 삼켜 인증이 꼬였다. 새 하위 경로는 어디로 라우팅되는지 정확 매칭으로 못박아야 했다.
  • 무제한 계정에 적립하면 무제한이 깨진다. "무제한"을 잔액 -1로 표현했는데, 여기 금액을 더하니 유한값이 돼 무제한이 사라졌다. 매칭 계정이 무제한이면 적립을 건너뛰도록 가드.
  • 웹훅은 신뢰하되 검증하라. 서명·멱등·live 가드 — 이 셋 중 하나만 빠져도 (가짜 적립 / 이중 적립 / 테스트 적립) 사고가 난다. 공개 엔드포인트는 "일단 다 의심"이 기본값.

정직한 결과

여기까지의 솔직한 상태: 결제를 받아 자동 적립하는 인프라는 완성됐다. Polar 체크아웃·서명검증 웹훅·환율·매칭·포인트 적립, 그리고 한국용 계좌이체/SMS 병행까지 실제로 연결돼 있다.

그런데 실제 매출은 아직 거의 없다. Polar 정식 주문은 아직 0건이다. 이게 이 프로젝트가 준 가장 큰 교훈이었다 — 병목은 결제 인프라가 아니었다. 사업자 등록이라는 벽도 MoR로 우회했고, 돈을 받을 준비는 코드로 다 해뒀지만, 정작 필요한 건 "낼 사람이 오는 것"이었다. 결제 파이프라인을 붙이는 일은 재밌고 배울 것도 많았지만, 그 앞단(사람들이 왜 이걸 써야 하는가)이 없으면 아무리 정교해도 숫자는 0이다.

그래서 지금은 이 인프라를 잘 둔 채, 힘을 입구(콘텐츠·수요) 쪽으로 옮기고 있다. 결제는 언제든 켤 수 있게 준비돼 있으니까.

— 한국어 AI 챗봇 리엘을 혼자 만드는 개발자의 빌드인퍼블릭 기록.