Vertueal Docs
SubsManager

TypeScript SDK & Integration Examples

Production-ready TypeScript client class and end-to-end integration workflows.

TypeScript SDK & Integration Examples

This reference provides a complete, copy-pasteable TypeScript integration client (SubsManagerClient) that wraps the SubsManager REST API with strong type definitions, error parsing, and offline payment workflows.


1. Complete TypeScript Client Wrapper

Save this file as lib/subsmanager.ts in your application codebase:

export interface Plan {
  _id: string;
  name: string;
  description: string;
  price: number;
  currency: string;
  billingCycle: 'monthly' | 'yearly';
  trialPeriodDays?: number;
  features: string[];
}

export interface Subscription {
  _id: string;
  referenceNumber: string;
  status: 'trialing' | 'pending' | 'active' | 'past_due' | 'cancelled' | 'expired';
  startDate: string;
  endDate?: string;
  trialStart?: string;
  trialEnd?: string;
  autoRenew: boolean;
}

export interface Invoice {
  _id: string;
  invoiceNumber: string;
  status: 'draft' | 'open' | 'paid' | 'uncollectible' | 'void';
  amount: number;
  currency: string;
  dueDate: string;
  paidAt?: string;
  invoiceUrl: string;
  bankingDetailsSnapshot?: {
    bankName: string;
    accountName: string;
    accountNumber: string;
    branchCode: string;
    swiftCode?: string;
  };
}

export interface SubscriptionResponse {
  subscription: Subscription;
  checkoutUrl?: string;
}

export class SubsManagerClient {
  private baseUrl: string;
  private apiKey: string;

  constructor(apiKey: string, baseUrl = 'https://subsmanager.vertueal.com/api/v1') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl.replace(/\/$/, '');
  }

  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const res = await fetch(`${this.baseUrl}${endpoint}`, {
      ...options,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        ...options.headers,
      },
    });

    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.error || `SubsManager API error: ${res.status}`);
    }

    return res.json();
  }

  /**
   * 1. Retrieve all active subscription plans
   */
  async getPlans(): Promise<Plan[]> {
    const res = await this.request<{ plans: Plan[] }>('/plans');
    return res.plans;
  }

  /**
   * 2. Upsert an end-user client
   */
  async upsertClient(externalId: string, email: string, name?: string) {
    return this.request<{ client: { _id: string; externalId: string; email: string } }>('/clients', {
      method: 'POST',
      body: JSON.stringify({ externalId, email, name }),
    });
  }

  /**
   * 3. Subscribe a client to a plan (Handles Trials, Offline EFT, and Card Checkouts)
   */
  async subscribe(params: {
    externalId: string;
    planId: string;
    successUrl?: string;
    cancelUrl?: string;
  }): Promise<SubscriptionResponse> {
    return this.request<SubscriptionResponse>('/subscriptions', {
      method: 'POST',
      body: JSON.stringify(params),
    });
  }

  /**
   * 4. Check active subscription entitlement
   */
  async getCurrentSubscription(externalId: string): Promise<Subscription | null> {
    const res = await this.request<{ subscription: Subscription | null }>(
      `/subscriptions/current?externalId=${encodeURIComponent(externalId)}`
    );
    return res.subscription;
  }

  /**
   * 5. Fetch invoices for subscriber billing history
   */
  async getInvoices(externalId: string, status?: string): Promise<Invoice[]> {
    const query = new URLSearchParams({ externalId });
    if (status) query.append('status', status);
    
    const res = await this.request<{ data: { invoices: Invoice[] } }>(`/invoices?${query.toString()}`);
    return res.data.invoices;
  }

  /**
   * 6. Reconcile offline EFT payment and activate subscription
   */
  async markInvoicePaid(invoiceId: string) {
    return this.request<{ success: boolean; data: { invoice: Invoice } }>(
      `/invoices/${invoiceId}/mark-paid`,
      { method: 'POST' }
    );
  }
}

2. End-to-End Subscription Workflow in Next.js

// app/api/subscribe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { SubsManagerClient } from '@/lib/subsmanager';

const subsManager = new SubsManagerClient(process.env.SUBSMANAGER_API_KEY!);

export async function POST(req: NextRequest) {
  const { userId, userEmail, planId } = await req.json();

  // 1. Ensure user client record exists
  await subsManager.upsertClient(userId, userEmail);

  // 2. Subscribe user
  const result = await subsManager.subscribe({
    externalId: userId,
    planId,
    successUrl: 'https://myapp.com/billing?status=success',
    cancelUrl: 'https://myapp.com/billing?status=cancelled',
  });

  // 3. If online card gateway returned checkoutUrl, return redirect link
  if (result.checkoutUrl) {
    return NextResponse.json({ redirectUrl: result.checkoutUrl });
  }

  // 4. If free trial or offline EFT, subscription is created immediately
  return NextResponse.json({
    status: result.subscription.status,
    referenceNumber: result.subscription.referenceNumber,
    message: result.subscription.status === 'trialing' 
      ? 'Free trial active!' 
      : 'Subscription created. Check your email for EFT bank transfer details.',
  });
}

On this page