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

@coinfra/payments

v0.1.0

Published

The money wheel: provider-neutral payment orders, subscriptions, webhook reliability, wallets, ledger holds, metering, and USDT escrow adapters.

Downloads

85

Readme

@coinfra/payments

The money wheel. Provider-neutral payment orders, subscriptions, reliable webhooks, wallets, an append-only ledger, metering holds, payouts, and USDT escrow — embedded in your application.

Coinfra does not replace Stripe, PayPal, Alipay, WeChat Pay, PostgreSQL, or a blockchain node. It keeps their native SDKs and data, while providing one lifecycle and one set of financial invariants around them.

Status

0.1.0-alpha.5 is used by two real consumers:

  • a SaaS product using Stripe subscription Checkout, Customer Portal, webhooks, trials and generation entitlements;
  • an AI marketplace using one-time wallet top-ups, Payment Orders, ledger entries, usage holds, authoritative usage settlement, debt, seller settlement and TRC20/ERC20 USDT deposits.

Alpha means the API may still change. It does not mean mock money: the state machines, integer arithmetic, webhook signature handling and PostgreSQL consumer tests are real.

Install

pnpm add @coinfra/payments

Install only the native provider SDKs you use:

pnpm add stripe                         # Stripe
pnpm add @paypal/paypal-server-sdk      # PayPal
pnpm add alipay-sdk                     # Alipay

Modules

| Import | Purpose | |---|---| | @coinfra/payments | Core money, orders, webhook orchestration, entitlements, ledger contracts, payouts | | @coinfra/payments/stripe | Stripe Checkout, subscriptions, Portal, refunds, signed webhooks and reconciliation | | @coinfra/payments/paypal | PayPal Orders/Subscriptions and official REST webhook verification | | @coinfra/payments/alipay | Alipay page pay, signed notifications, query and refunds | | @coinfra/payments/wechat-pay | WeChat Pay v3 client contract and normalized Native/JSAPI/H5/App lifecycle | | @coinfra/payments/escrow/usdt | TRC20/ERC20 USDT deposit and withdrawal state machine | | @coinfra/payments/testing | Fake provider and executable in-memory reference repositories |

Money

No floating point is used for stored or calculated money.

import { money, formatMajorUnits, parseMajorUnits } from '@coinfra/payments';

money(1900, 'USD');                // $19.00
formatMajorUnits(money(1234, 'KWD')); // "1.234"
parseMajorUnits('12.34', 'USD');   // { amountMinor: 1234n, currency: 'USD' }

Zero-, two-, three- and four-decimal ISO currencies are supported. Excess precision is rejected rather than rounded silently.

Stripe subscriptions (SaaS)

import Stripe from 'stripe';
import { stripePayments } from '@coinfra/payments/stripe';

const stripe = stripePayments({
  client: new Stripe(process.env.STRIPE_SECRET_KEY!),
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
  environment: 'test',
});

const checkout = await stripe.createSubscriptionCheckout!({
  ownerId: merchant.id,
  customer: { id: stripeCustomerId, email: owner.email },
  priceId: process.env.STRIPE_STARTER_PRICE_ID!,
  quantity: 1,
  allowPromotionCodes: true,
  successUrl: 'https://app.example.com/billing?success=1',
  cancelUrl: 'https://app.example.com/billing?canceled=1',
  metadata: { ownerId: merchant.id, plan: 'starter' },
});

Use parseWebhook() with the raw request body. A browser success redirect must never activate access or credit money.

const event = await stripe.parseWebhook({ body: rawBody, headers: request.headers });

if (event.kind === 'subscription_changed') {
  await repository.saveSubscription(event.subscription);
}

The normalized snapshot retains the native provider status (active, past_due, incomplete, unpaid, etc.), cycle timestamps and raw provider object.

One-time payments and wallet top-ups

createPayments() owns the provider-neutral Payment Order, idempotency and webhook inbox. The application owns its database implementation and what happens after confirmation.

const payments = createPayments({ provider, repository });

const order = await payments.createOneTimeCheckout({
  ownerId: buyer.id,
  amount: money(5000, 'CNY'),
  description: 'Wallet top-up',
  successUrl,
  cancelUrl,
  idempotencyKey,
});

await payments.processWebhook({
  body: rawBody,
  headers,
  handlers: {
    async onPaymentConfirmed({ order }, tx) {
      // This callback and the order/event transition share the application
      // repository transaction. Credit the wallet and append its ledger entry here.
      await wallet.credit(order.ownerId, order.amount, order.id, tx);
    },
  },
});

The same provider event can be delivered repeatedly without applying the business credit more than once. Amount and currency mismatches reject the entire transaction.

Wallet, ledger and metering

LedgerRepository defines the required semantics; the testing package contains the reference implementation and reusable behavior:

await ledger.credit({ ... });
await ledger.debit({ ... });
await ledger.reserve({ referenceType: 'runtime_request', referenceId, amount, ... });
await ledger.capture({ referenceType: 'runtime_request', referenceId, actualAmount, allowDebt: true });
await ledger.release({ referenceType: 'runtime_request', referenceId });
await ledger.releaseExpired();

Invariants:

  • balances never go negative;
  • every mutation is idempotent by reference;
  • available and frozen buckets move atomically;
  • capture below a hold releases the difference;
  • capture above a hold takes the remaining available balance and can create debt;
  • ledger entries are append-only; a wallet balance is a read-optimized snapshot.

For multiple usage dimensions, round once across all items:

