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

@tributary-so/payments

v1.0.0

Published

Stripe-compatible payments SDK for Tributary USDC subscriptions - zero API keys required

Readme

@tributary-so/payments

A minimal Stripe-compatible payments SDK for Tributary USDC subscriptions on Solana. Provides essential checkout session functionality with zero API keys required - developers can integrate immediately without registration.

Features

  • Zero API Keys: No registration, no configuration, just install and use
  • USDC Only: Single currency support (USDC on Solana)
  • Tributary Payment Method: Only supports "tributary" payment method type
  • Base64URL Encoding: Compact, URL-safe session encoding for sharing
  • Dual Lookup Strategy: User-based OR gateway-based subscription status checking
  • Real-time Status: Live subscription status using PaymentPolicy paymentCount
  • Pure Frontend: No backend or webhooks required for basic functionality
  • Type Safety: Full TypeScript support with Stripe-compatible types

Installation

npm install @tributary-so/payments

Quick Start

import { PaymentsClient } from "@tributary-so/payments";
import { Connection } from "@solana/web3.js";
import { Tributary } from "@tributary-so/sdk";

// Initialize with connection and tributary
const connection = new Connection("https://api.mainnet-beta.solana.com");
const tributary = new Tributary(connection, wallet);
const stripe = new PaymentsClient(connection, tributary);

const session = await stripe.checkout.sessions.create({
  payment_method_types: ["tributary"],
  line_items: [
    {
      description: "Monthly premium access to all features",
      unitPrice: 20.0, // $20.00
      quantity: 1,
    },
  ],
  paymentFrequency: "monthly",
  mode: "subscription",
  success_url: "https://yourapp.com/success",
  cancel_url: "https://yourapp.com/cancel",
  tributaryConfig: {
    gateway: "GATEWAY_PUBLIC_KEY_HERE",
    recipient: "RECIPIENT_PUBLIC_KEY_HERE",
    trackingId: "user_123_monthly_premium", // Your unique identifier
    autoRenew: true,
    memo: "Optional memo for the payment",
  },
});

// Redirect to hosted checkout
window.location.href = session.url;

Subscription Status Tracking

Check subscription status efficiently using our dual lookup strategy - either user-based OR gateway-based:

import { PaymentsClient } from "@tributary-so/payments";
import { Connection } from "@solana/web3.js";
import { Tributary } from "@tributary-so/sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const tributary = new Tributary(connection, wallet);
const stripe = new PaymentsClient(connection, tributary);

// Option 1: User-based lookup (for user-facing apps)
async function checkUserSubscription() {
  const status = await stripe.subscriptions.checkStatus({
    trackingId: "user_123_monthly_premium",
    userPublicKey: "USER_PUBLIC_KEY_HERE",
    tokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
  });

  if (status.status === "active") {
    console.log("Subscription active!", {
      paymentCount: status.paymentCount,
      nextPaymentDue: status.nextPaymentDue,
    });
    // Grant access, update UI, etc.
  } else if (status.status === "created") {
    console.log("Subscription created, waiting for first payment...");
    // Show pending status
  } else {
    console.log("Subscription not found or failed");
    // Handle error case
  }
}

// Option 2: Gateway-based lookup (for gateway management)
async function checkGatewaySubscription() {
  const status = await stripe.subscriptions.checkStatus({
    trackingId: "user_123_monthly_premium",
    gatewayPublicKey: "GATEWAY_PUBLIC_KEY_HERE",
  });

  if (status.status === "active") {
    console.log("Subscription active via gateway!", {
      paymentCount: status.paymentCount,
      nextPaymentDue: status.nextPaymentDue,
    });
    // Gateway management logic
  }
}

// Enhanced session retrieval with real-time status
async function getSessionWithStatus() {
  const session = await stripe.checkout.sessions.retrieve(
    "session_id_or_encoded_url"
  );

  if (session.subscription) {
    console.log("Subscription details:", {
      id: session.subscription.id,
      status: session.subscription.status,
      paymentCount: session.subscription.paymentCount,
      current_period_end: session.subscription.current_period_end,
    });
  }
}

// Check status on page load
checkUserSubscription();

Status Flow

The subscription status follows a simple flow based on PaymentPolicy state:

  1. pending: Subscription not yet created
  2. created: Subscription created on-chain, waiting for first payment
  3. active: First payment executed, subscription is active
