thru

npm packages

Four packages under the @thru-payment scope, MIT licensed; @thru-payment/server is at 0.3.1 (the others are 0.2.0). They are ES modules: use import, not require.

PackageRuns onCovers
@thru-payment/serverYour server: Node 20+, Cloudflare Workers, Deno, BunCheckout sessions, signed-return verification, webhook verification
@thru-payment/checkout-coreThe browser, with or without ReactReading and polling a payment's public status
@thru-payment/pay-sdkThe browser, React 18+A ready-made payment screen and its parts
@thru-payment/x402Your server (Node)Agent payments over HTTP 402, which are not enabled on api.thru.la yet (facilitator-x402)

There is no SDK for other languages (Go, Python, PHP…), and @thru-payment/server covers checkout sessions and payments.retrieve (0.3.1+), not products, invoices, refunds, settlement or webhook endpoints. For those, call the REST API directly; it is plain JSON with one header. The webhooks topic has signature verification code for Node, Go and Python.

The older @thru/pay-sdk package is superseded by these; do not use it.

@thru-payment/server

ts
import { createThruServerClient } from '@thru-payment/server';

const thru = createThruServerClient({ apiKey: process.env.THRU_API_KEY! });

await thru.checkout.sessions.create({ productSlug, reference, successUrl });
// A custom-amount product needs the amount as well: { productSlug, amount: '37', reference, ... }.
// The field takes the same rules as POST /v1/checkout/sessions; use a release of the package that
// declares it (see the package changelog), or call the endpoint directly.
await thru.checkout.sessions.retrieve(sessionId);
await thru.checkout.sessions.list({ createdAfter, status: 'completed', limit: 100, cursor });
await thru.checkout.sessions.expire(sessionId);

Also exported:

  • verifyThruReturn(input, checkoutSecret, { toleranceSeconds?, now? }), async. input can be a URL, a URL string, URLSearchParams or a plain object. It resolves to { verified: true, sessionId, status } or { verified: false, sessionId, status: null, reason }.
  • constructThruEvent(rawBody, headers, webhookSecret), async. It returns the parsed event or throws ThruSignatureError (with reason: missing_signature, bad_format, bad_signature or bad_payload).
  • isCheckoutSessionEvent(event), a type guard for the checkout.session.* events.
  • ThruApiError (with status and payload), thrown by the client on a non-2xx answer.
  • hmacHex, safeEqualHex, and the constants SIGNATURE_HEADER, EVENT_TYPE_HEADER and RETURN_PARAM_SESSION/STATUS/TIMESTAMP/SIGNATURE.
  • The subpaths @thru-payment/server/checkout and @thru-payment/server/webhooks, if you want to import less.

The full flow is in checkout-sessions.

@thru-payment/checkout-core

A read-only client for GET /v1/public/payments/:id, with polling that stops once the payment reaches a final status. It never sends a key.

React:

tsx
import { ThruProvider, usePayment } from '@thru-payment/checkout-core';

function PaymentStatus({ paymentId }: { paymentId: string }) {
  const { data: payment, loading, error } = usePayment(paymentId);
  if (loading) return <Spinner />;
  if (error || !payment) return <ErrorMessage />;
  return <MyPaymentScreen payment={payment} />;
}

<ThruProvider apiBaseUrl="https://api.thru.la/v1">
  <PaymentStatus paymentId={paymentId} />
</ThruProvider>

Without React (Vue, Svelte, plain JavaScript), import from the /core subpath:

ts
import { createThruClient, createPaymentStore } from '@thru-payment/checkout-core/core';

const store = createPaymentStore(createThruClient(), paymentId);
const unsubscribe = store.subscribe(({ data, error, loading }) => render(data));

@thru-payment/pay-sdk

React components for the payment screen of a direct payment. It includes the React API of checkout-core, so install only this one.

tsx
import { ThruProvider, ThruCheckout } from '@thru-payment/pay-sdk';
import '@thru-payment/pay-sdk/styles.css';

<ThruProvider apiBaseUrl="https://api.thru.la/v1">
  <ThruCheckout paymentId={paymentId} onStatusChange={(payment) => console.log(payment.status)} />
</ThruProvider>

<ThruCheckout> shows the amount, the address, a QR code and the live status. To build your own layout, use the parts: ThruRoot, PaymentAmount, PaymentAddress, PaymentQRCode and PaymentStatusBadge. The QR code encodes only the address, so always show the amount as text next to it.

Your server creates the payment with your key and gives the browser only the payment id.

Base URLs differ between package families

@thru-payment/server and @thru-payment/checkout-core take a base URL that includes /v1 (default https://api.thru.la/v1). The @thru-payment/x402 client takes one without it (default https://api.thru.la) and adds /v1 itself. Do not pass one shared variable to both.