Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions revenue-trial-pricing-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Revenue Trial Pricing Guard

This module is a focused Revenue Infrastructure slice for issue #20. It validates free trials, coupons, annual/volume discounts, and consortium pricing before subscription invoices are released.

The guard is intentionally dependency-free and synthetic-data-only. It does not call Stripe, PayPal, invoice providers, wallets, external APIs, or private billing systems.

## What It Checks

- Reused free trials on the same billing identity
- Trial overruns beyond the configured trial window
- Missing conversion evidence before trial-to-paid billing
- Expired coupons
- Coupon-to-plan mismatches
- Consortium discounts used by accounts that fail institution, domain, or minimum-seat requirements
- Non-stackable coupons combined on one invoice
- Annual, volume, coupon, and consortium discounts exceeding the configured discount cap

## Reviewer Commands

```bash
node revenue-trial-pricing-guard/test.js
node revenue-trial-pricing-guard/demo.js
```

The demo writes reviewer artifacts to `revenue-trial-pricing-guard/artifacts/`:

- `demo-output.json`
- `reviewer-report.md`
- `discount-risk-map.svg`
- `trial-pricing-guard-demo.mp4`

## Issue #20 Mapping

- Tiered subscription billing: evaluates plan-specific coupon eligibility, billing cycles, volume discounts, and consortium pricing.
- Free trials and coupons: blocks trial reuse, expired discounts, missing conversion evidence, and unsafe coupon stacking.
- Institutional pricing: verifies consortium eligibility using synthetic institution IDs, seat thresholds, and email domains.
- Invoice safety: emits deterministic `RELEASE_INVOICE`, `REVIEW_BEFORE_RELEASE`, or `HOLD_INVOICE` decisions before invoice release.
- Privacy and safety: uses synthetic fixtures only, with no real customers, secrets, credentials, payment processors, or external services.

## Why This Is Distinct

