From 75e2ff98cd630d9394761f664943b2dd3870e372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=98=81=EC=9D=80=28Tony=29?= <52942409+Monsteel@users.noreply.github.com> Date: Tue, 26 Sep 2023 18:47:03 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B2=B0=EC=A0=9C=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#249)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SRT/constants.py | 1 + SRT/srt.py | 84 +++++++ docs/advanced.md | 27 +++ .../pay_with_card_fail_bad_request.json | 22 ++ .../pay_with_card_fail_cant_installment.json | 22 ++ .../pay_with_card_fail_card_password.json | 22 ++ .../pay_with_card_fail_expired_card.json | 22 ++ ...ay_with_card_fail_invalid_auth_number.json | 22 ++ ...ay_with_card_fail_invalid_card_number.json | 22 ++ ...ith_card_fail_invalid_expiration_date.json | 22 ++ ...ay_with_card_fail_invalid_reservation.json | 22 ++ .../pay_with_card_fail_over_limit.json | 22 ++ .../pay_with_card_fail_suspension_card.json | 22 ++ .../mock_responses/pay_with_card_success.json | 192 +++++++++++++++ tests/test_srt.py | 46 ++++ tests/test_srt_mock.py | 222 ++++++++++++++++++ 16 files changed, 792 insertions(+) create mode 100644 tests/mock_responses/pay_with_card_fail_bad_request.json create mode 100644 tests/mock_responses/pay_with_card_fail_cant_installment.json create mode 100644 tests/mock_responses/pay_with_card_fail_card_password.json create mode 100644 tests/mock_responses/pay_with_card_fail_expired_card.json create mode 100644 tests/mock_responses/pay_with_card_fail_invalid_auth_number.json create mode 100644 tests/mock_responses/pay_with_card_fail_invalid_card_number.json create mode 100644 tests/mock_responses/pay_with_card_fail_invalid_expiration_date.json create mode 100644 tests/mock_responses/pay_with_card_fail_invalid_reservation.json create mode 100644 tests/mock_responses/pay_with_card_fail_over_limit.json create mode 100644 tests/mock_responses/pay_with_card_fail_suspension_card.json create mode 100644 tests/mock_responses/pay_with_card_success.json diff --git a/SRT/constants.py b/SRT/constants.py index b164891..f537edc 100644 --- a/SRT/constants.py +++ b/SRT/constants.py @@ -61,4 +61,5 @@ "ticket_info": f"{SRT_MOBILE}/ard/selectListArd02017_n.do?", "cancel": f"{SRT_MOBILE}/ard/selectListArd02045_n.do", "standby_option": f"{SRT_MOBILE}/ata/selectListAta01135_n.do", + "payment": f"{SRT_MOBILE}/ata/selectListAta09036_n.do", } diff --git a/SRT/srt.py b/SRT/srt.py index d47a415..28cbb03 100644 --- a/SRT/srt.py +++ b/SRT/srt.py @@ -1,3 +1,4 @@ +import json import re from datetime import datetime, timedelta @@ -126,6 +127,7 @@ def login(self, srt_id: str | None = None, srt_pw: str | None = None): raise SRTLoginError(r.json()["MSG"]) self.is_login = True + self.membership_number = json.loads(r.text).get("userMap").get("MB_CRD_NO") return True @@ -144,6 +146,8 @@ def logout(self) -> bool: raise SRTResponseError(r.text) self.is_login = False + self.membership_number = None + return True def search_train( @@ -571,3 +575,83 @@ def cancel(self, reservation: SRTReservation | int) -> bool: self._log(parser.message()) return True + + def pay_with_card( + self, + reservation: SRTReservation, + number: str, + password: str, + card_validation_number: str, + card_expire_date: str, + card_installment: int = 0, + card_type: str = "J", + ) -> bool: + """결제합니다. + + >>> reservation = srt.reserve(train) + >>> srt.pay_with_card(reservation, "1234567890123456", "12", "981204", "2309", 0, "J") + + Args: + reservation (:class:`SRTReservation`): 예약 내역 + number (str): 결제신용카드번호 (하이픈(-) 제외) + password (str): 카드비밀번호 앞 2자리 + card_validation_number (str): 생년월일 (card_type이 J인 경우) || 사업자번호 (card_type이 S인 경우) + card_expire_date (str): 카드유효기간(YYMM) + card_installment (int): 할부선택 (0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24) + card_type (str): 카드타입 (J : 개인, S : 법인) + + Returns: + bool: 결제 성공 여부 + """ + if not self.is_login: + raise SRTNotLoggedInError() + + url = constants.API_ENDPOINTS["payment"] + + data = { + "stlDmnDt": datetime.now().strftime("%Y%m%d"), # 날짜 (yyyyMMdd) + "mbCrdNo": self.membership_number, # 회원번호 + "stlMnsSqno1": "1", # 결제수단 일련번호1 (1고정값인듯) + "ststlGridcnt": "1", # 결제수단건수 (1고정값인듯) + "totNewStlAmt": reservation.total_cost, # 총 신규 결제금액 + "athnDvCd1": card_type, # 카드타입 (J : 개인, S : 법인) + "vanPwd1": password, # 카드비밀번호 앞 2자리 + "crdVlidTrm1": card_expire_date, # 카드유효기간(YYMM) + "stlMnsCd1": "02", # 결제수단코드1: (02:신용카드, 11:전자지갑, 12:포인트) + "rsvChgTno": "0", # 예약변경번호 (0 고정값인듯) + "chgMcs": "0", # 변경마이크로초 (0고정값인듯) + "ismtMnthNum1": card_installment, # 할부선택 (0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24) + "ctlDvCd": "3102", # 조정구분코드(3102 고정값인듯) + "cgPsId": "korail", # korail 고정 + "pnrNo": reservation.reservation_number, # 예약번호 + "totPrnb": reservation.seat_count, # 승차인원 + "mnsStlAmt1": reservation.total_cost, # 결제금액1 + "crdInpWayCd1": "@", # 카드입력방식코드 (@: 신용카드/ok포인트, "": 전자지갑) + "athnVal1": card_validation_number, # 생년월일/사업자번호 + "stlCrCrdNo1": number, # 결제신용카드번호1 + "jrnyCnt": "1", # 여정수(1 고정) + "strJobId": "3102", # 업무구분코드(3102 고정값인듯) + "inrecmnsGridcnt": "1", # 1 고정값인듯 + "dptTm": reservation.dep_time, # 출발시간 + "arvTm": reservation.arr_time, # 도착시간 + "dptStnConsOrdr2": "000000", # 출발역구성순서2 (000000 고정) + "arvStnConsOrdr2": "000000", # 도착역구성순서2 (000000 고정) + "trnGpCd": "300", # 열차그룹코드(300 고정) + "pageNo": "-", # 페이지번호(- 고정) + "rowCnt": "-", # 한페이지당건수(- 고정) + "pageUrl": "", # 페이지URL (빈값 고정) + } + + r = self._session.post(url=url, data=data) + + parser = json.loads(r.text) + + if ( + parser.get("outDataSets").get("dsOutput0")[0].get("strResult") + == RESULT_FAIL + ): + raise SRTResponseError( + parser.get("outDataSets").get("dsOutput0")[0].get("msgTxt") + ) + + return True diff --git a/docs/advanced.md b/docs/advanced.md index a0aa5a2..1a94db2 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -15,6 +15,7 @@ SRT 로그인, 승차표 찾기 설명은 생략합니다. Highly inspired by [@dotaitch](https://github.com/dotaitch). 예시) 어른 2명, 어린이 1명 예약 + ```python >>> from SRT.passenger import Adult, Child >>> srt.reserve(trains[0], passengers=[Adult(), Adult(), Child()]) @@ -29,6 +30,7 @@ Highly inspired by [@dotaitch](https://github.com/dotaitch). ## 일반실 / 특실 좌석 옵션 선택하기 예시) 일반실 우선 예약 + ```python >>> from SRT import SeatType >>> srt.reserve(self, trains[0], special_seat=SeatType.GENERAL_FIRST) @@ -38,3 +40,28 @@ Highly inspired by [@dotaitch](https://github.com/dotaitch). - SeatType.GENERAL_ONLY : 일반실만 - SeatType.SPECIAL_FIRST : 특실 우선 - SeatType.SPECIAL_ONLY : 특실만 + +## 결제하기 + +**⚠️ 주의: 이 API는 실제로 결제를 수행합니다**
+해당 결제 API는 비공식적인 API를 사용하기 때문에 언제든지 제대로 동작하지 않을 수 있습니다.
+이에 따른 문제 발생에 대한 책임은 API 사용자 본인에게 있음을 알립니다.
+ +--- + +```python +>>> reservation = srt.reserve(trains[0]) +>>> srt.pay_with_card(reservation, "1234567890123456", "12", "981204", "2309", 0, "J") +``` + +### 각 파라미터 기입요령 + +| 순서 | 변수명 | 설명 | 예시 | 기본 값 | +| ---- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | -------------------- | ------- | +| 1 | reservation | **결제대상 예약내역**
_\* `SRTReservation` 타입을 준수_ | - | - | +| 2 | number | **결제 카드번호**
_\* 하이픈(-) 제외_ | `"1234000056780000"` | - | +| 3 | password | **카드비밀번호 앞 2자리** | `"12"` | - | +| 4 | card_validation_number | **개인(`J`)인 경우: 생년월일
법인(`S`)인 경우: 사업자번호** | `"981204"` | - | +| 5 | card_expire_date | **카드유효기간**
_\* YYMM(연도+월) 형식의 만료일을 입력
\*카드 표현방식 MMYY(월+연도)형식과 착오에 주의_ | `"2309"` | - | +| 6 | card_installment | **할부선택**
_\* 할부 개월 수 입력, 0의 경우 일시불.
\* 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24 개월 선택 가능_ | `0` | `0` | +| 7 | card_type | **카드 유형**
_\* J : 개인, S : 법인_ | `"J"` | `"J"` | diff --git a/tests/mock_responses/pay_with_card_fail_bad_request.json b/tests/mock_responses/pay_with_card_fail_bad_request.json new file mode 100644 index 0000000..02666d8 --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_bad_request.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WZZ000001", + "strResult": "FAIL", + "msgTxt": "% 입력이 잘못되었습니다." + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WZZ000001", + "strResult": "FAIL", + "msgTxt": "% 입력이 잘못되었습니다." + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_cant_installment.json b/tests/mock_responses/pay_with_card_fail_cant_installment.json new file mode 100644 index 0000000..8b12824 --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_cant_installment.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV07574", + "strResult": "FAIL", + "msgTxt": "할부불가카드" + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV07574", + "strResult": "FAIL", + "msgTxt": "할부불가카드" + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_card_password.json b/tests/mock_responses/pay_with_card_fail_card_password.json new file mode 100644 index 0000000..9c3030a --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_card_password.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV06003", + "strResult": "FAIL", + "msgTxt": "비밀번호오류

