Skip to main content
Revstack’s provider system abstracts payment gateway complexity behind a unified API. Write your billing logic once, and switch between Stripe, Polar, or custom providers without changing application code.

Why Provider Abstraction?

Multi-Provider Support

Support multiple payment processors simultaneously for different markets

Vendor Independence

Switch providers without rewriting your entire billing stack

Unified API

One consistent API across all providers - no provider-specific quirks

Feature Parity

Graceful degradation when providers don’t support specific features

Provider Architecture

Revstack uses a plugin-based provider system: All providers implement the same IProvider interface, ensuring consistent behavior.

Available Providers

Official Providers

Revstack maintains these production-ready providers:
Stripe - The most popular payment processor for SaaS.
import { StripeProvider } from "@revstackhq/provider-stripe";

const provider = new StripeProvider({
  apiKey: process.env.STRIPE_SECRET_KEY,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
});
Capabilities:
  • Checkout: redirect (Stripe Checkout) or native_sdk (Stripe Elements)
  • Subscriptions: Native billing engine with proration
  • Payments: Full support including refunds, partial refunds, auth/capture
  • Catalog: Inline pricing (no pre-creation needed)
  • Webhooks: HMAC signature verification

Community Providers

Build your own provider for:
  • Regional payment gateways (Razorpay, Mercado Pago, etc.)
  • Crypto payment processors (Coinbase Commerce, BTCPay)
  • Custom billing systems
See the Custom Providers guide to build your own.

Provider Capabilities

Providers declare their capabilities via a manifest object:
import type { ProviderCapabilities } from "@revstackhq/provider-core";

const capabilities: ProviderCapabilities = {
  checkout: {
    supported: true,
    strategy: "redirect", // or 'native_sdk' or 'sdui'
  },
  payments: {
    supported: true,
    features: {
      refunds: true,
      partialRefunds: true,
      capture: true,
      disputes: true,
    },
  },
  subscriptions: {
    supported: true,
    mode: "native", // Provider handles billing, or 'virtual' for Revstack-managed
    features: {
      pause: true,
      resume: true,
      cancellation: true,
      proration: true,
    },
  },
  customers: {
    supported: true,
    features: {
      create: true,
      update: true,
      delete: true,
    },
  },
  webhooks: {
    supported: true,
    verification: "signature", // HMAC signature
  },
  catalog: {
    supported: true,
    strategy: "inline", // or 'pre_created'
  },
};

Checkout Strategies

strategy
CheckoutStrategy
redirect: User is redirected to provider’s hosted checkout page (e.g., Stripe Checkout)
  • Easiest integration, full PCI compliance
  • User leaves your domain
native_sdk: Embed provider’s payment form in your app (e.g., Stripe Elements)
  • User stays on your domain, seamless UX
  • Requires client-side SDK integration
sdui: Server-Driven UI - provider returns JSON primitives, Revstack renders natively
  • Use case: Crypto payments, bank transfers (Pix, Boleto)
  • No external scripts required

Subscription Modes

mode
SubscriptionMode
native: Provider acts as the billing engine
  • Provider handles recurring charges, retries, and proration
  • Revstack mirrors state via webhooks
  • Example: Stripe Billing, Polar Subscriptions
virtual: Revstack acts as the billing engine
  • Revstack runs the scheduler and triggers one-time payments
  • Use case: Providers without native subscription support (e.g., simple gateways)

Configuring Providers

Providers are configured in your Revstack Cloud dashboard or via environment variables:
.env
# Primary provider (e.g., Stripe)
REVSTACK_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Secondary provider (e.g., Polar for open-source tier)
POLAR_SECRET_KEY=polar_...
POLAR_WEBHOOK_SECRET=polar_whsec_...
Revstack automatically routes payments to the correct provider based on your configuration.

Provider Selection

For multi-provider setups, specify which provider to use:
import { Revstack } from "@revstackhq/node";

const revstack = new Revstack({ secretKey: process.env.REVSTACK_SECRET_KEY });

// Create subscription with specific provider
const subscription = await revstack.subscriptions.create({
  customerId: "usr_abc123",
  planId: "plan_pro",
  provider: "stripe", // Override default provider
});

// Create checkout session with provider selection
const session = await revstack.checkout.create({
  customerId: "usr_abc123",
  planId: "plan_oss",
  provider: "polar", // Use Polar for open-source tier
  successUrl: "https://myapp.com/success",
  cancelUrl: "https://myapp.com/pricing",
});

Provider Interface

All providers implement the IProvider interface:
import type {
  IProvider,
  ProviderContext,
  AsyncActionResult,
} from "@revstackhq/provider-core";

interface IProvider {
  // Required metadata
  readonly manifest: ProviderManifest;

  // Lifecycle
  onInstall(ctx: ProviderContext, input: InstallInput): Promise<AsyncActionResult<InstallResult>>;
  onUninstall(ctx: ProviderContext, input: UninstallInput): Promise<AsyncActionResult<boolean>>;

  // Webhooks (required)
  verifyWebhookSignature(ctx: ProviderContext, payload: string, headers: Record<string, string>, secret: string): Promise<AsyncActionResult<boolean>>;
  parseWebhookEvent(ctx: ProviderContext, payload: any): Promise<AsyncActionResult<RevstackEvent | null>>;

  // Checkout (optional)
  createCheckoutSession?(ctx: ProviderContext, input: CheckoutSessionInput): Promise<AsyncActionResult<CheckoutSessionResult>>;

