Do not key ephemeral per-attempt Stripe creates
Round 2 review (Ruling 17) found "is this a create?" is necessary but not sufficient. The question that matters: can this subject legitimately be created more t...
Keys belong on Stripe creates only, not on mutations
Round 1 review (Ruling 15) found the Critical: a stable idempotency key on an update/cancel/attach/detach is not a safeguard, it is a bug. Those calls already s...
Make every Stripe write idempotent, and fail the build on a new one without a key
A socket timeout on a Stripe write that actually succeeded is indistinguishable, from our side, from one that did not; without an idempotency key the retry crea...
Commit the processed flag with the money write, not ahead of it
markProcessed was REQUIRES_NEW, so it committed processed=true while the handler's money writes were still uncommitted in the suspended outer transaction. JpaTr...
Prove library HQL parses on the Hibernate this service actually runs
kamo-shared-library builds on Boot 3.3.4 / Hibernate 6.5; BillingService runs Boot 3.1.5 / Hibernate 6.2.13. The library's own HQL tests therefore pass vacuousl...
Make a replayed Stripe event a no-op
Adds BillingWebhookEventClaimService on its own bean, with REQUIRES_NEW on every method, and wires it into processEvent ahead of the dispatch switch. A losing c...
Pin the optional signature header and cover all three failure branches for body leaks
Answer Stripe webhooks with the right status and never echo an exception
Pin kamo-shared-library 1.6.0
The library moved to 1.6.0 while every consumer stayed on 1.5.0. Consumer CI builds the library from a fresh clone into a cold ~/.m2 and ci-settings.xml mirrors...
Stop selling add-ons the chosen plan already includes
The catalog has always recorded which add-ons a plan bundles, in **************** — Business and Enterprise carry video conferencing, the AI assistant, the HR s...
Publish the paid conversion for tracked organizations
Both paths that stamp BillingSourceType.PAID now publish marketing.visitor.paid, because hooking only one silently misses half the traffic — AccountSubscription...
Size the pod above its own JVM heap ceiling [skip ci]
The image starts the JVM with -XX:MaxRAMPercentage=70 -XX:+AlwaysPreTouch, so the heap alone may take 70% of the container limit and pre-touch keeps every commi...
Decide platform access with the shared resolver
@PlatformAdminOnly now names the platform right it requires (defaulting to MANAGE_ORGANIZATIONS, which is what this service's platform surface is about) and the...
Carry the renewal date on the coverage and group payloads
Both surfaces now report currentPeriodEnd and cancelAtPeriodEnd. The date is what a cancel confirmation actually needs to say — "you keep your seat until the en...
Cancelling stops future billing and nothing else
Cancel no longer takes an "immediate" flag, and no customer-facing route can ask for one. Passing it true ended the subscription the same day while asking Strip...
One plan per organization, a 25-seat Enterprise ceiling, and a way to stop paying
The plan is now an organization-level decision and nothing else can override it. Letting a self-payer or a group manager pick their own reads like a kindness an...
Let a group's plan be cancelled, and stop double-paying for one person
Three gaps in what shipped last: A group with a plan could never be removed. delete() refuses while a live subscription exists — correct, since the group row i...
Make billing groups work end to end, and stop self-pay charging for nothing
A group can now gather people, buy a plan on its manager's own card, and hand its seats out. None of that existed: the only operations were create, approve and ...
Rebuild against the renamed pay-group table
The entity moved from billing_groups to billing_pay_groups after it turned out the former is a live legacy table with a different shape. This service still hold...
Stop shipping a shared credential as a config default
The platform's shared password was baked in as the fallback for DB_PASSWORD and MINIO_ROOT_PASSWORD, so it lived in this repo and in the built image as well as ...
Tell people their payment failed, and stand the subscription down
The dunning that did not exist. invoice.payment_failed wrote a row and returned: no email, no flag, no state change, and billing.email-service-url was configure...
Let a member buy their own seat, and close a body-parameter hole
Self-pay had no route inside the console. Everything on the billing surface is account-shaped — you must already hold an AccountMember row before Plans & Billin...
Org policy and billing group APIs
Who inside an organization may pay Kamo is now answerable and settable. Both routes are org-scoped and therefore deliberately outside /api/billing/accounts/** a...
Default a new subscription's target org to the caller's
A first purchase has no existing subscription to copy targetOrganizationId from, and requiring it unconditionally is what made creating one from the console imp...
Stop revoking a member's billing access when adding them elsewhere
add() deleted the member's row on any other account to satisfy a global unique that no longer exists. Adding someone to a second billing account now leaves the ...
Buy a block when an opted-in org runs out of room
Runs shortly after the nightly snapshot sweep, because that is what produces the usage figure it decides on — expanding against yesterday's number would buy a b...
Let a catalog price change actually reach Stripe
Stripe Prices are immutable and the mapping holds one row per (org, type, code, cycle) behind a unique key, so once a price existed it could never change. A pri...
Report capacity alongside usage
The storage panel reported bytes stored against nothing at all — no allowance, no percentage, no money — so an owner looking at 400 GB had no way to tell whethe...
Make a first plan purchasable, and return real plan prices
Two things stopped an organization ever buying a first subscription. Plan lists were reachable only through sub.marketId on a subscription the org already held...
Let a sub-org reach the account that pays for it
AccountSubscription deliberately separates the payer from the organization whose members consume the seats, and auto-provisioned accounts sit on the top-level K...
Bill per-unit add-ons by units bought, not by headcount
Threads the new add-on quantity through create and update: a FLAT add-on now takes its Stripe quantity from what the subscription actually bought, so four 10 GB...
Charge what the catalog says, once, for the right quantity
Five defects that all moved real money, plus the sub-resource IDOR beneath them. Billing cycles. ANNUAL prices were sent to Stripe at the stored per-month rate...
Verify god mode against the database, not a forged header
AccountOrgScopeInterceptor short-circuited every org and member check on the raw X-God-Mode-Active header, and kamo-internal's billing proxy copies that header ...
CockroachDB-only SQL that PostgreSQL rejects
CockroachDB spells the text type STRING; PostgreSQL and YugabyteDB use TEXT. 25 casts across native queries were still emitting CAST(x AS STRING) and ::STRING, ...
Rebuild against shared-library @Lob LONGVARCHAR fix
Hibernate's PostgreSQL dialect read @Lob String columns as OIDs via getLong(). Affects notes, contacts, calendar, OAuth tokens, IMAP passwords, email campaign b...
Point at YugabyteDB and use PostgreSQLDialect
CockroachDB has been replaced by YugabyteDB. Connection strings move from cockroachdb-public:26257 to yb-tserver-service:5433, and the Hibernate dialect from **...
Read imaging usage from one deduplicated pass
Follows the shared-library change to one charge per distinct file per organization. Both the summary and the daily snapshot writer take a single imaging pass an...
Rebuild for the generated-previews domain
Picks up the shared-library change that bills thumbnails and tracks to the organization and reads derivative bytes from img_dat_derivatives.
Rebuild for the branding and docs-workspace domains
Picks up the shared-library change that reads both from object_storage_days.
Rebuild for the text message attachment domain
Picks up the shared-library change that measures MMS attachments from the data: URIs stored inline in VOIP_MESSAGES.
Rebuild against the per-category storage domains
Picks up the shared-library change that splits imgs into one domain per ImageAssocType, so call recordings, voicemails, chat attachments, HR resources and marke...
Record the corrected mailbox semantics
EMAIL now measures the mail Kamo actually hosts, attributed per mailbox to the organization that owns it, rather than summing a search index that both missed Ka...
Let a platform admin run the snapshot sweep on demand
The sweep is what makes period removals and lifetime totals for the hard-deleting domains answerable at all, so days lost to an outage are lost permanently unle...
Give each organization a real transaction boundary
StorageSnapshotSweep.catchUp carried @Transactional(REQUIRES_NEW) and was called from sweep() on the same bean. Spring applies @Transactional through a proxy, s...
Report what an organization is storing
The account stats tab has always said "No usage data available for this period", for every organization, because getCurrentUsage() in kamo-internal is a stub re...
Enforce self-or-owner on an account, not just the org boundary
Follow-up to the cross-org gate, now that the rule is decided: inside an organization you may manage your own billing account, and an organization owner may man...
Stop one organization reaching another organization billing
/api/billing/accounts scopes its listing by the X-Org-Id header, but every route beneath /{accountUid} took the id straight from the path and never checked who ...
Retry the shared-library build, which the LAN uplink keeps corrupting
Three builds died today on "Tag mismatch" pulling a jar — and the mirror added an hour ago did not help, because kbservice, mcpgatewayservice and ragservice hit...
Answer "is this sellable" from the app catalog, not the enum
Availability became operator-editable at runtime, so deriving it from ServiceType.getStage() meant publishing MLOS in Apps and Features left it invisible on the...
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
