Convert OIG/SAM from one-time $299/yr to recurring $79/month (card+ACH only) - the first real recurring-billing product in the system. Exclusion screening is a *monthly* federal obligation, so recurring monitoring fits the requirement and is the biggest valuation lever (vs a one-time annual run). Catalog (single source of truth): - service-catalog.ts: add billing_interval + allowed_methods to ComplianceService; oig-sam-screening -> 7900c, billing_interval:"month", allowed_methods:[card,ach], name "(Monthly Monitoring)". - gen-service-catalog.py + check-service-catalog-drift.py: carry/guard the two new fields; regenerate site catalog. Checkout (api/src/routes/checkout.ts): - mode:"subscription" with recurring price_data when billing_interval is set; surcharge absorbed for recurring (clean $79/mo); server-side METHOD_NOT_ALLOWED re-validation against allowed_methods. - ensureColumns + migration 100: compliance_orders.stripe_subscription_id, bundle_upsell_sent_at (+ subscription index). Webhooks (api/src/routes/webhooks.ts): - record stripe_subscription_id on checkout.session.completed (subscription mode). - invoice.paid (subscription_cycle only) -> re-dispatch screening for the cycle; invoice.payment_failed -> admin alert + first-failure customer nudge; customer.subscription.deleted -> mark order cancelled. (API 2026-03-25 moved the subscription link to invoice.parent.subscription_details.subscription.) Fulfillment: - job_server.py: pass recurring_cycle/invoice_id into the order. - npi_provider.py: OIG handler labels renewal cycles "[Monthly cycle]" + re-screen note; bundle action runs only the FIRST screening + flags the $79/mo upsell. Bundle land-and-expand: - Provider Compliance Bundle now includes only the first OIG/SAM screening (was giving away $948/yr of monitoring inside an $899 bundle). - new worker scripts/workers/bundle_upsell.py (+ pw-bundle-upsell timer): ~3 weeks after a paid bundle, emails the customer to continue $79/mo monitoring; dedup via bundle_upsell_sent_at; skips customers who already have an OIG/SAM order. Surfaces updated to $79/mo: PaymentStep (filters methods, "Billed every month, cancel anytime"), order pages, healthcare index, npi-compliance-check tool (also fixed stale $699 bundle drift -> $899), hc_oig_screening + hc_compliance_bundle emails. Docs: billing.md gains a "Stripe-native Subscriptions" section + a reality-check banner (Adyen/ERPNext-gateway model documented there is NOT live; Stripe is the real rail). Fixed run-migrations.yml container name bug (performancewest-postgres-1 -> performancewest-api-postgres-1, overridable). Tests: api/tests/recurring-subscription.test.ts (28 assertions) covers catalog gating, method validation, surcharge suppression, recurring line-item build, invoiceSubscriptionId extraction, renewal-cycle gating. tsc clean; site build clean; catalog drift OK. Manual deploy step: enable invoice.paid, invoice.payment_failed, customer.subscription.deleted on the Stripe webhook endpoint.
40 lines
1.6 KiB
Text
40 lines
1.6 KiB
Text
---
|
|
import Base from "../../layouts/Base.astro";
|
|
import VerticalOrderHeader from "../../components/VerticalOrderHeader.astro";
|
|
import Wizard from "../../components/intake/Wizard.astro";
|
|
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
|
|
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
|
|
|
|
const slug = "oig-sam-screening";
|
|
const steps = INTAKE_MANIFEST[slug];
|
|
const meta = SERVICE_META[slug];
|
|
const title = meta ? `Order ${meta.name}` : "Order";
|
|
const description = "Monthly OIG LEIE and SAM exclusion screening for you and your staff, with an audit-ready compliance certificate each month. $79/month, cancel anytime.";
|
|
---
|
|
|
|
<Base title={title} description={description}>
|
|
<main>
|
|
<section class="pw-order-intro">
|
|
<h1>{meta?.name}</h1>
|
|
<p class="pw-desc">{description}</p>
|
|
</section>
|
|
|
|
<VerticalOrderHeader vertical="healthcare" />
|
|
<Wizard service_slug={slug} steps={steps ?? ["npi-intake", "review", "payment"]} title={meta?.name ?? slug} />
|
|
</main>
|
|
<script>
|
|
// Hide price + tax on intake pages — if the user is here, they've
|
|
// already paid via the order page or batch checkout.
|
|
const price = document.getElementById("pw-price");
|
|
const tax = document.getElementById("pw-tax-notice");
|
|
if (price) price.style.display = "none";
|
|
if (tax) tax.style.display = "none";
|
|
</script>
|
|
</Base>
|
|
|
|
<style>
|
|
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
|
|
.pw-order-intro { margin-bottom: 1.5rem; }
|
|
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
|
|
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
|
|
</style>
|