interface SubscriptionStatus {
  id: string; // PaymentPolicy public key
  object: "subscription";
  status: "pending" | "created" | "active" | "failed";
  paymentCount: number; // Number of payments executed
  current_period_start?: number; // Unix timestamp
  current_period_end?: number; // Unix timestamp
  nextPaymentDue?: number; // Unix timestamp of next payment
  metadata: {
    trackingId: string;
    userPublicKey?: string;
    gatewayPublicKey?: string;
    recipient: string;
    tokenMint: string;
    amount: number;
    frequency: number;
  };
}

URL Encoding

The SDK uses Base64URL encoding to pack all subscription parameters into compact, shareable URLs:

// Session URL contains all necessary data
const session = await stripe.checkout.sessions.create({
  // ... configuration
});

// URL format: https://checkout.tributary.so/subscribe/{base64url-encoded-data}
console.log(session.url);
// Example: https://checkout.tributary.so/subscribe/eyJ0bSI6IkVQakZXZGRBdWZxU1NxZTJxTjF6eWJhcEM4RzR3RUdHa3p3eVREdjF2Iiwi...

The encoded data includes:

  • tm: Token mint (USDC)
  • r: Recipient public key
  • g: Gateway public key
  • a: Total amount (calculated from line items)
  • ar: Auto-renew flag
  • mr: Maximum renewals
  • pf: Payment frequency
  • st: Start time
  • tid: Tracking ID
  • li: Line items (JSON array)

MEMO Format

Tracking IDs are stored in Solana transaction MEMO fields using the format:

tributary:payment:{trackingId}

Example: tributary:payment:user_123_monthly_premium

This enables:

  • Blockchain verification: Payment status can be verified by anyone
  • No webhooks needed: Status checking via on-chain state
  • Client-side only: Pure frontend implementation possible

API Reference

PaymentsClient

The main client class - requires Connection and Tributary instances.

import { Connection } from "@solana/web3.js";
import { Tributary } from "@tributary-so/sdk";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const tributary = new Tributary(connection, wallet);
const stripe = new PaymentsClient(connection, tributary);

checkout.sessions.create()

Create a checkout session with encoded URL.

const session = await stripe.checkout.sessions.create({
  payment_method_types: ["tributary"], // Only "tributary" supported
  line_items: [
    {
      description: "Product Name",
      unitPrice: 20.0, // Amount in dollars
      quantity: 1,
    },
  ],
  paymentFrequency: "monthly", // "daily" | "weekly" | "monthly" | "annually"
  mode: "subscription",
  success_url: "https://yourapp.com/success",
  cancel_url: "https://yourapp.com/cancel",
  tributaryConfig: {
    gateway: "gateway-public-key", // Gateway public key
    recipient: "recipient-public-key", // Recipient public key
    trackingId: "unique-tracking-id", // Your unique identifier
    autoRenew: true,
    memo: "Optional memo for payments",
  },
});

Returns:

{
  id: string; // Session ID
  object: "checkout.session";
  url: string; // Encoded checkout URL
  payment_status: "unpaid";
  status: "open";
  amount_total: number;
  currency: "usd";
  // ... other Stripe-compatible fields
}

checkout.sessions.retrieve()

Retrieve a session with real-time subscription status.

// Retrieve by session ID
const session = await stripe.checkout.sessions.retrieve("cs_1234567890");

// Or retrieve by encoded URL (auto-detected)
const session = await stripe.checkout.sessions.retrieve(
  "https://checkout.tributary.so/subscribe/eyJ0bSI6IkVQakFWZGRBdWZxU1NxZTJxTjF6eWJhcEM4RzR3RUdHa3p3eVREdjF2Iiwi..."
);

// Enhanced response with subscription details
console.log(session.subscription);
// {
//   id: "sub_1234567890",
//   object: "subscription",
//   status: "active",
//   paymentCount: 3,
//   current_period_end: 1640995200,
//   nextPaymentDue: 1641081600,
//   metadata: { ... }
// }

Returns:

{
  id: string;
  object: "checkout.session";
  url: string;
  payment_status: "unpaid";
  status: "open";
  amount_total: number;
  currency: "usd";
  subscription?: SubscriptionStatus; // Real-time status if available
  // ... other Stripe-compatible fields
}

subscriptions.checkStatus()

Check subscription status using dual lookup strategy.

// User-based lookup
const status = await stripe.subscriptions.checkStatus({
  trackingId: "user_123_monthly_premium",
  userPublicKey: "USER_PUBLIC_KEY_HERE",
  tokenMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
});

// Gateway-based lookup
const status = await stripe.subscriptions.checkStatus({
  trackingId: "user_123_monthly_premium",
  gatewayPublicKey: "GATEWAY_PUBLIC_KEY_HERE",
});

