Custom Pricing Logic
While the standard configuration format covers many common pricing scenarios, you might need more complex pricing logic for your specific business needs. This guide explains how to implement custom pricing logic with Prices as Code.
Advanced Pricing Scenarios
Here are some examples of advanced pricing scenarios you might need to implement:
- Tiered pricing based on usage
- Volume discounts
- Region-specific pricing
- Time-limited promotional pricing
- Bundle discounts
- Custom tax handling
- Dynamic pricing based on external factors
Using JavaScript/TypeScript for Dynamic Pricing
When using a JavaScript or TypeScript configuration file, you can include dynamic logic to generate your pricing configuration:
import { Config } from 'prices-as-code';
// Helper function to calculate tiered pricing
function calculateTieredPrice(basePrice: number, tier: number, discount: number = 0.1): number {
return Math.round(basePrice * (1 - (tier - 1) * discount));
}
// Generate products for different regions
function generateRegionalProducts(regions: string[]): any[] {
const products = [];
for (const region of regions) {
products.push({
provider: "stripe",
name: `Pro Plan (${region})`,
description: `Pro plan for ${region} region`,
features: [
"Unlimited projects",
"100GB storage",
"Priority support",
],
highlight: region === 'US', // Highlight US plan
metadata: {
region,
displayOrder: regions.indexOf(region) + 1,
}
});
}
return products;
}
// Dynamic pricing configuration
const regions = ['US', 'EU', 'APAC'];
const basePriceUSD = 1999; // $19.99
const basePriceEUR = 1799; // €17.99
const basePriceAUD = 2499; // A$24.99
const regionCurrencies = {
'US': { currency: 'usd', basePrice: basePriceUSD },
'EU': { currency: 'eur', basePrice: basePriceEUR },
'APAC': { currency: 'aud', basePrice: basePriceAUD },
};
// Generate prices for each region and plan type
const prices = [];
regions.forEach((region) => {
const { currency, basePrice } = regionCurrencies[region];
// Monthly price
prices.push({
provider: "stripe",
name: `Pro Monthly (${region})`,
nickname: `Pro Monthly (${region})`,
unitAmount: basePrice,
currency,
type: "recurring",
recurring: {
interval: "month",
intervalCount: 1,
},
productKey: `pro_plan_${region.toLowerCase()}`,
metadata: {
region,
displayName: `Pro Monthly (${region})`,
}
});
// Annual price (with 20% discount)
prices.push({
provider: "stripe",
name: `Pro Annual (${region})`,
nickname: `Pro Annual (${region})`,
unitAmount: Math.round(basePrice * 10 * 0.8), // 20% off for annual billing
currency,
type: "recurring",
recurring: {
interval: "year",
intervalCount: 1,
},
productKey: `pro_plan_${region.toLowerCase()}`,
metadata: {
region,
displayName: `Pro Annual (${region})`,
savings: "20%",
}
});
});
const config: Config = {
products: [
// Basic product available globally
{
provider: "stripe",
name: "Basic Plan",
description: "For individuals and small teams",
features: ["5 projects", "10GB storage", "Email support"],
highlight: false,
metadata: {
displayOrder: 0,
global: true,
}
},
// Region-specific Pro plans
...generateRegionalProducts(regions),
],
prices: [
// Basic plan prices
{
provider: "stripe",
name: "Basic Monthly",
nickname: "Basic Monthly",
unitAmount: 999,
currency: "usd",
type: "recurring",
recurring: {
interval: "month",
intervalCount: 1,
},
productKey: "basic_plan",
metadata: {
displayName: "Basic Monthly",
}
},
// All region-specific prices generated above
...prices,
],
};
export default config;
This example demonstrates:
- Generating products and prices programmatically
- Creating region-specific pricing
- Applying different currencies and base prices by region
- Calculating annual discounts automatically
- Using metadata to store region information
Using External Data Sources
You can also load pricing data from external sources, such as databases or APIs:
import { Config } from 'prices-as-code';
import fetch from 'node-fetch';
import fs from 'fs/promises';
async function generateConfig() {
// Example: Load base prices from an API
const response = await fetch('https://api.example.com/pricing-data');
const pricingData = await response.json();
// Example: Load regional settings from a JSON file
const regionSettings = JSON.parse(
await fs.readFile('./region-settings.json', 'utf8')
);
// Process and transform the data into our configuration format
const products = [];
const prices = [];
// Process products
for (const productData of pricingData.products) {
products.push({
provider: "stripe",
name: productData.name,
description: productData.description,
features: productData.features,
highlight: productData.isHighlighted,
metadata: {
externalId: productData.id,
category: productData.category,
// Add any other metadata from the API
}
});
// Process prices for this product
for (const priceData of productData.prices) {
// Apply regional pricing if available
const regions = priceData.regions || ['global'];
for (const region of regions) {
const regionConfig = regionSettings[region] || regionSettings.default;
const adjustedAmount = Math.round(
priceData.baseAmount * regionConfig.priceMultiplier
);
prices.push({
provider: "stripe",
name: `${priceData.name} (${region})`,
nickname: `${priceData.nickname || priceData.name} (${region})`,
unitAmount: adjustedAmount,
currency: regionConfig.currency,
type: priceData.type,
recurring: priceData.recurring,
productKey: productData.key,
metadata: {
region,
externalId: priceData.id,
// Add any other metadata
}
});
}
}
}
return { products, prices };
}
// Use an async IIFE to generate the configuration
const config = await generateConfig();
export default config;
This approach allows you to:
- Source pricing data from external systems
- Apply dynamic transformations and business logic
- Keep pricing data separate from your code
- Integrate with existing pricing databases or APIs
Tiered and Volume Pricing
For tiered pricing based on usage, you can use Stripe's tiered pricing features:
import { Config } from 'prices-as-code';
const config: Config = {
products: [
{
provider: "stripe",
name: "API Access",
description: "Pay-as-you-go API access",
features: ["Unlimited users", "24/7 support", "99.9% uptime SLA"],
highlight: true,
metadata: {
category: "api",
}
},
],
prices: [
{
provider: "stripe",
name: "API Requests - Tiered",
nickname: "API Requests",
currency: "usd",
type: "recurring",
recurring: {
interval: "month",
intervalCount: 1,
},
productKey: "api_access",
tiersMode: "graduated",
tiers: [
{ upTo: 1000, unitAmount: 0, flatAmount: 0 }, // First 1,000 requests free
{ upTo: 10000, unitAmount: 5 }, // $0.05 per request up to 10,000
{ upTo: 100000, unitAmount: 3 }, // $0.03 per request up to 100,000
{ upTo: "inf", unitAmount: 2 }, // $0.02 per request thereafter
],
metadata: {
displayName: "API Requests",
description: "Pay only for what you use",
}
},
],
};
export default config;
Custom Transformation Functions
You can also create custom transformation functions to preprocess your configuration before it's synchronized:
import { Config, pac } from 'prices-as-code';
// Load base configuration
import baseConfig from './base-pricing.js';
// Custom transformation function
function applySeasonalPromotion(config: Config, discountPercentage: number = 15): Config {
// Create a deep copy of the config
const newConfig = JSON.parse(JSON.stringify(config));
// Apply promotional pricing to all prices
newConfig.prices = newConfig.prices.map(price => {
// Skip prices that already have a promotion
if (price.metadata?.promotion) {
return price;
}
// Calculate the discounted amount
const discountedAmount = Math.round(
price.unitAmount * (1 - discountPercentage / 100)
);
// Create a promotional version of the price
return {
...price,
name: `${price.name} (Holiday Special)`,
nickname: `${price.nickname || price.name} (Holiday Special)`,
unitAmount: discountedAmount,
metadata: {
...(price.metadata || {}),
promotion: 'holiday_2025',
originalPrice: price.unitAmount,
discountPercentage: `${discountPercentage}%`,
}
};
});
// Add a note about the promotion to each product
newConfig.products = newConfig.products.map(product => {
return {
...product,
description: `${product.description} - Now with a ${discountPercentage}% holiday discount!`,
metadata: {
...(product.metadata || {}),
hasPromotion: true,
}
};
});
return newConfig;
}
// Apply the transformation
const config = applySeasonalPromotion(baseConfig, 20);
// Use for synchronization
async function syncPricing() {
const result = await pac({
config, // Use the transformed config directly
providers: [
{
provider: 'stripe',
options: {
secretKey: process.env.STRIPE_SECRET_KEY,
}
}
]
});
console.log('Sync completed!');
}
export default config;
Next Steps
- Learn about Custom Providers to extend the library
- Explore Working with Metadata for enhanced flexibility
- Check out the API Documentation for more advanced features