public-transport / hafas-client

JavaScript client for HAFAS public transport APIs.
ISC License
263 stars 52 forks source link

DB: retrieve ticket & journey using booking reference & last name #268

Closed derhuerst closed 1 year ago

derhuerst commented 2 years ago

The DB Navigator app has the option, given a booking reference and the last name of the person who booked, to retrieve the ticket(s) as well as the journey it has been booked for.

Because the journey UI looks as in the rest of the app, I assume that the API behind this API is HAFAS-flavored.

What needs to be done to find this API, as documented in the (somewhat unrelated) Writing a profile guide:

  1. Get an iOS or Android device and download the "official" app.
  2. Configure a man-in-the-middle HTTP proxy like mitmproxy.
  3. Record requests of the app.

Please share the recorded requests in their entirety, if possible, but make sure to redact personal details, or e.g. tokens that they can be retrieved with! Then, we can figure out if a client for this API belongs in here, or in a separate library.

derhuerst commented 2 years ago

@vkrause @Adwirawien @weiland @juliuste Do you know anything about this API?

hannsadrian commented 2 years ago

Seems interesting, sadly I know as much as nothing! However, I think that it would be nice to know the endpoint for receiving journey info based on the ticket and name regardless of it being hafas related. If there only was a way to do this without purchasing an expensive ticket every time.

Does anyone have a 9-Euro Ticket linked to the DB Navigator app? It would be good to know if you are able to "book" trips with it and thus have a 9€ option of generating bigger amounts of reverse engineerable tickets even if its just for testing.

I personally don't know about the case with the 9 Euro Ticket. But from the Bahncard 100 I know that you can't "book" any journeys with it. If it's the same with 9 Euro I guess we are dependent on journeys that we have to really make.

derhuerst commented 2 years ago

If there only was a way to do this without purchasing an expensive ticket every time.

It seems to work with tickets/journeys from the past, at least with my Flex ticket from 2 weeks ago.

vkrause commented 2 years ago

This would certainly be very interesting to have, I haven't looked into anything needing user authentication in more detail yet though.

From the structure in the APK I would have guessed that tickets are out of scope for Hafas though:

envake commented 2 years ago

I looked into it with mitmproxy. The app is requesting fahrkarten.bahn.de and it seems to be an XML-RPC, probably the same that is used on this website https://fahrkarten.bahn.de/privatkunde/start/start.post?scope=bahnatsuche) Providing the ticket id and the last name, it is indeed possible to not only retrieve the full journey but every other data necessary to display the full online ticket like the full name, qr code and this weird generated background image that makes the ticket look 'real'.

I used the Navigator app on iOS 15 with mitmproxy in regular mode + ssl setup and tested this with old ticket numbers and last names, which worked well using the replay feature in mitmproxy. Exporting the curl command and executing it didnt work for me and only gives back an html page that displays an error. I dont know what the tnr parameter does, it seems to be generated by the app but you can just use a random string here. If you want to test this with you own tickets, just replace YOUR_TICKETNUMBER and YOUR_LASTNAME with some valid information.

In the response I replaced some personal and irrelevant information (like css and base64 PNGs) with 'abc'.

Request

