From 1a4941b0701744c748cfa3987a4410b7b331d725 Mon Sep 17 00:00:00 2001 From: seovalue Date: Tue, 28 Apr 2026 09:44:27 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Fix:=20=EA=B0=80=EC=83=81=EA=B3=84=EC=A2=8C?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=20API=EB=A5=BC=20=ED=98=84=ED=96=89=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#163)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 /kbank.do?method=kbankInit (POST) 엔드포인트는 IP 차단 정책으로 인해 동작하지 않는 문제가 있었습니다. 실제 동행복권 충전 페이지(/mypage/mndpChrg)의 JS를 분석한 결과 현행 엔드포인트는 아래와 같이 변경되어 있었습니다: - GET /mypage/kbankInit.do → reqVO(계좌번호·거래 파라미터) 획득 - GET /mypage/kbankProcess.do → resVO(최종 계좌번호) 획득 응답이 HTML 파싱 방식에서 JSON API 방식으로 바뀐 점도 함께 반영했습니다. Co-Authored-By: Claude Sonnet 4.6 --- src/dhapi/port/lottery_client.py | 93 ++++++++++++++------------------ 1 file changed, 40 insertions(+), 53 deletions(-) diff --git a/src/dhapi/port/lottery_client.py b/src/dhapi/port/lottery_client.py index c844b6b..74480c8 100644 --- a/src/dhapi/port/lottery_client.py +++ b/src/dhapi/port/lottery_client.py @@ -26,8 +26,8 @@ class LotteryClient: _ready_socket = "https://ol.dhlottery.co.kr/olotto/game/egovUserReadySocket.json" _game645_page = "https://ol.dhlottery.co.kr/olotto/game/game645.do" _cash_balance = "https://www.dhlottery.co.kr/mypage/home" - _assign_virtual_account_1 = "https://www.dhlottery.co.kr/kbank.do?method=kbankInit" - _assign_virtual_account_2 = "https://www.dhlottery.co.kr/kbank.do?method=kbankProcess" + _assign_virtual_account_1 = "https://www.dhlottery.co.kr/mypage/kbankInit.do" + _assign_virtual_account_2 = "https://www.dhlottery.co.kr/mypage/kbankProcess.do" _lotto_buy_list_url = "https://www.dhlottery.co.kr/mypage/selectMyLotteryledger.do" _DETAIL_REQUEST_DELAY = 0.5 @@ -428,66 +428,53 @@ def _parse_digit(self, text): def assign_virtual_account(self, deposit: Deposit): try: - resp = self._session.post( + resp = self._session.get( self._assign_virtual_account_1, - data={ + params={ + "VbankExpDate": self._get_tomorrow(), "PayMethod": "VBANK", - "VbankBankCode": "089", # 가상계좌 채번가능 케이뱅크 코드 - "price": str(deposit.amount), - "goodsName": "복권예치금", - "vExp": self._get_tomorrow(), + "VbankBankCode": "089", # 케이뱅크 코드 + "Price": str(deposit.amount), }, timeout=10, ) - logger.debug(f"status_code: {resp.status_code}") - - data = resp.json() - logger.debug(f"data: {data}") - - body = { - "PayMethod": "VBANK", - "GoodsName": data["GoodsName"], - "GoodsCnt": "", - "BuyerTel": data["BuyerTel"], - "Moid": data["Moid"], - "MID": data["MID"], - "UserIP": data["UserIP"], - "MallIP": data["MallIP"], - "MallUserID": data["MallUserID"], - "VbankExpDate": data["VbankExpDate"], - "BuyerEmail": data["BuyerEmail"], - # "SocketYN": '', - # "GoodsCl": '', - # "EncodeParameters": '', - "EdiDate": data["EdiDate"], - "EncryptData": data["EncryptData"], - "Amt": data["amt"], - "BuyerName": data["BuyerName"], - "VbankBankCode": data["VbankBankCode"], - "VbankNum": data["FxVrAccountNo"], - "FxVrAccountNo": data["FxVrAccountNo"], - "VBankAccountName": data["BuyerName"], - "svcInfoPgMsgYn": "N", - "OptionList": "no_receipt", - "TransType": "0", # 일반(0), 에스크로(1) - # "TrKey": None, - } - logger.debug(f"body: {body}") - - resp = self._session.post(self._assign_virtual_account_2, data=body, timeout=10) - logger.debug(f"resp: {resp}") - logger.debug(f"resp.text: {resp.text}") - - soup = BeautifulSoup(resp.text, "html5lib") - - elem = soup.select("#contents") + req = resp.json()["data"]["reqVO"] + logger.debug(f"kbankInit reqVO: {req}") + + resp2 = self._session.get( + self._assign_virtual_account_2, + params={ + "PayMethod": req.get("payMethod", "VBANK"), + "GoodsName": req.get("goodsName"), + "Moid": req.get("moid"), + "UserIP": req.get("userIP"), + "MallUserID": req.get("mallUserID"), + "VbankExpDate": req.get("vbankExpDate"), + "Amt": req.get("amt"), + "VbankBankCode": req.get("vbankBankCode"), + "VbankNum": req.get("fxVrAccountNo"), + "FxVrAccountNo": req.get("fxVrAccountNo"), + "VBankAccountName": req.get("buyerName"), + }, + timeout=10, + ) + res = resp2.json()["data"]["resVO"] + logger.debug(f"kbankProcess resVO: {res}") - logger.debug(f"elem: {elem}") + if res.get("resultCode") == "FAIL": + raise RuntimeError("❗ 가상계좌 발급이 거절되었습니다.") - 전용가상계좌 = elem[0].select("span")[0].contents[0] - 결제신청금액 = elem[0].select(".color_key1")[0].contents[0] + vbank_num = res.get("vbankNum", "") + bank_name = res.get("vbankBankName", "케이뱅크") + if not vbank_num: + raise RuntimeError("❗ 가상계좌 번호를 받지 못했습니다.") + formatted = f"{vbank_num[:3]}-{vbank_num[3:7]}-{vbank_num[7:10]}-{vbank_num[10:]}" + 전용가상계좌 = f"[{bank_name}] {formatted}" + 결제신청금액 = f"{deposit.amount:,} 원" self._lottery_endpoint.print_result_of_assign_virtual_account(전용가상계좌, 결제신청금액) + except RuntimeError: + raise except Exception: raise RuntimeError("❗ 가상계좌를 할당하지 못했습니다.") From d14f4fddf92c2b91c3a6cc275a6b7f025b0f8f45 Mon Sep 17 00:00:00 2001 From: seovalue Date: Sun, 10 May 2026 20:25:27 +0900 Subject: [PATCH 2/2] =?UTF-8?q?style:=20black=20=ED=8F=AC=EB=A7=B7?= =?UTF-8?q?=ED=8C=85=20=EC=88=98=EC=A0=95=20(=EB=94=95=EC=85=94=EB=84=88?= =?UTF-8?q?=EB=A6=AC=20=ED=82=A4=20=EC=A0=95=EB=A0=AC=20=EA=B3=B5=EB=B0=B1?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/dhapi/port/lottery_client.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dhapi/port/lottery_client.py b/src/dhapi/port/lottery_client.py index 74480c8..34151ef 100644 --- a/src/dhapi/port/lottery_client.py +++ b/src/dhapi/port/lottery_client.py @@ -444,16 +444,16 @@ def assign_virtual_account(self, deposit: Deposit): resp2 = self._session.get( self._assign_virtual_account_2, params={ - "PayMethod": req.get("payMethod", "VBANK"), - "GoodsName": req.get("goodsName"), - "Moid": req.get("moid"), - "UserIP": req.get("userIP"), - "MallUserID": req.get("mallUserID"), - "VbankExpDate": req.get("vbankExpDate"), - "Amt": req.get("amt"), - "VbankBankCode": req.get("vbankBankCode"), - "VbankNum": req.get("fxVrAccountNo"), - "FxVrAccountNo": req.get("fxVrAccountNo"), + "PayMethod": req.get("payMethod", "VBANK"), + "GoodsName": req.get("goodsName"), + "Moid": req.get("moid"), + "UserIP": req.get("userIP"), + "MallUserID": req.get("mallUserID"), + "VbankExpDate": req.get("vbankExpDate"), + "Amt": req.get("amt"), + "VbankBankCode": req.get("vbankBankCode"), + "VbankNum": req.get("fxVrAccountNo"), + "FxVrAccountNo": req.get("fxVrAccountNo"), "VBankAccountName": req.get("buyerName"), }, timeout=10,