Why `getOrCreateOrganizationCustomer` Must Run Before the SAM Chat DO Balance Check
The SAM chat Durable Object requires a valid Autumn customer ID to check credit balances, and getOrCreateOrganizationCustomer ensures that ID exists before the balance verification runs to prevent false "insufficient credits" errors.
The open-seo repository implements a credit-gated SAM chat system using Cloudflare Durable Objects and the Autumn billing platform. Before the SamChatAgent can verify if an organization has remaining usage credits, it must first guarantee that a corresponding billing customer exists in Autumn. The getOrCreateOrganizationCustomer function in src/server/billing/subscription.ts serves as this critical prerequisite, creating or validating the customer record that all subsequent balance checks depend upon.
The Autumn Billing Dependency
SAM chat functionality is protected by a usage-credits gate that prevents access when an organization's balance is depleted. This gate relies on the Autumn billing service to retrieve real-time credit balances using the organization's customer ID.
In src/server/billing/subscription.ts, the balance verification assumes the presence of a valid Autumn customer record. The checkUsageCreditsDepleted function (lines 58-66) calls autumn.check({ customerId, ... }) to read current balances. Without a pre-existing customer ID, this call would either error out or return a null balance interpreted as zero credits, incorrectly blocking user access to SAM chat features.
How getOrCreateOrganizationCustomer Works
The getOrCreateOrganizationCustomer function guarantees that a valid billing customer exists before any credit checks occur. Located at lines 34-63 in src/server/billing/subscription.ts, this function performs two critical operations:
- Customer creation or retrieval – It calls
autumn.customers.getOrCreateto either fetch an existing customer or create a new one in the Autumn platform, returning an object with anidproperty that serves as thecustomerIdfor subsequent operations. - Caching – It stores the customer existence validation in KV storage via
env.KV.put, allowing subsequent requests to skip redundant round-trips to the Autumn API.
This caching mechanism ensures that the relatively expensive customer lookup operation happens only once per organization, while guaranteeing that the customerId required by balance checks is always available.
The SAM Chat Credit Gate Execution Flow
The SamChatAgent Durable Object orchestrates SAM chat interactions and enforces credit limits through a specific execution sequence. According to the source code in src/server/features/sam/SamChatAgent.ts and src/server/features/sam/samChatTools.ts, the chat runtime follows this dependency chain:
- Context preparation – The system calls
getOrCreateOrganizationCustomerto obtain a validcustomerId. - Balance verification – Only after securing the customer ID does the code invoke
checkUsageCreditsDepletedto queryautumn.checkfor current usage statistics. - Access gating – If the balance check returns
depleted: true, the chat turn throws anOUT_OF_CREDITSerror; otherwise, it proceeds to deduct credits and process the chat request.
This ordering ensures that the balance check at lines 58-66 of src/server/billing/subscription.ts always operates against a valid customer record, avoiding the false-negative scenario where a missing customer appears as a zero-balance user.
Consequences of Incorrect Ordering
If checkUsageCreditsDepleted were to execute before getOrCreateOrganizationCustomer, the system would encounter a critical failure mode:
- Resolution errors – The
autumn.checkcall would fail to resolve thecustomerId, potentially throwing an API error that disrupts the chat flow. - False zero-balance blocks – More dangerously, Autumn might interpret a missing customer as a zero-balance account, causing the gate to return
depleted: trueand block legitimate users who actually have available credits.
By enforcing the creation of the customer record first, the open-seo codebase ensures that balance checks reflect the organization's true credit status rather than artifacts of missing billing data.
Implementation Example
The following pattern from the open-seo repository demonstrates the correct execution order:
// Step 1: Ensure the org has a billing customer (creates if missing)
import { getOrCreateOrganizationCustomer } from "@/server/billing/subscription";
async function prepareContext(context: BillingCustomerContext) {
// Guarantees a valid Autumn customer ID for later balance checks
const { id: customerId } = await getOrCreateOrganizationCustomer(context);
return { ...context, organizationId: customerId };
}
// Step 2: SAM chat turn – balance gate runs after the above step
import { checkUsageCreditsDepleted } from "@/server/billing/subscription";
async function samChatTurn(context: BillingCustomerContext) {
// Balance check assumes the customer exists
const { depleted, monthlyRemaining } = await checkUsageCreditsDepleted(context);
if (depleted) {
throw new Error("OUT_OF_CREDITS");
}
// …proceed with the chat turn, spend credits, etc.
}
In this implementation, prepareContext must complete successfully before samChatTurn can safely verify credits. The checkUsageCreditsDepleted function relies on the organizationId established by the prior customer creation step to query Autumn's billing API accurately.
Summary
getOrCreateOrganizationCustomercreates or validates the Autumn billing customer and caches the result insrc/server/billing/subscription.ts(lines 34-63).checkUsageCreditsDepletedperforms the credit balance verification in the same file (lines 58-66) and requires a valid customer ID to function correctly.- Execution order matters because missing customer records cause Autumn to return errors or zero balances, resulting in false "out of credits" rejections.
- SAM chat reliability depends on the
SamChatAgentDurable Object calling the customer creation function before any balance gates insamChatTools.ts.
Frequently Asked Questions
What happens if checkUsageCreditsDepleted runs before getOrCreateOrganizationCustomer?
The balance check would either error out due to an unresolvable customerId or return a null balance interpreted as zero credits. This would trigger an immediate "insufficient credits" rejection, blocking users from SAM chat even when they have available usage credits.
Where does the customer ID caching occur in the open-seo codebase?
The caching happens in src/server/billing/subscription.ts within the getOrCreateOrganizationCustomer function (lines 34-63). After creating or retrieving the customer from Autumn, the function stores the customer existence validation in KV storage via env.KV.put, allowing subsequent calls to skip the API round-trip.
Why does SAM chat use a Durable Object for credit checking?
The SamChatAgent Durable Object maintains stateful coordination of chat sessions while enforcing strict credit gates. Using a Durable Object ensures that credit balance checks and deductions happen atomically within the same execution context, preventing race conditions where multiple chat turns might incorrectly pass the balance gate before credits are deducted.
Can the customer creation and balance check be combined into a single function?
While technically possible, separating these concerns improves reliability and performance. The getOrCreateOrganizationCustomer function includes KV caching that persists across requests, whereas checkUsageCreditsDepleted performs real-time balance queries against Autumn's API. Keeping them separate allows the system to cache the customer lookup while always checking fresh balance data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →