Blog

integrácií 8 min čítania

Stripe webhooky v PHP: spoľahlivý handler, ktorému môžete dôverovať

Webhook Stripe, ktorý funguje na vašom notebooku, ešte nie je webhook, ktorému môžete dôverovať peniazmi. Táto príručka ukazuje, ako overiť podpisy, rýchlo potvrdiť, odstrániť duplicitné udalosti a zvládnuť opakované pokusy v jednoduchom PHP a Laravel s kódom, ktorý môžete vložiť do projektu.

Ilustrácia článku: Stripe webhooky v PHP: spoľahlivý handler, ktorému môžete dôverovať

V skratke

  • Za zdroj pravdivosti stavu platby považujte webhook, nie presmerovanie prehliadača.
  • Prečítajte si nespracované telo požiadavky a overte hlavičku Stripe-Signature predtým, ako sa dotknete užitočného obsahu.
  • Rýchlo vráťte odpoveď 2xx a urobte skutočnú prácu vo fronte alebo po uložení udalosti.
  • Uložte ID udalosti s jedinečným indexom: Stripe môže doručiť rovnakú udalosť viac ako raz a môže byť mimo poradia.
  • Pred spustením otestujte pomocou Stripe CLI (stripe listen a stripe trigger).

Ak prijímate platby pomocou Stripe, Stripe webhooky v PHP sú tou časťou, ktorá rozhoduje o tom, či sú objednávky označené ako platené, či sú aktivované predplatné a či sa refundácie prejavia vo vašej databáze. Stránka pokladne je len predné dvere: zákazníci zatvárajú karty, banky potvrdia neskoro a niektoré spôsoby platby sa vyrovnávajú o niekoľko minút alebo dní neskôr. Webhook je správa od Stripe, ktorá hovorí, čo sa skutočne stalo.

Vytvárame a udržiavame platobné integrácie pre klientov a pre naše vlastné produkty (napríklad Bill, ktorý prijíma platby Stripe a PayPal na verejnej platobnej stránke). Tento článok je kontrolný zoznam, ktorý dodržiavame pri písaní koncového bodu webhooku Stripe v PHP.

Ako fungujú webhooky Stripe za jednu minútu

Zaregistrujete si HTTPS URL v Stripe Dashboard (alebo cez API) a zvolíte, ktoré typy udalostí chcete prijímať. Keď sa niečo stane – dokončí sa relácia Checkout, zaplatí sa faktúra, vráti sa poplatok – Stripe odošle požiadavku POST s objektom udalosti JSON na túto adresu URL. Váš server musí odpovedať stavovým kódom 2xx. Ak sa tak nestane, Stripe to zopakuje s exponenciálnym stiahnutím až na tri dni v živom režime.

Celý dizajn formujú tri fakty:

  • Udalosti môžu prísť viackrát. Opakované pokusy a problémy so sieťou spôsobujú, že duplikáty sú normálne.
  • Udalosti môžu prísť mimo poradia. Môže sa vám zobraziť invoice.paid pred customer.subscription.created.
  • Na vašu adresu URL môže ZVEREJŇOVAŤ ktokoľvek. Bez overenia podpisu môže útočník označiť svoju vlastnú objednávku ako zaplatenú.

Krok 1. Nainštalujte SDK a vytvorte koncový bod

Použite oficiálnu knižnicu stripe/stripe-php a udržujte koncový bod v tajnosti (začína sa whsec_) v premennej prostredia, nikdy nie v úložisku.

composer require stripe/stripe-php

# .env
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...

Každý koncový bod webhooku v Dashboarde má svoje vlastné podpisové tajomstvo a tajomstvo z stripe listen vo vývoji je opäť iné. Veľmi častou chybou „overenie podpisu zlyhalo“ je jednoducho použitie nesprávneho.

Krok 2. Overte podpis na surovom tele

Stripe podpíše každú požiadavku. Hlavička Stripe-Signature obsahuje časovú pečiatku a jeden alebo viac podpisov vypočítaných ako HMAC-SHA256 timestamp.payload s vaším tajným kľúčom koncového bodu. Knižnica kontroluje podpis a štandardne odmieta udalosti staršie ako päť minút.

Dôležitý detail: overenie vyžaduje presné nespracované bajty odoslané Stripe. Ak framework analyzuje JSON a vy ho znova zakódujete, medzery sa zmenia a podpis sa už nezhoduje.

<?php
// public/webhooks/stripe.php (plain PHP)
require __DIR__ . '/../../vendor/autoload.php';

$payload   = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$secret    = getenv('STRIPE_WEBHOOK_SECRET');

try {
    $event = \Stripe\Webhook::constructEvent($payload, $sigHeader, $secret);
} catch (\UnexpectedValueException $e) {
    http_response_code(400);   // invalid JSON
    exit;
} catch (\Stripe\Exception\SignatureVerificationException $e) {
    http_response_code(400);   // bad or missing signature
    exit;
}