Existing same-issue slices cover broad billing ledgers, compute metering, tax/VAT, invoice delivery, quota rollover, compute replay, collections, analytics-seat licensing, and data-product freshness. This guard focuses narrowly on discount integrity for trials, coupons, and consortium pricing before invoices leave the platform.
198 changes: 198 additions & 0 deletions revenue-trial-pricing-guard/artifacts/demo-output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"summary": {
"invoiceDate": "2026-06-04",
"accountCount": 5,
"releaseCount": 1,
"reviewCount": 0,
"holdCount": 4,
"totalAtRiskCents": 4437850,
"totalNetInvoiceCents": 5875796,
"totalAtRiskDollars": 44378.5,
"totalNetInvoiceDollars": 58757.96
},
"accounts": [
{
"accountId": "acct-clean-lab",
"accountName": "Clean Lab Renewal",
"plan": "lab_group",
"baseCents": 758400,
"discountCents": 75840,
"netInvoiceCents": 682560,
"effectiveDiscountPercent": 0.1,
"action": "RELEASE_INVOICE",
"findings": [],
"discountEvents": [
{
"type": "annual",
"id": "annual_cycle",
"amountCents": 75840
}
],
"atRiskCents": 0
},
{
"accountId": "acct-repeat-trial",
"accountName": "Repeat Trial Startup",
"plan": "individual_pro",
"baseCents": 5800,
"discountCents": 1160,
"netInvoiceCents": 4640,
"effectiveDiscountPercent": 0.2,
"action": "HOLD_INVOICE",
"findings": [
{
"severity": "block",
"code": "trial_reuse",
"message": "Free trial was reused after a prior trial on the same billing identity.",
"atRiskCents": 5800
},
{
"severity": "block",
"code": "trial_overrun",
"message": "Trial age is 36 days, above the 30-day policy.",
"atRiskCents": 5800
},
{
"severity": "review",
"code": "missing_conversion_evidence",
"message": "Trial account has no conversion evidence before invoice release.",
"atRiskCents": 1450
},
{
"severity": "review",
"code": "coupon_missing_conversion_evidence",
"message": "Coupon trial_conversion_20 requires conversion evidence before billing.",
"atRiskCents": 580
}
],
"discountEvents": [
{
"type": "coupon",
"id": "trial_conversion_20",
"amountCents": 1160
}
],
"atRiskCents": 13630
},
{
"accountId": "acct-expired-coupon",
"accountName": "Expired Promo Lab",
"plan": "lab_group",
"baseCents": 94800,
"discountCents": 21804,
"netInvoiceCents": 72996,
"effectiveDiscountPercent": 0.23,
"action": "HOLD_INVOICE",
"findings": [
{
"severity": "block",
"code": "expired_coupon",
"message": "Coupon spring_promo_15 expired on 2026-05-15.",
"atRiskCents": 14220
}
],
"discountEvents": [
{
"type": "coupon",
"id": "spring_promo_15",
"amountCents": 14220
},
{
"type": "volume",
"id": "seat_volume",
"amountCents": 7584
}
],
"atRiskCents": 14220
},
{
"accountId": "acct-consortium-mismatch",
"accountName": "Ineligible Consortium Invoice",
"plan": "institutional",
"baseCents": 3528000,
"discountCents": 1587600,
"netInvoiceCents": 1940400,
"effectiveDiscountPercent": 0.45,
"action": "HOLD_INVOICE",
"findings": [
{
"severity": "block",
"code": "consortium_ineligible",
"message": "Coupon consortium_research_30 requires consortium eligibility that this account does not satisfy.",
"atRiskCents": 1058400
},
{
"severity": "review",
"code": "discount_cap_exceeded",
"message": "Effective discount 45% exceeds cap 40%.",
"atRiskCents": 176400
}
],
"discountEvents": [
{
"type": "coupon",
"id": "consortium_research_30",
"amountCents": 1058400
},
{
"type": "annual",
"id": "annual_cycle",
"amountCents": 352800
},
{
"type": "volume",
"id": "seat_volume",
"amountCents": 176400
}
],
"atRiskCents": 1234800
},
{
"accountId": "acct-stack-conflict",
"accountName": "Stacked Discount Consortium",
"plan": "institutional",
"baseCents": 7056000,
"discountCents": 3880800,
"netInvoiceCents": 3175200,
"effectiveDiscountPercent": 0.55,
"action": "HOLD_INVOICE",
"findings": [
{
"severity": "block",
"code": "non_stackable_coupon_combo",
"message": "One or more non-stackable coupons were combined on the same invoice.",
"atRiskCents": 2116800
},
{
"severity": "review",
"code": "discount_cap_exceeded",
"message": "Effective discount 55% exceeds cap 40%.",
"atRiskCents": 1058400
}
],
"discountEvents": [
{
"type": "coupon",
"id": "consortium_research_30",
"amountCents": 2116800
},
{
"type": "coupon",
"id": "accessibility_credit_10",
"amountCents": 705600
},
{
"type": "annual",
"id": "annual_cycle",
"amountCents": 705600
},
{
"type": "volume",
"id": "seat_volume",
"amountCents": 352800
}
],
"atRiskCents": 3175200
}
]
}
25 changes: 25 additions & 0 deletions revenue-trial-pricing-guard/artifacts/discount-risk-map.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions revenue-trial-pricing-guard/artifacts/reviewer-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Trial/Coupon/Consortium Pricing Guard Report

Invoice date: 2026-06-04

## Portfolio Summary

- Accounts evaluated: 5
- Released invoices: 1
- Review holds: 0
- Blocked invoices: 4
- Net invoice amount: $58757.96
- Discount risk detected: $44378.50

## Account Decisions

| Account | Action | Net invoice | At risk | Finding codes |
| --- | --- | ---: | ---: | --- |
| acct-clean-lab | RELEASE_INVOICE | $6825.60 | $0.00 | none |
| acct-repeat-trial | HOLD_INVOICE | $46.40 | $136.30 | trial_reuse, trial_overrun, missing_conversion_evidence, coupon_missing_conversion_evidence |
| acct-expired-coupon | HOLD_INVOICE | $729.96 | $142.20 | expired_coupon |
| acct-consortium-mismatch | HOLD_INVOICE | $19404.00 | $12348.00 | consortium_ineligible, discount_cap_exceeded |
| acct-stack-conflict | HOLD_INVOICE | $31752.00 | $31752.00 | non_stackable_coupon_combo, discount_cap_exceeded |

## Revenue Infrastructure Mapping

- Tiered subscription billing: checks plan-level coupon eligibility, billing cycle discounts, and seat-volume conflicts.
- Free trials and coupons: blocks reused trials, expired coupons, missing trial-conversion evidence, and non-stackable coupon combinations.
- Consortium pricing: verifies institution/domain/seat eligibility before discount release.
- Invoice safety: emits deterministic RELEASE_INVOICE, REVIEW_BEFORE_RELEASE, or HOLD_INVOICE actions before billing leaves the platform.
- Privacy: uses synthetic fixtures only; no payment processor calls, customer identifiers, credentials, or external APIs.
Binary file not shown.
81 changes: 81 additions & 0 deletions revenue-trial-pricing-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use strict";

