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





Invii il cliente a una pagina di pagamento ospitata da Tapaya per carte, Apple Pay e Google Pay, poi verifichi l'esito sul
Suo server. La Sua pagina non gestisce mai i dati della carta e Lei non aggiunge alcun campo della carta al Suo negozio.

<CheckoutFlowDiagram />

## Prima di iniziare [#before-you-start]

Scelga la chiave in base a chi gestisce il Suo backend. In entrambi i casi conservi la chiave sul Suo server; [Sicurezza di
Checkout](/online/security/checkout_security) elenca le regole lato server.

<Cards>
  <VisualCard title="Un solo negozio" visual="<Store />" href="/online/integration/checkout_settings#create-merchant-checkout-keys" description="Usi una chiave checkout dell'esercente. Identifica il proprio esercente ed è quella usata negli esempi di questa pagina." />

  <VisualCard title="Molti esercenti" visual="<Building2 />" href="/online/integration/api_integration#authentication" description="Usi la Secret key dell'organizzazione e invii un token dell'esercente con ogni richiesta." />
</Cards>

Poi, in **Checkout** nella [Tapaya Platform](https://platform.tapaya.com/checkout-settings):

1. **Aggiunga il Suo esercente** e verifichi che mostri **Ready**. Si veda [Impostazioni di Checkout](/online/integration/checkout_settings).
2. **Registri il Suo negozio** come sito web consentito, con gli URL predefiniti di successo e di annullamento, per la chiave scelta. Gli
   esempi usano `https://shop.example`.

<Callout title="Gli URL di ritorno seguono la chiave" type="info">
  Le sessioni create con una chiave dell'esercente usano gli URL di ritorno dell'esercente. Le sessioni create con la Secret key
  dell'organizzazione usano invece le [destinazioni di ritorno](/online/integration/checkout_settings#configure-return-destinations)
  dell'organizzazione. Le due configurazioni non vengono mai combinate.
</Callout>

## Accettare un pagamento [#accept-a-payment]

Tutte e tre le opzioni creano le stesse sessioni di Checkout con le stesse credenziali. Ne scelga una; ogni passaggio seguente si
adatta alla Sua scelta.

<div className="fd-steps [&_h3]:fd-step">
  ### Configurare il 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">
      Imposti `TAPAYA_CHECKOUT_API_URL` su `https://api.sandbox.tapaya.com` e salvi la Sua chiave in `TAPAYA_CHECKOUT_API_KEY`.
      Chiami l'API con il `fetch` del Suo runtime server, mai dal browser del cliente. Gli esempi leggono questi valori
      tramite `process.env`; adatti questa lettura se il Suo runtime usa un'interfaccia diversa per i segreti.

      Crei una funzione di supporto che controlli gli errori HTTP prima di restituire il corpo della risposta:

      ```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">
      Usi Node.js 22.13.0 o versioni successive e installi il pacchetto ESM sul Suo 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>

      Salvi la Sua chiave in `TAPAYA_SECRET_KEY` e selezioni l'ambiente sandbox. L'SDK non carica da solo i file `.env`,
      quindi carichi questi valori nell'ambiente del Suo server prima di creare il client.

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

      Senza `TAPAYA_ENVIRONMENT`, una chiave `tapaya_live_` seleziona la produzione e le altre chiavi usano la sandbox per impostazione predefinita. In
      modalità piattaforma, passi il token dell'esercente: `new Tapaya(key, { merchantToken })`, oppure `merchantToken` in ogni chiamata. Se
      la Sua chiave è salvata con un altro nome, la passi come primo argomento. Il [riferimento del Node.js
      SDK](/online/integration/node_sdk) tratta URL API personalizzati, timeout, nuovi tentativi ed errori.
    </TabsContent>

    <TabsContent value="curl">
      Esegua questi comandi dal Suo server o da un terminale attendibile. Carichi la Sua chiave in `TAPAYA_CHECKOUT_API_KEY`, la tenga fuori
      dal codice del browser e dal sistema di controllo versione e punti l'URL dell'API alla sandbox:

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

  ### Creare una sessione [#create-a-session]

  Calcoli il totale dell'ordine sul Suo server, imposte e spedizione comprese. Salvi l'ordine e un
  `checkoutAttemptKey` univoco **prima** di inviare la richiesta e carichi `order` da quello snapshot salvato. `merchantOrderId`
  è obbligatorio.

  <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">
      L'esempio addebita 121,00 EUR per `order-1001`, di cui 21,00 EUR di IVA e 6,05 EUR di spedizione, con la
      idempotency key salvata `order-1001-checkout-1`. Sostituisca questi valori con i dati del Suo ordine salvato.

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

  La risposta contiene l'`id` della sessione e l'`url` della pagina ospitata. Per riepiloghi degli articoli, ripartizioni delle imposte, dati del cliente
  e tutti i campi della risposta, si veda [Creare una sessione di Checkout](/online/integration/api_integration#create-a-checkout-session).

  **Se la richiesta non riesce o va in timeout,** la ripeta con la stessa chiave e un corpo invariato. Non generi mai una nuova chiave per
  un esito incerto: un corpo modificato con la stessa chiave restituisce HTTP 409.

  | Opzione            | Nuovi tentativi automatici                                                                                                                                                                                                                                                                                                                                    |
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | HTTP (fetch), cURL | No. Ripeta manualmente la richiesta. Si veda [Idempotenza ed errori](/online/integration/api_integration#idempotency-and-errors).                                                                                                                                                                                                                             |
  | Node.js SDK        | Fino a due volte dopo un errore di connessione, un timeout o un HTTP 429, 502, 503 o 504, con la stessa chiave e lo stesso corpo. Si veda [Idempotenza e nuovi tentativi](/online/integration/node_sdk#idempotency-and-retries). Tra chiamate separate o dopo un riavvio, passi la chiave salvata: se la omette, ne viene generata una nuova a ogni chiamata. |

  ### Reindirizzare il cliente [#redirect-the-customer]

  Salvi `session.id` insieme all'ordine come suo `checkoutSessionId`, poi invii il browser a `session.url` con HTTP
  303\. Usi l'URL invariato invece di costruirlo.

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

  Se il Suo framework usa un'API di risposta diversa, invii HTTP 303 con `session.url` nell'intestazione `Location`. Con
  cURL, apra l'`url` restituito nel browser per testare il pagamento manualmente.

  ### Verificare il pagamento [#verify-the-payment]

  Nel Suo handler di ritorno, individui l'ordine dal contesto di checkout autenticato della Sua applicazione e recuperi la
  sessione salvata dal Suo server. Lo faccia anche sull'URL di annullamento: una visita a uno qualsiasi dei due URL non prova nulla.

  <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">
      Carichi in `CHECKOUT_SESSION_ID` l'ID della sessione salvato con il Suo ordine:

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

      Per l'ordine di esempio, verifichi che `id` corrisponda all'ID della sessione salvato, che `merchantOrderId` sia `order-1001`, `amount`
      sia `12100`, `currency` sia `EUR` e `paymentStatus` sia `successful`. Una risposta HTTP 200 da sola non prova il pagamento.
    </TabsContent>
  </Tabs>

  <Callout title="Evadere una sola volta, e solo se pagato" type="warn">
    Registri il pagamento ed evada l'ordine solo quando la sessione corrisponde all'ordine salvato e `paymentStatus` è
    `successful`, e lo registri in modo atomico così che avvenga una sola volta. Se il recupero non riesce o il pagamento è in sospeso, conservi
    l'ID della sessione e ripeta il controllo. Si veda [Stato del pagamento](/online/integration/api_integration#payment-status).
  </Callout>
</div>

## Gestire gli esiti dei pagamenti [#handle-payment-outcomes]

[Sicurezza di Checkout](/online/security/checkout_security#verify-before-you-fulfill) spiega cosa verificare e come
ripetere una richiesta con esito sconosciuto senza creare una seconda sessione.

| Esito                                   | Cosa mostra la sessione                                         | Cosa fare                                                                                                                                                                                 |
| --------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Rifiutato**                           | `open`, con `paymentStatus: failed`                             | Nulla. Il cliente può provare un'altra carta o un altro wallet nella stessa pagina.                                                                                                       |
| **Non ancora noto**                     | `open` e `unpaid` mentre Tapaya conferma l'esito                | Recuperi di nuovo la sessione più tardi.                                                                                                                                                  |
| **Nuova sessione per lo stesso ordine** | La sessione precedente non pagata riporta `expired`             | Usi la nuova sessione. Un ordine pagato, o con un pagamento in corso, restituisce HTTP 409. Si veda [Un pagamento per ordine](/online/integration/api_integration#one-payment-per-order). |
| **Scaduta**                             | Smette di accettare nuovi pagamenti 30 minuti dopo la creazione | Crei una nuova sessione se l'ordine non è ancora pagato. Un pagamento inviato prima della scadenza viene comunque completato.                                                             |

<Callout title="Webhook non ancora disponibili" type="info">
  I webhook di Checkout non sono ancora disponibili. Esegua lato server un job di riconciliazione che recuperi le sessioni degli ordini i cui
  clienti non tornano mai.
</Callout>

## Verificare l'integrazione [#check-the-integration]

<Cards>
  <Card title="Carte di prova" href="/online/getting-started/testing" description="Provi ogni esito di pagamento nella sandbox." />

  <Card title="Checklist QA" href="/online/getting-started/checklist" description="Copra nuovi tentativi, checkout interrotti e scadenze prima di andare live." />
</Cards>
