payment-provider-router — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited payment-provider-router (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
What it is: The typed dispatch layer that sends verified payment events to the correct business handler. Mental model: Verification proves the event is authentic; the router decides what business operation the event represents. Why it exists: Payment events are high-impact and retried by providers, so ambiguous routing can duplicate work or miss fulfillment. What it is NOT: It is not webhook signature verification, subscription database writes, or provider SDK setup. Adjacent concepts: Event type maps, handler isolation, provider abstraction, idempotency. One-line analogy: It is the switchboard that sends a verified payment event to the right desk. Common misconception: Unknown events should return an HTTP error; for Stripe, acknowledging and logging unknown-but-valid events prevents retry storms.
Stripe.Event["type"] to handler functions, with a structured "unknown event" fallback that returns 200 (to prevent Stripe retry storms) and logs the unhandled typeStripe.CheckoutSessionCompletedEvent), not the generic Stripe.Event, to avoid casts inside handlersPaymentEvent canonical type so a future provider can be added without changing handler codeA payment event router has the same discipline requirement as a content source router: prefer an explicit handler over an implicit fallback, surface unhandled events loudly (in logs, not in HTTP status codes — a 400 triggers a retry, a 200 with a log entry does not), and never let one handler own two semantically distinct events. The event type is the authoritative signal for which business operation to perform; ambiguity at this layer produces double-charges, missed provisioning, and unfired dunning emails.
// lib/payments/router.ts
import Stripe from "stripe";
import { handleCheckoutComplete } from "./handlers/checkout-complete";
import { handlePaymentFailed } from "./handlers/payment-failed";
import { handleSubscriptionDeleted } from "./handlers/subscription-deleted";
type HandlerResult = { ok: boolean; message?: string };
const EVENT_HANDLERS: Partial<
Record<Stripe.Event["type"], (event: Stripe.Event) => Promise<HandlerResult>>
> = {
"checkout.session.completed": (e) =>
handleCheckoutComplete(e as Stripe.CheckoutSessionCompletedEvent),
"invoice.payment_failed": (e) =>
handlePaymentFailed(e as Stripe.InvoicePaymentFailedEvent),
"customer.subscription.deleted": (e) =>
handleSubscriptionDeleted(e as Stripe.CustomerSubscriptionDeletedEvent),
// Acknowledged non-actionable events — log and return OK
"invoice.paid": async () => ({ ok: true, message: "acknowledged" }),
};
export async function routePaymentEvent(event: Stripe.Event): Promise<HandlerResult> {
const handler = EVENT_HANDLERS[event.type];
if (!handler) {
console.warn("[payment-router] unhandled event type", { type: event.type, id: event.id });
// Return 200 — a 4xx or 5xx would trigger Stripe retry with backoff
return { ok: true, message: "unhandled_event_type" };
}
return handler(event);
}| Event type | Handler | Rationale |
|---|---|---|
checkout.session.completed | handleCheckoutComplete | Provision subscription, create org record |
invoice.payment_failed | handlePaymentFailed | Trigger dunning, update subscription status |
customer.subscription.deleted | handleSubscriptionDeleted | Revoke access, archive subscription |
invoice.paid | acknowledged | No action — success is implicit from checkout.session.completed |
| anything else | log + 200 | Unknown event — log for triage, do not retry |
EVENT_HANDLERS with a typed cast.lib/payments/handlers/<name>.ts — it receives the specific subtype.EVENT_HANDLERS with an explicit handler or acknowledgementStripe.Event{ ok: false } — they do not propagate to the routerroutePaymentEvent is only called after signature verification (grep for routePaymentEvent — every call site should be downstream of constructEvent)| Use instead | When |
|---|---|
stripe-webhook-signature-verification | The task is verifying the event's authenticity before routing |
postgres-rls-pattern | The task is writing the database statements inside a specific handler |
| (a generic event bus skill) | The application uses an event bus that is not payment-provider-specific |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.