# Hosted Checkout (/de/online/integration/hosted_checkout)





Leiten Sie den Kunden auf eine von Tapaya gehostete Zahlungsseite für Karten, Apple Pay und Google Pay weiter und verifizieren Sie das Ergebnis anschließend auf
Ihrem Server. Ihre Seite verarbeitet nie Kartendaten, und Sie fügen Ihrem Shop keine Kartenfelder hinzu.

<CheckoutFlowDiagram />

## Bevor Sie beginnen [#before-you-start]

Wählen Sie den Schlüssel, der dazu passt, wer Ihr Backend betreibt. Bewahren Sie jeden der beiden Schlüssel auf Ihrem Server auf; die serverseitigen Regeln listet die
[Checkout-Sicherheit](/online/security/checkout_security) auf.

<Cards>
  <VisualCard title="Ein Shop" visual="<Store />" href="/online/integration/checkout_settings#create-merchant-checkout-keys" description="Verwenden Sie einen Checkout-Händlerschlüssel. Er bestimmt seinen Händler, und die Beispiele hier verwenden ihn." />

  <VisualCard title="Viele Händler" visual="<Building2 />" href="/online/integration/api_integration#authentication" description="Verwenden Sie den Secret key der Organisation und senden Sie bei jeder Anfrage ein Händler-Token." />
</Cards>

