# Embedded Checkout (/it/online/integration/embedded_checkout)



Inserisca campi della carta sicuri all'interno del Suo checkout. Il modulo e il pulsante di invio restano Suoi; Tapaya gestisce la
convalida della carta, la tokenizzazione e 3-D Secure. I pagamenti incorporati sono live per gli esercenti approvati, con la sandbox disponibile
per i test. Questa guida usa Tapaya.js **0.2.0** e il Node.js SDK **0.3.0**.

<EmbeddedPaymentFlowDiagram />

<Callout title="Metodi di pagamento" type="info">
  Embedded Checkout supporta carte, Apple Pay e Google Pay. Usi il Suo pulsante di pagamento per le carte e i pulsanti ufficiali
  dei wallet per Apple Pay e Google Pay. Si veda [Aggiungere Apple Pay e Google Pay](#add-apple-pay-and-google-pay).
</Callout>

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

### Installare l'SDK [#install-the-sdk]

Un unico pacchetto contiene il client per il server, l'SDK per il browser e l'hook React:

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

| Import                       | Dove                                                                                                                                 |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `@tapayadot/checkout`        | Solo sul Suo server. Importarlo in una build per il browser genera un errore, quindi la Sua chiave API non raggiunge mai una pagina. |
| `@tapayadot/checkout/client` | La Sua pagina di checkout.                                                                                                           |
| `@tapayadot/checkout/react`  | La Sua pagina di checkout, se usa React.                                                                                             |

### Configurare il server [#configure-your-server]

I pagamenti incorporati non hanno bisogno di URL di ritorno. Gli esempi lato server seguenti sono disponibili nelle versioni &#x2A;*HTTP (fetch)**, **Node.js SDK**
e **cURL**, che chiamano tutte la stessa API dell'esercente e lasciano invariati i campi della carta nel browser.

|                          | Node.js SDK                                                                                                                                                                             | HTTP (fetch) e cURL                                                                                                       |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Chiave**               | `TAPAYA_SECRET_KEY`, letta da `new Tapaya()`                                                                                                                                            | `TAPAYA_CHECKOUT_API_KEY`                                                                                                 |
| **Ambiente**             | `TAPAYA_ENVIRONMENT=sandbox` o `production`. Senza questa variabile, una chiave `tapaya_live_` seleziona la produzione e le altre chiavi usano la sandbox per impostazione predefinita. | `TAPAYA_CHECKOUT_API_URL`: `https://api.sandbox.tapaya.com` o `https://api.tapaya.com`                                    |
| **Modalità piattaforma** | Passi il `merchantToken` dell'ordine a ogni chiamata, oppure lo imposti con `new Tapaya(key, { merchantToken })`                                                                        | Invii `X-Tapaya-Merchant-Token` in ogni richiesta. La funzione di supporto per fetch lo legge da `TAPAYA_MERCHANT_TOKEN`. |

Una chiave checkout dell'esercente non richiede altro. In modalità piattaforma, usi la Secret key dell'organizzazione e faccia in modo che un backend
che serve più esercenti scelga il token in base all'ordine autenticato. Si veda [Autenticazione di
Checkout](/online/integration/api_integration#authentication). I client secret sono specifici di ciascun pagamento.

Completi l'[onboarding](/merchants/onboarding) con i pagamenti online e verifichi che l'esercente sia **Ready** nelle
[impostazioni di Checkout](/online/integration/checkout_settings).

### Consentire Tapaya nella CSP [#allow-tapaya-in-your-csp]

Il loader scarica Tapaya.js **0.2.0** da `https://js.tapaya.com/checkout/0.2.0/checkout.js`; la versione dello script per il browser
è indipendente dalla versione del Node.js SDK. Aggiunga l'origine degli asset di Tapaya alla Sua Content Security
Policy esistente:

```text
script-src https://js.tapaya.com
frame-src https://js.tapaya.com
```

Il browser riceve la publishable key e il client secret specifico del pagamento. La Sua chiave API segreta dell'esercente
resta sul Suo server, e i dati della carta e gli script del fornitore di servizi di pagamento restano all'interno del frame di Tapaya. Per i test in locale,
ospiti Tapaya.js in proprio e passi la sua origine al loader, ad esempio
`loadTapaya(publishableKey, { apiOrigin, origin: 'http://localhost:4191' })`.

## Come funziona [#how-it-works]

1. La Sua pagina monta i campi della carta di Tapaya e li indirizza a un endpoint sul Suo server.
2. Al caricamento della pagina e quando il cliente paga, i campi della carta chiamano quell'endpoint. L'endpoint restituisce il pagamento
   dell'ordine come `clientSecret`: una credenziale valida per un solo pagamento, con cui i campi della carta confermano il pagamento direttamente con
   Tapaya. Il codice della Sua pagina non lo legge mai.
3. La Sua pagina segue l'`outcome` del pagamento. Quando il pagamento si completa, il Suo server lo verifica prima che Lei
   evada l'ordine.

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

<div className="fd-steps [&_h3]:fd-step">
  ### Ottenere le impostazioni per il browser [#get-the-browser-settings]

  Recuperi una sola volta sul Suo server le impostazioni per il browser e le aggiunga alla configurazione del Suo frontend.

  <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">
      Mantenga questa funzione di supporto sul Suo server. Controlla gli errori HTTP, tratta come `null` un pagamento dell'ordine mancante quando richiesto
      e non esegue nuovi tentativi automatici.

      ```js title="server/tapaya.js"
      const apiOrigin = new URL(process.env.TAPAYA_CHECKOUT_API_URL).origin;
      const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;
      const merchantToken = process.env.TAPAYA_MERCHANT_TOKEN;

      export async function paymentRequest(path, { method = 'GET', body, key, allowMissing = false } = {}) {
      	const response = await fetch(new URL(path, apiOrigin), {
      		method,
      		headers: {
      			Authorization: `Bearer ${apiKey}`,
      			...(merchantToken ? { 'X-Tapaya-Merchant-Token': merchantToken } : {}),
      			...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
      			...(key ? { 'Idempotency-Key': key } : {}),
      		},
      		...(body === undefined ? {} : { body: JSON.stringify(body) }),
      		redirect: 'error',
      		cache: 'no-store',
      		signal: AbortSignal.timeout(35000),
      	});
      	if (allowMissing && response.status === 404) return null;
      	if (!response.ok) throw new Error(`Payment request failed (${response.status})`);
      	return response.json();
      }

      const { publishableKey } = await paymentRequest('/merchant/payments/configuration');
      export const config = { publishableKey, apiOrigin };
      ```

      L'API restituisce `publishableKey` e `environment`. Imposti `apiOrigin` in base all'URL dell'API che ha chiamato; non è un campo
      della risposta di configurazione.
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/config.ts"
      import { Tapaya } from '@tapayadot/checkout'

      const tapaya = new Tapaya()
      const { publishableKey, apiOrigin } = await tapaya.checkout.configuration()
      // Sandbox: publishableKey: 'pk_test_…', apiOrigin: 'https://api.sandbox.tapaya.com'
      // Production: publishableKey: 'pk_live_…', apiOrigin: 'https://api.tapaya.com'
      ```
    </TabsContent>

    <TabsContent value="curl">
      Esegua questo comando sul Suo server o in un terminale attendibile. In modalità piattaforma, aggiunga
      `--header "X-Tapaya-Merchant-Token: $TAPAYA_MERCHANT_TOKEN"` a ogni comando cURL di questa guida.

      ```bash
      curl "$TAPAYA_CHECKOUT_API_URL/merchant/payments/configuration" \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Usi come configurazione per il browser la `publishableKey` restituita e l'origine del Suo `TAPAYA_CHECKOUT_API_URL`.
    </TabsContent>
  </Tabs>

  Entrambi i valori possono essere inclusi nella pagina in sicurezza. Le publishable key usano `pk_test_` con le chiavi del gateway di test e `pk_live_`
  con le chiavi del gateway live. Passi sempre al loader l'`apiOrigin` restituito dal Suo server, così che browser e server
  restino nello stesso ambiente.

  ### Aggiungere i campi della carta [#add-the-card-fields]

  Aggiunga un contenitore al Suo modulo di checkout esistente:

  ```html title="checkout.html"
  <form id="checkout">
  	<!-- Your other checkout inputs go here. -->
  	<div id="card-fields"></div>
  	<button type="submit" disabled>Pay</button>
  	<p id="message" role="status"></p>
  </form>
  ```

  Carichi Tapaya e monti i campi. `prepare` è l'endpoint che aggiungerà nel passaggio successivo.

  ```ts title="checkout.ts"
  import { loadTapaya } from '@tapayadot/checkout/client'
  import { config } from './config' // { publishableKey, apiOrigin } from your server

  const form = document.querySelector<HTMLFormElement>('#checkout')!
  const button = form.querySelector<HTMLButtonElement>('button')!
  const message = document.querySelector<HTMLElement>('#message')!
  const tapaya = await loadTapaya(config.publishableKey, { apiOrigin: config.apiOrigin })
  const payment = tapaya.createPayment({
  	prepare: '/api/payments',
  	onChange: ({ canSubmit, outcome }) => {
  		button.disabled = !canSubmit
  		if (outcome === 'completed') location.assign('/checkout/confirmation')
  		if (outcome === 'ended') message.textContent = 'This payment cannot be completed. Start a new checkout.'
  	},
  })
  await payment.mount('#card-fields')
  ```

  Quando vengono montati, i campi chiamano `prepare` per riprendere il pagamento esistente dell'ordine. Un ricaricamento durante un pagamento non
  comporta mai un nuovo addebito: la pagina mostra invece lo stato attuale del pagamento.

  ### Restituire il pagamento dell'ordine [#return-the-orders-payment]

  Colleghi questo handler a `POST /api/payments`. `getCheckoutOrder()` è una funzione della Sua applicazione: autentica
  il cliente, verifica l'accesso al suo ordine e ne restituisce `id`, `amount`, `currency` e `status` salvati. Applichi
  alla route la Sua consueta protezione CSRF.

  <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">
      Legga il pagamento esistente prima di creare un tentativo. Riutilizzi un pagamento in sospeso e crei un altro tentativo
      solo dopo un rifiuto o un annullamento con `retryAllowed: true`.

      ```js title="server/payments.js"
      import { paymentRequest } from './tapaya.js';
      import { getCheckoutOrder } from './orders.js';

      export async function findPayment(order) {
      	const query = new URLSearchParams({ merchantOrderId: order.id });
      	const payment = await paymentRequest(`/merchant/payments?${query}`, { allowMissing: true });
      	if (payment && (payment.merchantOrderId !== order.id ||
      		payment.amount !== order.amount || payment.currency !== order.currency)) {
      		throw new Error('Payment does not match the order');
      	}
      	return payment;
      }

      export function browserStatus(payment) {
      	if (payment.status === 'successful') return 'completed';
      	if (['declined', 'cancelled'].includes(payment.status) && !payment.retryAllowed) return 'ended';
      	return 'open';
      }

      export async function POST(request) {
      	const order = await getCheckoutOrder(request);
      	const payable = order.status === 'pending_payment';
      	let payment = await findPayment(order);
      	if (payable && (!payment ||
      		(['declined', 'cancelled'].includes(payment.status) && payment.retryAllowed))) {
      		// Derive the same key on retries, including after a lost creation response.
      		let key = `${order.id}:${payment ? `after:${payment.id}` : 'initial'}`;
      		if (/[^\x21-\x7e]/.test(key)) {
      			const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(key));
      			key = `prepare:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`;
      		}
      		payment = await paymentRequest('/merchant/payments', {
      			method: 'POST', key,
      			body: { merchantOrderId: order.id, amount: order.amount, currency: order.currency },
      		});
      		if (payment.merchantOrderId !== order.id || payment.amount !== order.amount || payment.currency !== order.currency) {
      			throw new Error('Payment does not match the order');
      		}
      	}
      	if (!payment) return Response.json({ error: 'Order cannot accept payment' }, { status: 409 });
      	return Response.json(
      		{ clientSecret: payable ? payment.clientSecret : null, status: browserStatus(payment) },
      		{ headers: { 'Cache-Control': 'no-store' } },
      	);
      }
      ```

      Usi un codice valuta in maiuscolo e un ID ordine salvato di al massimo 200 caratteri. Non modifichi i dati dell'ordine
      dopo l'avvio del pagamento. In caso di timeout, legga di nuovo il pagamento dell'ordine prima di decidere se serve un nuovo tentativo.
      Non generi mai una nuova chiave solo perché una risposta è andata persa.
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/payments.ts"
      import { Tapaya } from '@tapayadot/checkout'
      import type { PreparedPayment } from '@tapayadot/checkout'
      import { getCheckoutOrder } from './orders'

      const tapaya = new Tapaya()

      export async function POST(request: Request) {
      	const order = await getCheckoutOrder(request)
      	const details = { merchantOrderId: order.id, amount: order.amount, currency: order.currency }
      	// Only an order awaiting payment gets a payable session. Other orders report their payment's outcome.
      	const payable = order.status === 'pending_payment'
      	const session = payable
      		? await tapaya.checkout.sessions.prepare(details)
      		: await tapaya.checkout.sessions.verify(details)
      	if (!session) return Response.json({ error: 'Order cannot accept payment' }, { status: 409 })
      	// The card fields read both values. Your page code never handles them.
      	return Response.json(
      		{ clientSecret: payable ? session.clientSecret : null, status: session.status } satisfies PreparedPayment,
      		{ headers: { 'Cache-Control': 'no-store' } },
      	)
      }
      ```

      `prepare()` restituisce il pagamento dell'ordine. Crea il primo tentativo, restituisce invariato un tentativo in sospeso e
      crea il tentativo successivo dopo un rifiuto. Ricava da sé le idempotency key, quindi chiamarlo a ogni caricamento della pagina e
      a ogni invio non crea mai un secondo addebito. Blocchi il totale una volta avviato il pagamento: `prepare()` genera
      `TapayaOrderMismatchError` se l'importo o la valuta dell'ordine non corrispondono più al suo pagamento.
    </TabsContent>

    <TabsContent value="curl">
      Per prima cosa cerchi il pagamento dell'ordine. Questi esempi usano un ordine da 121,00 EUR salvato sul Suo server. Un `404` significa
      che non è stato trovato alcun pagamento; gli altri errori HTTP non autorizzano a creare un altro tentativo.

      ```bash
      curl --get "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--data-urlencode 'merchantOrderId=order-1001' \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Se non esiste alcun pagamento e l'ordine può ancora accettare un pagamento, crei il primo tentativo:

      ```bash
      curl --request POST "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY" \
      	--header 'Content-Type: application/json' \
      	--header 'Idempotency-Key: order-1001:initial' \
      	--data '{"merchantOrderId":"order-1001","amount":12100,"currency":"EUR"}'
      ```

      Riutilizzi un pagamento esistente in sospeso. Dopo `declined` o `cancelled`, crei un altro tentativo solo quando
      `retryAllowed` è `true`. Per quel tentativo, ripeta la POST con gli stessi dati dell'ordine e la chiave
      `order-1001:after:<previous-payment-id>`, sostituendo il segnaposto con l'`id` restituito. Riutilizzi quella chiave per
      le ripetizioni della richiesta. Dopo un timeout, cerchi di nuovo l'ordine; non presuma che la POST non sia riuscita.

      cURL illustra le chiamate all'API. L'handler autenticato `POST /api/payments` della Sua applicazione deve restituire
      `{ clientSecret, status }` ai campi della carta. Converta lo stato dell'API secondo la tabella seguente e invii
      `clientSecret: null` quando il Suo ordine non accetta più pagamenti. Non inserisca mai queste risposte in log di terminale condivisi.
    </TabsContent>
  </Tabs>

  La REST API restituisce gli stati del pagamento. Il Node SDK li converte nello `status` atteso dai campi della carta; le integrazioni con fetch e cURL
  applicano la stessa conversione nel proprio endpoint prepare:

  | `status` dell'API                                                                     | `status` dell'endpoint prepare |
  | ------------------------------------------------------------------------------------- | ------------------------------ |
  | `successful`                                                                          | `completed`                    |
  | `declined` o `cancelled`, con `retryAllowed: false`                                   | `ended`                        |
  | `ready`, `pending`, `action_required`, oppure un rifiuto o un annullamento ripetibile | `open`                         |

  <Accordions>
    <Accordion title="Regole per il Suo endpoint prepare">
      * **Prenda l'importo dall'ordine salvato,** in unità minime intere, mai dal browser. Blocchi il totale una volta
        avviato il pagamento.
      * **Risponda anche per gli ordini già conclusi.** Un ordine già pagato, o concluso dal Suo job pianificato, risponde comunque
        con lo `status` del suo pagamento, così un ricaricamento mostra l'esito invece di un errore. Non riceve mai un client secret,
        quindi il cliente non può pagare un ordine per cui Lei non accetta più pagamenti.
      * **Un solo pagamento riuscito per ordine.** Ogni rifiuto imposta `retryAllowed`, così il cliente può provare un'altra carta per
        lo stesso ordine. Il limite è condiviso con il [Hosted Checkout](/online/integration/api_integration#one-payment-per-order).
      * **Protegga il client secret.** Lo invii solo al cliente autorizzato a pagare questo ordine e non lo registri mai nei log.
      * **Si aspetti una semplice POST.** I campi della carta inviano una POST a `prepare` sulla Sua origine, con i cookie e senza corpo. Rifiutano
        i reindirizzamenti e non ripetono mai automaticamente la richiesta. Per intestazioni personalizzate, token CSRF o un corpo della richiesta, passi
        al posto dell'URL una funzione che restituisca lo stesso JSON: `prepare: async () => (await fetch(…)).json()`.
    </Accordion>
  </Accordions>

  ### Inviare dal proprio handler [#submit-from-your-handler]

  Aggiunga questo codice allo stesso script. Il Suo handler convalida il modulo e chiama `submit()`:

  ```ts title="checkout.ts"
  form.addEventListener('submit', async (event) => {
  	event.preventDefault()
  	if (!form.reportValidity() || !payment.snapshot.canSubmit) return
  	await payment.submit().catch(() => {
  		// The card fields show the reason. The shopper can try again when canSubmit allows it.
  	})
  })
  ```

  `submit()` chiama `prepare` per il pagamento, poi convalida la carta, esegue 3-D Secure e conferma il pagamento
  con Tapaya.

  ### Mostrare l'esito [#show-the-outcome]

  `onChange` riceve lo `snapshot` del pagamento ogni volta che cambia, anche quando la pagina viene caricata con un pagamento
  già concluso. Segua `snapshot.outcome`:

  | `outcome`   | Cosa mostrare                                                                                                                                                          |
  | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `open`      | Il pulsante di pagamento, abilitato finché `canSubmit` è true. Dopo un rifiuto resta `open` e il `submit()` successivo ottiene un nuovo tentativo.                     |
  | `pending`   | Un indicatore di avanzamento. L'esito non è ancora noto, quindi non è possibile pagare di nuovo. L'SDK controlla lo stato automaticamente finché la pagina è visibile. |
  | `completed` | La Sua pagina di conferma. Lì il Suo server verifica l'ordine.                                                                                                         |
  | `ended`     | Un nuovo checkout. Per questo ordine non sono possibili altri tentativi.                                                                                               |

  Chiami `payment.destroy()` quando la vista del checkout viene smontata. Con React, usi `usePayment()` da
  `@tapayadot/checkout/react`. Accetta le stesse opzioni più `publishableKey` e `apiOrigin`, e restituisce un
  `fieldRef` per il contenitore, lo `snapshot` attuale e `submit()`.

  ### Verificare prima di evadere [#verify-before-fulfilling]

  L'esito nel browser serve solo per la visualizzazione. Nella Sua pagina di conferma, legga il pagamento dell'ordine sul Suo server ed evada l'ordine
  solo quando il pagamento è stato completato:

  <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 title="server/confirmation.js"
      import { findPayment } from './payments.js';

      const order = await getCheckoutOrder(request);
      const payment = await findPayment(order); // Checks order ID, amount, and currency.
      if (payment?.status === 'successful') await markOrderPaid(order.id, payment.id);
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/confirmation.ts"
      const order = await getCheckoutOrder(request)
      const session = await tapaya.checkout.sessions.verify({
      	merchantOrderId: order.id,
      	amount: order.amount,
      	currency: order.currency,
      })
      if (session?.status === 'completed') await markOrderPaid(order.id, session.id)
      ```

      `verify()` non crea mai un pagamento e restituisce `null` prima del primo tentativo. Genera
      `TapayaOrderMismatchError` quando l'importo o la valuta differiscono dal Suo ordine.
    </TabsContent>

    <TabsContent value="curl">
      Recuperi il pagamento attuale dell'ordine al termine del flusso nel browser, oppure quando verifica un esito incerto:

      ```bash
      curl --get "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--data-urlencode 'merchantOrderId=order-1001' \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Prima di evadere l'ordine di esempio, verifichi `merchantOrderId: "order-1001"`, `amount: 12100`, `currency: "EUR"`
      e `status: "successful"`. Salvi l'`id` del pagamento restituito insieme al Suo ordine. Una risposta HTTP 200 da sola non prova il pagamento.
    </TabsContent>
  </Tabs>

  Faccia in modo che `markOrderPaid()` registri il pagamento una sola volta, anche se viene chiamata ripetutamente, e avvii l'evasione da quello
  stato salvato.

  <Callout title="Riconciliare gli ordini abbandonati dai clienti" type="warn">
    Un cliente può chiudere la scheda prima che si apra la pagina di conferma. Esegua un job pianificato che, per gli ordini non pagati, chiami `verify()` o
    legga `GET /merchant/payments?merchantOrderId=...`, con gli stessi controlli. I tentativi rifiutati e
    annullati consentono un nuovo tentativo, quindi un ordine non pagato resta `open`: lo liberi secondo le Sue tempistiche, e
    solo finché il suo `paymentStatus` non è `pending`. Una richiesta non riuscita o un timeout non è un rifiuto, quindi mantenga l'ordine
    riservato finché il pagamento non ha un esito noto.
  </Callout>
</div>

## Aggiungere Apple Pay e Google Pay [#add-apple-pay-and-google-pay]

I wallet compaiono all'interno del frame di Tapaya accanto ai campi della carta. Perché vengano mostrati, verifichi che:

* I wallet siano abilitati nelle [impostazioni di Checkout](/online/integration/checkout_settings#wallets-and-appearance).
* **Apple Pay:** il negozio abbia un [dominio del negozio attivo](/online/integration/checkout_settings#register-apple-pay-shop-domains).
* **Google Pay in produzione:** sia configurato un Google Pay merchant ID.
* Il dispositivo, il browser e il wallet del cliente supportino il metodo.

Passi `wallets` con il totale del checkout nell'unità minima della valuta. Tapaya mostra i metodi disponibili, mentre i wallet non supportati o
disattivati restano nascosti. Usi questo codice al posto dell'esempio `createPayment()` solo per carte:

```ts title="checkout-wallets.ts"
const payment = tapaya.createPayment({
	prepare: '/api/payments',
	wallets: { amount: order.totalInMinorUnits, currency: order.currency },
	onWalletClick: async () => {
		if (!form.reportValidity()) throw new Error('Complete the checkout details.')
		// Validate the cart and reserve the order on your server.
		const response = await fetch('/api/payments', { method: 'POST' })
		if (!response.ok) throw new Error('Could not prepare the payment.')
		return response.json() // { clientSecret, status }
	},
	onChange: (snapshot) => {
		button.hidden = snapshot.paymentMethod !== 'card'
		button.disabled = !snapshot.canSubmit
		if (snapshot.outcome === 'completed') location.assign('/checkout/confirmation')
	},
})
await payment.mount('#card-fields')
```

Il totale mostrato nel browser deve provenire dal Suo server, che comunque calcola e convalida l'importo
quando prepara il pagamento.

### Clic sui wallet [#wallet-clicks]

Il pulsante ufficiale del wallet apre la finestra di pagamento nativa del wallet a partire dal clic del cliente. `onWalletClick` convalida il Suo
checkout e ne riserva il pagamento quando si fa clic su quel pulsante, mai al montaggio o alla selezione del metodo. Senza questo
callback, i wallet usano `prepare` o `clientSecret`. Se riserva l'ordine solo al clic, faccia in modo che il callback `prepare`
eseguito al montaggio riprenda un ordine esistente senza crearne uno.

Mantenga visibile il Suo pulsante di pagamento solo quando `snapshot.paymentMethod` è `card`; i wallet usano i propri pulsanti con marchio
invece di `payment.submit()`. Gli esiti dei pagamenti con wallet seguono lo stesso flusso di verifica e ripristino delle carte. La chiusura della
finestra di pagamento del wallet lascia disponibile il checkout; un pagamento incerto resta in sospeso e blocca un altro addebito.

### Aggiornare il totale [#update-the-total]

Passi `wallets: { amount, currency, disabled: true }` finché i dati obbligatori del checkout sono incompleti. Aggiorni il
totale o abiliti i pulsanti senza rimontare i campi:

```ts
payment.update({ wallets: { amount: 2450, currency: 'EUR', disabled: false } })
```

Passi `wallets: null` per nascondere i wallet finché il totale non è pronto. Tapaya rifiuta la conferma se il pagamento preparato
differisce dal totale del wallet. L'hook React `usePayment()` accetta queste opzioni e aggiorna il totale quando
`wallets` cambia.

### Selettore del metodo personalizzato [#your-own-method-selector]

Se il selettore del metodo è gestito dalla Sua pagina, passi `paymentMethod: 'card'`, `'apple_pay'` o `'google_pay'` per mostrare
solo quel metodo. Un wallet selezionato ma non disponibile mostra l'etichetta `walletUnavailable` e riporta
`snapshot.error.code: 'wallet_unavailable'` quando è possibile avviare in sicurezza un altro pagamento; offra un altro metodo nel Suo
selettore. Blocchi il selettore finché `snapshot.busy` è true o il pagamento è `pending` o `action_required`.

### Frame e CSP [#frames-and-csp]

L'SDK aggiunge `allow="payment"` al proprio frame. Se il Suo negozio si trova all'interno di un altro frame, anche quel frame esterno deve consentire
i pagamenti. Apple Pay cross-origin richiede Safari 17 o versioni successive. Gli script dei fornitori dei wallet restano all'interno del frame di Tapaya, quindi
la Sua pagina non ha bisogno di origini aggiuntive dei fornitori nella propria CSP.

## Gestire l'accesso del browser scaduto [#handle-expired-browser-access]

Il client secret scade dopo 24 ore e a quel punto `clientSecret` è `null`. Un pagamento mai inviato viene
quindi annullato e il `prepare()` successivo avvia un nuovo tentativo per lo stesso ordine. Un pagamento in sospeso resta `pending`
nel browser e l'SDK continua a chiamare il Suo endpoint finché il Suo server non ne riporta l'esito come `status`. Le letture lato
server continuano a funzionare, quindi il Suo job pianificato lo conclude in ogni caso.

## Diagnosticare gli errori del server [#diagnose-server-errors]

Il Node SDK genera `PaymentsApiError` per le richieste all'API rifiutate. Esamini sul Suo server i relativi `status`, `code`, `message`,
`fieldErrors` e `requestId`. Tenga credenziali e client secret fuori dai log e mostri al cliente
messaggi scritti da Lei.

## Adattare il branding [#match-your-branding]

I campi della carta usano come colore di evidenziazione del focus (`colorFocus`) il colore di checkout effettivo dell'esercente definito nelle [impostazioni
di Checkout](/online/integration/checkout_settings#wallets-and-appearance).
I valori che passa in `appearance.variables` lo sostituiscono, e vengono inviate solo le variabili che imposta.
