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

@apogeopay/client

v0.2.0

Published

Official TypeScript client for ApogeoPay — payment orchestration over MercadoPago, PayPal, and Stripe.

Readme

@apogeopay/client

Official TypeScript client for ApogeoPay — a payment orchestration platform that fronts MercadoPago, PayPal, and Stripe behind a unified REST API.

Status: 0.2.0-rc.1 — full API surface for subscriptions, plans, payments, and tenants, plus webhook verification. Honors Retry-After. CJS + ESM dual build. Types are hand-maintained for now; auto-generation from openapi.json lands in a follow-up release.

License: MIT.


Install

npm install @apogeopay/client

Requires Node.js 20+ (uses native fetch).

Quickstart

import { ApogeopayClient, verifyWebhookSignature } from '@apogeopay/client';

const client = new ApogeopayClient({
  apiKey: process.env.APOGEOPAY_API_KEY!,
  baseUrl: process.env.APOGEOPAY_URL ?? 'https://api.apogeopay.com',
});

// Create a subscription. The client auto-generates an Idempotency-Key
// unless you pass one.
const sub = await client.subscriptions.create({
  planId: 'plan_pro_monthly',
  user: { id: 'usr_123', email: '[email protected]', country: 'AR' },
  metadata: { sale_intent: 'si_abc' },
}, { idempotencyKey: 'si_abc' });

console.log(sub.id, sub.status); // 'sub_xxx', 'PENDING'
if (sub.checkoutUrl) {
  // Redirect the user to authorize the subscription with the provider.
  console.log('Redirect →', sub.checkoutUrl);
}

Webhook verification

ApogeoPay signs every outbound webhook with HMAC-SHA256 over the raw body using your tenant's webhookSecret. The hex digest travels in X-ApogeoPay-Signature.

import express from 'express';
import { verifyWebhookSignature } from '@apogeopay/client';

const app = express();

// IMPORTANT: register the raw-body parser BEFORE express.json() for the
// webhook route — the HMAC is computed over the exact bytes received.
app.post(
  '/webhooks/apogeopay',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.header('x-apogeopay-signature');
    if (!sig) return res.status(401).end();

    const valid = verifyWebhookSignature(
      req.body as Buffer,
      sig,
      process.env.APOGEOPAY_WEBHOOK_SECRET!,
    );
    if (!valid) return res.status(401).end();

    const event = JSON.parse((req.body as Buffer).toString('utf8'));
    // ... process event idempotently by `event.delivery_id` ...

    res.status(200).end();
  },
);

The verifier uses crypto.timingSafeEqual for the comparison — safe against timing-side-channel attacks.

Reference

ApogeopayClient

new ApogeopayClient({
  apiKey: string,                 // required — `apgpay_*`
  baseUrl: string,                // required — `https://api.apogeopay.com`
  timeoutMs?: number,             // default 10_000
  maxRetries?: number,            // default 3 (retries 5xx, 429, network errors)
  retryDelayMs?: number,          // default 250 (exponential backoff with jitter)
  fetchImpl?: typeof fetch,       // inject a custom fetch (tests, polyfills)
  userAgent?: string,
})

client.subscriptions

| Method | Endpoint | |--------|----------| | create(input, opts?) | POST /v1/subscriptions | | getById(id) | GET /v1/subscriptions/:id | | getCurrent({ externalUserId }) | GET /v1/subscriptions/current?externalUserId=… | | cancel(id, input?, opts?) | DELETE /v1/subscriptions/:id | | pause(id, input?, opts?) | POST /v1/subscriptions/:id/pause | | resume(id, opts?) | POST /v1/subscriptions/:id/resume | | changePlan(id, input, opts?) | POST /v1/subscriptions/:id/change-plan | | revertScheduledChange(id, opts?) | DELETE /v1/subscriptions/:id/scheduled-change | | cancelAtPeriodEnd(id, opts?) | POST /v1/subscriptions/:id/cancel-at-period-end |

client.plans

| Method | Endpoint | |--------|----------| | create(input, opts?) | POST /v1/plans | | list() | GET /v1/plans | | getById(id) | GET /v1/plans/:id | | delete(id, opts?) | DELETE /v1/plans/:id |

client.payments

| Method | Endpoint | |--------|----------| | create(input, opts?) | POST /v1/payments | | list(input?) | GET /v1/payments (query params: externalUserId, status, limit, cursor) | | getById(id) | GET /v1/payments/:id | | refund(id, input?, opts?) | POST /v1/payments/:id/refund (omit amountCents for full refund) |

client.tenants

| Method | Endpoint | |--------|----------| | me() | GET /v1/tenants/me (returns the tenant resolved by the API key) |

All mutating methods accept { idempotencyKey?: string } as the last arg; when omitted, the client generates a UUID.

Retry-After

If the server returns a Retry-After header on 429 or 503, the client honors it (overrides the local exponential backoff). Both forms are supported: integer seconds (Retry-After: 5) and HTTP-date (Retry-After: Tue, 01 Jan 2030 12:00:00 GMT). Unparseable values fall back to the default backoff.

Errors

The client only throws three error types — every HTTP failure surfaces as an ApogeopayError:

import { isApogeopayError, isRetryableError } from '@apogeopay/client';

try {
  await client.subscriptions.create(/* ... */);
} catch (err) {
  if (isApogeopayError(err)) {
    console.error(err.code, err.httpStatus, err.details, err.requestId);
    if (isRetryableError(err)) {
      // The client already retried up to `maxRetries`. Give up or alert.
    }
  } else {
    throw err; // not from the SDK
  }
}

| Subclass | When | httpStatus | Retryable? | |----------|------|--------------|-----------| | ApogeopayError | Server returned 4xx/5xx with a JSON error.code body | the server's status | depends — see isRetryableError | | NetworkError | DNS, connect refused, socket hangup | 0 | yes | | TimeoutError | timeoutMs exceeded — aborted via AbortController | 0 | yes |

The retry helper considers 5xx, 429, network failures, and timeouts as transient. 4xx errors (except 429) propagate immediately — fix the input.

Roadmap

  • Auto-generated types from openapi.json (drift-free).
  • npm publish to the @apogeopay scope (currently private: true).
  • Real publish CI pipeline (currently --dry-run only).
  • signal: AbortSignal opt-in for caller-driven cancellation.
  • onRequest / onResponse hooks for consumer logging.

See tasks/task-016b-client-sdk-hardening.md for the current scope and DoD.