Embedded Checkout
Fügen Sie Ihrem eigenen Checkout sichere Kartenfelder sowie Apple Pay oder Google Pay hinzu.
Platzieren Sie sichere Kartenfelder in Ihrem eigenen Checkout. Formular und Absende-Button gehören Ihnen; Tapaya übernimmt die Validierung der Karte, die Tokenisierung und 3-D Secure. Eingebettete Zahlungen sind für freigegebene Händler live, zum Testen steht die Sandbox zur Verfügung. Dieser Leitfaden verwendet Tapaya.js 0.2.0 und das 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.
Zahlungsmethoden
Embedded Checkout unterstützt Karten, Apple Pay und Google Pay. Verwenden Sie für Karten Ihren eigenen Bezahl-Button und für Apple Pay und Google Pay die offiziellen Wallet-Buttons. Siehe Apple Pay und Google Pay hinzufügen.
Bevor Sie beginnen
SDK installieren
Ein einziges Paket enthält den Server-Client, das Browser-SDK und den React-Hook:
npm install @tapayadot/checkout| Import | Wo |
|---|---|
@tapayadot/checkout | Nur auf Ihrem Server. Ein Import in einem Browser-Build löst einen Fehler aus, sodass Ihr API-Schlüssel nie auf eine Seite gelangt. |
@tapayadot/checkout/client | Ihre Checkout-Seite. |
@tapayadot/checkout/react | Ihre Checkout-Seite, wenn Sie React verwenden. |
Server konfigurieren
Eingebettete Zahlungen benötigen keine Rückkehr-URLs. Die Serverbeispiele unten gibt es in den Varianten HTTP (fetch), Node.js SDK und cURL. Alle rufen dieselbe Händler-API auf und lassen die Kartenfelder im Browser unverändert.
| Node.js SDK | HTTP (fetch) und cURL | |
|---|---|---|
| Schlüssel | TAPAYA_SECRET_KEY, gelesen von new Tapaya() | TAPAYA_CHECKOUT_API_KEY |
| Umgebung | TAPAYA_ENVIRONMENT=sandbox oder production. Ohne diese Variable wählt ein tapaya_live_-Schlüssel die Produktion, andere Schlüssel verwenden standardmäßig die Sandbox. | TAPAYA_CHECKOUT_API_URL: https://api.sandbox.tapaya.com oder https://api.tapaya.com |
| Plattformmodus | Übergeben Sie das merchantToken der Bestellung bei jedem Aufruf oder setzen Sie es mit new Tapaya(key, { merchantToken }) | Senden Sie bei jeder Anfrage X-Tapaya-Merchant-Token. Die fetch-Hilfsfunktion liest ihn aus TAPAYA_MERCHANT_TOKEN. |
Ein Checkout-Händlerschlüssel benötigt nichts weiter. Verwenden Sie im Plattformmodus den Secret key der Organisation, und lassen Sie ein Backend, das mehrere Händler bedient, das Token aus der authentifizierten Bestellung wählen. Siehe Checkout-Authentifizierung. Client secrets sind für jede Zahlung spezifisch.
Schließen Sie das Onboarding mit Online-Zahlungen ab und prüfen Sie in den Checkout-Einstellungen, ob der Händler Ready ist.
Tapaya in Ihrer CSP erlauben
Der Loader lädt Tapaya.js 0.2.0 von https://js.tapaya.com/checkout/0.2.0/checkout.js; die Version des Browser-Skripts
ist unabhängig von der Version des Node.js SDK. Fügen Sie den Asset-Origin von Tapaya zu Ihrer bestehenden Content Security
Policy hinzu:
script-src https://js.tapaya.com
frame-src https://js.tapaya.comDer Browser erhält den publishable key und das zahlungsspezifische client secret. Ihr geheimer API-Schlüssel für Händler
bleibt auf Ihrem Server, und Kartendaten sowie Skripte des Zahlungsanbieters bleiben im Frame von Tapaya. Für lokale Tests
stellen Sie Tapaya.js selbst bereit und übergeben dessen Origin an den Loader, zum Beispiel
loadTapaya(publishableKey, { apiOrigin, origin: 'http://localhost:4191' }).
So funktioniert es
- Ihre Seite bindet die Kartenfelder von Tapaya ein und verweist sie auf einen Endpoint auf Ihrem Server.
- Wenn die Seite lädt und wenn der Kunde zahlt, rufen die Kartenfelder diesen Endpoint auf. Er gibt die Zahlung der Bestellung
als
clientSecretzurück: eine Zugangsberechtigung für genau eine Zahlung, mit der die Kartenfelder die Zahlung direkt bei Tapaya bestätigen. Ihr Seitencode liest es nie. - Ihre Seite verfolgt das
outcomeder Zahlung. Wenn sie abgeschlossen ist, verifiziert Ihr Server die Zahlung, bevor Sie die Bestellung abwickeln.
Eine Zahlung annehmen
Browser-Einstellungen abrufen
Rufen Sie die Browser-Einstellungen einmal auf Ihrem Server ab und fügen Sie sie Ihrer Frontend-Konfiguration hinzu.
Halten Sie diese Hilfsfunktion auf Ihrem Server. Sie prüft HTTP-Fehler, behandelt auf Wunsch eine fehlende Zahlung der Bestellung als null
und wiederholt nicht automatisch.
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 };Die API gibt publishableKey und environment zurück. Setzen Sie apiOrigin anhand der aufgerufenen API-URL; es ist kein Feld
in der Antwort zur Konfiguration.
Beide Werte können Sie unbedenklich in Ihre Seite aufnehmen. Publishable keys verwenden pk_test_ mit Test-Gateway-Schlüsseln und pk_live_
mit Live-Gateway-Schlüsseln. Übergeben Sie dem Loader immer den von Ihrem Server zurückgegebenen apiOrigin, damit Browser und Server
in derselben Umgebung bleiben.
Kartenfelder hinzufügen
Fügen Sie Ihrem bestehenden Checkout-Formular einen Container hinzu:
<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>Laden Sie Tapaya und binden Sie die Felder ein. prepare ist der Endpoint, den Sie im nächsten Schritt hinzufügen.
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')Beim Einbinden rufen die Felder prepare auf, um die bestehende Zahlung der Bestellung fortzusetzen. Ein Neuladen während einer Zahlung führt nie
zu einer erneuten Belastung: Die Seite zeigt stattdessen den aktuellen Zustand der Zahlung.
Zahlung der Bestellung zurückgeben
Verbinden Sie diesen Handler mit POST /api/payments. getCheckoutOrder() ist eine Funktion Ihrer eigenen Anwendung: Authentifizieren Sie
den Kunden, prüfen Sie den Zugriff auf seine Bestellung und geben Sie deren gespeicherte Werte id, amount, currency und status zurück. Wenden Sie
auf die Route Ihren üblichen CSRF-Schutz an.
Lesen Sie die bestehende Zahlung, bevor Sie einen Versuch erstellen. Verwenden Sie eine ungeklärte Zahlung erneut und erstellen Sie einen weiteren Versuch
nur nach einer Ablehnung oder einem Abbruch mit 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' } },
);
}Verwenden Sie einen Währungscode in Großbuchstaben und eine gespeicherte Bestell-ID mit höchstens 200 Zeichen. Lassen Sie die Bestelldaten unverändert, sobald die Zahlung begonnen hat. Lesen Sie bei einem Timeout die Zahlung der Bestellung erneut, bevor Sie entscheiden, ob ein neuer Versuch nötig ist. Erzeugen Sie niemals einen neuen Schlüssel, nur weil eine Antwort verloren ging.
Die REST-API gibt Zahlungsstatus zurück. Das Node SDK bildet sie auf den status ab, den die Kartenfelder erwarten; fetch- und
cURL-Integrationen wenden dieselbe Zuordnung in ihrem eigenen prepare-Endpoint an:
API-status | status des prepare-Endpoints |
|---|---|
successful | completed |
declined oder cancelled, mit retryAllowed: false | ended |
ready, pending, action_required oder eine wiederholbare Ablehnung bzw. ein wiederholbarer Abbruch | open |
Aus Ihrem Handler absenden
Fügen Sie dies demselben Skript hinzu. Ihr Handler validiert Ihr Formular und ruft submit() auf:
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() ruft prepare für die Zahlung auf, validiert dann die Karte, führt 3-D Secure aus und bestätigt die Zahlung
bei Tapaya.
Ergebnis anzeigen
onChange erhält den snapshot der Zahlung bei jeder Änderung, auch wenn die Seite mit einer Zahlung lädt, die
bereits abgeschlossen ist. Richten Sie sich nach snapshot.outcome:
outcome | Was angezeigt werden soll |
|---|---|
open | Der Bezahl-Button, aktiviert, solange canSubmit true ist. Eine Ablehnung bleibt open, und das nächste submit() erhält einen neuen Versuch. |
pending | Fortschritt. Das Ergebnis ist noch nicht bekannt, daher kann nichts erneut bezahlt werden. Das SDK prüft den Status automatisch, solange die Seite sichtbar ist. |
completed | Ihre Bestätigungsseite. Dort verifiziert Ihr Server die Bestellung. |
ended | Ein neuer Checkout. Für diese Bestellung ist kein weiterer Versuch möglich. |
Rufen Sie payment.destroy() auf, wenn Ihre Checkout-Ansicht ausgehängt wird. Verwenden Sie für React usePayment() aus
@tapayadot/checkout/react. Der Hook akzeptiert dieselben Optionen plus publishableKey und apiOrigin und gibt eine
fieldRef für den Container, den aktuellen snapshot und submit() zurück.
Vor der Abwicklung verifizieren
Das Ergebnis im Browser dient der Anzeige. Lesen Sie auf Ihrer Bestätigungsseite die Zahlung der Bestellung auf Ihrem Server und wickeln Sie die Bestellung nur ab, wenn die Zahlung abgeschlossen ist:
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);Sorgen Sie dafür, dass markOrderPaid() die Zahlung genau einmal verbucht, auch bei wiederholten Aufrufen, und lösen Sie die Abwicklung anhand dieses
gespeicherten Zustands aus.
Bestellungen abgleichen, deren Kunden die Seite verlassen
Ein Kunde kann den Tab schließen, bevor sich die Bestätigungsseite öffnet. Betreiben Sie einen geplanten Job, der für unbezahlte Bestellungen verify() aufruft oder
GET /merchant/payments?merchantOrderId=... liest, mit denselben Prüfungen. Abgelehnte und
abgebrochene Versuche erlauben einen neuen Versuch, daher bleibt eine unbezahlte Bestellung open: Geben Sie sie nach Ihrem eigenen Zeitplan frei, und
nur, solange ihr paymentStatus nicht pending ist. Eine fehlgeschlagene Anfrage oder ein Timeout ist keine Ablehnung, halten Sie die Bestellung daher
reserviert, bis das Ergebnis der Zahlung bekannt ist.
Apple Pay und Google Pay hinzufügen
Wallets erscheinen im Frame von Tapaya neben den Kartenfeldern. Prüfen Sie, bevor sie angezeigt werden, ob:
- Wallets in den Checkout-Einstellungen aktiviert sind.
- Apple Pay: der Shop eine aktive Shop-Domain hat.
- Google Pay in der Produktion: eine Google Pay Merchant ID konfiguriert ist.
- Gerät, Browser und Wallet des Kunden die Methode unterstützen.
Übergeben Sie wallets mit dem Gesamtbetrag des Checkouts in der kleinsten Währungseinheit. Tapaya zeigt die verfügbaren Methoden an, nicht unterstützte oder
deaktivierte Wallets bleiben ausgeblendet. Verwenden Sie dies anstelle des Beispiels createPayment() nur für Karten:
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')Der im Browser angezeigte Gesamtbetrag muss von Ihrem Server stammen, der den Betrag beim Vorbereiten der Zahlung weiterhin berechnet und validiert.
Klicks auf Wallets
Der offizielle Wallet-Button öffnet durch den Klick des Kunden das native Zahlungsfenster der Wallet. onWalletClick validiert Ihren
Checkout und reserviert dessen Zahlung, wenn dieser Button geklickt wird, niemals beim Einbinden oder bei der Auswahl der Methode. Ohne diesen
Callback verwenden Wallets prepare oder clientSecret. Wenn Sie die Bestellung erst beim Klick reservieren, sorgen Sie dafür, dass der beim Einbinden aufgerufene
Callback prepare eine bestehende Bestellung fortsetzt, ohne eine neue zu erstellen.
Lassen Sie Ihren eigenen Bezahl-Button nur sichtbar, wenn snapshot.paymentMethod gleich card ist; Wallets verwenden statt payment.submit() ihre eigenen
Marken-Buttons. Für Ergebnisse von Wallets gelten dieselben Abläufe zur Verifizierung und Wiederherstellung wie für Karten. Das Schließen des
Zahlungsfensters der Wallet lässt den Checkout verfügbar; eine unsichere Zahlung bleibt ausstehend und verhindert eine weitere Belastung.
Gesamtbetrag aktualisieren
Übergeben Sie wallets: { amount, currency, disabled: true }, solange erforderliche Checkout-Angaben unvollständig sind. Aktualisieren Sie den
Gesamtbetrag oder aktivieren Sie die Buttons, ohne neu einzubinden:
payment.update({ wallets: { amount: 2450, currency: 'EUR', disabled: false } })Übergeben Sie wallets: null, um Wallets auszublenden, bis der Gesamtbetrag feststeht. Tapaya verweigert die Bestätigung, wenn die vorbereitete Zahlung
vom Gesamtbetrag der Wallet abweicht. Der React-Hook usePayment() akzeptiert diese Optionen und aktualisiert den Gesamtbetrag, wenn sich
wallets ändert.
Eigene Auswahl der Zahlungsmethode
Wenn Ihre Seite die Auswahl der Zahlungsmethode selbst steuert, übergeben Sie paymentMethod: 'card', 'apple_pay' oder 'google_pay', um
nur diese Methode darzustellen. Eine ausgewählte, aber nicht verfügbare Wallet zeigt das Label walletUnavailable und meldet
snapshot.error.code: 'wallet_unavailable', wenn eine andere Zahlung gefahrlos gestartet werden kann; bieten Sie in Ihrer
Auswahl eine andere Methode an. Sperren Sie die Auswahl, solange snapshot.busy true ist oder die Zahlung pending oder action_required ist.
Frames und CSP
Das SDK fügt seinem Frame allow="payment" hinzu. Wenn sich Ihr Shop in einem anderen Frame befindet, muss auch dieser äußere Frame
Zahlungen erlauben. Apple Pay über verschiedene Origins hinweg (Cross-Origin) erfordert Safari 17 oder neuer. Die Skripte der Wallet-Anbieter bleiben im Frame von Tapaya, daher
benötigt Ihre Seite in ihrer CSP keine zusätzlichen Origins von Anbietern.
Abgelaufenen Browser-Zugriff behandeln
Das client secret läuft nach 24 Stunden ab, und clientSecret ist dann null. Eine Zahlung, die nie abgesendet wurde, wird
dann abgebrochen, und das nächste prepare() startet einen neuen Versuch für dieselbe Bestellung. Eine ausstehende Zahlung bleibt im Browser pending,
und das SDK ruft Ihren Endpoint weiter auf, bis Ihr Server ihr Ergebnis als status meldet. Lesezugriffe vom Server
funktionieren weiterhin, sodass Ihr geplanter Job sie in jedem Fall abschließt.
Serverfehler diagnostizieren
Das Node SDK wirft bei abgelehnten API-Anfragen PaymentsApiError. Prüfen Sie dessen status, code, message,
fieldErrors und requestId auf Ihrem Server. Halten Sie Zugangsdaten und client secrets aus Logs heraus und zeigen Sie dem Kunden
Ihre eigenen Texte an.
An Ihr Branding anpassen
Die Kartenfelder verwenden die wirksame Checkout-Farbe des Händlers aus den
Checkout-Einstellungen als Fokusakzent (colorFocus).
Werte, die Sie in appearance.variables übergeben, überschreiben sie, und es werden nur die Variablen gesendet, die Sie festlegen.