PolicyLookupOptions:

interface PolicyLookupOptions {
  trackingId: string;
  userPublicKey?: string; // Either userPublicKey OR gatewayPublicKey
  gatewayPublicKey?: string;
  tokenMint?: string; // Defaults to USDC
}

Returns:

{
  id: string;
  object: "subscription";
  status: "pending" | "created" | "active" | "failed";
  paymentCount: number;
  current_period_start?: number;
  current_period_end?: number;
  nextPaymentDue?: number;
  metadata: {
    trackingId: string;
    userPublicKey?: string;
    gatewayPublicKey?: string;
    recipient: string;
    tokenMint: string;
    amount: number;
    frequency: number;
  };
}

subscriptions.isActive()

Quick check if subscription is active (created + initial payment).

const isActive = await stripe.subscriptions.isActive({
  trackingId: "user_123_monthly_premium",
  userPublicKey: "USER_PUBLIC_KEY_HERE",
});
// Returns: boolean

subscriptions.getDetails()

Get detailed subscription information.

const details = await stripe.subscriptions.getDetails({
  trackingId: "user_123_monthly_premium",
  gatewayPublicKey: "GATEWAY_PUBLIC_KEY_HERE",
});
// Returns: SubscriptionStatus | null

The tributaryConfig object contains Tributary-specific settings:

  • gateway: Your Tributary gateway public key
  • recipient: The recipient public key (where payments go)
  • trackingId: Your unique identifier for tracking payments
  • autoRenew: Enable automatic subscription renewal (default: false)
  • memo: Optional memo to attach to each payment transaction

Tributary Configuration

Traditional Approach (Expensive)

// Expensive: Scan transaction history
const transactions = await connection.getSignaturesForAddress(recipient);
const paymentTxs = await Promise.all(
  transactions.map((sig) => connection.getParsedTransaction(sig.signature))
);
const hasPayment = paymentTxs.some((tx) =>
  tx.memo?.includes(`tributary:payment:${trackingId}`)
);

PaymentPolicy Approach (Efficient)

// Fast: Direct on-chain state lookup
const paymentPolicy = await tributary.getPaymentPolicy(policyPda);
const isActive = paymentPolicy?.paymentCount > 0;

Performance Benefits

Traditional Approach (Expensive)

// Expensive: Scan transaction history
const transactions = await connection.getSignaturesForAddress(recipient);
const paymentTxs = await Promise.all(
  transactions.map((sig) => connection.getParsedTransaction(sig.signature))
);
const hasPayment = paymentTxs.some((tx) =>
  tx.memo?.includes(`tributary:payment:${trackingId}`)
);

PaymentPolicy Approach (Efficient)

// Fast: Direct on-chain state lookup
const paymentPolicy = await tributary.getPaymentPolicy(policyPda);
const isActive = paymentPolicy?.paymentCount > 0;

Dual Lookup Strategy (Optimized)

Our SDK provides two optimized lookup methods:

User-based lookup:

// O(1) lookup via UserPayment PDA
const userPaymentPda = getUserPaymentPda(userPublicKey, tokenMint);
const policies = await getPaymentPoliciesByUserPayment(userPaymentPda);
const policy = policies.find((p) => p.trackingId === trackingId);

Gateway-based lookup:

// O(1) lookup via gateway public key
const policies = await getPaymentPoliciesByGateway(gatewayPublicKey);
const policy = policies.find((p) => p.trackingId === trackingId);

Benefits:

  • O(1) vs O(n): Direct lookup vs transaction scanning
  • Low RPC cost: Single query vs hundreds of transactions
  • Instant response: No need to scan entire payment history
  • Flexible: User-facing apps OR gateway management
  • Reliable: Uses on-chain state, not external indexes
  • Real-time: Live status from PaymentPolicy paymentCount

Error Handling

The SDK throws standard JavaScript errors for invalid inputs:

try {
  const session = await stripe.checkout.sessions.create(params);
} catch (error) {
  if (error.message.includes("Invalid gateway public key")) {
    // Handle invalid gateway key
  } else if (error.message.includes("Invalid trackingId format")) {
    // Handle invalid tracking ID
  } else if (
    error.message.includes("Either userPublicKey or gatewayPublicKey required")
  ) {
    // Handle missing lookup parameters
  }
  // ... other error handling
}

Development

# Install dependencies
npm install

# Build the package
npm run build

# Run tests
npm run test

# Lint the code
npm run lint

License

MIT