Rerun a webhook event whose first attempt died, and refuse one the SDK cannot read
A losing claim on an unprocessed row always answered 500, so an event whose first attempt failed (a unique-constraint race inside the invoice.created/finalized/...
Only a failed charge starts dunning, and Stripe must agree before dunning acts
invoice.finalized carries status=open with an amount due for every auto-charged invoice before the charge is attempted, and the invoice handler started the deli...
The same-commit-rebuild guard could never fire, and the digest check failed open
Both protections against a deploy that deploys nothing were inert, so the pipeline reported two green jobs while the pods stayed on the previous digest. That is...
A catalog row naming an app this build cannot resolve is not for sale
ServiceTypeConverter stopped throwing on an unknown app id and started resolving it to null, which is what a service must do β the enum ships inside each jar wh...
A price Stripe has never heard of is a fault, not a silence
The reconciler compared mapped prices against the catalogue and returned early whenever nothing was mapped at all. So a term the catalogue had gained since the ...
Charge what the screen says, and refuse rather than pretend
An audit of the Plans & Billing console against live Stripe keys found the seam between the screen and Stripe broken in every direction at once, and silent in a...
Prove the deploy by digest, not by tag
The preceding commit stops `set image` being a silent no-op. This asserts the outcome: after the rollout, the tag is resolved to a digest at the registry and th...
A rebuild of the same commit deployed nothing and reported success
The image is tagged with the commit SHA, so rebuilding the same commit produces an identical image reference. `kubectl set image` then changes nothing, the Depl...
Let a rollout finish what the old pod was doing
Deploys replaced the only pod of each service with nothing to catch the requests in flight. Three settings, applied across the fleet: - preStop sleeps 10s befo...
Stop offering the internal platform-owner plan for sale
listPlansForMarket filtered on isActive alone, and active is not the same as on the menu. OWNER_UNLIMITED ("Platform Owner β Internal") is active, carries no pr...
The platform org is not gated by its own catalog
The platform org was told "Requires the KamoPBX Hosting add-on" on its own phone-server screen, for an add-on it publishes. It owns the plan catalog, the add-o...
Scan the shared-lib email.billing repository package
@EnableJpaRepositories lists packages explicitly, so MailboxBillingDayRepository was invisible and the service refused to start outright β caught by booting it,...
Close the webhook idempotency gap BL0's final review found
Finding 1 (Critical): a losing claim on a redelivered Stripe event only proved a row existed, not that the prior attempt finished. A claimed row left behind by ...
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...
Answer Stripe webhooks with the right status and never echo an exception
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...
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...
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...
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 ...
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 ...
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...
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, ...
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...
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 ...
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...
Validate add-on codes before they can grant an app
active_addon_codes was written straight from the request body. That was harmless while nothing read the column, but it is now an authorization input: an add-on ...
Hide unreleased apps from the Plans & Billing catalog
**************** back the tab where a customer chooses what to subscribe to, so it must not offer a product still in the Not Available list β the same rule the ...
Keep the is-prefix on PlatformAccountSummary's wire names too
Same Lombok/Jackson hazard as PlatformOrgRow: isTopLevel() loses its `is` on serialization, so the DTO put `topLevel`/`platformProduct` on the wire while the fr...
Keep the is-prefix on the wire for isTopLevel/isPlatformProduct
Lombok generates isTopLevel() for a `boolean isTopLevel` field and Jackson strips the `is` prefix, so the live endpoint was serializing `topLevel` and `platform...
Declare security matchers explicitly so the service can start
The service has not deployed since 2026-07-03; every pod since has crash-looped before the context loaded: This method cannot decide whether these patterns a...
Harden the kubectl download against flaky egress [skip ci]
dl.k8s.io over the runner's egress intermittently drops mid-transfer: curl: (56) OpenSSL SSL_read: decryption failed or bad record mac which fails the deploy ...
Serialize Long ids as strings to stop org-id precision loss
Org ids are CockroachDB unique_rowid() values that exceed JS MAX_SAFE_INTEGER. BillingService was serializing them as raw JSON numbers, so kamo-internal's JSON....
Register esign repositories for the meter submission bean
EsignMeterSubmissionService needs EsignUsageRecordRepository, but @EnableJpaRepositories didn't scan com.kamo.z.shared.esign.repos, so the context failed to sta...
Back metered add-on prices with a Billing Meter
Stripe (>= 2025-03-31.basil) rejects the legacy usage_type+aggregate_usage metered model ('metered prices must be backed by meters'), so USAGE_BASED add-on pric...
Add @JsonProperty to is*-prefixed boolean DTO fields
Lombok generates isXxx() is-getters for boolean fields named isXxx. Jackson strips the leading 'is' from is-getters and serializes them as 'xxx' rather than 'is...
Include subscription-targeting accounts in org list
listForOrg now also returns accounts whose subscriptions target the requested org β not just accounts directly owned by it. In the multi-org model the paying Ac...
Use **************** primitive getter
isOrganizationOwner is a primitive boolean on TeamMember, so the Lombok- generated accessor is isOrganizationOwner() not getIsOrganizationOwner(). Fixes compile...
Expose commerce subscription on GET /subscriptions/current
When no Stripe mirror row exists, resolve the org active commerce SubscriptionRecord (e.g. COMP grants), include paymentMethod and plan features. Fixes Apps set...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.