Credit Card Payments
Every payment on a reservation is a payment item — its own object with its own id, status and amount, created before the reservation is confirmed. A reservation confirms only once its payment items cover the full balance.
total_remaining = total_price + total_fees − Σ total_allocated (non-cancelled payment items)
reservation confirms ⇔ total_remaining = 0 and every covering payment item is captured
Amounts are integers in the minor unit of the currency (6574 = EUR 65.74).
Card data goes to the tokenization proxy, not the API base URL. Any request carrying card data — number, security_code, or the tokenized equivalent — must be sent to https://secure.distribusion.com. Requests to the regular base URL are not tokenized: they are accepted, so the failure is silent and the raw card number travels on unprotected. Every other call on this page uses the regular base URL.
Two Processing Paths
The same endpoints serve both paths. Which one a reservation takes is configured by Distribusion per carrier and retailer — retailers do not select it, and the request shape does not change.
DT payment | Carrier payment | |
Who charges the customer | Distribusion, through the DT Payment Service | The carrier, through their own payment setup |
Distribusion's role | Full processing: authorize, 3DS, capture, settle with the carrier | Secure pass-through: card data is tokenized and forwarded |
Payment executed at |
|
|
Statuses observed |
|
|
Customer challenge (3DS, hosted payment page) | Surfaced on the payment item as | Carrier-dependent, mostly suppressed |
Workflow
- Create the reservation
POST /reservations/createholds the trip and establishes the price, the currency and the open balance every payment item is measured against. - Create a payment item
POST /reservations/:reservation_id/payment_items, once per payment instrument the customer uses. The response carriestotal_fees, which you add to the basket total shown to the customer, and — where the carrier requires extra data — acustom_fieldsrelationship. - Supply custom fields Repeat the
POSTwith the collected values whencustom_fieldscame back non-empty. - Confirm
PUT /reservations/confirm. Payment items on the reservation are picked up automatically; they are not referenced in the request body. The call returns immediately with the reservation inprocessingand its payment items included. - Poll
GET /reservations/:iduntil the payment items leave the in-flight statuses. On the DT path a payment item may enteraction_required: redirect the customer todetails.redirect_url, then keep polling. The reservation reachesconfirmedonce payment is captured and the carrier has confirmed.
Payment Methods
payment_method | details |
| Card details, see below |
| Hosted payment page (redirect), see below |
|
|
| — |
| — |
| — |
Endpoints
POST /reservations/reservation_id/payment_items
Field | Type | Required | Notes |
| string | Yes | See Payment Methods |
| object | Per method | Shape depends on |
details for credit_card:
Field | Type | Required | Notes |
| string | Yes |
|
| string | Yes | Card data tokenized by the proxy. Replaces |
| string | No | Raw card number, accepted instead of |
| string | No | Raw card only. |
| string | No | |
| string | No |
|
| object | No | DT path, see below |
details for sharegroop (hosted payment page):
Field | Type | Required | Notes |
| string | Yes | Where the customer lands after paying |
| object | No | See below |
details.browser_info is forwarded to the payment provider, which decides from it whether a 3DS challenge is required. Omitting it makes a challenge more likely.
Field | Type |
| string |
| string |
| string |
| integer |
| integer |
| integer (minutes) |
| boolean |
Request
{
"payment_method": "credit_card",
"details": {
"type": "visa",
"tokenized_data": "tok_1M2N3B4V5C6X",
"expiry_month": "03",
"expiry_year": "2030",
"holder_name": "PETER PAN",
"browser_info": {
"accept_header": "*/*",
"user_agent": "Mozilla/5.0 (Linux; Android 10; K)",
"language": "nb-NO",
"color_depth": 24,
"screen_height": 892,
"screen_width": 412,
"time_zone_offset": -120,
"java_enabled": false
}
}
}
Response
201. The created payment item is data; the reservation's payable object, its fees and any required custom fields are in included.
{
"data": {
"id": "QMTQ456P6BPYRS1",
"type": "payment_items",
"attributes": {
"id": "QMTQ456P6BPYRS1",
"status": "pending",
"details": {},
"processed_at": null,
"currency_code": "EUR",
"payment_method": "credit_card",
"total_charged": 0,
"total_allocated": 6574,
"total_fees": 100
},
"relationships": {
"payable_object": { "data": { "id": "RQVNHPLI", "type": "payable_objects" } },
"custom_fields": {
"data": [
{ "id": "DBTS-CORPORATECARDPAYMENT_KOSTENSTELLE", "type": "custom_fields" },
{ "id": "DBTS-CORPORATECARDPAYMENT_PROJEKTNUMMER", "type": "custom_fields" }
]
}
}
},
"included": [
{
"id": "RQVNHPLI",
"type": "payable_objects",
"attributes": {
"total_price": 6574,
"total_fees": 100,
"currency": "EUR",
"total_charged": 0,
"total_allocated": 6574,
"total_remaining": 0
},
"relationships": {
"payment_items": { "data": [{ "id": "QMTQ456P6BPYRS1", "type": "payment_items" }] },
"fees": { "data": [{ "id": "RQVNHPLI-PAYMENT_METHOD_SURCHARGE", "type": "fees" }] }
}
},
{
"id": "DBTS-CORPORATECARDPAYMENT_KOSTENSTELLE",
"type": "custom_fields",
"attributes": {
"id": "DBTS-CORPORATECARDPAYMENT_KOSTENSTELLE",
"name": "corporatecardpayment_kostenstelle",
"value": ""
}
},
{
"id": "RQVNHPLI-PAYMENT_METHOD_SURCHARGE",
"type": "fees",
"attributes": {
"code": "PAYMENT_METHOD_SURCHARGE",
"name": "Payment method surcharge",
"component": "carrier",
"included_in_price": false,
"amount": 100
}
}
]
}
Submitted card data is never echoed back. details in a response carries only code for vouchers and redirect_url for a challenge or hosted page.
GET /reservations/reservation_id/payment_items
Returns the payable object as data, with the payment items in included — the mirror of the POST response shape.
GET https://api.distribusion.com/retailers/v4/reservations/RVN3U21W/payment_items{
"data": {
"id": "RQVNHPLI",
"type": "payable_objects",
"attributes": {
"total_price": 6574,
"total_fees": 0,
"currency": "EUR",
"total_charged": 0,
"total_allocated": 6574,
"total_remaining": 0
},
"relationships": {
"payment_items": { "data": [{ "id": "QMTQ456P6BPYRS1", "type": "payment_items" }] },
"fees": { "data": [] }
}
},
"included": [
{
"id": "QMTQ456P6BPYRS1",
"type": "payment_items",
"attributes": {
"id": "QMTQ456P6BPYRS1",
"status": "pending",
"details": {},
"processed_at": null,
"currency_code": "EUR",
"payment_method": "credit_card",
"total_charged": 0,
"total_allocated": 6574,
"total_fees": 0
},
"relationships": { "custom_fields": { "data": [] } }
}
]
}
DELETE /reservations/reservation_id/payment_items/id
Removes a payment method from a reservation. Removal is a soft delete: the payment item moves to cancelled, releases its allocation and drops out of the reservation's payment items, so total_remaining rises by the amount it covered. The response returns the removed item and the recalculated payable object.
Only a pending payment item can be removed. Once it is with a payment provider (action_required, authorized, captured) the call is rejected with 400 / 400.800.053 Payment item is not cancellable. An in-flight payment has to resolve on its own, and a declined, failed or expired attempt reaches cancelled by itself.
DELETE https://api.distribusion.com/retailers/v4/reservations/RVN3U21W/payment_items/QMTQ456P6BPYRS1{
"data": {
"id": "QMTQ456P6BPYRS1",
"type": "payment_items",
"attributes": { "id": "QMTQ456P6BPYRS1" },
"relationships": {
"reservation": { "data": { "id": "RQVNHPLI", "type": "reservations" } }
}
},
"included": [
{
"id": "RQVNHPLI",
"type": "payable_objects",
"attributes": {
"total_price": 6574,
"total_fees": 0,
"currency": "EUR",
"total_charged": 0,
"total_allocated": 0,
"total_remaining": 6574
},
"relationships": { "payment_items": { "data": [] }, "fees": { "data": [] } }
}
]
}PUT /reservations/confirm
Confirms the reservation and commits its payment items. Payment items are not referenced in the body — those on the reservation are picked up automatically.
execute_payment is a boolean carried on the booking as information only. It does not decide whether the payment is executed. Do not send the legacy inline card fields (card_number, cvv, …) alongside payment items — they take precedence and the payment items are ignored.
{
"reservation_id": "RVN3U21W",
"execute_payment": true,
"terms_accepted": true,
"send_customer_email": true,
"title": "mr",
"first_name": "John",
"last_name": "Smith",
"email": "test@test.com",
"phone": "+493533400275",
"city": "Berlin",
"zip_code": "13355",
"street_and_number": "Wattstrasse 10",
"retailer_booking_number": "",
"transaction_reference": "",
"passengers": [
{
"type": "PNOS",
"first_name": "John",
"last_name": "Smith",
"birthdate": "1970-07-21",
"nationality": "DE",
"government_id_type": "passport_id",
"government_id": "DE1234567"
}
]
}The response carries the reservation with its payment items — processing while payment is in flight, confirmed once it is done.
{
"jsonapi": { "version": "1.0" },
"meta": { "locale": "en", "currency": "EUR" },
"data": {
"id": "R6VLFQHF",
"type": "reservations",
"attributes": {
"total_price": 2699,
"original_price": 2699,
"state": "processing",
"created_at": "2026-06-30T15:05",
"expires_at": "2026-06-30T15:15"
},
"relationships": {
"payment_items": { "data": [{ "id": "9AAGAIUNIPMJTC1U", "type": "payment_items" }] }
}
},
"included": [
{
"id": "9AAGAIUNIPMJTC1U",
"type": "payment_items",
"attributes": {
"id": "9AAGAIUNIPMJTC1U",
"status": "action_required",
"details": { "redirect_url": "https://3ds.com/path/to/challenge" },
"processed_at": null,
"currency_code": "EUR",
"payment_method": "credit_card",
"total_charged": 0,
"total_allocated": 2699,
"total_fees": 0
},
"relationships": { "custom_fields": { "data": [] } }
}
]
}Payment collected by a third-party processor
Where the payment was taken outside the Distribusion flow, pass it as an inline payment item at confirm instead of creating one on the reservation. psp_transaction_id marks the payment as already collected, and an inline payment item carrying it is the only kind the confirm request accepts.
Field | Type | Required | Notes |
| string | Yes | See Payment Methods |
| string | Yes | The processor's transaction id |
| string | No | Processor that took the payment |
| string | No | Echoed back in the payment item's |
{
"reservation_id": "RVN3U21W",
"terms_accepted": true,
"payment_items": [
{
"payment_method": "sharegroop",
"details": {
"psp_transaction_id": "SG1234567890",
"psp_provider": "sharegroop_direct",
"purchase_order_number": "PO-12345"
}
}
]
}GET /reservations/id
Carries the same payment_items relationship and is the endpoint to poll after confirming. There is no webhook and no separate 3DS endpoint.
Payment Item Attributes
Attribute | Description | Values |
| Unique identifier | 16-character string |
| Lifecycle status | See Statuses |
| Method used | See Payment Methods |
| Payment item currency | ISO 4217 |
| Amount this item covers. Set at creation, unchanged by charging | Integer, minor units |
| Amount actually charged. | Integer, minor units |
| Fees attached to this payment method, for example a corporate card surcharge | Integer, minor units |
| Method-specific data returned to the retailer: | Object |
| When the item reached a final status | Timestamp or |
Statuses
Status | Path | Meaning |
| Both | Registered on the reservation and counted against the balance. Intent to pay; nothing sent to a payment provider yet. Removable. |
| DT | The customer must act — 3DS challenge or hosted payment page. |
| DT | Funds held by the pre-authorization; capture follows. |
| DT | Funds captured. The booking is not created yet. |
| Both | Payment final and booked against the confirmed reservation. |
| Both | No funds moved — removed by the retailer while |
captured and confirmed are distinct: money is taken at captured, and the payment item becomes confirmed only once the carrier has confirmed the booking. On the carrier path the carrier charges as part of creating the booking, so pending → confirmed is the whole sequence.
carrier payment pending → confirmed
DT card, no challenge pending → authorized → captured → confirmed
DT card, 3DS challenge pending → action_required → authorized → captured → confirmed
DT hosted payment page pending → action_required → captured → confirmed
declined / expired pending → [action_required] → cancelled (reservation holds)Customer Challenges and the Payment Window
action_required means the ball is with the customer. Redirect them to details.redirect_url; the payment item advances on its own once they are done, and the reservation state does not change while this happens.
The reservation expiry is the payment window — a payment item carries no clock of its own. A challenge not completed before expiry resolves the payment item to cancelled, and any authorization landing afterwards is released. While a payment item is in flight (action_required, authorized, captured), amend and cancel are blocked on the reservation.
Fees
total_fees > 0 on the created payment item means the chosen method costs extra — a corporate card surcharge, for example. The customer pays these fees as part of the payment, so they must be added to the basket total shown at checkout and collected with it. They are not B2B fees invoiced to the retailer.
The individual fees are listed in the payable object's fees relationship, and total_remaining already accounts for them.
Card Data Security
Card data must be tokenized before it reaches the Retailer API. Send POST /payment_items requests carrying card data to the tokenization proxy at https://secure.distribusion.com, not to the regular base URL; requests to the regular endpoint are not tokenized. Access is scoped by your retailer API key. holder_name and the expiry fields stay plain text.
Custom Fields
Some carriers require data beyond the card itself. Corporate card bookings, for instance, may need a cost centre or project number. The POST response then lists the required fields as a custom_fields relationship with empty values. Collect them from the customer and repeat the POST with the values filled in.
An empty custom_fields array means nothing extra is needed. Treat every returned field as required.
Multiple Payment Items
A reservation can carry several payment items — a voucher covering part of the fare and a card covering the rest, for example. Each item carries only the amount it covers, and the sum must close the balance:
voucher total_allocated 2000 + card total_allocated 4574 = total_price 6574 → total_remaining 0
A second card replaces the first rather than adding to it, while the first is still pending.
Errors
Errors follow the JSON:API shape, with the carrier's or payment provider's reason surfaced in title.
{
"jsonapi": { "version": "1.0" },
"errors": [
{ "code": "400.101.110", "title": "Card number is invalid" }
]
}
Condition | Status | Code |
Invalid or unsupported card data |
| Carrier-specific, e.g. |
Missing required |
| |
Unsupported |
| |
Second primary payment item where replacement is restricted |
|
|
Removing a payment item that is not |
|
|
No payment method on the reservation at confirm |
|
|
Payment item or reservation not found |
|
|
Coverage of the balance is not validated at confirm.
Carrier Specifics
The table covers the carriers live today. More follow as the agreement with each carrier is in place.
Carrier | Path | Cards | Fees / additional data | Challenge |
Deutsche Bahn (DBAH, DBTS) | Carrier | Airplus, Amex, Diners, Mastercard, Visa | Surcharge on lodged and corporate cards; corporate card custom fields | Suppressed |
Iryo | Carrier | Amex, Diners, JCB, Mastercard (credit, debit), Visa (credit, debit, electron, purchasing) | None | Suppressed |
SNCF groups | DT | Hosted payment page ( | None | None Always — hosted page via |
Amtrak | coming soon | |||
Renfe | coming soon |
On this page
- Credit Card Payments