mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
7d3e3448cf
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.
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
package sms
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"bookra/apps/backend/internal/config"
|
|
"bookra/apps/backend/internal/db"
|
|
|
|
"github.com/stripe/stripe-go/v81"
|
|
"github.com/stripe/stripe-go/v81/invoiceitem"
|
|
)
|
|
|
|
type BillingService struct {
|
|
cfg config.Config
|
|
repo db.Repository
|
|
smsPriceIDs map[string]string // currency -> price ID
|
|
}
|
|
|
|
func NewBillingService(cfg config.Config, repo db.Repository) *BillingService {
|
|
return &BillingService{
|
|
cfg: cfg,
|
|
repo: repo,
|
|
smsPriceIDs: cfg.StripeSMSPriceMatrix,
|
|
}
|
|
}
|
|
|
|
// PriceIDForCurrency returns the Stripe price ID for SMS in the given currency
|
|
func (b *BillingService) PriceIDForCurrency(currency string) string {
|
|
c := strings.ToLower(strings.TrimSpace(currency))
|
|
if c == "" {
|
|
c = "czk"
|
|
}
|
|
if id := b.smsPriceIDs[c]; id != "" {
|
|
return id
|
|
}
|
|
// Fallback to CZK
|
|
return b.smsPriceIDs["czk"]
|
|
}
|
|
|
|
// CreateMonthlyInvoiceItem creates a Stripe InvoiceItem for the total SMS usage of a month.
|
|
// This adds a line item to the customer's next invoice — charging all messages together.
|
|
func (b *BillingService) CreateMonthlyInvoiceItem(ctx context.Context, customerID string, currency string, yearMonth string, messageCount int, totalCents int) (string, error) {
|
|
if customerID == "" {
|
|
return "", fmt.Errorf("customer id is empty")
|
|
}
|
|
|
|
priceID := b.PriceIDForCurrency(currency)
|
|
c := strings.ToLower(strings.TrimSpace(currency))
|
|
if c == "" {
|
|
c = "czk"
|
|
}
|
|
|
|
// If a Stripe price is configured, use Price + Quantity for a clean invoice line
|
|
if priceID != "" {
|
|
item, err := invoiceitem.New(&stripe.InvoiceItemParams{
|
|
Customer: stripe.String(customerID),
|
|
Price: stripe.String(priceID),
|
|
Quantity: stripe.Int64(int64(messageCount)),
|
|
Description: stripe.String(fmt.Sprintf("SMS Messages (%s) — %d messages", yearMonth, messageCount)),
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create invoice item: %w", err)
|
|
}
|
|
return item.ID, nil
|
|
}
|
|
|
|
// Fallback: explicit amount (for dev/testing when no price configured yet)
|
|
item, err := invoiceitem.New(&stripe.InvoiceItemParams{
|
|
Customer: stripe.String(customerID),
|
|
Amount: stripe.Int64(int64(totalCents)),
|
|
Currency: stripe.String(c),
|
|
Description: stripe.String(fmt.Sprintf("SMS Messages (%s) — %d messages", yearMonth, messageCount)),
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to create invoice item: %w", err)
|
|
}
|
|
return item.ID, nil
|
|
}
|