feat(sms): implement SMS messaging and metered billing
CI / Frontend (push) Successful in 9m50s
CI / Go - apps/auth-service (push) Failing after 4s
CI / Go - apps/backend (push) Successful in 10m18s
CI / Docker publish - auth-service (push) Has been skipped
CI / Docker publish - backend (push) Has been skipped

Implement a complete SMS messaging system including:
- Integration with SMS Manager.cz API for sending messages.
- Metered billing via Stripe using monthly aggregate invoice items.
- Backend services for managing SMS settings, usage logging, and monthly reporting.
- Database migrations for tenant settings, usage logs, and monthly reports.
- Frontend dashboard components for SMS configuration, usage tracking, and history.
- Support for customer phone numbers in the booking flow.

Includes new migrations, backend services, and frontend UI components.
This commit is contained in:
Tomas Dvorak
2026-05-10 11:40:53 +02:00
parent 164a37e997
commit 7d3e3448cf
28 changed files with 3633 additions and 3190 deletions
+3
View File
@@ -780,6 +780,7 @@ func entitlementsForPlan(planCode string) domain.PlanEntitlements {
WidgetEmbedding: true,
UmamiTracking: false,
APIAccess: false,
SMSAvailable: false,
}
case "business":
// Business: Unlimited everything, API access, dedicated manager
@@ -793,6 +794,7 @@ func entitlementsForPlan(planCode string) domain.PlanEntitlements {
UmamiTracking: true,
APIAccess: true,
DedicatedManager: true,
SMSAvailable: true,
}
default:
// Pro: 3 locations, 10 staff, unlimited bookings, email reminders, analytics
@@ -805,6 +807,7 @@ func entitlementsForPlan(planCode string) domain.PlanEntitlements {
WidgetEmbedding: true,
UmamiTracking: true,
APIAccess: false,
SMSAvailable: true,
}
}
}
@@ -52,7 +52,7 @@ func TestCreateCheckoutRequiresPaddleConfig(t *testing.T) {
response, err := service.CreateCheckoutSession(context.Background(), domain.Principal{
Subject: "demo-owner",
Email: "owner@bookra.dev",
}, "pro", "czk")
}, "pro", "czk", "monthly")
if err != ErrPaddleNotConfigured {
t.Fatalf("expected ErrPaddleNotConfigured, got response=%v err=%v", response, err)
}
@@ -64,7 +64,7 @@ func TestCreateCheckoutReturnsLaunchPayload(t *testing.T) {
response, err := service.CreateCheckoutSession(context.Background(), domain.Principal{
Subject: "demo-owner",
Email: "owner@bookra.dev",
}, "pro", "czk")
}, "pro", "czk", "monthly")
if err != nil {
t.Fatalf("create checkout: %v", err)
}