### Set Up Database and Run Application Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md Initialize the database using Docker and apply Prisma migrations. Then, install dependencies and start the development server. The application will be accessible at `http://localhost:3001`. ```bash docker compose up -d npx prisma migrate deploy ``` ```bash npm ci npm run dev ``` -------------------------------- ### Install Dependencies Source: https://github.com/freemius/freemius-js/blob/main/README.md Run this command after forking and cloning the repository to install project dependencies. ```bash npm install ``` -------------------------------- ### Copy Environment Example File Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md Copy the example environment file to `.env` to set up your application's configuration. Fill in the required environment variables, including Freemius pricing IDs. ```bash cp .env.example .env ``` -------------------------------- ### Develop SaaS Kit Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Start the development environment for the `@freemius/saas-kit` package. This command enables rapid iteration during development. ```bash npm run dev --workspace=@freemius/saas-kit ``` -------------------------------- ### Install Freemius Components with Bun Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md Use this command to add Freemius components to your project using Bun. Ensure you have Bun installed. ```bash bun x shadcn@latest add https://shadcn.freemius.com/all.json ``` -------------------------------- ### Install Freemius Components with npx Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md Use this command to add Freemius components to your project using npm. Ensure you have Node.js and npm installed. ```bash npx shadcn@latest add https://shadcn.freemius.com/all.json ``` -------------------------------- ### Install Freemius SDK and Dependencies Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Install the Freemius SDK along with Zod for schema validation. Requires Node.js 18+ or an Edge runtime with Web Crypto and standard fetch APIs. ```bash npm install @freemius/sdk @freemius/checkout zod ``` -------------------------------- ### Install Freemius Components with PNPM Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md Use this command to add Freemius components to your project using PNPM. Ensure you have Node.js and PNPM installed. ```bash pnpm dlx shadcn@latest add https://shadcn.freemius.com/all.json ``` -------------------------------- ### Install Freemius Components with Yarn Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/README.md Use this command to add Freemius components to your project using Yarn. Ensure you have Node.js and Yarn installed. ```bash yarn dlx shadcn@latest add https://shadcn.freemius.com/all.json ``` -------------------------------- ### Develop SDK with Watch Mode Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Start the development server for the `@freemius/sdk` package with watch mode enabled. Changes to the code will automatically trigger a rebuild. ```bash npm run dev:sdk ``` -------------------------------- ### Set up Webhook Listener with Node.js HTTP Server Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Listen for and securely process webhook events, such as license creation. This example demonstrates setting up a listener and integrating it with a Node.js HTTP server. Ensure the server is configured to handle POST requests to the '/webhook' endpoint. ```typescript import { createServer } from 'node:http'; const listener = freemius.webhook.createListener(); listener.on('license.created', async ({ objects: { license } }) => { // Persist or sync license state in your datastore console.log('license.created', license.id); }); const server = createServer(async (req, res) => { if (req.url === '/webhook' && req.method === 'POST') { await freemius.webhook.processNodeHttp(listener, req, res); } else { res.statusCode = 404; res.end('Not Found'); } }); server.listen(3000, () => { console.log('Webhook listener active on :3000'); }); ``` -------------------------------- ### Freemius Webhook Listener Setup Source: https://context7.com/freemius/freemius-js/llms.txt Initializes a Freemius webhook listener to handle various events like subscription creation, license extension, cancellation, and expiration. Requires importing the freemius SDK instance. ```typescript // app/api/webhook/route.ts - Webhook handler import { freemius } from '@/lib/freemius'; const listener = freemius.webhook.createListener(); listener.on('subscription.created', async ({ objects: { user, license, subscription } }) => { // New subscription - ensure entitlement exists const purchase = await freemius.purchase.retrievePurchase(license.id!); if (purchase) { await db.entitlements.upsert(purchase.toEntitlementRecord()); } }); listener.on('license.extended', async ({ data }) => { // Subscription renewed - update expiration await db.entitlements.updateExpiration(data.license_id, data.to); }); listener.on('subscription.cancelled', async ({ data }) => { // Mark as cancelled (keep access until expiration) await db.entitlements.markCancelled(data.license_id); }); listener.on('license.expired', async ({ data }) => { // Access expired - revoke features await db.entitlements.markExpired(data.license_id); }); export async function POST(request: Request) { return freemius.webhook.processFetch(listener, request); } ``` -------------------------------- ### Setup CheckoutProvider in Root Layout Source: https://context7.com/freemius/freemius-js/llms.txt Wrap your application with CheckoutProvider in your root layout file (e.g., app/layout.tsx or _app.tsx). This component requires checkout data fetched server-side and an API endpoint for processing. ```tsx // app/layout.tsx or _app.tsx import { CheckoutProvider } from '@/components/saas-kit/checkout-provider'; import { Freemius } from '@freemius/sdk'; // Server-side: Create checkout data async function getCheckoutData() { const freemius = new Freemius({ productId: Number(process.env.FREEMIUS_PRODUCT_ID), apiKey: process.env.FREEMIUS_API_KEY!, secretKey: process.env.FREEMIUS_SECRET_KEY!, publicKey: process.env.FREEMIUS_PUBLIC_KEY!, }); const session = await getSession(); // Your auth session const checkout = await freemius.checkout.create({ user: session?.user, isSandbox: process.env.NODE_ENV !== 'production' }); return checkout.serialize(); } export default async function RootLayout({ children }) { const checkoutData = await getCheckoutData(); return ( { // Custom sync logic console.log('Purchase completed:', purchaseData); }} onBeforeSync={(purchaseData) => { console.log('Starting sync for license:', purchaseData.purchase?.license_id); }} onAfterSync={(serverData) => { console.log('Synced to server:', serverData); // Redirect or update UI }} onError={(error) => { console.error('Checkout error:', error); }} > {children} ); } ``` -------------------------------- ### API Route for Customer Portal Data Source: https://context7.com/freemius/freemius-js/llms.txt Set up an API route to handle customer portal data retrieval and actions. This example demonstrates fetching portal data by email and handling subscription cancellations or purchase restorations. ```ts import { Freemius } from '@freemius/sdk'; const freemius = new Freemius({ /* config */ }); export async function GET(request: Request) { const session = await getSession(); if (!session?.user?.email) { return Response.json({ error: 'Unauthorized' }, { status: 401 }); } const portalData = await freemius.customerPortal.retrieveDataByEmail({ email: session.user.email }); return Response.json(portalData); } export async function POST(request: Request) { const url = new URL(request.url); const action = url.searchParams.get('action'); // Handle various portal actions switch (action) { case 'cancel_subscription': const { subscriptionId, feedback, reasons } = await request.json(); const result = await freemius.api.subscription.cancel( subscriptionId, feedback, reasons ); return Response.json(result); case 'restore_purchases': const session = await getSession(); const purchases = await freemius.purchase.retrievePurchasesByEmail( session.user.email ); // Sync each purchase to your database for (const purchase of purchases) { await db.entitlements.upsert(purchase.toEntitlementRecord()); } return Response.json(purchases.map(p => p.toData())); default: return Response.json({ error: 'Unknown action' }, { status: 400 }); } } ``` -------------------------------- ### Create Hosted Checkout URL and Overlay Options Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Use this to generate a hosted checkout URL for redirects or email links, or to retrieve options for embedding the checkout modal on your frontend. Ensure the `@freemius/checkout` package is installed for frontend usage. ```typescript const checkout = await freemius.checkout.create(); checkout.setCoupon({ code: 'SAVE10' }); checkout.setTrial('paid'); const hostedUrl = checkout.getLink(); // Redirect user or generate email link const overlayOptions = checkout.getOptions(); // Serialize & send to frontend for modal embed ``` -------------------------------- ### Retrieve Subscription for a License Source: https://context7.com/freemius/freemius-js/llms.txt Gets the subscription details associated with a given license ID. Requires a valid license ID. ```typescript const subscription = await freemius.api.license.retrieveSubscription(licenseId); if (subscription) { console.log(`Billing Cycle: ${subscription.billing_cycle}`); console.log(`Next Payment: ${subscription.next_payment}`); console.log(`Renewal Amount: $${subscription.renewal_amount}`); } ``` -------------------------------- ### Create Checkout Session API Source: https://context7.com/freemius/freemius-js/llms.txt Handles GET requests to create a new checkout session using the Freemius SDK. It retrieves user session and entitlement information to pre-fill checkout details. ```typescript // app/api/checkout/route.ts - Checkout API import { freemius } from '@/lib/freemius'; export async function GET(request: Request) { const session = await getSession(); const entitlement = await getUserEntitlement(session?.user?.email); const checkout = await freemius.checkout.create({ user: session?.user, licenseId: entitlement?.fsLicenseId, // For upgrades isSandbox: process.env.NODE_ENV !== 'production' }); return Response.json(checkout.serialize()); } ``` -------------------------------- ### Process Webhook in Next.js App Router Source: https://context7.com/freemius/freemius-js/llms.txt Provides an example of how to process incoming webhook requests within a Next.js App Router route handler (app/webhook/route.ts). ```typescript // Next.js App Router (app/webhook/route.ts) export async function POST(request: Request) { return await freemius.webhook.processFetch(listener, request); } ``` -------------------------------- ### Retrieve Serializable Purchase Data Source: https://context7.com/freemius/freemius-js/llms.txt Get purchase data as a plain object, suitable for use in environments like Next.js server components where class instances might not be directly serializable. ```typescript const purchaseData = await freemius.purchase.retrievePurchaseData(licenseId); ``` -------------------------------- ### Initialize Freemius SDK Instance Source: https://context7.com/freemius/freemius-js/llms.txt Set up the server-side Freemius SDK instance using environment variables for product ID, API keys, and public keys. Ensure all necessary environment variables are defined. ```typescript // lib/freemius.ts - Server-side SDK instance import { Freemius } from '@freemius/sdk'; export const freemius = new Freemius({ productId: Number(process.env.FREEMIUS_PRODUCT_ID), apiKey: process.env.FREEMIUS_API_KEY!, secretKey: process.env.FREEMIUS_SECRET_KEY!, publicKey: process.env.FREEMIUS_PUBLIC_KEY!, }); ``` -------------------------------- ### SDK Initialization Source: https://context7.com/freemius/freemius-js/llms.txt Initialize the Freemius SDK with your product credentials. Ensure that your product ID, API key, secret key, and public key are securely stored as environment variables. ```APIDOC ## SDK Initialization Initialize the Freemius SDK with your product credentials from the Freemius Developer Dashboard. The SDK requires product ID, API key, secret key, and public key stored securely as environment variables. ```typescript import { Freemius } from '@freemius/sdk'; // Initialize with environment variables const freemius = new Freemius({ productId: Number(process.env.FREEMIUS_PRODUCT_ID), apiKey: process.env.FREEMIUS_API_KEY!, secretKey: process.env.FREEMIUS_SECRET_KEY!, publicKey: process.env.FREEMIUS_PUBLIC_KEY!, }); // SDK provides access to these services: // freemius.api - Direct API client for users, licenses, subscriptions, payments // freemius.checkout - Checkout URL/options builder // freemius.purchase - Purchase and subscription retrieval // freemius.webhook - Webhook listener and processors // freemius.pricing - Pricing data retrieval // freemius.entitlement - Entitlement validation helpers // freemius.customerPortal - Customer portal data and actions ``` ``` -------------------------------- ### Build Packages Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md Commands to build all packages, specific packages, or build the SDK in watch mode for development. ```bash # Build all packages npm run build ``` ```bash # Build specific package npm run build:sdk npm run build:saas-kit ``` ```bash # Build SDK in watch mode during development npm run dev:sdk ``` -------------------------------- ### Initialize Freemius SDK Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Initialize the Freemius SDK using your product ID, API key, secret key, and public key. Store these credentials in environment variables. ```env FREEMIUS_PRODUCT_ID=12345 FREEMIUS_API_KEY=... FREEMIUS_SECRET_KEY=... FREEMIUS_PUBLIC_KEY=... ``` ```typescript import { Freemius } from '@freemius/sdk'; export const freemius = new Freemius({ productId: Number(process.env.FREEMIUS_PRODUCT_ID), apiKey: process.env.FREEMIUS_API_KEY!, secretKey: process.env.FREEMIUS_SECRET_KEY!, publicKey: process.env.FREEMIUS_PUBLIC_KEY!, }); ``` -------------------------------- ### Retrieve Active Subscriptions for a User Source: https://context7.com/freemius/freemius-js/llms.txt Get only the active subscriptions for a given user ID. This helps in managing ongoing services and renewals. ```typescript const subscriptions = await freemius.purchase.retrieveSubscriptions(userId); subscriptions.forEach(sub => { console.log(`Subscription ${sub.subscriptionId}: $${sub.renewalAmount}/${sub.billingCycle}`); }); ``` -------------------------------- ### Get Active Entitlement for a User Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Retrieve the active entitlement for a given user by querying their entitlements and then using the `getActive` function. This is useful for checking subscription status. ```typescript async function getActiveEntitlement(userId: number) { const entitlements = await db.entitlement.query({ userId, type: 'subscription' }); return freemius.entitlement.getActive(entitlements); } ``` -------------------------------- ### Build and Publish to npm Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md Executes the build process and publishes the packages to npm. ```bash npm run release ``` -------------------------------- ### Get User Entitlement Status Source: https://context7.com/freemius/freemius-js/llms.txt Retrieves the active entitlement for a given user email by querying the database and then using the Freemius SDK. This function is crucial for feature gating. ```typescript // lib/entitlements.ts - Feature gating export async function getUserEntitlement(email: string) { const entitlements = await db.entitlements.findByEmail(email); return freemius.entitlement.getActive(entitlements); } ``` -------------------------------- ### Build SaaS Kit Registry Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Build the registry for the `@freemius/saas-kit` package. This command is typically used as part of the build process for the SaaS kit. ```bash npm run build --workspace=@freemius/saas-kit ``` -------------------------------- ### Create a Changeset Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md Use this command to create a new changeset file after making changes that require a version bump. It will prompt for package selection, change type, and a summary. ```bash npx changeset ``` -------------------------------- ### Handle Multiple License Events Source: https://context7.com/freemius/freemius-js/llms.txt Demonstrates handling multiple event types ('license.created', 'license.extended') with a single handler by passing an array of event names. ```typescript // Handle multiple events with same handler listener.on(['license.created', 'license.extended'], async (event) => { console.log(`License event: ${event.type}`); }); ``` -------------------------------- ### Create and Configure Freemius Checkout Source: https://context7.com/freemius/freemius-js/llms.txt Use this to create a basic or advanced checkout. Configure user details, plan, pricing, coupons, and appearance. The `isSandbox` option is useful for testing. ```typescript const checkout = await freemius.checkout.create({ user: { email: 'customer@example.com', name: 'John Doe' }, planId: '1234', isSandbox: process.env.NODE_ENV !== 'production' }); const checkoutUrl = checkout.getLink(); console.log(`Redirect to: ${checkoutUrl}`); const overlayOptions = checkout.getOptions(); ``` ```typescript const advancedCheckout = await freemius.checkout.create(); advancedCheckout .setUser({ email: 'user@example.com', firstName: 'Jane', lastName: 'Smith' }, true) .setPlan('premium-plan-id') .setPricing('pricing-id') .setQuota(5) // Number of licenses .setCoupon({ code: 'SAVE20', hideUI: false }) .setTrial('paid') // 'free', 'paid', or boolean .setBillingCycle('annual', 'dropdown') .setCurrency('auto', 'usd', true) .setAppearance({ layout: 'horizontal', formPosition: 'right', fullscreen: true }) .setSocialProofing({ showReviews: true, showRefundBadge: true, refundPolicyPosition: 'below_form' }) .setDiscounts({ annual: true, multisite: 'auto', bundle: 'maximize' }) .setLanguage('auto') .setAffiliate(affiliateUserId) .setImage('https://example.com/product-image.png'); const finalUrl = advancedCheckout.getLink(); const finalOptions = advancedCheckout.getOptions(); const serialized = advancedCheckout.serialize(); ``` ```typescript const upgradeCheckout = await freemius.checkout.create({ licenseId: existingLicenseId // Automatically fetches authorization }); const upgradeUrl = upgradeCheckout.getLink(); ``` -------------------------------- ### Retrieve and Process Pricing Data Source: https://context7.com/freemius/freemius-js/llms.txt Retrieve all pricing data for your product, including plans, features, and currency-specific pricing. Iterate through plans and their pricing details, and identify top-up plans. ```typescript // Retrieve all pricing data const pricing = await freemius.pricing.retrieve(); console.log(`Selling unit: ${pricing.sellingUnit.singular}/${pricing.sellingUnit.plural}`); // Iterate through plans pricing.plans.forEach(plan => { console.log(`Plan: ${plan.title} (${plan.name})`); console.log(` Description: ${plan.description}`); console.log(` Featured: ${plan.is_featured}`); console.log(` Features:`); plan.features?.forEach(f => console.log(` - ${f.title}: ${f.value}`)); // Pricing per currency plan.pricing?.forEach(p => { console.log(` ${p.currency}: $${p.monthly_price}/mo, $${p.annual_price}/yr, $${p.lifetime_price} lifetime`); }); }); // Find top-up plan (one-off purchases) const topupPlan = pricing.topupPlan; if (topupPlan) { console.log(`Top-up plan: ${topupPlan.title}`); } // Retrieve with specific top-up plan ID const pricingWithTopup = await freemius.pricing.retrieve(topupPlanId); ``` -------------------------------- ### Reset Local Database Source: https://github.com/freemius/freemius-js/blob/main/packages/saas-kit/CONTRIBUTING.md Use this command to reset the local database to its initial state, which is useful during development. ```bash npx prisma migrate reset ``` -------------------------------- ### Handle License Creation Event Source: https://context7.com/freemius/freemius-js/llms.txt Registers a handler for the 'license.created' event. This handler logs new license details and syncs them to the database. ```typescript listener.on('license.created', async ({ objects: { license, user }, data }) => { console.log(`New license ${data.license_id} created`); console.log(`User: ${user?.email}, Expires: ${data.expiration}`); // Sync to your database await db.licenses.create({ licenseId: license.id, planId: license.plan_id, userId: user?.id, expiration: license.expiration }); }); ``` -------------------------------- ### Retrieve Full Purchase Information Source: https://context7.com/freemius/freemius-js/llms.txt Fetch complete details about a purchase using its license ID. This includes user, license, and subscription data, useful for syncing with your database. ```typescript const purchaseInfo = await freemius.purchase.retrievePurchase(licenseId); if (purchaseInfo) { console.log(`Customer: ${purchaseInfo.email}`); console.log(`Plan: ${purchaseInfo.planId}, Pricing: ${purchaseInfo.pricingId}`); console.log(`Billing Cycle: ${purchaseInfo.billingCycle}`); console.log(`Is Active: ${purchaseInfo.isActive}`); console.log(`Renewal Amount: $${purchaseInfo.renewalAmount} ${purchaseInfo.currency}`); console.log(`Next Renewal: ${purchaseInfo.renewalDate}`); if (purchaseInfo.isSubscription()) { console.log('This is a subscription'); if (purchaseInfo.isAnnual()) console.log('Billed annually'); if (purchaseInfo.isMonthly()) console.log('Billed monthly'); } else if (purchaseInfo.isOneOff()) { console.log('This is a one-time purchase'); } const entitlementRecord = purchaseInfo.toEntitlementRecord({ userId: 'your-internal-user-id', // Add custom fields createdBy: 'webhook' }); await db.entitlements.insert(entitlementRecord); } ``` -------------------------------- ### License API Source: https://context7.com/freemius/freemius-js/llms.txt Retrieve license details, associated subscriptions, and generate checkout upgrade authorization tokens for existing license holders. ```APIDOC ## License API ### Description Retrieve license details, associated subscriptions, and generate checkout upgrade authorization tokens for existing license holders. ### Methods #### Retrieve License by ID ##### Endpoint `GET /api/licenses/{licenseId}` (Conceptual endpoint based on usage) ##### Parameters - **licenseId** (string) - Required - The unique identifier of the license. ##### Response Example ```json { "secret_key": "your_license_key", "plan_id": "plan_123", "pricing_id": "pricing_456", "quota": 100, "expiration": "2024-12-31T23:59:59Z", "is_cancelled": false } ``` #### Retrieve Multiple Licenses ##### Endpoint `GET /api/licenses` (Conceptual endpoint based on usage) ##### Query Parameters - **is_cancelled** (boolean) - Optional - Filter licenses by cancellation status. - **count** (integer) - Optional - Number of licenses to retrieve. - **offset** (integer) - Optional - Offset for pagination. ##### Response Example ```json [ { "secret_key": "license_key_1", "plan_id": "plan_123", "pricing_id": "pricing_456", "quota": 100, "expiration": "2024-12-31T23:59:59Z", "is_cancelled": false } ] ``` #### Retrieve Subscription Associated with a License ##### Endpoint `GET /api/licenses/{licenseId}/subscription` (Conceptual endpoint based on usage) ##### Parameters - **licenseId** (string) - Required - The unique identifier of the license. ##### Response Example ```json { "billing_cycle": "monthly", "next_payment": "2024-07-15T00:00:00Z", "renewal_amount": 29.99 } ``` #### Generate Checkout Upgrade Authorization ##### Endpoint `POST /api/licenses/{licenseId}/checkout-upgrade-authorization` (Conceptual endpoint based on usage) ##### Parameters - **licenseId** (string) - Required - The unique identifier of the license. ##### Response Example ```json { "auth_token": "your_checkout_auth_token" } ``` ``` -------------------------------- ### Create and Configure Webhook Listener Source: https://context7.com/freemius/freemius-js/llms.txt Initializes a webhook listener with an error handler. Optionally, API verification can be used instead of signature headers. ```typescript // Create webhook listener const listener = freemius.webhook.createListener({ onError: async (error) => { console.error('Webhook processing error:', error); // Send to error tracking service }, // Optional: Use API verification instead of signature header // authenticationMethod: WebhookAuthenticationMethod.ApiVerification }); ``` -------------------------------- ### Retrieve License Details Source: https://context7.com/freemius/freemius-js/llms.txt Fetches a specific license by its ID and logs its details. Requires a valid license ID. ```typescript const license = await freemius.api.license.retrieve(licenseId); if (license) { console.log(`License Key: ${license.secret_key}`); console.log(`Plan ID: ${license.plan_id}, Pricing ID: ${license.pricing_id}`); console.log(`Quota: ${license.quota}, Expiration: ${license.expiration}`); console.log(`Is Cancelled: ${license.is_cancelled}`); } ``` -------------------------------- ### Retrieve Multiple Licenses with Filters Source: https://context7.com/freemius/freemius-js/llms.txt Fetches a paginated list of licenses, allowing filtering by properties like 'is_cancelled'. ```typescript const licenses = await freemius.api.license.retrieveMany( { is_cancelled: false }, { count: 50, offset: 0 } ); ``` -------------------------------- ### Use useCheckout Hook to Open Checkout Modal Source: https://context7.com/freemius/freemius-js/llms.txt Utilize the useCheckout hook within any component to trigger the checkout modal. Pass custom parameters to configure the checkout experience, such as plan, pricing, trial status, billing cycle, and coupons. ```tsx 'use client'; import { useCheckout } from '@/components/saas-kit/hooks/checkout'; import { Button } from '@/components/ui/button'; export function UpgradeButton({ planId, pricingId }) { const checkout = useCheckout(); const handleUpgrade = () => { checkout.open({ plan_id: planId, pricing_id: pricingId, trial: false, // Override any checkout options billing_cycle: 'annual', coupon: 'UPGRADE20' }); }; return ( ); } ``` ```tsx // Start a free trial export function TrialButton({ planId }) { const checkout = useCheckout(); return ( ); } ``` ```tsx // Paid trial with card required export function PaidTrialButton({ planId }) { const checkout = useCheckout(); return ( ); } ``` -------------------------------- ### Fetch Pricing Metadata Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Retrieve pricing metadata, including plans and currencies, to build custom pricing tables on your website. This function should be called on the backend. ```typescript async function fetchPricing() { return await freemius.pricing.retrieve(); } ``` -------------------------------- ### Process Webhook in Node.js HTTP Server Source: https://context7.com/freemius/freemius-js/llms.txt Shows how to integrate the webhook listener with a standard Node.js HTTP server, handling POST requests to the '/webhook' endpoint. ```typescript // Node.js HTTP server import { createServer } from 'http'; const server = createServer(async (req, res) => { if (req.url === '/webhook' && req.method === 'POST') { await freemius.webhook.processNodeHttp(listener, req, res); } else { res.statusCode = 404; res.end('Not Found'); } }); server.listen(3000, () => console.log('Webhook listener on :3000')); ``` -------------------------------- ### Listener Management Methods Source: https://context7.com/freemius/freemius-js/llms.txt Demonstrates methods for inspecting and managing registered event handlers on the webhook listener, including retrieving event types, handler counts, and removing handlers. ```typescript // Listener management console.log(`Registered events: ${listener.getRegisteredEventTypes()}`); console.log(`Handler count for license.created: ${listener.getHandlerCount('license.created')}`); listener.off('license.created', myHandler); // Remove specific handler listener.removeAll('license.created'); // Remove all handlers for event type ``` -------------------------------- ### Generate Checkout Upgrade Authorization Source: https://context7.com/freemius/freemius-js/llms.txt Creates an authorization token for existing license holders to upgrade their subscription via checkout. Requires a valid license ID. ```typescript // Generate checkout upgrade authorization for existing customer const authToken = await freemius.api.license.retrieveCheckoutUpgradeAuthorization(licenseId); // Use authToken in checkout builder to pre-authorize upgrade ``` -------------------------------- ### Apply Changesets and Update Versions Source: https://github.com/freemius/freemius-js/blob/main/RELEASE.md Applies all pending changeset files to update package versions, CHANGELOG.md files, and remove consumed changeset files. ```bash npx changeset version ``` -------------------------------- ### Process Purchase and Update Entitlements API Source: https://context7.com/freemius/freemius-js/llms.txt Handles POST requests to process purchases, retrieve purchase details from Freemius, update user information in the database, and store entitlement records. Requires a valid 'action' query parameter. ```typescript export async function POST(request: Request) { const url = new URL(request.url); const action = url.searchParams.get('action'); if (action === 'process_purchase') { const data = await request.json(); const licenseId = data.purchase?.license_id || data.trial?.license_id; const purchase = await freemius.purchase.retrievePurchase(licenseId); if (!purchase) { return Response.json({ error: 'Purchase not found' }, { status: 404 }); } // Get or create user in your system const session = await getSession(); const user = await db.users.upsert({ email: purchase.email, name: `${purchase.firstName} ${purchase.lastName}` }); // Store entitlement await db.entitlements.upsert(purchase.toEntitlementRecord({ userId: user.id })); return Response.json(purchase.toData()); } return Response.json({ error: 'Invalid action' }, { status: 400 }); } ``` -------------------------------- ### Generate OpenAPI Types for SDK Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Generate OpenAPI types specifically for the `@freemius/sdk` package. This command is useful for maintaining type safety with API definitions. ```bash npm run openapi:generate --workspace=@freemius/sdk ``` -------------------------------- ### Handle Subscription Creation Event Source: https://context7.com/freemius/freemius-js/llms.txt Registers a handler for the 'subscription.created' event. This handler logs new subscription details, including user email and renewal amount. ```typescript listener.on('subscription.created', async ({ objects: { user, subscription, license }, data }) => { console.log(`New subscription ${data.subscription_id} for license ${data.license_id}`); console.log(`User: ${user.email}`); console.log(`Amount: $${subscription.renewal_amount}/${subscription.billing_cycle}`); }); ``` -------------------------------- ### Retrieve User Information Source: https://context7.com/freemius/freemius-js/llms.txt Retrieve user details by email or ID. You can also fetch their billing information, active subscriptions, licenses, and payment history. ```typescript // Retrieve user by email const user = await freemius.api.user.retrieveByEmail('customer@example.com'); if (user) { console.log(`User ID: ${user.id}, Name: ${user.first} ${user.last}`); } ``` ```typescript // Retrieve user by ID const userById = await freemius.api.user.retrieve(123456); ``` ```typescript // Get user's billing information const billing = await freemius.api.user.retrieveBilling(user.id!); console.log(`Company: ${billing?.business_name}, Country: ${billing?.country_code}`); ``` ```typescript // Retrieve user's active subscriptions with discounts const subscriptions = await freemius.api.user.retrieveSubscriptions(user.id!, { filter: 'active' }); subscriptions.forEach(sub => { console.log(`Subscription ${sub.id}: $${sub.renewal_amount}/${sub.billing_cycle}`); sub.discounts?.forEach(d => console.log(` Discount: ${d.discount}%`)); }); ``` ```typescript // Get user's active licenses const licenses = await freemius.api.user.retrieveLicenses(user.id!, { type: 'active' }); licenses.forEach(license => { console.log(`License ${license.id}: Plan ${license.plan_id}, Expires: ${license.expiration}`); }); ``` ```typescript // Get user's payment history with pagination const payments = await freemius.api.user.retrievePayments(user.id!, {}, { count: 10, offset: 0 }); payments.forEach(payment => { console.log(`Payment ${payment.id}: $${payment.gross} on ${payment.created}`); }); ``` ```typescript // Download invoice PDF const invoiceBlob = await freemius.api.user.retrieveInvoice(user.id!, paymentId); if (invoiceBlob) { // Save or send the PDF blob const buffer = await invoiceBlob.arrayBuffer(); } ``` ```typescript // Update billing information const updatedBilling = await freemius.api.user.updateBilling(user.id!, { business_name: 'Acme Corp', address_street: '123 Main St', address_city: 'New York', address_zip: '10001', country_code: 'US' }); ``` -------------------------------- ### Subscription API Source: https://context7.com/freemius/freemius-js/llms.txt Manage subscriptions including retrieval, cancellation, and applying renewal discount coupons. ```APIDOC ## Subscription API ### Description Manage subscriptions including retrieval, cancellation, and applying renewal discount coupons. ### Methods #### Retrieve Subscription by ID ##### Endpoint `GET /api/subscriptions/{subscriptionId}` (Conceptual endpoint based on usage) ##### Parameters - **subscriptionId** (string) - Required - The unique identifier of the subscription. ##### Response Example ```json { "id": "sub_abc", "is_active": true, "gateway": "stripe", "initial_amount": 49.99, "renewal_amount": 49.99 } ``` #### Retrieve Multiple Subscriptions ##### Endpoint `GET /api/subscriptions` (Conceptual endpoint based on usage) ##### Query Parameters - **is_active** (boolean) - Optional - Filter subscriptions by active status. - **count** (integer) - Optional - Number of subscriptions to retrieve. ##### Response Example ```json [ { "id": "sub_abc", "is_active": true, "gateway": "stripe", "initial_amount": 49.99, "renewal_amount": 49.99 } ] ``` #### Cancel Subscription ##### Endpoint `DELETE /api/subscriptions/{subscriptionId}` (Conceptual endpoint based on usage) ##### Parameters - **subscriptionId** (string) - Required - The unique identifier of the subscription. - **feedback** (string) - Optional - Customer's feedback for cancellation. - **reason_ids** (array of strings) - Optional - IDs of cancellation reasons. ##### Request Body Example ```json { "feedback": "Switching to a competitor product", "reason_ids": ["too_expensive", "missing_features"] } ``` ##### Response Example ```json { "id": "sub_abc", "message": "Subscription cancelled successfully." } ``` #### Apply Renewal Coupon ##### Endpoint `POST /api/subscriptions/{subscriptionId}/apply-renewal-coupon` (Conceptual endpoint based on usage) ##### Parameters - **subscriptionId** (string) - Required - The unique identifier of the subscription. - **couponId** (string) - Required - The identifier of the coupon to apply. - **log_auto_renew** (boolean) - Required - Whether to log auto-renewal. ##### Request Body Example ```json { "couponId": "SUMMER20", "log_auto_renew": true } ``` ##### Response Example ```json { "id": "sub_abc", "renewal_amount": 39.99, "message": "Coupon applied successfully." } ``` ``` -------------------------------- ### Retrieve All Purchases for a User Source: https://context7.com/freemius/freemius-js/llms.txt Fetch a list of all purchases associated with a specific user ID. Useful for displaying a user's purchase history. ```typescript const userPurchases = await freemius.purchase.retrievePurchases(userId); userPurchases.forEach(p => { console.log(`License ${p.licenseId}: ${p.planId} - Active: ${p.isActive}`); }); ``` -------------------------------- ### Define and Use Entitlement Data Structure Source: https://context7.com/freemius/freemius-js/llms.txt Define your entitlement data structure, ensuring it includes required Freemius fields. Then, retrieve user entitlements from your database and use helper functions to find active subscriptions and calculate their age. ```typescript // Define your entitlement data structure (must include these fields) interface MyEntitlement { fsLicenseId: string; fsPlanId: string; fsPricingId: string; fsUserId: string; type: 'subscription' | 'oneoff'; expiration: Date | string | null; createdAt: Date | string; isCanceled: boolean; // Your custom fields userId: string; tier: string; } // Get entitlements from your database const userEntitlements = await db.entitlements.findByUserId(userId); // Find the single active subscription entitlement const activeEntitlement = freemius.entitlement.getActive(userEntitlements); if (activeEntitlement) { console.log(`Active plan: ${activeEntitlement.fsPlanId}`); console.log(`Expires: ${activeEntitlement.expiration}`); } else { console.log('No active subscription found'); } // Get all active entitlements (if you support multiple subscriptions) const allActive = freemius.entitlement.getActives(userEntitlements); allActive?.forEach(e => console.log(`Active: ${e.fsPlanId}`)); // Calculate subscription age for loyalty features const monthsSubscribed = freemius.entitlement.getElapsedMonth(activeEntitlement); const yearsSubscribed = freemius.entitlement.getElapsedYear(activeEntitlement); if (yearsSubscribed >= 1) { console.log(`Loyal customer! ${yearsSubscribed} years subscribed`); // Grant loyalty benefits } // Get Freemius user info from entitlement (for checkout/portal) const fsUser = freemius.entitlement.getFsUser(activeEntitlement, 'user@example.com'); // { email, id: fsUserId, primaryLicenseId: fsLicenseId } ``` -------------------------------- ### Custom Pricing Table with Control Source: https://context7.com/freemius/freemius-js/llms.txt Implement a custom pricing table using `PricingTable` for more control over plan display and checkout behavior. Fetches pricing data using `usePricingData` hook. Supports 'free', 'paid', or `false` for the `trial` prop. ```tsx 'use client'; import { Subscribe } from '@/components/saas-kit/subscribe'; import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table'; import { usePricingData } from '@/components/saas-kit/hooks/data'; // Custom pricing table with more control export function CustomPricingPage() { const { data, isLoading, error } = usePricingData(null, true); if (isLoading) return
Loading pricing...
; if (error) return
Error loading pricing: {error.message}
; return ( console.log('Checkout opened')} /> ); } ``` -------------------------------- ### Retrieve Purchase Data by License ID Source: https://github.com/freemius/freemius-js/blob/main/packages/sdk/README.md Fetch detailed purchase information using a license ID. This function is intended for backend use to resolve purchase data or validate entitlement status. It returns purchase details or throws an error if the purchase is not found. ```typescript async function retrievePurchase(licenseId: number) { const purchase = await freemius.purchase.retrievePurchase(licenseId); if (!purchase) throw new Error('Purchase not found'); return purchase; } const purchase = await retrievePurchase(123456); if (purchase) { db.entitlement.insert(purchase.toEntitlementRecord()); } ``` -------------------------------- ### Display Pricing Plans with Subscribe Component Source: https://context7.com/freemius/freemius-js/llms.txt Use the Subscribe component for a simple display of pricing plans with automatic checkout integration. It fetches pricing data automatically. Track checkout initiation with the `onCheckout` callback. ```tsx 'use client'; import { Subscribe } from '@/components/saas-kit/subscribe'; import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table'; import { usePricingData } from '@/components/saas-kit/hooks/data'; // Simple subscribe component (fetches pricing automatically) export function PricingPage() { return (