사용하신 각 신용카드사의 고객센터로 문의 바랍니다." + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV06003", + "strResult": "FAIL", + "msgTxt": "비밀번호오류

사용하신 각 신용카드사의 고객센터로 문의 바랍니다." + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_expired_card.json b/tests/mock_responses/pay_with_card_fail_expired_card.json new file mode 100644 index 0000000..19afc2a --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_expired_card.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV08314", + "strResult": "FAIL", + "msgTxt": "유효기간경과카드" + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV08314", + "strResult": "FAIL", + "msgTxt": "유효기간경과카드" + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_invalid_auth_number.json b/tests/mock_responses/pay_with_card_fail_invalid_auth_number.json new file mode 100644 index 0000000..c6fb03d --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_invalid_auth_number.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV06006", + "strResult": "FAIL", + "msgTxt": "주민번호 또는 사업자번호 오류입니다." + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV06006", + "strResult": "FAIL", + "msgTxt": "주민번호 또는 사업자번호 오류입니다." + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_invalid_card_number.json b/tests/mock_responses/pay_with_card_fail_invalid_card_number.json new file mode 100644 index 0000000..f0c65ea --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_invalid_card_number.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV08037", + "strResult": "FAIL", + "msgTxt": "카드번호오류" + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV08037", + "strResult": "FAIL", + "msgTxt": "카드번호오류" + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_invalid_expiration_date.json b/tests/mock_responses/pay_with_card_fail_invalid_expiration_date.json new file mode 100644 index 0000000..dd81dad --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_invalid_expiration_date.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV08315", + "strResult": "FAIL", + "msgTxt": "유효기간을 잘못입력하셨습니다." + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV08315", + "strResult": "FAIL", + "msgTxt": "유효기간을 잘못입력하셨습니다." + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_invalid_reservation.json b/tests/mock_responses/pay_with_card_fail_invalid_reservation.json new file mode 100644 index 0000000..2363b73 --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_invalid_reservation.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRT200178", + "strResult": "FAIL", + "msgTxt": "취소된 여정이므로 발매할 수 없습니다.
비회원은 다시 예약하셔야합니다." + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRT200178", + "strResult": "FAIL", + "msgTxt": "취소된 여정이므로 발매할 수 없습니다.
비회원은 다시 예약하셔야합니다." + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_over_limit.json b/tests/mock_responses/pay_with_card_fail_over_limit.json new file mode 100644 index 0000000..32f932e --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_over_limit.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV08326", + "strResult": "FAIL", + "msgTxt": "사용한도초과" + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV08326", + "strResult": "FAIL", + "msgTxt": "사용한도초과" + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_fail_suspension_card.json b/tests/mock_responses/pay_with_card_fail_suspension_card.json new file mode 100644 index 0000000..b4b2ed7 --- /dev/null +++ b/tests/mock_responses/pay_with_card_fail_suspension_card.json @@ -0,0 +1,22 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput0": [ + { + "msgCd": "WRTV08324", + "strResult": "FAIL", + "msgTxt": "거래정지카드" + } + ] + }, + "dsResultMap": { + "dsOutput0": [ + { + "msgCd": "WRTV08324", + "strResult": "FAIL", + "msgTxt": "거래정지카드" + } + ] + } +} diff --git a/tests/mock_responses/pay_with_card_success.json b/tests/mock_responses/pay_with_card_success.json new file mode 100644 index 0000000..80bc179 --- /dev/null +++ b/tests/mock_responses/pay_with_card_success.json @@ -0,0 +1,192 @@ +{ + "ERROR_MSG": "", + "ERROR_CODE": "0", + "outDataSets": { + "dsOutput3": [ + { + "TKLIST_KEY": 0, + "dcntKndNm": null, + "DCNTLIST_KEY": 0 + } + ], + "dsOutput2": [ + { + "trnNo1": "", + "rcvdAmt": "00000036900", + "seatNo1": "", + "arvDt1": "", + "scarNo": 2, + "crwPwd": "000000000000000000", + "jrnyTpCd": "11", + "tkPrc": 36900, + "dptRsStnNmEn": "Suseo", + "trnGpNm1": "", + "arvTm1": "", + "dptTm": "053000", + "entNm": "", + "dptRsStnNm2": "", + "TKLIST_KEY": 0, + "dptRsStnNm1": "", + "arvDt": "20231024", + "psrmClCdNm": "일반실", + "trnClsfNm": "SRT", + "trnGpNm": "SRT", + "rmkCont": "신용000000", + "trnGpCd": "300", + "scarNo1": 0, + "psgTpCdNm": "어른", + "arvTm": "071100", + "dptRsStnNmKo": "수서", + "arvRsStnNm1": "", + "trnClsfNm1": "", + "dcntGroupGridcnt": 0, + "tkSqno": 0, + "psrmClCdNm1": "", + "tkKndCdNm": "스마트티켓", + "arvRsStnNmEn": "Dongdaegu", + "arvRsStnNm2": "", + "dlayDscpFlg": "N", + "saleSqno": 10000, + "arvRsStnNmKo": "동대구", + "stdrPrc": "00000037500", + "dptDt1": "", + "dptDt": "20231024", + "trnNo": "00301", + "seatNo": "10A", + "tkRetNo": "0000000000000000", + "mogoNm": "", + "dcntAmt": "00000000600", + "dptTm1": "", + "saleDt": "20230923" + } + ], + "dsOutput1": [ + { + "pontDvCd": "", + "apvDt": "20230923", + "stlSqno": "00000000001", + "stlCrCrdNo": "0000000000000000", + "stlApvTm": "145030", + "stlMnsCd": "02", + "mnsStlAmt": "36900", + "apvNo": "00000000" + } + ], + "dsOutput0": [ + { + "tkNum": 1, + "buyPsNm": "김철수", + "totPrc": "00000036900", + "acmMlgNum": 0, + "strResult": "SUCC", + "cgPsId": "korail", + "stlAmt": "36900", + "totNewStlAmt": "00000000036900", + "pnrNo": "000000000000000", + "msgCd": "IRT000000", + "wctNo": "00000", + "totRcvdAmt": "0000000000036900", + "totDcntAmt": 600, + "rmkCont": "", + "msgTxt": "정상발매처리,정상발권처리", + "stlMnsCd": "02", + "uuid": "APP0000000000000000000000000000000", + "apvNo": "00000000" + } + ] + }, + "dsResultMap": { + "dsOutput3": [ + { + "TKLIST_KEY": 0, + "dcntKndNm": null, + "DCNTLIST_KEY": 0 + } + ], + "dsOutput2": [ + { + "trnNo1": "", + "rcvdAmt": "00000036900", + "seatNo1": "", + "arvDt1": "", + "scarNo": 2, + "crwPwd": "000000000000000000", + "jrnyTpCd": "11", + "tkPrc": 36900, + "dptRsStnNmEn": "Suseo", + "trnGpNm1": "", + "arvTm1": "", + "dptTm": "053000", + "entNm": "", + "dptRsStnNm2": "", + "TKLIST_KEY": 0, + "dptRsStnNm1": "", + "arvDt": "20231024", + "psrmClCdNm": "일반실", + "trnClsfNm": "SRT", + "trnGpNm": "SRT", + "rmkCont": "신용000000", + "trnGpCd": "300", + "scarNo1": 0, + "psgTpCdNm": "어른", + "arvTm": "071100", + "dptRsStnNmKo": "수서", + "arvRsStnNm1": "", + "trnClsfNm1": "", + "dcntGroupGridcnt": 0, + "tkSqno": 0, + "psrmClCdNm1": "", + "tkKndCdNm": "스마트티켓", + "arvRsStnNmEn": "Dongdaegu", + "arvRsStnNm2": "", + "dlayDscpFlg": "N", + "saleSqno": 10000, + "arvRsStnNmKo": "동대구", + "stdrPrc": "00000037500", + "dptDt1": "", + "dptDt": "20231024", + "trnNo": "00301", + "seatNo": "10A", + "tkRetNo": "0000000000000000", + "mogoNm": "", + "dcntAmt": "00000000600", + "dptTm1": "", + "saleDt": "20230923" + } + ], + "dsOutput1": [ + { + "pontDvCd": "", + "apvDt": "20230923", + "stlSqno": "00000000001", + "stlCrCrdNo": "0000000000000000", + "stlApvTm": "145030", + "stlMnsCd": "02", + "mnsStlAmt": "36900", + "apvNo": "00000000" + } + ], + "dsOutput0": [ + { + "tkNum": 1, + "buyPsNm": "김철수", + "totPrc": "00000036900", + "acmMlgNum": 0, + "strResult": "SUCC", + "cgPsId": "korail", + "stlAmt": "36900", + "totNewStlAmt": "00000000036900", + "pnrNo": "000000000000000", + "msgCd": "IRT000000", + "wctNo": "00000", + "totRcvdAmt": "0000000000036900", + "totDcntAmt": 600, + "rmkCont": "", + "msgTxt": "정상발매처리,정상발권처리", + "stlMnsCd": "02", + "uuid": "APP0000000000000000000000000000000", + "apvNo": "00000000" + } + ] + } +} diff --git a/tests/test_srt.py b/tests/test_srt.py index eb42693..7a401fe 100644 --- a/tests/test_srt.py +++ b/tests/test_srt.py @@ -93,3 +93,49 @@ def test_reserve_and_cancel(srt, pytestconfig): pytest.warns(Warning("Empty seat not found, skipping reservation test")) srt.cancel(reservation) + + +def test_reserve_and_pay_with_card(srt, pytestconfig): + pytestconfig.getoption("--full", skip=True) + dep = "수서" + arr = "대전" + time = "000000" + + membership_number = "1234567890" + password = "12" + card_expire_date = "2209" + card_installment = 0 + card_validation_number = "1234567890123" + number = "1234567890123456" + card_type = "J" + + # loop until empty seat is found + reservation = None + for day in range(5, 30): + date = (datetime.now() + timedelta(days=day)).strftime("%Y%m%d") + + trains = srt.search_train(dep, arr, date, time) + + assert len(trains) != 0 + + for train in trains: + if train.general_seat_available(): + reservation = srt.reserve(train) + break + + if reservation is not None: + break + + if reservation is None: + pytest.warns(Warning("Empty seat not found, skipping reservation test")) + + srt.pay_with_card( + reservation, + membership_number, + password, + card_expire_date, + card_installment, + card_validation_number, + number, + card_type, + ) diff --git a/tests/test_srt_mock.py b/tests/test_srt_mock.py index c501747..0d5c64e 100644 --- a/tests/test_srt_mock.py +++ b/tests/test_srt_mock.py @@ -2,6 +2,8 @@ import pytest +from SRT.reservation import SRTReservation + mock_response_dir = Path(__file__).parent / "mock_responses" @@ -19,6 +21,7 @@ def mock_server(httpserver, monkeypatch): "ticket_info": httpserver.url_for("/ticket_info"), "cancel": httpserver.url_for("/cancel"), "standby_option": httpserver.url_for("/standby_option"), + "payment": httpserver.url_for("/payment"), } monkeypatch.setattr( @@ -75,3 +78,222 @@ def test_logout(mock_server, httpserver): srt = SRT("010-1234-1234", "password") srt.logout() assert not srt.is_login + + +# 결제 테스트를 위한 mock reservation +mock_reservation = SRTReservation( + { + "pnrNo": "000000000", + "tkSpecNum": "1", + "rcvdAmt": "36900", + }, + { + "stlbTrnClsfCd": "00", + "trnNo": "0000", + "dptDt": "20231024", + "dptTm": "000000", + "dptRsStnCd": "0551", + "arvTm": "000000", + "arvRsStnCd": "0015", + "iseLmtDt": "20231024", + "iseLmtTm": "000000", + "stlFlg": "N", + }, + [ + { + "scarNo": "1", + "seatNo": "1", + "psrmClCd": "1", + "psgTpCd": "1", + "rcvdAmt": "36900", + "stdrPrc": "36900", + "dcntPrc": "600", + } + ], +) + + +# 결제 성공 +def test_pay_with_card_success(mock_server, httpserver): + from SRT import SRT + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response(httpserver, "/payment", "pay_with_card_success.json") + + srt = SRT("010-1234-1234", "password") + + assert srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 입력이 잘못되었습니다 +def test_pay_with_card_fail_bad_request(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_bad_request.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="% 입력이 잘못되었습니다."): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 할부 불가 카드 +def test_pay_with_card_fail_cant_installment(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_cant_installment.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="할부불가카드"): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 비밀번호 오류 +def test_pay_with_card_fail_card_password(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_card_password.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises( + SRTResponseError, match="비밀번호오류

