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:
- Generates a
content_idfrom the file path (e.g.,blog/premium-article) - Searches Polar for existing product with matching
content_id - Creates new product or updates existing one
- 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:
- Polar creates a BenefitGrant linking the customer to each benefit
- MonaKiosk checks for grants to determine access
- 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:
| Cookie | Purpose |
|---|---|
mona_kiosk_session | Polar customer session token |
mona_kiosk_customer_id | Customer’s Polar ID |
mona_kiosk_customer_email | Customer’s email |
Access Validation
To check if a customer has access, MonaKiosk:
- Gets the customer session token from cookies
- Calls Polar’s Customer Portal API
- 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,
);
Access Cookie Caching
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
- Products map 1:1 with paywalled content via
content_idmetadata - Benefits are attached to products — custom for access info, downloadables for files
- Customers are identified by session tokens stored in cookies
- BenefitGrants link customers to benefits they’ve purchased
- 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.