calculateMeteredCharge({
  items: [
    { units: inputTokens, pricePerUnit: money(inputRate, 'CNY') },
    { units: outputTokens, pricePerUnit: money(outputRate, 'CNY') },
  ],
  unitScale: 1_000_000,
});

Entitlements

const access = evaluateEntitlement({ active: subscriptionActive, used: 47, limit: 200 });
// { allowed: true, remaining: 153n, reason: 'allowed', ... }

Products decide when trials begin, how usage is counted, and which entitlement a plan provides. Coinfra only evaluates the supplied state.

Provider support

| Provider / rail | One-time | Subscription | Signed webhook | Refund | Payout | |---|---:|---:|---:|---:|---:| | Stripe | ✅ | ✅ | ✅ | ✅ | extension point | | PayPal | ✅ | ✅ | ✅ via official REST verification | client extension | extension point | | Alipay | ✅ page pay | — | ✅ official SDK | ✅ | extension point | | WeChat Pay v3 | ✅ Native/JSAPI/H5/App | — | ✅ delegated to injected verified v3 client | ✅ client contract | extension point | | USDT TRC20/ERC20 | deposit | — | chain confirmations | withdrawal broadcaster | manual/adapter-driven | | Fake provider | ✅ | ✅ | deterministic | testing | testing |

Stripe, PayPal and Alipay use their maintained native SDKs/protocols. For WeChat Pay, Coinfra intentionally accepts an injected verified v3 client instead of locking consumers to a weakly maintained unofficial Node package.

PayPal official adapter

import { Client, Environment } from '@paypal/paypal-server-sdk';
import { paypalOfficialClient, paypalPayments } from '@coinfra/payments/paypal';

const native = new Client({
  environment: Environment.Sandbox,
  clientCredentialsAuthCredentials: {
    oAuthClientId: process.env.PAYPAL_CLIENT_ID!,
    oAuthClientSecret: process.env.PAYPAL_CLIENT_SECRET!,
  },
});

const provider = paypalPayments({
  client: paypalOfficialClient({
    client: native,
    clientId: process.env.PAYPAL_CLIENT_ID!,
    clientSecret: process.env.PAYPAL_CLIENT_SECRET!,
    webhookId: process.env.PAYPAL_WEBHOOK_ID!,
    environment: 'test',
  }),
});

The SDK handles Orders and Subscriptions; Coinfra calls PayPal's official verify-webhook-signature REST endpoint because that endpoint is not currently included in the generated TypeScript Server SDK.

USDT escrow

createUsdtEscrow() provides the provider-neutral state machine for:

  • one derived address per deposit;
  • TRC20/ERC20 network and token-contract identity;
  • confirmation thresholds and transfer-event deduplication;
  • underpayment tolerance;
  • fiat-rate snapshot at deposit creation;
  • frozen-wallet credit;
  • withdrawal request, cooldown and address validation;
  • adapter-driven broadcast and idempotent completion.

Applications inject:

  • UsdtChainAdapter (derive address, poll transfer logs, validate address);
  • UsdtEscrowRepository;
  • LedgerRepository;
  • optionally UsdtWithdrawalBroadcaster.

Private keys and HD mnemonics must live behind a secret-store/KMS/Vault implementation, never in the application database. The package does not claim that running a custodial USDT service is legally permitted in your jurisdiction; KYC/AML, custody, accounting and operational security remain deployment responsibilities.

Payouts and settlements

const settlement = calculateSettlement({
  gross: money(10_000, 'CNY'),
  platformFeeBps: 1500,
  fixedFee: money(100, 'CNY'),
});

const payouts = createPayouts({
  repository,
  secretStore,
  provider,          // optional; omit for manual-review payouts
  requireApproval: true,
});

Raw bank/Alipay/crypto destinations are stored through PayoutSecretStore; public payout records contain only a masked destination and secret reference. A successful payout deletes the stored destination.

Testing

import {
  FakePaymentProvider,
  MemoryLedgerRepository,
  MemoryPaymentRepository,
  MemoryUsdtEscrowRepository,
} from '@coinfra/payments/testing';

The package test suite covers money precision, idempotency conflicts, concurrent replay, webhook duplication, amount tampering, transaction rollback, out-of-order events, subscription state, holds, debt, property-based balance invariants, Stripe signatures, PayPal verification, payouts, TRC20/ERC20 escrow semantics and tarball ESM/CJS imports.

Security rules

  • Never collect or store card data; use provider-hosted Checkout or native provider SDKs.
  • Never credit from a success redirect. Credit only after a signed webhook or authoritative provider reconciliation.
  • Preserve raw provider status and data; normalization must not erase information.
  • Store money as integer minor units (bigint).
  • Make all external mutations idempotent.
  • Keep provider network calls outside database transactions; commit the resulting state and business credit atomically afterward.
  • Treat webhook delivery as duplicated, delayed and out of order by default.
  • Use raw request bytes for signature verification.
  • Keep payout destinations, wallet mnemonics and private keys in Vault/KMS/secret storage.

Deliberate alpha limits

  • No built-in tax or invoice engine.
  • No automatic Stripe Connect seller onboarding yet.
  • No claim that USDT custody/withdrawal is production-compliant without deployment-specific KYC/AML and key-management controls.
  • WeChat Pay requires a correctly implemented/injected v3 client for signing and callback decryption.
  • Database adapters are application-owned in alpha; in-memory reference implementations are shipped, and the two initial consumers provide Prisma/SQLite and PostgreSQL integration.

License

MIT © corelli