Skip to content

Commit

Permalink
feat: generate invoice for overdraft credits
Browse files Browse the repository at this point in the history
It's necessary to configure the product name to calculate
per unit price before the overdraft credits can be invoiced
in `billing.customer.credit_overdraft_product`.
If not set, invoice reconcilation and creation is skipped.

If there is already an open(unpaid) invoice for overdraft
credits, no new invoice will be created for that customer.
Reconcilation of these invoice happens on same cadence as
invoices gets synced from the billing provider.

Signed-off-by: Kush Sharma <[email protected]>
  • Loading branch information
kushsharma committed Oct 21, 2024
1 parent 581f66c commit fe89ad2
Show file tree
Hide file tree
Showing 22 changed files with 674 additions and 74 deletions.
4 changes: 4 additions & 0 deletions billing/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ 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"`
}

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()
}
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
55 changes: 51 additions & 4 deletions billing/invoice/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,31 @@ var (
ErrInvalidDetail = fmt.Errorf("invalid invoice detail")
)

const (
ItemTypeMetadataKey = "item_type"
ReconciledMetadataKey = "reconciled"

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 +48,39 @@ 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"`
}

type Filter struct {
CustomerID string
NonZeroOnly bool
State State

Pagination *pagination.Pagination
}
Loading

0 comments on commit fe89ad2

Please sign in to comment.