
지난 포스팅에서 저는 "서로 다른 두 사이트가 Stripe 계정을 공유할 때, 웹훅이 브로드캐스팅(Broadcasting)되는 구조"를 이용해 버그를 잡겠다고 예고했습니다.
그리고 오늘, 컴퓨터 2대와 DB 강제 조작을 통해 그 가설을 완벽하게 증명해 냈습니다. 멀티 사이트 환경에서 우연히 발생할 수 있는 '죽음의 ID 충돌' 시나리오. 그 재현 과정을 공개합니다.
https://simjaejin.tistory.com/103
[오픈소스 기여 도전기 #1] 거대한 코드 숲에서 길을 찾다 (구조 분석 & 웹훅 테스트)
지난 포스팅에서 우여곡절 끝에 로컬 개발 환경(wp-env) 구축에 성공했습니다. 하지만 기쁨도 잠시, 막상 IDE를 켜고 코드를 보니 눈앞이 캄캄해졌습니다."환경은 떴는데... 도대체 코드는 어디를
simjaejin.tistory.com
🧪 1. 실험 환경: 완벽한 충돌을 위한 무대
이 버그를 재현하려면 "서로 다른 도메인을 가진 두 개의 워드프레스"가 필요합니다. 무료 터널링 툴의 제약을 극복하기 위해 하이브리드 구성을 마쳤습니다.
- PC 1 (Victim, 피해자): ngrok 사용
- URL: https://myrtle-subbasal-vaughn.ngrok-free.dev
- 역할: 환불(Refund) 데이터를 가지고 있다가, 엉뚱한 웹훅을 맞고 뻗을 예정.
- PC 2 (Trigger, 가해자): Cloudflare Tunnel 사용
- URL: https://config-peer-bloggers-salmon.trycloudflare.com
- 역할: 실제 결제를 발생시켜 Stripe가 웹훅을 쏘게 만듦.
Stripe 대시보드에는 이 두 도메인의 웹훅 엔드포인트가 모두 등록되어 있습니다. 즉, PC 2에서 결제하면 PC 1에도 알림이 갑니다.

⚙️ 2. 시나리오 설계: 운명을 조작하다 (DB Hacking)
자연 상태에서 두 사이트의 Order ID가 우연히 겹치기를 기다리려면 몇 년이 걸릴지 모릅니다. 그래서 저는 MySQL의 AUTO_INCREMENT 값을 조작하여 강제로 충돌을 유도했습니다.
상황 설정
- 목표 ID: 1000040
- PC 1 (Victim): ID 1000040을 '환불(Refund)' 객체로 만듦.
- PC 2 (Trigger): ID 1000040을 '주문(Order)' 객체로 만듦.
PC 1 작업: '지뢰' 매설
먼저 PC 1에서 주문(1000039)을 생성하고 즉시 환불 처리했습니다. 워드프레스(WooCommerce)는 환불 객체도 wp_posts 테이블에 저장하므로, 이 환불 건이 **ID 1000040**을 차지하게 됩니다.
(여기에 PC 1 DB: 1000039(주문), 1000040(환불) row 데이터 스크린샷)
PC 2 작업: '방아쇠' 당기기 준비
PC 2의 워드프레스 DB에 접속해, 다음 주문이 무조건 1000040번이 되도록 강제 설정했습니다.
ALTER TABLE wp_posts AUTO_INCREMENT = 1000039;
이제 PC 2에서 결제 버튼을 누르면, 생성될 주문 번호는 1000040이 됩니다. 모든 준비는 끝났습니다.
💥 3. 발사 및 피격 (The Impact)
PC 2(Cloudflare) 사이트에서 상품을 장바구니에 담고 결제를 진행했습니다. 예상대로 PC 2에서는 Order #1000040이 정상적으로 생성되고 결제가 완료되었습니다.

바로 그 순간, Stripe는 "Order #1000040 결제 성공"이라는 웹훅을 PC 1에게도 전송했습니다.
PC 1의 ngrok 터미널을 확인해 볼까요?
HTTP Requests
-------------
22:40:46.949 KST POST / 500 Internal Server Error
22:40:30.159 KST POST / 500 Internal Server Error
(경) 500 에러 발생 (축) 성공입니다! PC 1 서버가 비명을 질렀습니다.

🕵️♂️ 4. 로그 분석: 범인은 '타입 체크 부재'
서버가 뱉어낸 에러 로그를 뜯어보니, 제가 예상했던 바로 그 지점에서 정확하게 터졌습니다.
[15-Feb-2026 13:40:47 UTC] PHP Fatal error: Uncaught TypeError: WC_Stripe_Order_Helper::get_intent_id_from_order(): Argument #1 ($order) must be of type WC_Order, Automattic\WooCommerce\Admin\Overrides\OrderRefund given...
해석
- Stripe 웹훅이 order_id: 1000040을 달고 PC 1에 도착했습니다.
- PC 1의 코드는 wc_get_order(1000040)을 호출했습니다.
- 하지만 PC 1에서 1000040은 주문이 아니라 '환불(Refund)' 객체였습니다.
- 코드는 이걸 확인하지 않고, 주문 객체(WC_Order)만 쓸 수 있는 get_intent_id_from_order() 함수에 환불 객체를 집어넣었습니다.
- PHP: "이건 주문이 아니잖아!" -> Fatal Error 💥

📝 5. 결론 및 다음 계획
이로써 "WooCommerce Stripe 플러그인은 웹훅 처리 시 객체 타입(Order vs Refund)을 확인하지 않아, 멀티 사이트 환경에서 치명적인 오류를 일으킬 수 있다"는 사실이 증명되었습니다.
단순히 코드를 읽어서 짐작하는 것과, 이렇게 실제 환경을 구축해서 에러를 터뜨려 보는 것은 하늘과 땅 차이입니다. 이제 저는 확신을 가지고 코드를 수정할 수 있게 되었습니다.
다음 편 예고: 이제 원인을 알았으니 고칠 차례입니다. 단 3줄의 코드 수정으로 이 문제를 우아하게 해결하고, GitHub에 Pull Request(PR)를 보내 오픈소스 컨트리뷰터가 되는 마지막 과정을 공개하겠습니다.
'사이드프로젝트' 카테고리의 다른 글
| [오픈소스 기여 도전기 #1] 단 3줄의 코드로 세상을 바꾸다: PR 제출부터 머지까지 (완결) (0) | 2026.02.18 |
|---|---|
| [오픈소스 기여 도전기 #1] 거대한 코드 숲에서 길을 찾다 (구조 분석 & 웹훅 테스트) (0) | 2026.02.17 |
| [오픈소스 기여 도전기 #1] 맨땅에 헤딩: 우커머스 로컬 환경 구축부터 결제 성공까지 (0) | 2026.02.17 |
| [오픈소스 기여 도전기 #1] 12년 차 개발자의 전략적 접근법: 프로젝트 선정부터 이슈 분석까지 (0) | 2026.02.17 |
| [사이드 프로젝트] #2. RAG 챗봇 개발기 (6) - 하이브리드 검색과 '의외의' 결과 (0) | 2025.11.21 |