const fs = require("node:fs");
const path = require("node:path");
const scenarios = require("./fixtures/scenarios.json");
const { centsToDollars, evaluatePortfolio } = require("./guard");

const outputDir = path.join(__dirname, "artifacts");
fs.mkdirSync(outputDir, { recursive: true });

const result = evaluatePortfolio(scenarios);
const jsonPath = path.join(outputDir, "demo-output.json");
fs.writeFileSync(jsonPath, `${JSON.stringify(result, null, 2)}\n`);

const rows = result.accounts
.map((account) => {
const findings = account.findings.map((finding) => finding.code).join(", ") || "none";
return `| ${account.accountId} | ${account.action} | $${centsToDollars(account.netInvoiceCents).toFixed(2)} | $${centsToDollars(account.atRiskCents).toFixed(2)} | ${findings} |`;
})
.join("\n");

const report = [
"# Trial/Coupon/Consortium Pricing Guard Report",
"",
`Invoice date: ${result.summary.invoiceDate}`,
"",
"## Portfolio Summary",
"",
`- Accounts evaluated: ${result.summary.accountCount}`,
`- Released invoices: ${result.summary.releaseCount}`,
`- Review holds: ${result.summary.reviewCount}`,
`- Blocked invoices: ${result.summary.holdCount}`,
`- Net invoice amount: $${result.summary.totalNetInvoiceDollars.toFixed(2)}`,
`- Discount risk detected: $${result.summary.totalAtRiskDollars.toFixed(2)}`,
"",
"## Account Decisions",
"",
"| Account | Action | Net invoice | At risk | Finding codes |",
"| --- | --- | ---: | ---: | --- |",
rows,
"",
"## Revenue Infrastructure Mapping",
"",
"- Tiered subscription billing: checks plan-level coupon eligibility, billing cycle discounts, and seat-volume conflicts.",
"- Free trials and coupons: blocks reused trials, expired coupons, missing trial-conversion evidence, and non-stackable coupon combinations.",
"- Consortium pricing: verifies institution/domain/seat eligibility before discount release.",
"- Invoice safety: emits deterministic RELEASE_INVOICE, REVIEW_BEFORE_RELEASE, or HOLD_INVOICE actions before billing leaves the platform.",
"- Privacy: uses synthetic fixtures only; no payment processor calls, customer identifiers, credentials, or external APIs.",
""
].join("\n");

fs.writeFileSync(path.join(outputDir, "reviewer-report.md"), report);

const barWidth = 620;
const barHeight = 34;
const maxRisk = Math.max(...result.accounts.map((account) => account.atRiskCents), 1);
const bars = result.accounts.map((account, index) => {
const width = Math.round((account.atRiskCents / maxRisk) * barWidth);
const y = 80 + index * 58;
const color = account.action === "RELEASE_INVOICE" ? "#16a34a" : account.action === "REVIEW_BEFORE_RELEASE" ? "#f59e0b" : "#dc2626";
return [
`<text x="36" y="${y - 10}" font-family="Arial" font-size="16" fill="#111827">${account.accountId}</text>`,
`<rect x="36" y="${y}" width="${barWidth}" height="${barHeight}" rx="4" fill="#e5e7eb"/>`,
`<rect x="36" y="${y}" width="${Math.max(width, account.atRiskCents > 0 ? 8 : 0)}" height="${barHeight}" rx="4" fill="${color}"/>`,
`<text x="${barWidth + 56}" y="${y + 23}" font-family="Arial" font-size="15" fill="#111827">$${centsToDollars(account.atRiskCents).toFixed(2)} at risk</text>`
].join("\n");
}).join("\n");

const svg = [
`<svg xmlns="http://www.w3.org/2000/svg" width="900" height="${150 + result.accounts.length * 58}" viewBox="0 0 900 ${150 + result.accounts.length * 58}">`,
'<rect width="100%" height="100%" fill="#f8fafc"/>',
'<text x="36" y="42" font-family="Arial" font-size="26" font-weight="700" fill="#111827">Revenue discount risk map</text>',
`<text x="36" y="66" font-family="Arial" font-size="15" fill="#475569">Blocked invoices: ${result.summary.holdCount} | Review: ${result.summary.reviewCount} | Total at risk: $${result.summary.totalAtRiskDollars.toFixed(2)}</text>`,
bars,
"</svg>"
].join("\n");

fs.writeFileSync(path.join(outputDir, "discount-risk-map.svg"), svg);

console.log(`Wrote ${jsonPath}`);
console.log(report);
Loading