POST /mobile/dbc/xs.go? HTTP/1.1
Host: fahrkarten.bahn.de
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Accept: */*
User-Agent: DB%20Navigator/13056071 CFNetwork/1333.0.4 Darwin/21.5.0
Accept-Language: de-DE,de;q=0.9
Content-Length: 255
Accept-Encoding: gzip, deflate, br

<?xml version="1.0"?>
<rqorderdetails version="1.0"><rqheader tnr="75547FAD-213F-3A42-AGC7-62DB101B" ts="2022-07-15T17:40:13" l="de" v="22060000" d="iPhone13,1" os="iOS_15.5" app="NAVIGATOR"/><rqorder on="YOUR_TICKETNUMBER"/><authname tln="YOUR_LASTNAME"/></rqorderdetails>

Response

HTTP/1.1 200 OK
Date: Fri, 15 Jul 2022 17:59:18 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: AWSALB=abc; Expires=Fri, 22 Jul 2022 17:59:17 GMT; Path=/
Set-Cookie: AWSALBCORS=abc; Expires=Fri, 22 Jul 2022 17:59:17 GMT; Path=/; SameSite=None; Secure
Server: Apache
X-ORACLE-DMS-ECID: c45d72d8-6e6f-49eb-9756-cda3768e83e9-0098f908
X-ORACLE-DMS-RID: 0
Set-Cookie: DB4-pb-asid=abc; path=/;SameSite=none; secure; HttpOnly
Set-Cookie: DB4-pb-gsid=abc; domain=.bahn.de; path=/; SameSite=none;; secure; HttpOnly
Set-Cookie: DB4-pb-shopId=.4c; path=/; SameSite=none;; secure
Strict-Transport-Security: max-age=16070400; includeSubDomains

be2c
<?xml version="1.0" encoding="UTF-8"?><rporderdetails version="1.0"><rpheader tnr="65557FAD-203F-4A43-BFA7-24CB181A" ts="2022-07-15T19:59:17"/><order cdt="2022-05-25T17:25:02" cid="51451984" ddt="2023-07-25T17:25:02" fkat="5" hkey="d8cec5fd022181d8a76f39132bebf429" ldt="2022-05-25T17:25:05" on="abc" pg="1" sdt="2022-05-25T17:39:00" version="17" vfrom="2022-05-25T00:00:00" vto="2022-05-25T23:59:59" zweg="1"><schedulelist><out posnr="1"><txt>Zwickau(Sachs)Hbf 17:39 - Jena West 19:36 </txt><trainlist><train tid="C0-0.0" tn="RB 74069" type="T"><zugnr>74069</zugnr><gat>RB</gat><sci>N</sci><dep dt="2022-05-25T00:00:00" t="17:39:00"><n>Zwickau(Sachs)Hbf</n><nr>8010397</nr><ebhf_name>Zwickau(Sachs)Hbf</ebhf_name><ebhf_nr>8010397</ebhf_nr><ptf>4</ptf><plz>8056</plz><x>12474717</x><y>50714667</y></dep><arr dt="2022-05-25T00:00:00" t="17:54:00"><n>Glauchau(Sachs)</n><nr>8010129</nr><ebhf_name>Glauchau(Sachs)</ebhf_name><ebhf_nr>8010129</ebhf_nr><ptf>1</ptf><plz>8371</plz><x>12548645</x><y>50828525</y></arr></train><train tid="C0-0.1" tn="RE  3666" type="T"><zugnr>3666</zugnr><gat>RE</gat><sci>N</sci><dep dt="2022-05-25T00:00:00" t="18:07:00"><n>Glauchau(Sachs)</n><nr>8010129</nr><ebhf_name>Glauchau(Sachs)</ebhf_name><ebhf_nr>8010129</ebhf_nr><ptf>3</ptf><plz>8371</plz><x>12548645</x><y>50828525</y></dep><arr dt="2022-05-25T00:00:00" t="19:36:00"><n>Jena West</n><nr>8011957</nr><ebhf_name>Jena West</ebhf_name><ebhf_nr>8011957</ebhf_nr><ptf>1</ptf><plz>7745</plz><x>11577846</x><y>50923289</y></arr></train></trainlist></out></schedulelist><tcklist><tck posnr="2"><htdata><ht name="barcode" pos="1" type="image/png;base64">data:image/png;base64,
abc
</ht><ht name="httext" pos="2" type="text/html;base64">abc
</ht><ht name="htstyle" pos="2" type="text/css">
        abc
    </ht><ht name="sichtmerkmal" pos="10" type="image/png;base64">data:image/png;base64,abc
</ht><ht name="pass" pos="0" type="application/vnd.apple.pkpass;base64">abc
</ht></htdata><mtk dir="1" status="8"><txt>Einfache Fahrt, Regio120 Ticket, 2. Kl., 1 Erw., Zwickau(Sachs)/Jena#</txt><zb>N</zb><iss>80</iss><tkey>abc</tkey><ot_nr_hin>699347036</ot_nr_hin><reisender_vorname>abc</reisender_vorname><reisender_nachname>abc</reisender_nachname><nvplist><nvp name="k_erm_db">-------</nvp><nvp name="anzerw">1</nvp><nvp name="anzkind">0</nvp><nvp name="klasse">S2</nvp><nvp name="preisartcodedb">R120</nvp><nvp name="status">8</nvp></nvplist></mtk></tck></tcklist><posinfolist><posinfo dir="1" dirlabel="none" posnr="2" shownr="1002" state="8" type="TCK" typeinfo="0" vfo="2022-05-25T00:00:00" vto="2022-05-25T23:59:59"><childlist><posinfo dir="1" dirlabel="none" posnr="1" shownr="1100" state="8" type="EVA" typeinfo="0" vfo="2022-05-25T17:39:00" vto="2022-05-25T19:36:00"><childlist/></posinfo></childlist></posinfo></posinfolist><txt>Zwickau(Sachs)Hbf - Jena West</txt></order></rporderdetails>

0

Im kind of new to this stuff but for me it looks like its not related to hafas and they just made a quick way to use the 'Auftragssuche' natively in their navigator app.

derhuerst commented 2 years ago

Thanks! To expand on this, this is the response for my ticket, nicely formatted (and redacted a bit using *):

curl -X POST 'https://fahrkarten.bahn.de/mobile/dbc/xs.go?' \
    -H 'User-Agent: DB%20Navigator/13056071 CFNetwork/1333.0.4 Darwin/21.5.0' \
    -H 'Accept-Language: de-DE,de;q=0.9' --compressed \
    -d '<?xml version="1.0"?><rqorderdetails version="1.0"><rqheader tnr="foo" ts="2022-07-15T22:29:00" l="de" v="22060000" d="iPhone13,1" os="iOS_15.5" app="NAVIGATOR"/><rqorder on="GU****"/><authname tln="R******"/></rqorderdetails>' \
    -s | xmllint --format -
<?xml version="1.0" encoding="UTF-8"?>
<rporderdetails version="1.0">
    <!-- `tnr` & `ts` are from my request -->
    <rpheader tnr="foo" ts="2022-07-15T22:30:50"/>
    <!--
    `cdt`/`ddt`/`ldt`: booking date+time?
    `on`: booking reference, from my request
    `pg`: page?
    `vfrom`: valid from
    `vto`: valid until
    `sdt`: journey start date+time
    -->
    <order
        cdt="2022-06-29T17:13:21"
        cid="4110****"
        ddt="2023-08-29T17:13:21"
        fkat="5"
        hkey="b59b05275bc67983273eb7**********"
        ldt="2022-06-29T17:13:22"
        on="GU****"
        pg="1"
        sdt="2022-06-29T17:18:00"
        version="17"
        vfrom="2022-06-29T00:00:00"
        vto="2022-06-30T23:59:59"
        zweg="1"
    >
        <schedulelist>
            <out posnr="1">
                <txt>Düsseldorf Hbf 17:18 - Hauptbahnhof, Darmst 19:16 </txt>
                <tp>
                    <depn>Düsseldorf+City</depn>
                    <arrn>Frankfurt(M)Flugh.</arrn>
                </tp>
                <trainlist>
                    <!-- `tid` looks a lot like the journey IDs & leg IDs from the bahn.de shop -->
                    <train tid="C0-0.0" tn="ICE  727" type="T">
                        <zugnr>727</zugnr>
                        <gat>ICE</gat>
                        <sci>Y</sci>
                        <dep dt="2022-06-29T00:00:00" t="17:18:00">
                            <n>Düsseldorf Hbf</n>
                            <nr>8000085</nr>
                            <ebhf_name>Düsseldorf Hbf</ebhf_name>
                            <ebhf_nr>8000085</ebhf_nr>
                            <ptf>15</ptf>
                            <plz>40210</plz>
                            <x>6794317</x>
                            <y>51219960</y>
                        </dep>
                        <arr dt="2022-06-29T00:00:00" t="18:33:00">
                            <n>Frankfurt(M) Flughafen Fernbf</n>
                            <nr>8070003</nr>
                            <ebhf_name>F-Flughafen Fernbf.</ebhf_name>
                            <ebhf_nr>8091032</ebhf_nr>
                            <ptf>Fern 4</ptf>
                            <plz>60549</plz>
                            <x>8570181</x>
                            <y>50053169</y>
                        </arr>
                    </train>
                    <!-- C0-0.1 is probably not used because it represents the walking leg in my original HAFAS journey? -->
                    <train tid="C0-0.2" tn="Bus  AIR" type="T">
                        <zugnr>AIR</zugnr>
                        <gat>Bus</gat>
                        <dep dt="2022-06-29T00:00:00" t="18:44:00">
                            <n>Flughafen Terminal 1, Frankfurt a.M.</n>
                            <nr>102932</nr>
                            <ebhf_name>Flughafen Terminal 1</ebhf_name>
                            <ebhf_nr>102932</ebhf_nr>
                            <x>8571520</x>
                            <y>50051075</y>
                        </dep>
                        <arr dt="2022-06-29T00:00:00" t="19:16:00">
                            <n>Hauptbahnhof, Darmstadt</n>
                            <nr>104734</nr>
                            <ebhf_name>Hauptbahnhof, Darmst</ebhf_name>
                            <ebhf_nr>104734</ebhf_nr>
                            <plz>64293</plz>
                            <x>8631182</x>
                            <y>49872684</y>
                        </arr>
                    </train>
                </trainlist>
            </out>
        </schedulelist>
        <tcklist>
            <tck posnr="2">
                <htdata>
                    <ht name="barcode" pos="1" type="image/png;base64">data:image/png;base64,****</ht>
                    <ht name="httext" pos="2" type="text/html;base64">PHA+SGVycsKgIEphbm5pcyBSZWRtYW5uPGJyLz4KPGJyLz4KSUNFIEZhaHJrYXJ0
ZSwgRmxleHByZWlzIChFaW5mYWNoZSBGYWhydCk8YnIvPgpGYWhydGFudHJpdHQg
YW0gMjkuMDYuMjAyMjxici8+CkNpdHkgYW0gMjkuMDYuMjI8YnIvPgo8YnIvPgpL
bGFzc2U6IDIsIEVydy46IDE8YnIvPgpIaW5mYWhydDogRMO8c3NlbGRvcmYrQ2l0
eSAtIEZyYW5rZnVydChNKUZsdWdoLiwgbWl0IElDRTxici8+Cjxici8+CsOcYmVy
OiBWSUE6IEsqVFJPSSpTSUdCKk1UKkxNPGJyLz4KPGJyLz4KTnVyIGfDvGx0aWcg
bWl0IGFtdGxpY2hlbSBMaWNodGJpbGRhdXN3ZWlzICh6LkIuIFBlcnNvbmFsYXVz
d2VpcykuIERpZXNlciBpc3QgYmVpIGRlciBLb250cm9sbGUgdm9yenV6ZWlnZW4u
PGJyLz4KQmVpIEZhaHJrYXJ0ZW4gbWl0IEJhaG5DYXJkLVJhYmF0dCB6ZWlnZW4g
U2llIGJpdHRlIHp1c8OkdHpsaWNoIElocmUgZ8O8bHRpZ2UgQmFobkNhcmQgdm9y
Ljxici8+Cjxici8+ClN0b3JubyBrb3N0ZW5mcmVpIGJpcyAxIFRhZyB2b3IgMS4g
R2VsdHVuZ3N0YWcuPGJyLz4KPGJyLz4KQXVmdHJhZ3MtTnI6IEdVTlJMNDxici8+
Ckdlc2FtdHByZWlzOiA4MCw3MCBFVVI8YnIvPgpHZWJ1Y2h0IGFtIDI5LjA2LjIw
MjIgdW0gMTc6MTMgVWhyPGJyLz4KPC9wPg==
                    </ht>
                    <ht name="htstyle" pos="2" type="text/css">
                        body {
                            font-family: sans-serif;
                            font-size: 16px;
                            font-size: 1rem;
                            text-align: center;
                        }

                        .barcode img,
                        .sichtmerkmal img {
                            width: 90%;
                        }

                        p.AngebotBezeichnung,
                        .httext {
                            padding-left: 1em;
                            padding-right: 1em;
                            text-align: left;
                            line-height: 1.1;
                            word-break: break-word;
                        }

                        .httext h4 {
                            font-weight: bold;
                            font-size: 1.1rem;
                        }

                        p.CityMobilTarifangaben {
                            margin: 0 1em;
                            padding-left: 0;
                            padding-right: 0;
                            font-weight: bold;
                        }

                        .ShtAngebotbezeichnung {
                            font-weight: bold;
                            font-size: 1.1rem;
                        }

                        .OsaIPSIProduktbezeichnung {
                            font-weight: bold;
                            font-size: 1.1rem;
                        }

                        .OsaIPSIKondition {
                            font-size: 0.8rem;
                        }
                    </ht>
                    <ht name="sichtmerkmal" pos="10" type="image/png;base64">data:image/png;base64,****</ht>
                </htdata>
                <mtk dir="1" status="8">
                    <txt>Einfache Fahrt, Flexpreis, 2. Kl., 1 Erw., Düsseldorf+City/Frankfurt(M)Flugh.#</txt>
                    <zb>N</zb>
                    <!-- UIC country code? -->
                    <iss>80</iss>
                    <!-- booking reference -->
                    <tkey>GU****-3</tkey>
                    <ot_nr_hin>71301****</ot_nr_hin>
                    <reisender_vorname>Jannis</reisender_vorname>
                    <reisender_nachname>R******</reisender_nachname>
                    <nvplist>
                        <nvp name="k_erm_db">-------</nvp>
                        <nvp name="anzerw">1</nvp>
                        <nvp name="anzkind">0</nvp>
                        <!-- 2nd class? -->
                        <nvp name="klasse">S2</nvp>
                        <!-- normalpreis? -->
                        <nvp name="preisartcodedb">NP</nvp>
                        <nvp name="tpevahstart">Düsseldorf Hbf</nvp>
                        <nvp name="tpevahziel">Frankfurt(M) Flughafen Fernbf</nvp>
                        <nvp name="status">8</nvp>
                        <nvp name="citystart">Düsseldorf+City</nvp>
                    </nvplist>
                </mtk>
            </tck>
        </tcklist>
        <!-- how does this work? -->
        <posinfolist>
            <posinfo dir="1" dirlabel="none" posnr="2" shownr="1002" state="8" type="TCK" typeinfo="0" vfo="2022-06-29T00:00:00" vto="2022-06-30T23:59:59">
                <childlist>
                    <posinfo dir="1" dirlabel="none" posnr="1" shownr="1100" state="8" type="EVA" typeinfo="0" vfo="2022-06-29T17:18:00" vto="2022-06-29T19:16:00">
                        <childlist/>
                    </posinfo>
                </childlist>
            </posinfo>
        </posinfolist>
        <txt>Düsseldorf Hbf - Hauptbahnhof, Darmst</txt>
    </order>
</rporderdetails>

The <ht rel="httext"> element contains this:

<p>Herr  Jannis R******<br/>
<br/>
ICE Fahrkarte, Flexpreis (Einfache Fahrt)<br/>
Fahrtantritt am 29.06.2022<br/>
City am 29.06.22<br/>
<br/>
Klasse: 2, Erw.: 1<br/>
Hinfahrt: Düsseldorf+City - Frankfurt(M)Flugh., mit ICE<br/>
<br/>
Über: VIA: K*TROI*SIGB*MT*LM<br/>
<br/>
Nur gültig mit amtlichem Lichtbildausweis (z.B. Personalausweis). Dieser ist bei der Kontrolle vorzuzeigen.<br/>
Bei Fahrkarten mit BahnCard-Rabatt zeigen Sie bitte zusätzlich Ihre gültige BahnCard vor.<br/>
<br/>
Storno kostenfrei bis 1 Tag vor 1. Geltungstag.<br/>
<br/>
Auftrags-Nr: GU****<br/>
Gesamtpreis: **,** EUR<br/>
Gebucht am 29.06.2022 um 17:13 Uhr<br/>
</p>
derhuerst commented 2 years ago

Given that this format doesn't seem to be HAFAS-related, let's implement a client in a separate repo.

Does anyone want to do it? I'll happily create a repo in the public-transport org and transfer this Issue there.

vkrause commented 2 years ago

Nice find, works here too! I somehow had always assumed this needs full user authentication, apparently not :D

The XML requests match strings I see in the db.bahn.dbtickets.* subtree of the DB Navigator APK, which then would suggest we can also download BahnCards that way.

envake commented 1 year ago

Does anyone want to do it? I'll happily create a repo in the public-transport org and transfer this Issue there.

I can do it. I allready have a simple request in JS that just returns the whole xml.

derhuerst commented 1 year ago

I can do it. I allready have a simple request in JS that just returns the whole xml.

Great!

I think it would be great to parse the data into a usable format that we can keep (mostly) stable if their API changes. I (try to) do it the same way with generate-db-shop-urls.

envake commented 1 year ago

Ok cool. Are there any good practices regarding the data structure that should be returned? Cause that would be the next step I guess. Here is what I got so far: db-tickets

derhuerst commented 1 year ago

Are there any good practices regarding the data structure that should be returned?

It's definitely good practice not to make up yet another structure (as some others and I did too a while ago tbh 😬).

The hafas-client ecosystem, as well as some ports to other languages, follow the Friendly Public Transport Format (FPTF) (with some unnecessary exceptions).

There also many tools that follow one of OpenTripPlanner's APIs' semantics, or navitia's /journeys's semantics.

The EU made Transmodel, a semantic model defining many aspects of public transportation, that may gain momentum over the next years. NeTEx is based on it.

Unfortunately, I can't give a clear recommendation which one to use; I guess it comes down to which way of representing the data you like most, and what makes sense given the existing data model in the tickets API.

schildbach commented 1 year ago

Just throwing in my cents, in case you haven't found already, the API also yields other types of product, like seat reservations.

derhuerst commented 1 year ago

@schildbach Thanks for the hint. Do you have examples that you can comment, either here or in the db-tickets repo?

envake commented 1 year ago

Just published an npm package with a basic, stable version. Feel free to add it to the public-transport org. db-tickets