Choose Your Plan

{ // Track checkout initiated analytics.track('checkout_started'); }} >

All plans include a 14-day money-back guarantee

); } ``` -------------------------------- ### Handle License Cancellation Event Source: https://context7.com/freemius/freemius-js/llms.txt Registers a handler for the 'license.cancelled' event. This handler logs the cancellation and marks the license as cancelled in the database. ```typescript listener.on('license.cancelled', async ({ objects: { license }, data }) => { console.log(`License ${data.license_id} cancelled`); await db.licenses.update(data.license_id, { isCancelled: true }); }); ``` -------------------------------- ### Clean Build Artifacts Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Remove generated build files and artifacts from the project. This is useful for ensuring a clean build environment. ```bash npm run clean ``` -------------------------------- ### Individual Pricing Card Component Source: https://context7.com/freemius/freemius-js/llms.txt Render a single pricing plan as a card using the `PricingTableItem` component. This is useful for highlighting a featured plan. The `plan` object structure is defined within the component. ```tsx 'use client'; import { Subscribe } from '@/components/saas-kit/subscribe'; import { PricingTable, PricingTableItem } from '@/components/saas-kit/pricing-table'; import { usePricingData } from '@/components/saas-kit/hooks/data'; // Individual pricing card export function FeaturedPlanCard() { const plan = { id: 'pro-plan', pricing_id: 'pro-monthly', title: 'Professional', description: 'Best for growing teams', price: '$49', featured: true, features: [ { title: 'Unlimited projects', value: '' }, { title: 'Priority support', value: '24/7' }, { title: 'Advanced analytics', value: '' }, { title: 'Team collaboration', value: 'Up to 10 members' } ] }; return ( console.log('Pro plan selected')} /> ); } ``` -------------------------------- ### Run Linting Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Perform code linting to enforce coding standards and identify potential issues. The `lint:fix` command attempts to automatically resolve linting errors. ```bash npm run lint ``` ```bash npm run lint:fix ``` -------------------------------- ### Run Type Checking Source: https://github.com/freemius/freemius-js/blob/main/CONTRIBUTING.md Execute TypeScript type checking for the entire project. This script ensures code quality and catches type-related errors. ```bash npm run typecheck ``` -------------------------------- ### Retrieve Subscription Details Source: https://context7.com/freemius/freemius-js/llms.txt Fetches a specific subscription by its ID and logs its details. Requires a valid subscription ID. ```typescript // Retrieve subscription by ID const subscription = await freemius.api.subscription.retrieve(subscriptionId); if (subscription) { console.log(`Status: ${subscription.is_active ? 'Active' : 'Inactive'}`); console.log(`Gateway: ${subscription.gateway}`); console.log(`Initial Amount: $${subscription.initial_amount}`); console.log(`Renewal Amount: $${subscription.renewal_amount}`); } ``` -------------------------------- ### Find Purchases by Email Source: https://context7.com/freemius/freemius-js/llms.txt Search for purchases using a customer's email address. Provides methods to retrieve all purchases or just subscriptions by email. ```typescript const purchasesByEmail = await freemius.purchase.retrievePurchasesByEmail('customer@example.com'); const subscriptionsByEmail = await freemius.purchase.retrieveSubscriptionsByEmail('customer@example.com'); ``` -------------------------------- ### API Route for Checkout Processing Source: https://context7.com/freemius/freemius-js/llms.txt Implement a POST API route to handle checkout processing. This endpoint receives purchase data, retrieves full purchase information using the Freemius SDK, and syncs it to your database. ```ts // API route for checkout processing (app/api/checkout/route.ts) import { Freemius } from '@freemius/sdk'; const freemius = new Freemius({ /* config */ }); export async function POST(request: Request) { const url = new URL(request.url); const action = url.searchParams.get('action'); if (action === 'process_purchase') { const purchaseData = await request.json(); const licenseId = purchaseData.purchase?.license_id || purchaseData.trial?.license_id; // Retrieve full purchase info and sync to database const purchase = await freemius.purchase.retrievePurchase(licenseId); if (purchase) { await db.entitlements.upsert(purchase.toEntitlementRecord()); return Response.json(purchase.toData()); } } return Response.json({ error: 'Invalid action' }, { status: 400 }); } ``` -------------------------------- ### Retrieve Multiple Subscriptions with Filters Source: https://context7.com/freemius/freemius-js/llms.txt Fetches a paginated list of subscriptions, allowing filtering by properties like 'is_active'. ```typescript // Retrieve multiple subscriptions with filters const activeSubscriptions = await freemius.api.subscription.retrieveMany( { is_active: true }, { count: 100 } ); ``` -------------------------------- ### Handle License Extension Event Source: https://context7.com/freemius/freemius-js/llms.txt Registers a handler for the 'license.extended' event. It logs the extension details and updates the expiration date in the database. ```typescript listener.on('license.extended', async ({ objects: { license }, data }) => { console.log(`License ${data.license_id} extended from ${data.from} to ${data.to}`); if (data.is_renewal) { console.log('Extended due to subscription renewal'); } await db.licenses.update(data.license_id, { expiration: data.to }); }); ``` -------------------------------- ### Create Reusable Webhook Request Processor Source: https://context7.com/freemius/freemius-js/llms.txt Creates a reusable function to process webhook requests, which can be used in any environment compatible with the Fetch API. ```typescript // Create reusable request processor const processWebhook = freemius.webhook.createRequestProcessor(listener); // Use in any Fetch API compatible environment const response = await processWebhook(request); ``` -------------------------------- ### Handle Subscription Cancellation Event Source: https://context7.com/freemius/freemius-js/llms.txt Registers a handler for the 'subscription.cancelled' event. It logs the subscription ID and any cancellation reasons provided. ```typescript listener.on('subscription.cancelled', async ({ objects: { subscription }, data }) => { console.log(`Subscription ${data.subscription_id} cancelled`); console.log(`Reasons: ${data.reason_ids.join(', ')}`); if (data.reason) console.log(`Other reason: ${data.reason}`); }); ```