Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cloud billing customer creation and reporting #5050

Merged
merged 30 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5226b4f
cloud billing customer creation and reporting
pjain1 Jun 10, 2024
cd7f42e
Merge branch 'main' into cloud_billing
pjain1 Jun 10, 2024
3b2a816
col name
pjain1 Jun 10, 2024
61dfade
fix tx ctx
pjain1 Jun 11, 2024
53749ab
customer if refactor
pjain1 Jun 11, 2024
eb4e9ce
sudo api for assigning changing billing plan
pjain1 Jun 11, 2024
038a4f0
apis for listing plans, subscriptions, changing plan, resolve some re…
pjain1 Jun 12, 2024
2ff4b24
rill cloud metrics api
pjain1 Jun 13, 2024
c39ba48
reporter refactor, use org ids, other review comments
pjain1 Jun 15, 2024
d7f5a1e
fix org insert
pjain1 Jun 15, 2024
1b1a5f1
billing related cli cmds
pjain1 Jun 17, 2024
3fb33c1
fix issues
pjain1 Jun 19, 2024
b2e2ac8
return early in case of noop billing
pjain1 Jun 19, 2024
8d175a5
lint fixes
pjain1 Jun 19, 2024
037b595
fixes
pjain1 Jun 19, 2024
ea471ba
safety fixes
pjain1 Jun 20, 2024
a5fb919
Merge branch 'main' into cloud_billing
pjain1 Jun 20, 2024
04b387b
simplify reporting logic, review comments
pjain1 Jun 24, 2024
c761d39
batch reporting
pjain1 Jun 24, 2024
74e8505
fix crontab
pjain1 Jun 24, 2024
c034a40
add validation
pjain1 Jun 24, 2024
f50dacc
fix push
pjain1 Jun 24, 2024
5cee68d
Merge branch 'main' into cloud_billing
pjain1 Jun 24, 2024
17cd5d2
review comments
pjain1 Jun 25, 2024
84c8d21
Merge branch 'main' into cloud_billing
pjain1 Jun 25, 2024
02fe010
pagination for getting usage data
pjain1 Jun 26, 2024
3fee383
assume single subscription
pjain1 Jun 26, 2024
39a3e93
remove unecessary change
pjain1 Jun 26, 2024
4d27a70
review comments
pjain1 Jun 28, 2024
4f27c74
Merge branch 'main' into cloud_billing
pjain1 Jun 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

"github.com/rilldata/rill/admin/ai"
"github.com/rilldata/rill/admin/billing"
"github.com/rilldata/rill/admin/database"
"github.com/rilldata/rill/admin/provisioner"
"github.com/rilldata/rill/runtime/pkg/email"
Expand Down Expand Up @@ -39,9 +40,10 @@ type Service struct {
VersionCommit string
metricsProjectID string
AutoscalerCron string
Biller billing.Biller
}

