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





Переспрямуйте клієнта на платіжну сторінку, розміщену Tapaya, для оплати картками, Apple Pay і Google Pay, а потім перевірте результат на
своєму сервері. Ваша сторінка ніколи не працює з даними картки, і вам не потрібно додавати поля картки до свого магазину.

<CheckoutFlowDiagram />

## Перш ніж почати [#before-you-start]

Виберіть ключ залежно від того, хто керує вашим бекендом. Будь-який із ключів зберігайте на своєму сервері; серверні правила наведено в розділі [Безпека
Checkout](/online/security/checkout_security).

<Cards>
  <VisualCard title="Один магазин" visual="<Store />" href="/online/integration/checkout_settings#create-merchant-checkout-keys" description="Використовуйте checkout-ключ мерчанта. Він сам визначає свого мерчанта, і приклади на цій сторінці використовують саме його." />

  <VisualCard title="Багато мерчантів" visual="<Building2 />" href="/online/integration/api_integration#authentication" description="Використовуйте Secret key організації та надсилайте токен мерчанта з кожним запитом." />
</Cards>

Потім у розділі **Checkout** у [Tapaya Platform](https://platform.tapaya.com/checkout-settings):

1. **Додайте свого мерчанта** та перевірте, що він має статус **Ready**. Див. [Налаштування Checkout](/online/integration/checkout_settings).
2. **Зареєструйте свій магазин** як дозволений вебсайт із типовими URL успіху та скасування для вибраного ключа. У
   прикладах використовується `https://shop.example`.

<Callout title="URL повернення залежать від ключа" type="info">
  Сесії, створені ключем мерчанта, використовують URL повернення мерчанта. Сесії, створені за допомогою Secret key організації, натомість використовують
  [адреси повернення](/online/integration/checkout_settings#configure-return-destinations) організації.
  Ці два набори ніколи не поєднуються.
</Callout>

## Прийом платежу [#accept-a-payment]

Усі три варіанти створюють однакові сесії Checkout з тими самими обліковими даними. Виберіть один; кожен крок нижче враховує
ваш вибір.

<div className="fd-steps [&_h3]:fd-step">
  ### Налаштуйте свій сервер [#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">
      Задайте для `TAPAYA_CHECKOUT_API_URL` значення `https://api.sandbox.tapaya.com` і збережіть свій ключ у `TAPAYA_CHECKOUT_API_KEY`.
      Викликайте API через `fetch` свого серверного середовища, ніколи з браузера клієнта. Приклади зчитують ці значення
      через `process.env`; адаптуйте це зчитування, якщо ваше середовище використовує інший інтерфейс для секретів.

      Створіть допоміжну функцію, яка перевіряє 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">
      Використовуйте Node.js 22.13.0 або новішу версію та встановіть 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>

      Збережіть свій ключ у `TAPAYA_SECRET_KEY` і виберіть середовище пісочниці (sandbox). SDK сам не завантажує файли `.env`,
      тому завантажте ці значення в середовище свого сервера перед створенням клієнта.

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

      Без `TAPAYA_ENVIRONMENT` ключ `tapaya_live_` вибирає робоче середовище, а інші ключі типово використовують пісочницю. У
      режимі платформи передайте токен мерчанта: `new Tapaya(key, { merchantToken })` або `merchantToken` у кожному виклику. Якщо
      ваш ключ зберігається під іншою назвою, передайте його як перший аргумент. [Довідник Node.js
      SDK](/online/integration/node_sdk) описує власні URL API, тайм-аути, повторні спроби та помилки.
    </TabsContent>

    <TabsContent value="curl">
      Виконуйте ці команди на своєму сервері або в довіреному терміналі. Завантажте свій ключ у `TAPAYA_CHECKOUT_API_KEY`, не допускайте його
      потрапляння в код браузера чи систему контролю версій і спрямуйте URL API на пісочницю:

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

  ### Створіть сесію [#create-a-session]

  Обчисліть суму замовлення на своєму сервері, включно з податком і доставкою. Збережіть замовлення та унікальний
  `checkoutAttemptKey` **до** надсилання запиту й завантажуйте `order` із цього збереженого знімка. Поле `merchantOrderId`
  обов'язкове.

  <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">
      Приклад стягує 121,00 EUR за `order-1001`, включно з 21,00 EUR ПДВ і 6,05 EUR за доставку, зі
      збереженим idempotency key `order-1001-checkout-1`. Замініть ці значення даними свого збереженого замовлення.

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

  Відповідь містить `id` сесії та `url` сторінки Hosted Checkout. Про підсумки позицій, розбивку податків, дані клієнта
  та всі поля відповіді див. [Створення сесії Checkout](/online/integration/api_integration#create-a-checkout-session).

  **Якщо запит не вдався або завершився тайм-аутом,** повторіть його з тим самим ключем і незмінним тілом. Ніколи не генеруйте новий ключ через
  невизначений результат: змінене тіло з тим самим ключем повертає HTTP 409.

  | Варіант            | Автоматичні повторні спроби                                                                                                                                                                                                                                                                                                                        |
  | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | HTTP (fetch), cURL | Ні. Повторіть запит самостійно. Див. [Ідемпотентність і помилки](/online/integration/api_integration#idempotency-and-errors).                                                                                                                                                                                                                      |
  | Node.js SDK        | До двох разів після помилки з'єднання, тайм-ауту або HTTP 429, 502, 503 чи 504, з тим самим ключем і тілом. Див. [Ідемпотентність і повторні спроби](/online/integration/node_sdk#idempotency-and-retries). Між окремими викликами чи перезапусками передавайте свій збережений ключ: якщо його не вказати, для кожного виклику генерується новий. |

  ### Переспрямуйте клієнта [#redirect-the-customer]

  Збережіть `session.id` разом із замовленням як його `checkoutSessionId`, а потім переспрямуйте браузер на `session.url` за допомогою HTTP
  303\. Використовуйте URL без змін, а не формуйте його самостійно.

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

  Якщо ваш фреймворк використовує інший API для відповідей, надішліть HTTP 303 із `session.url` у заголовку `Location`. З
  cURL відкрийте повернений `url` у браузері, щоб протестувати платіж вручну.

  ### Перевірте платіж [#verify-the-payment]

  У своєму обробнику повернення визначте замовлення з автентифікованого контексту оформлення замовлення вашого застосунку та отримайте його
  збережену сесію на своєму сервері. Робіть це й на URL скасування: відвідування будь-якого з цих URL нічого не доводить.

  <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 сесії, збережений разом із замовленням, у `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"
      ```

      Для прикладу замовлення перевірте, що `id` збігається зі збереженим ID сесії, `merchantOrderId` має значення `order-1001`, `amount`
      має значення `12100`, `currency` має значення `EUR`, а `paymentStatus` має значення `successful`. Сама лише відповідь HTTP 200 не доводить оплату.
    </TabsContent>
  </Tabs>

  <Callout title="Виконуйте замовлення один раз і лише після оплати" type="warn">
    Записуйте платіж і виконуйте замовлення лише тоді, коли сесія відповідає вашому збереженому замовленню, а `paymentStatus` має значення
    `successful`, і записуйте це атомарно, щоб це відбулося лише один раз. Якщо отримати сесію не вдається або результат платежу ще не визначено, збережіть
    ID сесії та перевірте знову. Див. [Статус платежу](/online/integration/api_integration#payment-status).
  </Callout>
</div>

## Обробка результатів платежу [#handle-payment-outcomes]

[Безпека Checkout](/online/security/checkout_security#verify-before-you-fulfill) описує, що перевіряти і як
повторити запит із невідомим результатом, не створюючи другої сесії.

| Результат                                 | Що показує сесія                                               | Що робити                                                                                                                                                                                             |
| ----------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Відхилено**                             | `open` з `paymentStatus: failed`                               | Нічого. Клієнт може спробувати іншу картку чи гаманець на тій самій сторінці.                                                                                                                         |
| **Ще невідомо**                           | `open` і `unpaid`, поки Tapaya підтверджує результат           | Отримайте сесію знову пізніше.                                                                                                                                                                        |
| **Нова сесія для того самого замовлення** | Попередня неоплачена сесія повідомляє `expired`                | Використовуйте нову сесію. Оплачене замовлення або замовлення, платіж за яким триває, повертає HTTP 409. Див. [Один платіж на замовлення](/online/integration/api_integration#one-payment-per-order). |
| **Термін дії минув**                      | Припиняє приймати нові платежі через 30 хвилин після створення | Створіть нову сесію, якщо замовлення все ще не оплачено. Платіж, надісланий до закінчення терміну дії, однаково завершується.                                                                         |

<Callout title="Вебхуків поки немає" type="info">
  Вебхуків Checkout поки немає. Запустіть на сервері завдання звірки, яке отримує сесії для замовлень, чиї
  клієнти так і не повертаються.
</Callout>

## Перевірка інтеграції [#check-the-integration]

<Cards>
  <Card title="Тестові картки" href="/online/getting-started/testing" description="Перевірте кожен результат платежу в пісочниці." />

  <Card title="Контрольний список QA" href="/online/getting-started/checklist" description="Перевірте повторні спроби, перервані оформлення замовлень і закінчення терміну дії, перш ніж переходити в робочий режим." />
</Cards>