  // Subscriptions (optional)
  createSubscription?(ctx: ProviderContext, input: CreateSubscriptionInput): Promise<AsyncActionResult<string>>;
  cancelSubscription?(ctx: ProviderContext, input: CancelSubscriptionInput): Promise<AsyncActionResult<string>>;
  pauseSubscription?(ctx: ProviderContext, input: PauseSubscriptionInput): Promise<AsyncActionResult<string>>;
  resumeSubscription?(ctx: ProviderContext, input: ResumeSubscriptionInput): Promise<AsyncActionResult<string>>;

  // Payments (optional)
  createPayment?(ctx: ProviderContext, input: CreatePaymentInput): Promise<AsyncActionResult<string>>;
  refundPayment?(ctx: ProviderContext, input: RefundPaymentInput): Promise<AsyncActionResult<string>>;
  capturePayment?(ctx: ProviderContext, input: CapturePaymentInput): Promise<AsyncActionResult<string>>;

  // Customers (optional)
  createCustomer?(ctx: ProviderContext, input: CreateCustomerInput): Promise<AsyncActionResult<string>>;
  updateCustomer?(ctx: ProviderContext, input: UpdateCustomerInput): Promise<AsyncActionResult<string>>;
  deleteCustomer?(ctx: ProviderContext, input: DeleteCustomerInput): Promise<AsyncActionResult<boolean>>;

  // Catalog (optional)
  createProduct?(ctx: ProviderContext, input: ProductInput): Promise<AsyncActionResult<string>>;
  createPrice?(ctx: ProviderContext, input: PriceInput): Promise<AsyncActionResult<string>>;
}

BaseProvider Class

Extend BaseProvider to inherit default implementations:
import { BaseProvider } from "@revstackhq/provider-core";

export class MyCustomProvider extends BaseProvider {
  readonly manifest = {
    slug: "my-gateway",
    name: "My Payment Gateway",
    version: "1.0.0",
    capabilities: {
      checkout: { supported: true, strategy: "redirect" },
      subscriptions: { supported: false, mode: "virtual" },
      payments: { supported: true, features: { refunds: true } },
      // ...
    },
  };

  async onInstall(ctx, input) {
    // Setup provider credentials
    return { status: "success", data: { webhookUrl: ctx.webhookUrl } };
  }

  async onUninstall(ctx, input) {
    // Cleanup provider resources
    return { status: "success", data: true };
  }

  async verifyWebhookSignature(ctx, payload, headers, secret) {
    // Verify HMAC signature
    const signature = headers["x-gateway-signature"];
    const isValid = verifyHmac(payload, signature, secret);
    return { status: "success", data: isValid };
  }

  async parseWebhookEvent(ctx, payload) {
    // Map provider event to Revstack event
    if (payload.type === "charge.succeeded") {
      return {
        status: "success",
        data: {
          type: "payment.succeeded",
          data: { paymentId: payload.id, customerId: payload.customer },
        },
      };
    }
    return { status: "success", data: null };
  }

  async createCheckoutSession(ctx, input) {
    // Create hosted checkout page
    const session = await myGateway.checkout.create({
      amount: input.amount,
      currency: input.currency,
    });
    return { status: "success", data: { url: session.url, sessionId: session.id } };
  }
}

Error Handling

Providers return AsyncActionResult for consistent error handling:
type AsyncActionResult<T> =
  | { status: "success"; data: T }
  | { status: "failed"; error: { code: RevstackErrorCode; message: string } };
Revstack SDK automatically converts provider errors:
try {
  const sub = await revstack.subscriptions.create({ /* ... */ });
} catch (error) {
  if (error.code === "provider_not_implemented") {
    console.error("This provider doesn't support subscriptions");
  } else if (error.code === "payment_failed") {
    console.error("Payment was declined");
  }
}

Webhook Handling

Providers translate webhook events to Revstack’s normalized event format:
// Provider-specific webhook payload
const stripeEvent = {
  type: "customer.subscription.created",
  data: { object: { id: "sub_123", customer: "cus_abc" } },
};

// Provider maps it to Revstack event
const revstackEvent = {
  type: "subscription.created",
  data: { id: "sub_123", customerId: "cus_abc" },
};
Your application always receives normalized events, regardless of provider.

Testing Providers

Revstack provides a smoke test runner for validating provider implementations:
import { runProviderSmokeTests } from "@revstackhq/provider-core";
import { MyCustomProvider } from "./my-provider";

const provider = new MyCustomProvider();

await runProviderSmokeTests(provider, {
  credentials: {
    apiKey: process.env.GATEWAY_API_KEY,
  },
  testMode: true,
});
The test suite validates:
  • Webhook signature verification
  • Event parsing
  • Checkout session creation
  • Subscription lifecycle (if supported)
  • Refund processing (if supported)

Provider Comparison

FeatureStripePolarCustom
Checkout StrategyRedirect + Native SDKRedirectVaries
Subscription ModeNativeNativeVirtual (Revstack-managed)
Proration✅ Automatic✅ Automatic❌ Manual
Pause/Resume✅ Yes❌ NoDepends
Refunds✅ Full + Partial✅ Full onlyDepends
Auth/Capture✅ Yes❌ NoDepends
Billing Portal✅ Yes✅ Yes❌ No

Next Steps

Custom Providers

Build your own provider for regional gateways

Billing as Code

Define your pricing model in TypeScript

Build docs developers (and LLMs) love