217 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
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
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
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
c952040b2b feat(api): allow multiple webhook subscriptions 2026-05-24 13:35:32 +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
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
8286938b9e ux: improve onboarding steps 2026-03-31 05:16:13 +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
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
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
isra el
60808db77a refactor(api): implement batch processing for FCM messages in heartbeat check task 2026-03-11 19:49:52 +03:00
isra el
7a56692586 refactor(api): update queue configuration to set removal age for completed and failed SMS tasks 2026-03-11 19:39:58 +03:00
isra el
51cb1ca198 chore(api): track sms sending failure due to FCM issue 2026-03-11 11:09:30 +03:00
isra el
25c70eb09d feat(api): save default sms sending delay config 2026-03-11 11:04:12 +03:00
isra el
5023f80f0f chore(api): update email template 2026-03-06 14:03:15 +03:00
isra el
b42a681816 chore(api): skip automatic disabling of webhook subscriptions if theres a recent successful delivery 2026-03-06 13:42:15 +03:00
isra el
540bb9068b feat(api): mark notifications older than one month as aborted to prevent retries 2026-03-05 19:50:48 +03:00
isra el
338fb665bc fix(api): fix webhooksubscription save issue 2026-03-05 11:54:20 +03:00
isra el
40b0df297c chore(api): loosen webhook subscription auto-disable logic 2026-03-05 11:43:20 +03:00
isra el
6e7ed42fe8 fix(api): fix failing test 2026-03-05 11:15:29 +03:00
isra el
8fd06f06d1 feat(api): auto-disable webhook ubscriptions with high failure rate 2026-03-05 11:01:51 +03:00
isra el
0144cff16e chore(api): update webhook notifications delivery check cron time and query 2026-03-05 10:33:59 +03:00
isra el
7ff6ea23fc chore(api): update webhook notification delivery timeout to 30ms 2026-03-05 10:33:20 +03:00
isra el
4d1b53e247 chore(api): improve webhook notifications delivery reliability 2026-03-04 13:11:24 +03:00
isra el
f5bef39cec chore(api): update email template 2026-02-09 10:21:48 +03:00
isra el
d8460bcb8b chore(api): update email templates 2026-02-09 06:39:21 +03:00
isra el
eb94085f6e chore(api): handle missing discountId 2026-02-09 06:32:58 +03:00
isra el
1b24cd9754 feat(api): set custom device name 2026-02-06 18:33:55 +03:00
isra el
b6dde1a096 fix failing test 2026-01-29 19:25:14 +03:00