add_filter('wc_stripe_payment_intent_args', function ($args, $order) {
// Interceptăm crearea PaymentIntent-ului în pluginul oficial WooCommerce Stripe
$vendor_stripe_id = get_post_meta($order->get_id(), '_vendor_stripe_account_id', true);
if (!$vendor_stripe_id) {
return $args; // Comandă normală, fără split
}
$total_cents = (int) round($order->get_total() * 100);
$marketplace_fee_percent = 0.12; // 12% comision platformă
$fee_cents = (int) round($total_cents * $marketplace_fee_percent);
// Aplicăm Destination Charge pentru vânzător unic per comandă
$args['application_fee_amount'] = $fee_cents;
$args['transfer_data'] = [
'destination' => $vendor_stripe_id,
];
return $args;
}, 10, 2);Povestea din producție
Anul trecut am lucrat la un marketplace B2B pe WooCommerce cu vreo 280 de furnizori activi și o medie de 4.000 de tranzacții lunar. Cerința era clasică: clientul plătește un coș mixt (produse de la 2 furnizori diferiți), iar banii trebuie să se împartă automat — platforma își ia comisionul de 12%, iar restul pleacă direct în conturile bancare ale vendorilor.
Prima tentativă a clientului a fost să cumpere două pluginuri de marketplace de pe CodeCanyon. După trei săptămâni de teste în staging, am avut coșmaruri cu webhook-uri ratate, plăți blocate în Stripe pending și refund-uri care trăgeau banii doar din contul platformei, lăsând furnizorul cu banii intacți. Așa că am aruncat mizeriile alea la gunoi și am scris logica direct pe API-ul Stripe Connect.
Cele trei modele de Stripe Connect (și ce alegi)
Când lucrezi cu Stripe Connect pe WooCommerce, ai trei opțiuni mari și late:
- Direct Charges: Clientul plătește direct furnizorul, iar tu îi ceri furnizorului un application fee. E ok fiscal pentru unele nișe, dar devine un iad pe Woo dacă ai coșuri multi-vendor (nu poți împărți o singură tranzacție de card către doi destinatari diferiți direct).
- Separate Charges and Transfers: Încasezi tot pe platformă, apoi declanșezi manual/cron transferuri către vendori. E flexibil, dar ești 100% răspunzător legal pentru chargeback-uri și ești considerat merchant of record pe toată suma.
- Destination Charges: Platforma încasează banii, dar în momentul autorizării specifici
transfer_data[destination]șiapplication_fee_amount. Banii ajung la furnizor, iar comisionul tău rămâne la tine. Dacă un coș conține produse de la un singur vendor, asta e de departe cea mai elegantă variantă.
Pentru coșuri multi-vendor pure, din păcate, soluția curată din punct de vedere Stripe rămâne Separate Charges & Transfers, rulată pe un hook de woocommerce_order_status_processing.
Unde crapă sistemele gata făcute?
Problema principală la soluțiile no-code/low-code din Woo este că presupun că webhook-urile Stripe vin mereu instant și în ordine perfectă. În realitate, la volume mari, un webhook de payment_intent.succeeded poate ajunge la 15 secunde după ce clientul a fost redirecționat în pagina de Thank You.
Dacă procesezi split-ul direct pe hook-ul de checkout synchronous, riști timeout de PHP dacă API-ul Stripe răspunde mai greu. Dacă îl pui strict pe webhook, riști race-condition când clientul dă refresh în pagina de comenzi.
Soluția pe care am aplicat-o a fost decuplarea: creăm PaymentIntent-ul cu metadate despre sub-comenzi (vendor_id, split_amount) și procesăm split-ul asincron prin Action Scheduler (librăria nativă din Woo pentru fundal), declanșată de primul event valid care confirmă plata.
Cum gestionezi refund-urile parțiale?
Aici e cel mai mare trade-off. Dacă faci refund la o comandă split:
- Tragi comisionul înapoi de la tine?
- Îi ceri lui Stripe să reverseze transferul către vendor (
reverse_transfer => true)?
Dacă vendorul și-a scos deja banii din contul Stripe Connect (payout automat zilnic), reversal-ul de transfer îi va băuca soldul pe negativ. Dacă nu are fonduri, Stripe îți va opri ție banii din soldul platformei pentru a acoperi refund-ul cumpărătorului. E un risc de business pe care trebuie să-l clarifici cu clientul înainte să scrii prima linie de cod.
Voi cum ați rezolvat problema asta pe proiectele mari de WooCommerce — ați mers pe Stripe Connect Custom (unde controlați voi tot UI-ul de onboarding) sau pe Express/Standard?