Прескокни до содржината

SDK webhook пораки

Објавено издание: SDK 0.1.3 е јавно достапен на npm. Инсталацијата не активира серверски функции.

Дали ми треба сопствен Stripe webhook за да знам дека купувачот платил?

Дел „Дали ми треба сопствен Stripe webhook за да знам дека купувачот платил?“

Не — за стандардната Kordiva хостирана наплата не треба. Kordiva ги прима и проверува потпишаните Stripe известувања на своите сервери и го запишува авторитативниот платежен/нарачков статус. На вашата веб-страница не треба да поставувате Stripe webhook secret или посебен Stripe webhook endpoint.

SDK 0.1.3 додава Business Merchant настани со одделен HMAC проверувач. Постојниот тек за еднократна потврда подолу останува поддржан. За сопствена Node.js, React или Next.js продавница, вашиот автентициран сервер ја проверува постојната LIVE Kordiva платежна потврда со @kordiva/sdk. Прелистувачот го прашува вашиот backend, никогаш Kordiva директно со таен клуч. Враќањето на success URL или успешно создавање checkout не значи платена нарачка.

  1. На сервер повикајте client.checkout.createHosted(). Зачувајте ги добиениот sessionId, овластениот купувач/кошничка и стабилниот idempotencyKey во трајна серверска база, пред на прелистувачот да му го вратите само checkoutUrl. Малиот /api/checkout пример враќа само URL; дополнете го со ова зачувување пред продукциска употреба.
  2. По враќање од наплата, frontend го повикува заштитениот GET /api/payment-status?attempt=... на вашата веб-страница. Автентицирајте го купувачот и најдете негова сопствена checkout сесија; не верувајте на sessionId од URL параметри.
  3. Вашиот сервер повикува client.payments.confirm({ sessionId, idempotencyKey }) со зачуваните референци. Покажете paid само ако status === 'paid', isPaid === true, идентитетот се совпаѓа и постои поврзана платена Kordiva нарачка. Инаку прикажете pending, expired или привремена недостапност.
  4. Прелистувачот може ограничено да го прашува вашиот backend на околу 5 секунди, најмногу една минута, потоа да прикаже „Плаќањето се обработува — проверете подоцна“. За затворени страници или доцнење користете автентицирана серверска задача за усогласување. Без бесконечно проверување и без нова наплата по timeout.
  5. Потврда, пристап или дигитална испорака испраќајте еднаш, од вашиот сервер, по авторитативен платежен успех и дополнителните Individual проверки за право на испорака, каде што се применливи. Самото paid не дава право на Individual download.

Платежни исходи што треба да ги обработи backend

Дел „Платежни исходи што треба да ги обработи backend“

API за потврда намерно има мал број состојби:

| Потврда | Што треба да направи backend | | --- | --- | | paid + isPaid === true + orderId | Проверете дека зачуваниот обид му припаѓа на автентицираниот купувач, па исполнете еднаш. | | pending | Не исполнувајте и не отворајте нова наплата. Повторно проверете го истиот зачуван обид со ограничено polling или серверско усогласување. | | expired | Сметајте го тој checkout обид за затворен. Нова наплата бара нов овластен обид. | | Недостапно барање/API | Прикажете привремен статус и усогласете подоцна. Не претворајте мрежна/API грешка во paid или во втора наплата. |

SDK 0.1.3 нема општ failed статус во payment confirmation. Одбиена или неуспешна картичка останува во хостираниот платежен тек; merchant confirmation враќа само paid, pending или expired. Никогаш не заклучувајте успех од browser return path.

Следниот тестиран Node пример го користи истиот actor:attempt идентитет како примерот за наплата. Вие обезбедувате вистинска купувачка автентикација и трајна findOwnedAttempt база; кодот намерно не измислува login или складиште. За точно истекување зачувајте го sessionId од createHosted. Само со idempotencyKey API понекогаш не може да го одреди истекувањето на сесијата.

