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

feat: generate invoice for overdraft credits #796

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TAG := $(shell git rev-list --tags --max-count=1)
VERSION := $(shell git describe --tags ${TAG})
.PHONY: build check fmt lint test test-race vet test-cover-html help install proto ui compose-up-dev
.DEFAULT_GOAL := build
PROTON_COMMIT := "5e5f98bafba2a218b35a9130d75bd68980151d00"
PROTON_COMMIT := "251281aa0c311904eb263eb9bfa7e3b9c41f99b0"

ui:
@echo " > generating ui build"
Expand Down
11 changes: 11 additions & 0 deletions billing/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ type AccountConfig struct {
DefaultPlan string `yaml:"default_plan" mapstructure:"default_plan"`
DefaultOffline bool `yaml:"default_offline" mapstructure:"default_offline"`
OnboardCreditsWithOrg int64 `yaml:"onboard_credits_with_org" mapstructure:"onboard_credits_with_org"`

// CreditOverdraftProduct helps identify the product pricing per unit amount for the overdraft
// credits being invoiced
CreditOverdraftProduct string `yaml:"credit_overdraft_product" mapstructure:"credit_overdraft_product"`

// CreditOverdraftInvoiceDay is the day of the range(month) when the overdraft credits are invoiced
CreditOverdraftInvoiceDay int `yaml:"credit_overdraft_invoice_day" mapstructure:"credit_overdraft_invoice_day" default:"1"`

// CreditOverdraftInvoiceRangeShift is the shift in the invoice range for the overdraft credits
// if positive, the invoice range will be shifted to the future else it will be shifted to the past
CreditOverdraftInvoiceRangeShift int `yaml:"credit_overdraft_invoice_range_shift" mapstructure:"credit_overdraft_invoice_range_shift" default:"0"`
}

type PlanChangeConfig struct {
Expand Down
14 changes: 10 additions & 4 deletions billing/credit/credit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package credit

import (
"errors"
"strings"
"time"

"github.com/google/uuid"
Expand All @@ -20,10 +21,11 @@ var (
// TxNamespaceUUID is the namespace for generating transaction UUIDs deterministically
TxNamespaceUUID = uuid.MustParse("967416d0-716e-4308-b58f-2468ac14f20a")

SourceSystemBuyEvent = "system.buy"
SourceSystemAwardedEvent = "system.awarded"
SourceSystemOnboardEvent = "system.starter"
SourceSystemRevertEvent = "system.revert"
SourceSystemBuyEvent = "system.buy"
SourceSystemAwardedEvent = "system.awarded"
SourceSystemOnboardEvent = "system.starter"
SourceSystemRevertEvent = "system.revert"
SourceSystemOverdraftEvent = "system.overdraft"
)

type TransactionType string
Expand Down Expand Up @@ -71,3 +73,7 @@ type Filter struct {
StartRange time.Time
EndRange time.Time
}

func TxUUID(tags ...string) string {
return uuid.NewSHA1(TxNamespaceUUID, []byte(strings.Join(tags, ":"))).String()
}
63 changes: 62 additions & 1 deletion billing/credit/mocks/transaction_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions billing/credit/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package credit
import (
"context"
"fmt"
"time"

"github.com/pkg/errors"

Expand All @@ -14,6 +15,7 @@ type TransactionRepository interface {
GetBalance(ctx context.Context, id string) (int64, error)
List(ctx context.Context, flt Filter) ([]Transaction, error)
GetByID(ctx context.Context, id string) (Transaction, error)
GetBalanceForRange(ctx context.Context, accountID string, start time.Time, end time.Time) (int64, error)
}

type Service struct {
Expand Down Expand Up @@ -115,6 +117,12 @@ func (s Service) GetBalance(ctx context.Context, accountID string) (int64, error
return s.transactionRepository.GetBalance(ctx, accountID)
}

// GetBalanceForRange returns the balance for the given accountID within the given time range
// start time is inclusive, end time is exclusive
func (s Service) GetBalanceForRange(ctx context.Context, accountID string, start time.Time, end time.Time) (int64, error) {
return s.transactionRepository.GetBalanceForRange(ctx, accountID, start, end)
}

func (s Service) GetByID(ctx context.Context, id string) (Transaction, error) {
return s.transactionRepository.GetByID(ctx, id)
}
3 changes: 3 additions & 0 deletions billing/customer/customer.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ type Filter struct {
OrgID string
ProviderID string
State State

Online *bool
AllowedOverdraft *bool
}

type PaymentMethod struct {
Expand Down
71 changes: 67 additions & 4 deletions billing/invoice/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,42 @@ var (
ErrInvalidDetail = fmt.Errorf("invalid invoice detail")
)

const (
// ItemIDMetadataKey is used to uniquely identify the item in the invoice
// this is useful when reconciling the invoice items for payments and
// avoid creating duplicate credits
ItemIDMetadataKey = "item_id"
// ItemTypeMetadataKey is used to identify the item type in the invoice
// this is useful when reconciling the invoice items for payments and
// avoid creating duplicate invoices
ItemTypeMetadataKey = "item_type"

// ReconciledMetadataKey marks the invoice as reconciled and avoid processing it again
// as an optimization
ReconciledMetadataKey = "reconciled"

// GenerateForCreditLockKey is used to lock the invoice generation within current application
GenerateForCreditLockKey = "generate_for_credit"
)

type State string

func (s State) String() string {
return string(s)
}

const (
DraftState State = "draft"
OpenState State = "open"
PaidState State = "paid"
)

type Invoice struct {
ID string
CustomerID string
ProviderID string
State string
ID string
CustomerID string
ProviderID string
// State could be one of draft, open, paid, uncollectible, void
State State
Currency string
Amount int64
HostedURL string
Expand All @@ -28,12 +59,44 @@ type Invoice struct {
PeriodStartAt time.Time
PeriodEndAt time.Time

Items []Item
Metadata metadata.Metadata
}

type ItemType string

func (t ItemType) String() string {
return string(t)
}

const (
// CreditItemType is used to charge for the credits used in the system
// as overdraft
CreditItemType ItemType = "credit"
)

type Item struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
// Name is the item name
Name string `json:"name"`
// Type is the item type
Type ItemType `json:"type"`
// UnitAmount is per unit cost
UnitAmount int64 `json:"unit_amount"`
// Quantity is the number of units
Quantity int64 `json:"quantity"`

// TimeRangeStart is the start time of the item since it's effective
TimeRangeStart *time.Time `json:"range_start"`
// TimeRangeEnd is the end time of the item since it's effective
TimeRangeEnd *time.Time `json:"range_end"`
}

type Filter struct {
CustomerID string
NonZeroOnly bool
State State

Pagination *pagination.Pagination
}
Loading
Loading