Skip to main content

@paymint/react

React hooks for Paymint billing.

Installation

npm install @paymint/react
Note: For Next.js apps, use @paymint/nextjs instead - it includes everything from this package plus server utilities.

Quick Start

1. Set up Provider

import { PaymintProvider } from '@paymint/react';

function App() {
  return (
    <PaymintProvider 
      apiRoute="/api/billing"           // Your backend API route
      customerEmail="[email protected]"  // Current user's email
      onCheckoutComplete={(txnId) => {
        // Handle successful checkout
        window.location.href = `/success?txn=${txnId}`;
      }}
    >
      <YourApp />
    </PaymintProvider>
  );
}

2. Use Hooks

import { useBilling, useCheckout } from '@paymint/react';

function PricingPage() {
  const { products, subscription, loading, hasActiveSubscription } = useBilling();
  const { openCheckout, ready } = useCheckout();

  if (loading) return <div>Loading...</div>;

  if (hasActiveSubscription) {
    return <div>Current plan: {subscription.productName}</div>;
  }

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>
          <h2>{product.name}</h2>
          {product.prices.map(price => (
            <button
              key={price.id}
              onClick={() => openCheckout(price.id)}
              disabled={!ready}
            >
              ${price.unitPrice.amount / 100}/{price.billingCycle.interval}
            </button>
          ))}
        </div>
      ))}
    </div>
  );
}

Hooks

useBilling()

Combined billing data (products + subscription).
const {
  products,              // Product[]
  subscription,          // Subscription | null
  loading,               // boolean
  error,                 // Error | null
  refresh,               // () => Promise<void>
  hasActiveSubscription, // boolean
  isOnTrial,             // boolean
} = useBilling();

useProducts()

Available products only.
const {
  products,  // Product[]
  loading,   // boolean
  error,     // Error | null
  refresh,   // () => Promise<void>
} = useProducts();

useSubscription()

Subscription management.
const {
  subscription,          // Subscription | null
  loading,               // boolean
  hasActiveSubscription, // boolean
  isOnTrial,             // boolean
  isPaused,              // boolean
  isCanceling,           // boolean
  cancel,                // (options?) => Promise<void>
  pause,                 // (options?) => Promise<void>
  resume,                // () => Promise<void>
  isMutating,            // boolean
} = useSubscription();

// Cancel at end of billing period
await cancel({ effectiveFrom: 'next_billing_period' });

// Pause subscription
await pause({ effectiveFrom: 'next_billing_period' });

// Resume paused subscription
await resume();

useCheckout()

Paddle checkout integration.
const {
  ready,         // boolean - checkout is ready
  loading,       // boolean - initializing
  openCheckout,  // (priceId | options) => void
  closeCheckout, // () => void
  error,         // Error | null
} = useCheckout();

// Simple
openCheckout('pri_xxx');

// With options
openCheckout({
  items: [{ priceId: 'pri_xxx', quantity: 1 }],
  discountCode: 'SAVE20',
  settings: { 
    theme: 'dark',
    successUrl: '/checkout/success',
  },
});

Provider Props

<PaymintProvider
  apiRoute="/api/billing"             // Required: your backend route
  customerEmail="[email protected]"    // Customer email
  autoLoadProducts={true}             // Auto-fetch products (default: true)
  autoLoadSubscription={true}         // Auto-fetch subscription (default: true)
  onCheckoutComplete={(txnId) => {}}  // Checkout success callback
  onCheckoutClose={() => {}}          // Checkout closed callback
>

Post-Checkout Success Handling

Option 1: onCheckoutComplete Callback

<PaymintProvider
  apiRoute="/api/billing"
  customerEmail={email}
  onCheckoutComplete={(transactionId) => {
    // Redirect to success page
    window.location.href = `/checkout/success?txn=${transactionId}`;
  }}
>

Option 2: Paddle’s successUrl

openCheckout({
  items: [{ priceId: 'pri_xxx' }],
  settings: { 
    successUrl: '/checkout/success',  // Paddle redirects here
  },
});
Note: Subscriptions are created via webhooks. There may be a 1-5 second delay between payment and subscription appearing in the database.

Backend Required

This package requires a backend API that proxies to Paymint. The provider calls:
GET  {apiRoute}/products        → List products
GET  {apiRoute}/subscriptions   → Get subscriptions  
POST {apiRoute}/initialize      → Initialize checkout
POST {apiRoute}/cancel/{id}     → Cancel subscription
POST {apiRoute}/pause/{id}      → Pause subscription
POST {apiRoute}/resume/{id}     → Resume subscription
Use @paymint/server or @paymint/nextjs to create these endpoints.

Security

Backend Developer Responsibilities

When creating your backend API route, ensure:

1. Get Email from Auth Session (CRITICAL)

// ✅ CORRECT - Email from authenticated session
const email = session.user.email;  // From your auth system

// ❌ WRONG - Email from request body
const { email } = req.body;  // Attacker can impersonate any user!

2. Keep API Key Server-Side

// ✅ CORRECT
const paymint = new PaymintServer({
  apiKey: process.env.PAYMINT_API_KEY,  // Server-side only
});

// ❌ WRONG
apiKey: process.env.NEXT_PUBLIC_API_KEY,  // Exposed to client!

What Paymint Handles

  • API key encryption
  • Subscription ownership verification
  • Paddle API security
  • Webhook signature verification

What Your Backend Must Handle

  • Authenticate users before billing operations
  • Get email from auth session (not request body)
  • Keep API key server-side only

License

MIT