Stripe Gebühren in PHP: Fee-Logik, Tax API und Netto-Berechnung
Die Prozentsätze stehen auf stripe.com/pricing. Sie in den Code zu schreiben ist sinnlos: die Sätze hängen vom Land des Kontos, dem Kartentyp und dem Produkt ab, und Stripe aktualisiert sie gelegentlich. Nützlicher ist zu wissen, wo man die tatsächliche Gebühr eines konkreten Payments per API abruft.
Weder PaymentIntent noch Charge enthalten ein Feld mit der Gebührensumme. Diese Information lebt auf dem BalanceTransaction-Objekt. Dieser Artikel zeigt, wie man es abruft, was in fee_details steckt, wie application_fee auf Connect-Plattformen funktioniert und warum tax_behavior auf dem Price-Objekt wichtig ist. Alles mit PHP-Code, der bei der nächsten Tarifänderung weiterläuft.
Für die SDK-Grundlagen siehe den Payment-Integration-Guide. Fehlerbehandlung bei Charges deckt die Fehler-Referenz ab.
Wo die Gebühr in der API steckt
Der BalanceTransaction beschreibt jede Geldbewegung auf dem Kontostand: Einzahlung, Abbuchung, Rückerstattung, Korrektur. Drei Kernfelder: amount (eingegangene Summe), fee (einbehaltene Gebühr), net (was auf dem Guthaben landet).
$stripe = new \Stripe\StripeClient('sk_test_...');
$charge = $stripe->charges->retrieve('ch_1abc...', [
'expand' => ['balance_transaction'],
]);
$bt = $charge->balance_transaction;
echo $bt->amount; // 10000 (in Minoreinheiten, Cent)
echo $bt->fee; // 320
echo $bt->net; // 9680
echo $bt->currency; // "eur"
Ohne expand gibt das Feld balance_transaction einen String wie txn_1abc... zurück. Alternativ separat abrufen:
$bt = $stripe->balanceTransactions->retrieve('txn_1abc...');
Warum fee nicht auf dem Charge liegt
Stripe trennt das Zahlungsereignis (Charge, PaymentIntent) von der Auswirkung auf den Kontostand (BalanceTransaction). Die Trennung hat einen Grund: ein Charge kann mehrere Balance Transactions erzeugen bei Teilrückerstattungen, Disputes oder Währungskonvertierung.
fee_details: woraus sich die Gebühr zusammensetzt
Das fee-Feld auf dem BalanceTransaction enthält die Gesamtsumme. Die Aufschlüsselung liegt in fee_details:
foreach ($bt->fee_details as $detail) {
printf(
"%s: %d %s (%s)\n",
$detail->type,
$detail->amount,
$detail->currency,
$detail->description
);
}
Für eine inländische Karte eine Zeile:
stripe_fee: 59 eur (Stripe processing fees)
Eine internationale Karte mit Währungskonvertierung fügt weitere Zeilen hinzu:
stripe_fee: 59 eur (Stripe processing fees)
stripe_fee: 30 eur (International card fee)
stripe_fee: 20 eur (Currency conversion fee)
Mögliche type-Werte: stripe_fee, application_fee, payment_method_passthrough_fee, tax, withheld_tax. Jede Zeile hat eigene amount und description.
Cross-Border: drei Zeilen statt einer
Auf Reddit tauchen regelmäßig Threads über “22% Stripe-Gebühr” auf. Dahinter steckt meistens die Summe dreier Zeilen: Basisverarbeitung, internationale Karte, Währungskonvertierung. Wer nur $bt->fee loggt, sieht die Gesamtsumme, aber nicht die Ursache. In Produktion die volle Aufschlüsselung loggen:
$feeLog = array_map(
fn($d) => "{$d->description}: {$d->amount}",
$bt->fee_details
);
error_log('Fee breakdown: ' . implode(', ', $feeLog));
Wenn balance_transaction noch nicht bereit ist
Der BalanceTransaction wird erstellt, wenn Gelder tatsächlich fließen. Im Webhook-Handler für payment_intent.succeeded kann das Feld balance_transaction auf dem Charge noch null sein, wenn Stripe die Transaktion noch nicht angelegt hat.
$intent = $event->data->object;
$charge = $stripe->charges->retrieve($intent->latest_charge, [
'expand' => ['balance_transaction'],
]);
if (null === $charge->balance_transaction) {
// Noch nicht abgerechnet, Retry oder auf charge.updated warten
return;
}
$fee = $charge->balance_transaction->fee;
Für endgültige Zahlen auf charge.updated oder invoice.finalized lauschen. Zum Zeitpunkt von payout.paid haben alle Balance Transactions im Auszahlungsfenster finale Werte.
Gebühren-Lookup-Funktion
Statt eines Rechners mit festcodierten “2.9% + $0.30” die tatsächlichen Daten abfragen:
function getPaymentFee(\Stripe\StripeClient $stripe, string $chargeId): array
{
$charge = $stripe->charges->retrieve($chargeId, [
'expand' => ['balance_transaction'],
]);
$bt = $charge->balance_transaction;
if (null === $bt || !is_object($bt)) {
throw new \RuntimeException('Balance transaction not available yet');
}
$details = [];
foreach ($bt->fee_details as $d) {
$details[] = [
'type' => $d->type,
'amount' => $d->amount,
'currency' => $d->currency,
'description' => $d->description,
];
}
return [
'gross' => $bt->amount,
'fee' => $bt->fee,
'net' => $bt->net,
'details' => $details,
];
}
Die Funktion liefert aktuelle Daten unabhängig von den geltenden Tarifen. Wenn Stripe die Sätze ändert, funktioniert der Code weiter.
application_fee für Connect-Plattformen
Marketplaces und SaaS-Plattformen auf Stripe Connect nehmen ihren Anteil über application_fee_amount. Das ist die Plattformgebühr, nicht die Verarbeitungsgebühr von Stripe.
$intent = $stripe->paymentIntents->create([
'amount' => 5000,
'currency' => 'eur',
'payment_method' => $pmId,
'confirm' => true,
'application_fee_amount' => 500, // 5,00 EUR Plattformanteil
], [
'stripe_account' => 'acct_connected_123',
]);
In fee_details des Connected Account erscheinen zwei Zeilen:
stripe_fee: 175 eur (Stripe processing fees)
application_fee: 500 eur (Application fee)
Der Connected Account zahlt beide. Die Plattform erhält die 500 Cent als separate BalanceTransaction vom Typ application_fee auf dem eigenen Konto.
Application Fees abrufen
$fees = $stripe->applicationFees->all([
'charge' => 'ch_1abc...',
'limit' => 10,
]);
foreach ($fees->data as $appFee) {
echo $appFee->amount; // 500
echo $appFee->account; // "acct_connected_123"
echo $appFee->balance_transaction; // BT auf dem Plattform-Konto
}
Häufiger Fehler: application_fee_amount mit der Stripe-Verarbeitungsgebühr verwechseln. Die Application Fee geht an die Plattform, die Processing Fee an Stripe. Beide werden addiert: von einer 50,00 EUR-Zahlung mit 5,00 EUR Application Fee erhält der Connected Account etwa 43,25 EUR nach Abzug beider Gebühren.
Steuerberechnung mit Stripe Tax API
Die Stripe Tax API (verfügbar seit 2022) automatisiert die Berechnung von Umsatzsteuer, VAT und GST. Vor ihrer Einführung wurden Steuern als manuelle Line Items mit festcodierten Prozentsätzen hinzugefügt. Für einen eigenen Zahlungsflow (nicht Checkout) wird die Steuer serverseitig vor dem PaymentIntent berechnet:
$calculation = $stripe->tax->calculations->create([
'currency' => 'eur',
'line_items' => [
[
'amount' => 10000, // 100,00 EUR Artikelpreis
'reference' => 'L1',
],
],
'customer_details' => [
'address' => [
'line1' => 'Friedrichstr. 123',
'city' => 'Berlin',
'postal_code' => '10117',
'country' => 'DE',
],
'address_source' => 'shipping',
],
]);
echo $calculation->amount_total; // 11900 (Artikel + 19% MwSt)
echo $calculation->tax_amount_exclusive; // 1900
$intent = $stripe->paymentIntents->create([
'amount' => $calculation->amount_total,
'currency' => 'eur',
'payment_method' => $pmId,
'confirm' => true,
]);
Nach der Zahlung die Transaktion für die Berichterstattung festhalten:
$stripe->tax->transactions->createFromCalculation([
'calculation' => $calculation->id,
'reference' => 'order_' . $orderId,
]);
tax_behavior: inclusive und exclusive
Beim Erstellen eines Price-Objekts bestimmt tax_behavior, ob der Betrag die Steuer bereits enthält:
$price = $stripe->prices->create([
'unit_amount' => 10000,
'currency' => 'eur',
'product' => 'prod_abc...',
'tax_behavior' => 'inclusive', // oder 'exclusive'
]);
Exclusive (typisch in den USA): Steuer wird aufgeschlagen. Ein Artikel für $100,00 kostet bei 10,3% Steuer $110,30 an der Kasse.
Inclusive (typisch in der EU): der angezeigte Preis enthält die Steuer bereits. Ein Artikel für 100,00 EUR bleibt 100,00 EUR; Stripe berechnet den Steueranteil rückwärts (z.B. 15,97 EUR bei 19% MwSt).
In einer Checkout Session lassen sich beide Modi kombinieren. Jeder Line Item kann ein eigenes tax_behavior haben, Stripe summiert korrekt.
Price-Objekt und Adaptive Pricing
Das Price-Objekt hat den veralteten Plan ersetzt. Jedes Product hat mindestens einen Price mit Währung, Betrag und, für Abos, einem Abrechnungsintervall.
// Einmalzahlung
$price = $stripe->prices->create([
'unit_amount' => 2500,
'currency' => 'eur',
'product' => 'prod_abc...',
]);
// Abo (monatlich)
$monthly = $stripe->prices->create([
'unit_amount' => 1999,
'currency' => 'eur',
'product' => 'prod_abc...',
'recurring' => ['interval' => 'month'],
]);
Mehrwährungsfähigkeit
Der klassische Weg: je einen Price pro Währung erstellen.
Die modernere Alternative: Adaptive Pricing. Bei Aktivierung auf einer Checkout Session konvertiert Stripe den Basispreis in die lokale Währung des Käufers nach aktuellem Kurs. Ein Price, den Rest erledigt Stripe.
$session = $stripe->checkout->sessions->create([
'mode' => 'payment',
'line_items' => [[
'price' => 'price_abc...',
'quantity' => 1,
]],
'adaptive_pricing' => ['enabled' => true],
'success_url' => 'https://example.com/thanks',
'cancel_url' => 'https://example.com/cart',
]);
Der Käufer sieht den Preis in seiner Währung auf der Checkout-Seite. Die Abrechnung mit dem Verkäufer erfolgt in der Kontowährung. Die Konvertierungsgebühr erscheint als separate Zeile in fee_details.
Adaptive Pricing funktioniert nur mit Checkout Sessions. In einem PaymentIntent-Flow braucht Mehrwährungsfähigkeit weiterhin separate Price-Objekte oder manuelle Konvertierung.
Gebühr an den Käufer weitergeben (Surcharging)
Manche Unternehmen schlagen eine Gebühr auf, um Stripes Kosten zu decken. Die Formel sieht einfach aus, aber die meisten Umsetzungen rechnen falsch:
// Falsch: der Aufschlag selbst wird gebührenpflichtig
$amount = 10000;
$surcharge = (int) round($amount * 0.029 + 30);
$total = $amount + $surcharge;
// Stripe nimmt 2.9% + $0.30 von $total, das ist mehr als $surcharge
// Richtig: Gleichung total - fee(total) = amount lösen
// total = (amount + fixed) / (1 - rate)
$rate = 0.029;
$fixed = 30; // Cent
$total = (int) ceil(($amount + $fixed) / (1 - $rate));
// $total = 10330 → fee von 10330 = 330 → net = 10000 = $100
Rechtlicher Hinweis: Surcharging von Kreditkartenzahlungen ist in einigen Rechtsräumen reguliert oder verboten (EU, Australien, Teile der USA). Vor der Umsetzung die lokale Gesetzgebung prüfen.
Kleinbeträge: wenn die Fixgebühr dominiert
Bei kleinen Beträgen nimmt die feste Pro-Transaktion-Gebühr ($0.30) einen unverhältnismäßig großen Anteil ein:
function effectiveRate(int $amountCents, float $rate, int $fixedCents): float
{
$fee = (int) round($amountCents * $rate) + $fixedCents;
return round($fee / $amountCents * 100, 2);
}
echo effectiveRate(200, 0.029, 30); // 18.0% bei $2.00
echo effectiveRate(500, 0.029, 30); // 9.0% bei $5.00
echo effectiveRate(10000, 0.029, 30); // 3.2% bei $100.00
echo effectiveRate(50000, 0.029, 30); // 2.96% bei $500.00
Für Produkte unter $5.00 mehrere Positionen in eine Transaktion bündeln oder einen Mindestbestellwert einführen. Stripe bot früher einen Mikrozahlungs-Tarif mit niedrigerer Fixgebühr an, der ist für neue Konten nicht mehr verfügbar. Aktuelle Sätze auf stripe.com/pricing.
Rückerstattungen: Stripe behält die Gebühr
Seit September 2017 (neue Konten) bzw. September 2020 (Legacy) erstattet Stripe die Verarbeitungsgebühr bei einem Refund nicht zurück. Ein vollständiger Refund einer 100,00 EUR-Zahlung kostet den Verkäufer die ursprüngliche Gebühr.
$refund = $stripe->refunds->create([
'charge' => 'ch_1abc...',
]);
$refundBt = $stripe->balanceTransactions->retrieve(
$refund->balance_transaction
);
echo $refundBt->amount; // -10000 (Rückerstattung an Käufer)
echo $refundBt->fee; // 0 (keine Gebühr zurückerstattet)
echo $refundBt->net; // -10000 (Guthaben sinkt um vollen Betrag)
Die ursprüngliche Gebühr (z.B. 320 Cent) bleibt bei Stripe. Der tatsächliche Verlust bei einem vollen Refund: Zahlungsbetrag + ursprüngliche Gebühr. In der Finanzabstimmung und im Code berücksichtigen.
Alte Rechner und Blogposts, die eine Gebührenrückerstattung annehmen, liefern falsche Zahlen.
Stripe, PayPal, Square: Vergleich für Entwickler
Die Basissätze aller drei Systeme unterscheiden sich um Zehntel-Prozentpunkte. Aktuelle Zahlen auf stripe.com/pricing, paypal.com/webapps/mpp/merchant-fees, squareup.com/pricing. Der reale Unterschied für PHP-Entwickler liegt nicht in den Prozenten, sondern in der API.
Fee-Transparenz in der API. Stripe liefert jeden Bestandteil über balance_transaction.fee_details. PayPal gibt transaction_fee als einzelne Zahl zurück, ohne Aufschlüsselung nach Cross-Border, Konvertierung und Kartentyp. Square liegt näher an Stripe: der processing_fee-Array auf dem Payment-Objekt enthält separate Zeilen.
Rückerstattungspolitik. Stripe und PayPal (seit 2019) behalten die Gebühr bei Refunds. Beträge unterscheiden sich, das Modell ist gleich.
Gebühreneinbehaltung. Stripe zieht die Fee aus jeder Zahlung vor der Gutschrift auf dem Guthaben ab; das net-Feld zeigt das Ergebnis. PayPal bucht die Fee separat vom PayPal-Kontostand.
Marketplaces. Stripe Connect bietet application_fee_amount mit expliziter Weiterleitung an die Plattform. PayPals Äquivalent (Partner Referrals / Marketplace) nutzt ein anderes Fee-Verhandlungsmodell, weniger transparent in der API-Response.
Das Auswahlkriterium für Entwickler: welche API liefert zuverlässige Fee-Daten ohne manuelle Berechnungen. An Granularität von fee_details liegt Stripe vorn.
Abstimmung: Daten per API prüfen
Für die Finanzberichterstattung Balance Transactions über einen Zeitraum iterieren und Summen prüfen:
$transactions = $stripe->balanceTransactions->all([
'created' => [
'gte' => strtotime('2026-04-01'),
'lte' => strtotime('2026-04-30'),
],
'type' => 'charge',
'limit' => 100,
]);
$totalGross = 0;
$totalFee = 0;
$totalNet = 0;
foreach ($transactions->autoPagingIterator() as $bt) {
$totalGross += $bt->amount;
$totalFee += $bt->fee;
$totalNet += $bt->net;
}
assert($totalNet === $totalGross - $totalFee);
printf(
"Brutto: %s, Gebühren: %s, Netto: %s\n",
number_format($totalGross / 100, 2),
number_format($totalFee / 100, 2),
number_format($totalNet / 100, 2)
);
autoPagingIterator() übernimmt die Pagination automatisch über starting_after. Details zur Pagination im API-Referenz-Artikel.
Den type-Filter nutzen, um Charges von Payouts, Refunds und Korrekturen zu trennen. Für einen vollständigen Gebührenbericht über alle Transaktionstypen die Ergebnisse nach reporting_category gruppieren – das Feld entspricht den Kategorien in Stripes Finanzberichten und vereinfacht die Abstimmung mit Dashboard-Exporten.
FAQ
Nimmt Stripe 3%?
Der Basissatz hängt vom Land und Kartentyp ab. Für US-Konten typischerweise 2,9% + $0,30 pro erfolgreicher Zahlung, für europäische Konten gelten andere Sätze. Aktuelle Tarife auf stripe.com/pricing. Im Code nie den Prozentsatz festcodieren; balance_transaction.fee nach dem Charge auslesen.
Wie viel nimmt Stripe bei 100 EUR?
Für eine inländische EU-Karte ungefähr 1,4% + 0,25 EUR, also etwa 1,65 EUR. Bei internationaler Karte kommen Cross-Border-Zuschläge hinzu. Die einzige Quelle für genaue Daten: balance_transaction.fee auf dem konkreten Charge. Zwei Zahlungen mit gleichem Betrag können unterschiedliche Gebühren haben.
Ist die Einrichtung von Stripe kostenlos?
Keine Einrichtungsgebühr, kein Monatsabo, kein Mindestvolumen. Bezahlung nur pro Transaktion. API, Dashboard, Testmodus und Entwicklertools sind kostenlos.
Wie füge ich eine 3%-Gebühr hinzu?
Die Formel: total = (amount + fixed_fee) / (1 - rate). Ein naiver Aufschlag von amount * 0.03 reicht nicht, weil der Aufschlag selbst gebührenpflichtig ist. Details in der Surcharging-Sektion. Vor der Umsetzung prüfen, ob Surcharging in der eigenen Rechtsordnung erlaubt ist.
PayPal oder Stripe günstiger?
Basissätze liegen 0,1-0,5% auseinander je nach Region. Der reale Unterschied kommt von Rückerstattungspolitik, Cross-Border-Fees und Mengenrabatten. Beide behalten die Gebühr bei Refunds. Für Unternehmen mit hohem Volumen bietet Stripe individuelle Sätze. Auf Basis des tatsächlichen Transaktionsmix vergleichen (inländisch vs. international, Durchschnittsbetrag, Refund-Rate), nicht auf Basis der Headline-Prozente.
Nimmt Stripe Gebühren für Debitkarten?
Ja. In den meisten Regionen gilt der gleiche Satz wie für Kreditkarten. In manchen Ländern sind Debit-Sätze niedriger. Die aktuellen Tarife für das eigene Konto stehen im Dashboard unter Settings > Payment methods.
Was ist Adaptive Pricing?
Eine Checkout-Funktion, die den Produktpreis automatisch in die lokale Währung des Käufers zum aktuellen Kurs konvertiert. Man definiert einen Price in der Basiswährung, Stripe zeigt dem Käufer den Preis in seiner Währung. Abrechnung mit dem Verkäufer in der Kontowährung. Konvertierungsgebühren erscheinen in fee_details. Funktioniert nur in Checkout Sessions, nicht in PaymentIntent-Flows.
Etwas Ungenaues auf dieser Seite entdeckt?
Fehler melden