MonaKiosk in Action / Understanding Polar

Products, Benefits & Customers

Understanding Polar’s data model is essential for effective MonaKiosk usage. This chapter covers the three core concepts: Products, Benefits, and Customers.

Products

A Product represents something customers can purchase. In MonaKiosk:

  • Each paywalled content item creates one product
  • Products have pricing (one-time or subscription)
  • Products are linked to content via metadata

Product Structure

interface Product {
  id: string; // Polar-generated UUID
  name: string; // Display name
  description: string; // Product description
  prices: Price[]; // Pricing options
  benefits: Benefit[]; // What customers receive
  metadata: {
    content_id: string; // MonaKiosk content identifier
    collection: string; // Astro collection name
    pricing_model: string; // "one_time" or "subscription"
    updatedAt: number; // Last sync timestamp
  };
  recurringInterval?: string; // For subscriptions: "month", "year", etc.
}

How MonaKiosk Creates Products

When you add price: 999 to your frontmatter, MonaKiosk:

  1. Generates a content_id from the file path (e.g., blog/premium-article)
  2. Searches Polar for existing product with matching content_id
  3. Creates new product or updates existing one
  4. Attaches benefits (explained below)
// Simplified product creation logic
const product = await polar.products.create({
  name: "Premium Article",
  description: "Access to premium content",
  metadata: {
    content_id: "blog/premium-article",
    collection: "blog",
    pricing_model: "one_time",
  },
  prices: [
    {
      amountType: "fixed",
      priceAmount: 999, // $9.99 in cents
      priceCurrency: "usd",
    },
  ],
});

One-Time vs Subscription

One-time purchase:

---
price: 999 # Customer pays once, has access forever
---

Subscription:

---
price: 999
interval: month # Customer pays $9.99/month for continued access
---

For subscriptions, MonaKiosk sets recurringInterval on the product:

const product = await polar.products.create({
  // ...
  recurringInterval: "month", // or "year", "week", "day"
});

Benefits

A Benefit represents what customers receive after purchase. MonaKiosk creates two types:

1. Custom Benefit

Contains content access information:

interface CustomBenefit {
  type: "custom";
  description: string; // Short description (max 42 chars)
  metadata: {
    content_id: string;
    collection: string;
    title: string;
  };
  properties: {
    note: string; // Private note with content URL
  };
}

The note field contains:

Premium Article description here.

Access your content here: https://yoursite.com/blog/premium-article

2. Downloadables Benefit

For content with attached files:

interface DownloadablesBenefit {
  type: "downloadables";
  description: string;
  metadata: {
    content_id: string;
    collection: string;
    title: string;
  };
  properties: {
    files: string[]; // Array of Polar file IDs
  };
}

Benefit-Product Relationship

erDiagram
    Product ||--o{ Benefit : "has"
    Benefit ||--o{ File : "contains"
    Customer ||--o{ BenefitGrant : "has"
    BenefitGrant }o--|| Benefit : "grants"

    Product {
        string id
        string name
        int price
        string content_id
    }
    Benefit {
        string id
        string type
        string description
    }
    Customer {
        string id
        string email
    }
    BenefitGrant {
        string id
        boolean is_granted
    }

When a customer purchases:

  1. Polar creates a BenefitGrant linking the customer to each benefit
  2. MonaKiosk checks for grants to determine access
  3. If downloadables benefit exists, customer can download files

Customers

A Customer represents someone who has interacted with your Polar-powered content.

Customer Session Flow

sequenceDiagram
    participant User
    participant Site as Your Site
    participant MK as MonaKiosk
    participant Polar

    User->>Site: Visit paywalled content
    MK->>MK: Check session cookie
    alt No session
        MK->>Site: Show preview + sign-in link
        User->>Polar: Sign in / Purchase
        Polar->>Site: Redirect with session token
        MK->>MK: Set session cookies
    else Has session
        MK->>Polar: Validate access
        Polar-->>MK: Access granted/denied
        MK->>Site: Show content or preview
    end

Session Cookies

MonaKiosk stores three cookies:

CookiePurpose
mona_kiosk_sessionPolar customer session token
mona_kiosk_customer_idCustomer’s Polar ID
mona_kiosk_customer_emailCustomer’s email

Access Validation

To check if a customer has access, MonaKiosk:

  1. Gets the customer session token from cookies
  2. Calls Polar’s Customer Portal API
  3. Checks for benefit grants matching the content
// Simplified access check
const grants = await polar.customerPortal.benefitGrants.list({
  customerId: customerId,
  isGranted: true,
});

const hasAccess = grants.some(
  (grant) => grant.benefit.metadata?.content_id === contentId,
);

To reduce API calls, MonaKiosk caches access results:

monaKiosk({
  accessCookieSecret: "your-secret", // Required for signing
  accessCookieTtlSeconds: 1800, // Cache for 30 minutes
  accessCookieMaxEntries: 20, // Max cached content items
});

The access cookie is:

  • HMAC-signed to prevent tampering
  • Automatically refreshed on access
  • Scoped to specific content IDs

Relevant Polar APIs

MonaKiosk uses these Polar SDK methods:

Products API

// List products with metadata filter
polar.products.list({
  organizationId: "...",
  metadata: { content_id: "blog/article" },
});

// Create product
polar.products.create({ name, description, prices, metadata });

// Update product
polar.products.update({ id, productUpdate: { ... } });

// Update product benefits
polar.products.updateBenefits({ id, benefits: ["benefit-id-1", "benefit-id-2"] });

Benefits API

// List benefits
polar.benefits.list({
  organizationId: "...",
  metadata: { content_id: "blog/article" },
});

// Create custom benefit
polar.benefits.create({
  type: "custom",
  description: "Article access",
  metadata: { content_id: "..." },
  properties: { note: "..." },
});

// Create downloadables benefit
polar.benefits.create({
  type: "downloadables",
  description: "Files for article",
  properties: { files: ["file-id-1", "file-id-2"] },
});

Customer Portal API

// Get customer from session token
polar.customerPortal.customers.get({
  customerSessionToken: "token",
});

// List benefit grants
polar.customerPortal.benefitGrants.list({
  customerId: "...",
  isGranted: true,
});

// Get downloadable files
polar.customerPortal.downloadables.list({
  customerId: "...",
  benefitId: "...",
});

Key Takeaways

  1. Products map 1:1 with paywalled content via content_id metadata
  2. Benefits are attached to products — custom for access info, downloadables for files
  3. Customers are identified by session tokens stored in cookies
  4. BenefitGrants link customers to benefits they’ve purchased
  5. MonaKiosk abstracts all API calls — you work with frontmatter, it handles Polar

Next, we’ll apply these concepts to real-world scenarios, starting with paywalled blog posts.