payment-status.ts
import type { Kordiva } from "@kordiva/sdk";
/** Example only: implement both callbacks using a durable cart-attempt store
* and your existing customer authentication. Never send a Store API key to a browser.
*/
export interface PaymentStatusRouteOptions {
client: Kordiva;
authorize: (request: Request) => Promise<string | null>;
findOwnedAttempt: (
actor: string, attempt: string,
) => Promise<{ sessionId?: string | null } | null>;
}
const ATTEMPT = /^[A-Za-z0-9_-]{8,64}$/;
const ACTOR = /^[A-Za-z0-9_-]{1,48}$/;
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Place behind GET /api/payment-status on a protected Node backend.
* The browser polls YOUR authenticated endpoint; Kordiva processes Stripe events.
* This endpoint is not a merchant Stripe webhook or an outgoing push notification.
*/
export function createPaymentStatusRoute({
client, authorize, findOwnedAttempt,
}: PaymentStatusRouteOptions) {
return async function GET(request: Request): Promise<Response> {
const headers = { "cache-control": "no-store" };
if (request.method !== "GET")
return Response.json({ error: "Method not allowed" }, { status: 405, headers });
if (client.environment !== "LIVE")
return Response.json({ error: "LIVE only" }, { status: 403, headers });
const attempt = new URL(request.url).searchParams.get("attempt");
if (!attempt || !ATTEMPT.test(attempt))
return Response.json({ error: "Invalid attempt" }, { status: 400, headers });
try {
const actor = await authorize(request);
if (!actor || !ACTOR.test(actor))
return Response.json({ error: "Unauthorized" }, { status: 401, headers });
const owned = await findOwnedAttempt(actor, attempt);
if (!owned)
return Response.json({ error: "Not found" }, { status: 404, headers });
const sessionId = owned.sessionId ?? null;
if (sessionId !== null && !UUID.test(sessionId))
throw Error("Invalid stored session identity");
// Matches the actor:attempt identity used by the published checkout example.
// Save createHosted().sessionId for accurate expiration results when possible.
const idempotencyKey = `${actor}:${attempt}`;
const payment = await client.payments.confirm({
...(sessionId ? { sessionId } : {}),
idempotencyKey,
});
if (
payment.idempotencyKey !== idempotencyKey ||
(sessionId && payment.sessionId !== sessionId)
) throw Error("Payment identity mismatch");
if (payment.status === "paid" && payment.isPaid && payment.orderId)
return Response.json({ status: "paid", isPaid: true }, { headers });
if (payment.status === "expired" && !payment.isPaid)
return Response.json({ status: "expired", isPaid: false }, { headers });
return Response.json({ status: "pending", isPaid: false }, { headers });
} catch {
// Fail closed: return neither an invented success nor leaked payment data.
return Response.json(
{ status: "unavailable", isPaid: false }, { status: 503, headers },
);
}
};
}

TEST е одделен: checkout.createTest() создава изолиран TEST линк; payments.confirm() работи само во LIVE и ги одбива TEST клучевите. Завршените TEST нарачки проверете ги во Kordiva TEST табелите и со Stripe TEST webhook доказ. TEST плаќање не создава LIVE нарачка или исплата.

Kordiva ги обработува Stripe настаните за своите плаќања. Не копирајте Kordiva Stripe тајни во трговска интеграција и не ги заменувајте постојните обработувачи. Потврдувајте плаќања преку поддржаната серверска адреса. URL за враќање во прелистувач не е доверлив настан.

Business endpoint се поставува во CMS → Settings → API keys → Developers / Webhooks. SDK проверува настани, без методи за регистрација или управување клучеви; видете поставување и испорака. По потреба изградете ограничена серверска задача за усогласување; не проверувајте од недоверлив прелистувач со таен клуч.

Постоен, одделно одобрен agentic канал за нарачки користи P-256 потпишани HTTP пораки. Ако Kordiva изречно ја вклучила интеграцијата и доставила доверлив јавен клуч/ID, серверскиот @kordiva/sdk/webhooks дава verifyOrderWebhook:

  1. Прифаќајте само POST и читајте непроменети bytes со серверско ограничување на големина.
  2. Доставете body, headers, точен потпишан приемен url, доверливи publicKeyPem и keyId. Зад proxy користете конфигуриран канонски URL; не верувајте слепо на forwarded host.
  3. Проверете пред дејство. Се проверуваат digest, опфат на потпис, клуч, патека/query и најмногу петминутен рок. Ова не е проверка на Stripe потпис.
  4. Трајно евидентирајте eventRef и атомски применете orderRevision пред ефекти. Потписот не обезбедува трајна заштита од повторување или редослед. Потврдете веќе обработени настани без дуплирање.
  5. Ротирајте доверливи јавни клучеви преку проверен оперативен канал; не преземајте клуч од URL доставен во настан.

Помошната функција не дава agent идентитет, вклучување трговец, пристап до нарачки или платежна власт. Компатибилноста е тестирана со вистинскиот Kordiva потпишувач.

Поврзани водичи

Дел „Поврзани водичи“

SDK · Checkout · Безбедност · API

Recurring checkout and subscription reads → Signed Business events → Raw-body verification → Business refunds → API reference. Individual MoR follows its separate refund process.