Embedded Checkout
Aggiunga campi della carta sicuri e Apple Pay o Google Pay al Suo 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.
- 1. Your frontend to Your backend: Prepare payment.
- 2. Your backend to Tapaya: Create or resume payment.
- 3. Tapaya to Your backend: Payment and client secret.
- 4. Your backend to Your frontend: Client secret and status.
- 5. Your frontend to Tapaya: Confirm card and 3-D Secure.
- 6. Tapaya to Your frontend: Payment outcome.
- 7. Your backend to Tapaya: Read order's payment.
- 8. Tapaya to Your backend: Payment status.
- 9. Your backend to Your frontend: Show verified result.
Metodi di pagamento
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.
Prima di iniziare
Installare l'SDK
Un unico pacchetto contiene il client per il server, l'SDK per il browser e l'hook React:
npm install @tapayadot/checkout| 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
I pagamenti incorporati non hanno bisogno di URL di ritorno. Gli esempi lato server seguenti sono disponibili nelle versioni 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. I client secret sono specifici di ciascun pagamento.
Completi l'onboarding con i pagamenti online e verifichi che l'esercente sia Ready nelle impostazioni di Checkout.
Consentire Tapaya nella 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:
script-src https://js.tapaya.com
frame-src https://js.tapaya.comIl 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
- La Sua pagina monta i campi della carta di Tapaya e li indirizza a un endpoint sul Suo server.
- 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. - La Sua pagina segue l'
outcomedel pagamento. Quando il pagamento si completa, il Suo server lo verifica prima che Lei evada l'ordine.
Accettare un pagamento
Ottenere le impostazioni per il browser
Recuperi una sola volta sul Suo server le impostazioni per il browser e le aggiunga alla configurazione del Suo frontend.
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.
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.
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
Aggiunga un contenitore al Suo modulo di checkout esistente:
<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.
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
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.
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.
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.
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 |
Inviare dal proprio handler
Aggiunga questo codice allo stesso script. Il Suo handler convalida il modulo e chiama submit():
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
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
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:
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);Faccia in modo che markOrderPaid() registri il pagamento una sola volta, anche se viene chiamata ripetutamente, e avvii l'evasione da quello
stato salvato.
Riconciliare gli ordini abbandonati dai clienti
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.
Aggiungere Apple Pay e 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.
- Apple Pay: il negozio abbia un dominio del negozio attivo.
- 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:
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
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
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:
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
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
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
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
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
I campi della carta usano come colore di evidenziazione del focus (colorFocus) il colore di checkout effettivo dell'esercente definito nelle impostazioni
di Checkout.
I valori che passa in appearance.variables lo sostituiscono, e vengono inviate solo le variabili che imposta.