annual'
);
-- Psychological Triggers
CREATE TABLE pricing_triggers (
id UUID PRIMARY KEY,
trigger_type VARCHAR(50) NOT NULL, -- 'charm', 'decoy', 'anchor', 'scarcity'
config JSONB NOT NULL, -- e.g., {"decoy_ratio": 1.2, "anchor_offset": 500}
is_active BOOLEAN DEFAULT TRUE
);
-- Pricing Rules (Associates Tiers with Triggers per Segment)
CREATE TABLE pricing_rules (
id UUID PRIMARY KEY,
tier_id UUID REFERENCES pricing_tiers(id),
trigger_id UUID REFERENCES pricing_triggers(id),
user_segment VARCHAR(50), -- 'enterprise', 'startup', 'geo:us'
effective_from TIMESTAMP,
effective_to TIMESTAMP
);
#### 2. Pricing Strategy Engine (TypeScript)
The engine should use the Strategy Pattern to apply triggers. This allows swapping psychological behaviors without modifying core logic.
```typescript
interface PricingContext {
userId: string;
segment: string;
geo: string;
referrer?: string;
}
interface PricingResult {
tierId: string;
displayPrice: string;
actualPriceCents: number;
triggers: string[];
uiHints: Record<string, any>;
}
interface PricingStrategy {
apply(
basePrice: number,
context: PricingContext,
config: any
): PricingResult;
}
class CharmPricingStrategy implements PricingStrategy {
apply(basePrice: number, _context: PricingContext, config: { roundDown?: boolean }): PricingResult {
const adjusted = config.roundDown
? Math.floor(basePrice / 10) * 10 - 1
: basePrice - 1;
return {
triggers: ['charm_pricing'],
uiHints: { priceEnding: adjusted % 10 === 9 ? '9' : '5' },
// ... rest of result
} as PricingResult;
}
}
class DecoyPricingStrategy implements PricingStrategy {
apply(basePrice: number, context: PricingContext, config: { decoyRatio: number }): PricingResult {
const decoyPrice = Math.ceil(basePrice * config.decoyRatio);
return {
triggers: ['decoy_effect'],
uiHints: {
decoyPrice,
highlightTier: 'pro'
},
// ... rest of result
} as PricingResult;
}
}
class PricingEngine {
private strategies: Map<string, PricingStrategy> = new Map();
private cache: Map<string, PricingResult> = new Map();
constructor() {
this.strategies.set('charm', new CharmPricingStrategy());
this.strategies.set('decoy', new DecoyPricingStrategy());
}
async evaluate(
tierId: string,
context: PricingContext,
ruleConfig: any
): Promise<PricingResult> {
// Cache key includes segment and geo to prevent cross-contamination
const cacheKey = `${tierId}:${context.segment}:${context.geo}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}
// Fetch base price from billing service
const basePrice = await this.fetchBasePrice(tierId);
// Apply active strategies
const result: PricingResult = {
tierId,
actualPriceCents: basePrice,
triggers: [],
uiHints: {},
displayPrice: '$0'
};
if (ruleConfig.strategies.includes('charm')) {
const charmResult = this.strategies.get('charm')!.apply(
basePrice,
context,
ruleConfig.charmConfig
);
Object.assign(result, charmResult);
}
// Calculate display price
result.displayPrice = this.formatDisplayPrice(result.actualPriceCents, result.uiHints);
this.cache.set(cacheKey, result);
return result;
}
private formatDisplayPrice(cents: number, hints: any): string {
// Implementation of formatting based on hints
return `$${(cents / 100).toFixed(2)}`;
}
}
3. Architecture Decisions
- Decoupling: The pricing engine must not call the billing API synchronously for every request. Implement a caching layer (Redis) with TTL based on pricing rule versioning. Pricing changes should invalidate the cache via pub/sub.
- Feature Flags: Integrate with a feature flag system (e.g., LaunchDarkly, Unleash). Pricing rules should be gated by flags to allow safe rollout. Example:
flag: pricing_decoy_enabled controls the DecoyPricingStrategy.
- Idempotency: Pricing results must be idempotent per session. Once a user sees a price, that price must be locked for the duration of the checkout flow to prevent "bait and switch" perceptions, which destroy trust. Store the pricing result in the session state.
- Observability: Emit structured events for every pricing evaluation.
pricing_evaluated: tier, segment, triggers_applied, display_price, latency_ms.
pricing_variant_impression: Essential for A/B testing conversion lift per psychological trigger.
Pitfall Guide
-
Hardcoding Prices in UI Components:
- Mistake: Developers embedding price values directly in React/Vue components.
- Impact: Requires code deployment for any price change. Prevents dynamic triggers.
- Fix: Prices must be fetched from the Pricing Engine API. UI components should be dumb renderers of the
PricingResult object.
-
Inconsistent State Across Checkout:
- Mistake: The pricing page shows a discounted price, but the checkout API calculates a different price based on real-time rules.
- Impact: High abandonment rates. Users feel deceived.
- Fix: Implement a pricing lock mechanism. The checkout API must accept a
pricing_token or locked_price validated against the session.
-
Ignoring Localization Psychology:
- Mistake: Applying charm pricing globally without considering currency norms.
- Impact: In some markets, prices ending in 9 signal low quality. In others, round numbers are preferred for B2B.
- Fix: Include
geo and currency in the pricing context. Configure strategies per region.
-
Performance Degradation:
- Mistake: Evaluating complex pricing rules on every page load without caching.
- Impact: Increased latency on pricing pages, hurting SEO and user experience.
- Fix: Use edge caching or CDN for pricing variants. Pre-compute pricing for common segments.
-
Dark Patterns vs. Psychology:
- Mistake: Confusing ethical psychological triggers with deceptive dark patterns (e.g., hidden fees, forced continuity without clear consent).
- Impact: Regulatory risk, brand damage, high churn.
- Fix: Audit all triggers against ethical guidelines. Transparency is paramount. Anchoring is ethical; hiding cancellation flows is not.
-
Lack of Attribution:
- Mistake: Running a pricing test without tracking the specific trigger exposure.
- Impact: Inability to determine if conversion lift came from the price change or the psychological trigger.
- Fix: Tag every pricing impression with the active trigger IDs. Analyze conversion by trigger, not just by tier.
-
Over-Engineering Early Stage:
- Mistake: Building a complex ML-driven pricing engine for an MVP.
- Impact: Wasted engineering resources.
- Fix: Start with static tiers + feature flags. Introduce the engine only when experiment velocity becomes a bottleneck.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Early Stage MVP | Static Tiers + Feature Flags | Speed to market; low complexity; allows basic testing. | Low |
| Growth SaaS ($1M-$10M ARR) | Dynamic Engine + Decoy/Charm Strategies | Optimization needed; engineering capacity available for engine. | Medium |
| Enterprise/Complex | Dynamic Engine + ML Personalization | High volume justifies ML; complex segments require automation. | High |
| B2B Marketplace | Tiered Pricing + Volume Discounts | Psychology less effective; value-based pricing dominates. | Low |
Configuration Template
Use this JSON structure to configure pricing rules in your system. This template supports multiple triggers and segment targeting.
{
"pricing_version": "2.1.0",
"tiers": [
{
"id": "tier_pro",
"base_price_cents": 4900,
"interval": "monthly",
"triggers": [
{
"type": "charm",
"config": { "round_down": true }
},
{
"type": "anchor",
"config": { "reference_price_cents": 7900, "label": "Standard Value" }
}
]
},
{
"id": "tier_enterprise",
"base_price_cents": 15000,
"interval": "monthly",
"triggers": [
{
"type": "decoy",
"config": {
"decoy_tier_id": "tier_pro",
"highlight": true
}
}
]
}
],
"segments": {
"geo:us": {
"rules": {
"tier_pro": ["charm", "anchor"],
"tier_enterprise": ["decoy"]
}
},
"geo:eu": {
"rules": {
"tier_pro": ["charm"],
"tier_enterprise": []
}
}
}
}
Quick Start Guide
- Define Schema: Create the
pricing_tiers and pricing_rules tables in your database. Seed with base tiers.
- Deploy Engine: Implement the
PricingEngine class in your backend service. Add Redis caching for rule evaluation.
- Hook UI: Replace static price components with a
PricingDisplay component that fetches data from /api/pricing/evaluate.
- Enable Trigger: Create a feature flag
charm_pricing_enabled. Set it to true for 50% of traffic.
- Validate: Check analytics dashboard for
pricing_evaluated events. Verify that 50% of users see prices ending in 9. Monitor conversion rate delta.