Tapaya
Online-ZahlungenIntegrationsleitfadenEmbedded Checkout

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.

Your frontend
Your backend
Tapaya
  1. 1. Your frontend to Your backend: Prepare payment.
  2. 2. Your backend to Tapaya: Create or resume payment.
  3. 3. Tapaya to Your backend: Payment and client secret.
  4. 4. Your backend to Your frontend: Client secret and status.
  5. 5. Your frontend to Tapaya: Confirm card and 3-D Secure.
  6. 6. Tapaya to Your frontend: Payment outcome.
  7. 7. Your backend to Tapaya: Read order's payment.
  8. 8. Tapaya to Your backend: Payment status.
  9. 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
ImportWo
@tapayadot/checkoutNur 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/clientIhre Checkout-Seite.
@tapayadot/checkout/reactIhre 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 SDKHTTP (fetch) und cURL
SchlüsselTAPAYA_SECRET_KEY, gelesen von new Tapaya()TAPAYA_CHECKOUT_API_KEY
UmgebungTAPAYA_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.com

Der 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

  1. Ihre Seite bindet die Kartenfelder von Tapaya ein und verweist sie auf einen Endpoint auf Ihrem Server.
  2. Wenn die Seite lädt und wenn der Kunde zahlt, rufen die Kartenfelder diesen Endpoint auf. Er gibt die Zahlung der Bestellung als clientSecret zurück: eine Zugangsberechtigung für genau eine Zahlung, mit der die Kartenfelder die Zahlung direkt bei Tapaya bestätigen. Ihr Seitencode liest es nie.
  3. Ihre Seite verfolgt das outcome der 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.

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

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:

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>

Laden Sie Tapaya und binden Sie die Felder ein. prepare ist der Endpoint, den Sie im nächsten Schritt hinzufügen.

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

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.

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' } },
	);
}

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-statusstatus des prepare-Endpoints
successfulcompleted
declined oder cancelled, mit retryAllowed: falseended
ready, pending, action_required oder eine wiederholbare Ablehnung bzw. ein wiederholbarer Abbruchopen

Aus Ihrem Handler absenden

Fügen Sie dies demselben Skript hinzu. Ihr Handler validiert Ihr Formular und ruft submit() auf:

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() 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:

outcomeWas angezeigt werden soll
openDer Bezahl-Button, aktiviert, solange canSubmit true ist. Eine Ablehnung bleibt open, und das nächste submit() erhält einen neuen Versuch.
pendingFortschritt. Das Ergebnis ist noch nicht bekannt, daher kann nichts erneut bezahlt werden. Das SDK prüft den Status automatisch, solange die Seite sichtbar ist.
completedIhre Bestätigungsseite. Dort verifiziert Ihr Server die Bestellung.
endedEin 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:

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

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:

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

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.

Fehlt Ihnen etwas auf dieser Seite?Wünschen Sie sich eine Funktion. Wir hängen diese Seite an, damit das Team den Kontext sieht.