사용하신 각 신용카드사의 고객센터로 문의 바랍니다." + ): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 유효기간 경과 카드 +def test_pay_with_card_fail_expired_card(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_expired_card.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="유효기간경과카드"): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 주민번호 또는 사업자번호 오류 +def test_pay_with_card_fail_invalid_auth_number(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_invalid_auth_number.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="주민번호 또는 사업자번호 오류입니다."): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 카드번호 오류 +def test_pay_with_card_fail_invalid_card_number(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_invalid_card_number.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="카드번호오류"): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 유효기간 입력 오류 +def test_pay_with_card_fail_invalid_expiration_date(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_invalid_expiration_date.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="유효기간을 잘못입력하셨습니다."): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 취소된 예약 +def test_pay_with_card_fail_invalid_reservation(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_invalid_reservation.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises( + SRTResponseError, match="취소된 여정이므로 발매할 수 없습니다.
비회원은 다시 예약하셔야합니다." + ): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 한도 초과 +def test_pay_with_card_fail_over_limit(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response(httpserver, "/payment", "pay_with_card_fail_over_limit.json") + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="사용한도초과"): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + ) + + +# 결제 실패 - 거래정지 카드 +def test_pay_with_card_fail_suspension_card(mock_server, httpserver): + from SRT import SRT, SRTResponseError + + register_mock_response(httpserver, "/login", "login_success.json") + register_mock_response( + httpserver, "/payment", "pay_with_card_fail_suspension_card.json" + ) + + srt = SRT("010-1234-1234", "password") + + with pytest.raises(SRTResponseError, match="거래정지카드"): + srt.pay_with_card( + mock_reservation, "12", "1221", 0, "981204", "0000000000000000", "J" + )