Comisiones de Stripe para desarrolladores: fees, Tax y cálculo neto en PHP
Cientos de posts listan los porcentajes de Stripe. Este no lo hará. Las tasas exactas cambian por país, marca de tarjeta y tier de producto; hardcodearlas en tu código garantiza un bug la próxima vez que Stripe actualice su página de precios. Lo que no cambia es dónde viven esos números en el API y cómo tu código PHP debe leerlos.
La pregunta práctica: un cliente pagó $100, ¿cuánto te quedas realmente? La respuesta involucra un objeto BalanceTransaction que la mayoría de tutoriales ignoran, un array fee_details que separa procesamiento de conversión de moneda, y un campo net que ya tiene la matemática hecha. Para plataformas Connect se agrega application_fee_amount. Para quienes cobran impuestos, tax_behavior en el objeto Price controla si el precio mostrado es bruto o neto.
Si necesitas los fundamentos del SDK, empieza por la guía de integración de pagos. Para el manejo de excepciones en cobros fallidos, consulta la guía de errores.
Dónde viven las comisiones de Stripe en el API
Un objeto PaymentIntent o Charge no lleva información de fees. La comisión vive en el BalanceTransaction, un objeto separado que representa cualquier movimiento de fondos en tu balance de Stripe.
$stripe = new \Stripe\StripeClient('sk_test_...');
// Recuperar un charge con su balance transaction expandido
$charge = $stripe->charges->retrieve('ch_1abc...', [
'expand' => ['balance_transaction'],
]);
$bt = $charge->balance_transaction;
echo $bt->amount; // 10000 (bruto, en centavos)
echo $bt->fee; // 320 (comisión total en centavos)
echo $bt->net; // 9680 (lo que llega a tu balance)
echo $bt->currency; // "usd"
Tres campos, tres números. amount es lo que el cliente pagó, fee es lo que Stripe se quedó, net es la diferencia. Sin porcentajes en el código, sin fórmulas que mantener.
Sin el parámetro expand obtienes un string como "txn_1abc..." en vez del objeto. También puedes recuperarlo por separado:
$bt = $stripe->balanceTransactions->retrieve('txn_1abc...');
Por qué no leer el fee del Charge directamente
Porque Charge no tiene campo fee. La jerarquía de objetos de Stripe separa el evento de pago (Charge/PaymentIntent) del impacto en el balance (BalanceTransaction). Esto es deliberado: un charge puede producir múltiples balance transactions cuando hay reembolsos parciales, disputas o conversiones de moneda.
fee_details: de qué se compone la comisión
El entero fee es una suma. El desglose vive en fee_details:
foreach ($bt->fee_details as $detail) {
printf(
"%s: %d %s (%s)\n",
$detail->type,
$detail->amount,
$detail->currency,
$detail->description
);
}
Un cobro con tarjeta doméstica produce una línea:
stripe_fee: 59 usd (Stripe processing fees)
Un cobro cross-border con conversión de moneda agrega más:
stripe_fee: 59 usd (Stripe processing fees)
stripe_fee: 30 usd (International card fee)
stripe_fee: 20 usd (Currency conversion fee)
Los valores posibles de type: stripe_fee, application_fee, payment_method_passthrough_fee, tax, withheld_tax. Cada línea tiene su propio amount y description. Si necesitas un desglose para contabilidad, parsea este array en vez de adivinar porcentajes.
Las comisiones internacionales de Stripe son líneas separadas
Los hilos de Reddit quejándose de “22% de comisión de Stripe” suelen involucrar una pila de tres líneas: procesamiento base, recargo por tarjeta internacional y conversión de moneda. Cada una es un cargo distinto en fee_details. Si registras solo $bt->fee verás el total pero no entenderás por qué es más alto de lo esperado. Siempre registra el desglose completo en producción:
$feeLog = array_map(
fn($d) => "{$d->description}: {$d->amount}",
$bt->fee_details
);
error_log('Desglose de fees: ' . implode(', ', $feeLog));
Cuándo balance_transaction.fee no está disponible aún
El BalanceTransaction se crea cuando los fondos realmente se mueven. En un evento webhook payment_intent.succeeded, el campo balance_transaction del charge puede estar en null si Stripe aún no creó la transacción.
// Dentro de un handler de webhook
$intent = $event->data->object;
$charge = $stripe->charges->retrieve($intent->latest_charge, [
'expand' => ['balance_transaction'],
]);
if (null === $charge->balance_transaction) {
// Aún no liquidado; programar reintento o escuchar charge.updated
return;
}
$fee = $charge->balance_transaction->fee;
Para datos de fee confiables, escucha charge.updated o invoice.finalized. Para cuando se dispara payout.paid, cada balance transaction en esa ventana de payout tiene números finales.
Construir un lookup de fees en PHP
En vez de una calculadora que hardcodee “2.9% + $0.30”, consulta el fee real desde el API:
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 aún no disponible');
}
$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,
];
}
Esta función trabaja sin importar cuáles sean las tasas actuales de Stripe. Cuando las tasas cambien, tu código no.
Application fees para plataformas Connect
Si manejas un marketplace o plataforma SaaS con Stripe Connect, cobras ingresos a través de application_fee_amount. Esta es la parte de tu plataforma, separada de la comisión de procesamiento de Stripe.
$intent = $stripe->paymentIntents->create([
'amount' => 5000,
'currency' => 'usd',
'payment_method' => $pmId,
'confirm' => true,
'application_fee_amount' => 500, // $5.00 fee de plataforma
], [
'stripe_account' => 'acct_connected_123',
]);
En fee_details del balance transaction de la cuenta conectada, aparecen dos líneas:
stripe_fee: 175 usd (Stripe processing fees)
application_fee: 500 usd (Application fee)
La cuenta conectada paga ambos. Tu plataforma recibe los 500 centavos como un BalanceTransaction separado de tipo application_fee en tu propia cuenta.
Un error habitual: confundir application_fee_amount con la comisión de procesamiento de Stripe. El application fee va para ti, la plataforma. La comisión de procesamiento va para Stripe. Se acumulan: un pago de $50.00 con un application fee de $5.00 podría dejarle a la cuenta conectada $43.25 después de ambas comisiones.
Cálculo de impuestos con Stripe Tax API
El Stripe Tax API (disponible desde 2022) automatiza sales tax, IVA y GST. Reemplazó el patrón anterior de agregar impuestos manualmente como line items con porcentajes hardcodeados. Para un flujo de pago personalizado (no Checkout), calculas el impuesto en el servidor antes de crear el PaymentIntent:
$calculation = $stripe->tax->calculations->create([
'currency' => 'usd',
'line_items' => [
[
'amount' => 10000,
'reference' => 'L1',
],
],
'customer_details' => [
'address' => [
'line1' => '920 5th Ave',
'city' => 'Seattle',
'state' => 'WA',
'postal_code' => '98104',
'country' => 'US',
],
'address_source' => 'shipping',
],
]);
echo $calculation->amount_total; // 11030 (item + impuesto)
echo $calculation->tax_amount_exclusive; // 1030
$intent = $stripe->paymentIntents->create([
'amount' => $calculation->amount_total,
'currency' => 'usd',
'payment_method' => $pmId,
'confirm' => true,
]);
Después de que el pago tiene éxito, registra la transacción para reportes:
$stripe->tax->transactions->createFromCalculation([
'calculation' => $calculation->id,
'reference' => 'order_' . $orderId,
]);
tax_behavior: inclusive vs exclusive
Cuando creas un objeto Price, tax_behavior controla si el monto ya incluye impuestos:
$price = $stripe->prices->create([
'unit_amount' => 10000,
'currency' => 'eur',
'product' => 'prod_abc...',
'tax_behavior' => 'inclusive', // o 'exclusive'
]);
Exclusive (común en EE.UU. y México): el impuesto se calcula encima. Un artículo de $100.00 se convierte en $110.30 en el checkout si la tasa es 10.3%.
Inclusive (común en la UE): el precio de etiqueta ya contiene el impuesto. Un artículo de 100.00 EUR se queda en 100.00 EUR; Stripe calcula la porción de impuesto hacia atrás (ej., 16.67 EUR si el IVA es 20%).
El objeto Price y adaptive pricing
El objeto Price reemplazó al deprecado Plan para definir cuánto cobrar. Cada producto tiene al menos un Price, con moneda, intervalo de facturación (para suscripciones) y monto.
Multi-moneda con objetos Price
Para pricing internacional tienes dos opciones. La tradicional: crear un Price por moneda.
$priceEur = $stripe->prices->create([
'unit_amount' => 2300,
'currency' => 'eur',
'product' => 'prod_abc...',
]);
La más nueva: Adaptive Pricing. Cuando se habilita en una Checkout Session, Stripe convierte tu precio base a la moneda local del cliente usando tasas de cambio en tiempo real. Defines un precio, Stripe maneja el resto.
$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',
]);
Adaptive Pricing muestra el precio convertido en la página de Checkout antes de que el cliente confirme. La liquidación sigue ocurriendo en la moneda por defecto de tu cuenta. Las comisiones de conversión de moneda aparecen como línea separada en fee_details.
Pasar la comisión de procesamiento al cliente
Algunos negocios agregan un recargo para cubrir la comisión de Stripe. La matemática parece simple pero se equivoca en la fórmula más seguido de lo que uno esperaría:
// Mal: el recargo en sí mismo se le cobra una comisión
$amount = 10000;
$surcharge = (int) round($amount * 0.029 + 30);
$total = $amount + $surcharge;
// Stripe cobra 2.9% + $0.30 del $total, que es más que $surcharge
// Correcto: resolver para total donde total - fee(total) = amount
// total = (amount + fixed) / (1 - rate)
$rate = 0.029;
$fixed = 30; // centavos
$total = (int) ceil(($amount + $fixed) / (1 - $rate));
// $total = 10330, fee sobre 10330 = 330, net = 10000
Nota legal: agregar recargos a pagos con tarjeta de crédito está regulado o prohibido en algunas jurisdicciones (UE, Australia, partes de EE.UU., y en algunos países de América Latina). Verifica las reglas locales antes de implementar esto.
Micro-pagos: cuando el fee fijo domina
En un cobro de $2.00, el fee fijo por transacción de Stripe representa un porcentaje mucho mayor que en $100.00. Por esto la tasa efectiva en montos pequeños puede parecer alarmante:
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% en $2.00
echo effectiveRate(500, 0.029, 30); // 9.0% en $5.00
echo effectiveRate(10000, 0.029, 30); // 3.2% en $100.00
echo effectiveRate(50000, 0.029, 30); // 2.96% en $500.00
Para productos bajo $5.00, considera agrupar múltiples items en un solo cobro o usar un monto mínimo de orden. El calendario de fees actual está en stripe.com/pricing.
Reembolsos: Stripe se queda con la comisión
Desde septiembre 2017 para cuentas nuevas (y septiembre 2020 para cuentas legacy), Stripe ya no reembolsa la comisión de procesamiento cuando haces un refund. Un cobro de $100.00 que reembolsas completamente te cuesta la comisión original sin recuperación.
$refund = $stripe->refunds->create([
'charge' => 'ch_1abc...',
]);
$refundBt = $stripe->balanceTransactions->retrieve(
$refund->balance_transaction
);
echo $refundBt->amount; // -10000 (reembolsado al cliente)
echo $refundBt->fee; // 0 (no se devuelve comisión)
echo $refundBt->net; // -10000 (tu balance decrece por el monto completo)
La comisión original del cobro (ej., 320 centavos) se queda con Stripe. Tu pérdida real en un reembolso completo es $amount + $originalFee. Factoriza esto en tu política de reembolsos y en cualquier código de reconciliación financiera.
Posts y calculadoras antiguos que asumen devolución de comisión te darán números incorrectos.
Stripe vs PayPal: comparación de comisiones para desarrolladores
Ambos cobran un porcentaje base más un fee fijo para pagos domésticos con tarjeta. Los números exactos difieren por país y cambian periódicamente; revisa stripe.com/pricing y paypal.com/webapps/mpp/merchant-fees para las tasas actuales.
Donde divergen para desarrolladores:
Transparencia de fees en el API. Stripe expone cada componente de la comisión a través de balance_transaction.fee_details. El campo transaction_fee de PayPal en una captura te da un solo número sin desglose de cross-border, conversión de moneda ni recargos por método de pago.
Políticas de reembolso. Stripe se queda con la comisión de procesamiento en refunds. PayPal también (cambió en 2019). Ambos te cuestan dinero por reembolso, pero los montos exactos difieren.
Cobro de fees. Stripe deduce comisiones de cada pago antes de que llegue a tu balance. PayPal debita fees de tu balance de PayPal. En código, el campo net de Stripe te da el monto post-fee; con PayPal lo calculas de gross_amount - fee_amount.
Fees de marketplace. Stripe Connect proporciona application_fee_amount con ruteo explícito a la plataforma. El equivalente de PayPal usa un modelo de negociación de fees diferente. El enfoque de Stripe es más transparente en la respuesta del API.
La pregunta real no es cuál es 0.1% más barato, sino cuál API le da a tu código datos de fee confiables sin gimnasia de hojas de cálculo. En esa métrica, fee_details de Stripe es el más granular.
Comisiones por país
Las comisiones de Stripe varían significativamente por región. Si buscas las comisiones de Stripe en Colombia, Argentina o Perú, estos son los puntos relevantes para desarrolladores:
- México – tasas base más altas que EE.UU., más comisión adicional para meses sin intereses (MSI). Las comisiones de MSI las absorbe el comerciante, no el cliente.
- Colombia, Perú, Argentina – disponibilidad más reciente, tasas que reflejan el costo de procesamiento local. Algunos métodos de pago alternativos (OXXO, PSE) tienen estructuras de fee distintas a las tarjetas.
- España – tasas europeas estándar con IVA incluido. Las tarjetas intra-EEE tienen fees más bajos que las internacionales.
Los números concretos cambian; consulta siempre stripe.com/pricing seleccionando tu país. En código, la única fuente de verdad sigue siendo balance_transaction.fee.
Reconciliación: verificar la matemática de Stripe
Para reportes financieros, itera sobre balance transactions en un rango de fechas y verifica los totales:
$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;
}
if ($totalNet !== $totalGross - $totalFee) {
throw new RuntimeException('Suma neta no cuadra: bruto - fees != neto');
}
printf(
"Bruto: %s, Comisiones: %s, Neto: %s\n",
number_format($totalGross / 100, 2),
number_format($totalFee / 100, 2),
number_format($totalNet / 100, 2)
);
Usa autoPagingIterator() para paginación. La referencia del API cubre los patrones de paginación en detalle. Filtra por type para separar charges de payouts, refunds y ajustes.
Preguntas frecuentes
¿Stripe cobra 3%?
La tasa base de procesamiento depende de tu país y tipo de tarjeta. Para cuentas en EE.UU. es típicamente 2.9% + $0.30 por cobro exitoso, pero esto varía para tarjetas internacionales, AMEX y métodos de pago específicos. Revisa stripe.com/pricing para tu región. En código, nunca hardcodees este número; lee balance_transaction.fee después de que el cobro se liquide.
¿Cuánto cobra Stripe por $100?
Para una tarjeta doméstica en EE.UU., aproximadamente $3.20 (2.9% de $100 + $0.30 fijo). Pero si la tarjeta es internacional, se agrega el recargo cross-border. Si hay conversión de moneda, se agrega esa también. La única fuente de verdad es balance_transaction.fee en el cobro real.
¿Cómo agrego un recargo del 3% en Stripe?
Usa la fórmula total = (amount + fixed_fee) / (1 - rate) para calcular el recargo correctamente. Un ingenuo amount * 0.03 se queda corto porque al recargo en sí se le cobra comisión. Consulta la sección de recargos arriba. Y verifica si el recargo es legal en tu jurisdicción.
¿Qué es más barato, PayPal o Stripe?
Las tasas base están a 0.1-0.5% de diferencia para la mayoría de las regiones. La diferencia real de costo viene de políticas de reembolso, fees cross-border y descuentos por volumen. Compara basándote en tu mix real de transacciones (doméstico vs internacional, ticket promedio, tasa de refund), no en porcentajes de portada.
¿Qué son los meses sin intereses (MSI) en Stripe?
En México, Stripe acepta MSI como opción de pago. El comerciante absorbe una comisión adicional (que varía según el número de meses). Stripe maneja la lógica internamente; tu código solo necesita habilitar la opción en la Checkout Session o en el PaymentIntent. La comisión de MSI aparece como línea separada en fee_details.
¿Has visto algo inexacto en esta página?
Reportar un error