Files
skills/docs/money/payment/payment-providers/src/http/http.md
2026-05-29 08:20:56 +01:00

4.2 KiB

httpRequest helper

(no submodules)

Purpose

Universal HTTP helper used by every provider to issue typed, cancellable JSON requests. The helper is a thin wrapper over an injectable FetchLike (defaulting to globalThis.fetch) that JSON-serialises non-string bodies, applies a per-call timeout via AbortSignal, composes a caller-supplied signal with the internal timeout signal, and converts every failure path into a typed PaymentProviderError so callers can branch on error.code instead of parsing strings.

Features

  • Injectable FetchLike (fetch?: FetchLike) — defaults to globalThis.fetch
  • JSON serialisation of object bodies (string bodies pass through unchanged, enabling application/x-www-form-urlencoded flows)
  • Per-call timeoutMs (default 30_000) via internal AbortController
  • External signal?: AbortSignal composed with the internal timeout
  • Typed errors: HTTP_ERROR, TIMEOUT, PARSE_ERROR
  • Empty-body handling: 204 / empty responses resolve to data: {}
  • provider?: string discriminant surfaced on every raised error
  • composeAbortSignal(internal, external) public helper — reusable by sibling provider packages that issue out-of-band HTTP calls (OAuth token fetches, webhook signature fetches) and want the same caller-signal composition without re-implementing the wiring

Usage: Basic

import { httpRequest } from "@suaveplan/payment-providers";

const { status, data } = await httpRequest<{ id: string }>({
    method: "POST",
    url: "https://api.example.com/charges",
    headers: { Authorization: "Bearer secret" },
    body: { amount: 1000 },
    timeoutMs: 10_000,
    provider: "example",
});

Usage: Advanced

import { type FetchLike, httpRequest } from "@suaveplan/payment-providers";

// Inject a recording FetchLike for tests — no globalThis monkey-patching.
const calls: Array<{ url: string; init?: RequestInit }> = [];
const fetch: FetchLike = async (url, init) => {
    calls.push({ url, init });
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
};

const external = new AbortController();
const promise = httpRequest({
    method: "GET",
    url: "https://api.example.com/intent/1",
    fetch,
    signal: external.signal,
    timeoutMs: 5_000,
});
// Cancel externally — either signal firing aborts the request.
external.abort();
await promise.catch((err) => console.log(err.code));

API Reference

Symbol Kind Description
FetchLike type (input: string, init?: RequestInit) => Promise<Response>
httpRequest<T>(options: HttpOptions): Promise<ProviderResponse<T>> function issues the HTTP call, returns parsed JSON
composeAbortSignal(internal, external?): AbortSignal function returns internal when external is undefined; otherwise returns a fresh signal that aborts when either input aborts, with already-aborted external propagating synchronously

HttpOptions

Field Type Default Description
method "GET" | "POST" | "DELETE" | "PATCH" — HTTP verb
url string — request URL
headers Record<string, string> {} extra headers (override the JSON defaults if set)
body unknown — JSON-serialised unless already a string
timeoutMs number 30_000 per-call timeout
fetch FetchLike globalThis.fetch injectable HTTP client
signal AbortSignal — external cancellation
provider string "unknown" discriminant placed on raised errors

Implementation Notes

The internal AbortController always wins on timeout — its abort() is called from a setTimeout whose handle is cleared in finally, so no listener leaks past the call. The composed AbortSignal is created inline via a third AbortController; both source signals (timeoutController.signal and the caller's signal) propagate to it via addEventListener("abort", …, { once: true }), plus an upfront check for the already-aborted case. Non-Error throws from the injected FetchLike (e.g. raw strings) become HTTP_ERROR with a stringified body excerpt — the helper never lets an untyped failure escape.