641 Commits

Author SHA1 Message Date
isra el
ef45421150 fix(billing): retain paid plan until period end on cancellation
Polar's subscription.canceled signals a scheduled cancellation (access
continues until the period end), while subscription.revoked is the event
that should actually remove access. The webhook handler was downgrading
immediately on cancel and never handled revoke, so a Pro user dropped to
Free the moment they canceled instead of at their real period end.

- cancelSubscription now records cancelAtPeriodEnd + currentPeriodEnd and
  keeps the subscription active instead of deactivating it
- add revokeSubscription for the real downgrade, wired to the
  subscription.revoked event in the Polar webhook handler
- add controller + service specs covering event dispatch and the
  record-vs-downgrade DB semantics
- add jest moduleNameMapper so specs can resolve src/* path aliases

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:02:39 +03:00
isra el
de8df1c53c fix(devices): register devices as enabled and surface toggle feedback
The 2.8 Kotlin client stopped sending `enabled` on registration, so the
backend created devices with the schema default `enabled: false` ("Disabled"),
which users could not activate. Default new registrations to enabled on the
server (so existing 2.8 clients are fixed without an app update), still gated
by the device-limit check, and send `enabled = true` from onboarding.

Also make the gateway toggle always give feedback: show a success toast on
enable/disable and surface the server's reason (e.g. device-limit 429) on
failure instead of silently snapping back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:26:35 +03:00
isra el
164124d616 feat(billing): update existing Polar subscription on plan change instead of creating a new checkout
A paid subscriber upgrading (pro -> scale) or downgrading (scale -> pro)
previously got a brand-new Polar checkout, ending up with two live Polar
subscriptions and double billing. Now an active paid subscription is
updated in place via Polar's subscription update API.

- store polarSubscriptionId/polarCustomerId/cancelAtPeriodEnd on
  subscriptions (recovered via externalCustomerId for legacy records)
- POST /billing/checkout returns a planChange preview for paid users;
  new POST /billing/change-plan executes it (uncancels a scheduled
  cancellation first, org-default proration, idempotent with webhooks)
- allow monthly<->yearly interval switches; keep ALREADY_ON_PLAN only
  for same plan + same interval; block custom plans (CONTACT_BILLING)
- map Polar 402/403/409 errors to actionable messages; run plan-change
  detection before cached checkout-session reuse
- checkout page shows a confirmation screen before applying the change;
  account page shows a success toast

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:09:00 +03:00
isra el
c846279d41 fix(billing): correctly route webhook events per plan and scope cancellations
- switchPlan now resolves plan strictly by polarProductId (no name fallback when product ID is provided); throws on unknown product ID
- cancelSubscription: new method that deactivates only the subscription matching the cancelled product, instead of wiping all active subscriptions
- Webhook handler: subscription.created/active/updated no longer hardcodes newPlanName='pro'; subscription.cancelled/canceled use cancelSubscription; subscription.revoked commented out

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 18:06:49 +03:00
isra el
fd7e6cac3f feat(billing): introduce Scale plan tier above Pro
Add Scale plan ($29.99/mo, 25k SMS/mo, 15 devices) between Pro and
Custom in the subscription priority chain. Update dashboard upgrade
prompts to surface Scale for Pro users approaching their monthly limit,
and expose Scale upgrade links in subscription-info and account-settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 22:32:13 +03:00
isra el
3f1037373f feat(billing): enforce per-plan device limits and consolidate billingInterval naming
Add deviceLimit to plans (default -1 = unlimited) with per-subscription
customDeviceLimit override, resolved in getEffectiveLimits and exposed
via the usage object. Gateway blocks device creation and disabled to
enabled transitions with 429 once the enabled-device count reaches the
limit; already-enabled devices are never affected and the check fails
open on lookup errors. Send a throttled device_limit_reached email
notification and show approaching/reached banners with an upgrade CTA
in the dashboard device list.

Also replace the isYearly checkout field with billingInterval
('monthly' | 'yearly') across DTO, service, and checkout page (legacy
?billing= param still accepted until the marketing site redeploys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:23:13 +03:00
isra el
288ad7d15a feat(billing): respect selected billing interval in polar checkout
Order the polar products array from the isYearly flag so the chosen
interval is preselected at checkout, forward the ?billing= param from
the checkout page as isYearly, and only reuse cached checkout sessions
that match the requested plan and billing interval and are neither
completed nor abandoned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:51:38 +03:00
isra el
6ce9fce9a2 chore(android): bump version to 2.8.0 (versionCode 18)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 09:00:56 +03:00
isra el
243bbdd1d0 feat(android): complete Kotlin migration, UI overhaul, and dashboard polish
Kotlin migration (phases 3-5):
- Port all DTOs, helpers, models, workers, receivers, and services from Java to Kotlin
- Room DB files ported to Kotlin with all logic kept commented out (not yet enabled)
- Add SMSFilterScreen and SMSFilterViewModel in Compose (replaces Java SMSFilterActivity in new UI)
- All helpers exposed as Kotlin objects with @JvmStatic for Java interop

Onboarding improvements:
- Rewrote copy on all 5 onboarding screens (Welcome, Credentials, DeviceSetup, Permissions, SetupComplete)
- Added receive SMS toggle on SetupCompleteScreen (defaults on)
- Gateway now set to enabled by default after successful registration

Dashboard improvements:
- Add receive SMS toggle and SIM subscription ID display in device card
- Add permission warning card when SMS permissions are missing
- Remove all-time stats section (replaced by subscription usage bars)
- Trim Quick Actions to Dashboard + Docs; move Get Support and Share to Settings
- Merge user greeting into TopAppBar subtitle
- Device ID now has inline copy button

Dashboard UI polish (community review fixes):
- Redundant Enabled badge removed; only shows when gateway is Disabled
- Add Gateway label below the main switch for clarity
- Replace infinity symbol with Unlimited in usage display
- SIM subscription IDs use neutral color instead of primary/orange
- Status bar matches background color instead of primary orange

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 08:57:16 +03:00
isra el
9b629d3291 feat(android): new Kotlin/Compose UI with onboarding, dashboard, messages, and settings
Adds a full parallel Jetpack Compose UI alongside the legacy Java/XML app.
Users can switch between UIs via Settings; SplashActivity routes on launch.

New screens:
- Onboarding wizard (QR/manual API key, device registration, permissions)
- Dashboard (device status, all-time stats, subscription card, quick actions)
- Messages tab (SMS history with filter chips, pagination, detail dialog)
- Compose screen (multi-recipient input, send with snackbar feedback)
- Settings (account, gateway, SMS, legal, system, about sections)
- Splash screen with textbee logo and routing logic

Infrastructure:
- Compose BOM 2023.08.00, Material3, Navigation Compose, Kotlin coroutines
- GatewayApiServiceKt (suspend Retrofit interface) + ApiManagerKt singleton
- Kotlin DTOs for stats, subscription, user profile, messages, send SMS
- Material3 theme with brand orange, dark mode safe (dynamicColor = false)

Also adds MIGRATION.md tracking what's done vs what remains to port.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 22:11:27 +03:00
isra el
f6a8f21e64 fix(infra): pin pnpm to v9 in Dockerfiles and update GitHub Actions
pnpm v10 is now the npm "latest" tag but generates a different lockfile
format than v9.0 used in this repo, causing --frozen-lockfile to fail.
Replace corepack pnpm@latest with npm install -g pnpm@9 in api and web
Dockerfiles. Also bump docker/* actions to v3/v6 to resolve Node.js 20
deprecation warnings ahead of the June 2026 forced migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 07:28:30 +03:00
isra el
08d9015048 remove lifecycle email logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:42:27 +03:00
isra el
c235526583 remove abandoned checkout email logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 13:37:45 +03:00
isra el
3bef5f90a0 feat(billing): wire up all 6 abandoned checkout emails
- Add fourth_reminder to AbandonedEmailType and schema enum
- Replace expiry-based timing with createdAt-based timing so the
  schedule is independent of Stripe session expiry windows
- Register all 6 emails in emailSchedule with correct delays:
  10 min, 1 hr, 24 hr, 3 d, 7 d, 14 d after session creation
- Add isCompleted filter to query so paid users are never emailed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 12:58:39 +03:00
isra el
0e4c7481b9 feat(mail): rewrite abandoned checkout email sequence
Complete rewrite of all 6 abandoned checkout templates for better
conversion and deliverability:

- Remove social media icons from all templates (promotions tab signal)
- Remove fake discounts, fabricated testimonials, and unverifiable
  claims (money-back guarantee, Calendly link)
- Give each email a distinct purpose: recovery nudge (10min), feature
  comparison table (1hr), cost objection handling (24hr), personal
  founder message (3d), honest comparison + low pressure (7d),
  graceful farewell + feedback ask (14d)
- Add opt-out notice to every email footer (required for marketing emails)
- Fix spam trigger subject ' Your textbee pro upgrade is waiting!'
  to 'Your TextBee checkout is still open'
- Standardise year to 2026 and brand to on-brand orange throughout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 12:53:58 +03:00
isra el
110bdbc3ef feat(api): enhance webhook subscription management
- Added `lastEnabledAt` property to track when a user re-enables a previously disabled webhook, preventing immediate auto-disable due to historical failures.
- Updated `WebhookService` to set `lastEnabledAt` when a webhook is re-enabled and adjusted the auto-disable logic to respect a grace period based on this new property.
2026-05-27 16:15:41 +03:00
isra el
93ac880dd7 chore(api): update polar sdk version 2026-05-24 17:06:04 +03:00
isra el
35a98f3288 infra: update node version for github workflow 2026-05-24 16:56:47 +03:00
isra el
b1c8585e11 chore(api): update polar sdk version 2026-05-24 16:48:39 +03:00
isra el
f4e23ba3d3 fix(api): resolve Mongoose strict type conflict in GatewayService
- Updated device query in GatewayService to cast the filter object, addressing a type collision with the reserved `model` field in Mongoose 9.6. This change maintains the runtime behavior while ensuring type compatibility.
2026-05-24 16:39:33 +03:00
isra el
1b35e76d82 chore(web): improve webhooks section ui 2026-05-24 16:32:20 +03:00
isra el
8046324c48 fix(api): disambiguate model query in GatewayService
- Updated the model query in GatewayService to use $eq for clarity, resolving a conflict with Mongoose Query.model in TypeScript 9.6+.
2026-05-24 14:08:07 +03:00
isra el
31a69e62ee fix(web): improve date formatting and device label handling in webhook table
- Updated formatDate function to handle undefined and invalid date strings.
- Introduced buildDeviceLabel function to construct device labels more robustly.
- Refactored deviceName mapping in ProductClient to utilize the new buildDeviceLabel function.
2026-05-24 14:07:23 +03:00
isra el
336bb65e0a feat(web): allow multiple webhook subscriptions 2026-05-24 13:35:59 +03:00
isra el
c952040b2b feat(api): allow multiple webhook subscriptions 2026-05-24 13:35:32 +03:00
isra el
cf02c3c50d fix(android): remove unnecessary usesCleartextTraffic attribute from AndroidManifest
- Removed the usesCleartextTraffic attribute from the AndroidManifest.xml file as it is no longer needed.
2026-04-20 00:25:14 +03:00
isra el
004f50d82d refactor(android): remove unnecessary SMS permissions and adjust receiver priority
- Removed READ_SMS permission from AndroidManifest and AppConstants.
- Updated SMSBroadcastReceiver intent filter priority from maximum to a standard value.
2026-04-20 00:20:36 +03:00
isra el
f6556ea512 feat(web): show past-due billing alert with Polar portal CTA
Add dashboard banner when subscription status is past_due for paid plans.
Links to Polar customer portal via existing polarCustomerPortalRequestUrl.
2026-04-05 19:24:21 +03:00
isra el
7e9c036ec1 fix(web): structural skeleton for Get Started card while whoAmI loads
Replace the monolithic h-64 skeleton with GetStartedCardSkeleton that mirrors
the card header and step list so first paint matches the loaded layout.

Made-with: Cursor
2026-04-05 18:05:05 +03:00
isra el
1a5e095c56 fix(api): use subscription document _id in webhook updateOne filters
Mongoose 9 updateOne expects an ObjectId for _id; webhookSubscriptionId
was inferred as ObjectId | WebhookSubscription in CI, causing TS2769.
Use webhookSubscription._id after findById instead.

Made-with: Cursor
2026-04-05 17:43:18 +03:00
isra el
6f5190aade feat(web): prefill Polar customer portal with user email
Add polarCustomerPortalRequestUrl() pointing to /portal/request with a
URL-encoded email query when available. Wire Manage Subscription links in
account settings and subscription info; subscription info loads currentUser
via whoAmI (shared React Query cache with account settings).

Made-with: Cursor
2026-04-05 17:34:14 +03:00
isra el
aea2d27fbb feat(web): allow device deletion 2026-04-05 10:01:25 +03:00
isra el
9586712712 feat(api): enhance API key retrieval logic
- Added a new method in AuthService to find active API keys using a masked match and fallback to regex.
- Updated OptionalAuthGuard and AuthGuard to utilize the new method for improved API key validation.
- Introduced an index on the apiKey field in the ApiKey schema for optimized query performance.
2026-04-05 09:32:41 +03:00
isra el
77f2f38686 chore: improve API key management
- Added a query parameter to filter API keys by status (active, revoked, all) in the getApiKey endpoint.
- Updated the AuthService to handle status filtering logic for API key retrieval.
- Modified the frontend to support status-based API key listing and added a button to view revoked keys.
2026-04-01 07:41:02 +03:00
isra el
5467a85fb0 chore(api): allow device deletion 2026-03-31 22:59:16 +03:00
isra el
9b6e044aa2 Revert "chore(web): update dashboard banners"
This reverts commit f64a520b86.
2026-03-31 15:16:19 +03:00
isra el
8286938b9e ux: improve onboarding steps 2026-03-31 05:16:13 +03:00
isra el
f64a520b86 chore(web): update dashboard banners 2026-03-30 15:02:51 +03:00
isra el
4da8570cd2 feat(api): add fallback for SMS document insertion
- Implemented a check for the existence of the insertMany method in the SMS model to enhance flexibility.
- Added a fallback mechanism for models that do not support insertMany, allowing for individual document creation.
- Improved SMS document insertion process by maintaining performance while ensuring compatibility with various model types.
2026-03-24 17:54:40 +03:00
isra el
839ad24c83 feat(api): enhance SMS processing and queue management
- Introduced batching for SMS document insertion to improve performance.
- Added metadata tracking for SMS to FCM message mapping.
- Implemented error handling for mismatched SMS records and queue payloads.
- Updated SMS queue service to support dynamic batch sizes and immediate queue delays.
2026-03-24 17:24:10 +03:00
isra el
af03623396 fix(api): enhance SMS queue processing and error handling
- Refactored SMS status updates in SmsQueueProcessor to batch updates for failed and dispatched SMS records.
- Improved error handling by collecting failed SMS details and updating their status in a single operation.
- Updated the SMS queue registration to use asynchronous configuration with dynamic limits from the ConfigService.
2026-03-24 17:23:57 +03:00
isra el
ab76237fbb test(api): fix failing tests 2026-03-23 14:22:39 +03:00
isra el
01af5a9786 chore(api): invalidate stale fcm tokens 2026-03-23 14:17:57 +03:00
isra el
7bec9e8acf chore(api): logging improvements 2026-03-23 13:54:52 +03:00
isra el
002ac9c144 chore(web): show update app version cta 2026-03-13 21:54:00 +03:00
isra el
87006c226f refactor(ui): enhance layout and error display in SmsDetailsDialog
- Updated the grid layout for the Info Grid to better represent label-value proportions.
- Improved error details section to display error codes and messages in a full-width format for better visibility.
- Ensured consistent styling for error messages and codes to enhance user experience.
2026-03-12 17:31:12 +03:00
isra el
9d4ec8186b fix(api): improve FCM error handling and messaging in SmsQueueProcessor
- Updated getFcmErrorCode function to remove 'messaging/' prefix from error codes.
- Introduced getFcmErrorMessage function to provide actionable feedback for invalid device tokens.
- Enhanced error messages in SMS processing to utilize the new getFcmErrorMessage function for better clarity.
2026-03-12 17:18:04 +03:00
isra el
dafc433575 fix(ui): improve error display in message history component
- Enhanced error handling in the SmsDetailsDialog to display both error codes and messages.
- Added conditional rendering for error code and message sections to improve clarity for users.
2026-03-12 17:00:21 +03:00
isra el
a87094498c Merge branch 'main' into dev 2026-03-12 16:39:58 +03:00
isra el
c488c16f59 feat(api): enhance SMS status processing and error handling improvements
- Added `dispatchedAt` property to SMS schema and updated status options to include 'dispatched'.
- Implemented logic in SmsQueueProcessor to mark SMS as 'dispatched' upon successful FCM push.
- Enhanced error handling for SMS failures, including specific error codes for FCM delivery issues.
- Updated SmsStatusUpdateTask to handle both 'pending' and 'dispatched' statuses for timeout updates.
2026-03-12 16:38:23 +03:00