V Laravel si prečítajte $request->getContent() namiesto $request->all() a vylúčte trasu z ochrany CSRF (webhooky nenesú token CSRF, ochrana je podpis). Ak môžete, umiestnite trasu mimo skupiny middlewaru web relácie.

Krok 3. Urobte psovoda idempotentným

Idempotentný znamená: spracovanie tej istej udalosti dvakrát dáva rovnaký výsledok ako jej spracovanie raz. Najjednoduchším spoľahlivým prístupom je tabuľka spracovaných ID udalostí s jedinečným indexom. Najprv vložte; ak vloženie zlyhá, pretože ID existuje, udalosť ste už videli.

CREATE TABLE stripe_events (
  id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  event_id      VARCHAR(64) NOT NULL,
  type          VARCHAR(100) NOT NULL,
  payload       JSON NOT NULL,
  status        ENUM('received','processed','failed') NOT NULL DEFAULT 'received',
  received_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  processed_at  TIMESTAMP NULL,
  UNIQUE KEY uniq_event (event_id)
);
try {
    $pdo->prepare('INSERT INTO stripe_events (event_id, type, payload) VALUES (?, ?, ?)')
        ->execute([$event->id, $event->type, $payload]);
} catch (PDOException $e) {
    if ($e->getCode() === '23000') {   // duplicate key: already received
        http_response_code(200);
        exit;
    }
    throw $e;
}

Uloženie nespracovaného užitočného zaťaženia vám poskytne auditnú stopu: keď sa zákazník spýta „prečo mi bolo účtované dvakrát?“, môžete si prehrať presne to, čo vám povedal Stripe. Umožňuje tiež opätovné spracovanie neúspešných udalostí po oprave chyby.

Krok 4. Potvrďte rýchlo, spracujte samostatne

Stripe očakáva odpoveď v priebehu niekoľkých sekúnd. Ak váš obslužný program posiela e-maily, volá iné rozhrania API alebo generuje vložené súbory PDF, vyprší časový limit, Stripe to zopakuje a vy budete robiť rovnakú prácu znova. Urobte v požiadavke minimum – overte, uložte, odpovedzte 200 – a spustite skutočnú logiku v pracovníkovi frontu ( fronty Laravel, Symfony Messenger, status = 'received' alebo pracovník riadený supervízorom).

Ak sa udalosť nedá vôbec uložiť (databáza je vypnutá), vráťte stav 5xx. Toto je jediný prípad, keď chcete, aby Stripe zopakoval neskôr.

Krok 5. Zvládnite udalosti, na ktorých záleží

Nepotrebujete každý typ udalosti. Predplaťte si iba to, čo používa vaša obchodná logika; menej odberov znamená menej hluku a menšiu útočnú plochu. Typická sada:

  • checkout.session.completed – zákazník dokončil platbu. Vyhľadajte svoju objednávku pomocou client_reference_id alebo metadát, ktoré ste nastavili pri vytváraní relácie.
  • payment_intent.succeeded a payment_intent.payment_failed – pre vlastné platobné toky.
  • invoice.paid a invoice.payment_failed - fakturacia predplatneho.
  • customer.subscription.updated a customer.subscription.deleted - zmeny a zrušenia plánu.
  • charge.refunded a charge.dispute.created - peniaze idú späť.
switch ($event->type) {
    case 'checkout.session.completed':
        $session = $event->data->object;
        $orderId = $session->client_reference_id;
        if ($session->payment_status === 'paid') {
            markOrderPaid($orderId, $session->payment_intent);
        }
        break;

    case 'invoice.payment_failed':
        notifyBillingProblem($event->data->object->customer);
        break;

    case 'charge.refunded':
        markOrderRefunded($event->data->object->payment_intent);
        break;

    default:
        // unknown or unsubscribed type: acknowledge and ignore
        break;
}

Dva zvyky zabraňujú väčšine chýb. Po prvé, nikdy neverte sumám z prehliadača: porovnajte zaplatenú sumu a menu v prípade s objednávkou vo vašej databáze. Po druhé, pretože udalosti môžu byť mimo poradia, urobte zmeny stavu monotónne: nepresúvajte objednávku z „zaplatenej“ späť do „nevybavenej“, pretože staršia udalosť prišla neskoro. Ak máte pochybnosti, načítajte aktuálny objekt z API namiesto spoliehania sa na snímku udalosti.

Krok 6. Otestujte lokálne pomocou Stripe CLI

Stripe CLI preposiela skutočné udalosti testovacieho režimu do vášho počítača, takže nepotrebujete verejný tunel:

stripe login
stripe listen --forward-to http://localhost:8000/webhooks/stripe
# in another terminal:
stripe trigger checkout.session.completed
stripe trigger invoice.payment_failed

