npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tradom/payment

v0.2.1

Published

Official TypeScript / Node.js client for the TRADOM Payment (T-Pay) API.

Readme

@tradom/payment

Official TypeScript / Node.js client for the TRADOM Payment (T-Pay) API.

Covers the full merchant surface of the platform:

| Resource | API | Key | | --- | --- | --- | | client.gateway | EC Gateway — redirect-based hosted checkout (POST /api/gateway/orders, status reconciliation) | gw_live_… | | client.paymentIntents | Stripe-style PaymentIntent API (/api/v1/payment_intents…) — create / quote / confirm / cancel / refund / sandbox deposit | sk_test_… / sk_live_… | | client.webhookEndpoints | Webhook receiver registration (/api/v1/webhook_endpoints) | sk_… | | client.events | Poll-based event access (/api/v1/events) | sk_… | | verifyWebhookSignature / constructEvent | Verify signed webhook deliveries (Tradom-Signature) | whsec_… |

⚠️ Server-side only. Every key this SDK takes is a secret. Never ship it to the browser — call the SDK from your backend / BFF.

The two key families are separate credentials: the EC Gateway uses your merchant gateway key (gw_live_…); the PaymentIntent API uses a secret key (sk_test_… / sk_live_…). Construct one PaymentClient per key.

Install

npm install @tradom/payment

Works with any package manager (pnpm add / yarn add / bun add @tradom/payment).

Quick start — EC Gateway (redirect flow)

Your EC backend sends the order, TRADOM runs KYC and hosts the entire payment UI; you redirect the buyer and reconcile server-side.

import { PaymentClient } from "@tradom/payment";

const tpay = new PaymentClient({
  apiKey: process.env.TPAY_GATEWAY_KEY!, // gw_live_...
  // baseUrl defaults to https://payment.tradom.jp
});

const order = await tpay.gateway.createOrder({
  orderId: "order_12345", // your id — idempotent per merchant
  amount: 50000, // JPY, integer > 0
  buyerName: "Taro Yamada",
  buyerEmail: "[email protected]",
  country: "JP",
  items: [{ name: "Coffee beans", category: "food" }],
  returnUrl: "https://shop.example/checkout/complete",
  cancelUrl: "https://shop.example/checkout/cancel",
});

// Discriminated union — a declined KYC is a normal result, not an error.
redirect(order.checkoutUrl); // "ok" → hosted checkout / "kyc_declined" → decline page

Reconcile before fulfilment — never trust the browser redirect alone:

// token = the path segment of order.checkoutUrl (…/gateway/checkout/<token>/)
const status = await tpay.gateway.checkoutStatus(token);
if (status.confirmed) fulfil(); // or gate on status.state.state >= "deposit_detected"
console.log(status.state.state, status.message?.titleJa);

Quick start — PaymentIntent API (server-driven flow)

const tpay = new PaymentClient({ apiKey: process.env.TPAY_SECRET_KEY! }); // sk_test_...

// 1) Create from a payment link (idempotent via Idempotency-Key)
const pi = await tpay.paymentIntents.create({
  paymentLinkToken: "<share_token>",
  metadata: { order: "42" },
  idempotencyKey: "order-42",
});

// 2) Optional preview
const quote = await tpay.paymentIntents.cryptoQuote(pi.id, {
  stablecoin: "usdc",
  network: "ethereum",
});

// 3) Lock the rail → deposit instructions (15-minute window)
const confirmed = await tpay.paymentIntents.confirm(pi.id, {
  stablecoin: "usdc",
  network: "ethereum",
});
const deposit = confirmed.nextAction!.cryptoDeposit;
// deposit.depositAddress / deposit.amount / deposit.eip681Uri / deposit.expiresAt

// 4) [sandbox] simulate the on-chain deposit → "succeeded"
await tpay.paymentIntents.simulateDeposit(pi.id);

// 5) Refund (fully, or partially with { amount })
const { refund } = await tpay.paymentIntents.refund(pi.id, { amount: 50 });

Amounts are major fiat units (USD dollars / JPY yen) — not cents. Don't multiply by 100.

Webhooks

Register a receiver once, store the one-time secret, then verify every delivery:

const ep = await tpay.webhookEndpoints.create({ url: "https://shop.example/tpay-webhook" });
save(ep.secret); // whsec_… — shown only at creation
import { constructEvent, PaymentError } from "@tradom/payment";

