srt_mobile_api.client
¶
SrtClient — 이 패키지에 하나뿐인 진입점.
폼을 만드는 일은 srt_mobile_api.payloads, HTML 을 값으로 바꾸는 일은
srt_mobile_api.parsers 에 있고 이 모듈은 그 둘을 라우트 하나에 엮습니다.
공개 메서드는 읽기와 상태변경(6개: reserve, reserve_transfer, cancel,
pay_with_card, refund, register_discount_coupon) 둘로 나뉩니다. 후자는
require_mutation_consent 로 시작하고,
srt_mobile_api.safety 가 전송 직전에 다시 검사합니다.
SrtClient
¶
SRT 모바일 앱(v2.0.41)이 쓰는 HTTP API 를 그대로 호출하는 클라이언트.
리버스 엔지니어링한 앱 경로에 앱과 같은 헤더·폼으로 요청을 보내고, 응답을
models 의 불변 데이터클래스로 돌려줍니다.
인자 없이 SrtClient() 로 만들면 바로 동작합니다.
SrtConfig 는 타임아웃·User-Agent·device key 를
바꿀 때만 넘기며, 정규 SRT 오리진 두 개 말고는 가리키지 못합니다.
transport/clock/sleep 은 테스트용 주입점입니다.
조회는 login 뒤에 바로 부르면 됩니다. 상태를 바꾸는 여섯 메서드
(reserve, reserve_transfer, cancel,
pay_with_card, refund, register_discount_coupon)는
MutationConsent 를 키워드로 반드시 받고,
동의가 없으면 아무것도 만들기 전에
SrtMutationNotAllowedError 로 막힙니다. 기본
동의는 dry_run=True 라서 보낼 폼을 마스킹해 담은
MutationPreview 만 돌려주고 통신하지 않습니다.
정리는 close 로 합니다. with 문은 지원하지 않으며, 쿠키까지
버리려면 clear_session 을 따로 부르면 됩니다.
from srt_mobile_api import PassengerCounts, SrtClient, TrainSearchQuery
client = SrtClient()
client.login("you@example.com", "password")
try:
query = TrainSearchQuery(
departure_station_code="0551", # 수서
arrival_station_code="0015", # 동대구
departure_date="20260809", # YYYYMMDD
departure_time="060000", # HHMMSS, 이 시각부터
passengers=PassengerCounts(adult=1),
)
for train in client.search_trains(query).trains:
print(train.train_no, train.departure_time, train.arrival_time)
finally:
client.close()
close
¶
내부 HTTP 커넥션 풀을 닫습니다.
세션 쿠키와 저장된 SrtSession 은 지우지 않습니다
— 그것은 clear_session 의 일입니다. 서버로는 아무것도 보내지 않습니다.
with 문은 지원하지 않으므로 직접 부르거나 try/finally 로 감쌉니다.
login
¶
아이디와 비밀번호로 로그인하고 SrtSession 을 돌려줍니다.
login_id 는 이메일·휴대폰번호·회원번호 중 아무거나 됩니다. 형태를 보고
srchDvCd 를 고릅니다 — 이메일이면 "2", 01X 로 시작하는 휴대폰이면
"3"(하이픈은 떼고 보냅니다), 그 외에는 회원번호로 보고 "1".
login_type 을 주면 그 값이 이깁니다.
호출하면 먼저 기존 쿠키를 버립니다. 응답의 userMap.RTNCD 가 "Y" 여야
성공이고, 그 뒤 /main/main.do 와 /ara/ara0101v.do 를 실제로 읽어
인증됐는지 확인한 다음에야 세션을 저장합니다.
실패는 전부 SrtAuthError 입니다 — 없는 회원과
틀린 비밀번호를 서버가 구분해 주지 않습니다. IP 차단은 그 하위 클래스인
SrtIpBlockedError 이고, 어떤 이유로 실패하든
세션은 다시 비워집니다.
clear_session
¶
쿠키 항아리를 비우고 저장된 세션을 버립니다.
서버로는 아무것도 보내지 않습니다. 로그아웃 요청이 아니라 클라이언트 쪽 상태만 지우는 것이라, 서버 세션은 스스로 만료될 때까지 남아 있습니다.
logout
¶
clear_session 의 다른 이름 — 로컬 쿠키와 세션만 버립니다.
서버에 로그아웃 요청을 보내지 않습니다. 이름 때문에 서버 세션까지 끊긴다고 읽히기 쉬워서 적어 둡니다.
get_main
¶
로그인 뒤 메인 화면의 HTML 을 파싱 없이 돌려줍니다.
GET /main/main.do?deviceId=<device_key>. 반환
HtmlPage 에는 원본 raw 와 태그를 걷어낸
text 뿐이고 구조화된 필드는 없습니다.
로그인 세션이 필요합니다. 서버가 로그인 페이지를 돌려주면 HTTP 계층이
SrtSessionExpiredError 를 내고 저장된 세션도
같이 비워집니다.
get_booking_page
¶
승차권 예매 화면(GET /ara/ara0101v.do)의 HTML 을 파싱 없이 돌려줍니다.
검색·예약 폼이 서버에서 렌더링돼 실려 오는 페이지입니다. 이 클라이언트는 검색과
예약 때 이 경로를 Referer 로 쓰고, 여기 박혀 있는 MY SRT 메뉴에서
get_discount_coupons 와 get_public_discounts 의 경로를 찾았습니다.
반환은 파싱되지 않은 HtmlPage 입니다. 로그인 세션이
필요하고, 미인증 응답은
SrtSessionExpiredError 로 올라옵니다.
get_notice_list
¶
공지사항 응답을 서버가 준 dict 모양 그대로 돌려줍니다.
POST /main/noticeList.do (pageId=MB0101000000). 모양을 바꾸지 않는
구버전 인터페이스이고, 타입이 붙은 행이 필요하면
get_typed_notice_list 를 씁니다 — 요청은 완전히 같고 파싱만 다릅니다.
공지가 하나도 없으면 noticeList 가 빈 리스트인 정상 응답이며 예외가 아닙니다.
get_typed_notice_list
¶
같은 공지 조회를 NoticeListResult 로 돌려줍니다.
get_notice_list 와 요청은 완전히 같습니다. 차이는 반환뿐 — 각 행이 불변
Notice 이고, SUBJ/BODY/CREATE_DATE/
IS_MAIN/IS_NOTICE/PAGE_ID 가 문자열이 아니면
SrtProtocolError 로 거릅니다. 서버 응답 전체는
raw 에 남습니다.
공지가 없으면 빈 notices 가 정상입니다.
get_ticket_list
¶
로그인한 계정의 승차권 확인 페이지 HTML 을 파싱 없이 돌려줍니다.
GET /atc/selectListAtc14017_n.do?pageNo=<page_no>, 발권이 끝난 승차권을
보여 주는 화면입니다. 예약을 구조화된 행으로 받으려면 get_reservations
를 씁니다 — 그쪽은 이웃 경로 Atc14016 을 POST 해 JSON 을 받는 다른 읽기입니다.
반환 HtmlPage 에는 승차권이 파싱돼 있지 않습니다.
로그인이 필요하고, 이 읽기는 응답이 로그인 페이지인지 명시적으로 확인해
SrtSessionExpiredError 를 냅니다. 만료된 세션이
빈 승차권 목록으로 읽히는 일을 막기 위한 것입니다.
get_discount_coupons
¶
계정이 보유한 할인쿠폰 목록을 읽습니다.
GET /apa/selectListApa03020_n.do, 파라미터 없음. 반환은
DiscountCouponList 이고 로그인이
필요합니다.
쿠폰이 하나도 없으면 빈 목록이 정상 반환입니다 — 페이지가 "보유한 쿠폰이
없습니다." 라고 말하는 경우입니다. 목록도 없고 그 문구도 없거나 둘이 동시에
있으면 SrtProtocolError 입니다.
같은 페이지에 등록 폼이 있지만 등록은 다른 경로
(POST /arb/selectListArb02A01_n.do)이고
register_discount_coupon 의 일입니다.
v2.0.41 번들에는 /apa/ 라우트가 없고 서버가 렌더링하는 MY SRT 메뉴에서
찾은 경로입니다. 확인된 것은 빈 상태뿐이고 채워진 상태는 미검증입니다 —
필드명 근거는 DiscountCoupon 참고.
get_public_discounts
¶
계정이 승인받은 공공할인 자격을 읽습니다.
GET /common/ARA/ARA0301V/view.do, 할인 승차권 페이지, 파라미터 없음.
반환은 PublicDiscountPage 이고 로그인이
필요합니다.
자격이 하나도 없는 계정도 정상 반환입니다 —
is_eligible 가 False
로 올 뿐입니다. 자격을 가진 계정의 응답은 미검증입니다.
검색은 하지 않습니다. 이 페이지가 곧 검색 폼이고, 검색은
search_public_discount_trains 가 따로 보냅니다.
공공할인의 승객 어휘는 PassengerCounts 보다
넓습니다 — 청소년(04) 아래에는 어느 commCode.js 에도 없는
psgTpCd6 이 있고, discounts 에 기록만 해
두었습니다.
get_reservations
¶
계정의 예약/발권 목록을 구조화된 행으로 읽습니다.
POST /atc/selectListAtc14016_n.do. 반환은
SrtReservationListResult 이고 로그인이
필요합니다.
예약을 열거할 수 있는 유일한 읽기입니다. PNR 을 잃어버린 홀드로
돌아가는 길이기도 해서, 파서는 모양이 예상과 달라도 PNR 만은 건져냅니다
(parse_reservation_list_response).
HTML 을 주는 get_ticket_list(Atc14017 GET)와는 다른 읽기입니다.
page_no 는 pageNo 로 나가고, 전체 페이지 수는 결과의
total_page_count
(totPageCnt)입니다.
예약이 없으면 빈 결과가 정상입니다. 서버는 strResult="SUCC" /
IRZ000005 / "조회할 자료가 없습니다." 를 trainListMap: [] 와 함께
보냅니다. 채워진 모양은 미검증이고 행 필드명은 srtgo(srt.py:1069-1082)
출처입니다.
get_station_selector
¶
역 선택 팝업 HTML(POST /common/ARA/ARA0501P/view.do).
역 코드·이름 표는 stations 에 이미 있으므로 역을
고르기 위해 이 호출을 할 필요는 없습니다. 로그인 필요.
get_station_map_selector
¶
노선도 방식 역 선택 팝업 HTML(POST /common/ARA/ARA0502P/view.do).
인자 없음 — 고정값. 역 코드는 stations 에서 얻는 편이
낫습니다. 로그인 필요.
get_date_selector
¶
날짜 선택 팝업 HTML(POST /common/ARA/ARA0401P/view.do).
date 는 YYYYMMDD, 아니면 ValueError. 로그인 필요.
get_passenger_selector
¶
승차인원 선택 팝업 HTML(POST /common/ARA/ARA0901P/view.do).
검색·예약과 무관합니다. 로그인 필요.
get_seat_option_selector
¶
get_seat_option_selector(*, request_seat_attr_code='015', location_seat_attr_code='000', seat_name='일반/기본')
좌석 위치·속성 선택 팝업 HTML(POST /common/ARA/ARA0701P/view.do).
기본값은 앱의 초기 상태와 동일. 실제 좌석 확인은 get_seat_page /
get_seat_grid. 로그인 필요.
get_train_group_selector
¶
열차 종류 선택 팝업 HTML(POST /common/ARA/ARA0201V/view.do).
기본 "109"/"전체" 는 TrainSearchQuery
기본값과 같습니다. 로그인 필요.
get_mutual_verification
¶
상호검증값(mutMrkVrfCd)을 발급받습니다.
POST /ara/selectListAra10130_n.do, 본문 없음. 앱은 이 값을 코레일 연계
(KTX) 예약 분기에서 씁니다(ara1001l.js:229-241). 이 라이브러리의 예약
경로는 쓰지 않으므로 발급까지가 전부입니다.
로그인이 필요 없습니다 — 다른 읽기와 달리 공개 경로입니다.
반환은 MutualVerificationResult 입니다.
strResult 가 "FAIL" 이면 SrtAppError
계열, mutMrkVrfCd 가 비어 있으면
SrtProtocolError 입니다. msgCd 는
정보성이라 없어도 되고, 모델에 없는 wctNo/uuid/cgPsId 는
raw 로 닿을 수 있습니다.
search_trains
¶
직통 개인 검색 — 한 페이지 분량의 열차 행을 돌려줍니다.
query 의 역 코드, 날짜(YYYYMMDD), 시각(HHMMSS, 그 시각부터),
인원으로 POST /ara/selectListAra10007_n.do 를 보내고
TrainSearchResult 를 돌려줍니다. 로그인이
필요합니다.
결과가 없으면 빈 목록이 아니라 예외입니다 —
SrtNoResultsError. 직통은 없지만 환승이 되는
구간이면 그 하위 클래스인
SrtNoDirectTrainError(WRD000061)가 오고,
같은 query 를 search_transfer_trains 에 넘기면 됩니다.
인원이 10명 이상이면 요청 전에 ValueError 입니다
(ara0101v.js:562-567). 그때는 search_group_trains 를 쓰면 됩니다.
NetFunnel act_10 대기열은 앱처럼 통과하고, NET000001 이 오면 한 번만
다시 시도합니다. 다음 페이지는 iter_train_search_pages 입니다.
search_group_trains
¶
단체(10명 이상) 검색 — 같은 질의를 단체 경로로 보냅니다.
POST /ara/selectListAra10082_n.do. 경로를 가르는 것은 grpDv 하나이고
(ara1001l.js:174-181), 본문은 grpDv="1" 과 psgNum 말고는
search_trains 와 같습니다. 반환도 같은
TrainSearchResult 입니다. 로그인이 필요합니다.
인원이 10명 미만이면 요청 전에 ValueError — 앱도 클라이언트에서
막습니다(ara0101v.js:551-554).
이 라이브러리는 단체 예약은 하지 않습니다. 잔여석과 운임 조회까지가 전부이고, 단체 예약 경로는 등록조차 돼 있지 않습니다.
빈 결과가 예외라는 것과 NetFunnel 처리는 search_trains 와 같습니다.
search_public_discount_trains
¶
할인 승차권 검색 — 계정이 승인받은 공공할인이 붙는 열차를 찾습니다.
POST /ara/selectListAra10131_n.do. 읽기이며 일반 검색 ajax 와 같은 자격으로
READ_ONLY_ROUTES 에 자기 23필드 계약과 함께
등록돼 있습니다. 반환은 TrainSearchResult
이고 로그인이 필요합니다.
한 번도 보내 본 적이 없습니다. 필드명·값·응답 키는 서버가 렌더링해 주는 할인 승차권 페이지에서 읽은 것이라 요청 모양에는 근거가 있고 효과에는 없습니다.
discount 의 PBL_DISC_CD 는 알려진 값 범위(01 다자녀 … 06 3세대
동행할인, 그리고 07/08 분기)이고 TGT_DTRM_YN 은 늘 "Y"
입니다. PBL_DISC_NM 은 ajax 폼에 필드가 없어 전송되지 않고,
PBL_DISC_MG_NO 는 서버가 발급하는 승인번호라 호출자가 넘깁니다(기본
"").
승객 타입 구성은 전송되지 않습니다. 이 폼은 인원 합계 psgNum 만 싣고
psgTpCd/psgInfoPerPrnb 가 없어서 청소년·유아는 합계만 바꿉니다.
page_cursor 는 gdNo 이고 첫 페이지는 "" 입니다. 종료 조건
(trainListMap[0].fllwPgExt)을 본 적이 없어 자동으로 돌지 않습니다.
NetFunnel 자리는 잡았다 반납하지만 이 경로의 폼에는 netfunnelKey 필드가
없어 키는 나가지 않습니다.
알 수 없는 할인 코드이거나 다자녀·3세대 동행할인인데 인원이 3명 미만이면
어떤 I/O 도 하기 전에 ValueError 입니다(페이지 규칙 rsv071).
search_transfer_trains
¶
환승 검색 — 짝지어진 TransferItinerary 를 돌려줍니다.
요청은 search_trains 와 거의 같습니다. 경로가 같고, 본문 차이는
chtnDvCd 가 "1" 에서 "2" 로 바뀌는 것뿐이며, 하이드레이션 GET 에
jrnyTpCd="14"/jrnyCnt="2" 가 더 붙습니다(ara0101v.js:302-303).
로그인이 필요합니다.
별도 메서드인 이유는 결과입니다. 서버가 주는 행은 한 여정의 절반
인데 표시가 없어서, 그대로 reserve 에 넘기면 환승역까지만 끊긴
승차권이 나옵니다. 그래서 짝짓기를 거친
TransferItinerary 와 reserve_transfer
쌍으로 갈라 두었습니다.
반환 TransferSearchResult 에는 짝지어진
itineraries, 짝이 맞지 않아 이유와 함께 남겨 둔 unpaired, 손대지 않은
원본 search 가 함께 들어 있습니다. 규칙은
pair_transfer_itineraries 참고.
행은 있는데 하나도 짝지어지지 않으면
SrtProtocolError 입니다. 페이징은 지원하지
않습니다 — 둘째 커서 fllwPgExt2 는 늘 null 이라 용도를 모릅니다.
여기 닿는 자연스러운 길은 직통 검색의 WRD000061, 즉
SrtNoDirectTrainError 입니다.
iter_train_search_pages
¶
검색 결과를 페이지 단위로 게으르게 넘기는 제너레이터를 돌려줍니다.
첫 페이지는 search_trains(group=True 면
search_group_trains)와 같은 요청이고, 이어지는 페이지는 직전 페이지
마지막 열차의 출발시각을 dptTm 커서로 밀어 올려 다시 POST 합니다.
TrainSearchResult 를 한 페이지씩 내놓습니다.
max_pages 는 양의 정수여야 하고(bool 은 거부) 아니면
ValueError 입니다. 이 검사만은 제너레이터를 돌리기 전 호출 즉시
일어납니다. 기본 10 페이지에서 멈추며, 그전이라도 서버가 다음 페이지 없음을
말하거나 페이지가 비면 끝납니다. 커서가 반복되거나 나아가지 않으면
SrtProtocolError 입니다.
NetFunnel 자리는 걷기 전체가 끝날 때 반납됩니다. 도중에 제너레이터를
버려도(GeneratorExit) 반납됩니다.
환승 검색에는 쓸 수 없습니다 — search_transfer_trains 참고.
get_seat_page
¶
좌석 선택 1단계 — 호차 목록이 담긴 좌석선택 페이지를 읽습니다.
POST /arc/selectListArc02012_n.do. train 은 검색이 돌려준 완전한
TrainSummary 행이어야 합니다. 반환
SeatSelectionPage 는 HTML 에
cars(호차 옵션)가 붙은 것이고,
호차 하나의 좌석 배치는 get_seat_grid 가 이어서 읽습니다.
choiceSeatCount 는 상수가 아니라 일행 인원입니다 — 앱은
lfn_getRsv("totPrnb") 를 보냅니다(ara1001l.js:1511). 그래서 passengers
를 넘기면 거기서 인원을 뽑습니다. seat_count 는 명시적 override 이고 둘 다
주면 이쪽이 이깁니다. 둘 다 없으면 "1".
로그인이 필요합니다.
get_seat_grid
¶
get_seat_grid(train, car_number, cabin_class='1', seat_count=None, *, passengers=None, seat_attr_code='015')
좌석 선택 2단계 — 호차 하나의 좌석배치도를 읽습니다.
POST /arc/selectListArc02011_n.do. get_seat_page 가 "어느 호차에
자리가 있나" 를 답한다면 이쪽은 "어느 좌석이고, 고를 수 있나" 를 답합니다.
반환 SeatGrid 의
choose 로
SeatDesignation 을 만들어 reserve 의
designated_seats 에 넘길 수 있습니다.
car_number 는 cars 의
car_number 값이고, passengers/seat_count/cabin_class 는
get_seat_page 에 준 것과 같게 주면 됩니다.
열차번호를 다섯 자리로 0 채움해 보냅니다 — 그 하나가 좌석배치도와
147바이트짜리 경고 껍데기를 가릅니다
(SEAT_TRAIN_NUMBER_LENGTH).
서버가 거절하면("0#<메시지>")
SrtSeatUnavailableError 이지 프로토콜 오류가
아닙니다. 로그인이 필요하고, 응답이 로그인 페이지면
SrtSessionExpiredError 입니다.
get_timetable
¶
열차 하나의 정차역별 시간표를 읽습니다.
POST /ara/selectListAra12009_n.do, 어느 열차인지는 검색 행 train 이
말합니다. 반환 TimetablePage 는 HTML 에 정차역 행
(TimetableRow)이 붙은 것입니다.
역 이름은 HTML 안에 없습니다. 서버는 코드만 내려보내고 WebView 가
getStationNameByCode('0551') 로 채웁니다. 이 파서는 앱과 같은 표를 담은
srt_mobile_api.stations 로 이름을 해결합니다.
로그인이 필요합니다.
get_fare
¶
열차 한 다리의 운임·요금 표를 읽습니다.
POST /ara/selectListAra13010_n.do. passengers 를 생략하면 성인 1명
(PassengerCounts 의 기본값)으로 묻습니다. 반환은
FarePage.
둘째 다리 표는 읽지 않습니다. 이 페이지는 환승 여정용이라 trainPayInfo1
(첫 다리)과 trainPayInfo22(둘째 다리) 두 표를 렌더링하는데, 이 요청은 한
다리만 말할 수 있어 둘째 표는 금액이 0원 인 자리표시자로 옵니다. 그것까지
읽으면 "어른 일반실 → 0" 처럼 돈에 대해 조용히 틀린 답이 나옵니다.
로그인이 필요합니다.
reserve
¶
reserve(train, *, consent, passengers=None, seat_type=GENERAL_FIRST, window_seat=None, netfunnel_key=None, standby=False, round_trip=False, designated_seats=None, seat_attr_code=None)
열차 한 편에 개인예약 홀드를 만듭니다. consent 게이트.
POST /arc/selectListArc05013_n.do. 기본 dry_run=True 면
MutationPreview 만 돌려주고 네트워크 무사용.
dry_run=False 면 실전송 → SrtReservationHold.
재시도 없음(이중예약 방지). 파싱 실패해도 PNR 있으면 최소 홀드 건짐.
standby = 예약대기(jobId=1102, ara0101v.js:90, ara1001l.js:1445-1448).
round_trip = rtnDv=1; 두 번째 요청을 보내지 않으므로 각각 호출.
designated_seats = 좌석지정(jobId=1103).
확인된 조합: 성인 1명·1여정·일반실·직통. 나머지 미검증.
reserve_transfer
¶
reserve_transfer(itinerary, *, consent, passengers=None, seat_type=GENERAL_FIRST, window_seat=None, netfunnel_key=None, seat_attr_code=None)
환승 예약 — 두 다리를 한 요청으로 예약합니다. consent 게이트.
경로도 consent 카테고리도 reserve 와 같습니다
(/arc/selectListArc05013_n.do, "reserve"). jrnyTpCd="14" +
jrnyCnt="2" (ara0101v.js:302-303). 전송 경로·안전 속성 전부 동일.
인자가 TransferItinerary 인 것은 이음새
검사를 생성 시점에 하기 위해서입니다. round_trip·standby·단체·좌석지정
조합 불가.
2026-07-31 실서버 확인. 동대구(0015)→목포(0041), 열차 304 + 653, 오송
환승, 성인 1명으로 SUCC/IRR000018 을 받았습니다(PNR
320260733059548). 유추였던 슬롯 2 키 5개가 그대로 받아들여졌습니다.
확인된 것은 1인·일반실·직통 아님·편도 한 건입니다.
취소는 jrnyCnt="2" 로 해야 합니다. 이 예약을 PNR 문자열만으로
취소하면 cancel 이 jrnyCnt="1" 을 보내고 서버가
FAIL/ERR800052 로 거절합니다 — 홀드는 그대로 남습니다. 홀드 객체를
넘기면 자기 값을 기억하므로 맞고, PNR 만 있으면 journey_count="2" 를
직접 줘야 합니다.
환승 조회 결과에는 코레일 열차가 섞여 옵니다. 같은 날 부산→익산 의 환승
5개 중 3개는 구간 하나 이상이 KTX(stlbTrnClsfCd="00") 또는
KTX-산천("07") 이었고, 그런 여정을 넘기면
personal_reservation_payload 가
reservation requires an SRT train (service_class_code '17') 로 전송 전에
거절합니다. 예약할 수 있는 것은 양쪽 구간이 다 "17" 인 여정뿐입니다.
cancel
¶
미결제 예약을 취소합니다. consent 게이트.
POST /ard/selectListArd02045_n.do, 본문 pnrNo/jrnyCnt/rsvChgTno.
reservation 은 SrtReservationHold 또는
PNR 문자열. journey_count 생략 시 홀드가 기억하는 값(환승 "2"), 맨
PNR 이면 "1". 기본 dry_run=True.
register_discount_coupon
¶
할인쿠폰을 계정에 등록합니다(할인쿠폰 등록). consent 게이트가 있고, 전송은 막혀 있습니다.
POST /arb/selectListArb02A01_n.do, 본문은 {dscp_no, dscp_pwd}.
get_discount_coupons 가 읽는 페이지의 나머지 절반으로, 그 페이지의
couponReg() 가 #couponInfo 를 여기로 보내며
resultMap[0].RTNCD/.MSG 를 읽습니다.
consent 카테고리는 다섯 번째인 "coupon" 입니다. 쿠폰 등록은 소지
크리덴셜을 소진하는 일이라 reserve 나 payment 에 얹지 않았습니다.
require_mutation_consent(consent, "coupon") 과 인증 세션이 필요하고, 두
검사 모두 dry_run 분기보다 앞입니다.
보낼 수 없습니다. "coupon" 은
SRT_LIVE_MUTATION_CATEGORIES 밖이라
dry_run=False 로 불러도 전송 계층이
SrtMutationNotAllowedError 로 거절합니다.
미리보기는 dscp_no 와 dscp_pwd 를 모두 가립니다
(SENSITIVE_KEYS). 입력 검증은 페이지 자신의
규칙으로, 번호는 숫자만 최대 10자리, 비밀번호는 최대 4자이고 둘 다 비어 있으면
안 됩니다.
성공은 요청이 접수됐다는 뜻이지 쿠폰이 보인다는 뜻이 아닙니다(쿠폰등록
요청을 완료하였습니다 … 바로 조회 되지 않을 수 있습니다). 등록 직후
get_discount_coupons 가 아무것도 못 봐도 정상일 수 있습니다.
경로·필드명·응답 키는 서버가 렌더링하는 실제 페이지에서 읽었습니다. v2.0.41
번들에는 /arb/ 라우트가 없고, 번들이 뒷받침하는 것은 어휘뿐입니다
(js/common/messages.js:111-113, sub/main.html:475 의 DSCP_YN).
pay_with_card
¶
미결제 PNR 을 카드로 결제합니다. consent 게이트.
POST /ata/selectListAta09036_n.do, 31개 필드. 세션에 회원번호 필수.
reservation 은 get_reservations 행. 금액은 rcvdAmt 에서만.
기본 dry_run=True → 민감정보 가린 미리보기. dry_run=False 면
require_card_kind_claim 추가 필요.
확인된 조합: 1여정·성인 1명·일반실·개인카드·일시불(SUCC/IRT000000).
Ata09* 전체가 v2.0.41 번들 0-hit. 출처가 얇은 경로입니다.
get_refund_ticket_info
¶
발권된 승차권의 환불 식별 정보를 읽습니다 — 환불 2단계 중 1단계.
POST /atc/getListAtc14087.do, 본문 없음. 승차권을 지목하는 것은
Referer 로 실려 가는 /common/ATC/ATC0201L/view.do?pnrNo=<PNR>
입니다. 응답의 outDataSets.dsOutput1[0] 이
SrtRefundTicketInfo 가 되고, 그대로
refund 에 넘기면 됩니다. 로그인이 필요합니다.
consent 게이트가 아니라 읽기 경로를 탑니다
(READ_ONLY_ROUTES). 그 분류는 증명이 아니라
추론입니다 — 서버가 부작용 없다고 말해 준 적은 없습니다.
pnr_no 는 ASCII 영숫자와 하이픈 1~32자만 받고, 어기면 요청 전에
ValueError 입니다. Referer URL 에 그대로 끼워 넣는 값이라 다른
문자는 질의 파라미터를 몰래 붙일 수 있습니다. 이 Referer 는 이 요청에만
붙습니다.
없는 PNR 도 오류가 아니라 정상 업무 응답(WRT300005, "조회자료가
없습니다.")으로 돌아옵니다. 경로는 v2.0.41 번들에 없고 참조 구현 하나만 이걸
압니다.
refund
¶
발권까지 끝난 승차권을 환불합니다 — 2단계 흐름의 둘째. consent 게이트가 있습니다.
POST /atc/selectListAtc02063_n.do, 7개 필드.
require_mutation_consent(consent, "refund") 와 인증 세션이 필요하고, 두
검사 모두 dry_run 분기보다 앞입니다. 성공하면
SrtRefundResult 입니다.
ticket_info 는 get_refund_ticket_info 가 준 것입니다. 두 단계를
일부러 합치지 않았습니다 — 이 메서드가 스스로 1단계를 부르지 않으므로
거절당한 환불은 아무 요청도 남기지 않고, dry run 이 통신을 전혀 하지
않습니다.
dry_run=True(기본)면 반환비밀번호·구매자명·PNR 을 가리고 판매 식별자는
남긴 MutationPreview 만 돌려줍니다.
확인된 것은 1여정·성인 1명 한 건이고(SUCC/IRT200277), 성공 뒤 계정에는
예약도 승차권도 남지 않습니다.
출처는 pay_with_card 보다도 얇습니다 — 참조 구현 하나에만 있고
Atc02063 은 v2.0.41 번들에서 0-hit 입니다. 필드명 tkRetPwd·psgNm
은 srtgo 철자이고 서버가 그대로 받습니다. 앱 쪽 철자 retPwd/buyPsNm
은 로컬 승차권 캐시 핸들러의 것이라 이 요청과 무관합니다.