Gehen Sie dann unter **Checkout** in der [Tapaya Platform](https://platform.tapaya.com/checkout-settings) wie folgt vor:

1. **Fügen Sie Ihren Händler hinzu** und prüfen Sie, ob er **Ready** anzeigt. Siehe [Checkout-Einstellungen](/online/integration/checkout_settings).
2. **Registrieren Sie Ihren Shop** als erlaubte Website mit Standard-URLs für Erfolg und Abbruch, und zwar für den gewählten Schlüssel. Die
   Beispiele verwenden `https://shop.example`.

<Callout title="Rückkehr-URLs richten sich nach dem Schlüssel" type="info">
  Sessions mit Händlerschlüssel verwenden die Rückkehr-URLs des Händlers. Sessions, die mit dem Secret key der Organisation erstellt werden, verwenden stattdessen die
  [Rückkehrziele](/online/integration/checkout_settings#configure-return-destinations) der Organisation.
  Die beiden werden nie kombiniert.
</Callout>

## Eine Zahlung annehmen [#accept-a-payment]

Alle drei Optionen erstellen dieselben Checkout-Sessions mit denselben Zugangsdaten. Wählen Sie eine aus; jeder Schritt unten folgt
Ihrer Wahl.

<div className="fd-steps [&_h3]:fd-step">
  ### Server einrichten [#set-up-your-server]

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      Setzen Sie `TAPAYA_CHECKOUT_API_URL` auf `https://api.sandbox.tapaya.com` und speichern Sie Ihren Schlüssel in `TAPAYA_CHECKOUT_API_KEY`.
      Rufen Sie die API mit dem `fetch` Ihrer Serverlaufzeit auf, niemals aus dem Browser des Kunden. Die Beispiele lesen diese Werte
      über `process.env`; passen Sie diesen Zugriff an, wenn Ihre Laufzeitumgebung eine andere Schnittstelle für Secrets verwendet.

      Erstellen Sie eine Hilfsfunktion, die HTTP-Fehler prüft, bevor sie den Body der Antwort zurückgibt:

      ```js title="server/checkout.js"
      const apiUrl = process.env.TAPAYA_CHECKOUT_API_URL;
      const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;

      async function checkoutRequest(path, options = {}) {
      	const response = await fetch(new URL(path, apiUrl), {
      		...options,
      		headers: {
      			...options.headers,
      			Authorization: `Bearer ${apiKey}`,
      		},
      		redirect: 'error',
      		cache: 'no-store',
      		signal: AbortSignal.timeout(8000),
      	});

      	if (!response.ok) {
      		throw new Error(`Checkout request failed (${response.status})`);
      	}

      	return response.json();
      }
      ```
    </TabsContent>

    <TabsContent value="node">
      Verwenden Sie Node.js 22.13.0 oder neuer und installieren Sie das ESM-Paket auf Ihrem Server:

      <CodeBlockTabs defaultValue="npm" groupId="package-manager">
        <CodeBlockTabsList>
          <CodeBlockTabsTrigger value="npm">
            npm
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="pnpm">
            pnpm
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="bun">
            Bun
          </CodeBlockTabsTrigger>
        </CodeBlockTabsList>

        <CodeBlockTab value="npm">
          ```sh
          npm install @tapayadot/checkout
          ```
        </CodeBlockTab>

        <CodeBlockTab value="pnpm">
          ```sh
          pnpm add @tapayadot/checkout
          ```
        </CodeBlockTab>

        <CodeBlockTab value="bun">
          ```sh
          bun add @tapayadot/checkout
          ```
        </CodeBlockTab>
      </CodeBlockTabs>

      Speichern Sie Ihren Schlüssel in `TAPAYA_SECRET_KEY` und wählen Sie die Sandbox-Umgebung. Das SDK lädt `.env`-Dateien
      nicht selbst. Laden Sie diese Werte daher in die Umgebung Ihres Servers, bevor Sie den Client erstellen.

      ```sh title=".env"
      TAPAYA_SECRET_KEY=tapaya_test_...
      TAPAYA_ENVIRONMENT=sandbox
      ```

      ```ts title="server/checkout.ts"
      import { Tapaya } from '@tapayadot/checkout';

      const tapaya = new Tapaya(); // reads TAPAYA_SECRET_KEY
      ```

      Ohne `TAPAYA_ENVIRONMENT` wählt ein `tapaya_live_`-Schlüssel die Produktion, andere Schlüssel verwenden standardmäßig die Sandbox. Übergeben Sie im
      Plattformmodus das Händler-Token: `new Tapaya(key, { merchantToken })` oder `merchantToken` bei jedem Aufruf. Wenn
      Ihr Schlüssel unter einem anderen Namen gespeichert ist, übergeben Sie ihn als erstes Argument. Die [Referenz zum Node.js
      SDK](/online/integration/node_sdk) behandelt eigene API-URLs, Timeouts, Wiederholungsversuche und Fehler.
    </TabsContent>

    <TabsContent value="curl">
      Führen Sie diese Befehle auf Ihrem Server oder in einem vertrauenswürdigen Terminal aus. Laden Sie Ihren Schlüssel in `TAPAYA_CHECKOUT_API_KEY`, halten Sie ihn
      aus Browser-Code und Versionsverwaltung heraus und richten Sie die API-URL auf die Sandbox aus:

      ```bash
      export TAPAYA_CHECKOUT_API_URL='https://api.sandbox.tapaya.com'
      ```
    </TabsContent>
  </Tabs>

  ### Session erstellen [#create-a-session]

  Berechnen Sie den Gesamtbetrag der Bestellung auf Ihrem Server, einschließlich Steuern und Versand. Speichern Sie die Bestellung und einen eindeutigen
  `checkoutAttemptKey`, **bevor** Sie die Anfrage stellen, und laden Sie `order` aus diesem gespeicherten Snapshot. `merchantOrderId`
  ist erforderlich.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      ```js
      const session = await checkoutRequest('/merchant/checkout-sessions', {
      	method: 'POST',
      	headers: {
      		'Content-Type': 'application/json',
      		'Idempotency-Key': order.checkoutAttemptKey,
      	},
      	body: JSON.stringify({
      		merchantOrderId: order.id,
      		amount: order.totalInMinorUnits,
      		currency: order.currency,
      		successUrl: 'https://shop.example/checkout/return',
      		cancelUrl: 'https://shop.example/checkout/cancel',
      	}),
      });
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts
      const session = await tapaya.checkout.sessions.create(
      	{
      		merchantOrderId: order.id,
      		amount: order.totalInMinorUnits,
      		currency: order.currency,
      		successUrl: 'https://shop.example/checkout/return',
      		cancelUrl: 'https://shop.example/checkout/cancel',
      	},
      	{ idempotencyKey: order.checkoutAttemptKey },
      );
      ```
    </TabsContent>

    <TabsContent value="curl">
      Das Beispiel belastet 121,00 EUR für `order-1001`, einschließlich 21,00 EUR Mehrwertsteuer und 6,05 EUR Versand, mit dem
      gespeicherten idempotency key (Idempotenzschlüssel) `order-1001-checkout-1`. Ersetzen Sie diese Werte durch die Daten Ihrer gespeicherten Bestellung.

      ```bash
      curl --request POST \
      	"$TAPAYA_CHECKOUT_API_URL/merchant/checkout-sessions" \
      	--fail-with-body --silent --show-error --max-time 8 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY" \
      	--header 'Content-Type: application/json' \
      	--header 'Idempotency-Key: order-1001-checkout-1' \
      	--data '{
      		"merchantOrderId": "order-1001",
      		"amount": 12100,
      		"currency": "EUR",
      		"tax": 2100,
      		"shipping": 605,
      		"taxRate": 21,
      		"successUrl": "https://shop.example/checkout/return",
      		"cancelUrl": "https://shop.example/checkout/cancel"
      	}'
      ```
    </TabsContent>
  </Tabs>

  Die Antwort enthält die `id` der Session und die gehostete `url`. Positionsübersichten, Steueraufschlüsselungen, Kundendaten
  und alle Felder der Antwort beschreibt [Checkout-Session erstellen](/online/integration/api_integration#create-a-checkout-session).

  **Wenn die Anfrage fehlschlägt oder in ein Timeout läuft,** wiederholen Sie sie mit demselben Schlüssel und unverändertem Body. Erzeugen Sie für
  ein unsicheres Ergebnis nie einen neuen Schlüssel: Ein geänderter Body mit demselben Schlüssel gibt HTTP 409 zurück.

  | Option             | Automatische Wiederholung                                                                                                                                                                                                                                                                                                                                                               |
  | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | HTTP (fetch), cURL | Nein. Wiederholen Sie die Anfrage selbst. Siehe [Idempotenz und Fehler](/online/integration/api_integration#idempotency-and-errors).                                                                                                                                                                                                                                                    |
  | Node.js SDK        | Bis zu zweimal nach einem Verbindungsfehler, einem Timeout oder HTTP 429, 502, 503 oder 504, mit demselben Schlüssel und Body. Siehe [Idempotenz und Wiederholungsversuche](/online/integration/node_sdk#idempotency-and-retries). Übergeben Sie über getrennte Aufrufe oder Neustarts hinweg Ihren gespeicherten Schlüssel: Wenn Sie ihn weglassen, wird pro Aufruf ein neuer erzeugt. |

  ### Kunden weiterleiten [#redirect-the-customer]

  Speichern Sie `session.id` mit Ihrer Bestellung als deren `checkoutSessionId` und leiten Sie den Browser dann mit HTTP
  303 auf `session.url` weiter. Verwenden Sie die URL unverändert, statt sie selbst zusammenzusetzen.

  ```ts
  // First persist session.id as the order's checkoutSessionId.
  return Response.redirect(session.url, 303);
  ```

  Wenn Ihr Framework eine andere API für Antworten verwendet, senden Sie HTTP 303 mit `session.url` im Header `Location`. Öffnen Sie bei
  cURL die zurückgegebene `url` in Ihrem Browser, um die Zahlung von Hand zu testen.

  ### Zahlung verifizieren [#verify-the-payment]

  Ermitteln Sie in Ihrem Rückkehr-Handler die Bestellung aus dem authentifizierten Checkout-Kontext Ihrer Anwendung und rufen Sie ihre
  gespeicherte Session von Ihrem Server ab. Tun Sie dies auch bei der Abbruch-URL: Ein Aufruf einer der beiden URLs beweist nichts.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      ```js
      const current = await checkoutRequest(
      	`/merchant/checkout-sessions/${encodeURIComponent(order.checkoutSessionId)}`,
      );

      if (
      	current.id !== order.checkoutSessionId ||
      	current.merchantOrderId !== order.id ||
      	current.amount !== order.totalInMinorUnits ||
      	current.currency !== order.currency
      ) {
      	throw new Error('Checkout session does not match the order');
      }

      const paid = current.paymentStatus === 'successful';
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts
      const current = await tapaya.checkout.sessions.get(order.checkoutSessionId);

      if (
      	current.id !== order.checkoutSessionId ||
      	current.merchantOrderId !== order.id ||
      	current.amount !== order.totalInMinorUnits ||
      	current.currency !== order.currency
      ) {
      	throw new Error('Checkout session does not match the order');
      }

      const paid = current.paymentStatus === 'successful';
      ```
    </TabsContent>

    <TabsContent value="curl">
      Laden Sie die mit Ihrer Bestellung gespeicherte Session-ID in `CHECKOUT_SESSION_ID`:

      ```bash
      curl "$TAPAYA_CHECKOUT_API_URL/merchant/checkout-sessions/$CHECKOUT_SESSION_ID" \
      	--fail-with-body --silent --show-error --max-time 8 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Prüfen Sie für die Beispielbestellung, ob `id` mit der gespeicherten Session-ID übereinstimmt, `merchantOrderId` gleich `order-1001`, `amount`
      gleich `12100`, `currency` gleich `EUR` und `paymentStatus` gleich `successful` ist. Ein HTTP 200 allein beweist keine Zahlung.
    </TabsContent>
  </Tabs>

  <Callout title="Nur einmal abwickeln und nur, wenn bezahlt" type="warn">
    Verbuchen Sie die Zahlung und wickeln Sie die Bestellung nur ab, wenn die Session mit Ihrer gespeicherten Bestellung übereinstimmt und `paymentStatus`
    `successful` ist, und halten Sie dies atomar fest, damit es genau einmal geschieht. Wenn der Abruf fehlschlägt oder die Zahlung ungeklärt ist, behalten Sie
    die Session-ID und prüfen Sie erneut. Siehe [Zahlungsstatus](/online/integration/api_integration#payment-status).
  </Callout>
</div>

## Zahlungsergebnisse behandeln [#handle-payment-outcomes]

Die [Checkout-Sicherheit](/online/security/checkout_security#verify-before-you-fulfill) beschreibt, was Sie verifizieren sollten und wie Sie
ein unbekanntes Ergebnis wiederholen, ohne eine zweite Session zu erstellen.

| Ergebnis                                 | Was die Session zeigt                                              | Was zu tun ist                                                                                                                                                                                                            |
| ---------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Abgelehnt**                            | `open`, mit `paymentStatus: failed`                                | Nichts. Der Kunde kann auf derselben Seite eine andere Karte oder Wallet versuchen.                                                                                                                                       |
| **Noch unbekannt**                       | `open` und `unpaid`, während Tapaya das Ergebnis bestätigt         | Rufen Sie die Session später erneut ab.                                                                                                                                                                                   |
| **Neue Session für dieselbe Bestellung** | Die vorherige unbezahlte Session meldet `expired`                  | Verwenden Sie die neue Session. Eine bezahlte Bestellung oder eine Bestellung mit laufender Zahlung gibt HTTP 409 zurück. Siehe [Eine Zahlung pro Bestellung](/online/integration/api_integration#one-payment-per-order). |
| **Abgelaufen**                           | Nimmt 30 Minuten nach der Erstellung keine neuen Zahlungen mehr an | Erstellen Sie eine neue Session, wenn die Bestellung noch unbezahlt ist. Eine vor dem Ablauf abgesendete Zahlung wird trotzdem abgeschlossen.                                                                             |

<Callout title="Noch keine Webhooks" type="info">
  Es gibt noch keine Checkout-Webhooks. Betreiben Sie einen serverseitigen Abgleichsjob, der die Sessions von Bestellungen abruft, deren
  Kunden nie zurückkehren.
</Callout>

## Integration prüfen [#check-the-integration]

<Cards>
  <Card title="Testkarten" href="/online/getting-started/testing" description="Spielen Sie jedes Zahlungsergebnis in der Sandbox durch." />

  <Card title="QA-Checkliste" href="/online/getting-started/checklist" description="Decken Sie Wiederholungsversuche, unterbrochene Checkouts und den Ablauf ab, bevor Sie live gehen." />
</Cards>