// Next.js route handler — use the RAW body, not a re-serialised object.
export async function POST(request: Request) {
  let event;
  try {
    event = constructEvent({
      payload: await request.text(),
      signature: request.headers.get("Tradom-Signature") ?? "",
      secret: process.env.TPAY_WEBHOOK_SECRET!,
    });
  } catch (e) {
    if (e instanceof PaymentError) return new Response("bad signature", { status: 400 });
    throw e;
  }

  // At-least-once delivery — dedupe on event.id.
  if (event.type === "payment_intent.succeeded") {
    fulfil(event.data.object.metadata.order);
  }
  return new Response("ok");
}

Emitted event types: payment_intent.succeeded · payment_intent.canceled · payment_intent.refunded (there is no payment_intent.created). Deliveries retry every 10 minutes for up to 72h / 5 attempts; use client.events.list() as a catch-up mechanism.

API surface

new PaymentClient(options)

| Option | Type | Default | Notes | | ------------ | ------------------------- | --------------------------- | ------------------------------------------------------------ | | apiKey | string | — | Required. gw_live_… (gateway) or sk_… (PaymentIntent). | | baseUrl | string | https://payment.tradom.jp | Override to target a non-production environment. | | authScheme | "x-api-key" \| "bearer" | "x-api-key" | Gateway only — /api/v1 always uses Authorization: Bearer. | | timeoutMs | number | 15000 | Per-request timeout. | | fetch | FetchLike | globalThis.fetch | Inject for testing / custom agents. |

client.gateway

  • createOrder(params): Promise<OrderApproved | OrderDeclined> — see the field mapping below. Idempotent per (merchant, orderId).
  • checkoutStatus(token): Promise<CheckoutStatus> — public token-scoped reconciliation: confirmed, tosAccepted, canonical state (12 states, state.isTerminal, state.matchStatus), bilingual buyer message (M-code). Throws PaymentError("not_found", 404) for unknown tokens.

| createOrder field | Required | Maps to | | ------------------- | -------- | ------------- | | orderId | ✅ | order_id | | amount | ✅ | amount (JPY)| | buyerName | | buyer_name | | buyerEmail | | buyer_email | | country | | country | | items | | items | | returnUrl | | return_url | | cancelUrl | | cancel_url |

client.paymentIntents

  • create({ paymentLinkToken, mode?, metadata?, idempotencyKey? })
  • createCheckoutSession({ paymentLinkToken, mode?, metadata? }) — keyless hosted-checkout entry (returns clientSecret)
  • retrieve(id) / cryptoQuote(id, { stablecoin, network })
  • confirm(id, { stablecoin, network }) — testnet mode supports USDC only
  • cancel(id) / refund(id, { amount?, reason? }) / simulateDeposit(id, { amount? })

All calls resolve to a fully camelCased PaymentIntent (statuses: requires_payment_method → requires_action → succeeded, plus canceled).

Webhook helpers (standalone exports)

  • verifyWebhookSignature({ payload, signature, secret, toleranceSeconds?, now? }): boolean
  • constructEvent(sameParams): WebhookEvent — verify + parse, throws on mismatch

Signature scheme: Tradom-Signature: t=<unix>,v1=<hex hmac-sha256(secret, "<t>.<payload>")>, default freshness tolerance 300s, constant-time comparison. Requires Node ≥ 18 / Bun (node:crypto).

Errors

Transport problems, local validation failures, and API error envelopes all throw a PaymentError with code, httpStatus (0 for local/network) and, for PaymentIntent API errors, param:

| Source | Envelope | Example codes | | --- | --- | --- | | EC Gateway | {"status":"error","error":"…"} | missing_api_key (401) · invalid_api_key (403) · invalid_json · service messages like "amount must be greater than zero" · session_expired (409) | | PaymentIntent API | {"error":{"code","message","param"?}} | authentication_required (401) · parameter_missing / parameter_invalid (400) · resource_missing (404) · payment_link_inactive (409) · crypto_asset_unsupported (400) · payment_intent_unexpected_state (409) · url_invalid (400) | | Client-side | — | invalid_request · network_error · timeout · not_found · bad_response · signature_verification_failed |

Note: PaymentIntent API message strings are mixed Japanese/English — always branch on code, never on message.

Development

Tests run on Bun (bun:test):

bun install
bun test          # mocked fetch + webhook signature vectors
bun run typecheck
bun run build     # dist/ (ESM + d.ts)