Commit graph

4 commits

Author SHA1 Message Date
Paulo Castellano
aacc4390a2 refactor(billing): replace MonthlyCreditsLimit Pennant feature with BillingCycle
Credit allotment is now derived directly from BillingCycle::for($account)->creditAllotment()
instead of a cached Pennant feature, removing the dynamic cache-invalidation footgun
(forgetPlanFeatureCache) that had to be called from every subscription/workspace mutation.
2026-06-22 09:55:03 -03:00
Paulo Castellano
17a091975d fix: merge-readiness — close two billing/network bugs, harden tests
Bugs (both with regression tests):
- OnboardingController::store now guards already-subscribed accounts (mirrors
  index), preventing a second Stripe Checkout / double subscription if a
  subscribed user re-POSTs /onboarding.
- SocialAccountObserver: drop the `platform_user_id != …` clause from the
  creating-time one-per-network check. On create there is no "self" to exclude,
  so it only weakened the rule — the same account connected via two network
  variants (e.g. Instagram standalone + via Facebook, same id) could slip a
  second account into the network. Now any account in the network blocks.

Robustness:
- CreateWorkspace wraps create + member attach + switchWorkspace in a
  transaction (cache-forget / quantity-sync run after), so a partial failure
  can't leave an orphan workspace that inflates the Stripe seat count — covers
  both the signup and the add-workspace paths.

Test honesty & coverage:
- Scope the ten "connect multiple <platform> accounts" tests to self-hosted
  mode (config + name); they only passed because the test env defaults
  SELF_HOSTED=true, and in cloud the one-per-network rule blocks them.
- network_taken popup now has controller-level tests on all six OAuth
  controllers (added Threads, YouTube, LinkedInPage, and a new
  InstagramFacebook test file; LinkedInPage/InstagramFacebook also exercise
  variant collapse).
- Wiring tests that creating/deleting a workspace actually calls
  syncWorkspaceQuantity (guards per-seat billing against silent breakage).
- Strengthen TrialLengthTest to assert the configured length reaches
  trial_ends_at; add a same-id network-variant block test.
2026-06-22 09:26:31 -03:00
Paulo Castellano
0619f2a0f4 refactor: address code review for per-workspace pricing
Bug fix
- Surface the localized "network already connected" message on the
  Facebook/Instagram/InstagramFacebook/LinkedInPage/Threads/YouTube OAuth
  callbacks: catch NetworkAlreadyConnectedException before the generic catch
  so the conflict no longer falls through to a generic error + Log::error.

Scope / dead code
- Remove the orphaned BillingController::checkout() + app.billing.checkout route
  (onboarding starts checkout directly); delete the now-dead DiscordWidget and
  useFeatureAccess composable; prune orphaned i18n keys left by removing the
  plan picker / upgrade dialog / count limits (billing.subscribe.*,
  accounts.limit_reached, workspaces.limit_reached, common.discord.*).
- Drop the unused `plan` prop from the usage page and the unused `label` from
  the onboarding persona payload (labels come from i18n); remove
  Persona::options()/label().

Conventions
- declare(strict_types=1) on the two new migrations.
- Extract autofill validation into AutofillBrandRequest (FormRequest).
- Drop the unused $plan param from AccountPolicy::swapPlan.
- CreateUser: drop the stale config('cashier.trial_days', 7) fallback (now 8).
- Rename LimitEnforcementTest to InvitePermissionTest; use Pest mock() helper in
  WorkspaceQuantitySyncTest; move shared test helpers into Pest.php.
- Memoize BillingCycle window() + subscription lookup.
2026-06-21 21:28:47 -03:00
Paulo Castellano
cbf8fb283c feat: per-workspace pricing, onboarding, and billing overhaul
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
  workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
  on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
  (workspace/social/member) and the legacy plan tiers (single Workspace plan).

Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
  (Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
  (users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
  price. 8-day trial so Stripe displays 7.

Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
  banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.

System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
  (system feature, not the user's usage).

Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
2026-06-21 20:40:03 -03:00