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





Pošlete zákazníka na platební stránku hostovanou společností Tapaya pro karty, Apple Pay a Google Pay a poté ověřte výsledek na
svém serveru. Vaše stránka nikdy nepracuje s údaji o kartě a do svého obchodu nepřidáváte žádná pole pro kartu.

<CheckoutFlowDiagram />

## Než začnete [#before-you-start]

Zvolte klíč podle toho, kdo provozuje váš backend. Ať zvolíte kterýkoli, uchovávejte jej na svém serveru; pravidla pro stranu serveru uvádí stránka [Zabezpečení
Checkoutu](/online/security/checkout_security).

<Cards>
  <VisualCard title="Jeden obchod" visual="<Store />" href="/online/integration/checkout_settings#create-merchant-checkout-keys" description="Použijte Checkout klíč obchodníka. Určuje svého obchodníka a používají jej i zdejší příklady." />

  <VisualCard title="Mnoho obchodníků" visual="<Building2 />" href="/online/integration/api_integration#authentication" description="Použijte Secret key organizace a s každým požadavkem posílejte token obchodníka." />
</Cards>

Poté v části **Checkout** v [Tapaya Platform](https://platform.tapaya.com/checkout-settings):

1. **Přidejte svého obchodníka** a ověřte, že se zobrazuje jako **Ready**. Viz [Nastavení Checkoutu](/online/integration/checkout_settings).
2. **Zaregistrujte svůj obchod** jako povolený web s výchozími URL pro úspěch a zrušení, a to pro klíč, který jste zvolili.
   Příklady používají `https://shop.example`.

<Callout title="Návratové URL se řídí klíčem" type="info">
  Relace vytvořené klíčem obchodníka používají návratové URL obchodníka. Relace vytvořené pomocí Secret key organizace
  místo toho používají [návratové cíle](/online/integration/checkout_settings#configure-return-destinations) organizace.
  Tato dvě nastavení se nikdy nekombinují.
</Callout>

## Přijetí platby [#accept-a-payment]

Všechny tři možnosti vytvářejí stejné relace Checkoutu se stejnými přihlašovacími údaji. Zvolte jednu; každý následující krok
se řídí vaší volbou.

<div className="fd-steps [&_h3]:fd-step">
  ### Nastavte svůj server [#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">
      Nastavte `TAPAYA_CHECKOUT_API_URL` na `https://api.sandbox.tapaya.com` a svůj klíč uložte do `TAPAYA_CHECKOUT_API_KEY`.
      API volejte pomocí `fetch` v serverovém běhovém prostředí, nikdy z prohlížeče zákazníka. Příklady čtou tyto hodnoty
      přes `process.env`; pokud vaše běhové prostředí používá jiné rozhraní pro tajné údaje, toto načítání upravte.

      Vytvořte pomocnou funkci, která před vrácením těla odpovědi zkontroluje chyby HTTP:

      ```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">
      Použijte Node.js 22.13.0 nebo novější a nainstalujte na svůj server balíček ESM:

      <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>

      Svůj klíč uložte do `TAPAYA_SECRET_KEY` a zvolte prostředí sandbox. SDK samo soubory `.env` nenačítá,
      proto tyto hodnoty načtěte do prostředí svého serveru ještě před vytvořením klienta.

      ```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
      ```

      Bez `TAPAYA_ENVIRONMENT` klíč `tapaya_live_` zvolí produkční prostředí a ostatní klíče ve výchozím nastavení použijí sandbox. V
      režimu platformy předejte token obchodníka: `new Tapaya(key, { merchantToken })`, nebo `merchantToken` u každého volání. Pokud
      je váš klíč uložen pod jiným názvem, předejte jej jako první argument. [Referenční příručka Node.js
      SDK](/online/integration/node_sdk) popisuje vlastní URL API, časové limity, opakované pokusy a chyby.
    </TabsContent>

    <TabsContent value="curl">
      Tyto příkazy spouštějte ze svého serveru nebo z důvěryhodného terminálu. Načtěte svůj klíč do `TAPAYA_CHECKOUT_API_KEY`, nedávejte jej
      do kódu v prohlížeči ani do správy zdrojového kódu a nasměrujte URL API na sandbox:

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

  ### Vytvořte relaci [#create-a-session]

  Vypočítejte celkovou částku objednávky na svém serveru, včetně daně a dopravy. Objednávku a jedinečný
  `checkoutAttemptKey` uložte **před** odesláním požadavku a `order` načtěte z tohoto uloženého stavu. Pole `merchantOrderId`
  je povinné.

  <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">
      Příklad účtuje 121,00 EUR za `order-1001`, včetně 21,00 EUR DPH a 6,05 EUR za dopravu, s
      uloženým klíčem idempotence `order-1001-checkout-1`. Nahraďte tyto hodnoty údaji své uložené objednávky.

      ```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>

  Odpověď obsahuje `id` relace a hostovanou `url`. Souhrny položek, rozpisy daní, údaje o zákazníkovi
  a všechna pole odpovědi popisuje část [Vytvoření relace Checkoutu](/online/integration/api_integration#create-a-checkout-session).

  **Pokud požadavek selže nebo vyprší jeho časový limit,** zopakujte jej se stejným klíčem a nezměněným tělem. Při nejistém výsledku nikdy
  negenerujte nový klíč: změněné tělo se stejným klíčem vrátí HTTP 409.

  | Možnost            | Automatické opakování                                                                                                                                                                                                                                                                                                                             |
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | HTTP (fetch), cURL | Ne. Požadavek zopakujte sami. Viz [Idempotence a chyby](/online/integration/api_integration#idempotency-and-errors).                                                                                                                                                                                                                              |
  | Node.js SDK        | Až dvakrát po chybě připojení, vypršení časového limitu nebo HTTP 429, 502, 503 či 504, se stejným klíčem a tělem. Viz [Idempotence a opakované pokusy](/online/integration/node_sdk#idempotency-and-retries). Mezi samostatnými voláními a po restartech předávejte svůj uložený klíč: pokud jej vynecháte, vygeneruje se pro každé volání nový. |

  ### Přesměrujte zákazníka [#redirect-the-customer]

  Uložte `session.id` k objednávce jako její `checkoutSessionId` a poté pošlete prohlížeč na `session.url` pomocí HTTP
  303\. URL použijte beze změny, nesestavujte ji sami.

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

  Pokud váš framework používá jiné API pro odpovědi, pošlete HTTP 303 se `session.url` v hlavičce `Location`. S
  cURL otevřete vrácenou `url` v prohlížeči a platbu otestujte ručně.

  ### Ověřte platbu [#verify-the-payment]

  V handleru návratové URL dohledejte objednávku z autentizovaného kontextu platby ve vaší aplikaci a načtěte její
  uloženou relaci ze svého serveru. Udělejte to i na URL pro zrušení: návštěva kterékoli z obou URL nic nedokazuje.

  <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">
      ID relace uložené s vaší objednávkou načtěte do `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"
      ```

      U ukázkové objednávky ověřte, že `id` odpovídá uloženému ID relace, `merchantOrderId` je `order-1001`, `amount`
      je `12100`, `currency` je `EUR` a `paymentStatus` je `successful`. Samotná odpověď HTTP 200 platbu nedokazuje.
    </TabsContent>
  </Tabs>

  <Callout title="Objednávku vyřiďte jen jednou a jen po zaplacení" type="warn">
    Platbu zaznamenejte a objednávku vyřiďte pouze tehdy, když relace odpovídá vaší uložené objednávce a `paymentStatus` je
    `successful`, a zaznamenejte ji atomicky, aby k tomu došlo jen jednou. Pokud načtení selže nebo výsledek platby ještě není rozhodnutý, ponechte si
    ID relace a stav ověřte znovu. Viz [Stav platby](/online/integration/api_integration#payment-status).
  </Callout>
</div>

## Zpracování výsledků platby [#handle-payment-outcomes]

Stránka [Zabezpečení Checkoutu](/online/security/checkout_security#verify-before-you-fulfill) popisuje, co ověřit a jak
zopakovat požadavek s neznámým výsledkem, aniž byste vytvořili druhou relaci.

| Výsledek                               | Co relace ukazuje                                    | Co udělat                                                                                                                                                                                      |
| -------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Zamítnutí**                          | `open` s `paymentStatus: failed`                     | Nic. Zákazník může na stejné stránce zkusit jinou kartu nebo peněženku.                                                                                                                        |
| **Zatím neznámý výsledek**             | `open` a `unpaid`, zatímco Tapaya potvrzuje výsledek | Načtěte relaci později znovu.                                                                                                                                                                  |
| **Nová relace pro stejnou objednávku** | Předchozí nezaplacená relace hlásí `expired`         | Použijte novou relaci. Zaplacená objednávka nebo objednávka s probíhající platbou vrátí HTTP 409. Viz [Jedna platba na objednávku](/online/integration/api_integration#one-payment-per-order). |
| **Vypršení platnosti**                 | Přestane přijímat nové platby 30 minut po vytvoření  | Pokud objednávka stále není zaplacená, vytvořte novou relaci. Platba odeslaná před vypršením se i tak dokončí.                                                                                 |

<Callout title="Webhooky zatím nejsou k dispozici" type="info">
  Webhooky pro Checkout zatím neexistují. Spouštějte na straně serveru úlohu pro odsouhlasení, která načítá relace objednávek, jejichž
  zákazníci se nikdy nevrátí.
</Callout>

## Kontrola integrace [#check-the-integration]

<Cards>
  <Card title="Testovací karty" href="/online/getting-started/testing" description="Vyzkoušejte v sandboxu všechny výsledky platby." />

  <Card title="Kontrolní seznam QA" href="/online/getting-started/checklist" description="Před spuštěním ostrého provozu pokryjte opakované pokusy, přerušené platby a vypršení platnosti." />
</Cards>
