mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 14:37:26 +00:00
feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002) (#102)
* feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002) Replaces the $250/week retainer with a $400/hour rate billed against actual hours, invoiced through CoinPay after the client approves them. A 10-hour minimum engagement replaces the week as the unit of commitment. The weekly price lived in 12 places, not the 3 the PRD listed: the front-page Hire Us section, the Top-Level Pages list, /hire-us metadata, /pricing (metadata, two FAQ answers, rate bullet), /about, llms.txt, skill.md, and the Hire Us form success message. Metered billing rather than a committed weekly block, because the old "recurring CoinPay invoice" copy documented a mechanic that never existed: /api/payments/create makes a single one-shot payment, not a subscription. - coinpay-checkout derives amount_usd from hours x 400 instead of a hardcoded 250, validates hours as quarter-hour increments at or above the minimum, and returns 422 before calling CoinPay on bad input. Payment metadata carries billing/hours/rate_usd_per_hour in place of interval. - project-request returns a rate, billing mode, and minimum; no amount exists until hours are approved. - CoinPay config block documents COINPAY_RATE_USD_PER_HOUR / COINPAY_BILLING / COINPAY_MINIMUM_HOURS instead of a weekly amount and interval. - New real /terms route replacing the SPA stub: what is billable, the approve-then-invoice flow, the minimum, cancellation on one week's notice, and an explicit clause that existing engagements keep their terms until both sides agree in writing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(mcp): advance prd_next_id expectation to 0003 for PRD 0002 The standards test asserts prd_next_id against the live prd/ directory, so adding prd/0002-hourly-hire-us-rate.md moves the next free id to 0003. This assertion advances with every PRD added to the repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da5f6f8381
commit
eaf0a6162b
16 changed files with 446 additions and 43 deletions
|
|
@ -173,18 +173,20 @@ describe("POST /api/hire-us/coinpay-checkout", () => {
|
|||
expect(createCall?.[1]?.headers).toMatchObject({ authorization: "Bearer cp_test_key" });
|
||||
expect(createBody).toMatchObject({
|
||||
business_id: "business-123",
|
||||
amount_usd: 250,
|
||||
amount_usd: 4000,
|
||||
payment_method: "both",
|
||||
currency: "usdc_pol",
|
||||
blockchain: "USDC_POL",
|
||||
description: "LogicSRC Hire Us - $250/week",
|
||||
description: "LogicSRC Hire Us - 10h @ $400/hour",
|
||||
success_url: "https://logicsrc.test/hire-us?payment=success",
|
||||
cancel_url: "https://logicsrc.test/hire-us?payment=cancelled",
|
||||
redirect_url: "https://logicsrc.test/hire-us?payment=coinpay",
|
||||
webhook_url: "https://logicsrc.test/api/webhooks/coinpay",
|
||||
metadata: {
|
||||
product: "logicsrc-hire-us",
|
||||
interval: "week",
|
||||
billing: "metered_hours",
|
||||
hours: 10,
|
||||
rate_usd_per_hour: 400,
|
||||
source: "logicsrc.com/hire-us",
|
||||
buyer_email: "buyer@example.com"
|
||||
}
|
||||
|
|
@ -241,7 +243,71 @@ describe("POST /api/hire-us/coinpay-checkout", () => {
|
|||
const body = await response.json();
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(body.payment.amount_usd).toBe(250);
|
||||
expect(body.payment.amount_usd).toBe(4000);
|
||||
});
|
||||
|
||||
it("prices the checkout from the approved hours", async () => {
|
||||
process.env.COINPAY_API_KEY = "cp_test_key";
|
||||
process.env.COINPAY_API_URL = "https://coinpayportal.example";
|
||||
process.env.COINPAY_BUSINESS_ID = "business-123";
|
||||
process.env.COINPAY_ELIGIBILITY_MERCHANT_ID = "merchant-123";
|
||||
process.env.COINPAY_HIRE_US_BLOCKCHAIN = "USDC_POL";
|
||||
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
|
||||
const url = typeof input === "string" ? input : input.toString();
|
||||
if (url.includes("/api/payments/merchant-eligibility")) {
|
||||
return jsonResponse({ success: true, accepts_card: true, accepts_crypto: true, chains: ["USDC_POL"] });
|
||||
}
|
||||
return jsonResponse({ success: true, payment: { id: "pay_123", amount_usd: 10100 } }, 201);
|
||||
});
|
||||
|
||||
const response = await coinpayCheckout(
|
||||
new NextRequest("http://localhost/api/hire-us/coinpay-checkout", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ hours: 25.25 })
|
||||
})
|
||||
);
|
||||
const body = await response.json();
|
||||
|
||||
const createCall = fetchMock.mock.calls.find(([input]) =>
|
||||
(typeof input === "string" ? input : input.toString()).includes("/api/payments/create")
|
||||
);
|
||||
const createBody = JSON.parse((createCall?.[1]?.body as string) ?? "{}");
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(createBody.amount_usd).toBe(10100);
|
||||
expect(createBody.description).toBe("LogicSRC Hire Us - 25.25h @ $400/hour");
|
||||
expect(createBody.metadata).toMatchObject({ billing: "metered_hours", hours: 25.25, rate_usd_per_hour: 400 });
|
||||
expect(body.payment).toMatchObject({ amount_usd: 10100, hours: 25.25, rate_usd_per_hour: 400 });
|
||||
});
|
||||
|
||||
it("rejects hours below the minimum engagement or off the quarter-hour", async () => {
|
||||
process.env.COINPAY_API_KEY = "cp_test_key";
|
||||
process.env.COINPAY_API_URL = "https://coinpayportal.example";
|
||||
process.env.COINPAY_BUSINESS_ID = "business-123";
|
||||
process.env.COINPAY_ELIGIBILITY_MERCHANT_ID = "merchant-123";
|
||||
|
||||
const fetchMock = vi.spyOn(globalThis, "fetch");
|
||||
|
||||
for (const hours of [9.75, 12.3, "many", -40]) {
|
||||
const response = await coinpayCheckout(
|
||||
new NextRequest("http://localhost/api/hire-us/coinpay-checkout", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ hours })
|
||||
})
|
||||
);
|
||||
const body = await response.json();
|
||||
|
||||
expect(response.status).toBe(422);
|
||||
expect(body).toEqual({
|
||||
success: false,
|
||||
error: "Approved hours must be a quarter-hour increment of at least 10"
|
||||
});
|
||||
}
|
||||
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not create checkout when no payment rail is available", async () => {
|
||||
|
|
@ -290,7 +356,13 @@ describe("POST /api/hire-us/project-request", () => {
|
|||
expect(response.status).toBe(202);
|
||||
expect(body).toMatchObject({
|
||||
success: true,
|
||||
request: { status: "pending_acceptance", amount_usd: 250, interval: "week", invoice: "created_after_acceptance" }
|
||||
request: {
|
||||
status: "pending_acceptance",
|
||||
rate_usd_per_hour: 400,
|
||||
billing: "metered_hours",
|
||||
minimum_hours: 10,
|
||||
invoice: "created_after_acceptance"
|
||||
}
|
||||
});
|
||||
expect(body.request.id).toMatch(/^hire_/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,14 +39,17 @@ test.describe("LogicSRC PWA", () => {
|
|||
await page.goto("/hire-us");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Hire Us", exact: true })).toBeVisible();
|
||||
await expect(page.locator(".price-row strong", { hasText: "$250" })).toBeVisible();
|
||||
await expect(page.getByText("per week")).toBeVisible();
|
||||
await expect(page.locator(".price-row strong", { hasText: "$400" })).toBeVisible();
|
||||
await expect(page.getByText("per hour")).toBeVisible();
|
||||
await expect(page.getByText("Ten-hour minimum engagement")).toBeVisible();
|
||||
await expect(page.getByText("open infrastructure and open specs for AI agent systems")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Request review" })).toBeVisible();
|
||||
await expect(page.getByPlaceholder("you@example.com")).toBeVisible();
|
||||
await expect(page.getByPlaceholder("Describe the agent workflow")).toBeVisible();
|
||||
await expect(page.getByText("without exposing merchant credentials to the browser")).toBeVisible();
|
||||
await expect(page.getByText("COINPAY_PRODUCT=logicsrc-hire-us")).toBeVisible();
|
||||
await expect(page.getByText("COINPAY_RATE_USD_PER_HOUR=400")).toBeVisible();
|
||||
await expect(page.getByText("COINPAY_BILLING=metered_hours")).toBeVisible();
|
||||
await expect(page.getByText("COINPAY_STATUS=pending_acceptance")).toBeVisible();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import { HomeInteractivity } from "@/components/home-interactivity";
|
|||
// scrolled to the matching section. We preserve those URLs (they are canonical
|
||||
// in sitemap.xml) by rendering the same page for each known route and 404ing
|
||||
// anything else.
|
||||
// /about and /docs are now real routes (app/about, app/docs); the rest still
|
||||
// render the homepage SPA scrolled to their section.
|
||||
// /about, /docs, /pricing, and /terms are now real routes (app/about, app/docs,
|
||||
// app/pricing, app/terms); the rest still render the homepage SPA scrolled to
|
||||
// their section.
|
||||
const ROUTE_META: Record<string, { title: string; description: string }> = {
|
||||
openspec: {
|
||||
title: "LogicSRC vs OpenSpec.dev · LogicSRC",
|
||||
|
|
@ -21,9 +22,8 @@ const ROUTE_META: Record<string, { title: string; description: string }> = {
|
|||
},
|
||||
"hire-us": {
|
||||
title: "Hire Us · LogicSRC",
|
||||
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $250/week for accepted work, paid via CoinPay.",
|
||||
description: "Implementation help for LogicSRC, AgentSwarm, and Credential Sharing at $400/hour for accepted work, paid via CoinPay.",
|
||||
},
|
||||
terms: { title: "Terms · LogicSRC", description: "LogicSRC terms of use." },
|
||||
privacy: { title: "Privacy · LogicSRC", description: "LogicSRC privacy notes." },
|
||||
"agent-swarm": {
|
||||
title: "AgentSwarm · LogicSRC",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ export default function AboutPage(): ReactNode {
|
|||
|
||||
<h3>Work with us</h3>
|
||||
<p>
|
||||
Profullstack implements LogicSRC-based systems at $250/week for
|
||||
Profullstack implements LogicSRC-based systems at $400/hour for
|
||||
accepted work, paid via CoinPay. See <a href="/hire-us">Hire Us</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,23 @@ import { choosePaymentRail, fetchMerchantEligibility, parseJson } from "@/lib/co
|
|||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// POST /api/hire-us/coinpay-checkout — create a $250/week CoinPay checkout for
|
||||
// the Hire Us plan, choosing card/crypto/both based on merchant eligibility.
|
||||
// POST /api/hire-us/coinpay-checkout — create a CoinPay checkout for approved
|
||||
// Hire Us hours at $400/hour, choosing card/crypto/both based on merchant
|
||||
// eligibility. Billing is metered: the caller supplies the approved hours and the
|
||||
// amount is derived from them, never a fixed recurring figure.
|
||||
const RATE_USD_PER_HOUR = 400;
|
||||
const MINIMUM_HOURS = 10;
|
||||
|
||||
// Hours are quoted in quarter-hour increments; anything finer is a rounding
|
||||
// artifact rather than a real billing unit.
|
||||
function parseHours(value: unknown): number | null {
|
||||
const hours = typeof value === "number" ? value : Number(value);
|
||||
if (!Number.isFinite(hours) || hours < MINIMUM_HOURS) return null;
|
||||
const quarters = Math.round(hours * 4);
|
||||
if (Math.abs(hours * 4 - quarters) > 1e-9) return null;
|
||||
return quarters / 4;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const apiKey = process.env.COINPAY_API_KEY;
|
||||
const eligibilityApiKey = process.env.COINPAY_ELIGIBILITY_API_KEY || process.env.COINPAY_AGENT_API_KEY || apiKey;
|
||||
|
|
@ -22,6 +37,19 @@ export async function POST(request: NextRequest) {
|
|||
try {
|
||||
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
|
||||
const buyerEmail = typeof body.email === "string" ? body.email.trim().slice(0, 160) : "";
|
||||
const hours = body.hours === undefined ? MINIMUM_HOURS : parseHours(body.hours);
|
||||
|
||||
if (hours === null) {
|
||||
return json(
|
||||
{
|
||||
success: false,
|
||||
error: `Approved hours must be a quarter-hour increment of at least ${MINIMUM_HOURS}`
|
||||
},
|
||||
422
|
||||
);
|
||||
}
|
||||
|
||||
const amountUsdDue = Math.round(hours * RATE_USD_PER_HOUR * 100) / 100;
|
||||
const eligibility = await fetchMerchantEligibility(apiUrl, eligibilityApiKey, eligibilityMerchantId);
|
||||
const paymentRail = choosePaymentRail(eligibility, blockchain);
|
||||
|
||||
|
|
@ -37,18 +65,20 @@ export async function POST(request: NextRequest) {
|
|||
},
|
||||
body: JSON.stringify({
|
||||
business_id: businessId,
|
||||
amount_usd: 250,
|
||||
amount_usd: amountUsdDue,
|
||||
payment_method: paymentRail.method,
|
||||
currency: paymentRail.currency,
|
||||
...(paymentRail.blockchain ? { blockchain: paymentRail.blockchain } : {}),
|
||||
description: "LogicSRC Hire Us - $250/week",
|
||||
description: `LogicSRC Hire Us - ${hours}h @ $${RATE_USD_PER_HOUR}/hour`,
|
||||
success_url: `${publicUrl}/hire-us?payment=success`,
|
||||
cancel_url: `${publicUrl}/hire-us?payment=cancelled`,
|
||||
redirect_url: `${publicUrl}/hire-us?payment=coinpay`,
|
||||
webhook_url: `${publicUrl}/api/webhooks/coinpay`,
|
||||
metadata: {
|
||||
product: "logicsrc-hire-us",
|
||||
interval: "week",
|
||||
billing: "metered_hours",
|
||||
hours,
|
||||
rate_usd_per_hour: RATE_USD_PER_HOUR,
|
||||
source: "logicsrc.com/hire-us",
|
||||
...(buyerEmail ? { buyer_email: buyerEmail } : {})
|
||||
}
|
||||
|
|
@ -70,13 +100,15 @@ export async function POST(request: NextRequest) {
|
|||
}
|
||||
|
||||
const payment = (payload.payment as Record<string, unknown>) || {};
|
||||
const amountUsd = Number(payment.amount_usd ?? payment.amount ?? 250);
|
||||
const amountUsd = Number(payment.amount_usd ?? payment.amount ?? amountUsdDue);
|
||||
return json(
|
||||
{
|
||||
success: true,
|
||||
payment: {
|
||||
id: payment.id,
|
||||
amount_usd: Number.isFinite(amountUsd) ? amountUsd : 250,
|
||||
amount_usd: Number.isFinite(amountUsd) ? amountUsd : amountUsdDue,
|
||||
hours,
|
||||
rate_usd_per_hour: RATE_USD_PER_HOUR,
|
||||
payment_method: payment.stripe_checkout_url ? "card" : paymentRail.method,
|
||||
currency: payment.currency ?? payment.blockchain ?? paymentRail.blockchain ?? paymentRail.currency,
|
||||
crypto_amount: payment.amount_crypto ?? payment.crypto_amount ?? null,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@ import { json } from "@/lib/http";
|
|||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// POST /api/hire-us/project-request — accept a Hire Us project request before a
|
||||
// recurring CoinPay invoice is created (invoice is created after acceptance).
|
||||
// POST /api/hire-us/project-request — accept a Hire Us project request before any
|
||||
// CoinPay invoice is created. Hire Us bills metered hours at $400/hour, so there is
|
||||
// no amount until we accept the project and hours are approved.
|
||||
const RATE_USD_PER_HOUR = 400;
|
||||
const MINIMUM_HOURS = 10;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
|
||||
|
|
@ -20,7 +24,7 @@ export async function POST(request: NextRequest) {
|
|||
id: requestId,
|
||||
contact,
|
||||
project_length: project.length,
|
||||
plan: "250/week",
|
||||
plan: "400/hour",
|
||||
invoice: "pending_acceptance"
|
||||
});
|
||||
|
||||
|
|
@ -30,8 +34,9 @@ export async function POST(request: NextRequest) {
|
|||
request: {
|
||||
id: requestId,
|
||||
status: "pending_acceptance",
|
||||
amount_usd: 250,
|
||||
interval: "week",
|
||||
rate_usd_per_hour: RATE_USD_PER_HOUR,
|
||||
billing: "metered_hours",
|
||||
minimum_hours: MINIMUM_HOURS,
|
||||
invoice: "created_after_acceptance"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function GET(): Response {
|
|||
## Company & legal
|
||||
|
||||
- [About](${SITE_URL}/about): What LogicSRC is and who maintains it (Profullstack, Inc.).
|
||||
- [Hire Us](${SITE_URL}/hire-us): Implementation help at $250/week for accepted LogicSRC work.
|
||||
- [Hire Us](${SITE_URL}/hire-us): Implementation help at $400/hour for accepted LogicSRC work.
|
||||
- [Terms](${SITE_URL}/terms)
|
||||
- [Privacy](${SITE_URL}/privacy)
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { SiteShell } from "@/components/site-shell";
|
|||
export const metadata: Metadata = {
|
||||
title: "Pricing · LogicSRC",
|
||||
description:
|
||||
"LogicSRC the open specification, schemas, SDKs, and CLI are free and open source. Implementation help is $250/week for accepted work, paid via CoinPay.",
|
||||
"LogicSRC the open specification, schemas, SDKs, and CLI are free and open source. Implementation help is $400/hour for accepted work, paid via CoinPay.",
|
||||
alternates: { canonical: "/pricing" },
|
||||
};
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ const FAQ: Array<{ q: string; a: string }> = [
|
|||
},
|
||||
{
|
||||
q: "How does pricing work?",
|
||||
a: "The standard is free. If you want Profullstack to build a LogicSRC-based system for you, implementation work is billed at $250/week for accepted work, paid via a CoinPay recurring invoice.",
|
||||
a: "The standard is free. If you want Profullstack to build a LogicSRC-based system for you, implementation work is billed at $400/hour against actual hours worked, invoiced through CoinPay after you approve them. The minimum engagement is 10 hours.",
|
||||
},
|
||||
{
|
||||
q: "Who is LogicSRC for?",
|
||||
|
|
@ -24,7 +24,7 @@ const FAQ: Array<{ q: string; a: string }> = [
|
|||
},
|
||||
{
|
||||
q: "How do I pay or get started?",
|
||||
a: "Read the docs and adopt the schemas for free, or submit a project through the Hire Us form. Accepted projects are invoiced weekly via CoinPay.",
|
||||
a: "Read the docs and adopt the schemas for free, or submit a project through the Hire Us form. Accepted projects are invoiced for approved hours via CoinPay.",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -61,9 +61,11 @@ export default function PricingPage(): ReactNode {
|
|||
CLI, TUI, and reference plugins are open source.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Implementation — $250/week.</strong> Profullstack builds
|
||||
LogicSRC-based systems for accepted projects, billed weekly via
|
||||
CoinPay. See <a href="/hire-us">Hire Us</a>.
|
||||
<strong>Implementation — $400/hour.</strong> Profullstack builds
|
||||
LogicSRC-based systems for accepted projects, billed against actual
|
||||
hours worked and invoiced via CoinPay once you approve them.
|
||||
Ten-hour minimum engagement. See <a href="/hire-us">Hire Us</a> and{" "}
|
||||
<a href="/terms">Terms</a>.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Base URL: ${SITE_URL}
|
|||
- Read the LogicSRC coordination schemas and conventions.
|
||||
- Compare LogicSRC with OpenSpec.dev.
|
||||
- Request paid implementation help via the Hire Us flow (${SITE_URL}/hire-us),
|
||||
billed at $250/week for accepted work and paid through CoinPay.
|
||||
billed at $400/hour for accepted work and paid through CoinPay.
|
||||
|
||||
## Notes
|
||||
|
||||
|
|
|
|||
128
apps/logicsrc-web/src/app/terms/page.tsx
Normal file
128
apps/logicsrc-web/src/app/terms/page.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { SiteShell } from "@/components/site-shell";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Terms · LogicSRC",
|
||||
description:
|
||||
"Terms of engagement for LogicSRC: the specification and tooling are open source and free; Profullstack implementation work is billed at $400/hour against approved hours, with a 10-hour minimum.",
|
||||
alternates: { canonical: "/terms" },
|
||||
};
|
||||
|
||||
export default function TermsPage(): ReactNode {
|
||||
return (
|
||||
<SiteShell active="Terms">
|
||||
<article className="band" style={{ maxWidth: "48rem" }}>
|
||||
<div className="section-head">
|
||||
<h2>Terms</h2>
|
||||
<p>
|
||||
Two separate things live on this site: an open standard anyone may
|
||||
use, and paid implementation work from Profullstack, Inc. These terms
|
||||
cover both.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="blog-content" style={{ lineHeight: 1.7, marginTop: "1.5rem" }}>
|
||||
<h3>The standard is free</h3>
|
||||
<p>
|
||||
The LogicSRC specification, JSON schemas, SDKs, CLI, TUI, and
|
||||
reference plugins are open source under the project license. There is
|
||||
no license fee, no per-seat charge, and no obligation to hire us in
|
||||
order to implement the standard. Nothing on this page restricts your
|
||||
use of the spec.
|
||||
</p>
|
||||
|
||||
<h3>Rate</h3>
|
||||
<p>
|
||||
Profullstack implementation work is billed at{" "}
|
||||
<strong>$400 per hour</strong>. One rate applies to all
|
||||
implementation work — specs, CLIs, SDKs, MCP resources, APIs, PWAs,
|
||||
and provider-neutral plugin surfaces. There are no tiers, role-based
|
||||
rates, or volume discounts.
|
||||
</p>
|
||||
|
||||
<h3>What is billable</h3>
|
||||
<p>
|
||||
Billable hours are hours spent on your project: design, spec work,
|
||||
implementation, review, debugging, integration, and deployment, plus
|
||||
meetings and written communication about the work. Time is recorded
|
||||
in quarter-hour increments.
|
||||
</p>
|
||||
<p>
|
||||
The following are not billed: the initial scoping conversation,
|
||||
preparing your invoice, and time spent fixing defects in work we have
|
||||
already delivered and you have already paid for.
|
||||
</p>
|
||||
|
||||
<h3>How you are invoiced</h3>
|
||||
<p>
|
||||
Billing is metered against actual hours worked, not a subscription.
|
||||
After we accept a project, we send you a record of hours worked. Once
|
||||
you approve those hours, we issue a CoinPay invoice for exactly that
|
||||
amount at $400/hour. You are never charged for hours you have not
|
||||
seen and approved.
|
||||
</p>
|
||||
<p>
|
||||
Invoices are payable on receipt via CoinPay, by card or by supported
|
||||
cryptocurrency. Work may pause on invoices unpaid after 14 days.
|
||||
</p>
|
||||
|
||||
<h3>Minimum engagement</h3>
|
||||
<p>
|
||||
The minimum engagement is <strong>10 hours</strong>. Engagements
|
||||
smaller than this do not cover the cost of scoping and context, so we
|
||||
will decline them rather than quote them.
|
||||
</p>
|
||||
|
||||
<h3>Cancellation</h3>
|
||||
<p>
|
||||
Either side may end an engagement at any time with one week's
|
||||
written notice. You pay for hours already worked and approved,
|
||||
including hours worked during the notice period; nothing further is
|
||||
owed. We do not bill a cancellation fee and we do not hold unused
|
||||
committed hours. Work product produced by hours you have paid for is
|
||||
yours to keep.
|
||||
</p>
|
||||
|
||||
<h3>Existing engagements</h3>
|
||||
<p>
|
||||
Clients engaged under a prior pricing model keep their existing terms
|
||||
until both sides agree in writing to move to the hourly rate. This
|
||||
page does not reprice work already underway.
|
||||
</p>
|
||||
|
||||
<h3>Estimates</h3>
|
||||
<p>
|
||||
Any estimate of total hours is an estimate, not a fixed-price quote.
|
||||
If the work looks likely to exceed an estimate we will tell you
|
||||
before the additional hours are worked, so you can rescope or stop.
|
||||
</p>
|
||||
|
||||
<h3>Acceptable use</h3>
|
||||
<p>
|
||||
We build auditable, portable systems in the open. We decline work
|
||||
intended to deceive users, evade legal obligations, or produce
|
||||
systems that cannot be inspected by the people they affect.
|
||||
</p>
|
||||
|
||||
<h3>Reference implementations</h3>
|
||||
<p>
|
||||
Reference implementations published under the LogicSRC project exist
|
||||
to prove the specification is usable. They are provided as-is,
|
||||
without warranty, and are not a hosted product or a support
|
||||
commitment. Paid engagements are governed by the terms above, not by
|
||||
the license of any reference implementation.
|
||||
</p>
|
||||
|
||||
<h3>Questions</h3>
|
||||
<p>
|
||||
Submit a project through the <a href="/hire-us">Hire Us</a> form, or
|
||||
see <a href="/pricing">Pricing</a> for a summary. These terms may
|
||||
change; the version on this page at the time your engagement starts
|
||||
is the one that applies to it.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</SiteShell>
|
||||
);
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ export function HomeInteractivity(): null {
|
|||
}
|
||||
|
||||
result.replaceChildren(
|
||||
buildParagraph("Request received. If it is a fit, we will send a $250/week recurring CoinPay invoice.")
|
||||
buildParagraph("Request received. If it is a fit, we will scope the work and invoice approved hours at $400/hour via CoinPay.")
|
||||
);
|
||||
} catch (error) {
|
||||
result.replaceChildren(
|
||||
|
|
|
|||
|
|
@ -69,9 +69,9 @@ const pages = [
|
|||
{ id: "blog", title: "Blog", detail: "Project notes for LogicSRC, AgentSwarm, AgentByte, OpenSpec workflows, and reference implementations." },
|
||||
{ id: "openspec", title: "OpenSpec", detail: "Comparison and compatibility notes for OpenSpec.dev-style repo-local specs, proposals, tasks, and deltas." },
|
||||
{ id: "credential-sharing", title: "Credential Sharing", detail: "Open replacement architecture for portable secret sync across .env, Doppler, Railway variables, GitHub Secrets, and future providers." },
|
||||
{ id: "hire-us", title: "Hire Us", detail: "$250/week LogicSRC work on open infrastructure, specs, AI agent workflows, and reference implementations paid through CoinPay after project acceptance." },
|
||||
{ id: "hire-us", title: "Hire Us", detail: "$400/hour LogicSRC work on open infrastructure, specs, AI agent workflows, and reference implementations paid through CoinPay after project acceptance." },
|
||||
{ id: "about", title: "About", detail: "LogicSRC is the Profullstack open specification project for human and AI agent coordination." },
|
||||
{ id: "terms", title: "Terms", detail: "Draft terms will cover acceptable use, reference implementation boundaries, and hosted-product responsibilities." },
|
||||
{ id: "terms", title: "Terms", detail: "Terms of engagement: the $400/hour rate, what is billable, how approved hours are invoiced, the 10-hour minimum, cancellation, acceptable use, and reference implementation boundaries." },
|
||||
{ id: "privacy", title: "Privacy", detail: "Draft privacy notes will cover telemetry, audit events, identity data, and hosted-product data boundaries." }
|
||||
];
|
||||
|
||||
|
|
@ -358,7 +358,7 @@ logicsrc credentials rollback --run <runId></code></pre>
|
|||
<section id="hire-us" class="band hire-us">
|
||||
<div class="section-head">
|
||||
<h2>Hire Us</h2>
|
||||
<p>$250/week for accepted LogicSRC work using open infrastructure and open specs for AI agent systems.</p>
|
||||
<p>$400/hour for accepted LogicSRC work using open infrastructure and open specs for AI agent systems.</p>
|
||||
</div>
|
||||
<div class="hire-layout">
|
||||
<article class="hire-panel">
|
||||
|
|
@ -366,9 +366,10 @@ logicsrc credentials rollback --run <runId></code></pre>
|
|||
<h3>Open-spec AI agent implementation help</h3>
|
||||
<p>Hire us to turn agent ideas into portable LogicSRC specs, CLIs, SDKs, MCP resources, PWAs, APIs, and provider-neutral plugin workflows. We prioritize auditable contracts, repo-local artifacts, and integrations that can move between model providers and infrastructure.</p>
|
||||
<div class="price-row">
|
||||
<strong>$250</strong>
|
||||
<span>per week</span>
|
||||
<strong>$400</strong>
|
||||
<span>per hour</span>
|
||||
</div>
|
||||
<p class="price-terms">Billed against actual hours worked, invoiced after you approve them. Ten-hour minimum engagement; cancel any time with one week’s notice and pay only for hours already approved. See <a href="/terms">Terms</a>.</p>
|
||||
<form id="project-request-form" class="project-request-form">
|
||||
<label>
|
||||
<span>Contact</span>
|
||||
|
|
@ -395,12 +396,13 @@ logicsrc credentials rollback --run <runId></code></pre>
|
|||
`).join("")}
|
||||
</div>
|
||||
<article id="coinpay-setup" class="coinpay-panel">
|
||||
<h3>CoinPay recurring invoice</h3>
|
||||
<p>After we accept the project, we create a recurring CoinPay invoice for the weekly plan without exposing merchant credentials to the browser.</p>
|
||||
<h3>CoinPay hourly invoice</h3>
|
||||
<p>After we accept the project, we invoice approved hours at $400/hour through CoinPay without exposing merchant credentials to the browser.</p>
|
||||
<pre><code>COINPAY_ORG=profullstack
|
||||
COINPAY_PRODUCT=logicsrc-hire-us
|
||||
COINPAY_AMOUNT_USD=250
|
||||
COINPAY_INTERVAL=week
|
||||
COINPAY_RATE_USD_PER_HOUR=400
|
||||
COINPAY_BILLING=metered_hours
|
||||
COINPAY_MINIMUM_HOURS=10
|
||||
COINPAY_STATUS=pending_acceptance</code></pre>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -484,6 +484,17 @@ pre {
|
|||
font-weight: 800;
|
||||
}
|
||||
|
||||
.price-terms {
|
||||
color: #b5beb2;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.5;
|
||||
margin: -0.5rem 0 0;
|
||||
}
|
||||
|
||||
.price-terms a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
|||
|
|
@ -202,7 +202,8 @@ describe("MCP: OpenPRD", () => {
|
|||
|
||||
it("reports the next free id and the allowed lifecycle moves", async () => {
|
||||
const client = await connect();
|
||||
expect(toolText(await client.callTool({ name: "prd_next_id", arguments: {} }))).toBe("0002");
|
||||
// Asserted against the live prd/ directory, so this advances with every PRD added.
|
||||
expect(toolText(await client.callTool({ name: "prd_next_id", arguments: {} }))).toBe("0003");
|
||||
|
||||
const moves = await client.callTool({ name: "prd_next_statuses", arguments: { ref: "0001" } });
|
||||
const payload = JSON.parse(toolText(moves)) as { status: string; allowedNext: string[] };
|
||||
|
|
|
|||
146
prd/0002-hourly-hire-us-rate.md
Normal file
146
prd/0002-hourly-hire-us-rate.md
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
openprd: "0.2"
|
||||
id: "0002"
|
||||
title: Move Hire Us pricing from a weekly retainer to an hourly rate
|
||||
status: Accepted
|
||||
authors:
|
||||
- anthony@profullstack.com
|
||||
created: 2026-07-28
|
||||
updated: 2026-07-28
|
||||
repo: https://github.com/profullstack/logicsrc
|
||||
implementation: apps/logicsrc-web
|
||||
tags: [pricing, site, billing]
|
||||
---
|
||||
|
||||
## Problem
|
||||
|
||||
The Hire Us surface on logicsrc.com prices standards work at $250/week, paid through a
|
||||
recurring CoinPay invoice after project acceptance. That number reads as a token retainer
|
||||
rather than a rate for senior open-spec implementation work — schemas, CLIs, SDKs, MCP
|
||||
resources, and provider-neutral plugin surfaces. It anchors every inbound conversation at a
|
||||
price that cannot cover the work, and it selects for clients who are shopping on price rather
|
||||
than on the standard.
|
||||
|
||||
The replacement is $400/hour. This is not a bump to an existing hourly number; it is a change
|
||||
of pricing *model*, which touches page copy, the payment configuration, and the invoicing
|
||||
mechanics that currently assume a fixed weekly amount.
|
||||
|
||||
## Goals
|
||||
|
||||
- Every public price on logicsrc.com states one rate, in one unit, with no stale $250/week
|
||||
copy left behind on any surface.
|
||||
- Inbound Hire Us inquiries arrive already anchored to a senior rate, so the pricing
|
||||
conversation is about scope rather than about the number.
|
||||
- Billing can actually execute the new model: an accepted project produces a correct invoice
|
||||
without manual repair.
|
||||
- The change is reversible and auditable — the reasoning survives in the repo, not in a Slack
|
||||
thread.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Repricing the standard itself. LogicSRC schemas, specs, and reference implementations stay
|
||||
open and free; this covers implementation services only.
|
||||
- Building time tracking. Hourly billing needs hours captured, but that is an operational
|
||||
process for now, not a product to build.
|
||||
- Replacing CoinPay or adding a second payment provider.
|
||||
- Publishing a rate card with tiers, discounts, or role-based pricing. One rate, one line.
|
||||
- Migrating anyone currently engaged at $250/week. Handled case by case, not by this PRD.
|
||||
|
||||
## Users
|
||||
|
||||
- **Prospective clients** evaluating whether to hire Profullstack for LogicSRC work — mostly
|
||||
founders and engineering leads arriving from the spec pages, who read the price before they
|
||||
read anything else.
|
||||
- **Profullstack**, as the party quoting, invoicing, and collecting.
|
||||
- **Existing clients** on the weekly plan, who must not be silently repriced.
|
||||
|
||||
## Decisions
|
||||
|
||||
Two questions blocked this PRD at drafting time. Both were resolved before implementation:
|
||||
|
||||
- **The rate is $400/hour, confirmed deliberately.** Against $250/week this is roughly a 64x
|
||||
change at a 40-hour week. The magnitude is the point: the weekly figure was a token retainer,
|
||||
not a rate, and the new number is intended to filter out engagements too small to scope.
|
||||
- **Billing is metered against actual hours, not a committed weekly block.** This matches how
|
||||
the code already works — `/api/payments/create` creates a single one-shot payment, never a
|
||||
recurring subscription, so the previous "recurring invoice" copy documented a mechanic that
|
||||
did not exist. Metered billing also avoids the committed-block failure mode, which is a
|
||||
weekly rate wearing an hourly label. A **10-hour minimum engagement** replaces the week as
|
||||
the unit of commitment.
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1 [P0] The Hire Us section heading states the new rate and unit, replacing "$250/week for
|
||||
accepted LogicSRC work".
|
||||
- R2 [P0] The price display block shows $400 with the unit "per hour".
|
||||
- R3 [P0] The `/hire-us` entry in the Top-Level Pages list is updated; it repeated the weekly
|
||||
figure independently of the section above it.
|
||||
- R4 [P0] The `/hire-us` page body and its meta description are updated.
|
||||
- R5 [P0] The CoinPay configuration block reflects the new model. `COINPAY_AMOUNT_USD=250` and
|
||||
`COINPAY_INTERVAL=week` are both wrong under hourly pricing and are replaced by a rate, a
|
||||
billing mode, and a minimum rather than renumbered.
|
||||
- R6 [P0] A repo-wide search for `250`, `per week`, and `/week` returns no remaining pricing
|
||||
references before the change is considered done.
|
||||
- R7 [P1] The site states what an hour is billed against — metered actual hours, invoiced after
|
||||
the client approves them — so the invoice mechanic is legible before a client asks.
|
||||
- R8 [P1] Minimum engagement and cancellation terms are stated, since removing the weekly
|
||||
cadence also removes the implicit unit of commitment.
|
||||
- R9 [P2] Terms of engagement are documented at `/terms` rather than only in marketing copy.
|
||||
|
||||
Surfaces the original draft did not list, but which carried the weekly price and were therefore
|
||||
in scope for R6: `/pricing` (metadata, two FAQ answers, and the rate bullet), `/about`,
|
||||
`llms.txt`, `skill.md`, the Hire Us form's success message, and both the contract and e2e tests.
|
||||
|
||||
## UX Notes
|
||||
|
||||
The price appears in several places across the front page, `/pricing`, `/about`, and the
|
||||
machine-readable surfaces; they are separate strings and will drift if edited one at a time.
|
||||
Treat the set as one change.
|
||||
|
||||
The CoinPay block is rendered as example configuration, so it reads as documentation of how
|
||||
billing actually works. A weekly interval next to an hourly rate is worse than a stale price —
|
||||
it looks like the system does not do what the copy says.
|
||||
|
||||
The stated rate sits next to what it buys. The existing four capability cards (workflow specs,
|
||||
reference implementations, integration hardening, open infrastructure) already do that work and
|
||||
are unchanged.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `src/lib/page-markup.ts` — section heading, price row, a new `.price-terms` line carrying the
|
||||
minimum and cancellation summary, the CoinPay config block, and the Top-Level Pages entries
|
||||
for both Hire Us and Terms.
|
||||
- `src/app/api/hire-us/coinpay-checkout/route.ts` — the amount is now derived as
|
||||
`hours × $400` rather than hardcoded. Hours are validated as quarter-hour increments at or
|
||||
above the 10-hour minimum, defaulting to the minimum when omitted; invalid hours return 422
|
||||
before any call to CoinPay. Payment metadata carries `billing`, `hours`, and
|
||||
`rate_usd_per_hour` in place of `interval`.
|
||||
- `src/app/api/hire-us/project-request/route.ts` — returns a rate, billing mode, and minimum
|
||||
instead of a fixed `amount_usd`/`interval` pair, since no amount exists until hours are
|
||||
approved.
|
||||
- `src/app/terms/page.tsx` — new real route, replacing the `/terms` stub that rendered the
|
||||
homepage SPA. The stub's entry was removed from the catch-all's `ROUTE_META`.
|
||||
|
||||
## Success Metrics
|
||||
|
||||
- Zero occurrences of the weekly price across the site and repo after the change, verified by
|
||||
grep rather than by reading.
|
||||
- First accepted project under the new model invoices correctly on the first attempt, with no
|
||||
manual adjustment to the CoinPay invoice.
|
||||
- Inbound inquiries that reach a scoping conversation do not open by disputing the rate.
|
||||
- No existing engagement is repriced without explicit agreement.
|
||||
|
||||
## Risks & Open Questions
|
||||
|
||||
- **Hours are not currently tracked.** Metered billing won, and there is no capture mechanism.
|
||||
`/terms` now defines what is billable (project work, meetings, and written communication in
|
||||
quarter-hour increments; not scoping calls, invoicing, or warranty fixes), so the definition
|
||||
is settled even though the tooling is not. Capturing hours is an operational process until it
|
||||
is worth building.
|
||||
- **Client mix will change.** An hourly rate at this level filters out the small experimental
|
||||
engagements the weekly price attracted. This is the intent, recorded here as a decision
|
||||
rather than left to be discovered.
|
||||
- **Existing weekly clients** keep their terms until both sides agree in writing to move, per
|
||||
`/terms`. Whether any active weekly engagements exist is still unknown.
|
||||
- **Author attribution** is assumed from the CoinPay org configuration and should be corrected
|
||||
if wrong.
|
||||
|
|
@ -12,3 +12,4 @@ Status lives in each file's front-matter and is the source of truth:
|
|||
| ID | Title | Status | Tags |
|
||||
| --- | --- | --- | --- |
|
||||
| [0001](./0001-add-logicsrc-openontology-spec.md) | Add the LogicSRC OpenOntology specification | Draft | openontology, ontology, knowledge-graph, agents, mcp, schemas |
|
||||
| [0002](./0002-hourly-hire-us-rate.md) | Move Hire Us pricing from a weekly retainer to an hourly rate | Accepted | pricing, site, billing |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue