Products & prices
Model what you sell and how much it costs.
A product represents what you sell; a price attaches an amount, currency, and billing behavior to it. Keeping them separate lets you change pricing — add a yearly plan, launch in a new currency, run a promotion — without ever touching the catalog item your customers recognize.
The product objectAsk
A product is the durable, customer-facing thing in your catalog: "Pro plan", "Seat license", "API access". It carries identity and metadata, not money. Products are shared across Payments and Billing, so the same prod_ works for a one-off charge and a subscription.
import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });
const product = await vinr.products.create({
name: 'Pro plan',
description: 'Everything in Starter, plus priority support.',
active: true,
metadata: { sku: 'PRO-2026', feature_tier: 'pro' },
}); // "prod_..."Prop
Type
Use metadata for routing and fulfilment keys rather than encoding them in name. Names are user-visible and change; metadata is queryable and stable.
The price objectAsk
A price binds money and cadence to a product. It is immutable in its core fields — amount, currency, and recurrence never change after creation. That immutability is deliberate: an invoice issued last month must always reflect the price it was billed at. To "change a price" you create a new one and migrate. See Versioning prices.
const monthly = await vinr.prices.create({
product: product.id, // "prod_..."
amount: 2000, // €20.00 — integer minor units
currency: 'EUR',
recurring: { interval: 'month' },
nickname: 'Pro / monthly EUR', // internal label, never shown to customers
}); // "price_..."Prop
Type
One-time vs. recurring pricesAsk
The presence of the recurring field is the only switch between the two kinds.
- One-time — no
recurringblock. Used for payment links, invoice items, and single charges (setup fees, hardware, credits). - Recurring — set
recurring.intervaltoday,week,month, oryear. The price becomes attachable to a subscription, which generates an invoice each cycle.
// A €49.00 onboarding fee billed once.
const setupFee = await vinr.prices.create({
product: product.id,
amount: 4900,
currency: 'EUR',
});// €200.00 / year — interval_count lets you do "every 3 months", etc.
const yearly = await vinr.prices.create({
product: product.id,
amount: 20000,
currency: 'EUR',
recurring: { interval: 'year', interval_count: 1 },
});// Bill by reported usage instead of a fixed quantity.
const metered = await vinr.prices.create({
product: product.id,
amount: 5, // €0.05 per unit
currency: 'EUR',
recurring: { interval: 'month', usage_type: 'metered' },
});See Usage-based billing for reporting mbu_ records.
Multiple currenciesAsk
A single product can carry many prices, including the same plan in different currencies. Create one price per currency and let your checkout pick the one matching the customer's locale or country.
const prices = await Promise.all([
vinr.prices.create({ product: product.id, amount: 2000, currency: 'EUR', recurring: { interval: 'month' } }),
vinr.prices.create({ product: product.id, amount: 1800, currency: 'GBP', recurring: { interval: 'month' } }),
vinr.prices.create({ product: product.id, amount: 2200, currency: 'USD', recurring: { interval: 'month' } }),
]);A subscription bills in exactly one currency — the currency of its first price. You cannot mix currencies on one subscription. To move a customer between currencies, cancel and recreate, or migrate at renewal.
Versioning pricesAsk
Because prices are immutable, raising the cost of "Pro / monthly EUR" means creating a new price and deciding what happens to existing subscribers. A common, low-friction pattern:
Create the new price
Add price_v2 to the same product. Existing subscriptions keep billing on the old price, so nothing changes for current customers yet.
const priceV2 = await vinr.prices.create({
product: product.id,
amount: 2500, // €25.00 — the new rate
currency: 'EUR',
recurring: { interval: 'month' },
nickname: 'Pro / monthly EUR (v2)',
});Point new sales at v2
Update checkout, payment links, and signup flows to reference priceV2.id. New customers onboard at the new rate immediately.
Deactivate the old price
Set active: false on the original so it can no longer be attached to new subscriptions — without disturbing the customers already on it.
await vinr.prices.update(oldPrice.id, { active: false });Migrate existing subscribers when ready
Move current subscriptions to priceV2 on their next renewal. VINR prorates the change and emits subscription.updated so you can notify customers.
Keep a stable metadata.plan_key (e.g. pro_monthly) across price versions. Your code keys off the plan, not the specific price_ id, which makes migrations a data change rather than a deploy.
Next stepsAsk
Subscriptions
Attach prices to a recurring relationship.
Usage-based billing
Bill metered prices from reported usage.
Create a subscription
A runnable end-to-end guide.
Last updated on