Skip to main content

@paymint/server

Server-side SDK for Paymint billing.

Installation

npm install @paymint/server
Note: For Next.js apps, use @paymint/nextjs instead - it includes this package plus React hooks.

Quick Start

import { PaymintServer } from '@paymint/server';

const paymint = new PaymintServer({
  apiKey: process.env.PAYMINT_API_KEY!,
  // baseUrl is auto-configured:
  // - Default: https://api.paymint.dev
  // - Local: Set PAYMINT_USE_LOCAL_API=true for localhost:3002
});

// Or override explicitly:
const paymint = new PaymintServer({
  apiKey: process.env.PAYMINT_API_KEY!,
  baseUrl: 'http://localhost:3002', // Only for internal testing
});

// Get all products
const products = await paymint.getProducts();

// Initialize checkout (returns Paddle client token)
const init = await paymint.initialize('[email protected]');
// init.clientSideToken - for Paddle.js
// init.paddleEnvironment - 'sandbox' or 'production'

// Get billing state (products + subscriptions)
const billing = await paymint.getBilling('[email protected]');
// billing.products
// billing.subscriptions
// billing.hasActiveSubscription

// Get subscriptions only
const subscriptions = await paymint.getSubscriptions('[email protected]');

// Cancel subscription
await paymint.cancelSubscription('[email protected]', 'sub_xxx', {
  effectiveFrom: 'next_billing_period', // or 'immediately'
});

// Pause subscription
await paymint.pauseSubscription('[email protected]', 'sub_xxx', {
  effectiveFrom: 'next_billing_period',
  resumeAt: '2024-03-01', // optional auto-resume date
});

// Resume subscription
await paymint.resumeSubscription('[email protected]', 'sub_xxx');

// Activate trial
await paymint.activateSubscription('[email protected]', 'sub_xxx');

Scoped Customer Pattern

When making multiple calls for the same customer, use forCustomer() for cleaner code:
const paymint = new PaymintServer({ apiKey: process.env.PAYMINT_API_KEY! });

// Without scoped customer
const billing = await paymint.getBilling('[email protected]');
await paymint.cancelSubscription('[email protected]', 'sub_xxx', {
  effectiveFrom: 'next_billing_period'
});

// With scoped customer - cleaner!
const customer = paymint.forCustomer('[email protected]');
const billing = await customer.getBilling();
await customer.cancelSubscription('sub_xxx', { effectiveFrom: 'next_billing_period' });

Scoped Customer Methods

const customer = paymint.forCustomer('[email protected]');

// All methods - email is already bound
const billing = await customer.getBilling();
const subscriptions = await customer.getSubscriptions();
const init = await customer.initialize();

// Subscription actions
await customer.cancelSubscription('sub_xxx', { effectiveFrom: 'next_billing_period' });
await customer.pauseSubscription('sub_xxx', { effectiveFrom: 'next_billing_period' });
await customer.resumeSubscription('sub_xxx');
await customer.activateSubscription('sub_xxx');

BillingState Type

interface BillingState {
  products: Product[];
  subscriptions: Subscription[];
  hasActiveSubscription: boolean;
  activeSubscription: Subscription | null;
  isTrialing: boolean;
  isPaused: boolean;
  isCanceled: boolean;
}

Complete Examples

Check Subscription Access

import { PaymintServer } from '@paymint/server';

async function checkAccess(email: string) {
  const paymint = new PaymintServer({
    apiKey: process.env.PAYMINT_API_KEY!,
  });

  const billing = await paymint.getBilling(email);

  if (billing.hasActiveSubscription) {
    console.log('User has active subscription:', billing.activeSubscription?.id);
    return { access: true, subscription: billing.activeSubscription };
  }

  if (billing.isTrialing) {
    console.log('User is on trial');
    return { access: true, trial: true };
  }

  return { access: false };
}

Subscription Management

import { PaymintServer } from '@paymint/server';

async function manageSubscription(email: string, subscriptionId: string) {
  const paymint = new PaymintServer({
    apiKey: process.env.PAYMINT_API_KEY!,
  });

  const customer = paymint.forCustomer(email);

  // Get current state
  const billing = await customer.getBilling();
  const subscription = billing.subscriptions.find(s => s.id === subscriptionId);

  if (!subscription) {
    throw new Error('Subscription not found');
  }

  // Cancel at end of period (recommended)
  if (subscription.status === 'active') {
    await customer.cancelSubscription(subscriptionId, {
      effectiveFrom: 'next_billing_period'
    });
    console.log('Subscription will cancel at end of billing period');
  }

  // Or pause temporarily
  if (subscription.status === 'active') {
    await customer.pauseSubscription(subscriptionId, {
      effectiveFrom: 'next_billing_period',
      resumeAt: '2024-03-01T00:00:00Z' // Auto-resume
    });
    console.log('Subscription paused until March 1st');
  }

  // Resume paused subscription
  if (subscription.status === 'paused') {
    await customer.resumeSubscription(subscriptionId);
    console.log('Subscription resumed');
  }
}

Initialize Checkout

import { PaymintServer } from '@paymint/server';

async function createCheckout(email: string) {
  const paymint = new PaymintServer({
    apiKey: process.env.PAYMINT_API_KEY!,
  });

  const init = await paymint.initialize(email);

  return {
    token: init.clientSideToken,
    environment: init.paddleEnvironment,
    customerExists: init.customerExists,
  };
}

Error Handling

All errors extend PaymintError from @paymint/shared:
import { PaymintServer } from '@paymint/server';
import { PaymintError, ValidationError } from '@paymint/shared';

const paymint = new PaymintServer({ apiKey: process.env.PAYMINT_API_KEY! });

try {
  const billing = await paymint.getBilling('[email protected]');
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid email or parameters:', error.message);
  } else if (error instanceof PaymintError) {
    console.error('Paymint error:', error.message, 'Request ID:', error.requestId);
  } else {
    console.error('Unexpected error:', error);
  }
}

Environment

The API key format determines the environment:
  • paymint_test_xxx → Sandbox
  • paymint_live_xxx → Production

Comparison with @paymint/node

Use CasePackageReason
Full-stack Next.js@paymint/nextjsIncludes server + React hooks
React SPA + custom backend@paymint/serverServer utilities
Raw HTTP access@paymint/nodeLow-level client
Type definitions only@paymint/sharedTypes and errors

License

MIT