- Shipped
- August 7, 2026 at 5:08 AM UTC
- Author
- Kamo
- Commit
- e6cb8fd
createCustomer saved the Account before writing its addresses, and was not transactional — so when the address insert failed, the half-built Account stayed committed and every retry left another orphan behind. It is now @Transactional, and because the handler catches its own exceptions to shape a response (which suppresses Spring's automatic rollback) it marks the transaction rollback-only explicitly. Also on the customer surface: - primaryMemberId was looked up with a bare findById, so a caller could hand a member of ANOTHER org ownership of, and billing rights over, an account in this one. It is now org-scoped, matching updateCustomerMembers which already did this. - getAddresses and buildAccountSummary built responses with Map.of, which throws on a null value. An account with no primary address is the normal case, so GET /customers/{uid}/addresses was a guaranteed 500. - An unrecognised currency code persisted NULL instead of the org default. - Note authorship came from the request body, so it was forgeable; it now comes from the session. - The whole /customers surface required nothing but a valid session. Any member could create, edit and delete their org's accounts and rewrite AccountTier discounts, including roles not granted VIEW_ACCOUNTS. The **************** rights have existed all along and are now enforced, mirroring the MERGE_ACCOUNTS check already in this file. POST/DELETE /leads/{id}/account had no right check either: a caller holding only VIEW_LEADS could relink or unlink the account on a lead they cannot open, and got back an unmasked LeadDTO. They now carry the same gates as updateLead and mask the response through LeadFieldMask. A missing accountId no longer NPEs into a 400 carrying internal text.