stripe listen vypíše dočasné tajomstvo whsec_. Vložte to do svojho lokálneho .env. Zámerne tiež otestujte nešťastné cesty: pošlite požiadavku s nesprávnym podpisom (očakávajte 400), pošlite tú istú udalosť dvakrát (očakávajte jeden databázový efekt) a zastavte na minútu svojho pracovníka vo fronte, aby ste videli, že udalosti sa namiesto zmiznutia hromadia.

Časté chyby, s ktorými sa stretávame pri auditoch

  • Použitie presmerovania ako dôkaz o platbe. Stránku success_url môže otvoriť ktokoľvek; kým webhook nepríde, zobrazovať „potvrdzovanie platby“.
  • Analýza JSON pred overením. Znovu zakódované telá porušujú podpis; Častým vinníkom sú analyzátory kostrového tela.
  • Jedno tajomstvo na skúšku a naživo. Testovací a živý režim majú samostatné klávesy a samostatné koncové body webhooku.
  • Žiadne protokolovanie. Bez uložených udalostí a viditeľného kanála zlyhania nájdu zákazníci chyby pri platbe.
  • Pomalí manipulanti. Čokoľvek v priebehu niekoľkých sekúnd spustí opakovanie a duplicitnú prácu.
  • Blocking Stripe na firewalle alebo WAF. Ak používate Cloudflare alebo iný WAF, povoľte cestu webhooku a skontrolujte, či pravidlá robotov nevracajú 403 na servery Stripe.

Monitorovanie: ako viete, že stále funguje

Webhook sa môže prerušiť mesiace po spustení: nasadená zmena premenuje stĺpec, tajomstvo sa otočí, platnosť certifikátu vyprší. Panel Stripe Dashboard zobrazuje pokusy o doručenie a zlyhania pre každý koncový bod a Stripe vám pošle e-mail, keď koncový bod stále zlyháva. Na svojej strane upozornite na tri veci: rastúci počet udalostí v received alebo failed, náhly pokles udalostí za hodinu a akúkoľvek frekvenciu 4xx alebo 5xx na trase webhooku. Malá denná úloha zosúlaďovania, ktorá porovnáva zaplatené objednávky vo vašej databáze so zoznamom úspešných platieb Stripe, zachytí zvyšok.

Ako môžeme pomôcť

Navrhujeme a vytvárame produkčné integrácie platieb a webhooku s kontrolami podpisov, idempotenciou, opakovanými pokusmi a protokolmi, ktoré môže váš tím čítať. Ak už máte Stripe tok a chcete druhý pár očí, preskúmanie architektúry alebo konzultácie sú najrýchlejším spôsobom, ako nájsť slabé miesta. Ak chcete získať širší obraz o presune kritických udalostí zo Zapier alebo Make, prečítajte si, keď sa neporuší žiadny kód a pre iného poskytovateľa si pozrite nášho sprievodcu integráciou WayForPay v PHP.

Časté otázky

Prečo overenie podpisu webhooku Stripe zlyhá?

Zvyčajnými príčinami je použitie nesprávneho tajného podpisu (test vs. živé, CLI verzus koncový bod Dashboard), overenie analyzovaného alebo znovu zakódovaného tela namiesto nespracovaného tela požiadavky alebo proxy, ktorý upravuje užitočné zaťaženie. Overte proti presným nespracovaným bajtom pomocou vlastného tajomstva whsec_ koncového bodu.

Ako zabránim spracovaniu udalosti Stripe dvakrát?

Uložte každé ID udalosti do tabuľky s jedinečným indexom a vložte ho pred vykonaním akejkoľvek práce. Ak vloženie zlyhá s duplicitným kľúčom, vráťte 200 a preskočte spracovanie. Zabezpečte tiež, aby sa samotná obchodná akcia mohla bezpečne opakovať.

Aký kód odpovede by mal vrátiť webhook Stripe ?

Po overení a uložení udalosti vráťte kód 2xx (200 alebo 204). Vráťte 400 za neplatný podpis a 5xx len vtedy, keď nemôžete uložiť udalosť a chcete, aby to Stripe zopakoval.

Ako dlho Stripe skúša neúspešné webhooky?

V živom režime Stripe opakuje pokusy s exponenciálnym stiahnutím až na tri dni. V testovacom režime je okno opakovania oveľa kratšie, takže sa pri posudzovaní produkčného správania nespoliehajte na opakované pokusy v testovacom režime.

Môžem testovať webhooky Stripe bez verejnej adresy URL?

áno. Spustite stripe listen --forward-to s Stripe CLI, aby ste preposlali testovacie udalosti na localhost, a použite stripe trigger na generovanie vzorových udalostí.

Bezplatný objavovací hovor

Prineste chaotický stack alebo zaseknutú integráciu - Integrono namapuje najmúdrejší prvý krok.

Dostanete seniorský pohľad na rozpočtové pasce, závislosti a na to, či kúpiť, zlepiť alebo postaviť - kým nezačne platená práca. CEO ostáva v okruhu.