Create Temporal billing sweep schedule, workflow and activities and register them in the worker/start initialization. Add an HTMX preview route and banner for plan switches (switch button now GETs a preview; Confirm posts the switch). Extend the Stripe test mock to support invoice previews and add integration tests for PreviewSwitch behavior.
88 lines
3.1 KiB
Go
88 lines
3.1 KiB
Go
package billing
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/queues"
|
|
"go.temporal.io/sdk/client"
|
|
)
|
|
|
|
const (
|
|
// SweepScheduleID is the unique identifier for the scheduled-change sweep schedule.
|
|
SweepScheduleID = "billing-scheduled-change-sweep"
|
|
// SweepWorkflowID is the workflow ID used for sweep executions.
|
|
SweepWorkflowID = "billing-scheduled-change-sweep-workflow"
|
|
// DefaultSweepInterval is the default interval between sweeps.
|
|
DefaultSweepInterval = 1 * time.Hour
|
|
)
|
|
|
|
// ScheduleManager manages Temporal schedules for billing maintenance.
|
|
type ScheduleManager struct {
|
|
client client.Client
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewScheduleManager creates a new ScheduleManager.
|
|
func NewScheduleManager(c client.Client, logger *slog.Logger) *ScheduleManager {
|
|
return &ScheduleManager{client: c, logger: logger}
|
|
}
|
|
|
|
// SweepScheduleConfig holds configuration for the sweep schedule.
|
|
type SweepScheduleConfig struct {
|
|
// Interval is how often to run the sweep.
|
|
Interval time.Duration
|
|
// TriggerImmediately runs the sweep immediately when creating the schedule.
|
|
TriggerImmediately bool
|
|
}
|
|
|
|
// EnsureSweepSchedule creates or updates the scheduled-change sweep schedule.
|
|
// If the schedule already exists, it updates the spec; otherwise it creates it.
|
|
func (m *ScheduleManager) EnsureSweepSchedule(ctx context.Context, cfg SweepScheduleConfig) error {
|
|
if cfg.Interval == 0 {
|
|
cfg.Interval = DefaultSweepInterval
|
|
}
|
|
|
|
scheduleClient := m.client.ScheduleClient()
|
|
|
|
handle := scheduleClient.GetHandle(ctx, SweepScheduleID)
|
|
if _, err := handle.Describe(ctx); err == nil {
|
|
m.logger.Info("updating existing billing sweep schedule", slog.Duration("interval", cfg.Interval))
|
|
if err := handle.Update(ctx, client.ScheduleUpdateOptions{
|
|
DoUpdate: func(schedule client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
|
|
schedule.Description.Schedule.Spec = &client.ScheduleSpec{
|
|
Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}},
|
|
}
|
|
schedule.Description.Schedule.Action = &client.ScheduleWorkflowAction{
|
|
ID: SweepWorkflowID,
|
|
Workflow: SweepScheduledChangesWorkflow,
|
|
TaskQueue: queues.Main,
|
|
}
|
|
return &client.ScheduleUpdate{Schedule: &schedule.Description.Schedule}, nil
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to update sweep schedule: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.logger.Info("creating billing sweep schedule",
|
|
slog.Duration("interval", cfg.Interval), slog.Bool("triggerImmediately", cfg.TriggerImmediately))
|
|
if _, err := scheduleClient.Create(ctx, client.ScheduleOptions{
|
|
ID: SweepScheduleID,
|
|
Spec: client.ScheduleSpec{Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}}},
|
|
Action: &client.ScheduleWorkflowAction{
|
|
ID: SweepWorkflowID,
|
|
Workflow: SweepScheduledChangesWorkflow,
|
|
TaskQueue: queues.Main,
|
|
},
|
|
TriggerImmediately: cfg.TriggerImmediately,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to create sweep schedule: %w", err)
|
|
}
|
|
m.logger.Info("billing sweep schedule created", slog.String("scheduleID", SweepScheduleID))
|
|
return nil
|
|
}
|