func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Issuer, emailClient *email.Client, github Github, aiClient ai.Client) (*Service, error) {
func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Issuer, emailClient *email.Client, github Github, aiClient ai.Client, biller billing.Biller) (*Service, error) {
// Init db
db, err := database.Open(opts.DatabaseDriver, opts.DatabaseDSN)
if err != nil {
Expand Down Expand Up @@ -103,6 +105,7 @@ func New(ctx context.Context, opts *Options, logger *zap.Logger, issuer *auth.Is
VersionCommit: opts.VersionCommit,
metricsProjectID: metricsProjectID,
AutoscalerCron: opts.AutoscalerCron,
Biller: biller,
}, nil
}

Expand Down
116 changes: 116 additions & 0 deletions admin/billing/biller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package billing

import (
"context"
"errors"
"time"

"github.com/rilldata/rill/admin/database"
)

const (
SupportEmail = "[email protected]"
DefaultTimeZone = "UTC"
)

var ErrNotFound = errors.New("not found")

type Biller interface {
Name() string
GetDefaultPlan(ctx context.Context) (*Plan, error)
GetPlans(ctx context.Context) ([]*Plan, error)
// GetPublicPlans for listing purposes
GetPublicPlans(ctx context.Context) ([]*Plan, error)
// GetPlan returns the plan with the given biller plan ID.
GetPlan(ctx context.Context, id string) (*Plan, error)
// GetPlanByName returns the plan with the given Rill plan name.
GetPlanByName(ctx context.Context, name string) (*Plan, error)

// CreateCustomer creates a customer for the given organization in the billing system and returns the external customer ID.
CreateCustomer(ctx context.Context, organization *database.Organization) (string, error)

// CreateSubscription creates a subscription for the given organization.
// The subscription starts immediately.
CreateSubscription(ctx context.Context, customerID string, plan *Plan) (*Subscription, error)
CancelSubscription(ctx context.Context, subscriptionID string, cancelOption SubscriptionCancellationOption) error
GetSubscriptionsForCustomer(ctx context.Context, customerID string) ([]*Subscription, error)
ChangeSubscriptionPlan(ctx context.Context, subscriptionID string, plan *Plan) (*Subscription, error)
// CancelSubscriptionsForCustomer deletes the subscription for the given organization.
// cancellationDate only applicable if option is SubscriptionCancellationOptionRequestedDate
CancelSubscriptionsForCustomer(ctx context.Context, customerID string, cancelOption SubscriptionCancellationOption) error

ReportUsage(ctx context.Context, usage []*Usage) error

GetReportingGranularity() UsageReportingGranularity
GetReportingWorkerCron() string
}

type Plan struct {
ID string // ID of the plan in the external billing system
Name string // Unique name of the plan in Rill, can be empty if biller does not support it
DisplayName string
Description string
TrialPeriodDays int
Default bool
Public bool
Quotas Quotas
Metadata map[string]string
}

type Quotas struct {
StorageLimitBytesPerDeployment *int64

// Existing quotas
NumProjects *int
NumDeployments *int
NumSlotsTotal *int
NumSlotsPerDeployment *int
NumOutstandingInvites *int
}

type planMetadata struct {
Default bool `mapstructure:"default"`
Public bool `mapstructure:"public"`
StorageLimitBytesPerDeployment *int64 `mapstructure:"storage_limit_bytes_per_deployment"`
NumProjects *int `mapstructure:"num_projects"`
NumDeployments *int `mapstructure:"num_deployments"`
NumSlotsTotal *int `mapstructure:"num_slots_total"`
NumSlotsPerDeployment *int `mapstructure:"num_slots_per_deployment"`
NumOutstandingInvites *int `mapstructure:"num_outstanding_invites"`
}

type Subscription struct {
ID string
CustomerID string
Plan *Plan
StartDate time.Time
EndDate time.Time
CurrentBillingCycleStartDate time.Time
CurrentBillingCycleEndDate time.Time
TrialEndDate time.Time
Metadata map[string]string
}

type Usage struct {
CustomerID string
MetricName string
Amount float64
ReportingGrain UsageReportingGranularity
StartTime time.Time // Start time of the usage period
EndTime time.Time // End time of the usage period
Metadata map[string]interface{}
}

type UsageReportingGranularity string

const (
UsageReportingGranularityNone UsageReportingGranularity = ""
UsageReportingGranularityHour UsageReportingGranularity = "hour"
)

type SubscriptionCancellationOption int

const (
SubscriptionCancellationOptionEndOfSubscriptionTerm SubscriptionCancellationOption = iota
SubscriptionCancellationOptionImmediate
)
75 changes: 75 additions & 0 deletions admin/billing/noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package billing

import (
"context"

"github.com/rilldata/rill/admin/database"
)

var _ Biller = &noop{}

type noop struct{}

func NewNoop() Biller {
return noop{}
}

func (n noop) Name() string {
return "noop"
}

func (n noop) GetDefaultPlan(ctx context.Context) (*Plan, error) {
return nil, nil
}

func (n noop) GetPlans(ctx context.Context) ([]*Plan, error) {
return nil, nil
}

func (n noop) GetPlan(ctx context.Context, id string) (*Plan, error) {
return nil, nil
}

func (n noop) GetPlanByName(ctx context.Context, name string) (*Plan, error) {
return nil, nil
}

func (n noop) GetPublicPlans(ctx context.Context) ([]*Plan, error) {
return nil, nil
}

func (n noop) CreateCustomer(ctx context.Context, organization *database.Organization) (string, error) {
return "", nil
}

func (n noop) CreateSubscription(ctx context.Context, customerID string, plan *Plan) (*Subscription, error) {
return nil, nil
}

func (n noop) CancelSubscription(ctx context.Context, subscriptionID string, cancelOption SubscriptionCancellationOption) error {
return nil
}

func (n noop) GetSubscriptionsForCustomer(ctx context.Context, customerID string) ([]*Subscription, error) {
return nil, nil
}

func (n noop) ChangeSubscriptionPlan(ctx context.Context, subscriptionID string, plan *Plan) (*Subscription, error) {
return nil, nil
}

func (n noop) CancelSubscriptionsForCustomer(ctx context.Context, customerID string, cancelOption SubscriptionCancellationOption) error {
return nil
}

func (n noop) ReportUsage(ctx context.Context, usage []*Usage) error {
return nil
}

func (n noop) GetReportingGranularity() UsageReportingGranularity {
return UsageReportingGranularityNone
}

func (n noop) GetReportingWorkerCron() string {
return ""
}
Loading
Loading