billing

package
v1.0.0-beta.187 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2024 License: Apache-2.0 Imports: 24 Imported by: 0

README

Billing

This package contains the implementation for the billing stack (invoicing, tax and payments).

The package has the following main entities:

BillingProfile

Captures all the billing details, two main information is stored inside:

  • The billing workflow (when to invoice, due periods etc)
  • References to the apps responsible for tax, invoicing and payments (Sandbox or Stripe for now)

Only one default billing profile can exist per namespace.

CustomerOverride

Contains customer specific overrides for billing pruposes. It can reference a billing profile other than the default (e.g. when different apps or lifecycle should be used) and allows to override the billing workflow.

Invoice

Invoices are used to store the data required by tax, invoicing and payment app's master copy at OpenMeter side.

Upon creation all the data required to generate invoices are snapshotted into the invoice entity, so that no updates to entities like Customer, BillingProfile, CustomerOverride change an invoice retrospectively.

Gathering invoices

There are two general kinds of invoices (Invoice.Status) gathering invoices are used to collect upcoming lines that are to be added to future invoices. gathering invocie's state never changes: when upcoming line items become due, they are just assigned to a new invoice, so that we clone the data required afresh.

Each customer can have one gathering issue per currency.

For example, if the customer has upcoming charges in USD and HUF, then there will be one gathering invoice for HUF and one for USD.

If there are no upcoming items, the gathering invoices are (soft) deleted.

Collection

TODO: document when implemented

Invoices

The invoices are governed by the invoice state machine.

Invoices are composed of lines. Each invoice can only have lines from the same currency.

The lines can be of different types:

  • Fee: one time charge
  • UsageBased: usage-based charge (can be used to charge additional usage-based prices without the product catalog features)

Each line has a period (start, end) and an invoiceAt property. The period specifies which period of time the line is referring to (in case of usage-based pricing, the underlying meter will be queried for this time-period). invoiceAt specifies the time when it is expected to create an invoice that contains this line. The invoice's collection settings can defer this.

Invoices are always created by collecting one or more line from the gathering invoices. The /v1/api/billing/invoices/lines endpoint can be used to create new future line items. A new invoice can be created any time. In such case, the gathering items to be invoiced (invoiceAt) are already added to the invoice. Any usage-based line, that we can bill early is also added to the invoice for the period between the period.start of the line and the time of invoice creation.

Line splitting

To achieve the behavior described above, we are using line splitting. By default we would have one line per billing period that would eventually be part of an invoice:

 period.start                                              period.end
Line1 [status=valid] |--------------------------------------------------------|

When the usage-based line can be billed mid-period, we split the line into two:

 period.start              asOf                              period.end
Line1 [status=split]         |--------------------------------------------------------|
SplitLine1 [status=valid]    |------------------|
SplitLine2 [status=valid]                       |-------------------------------------|

As visible:

  • Line1's status changes from valid to split: it will be ignored in any calculation, it becomes a grouping line between invoices
  • SplitLine1 is created with a period between period.start and asof (time of invoicing): it will be addedd to the freshly created invoice
  • SplitLine2 is created with a period between asof and period.end: it will be pushed to the gathering invoice

When creating a new invoice between asof and period.end the same logic continues, but without marking SplitLine2 split, instead the new line is added to the original line's parent line:

 period.start              asOf1          asof2                period.end
Line1 [status=split]         |--------------------------------------------------------|
SplitLine1 [status=valid]    |------------------|
SplitLine2 [status=valid]                       |---------------|
SplitLine3 [status=valid]                                       |---------------------|

This flattening approach allows us not to have to recursively traverse lines in the database.

Usage-based quantity

When a line is created for an invoice, the quantity of the underlying meter is captured into the line's qty field. This information is never updated, so late events will have to create new invoice lines when needed.

Detailed Lines

Each (valid) line can have one or more detailed lines (children). These lines represent the actual sub-charges that are caused by the parent line.

Example:

If a line has:

  • Usage of 200 units
  • Tiered pricing:
  • Tier1: 1 - 50 units cost flat $300
  • Tier2: 51 - 100 units cost flat $400
  • Tier3: 100 - 150 units cost flat $400 + $1/unit
  • Tier4: more than 150 units cost $15/unit

This would yield the following lines:

  • Line with quantity=200
    • Line quantity=1 per_unit_amount=300 total=300 (Tier1)
    • Line quantity=1 per_unit_amount=400 total=400 (Tier2)
    • Line quantity=1 per_unit_amount=400 total=400 (Tier3, flat component)
    • Line quantity=50 per_unit_amount=1 total=50 (Tier3, per unit price)
    • Line quantity=50 per_unit_amount=15 total=759 (Tier4)

Apps can choose to syncronize the original line (if the upstream system understands our pricing model) or can use the sublines to syncronize individual lines without having to understand billing details.

Detailed Lines vs Splitting

When we are dealing with a split line, the calculation of the quantity is by taking the meter's quantity for the whole line period ([parent.period.start, splitline.period.end]) and the amount before the period (parent.period.start, splitline.period.start).

When substracting the two we get the delta for the period (this gets the delta for all supported meter types except of Min and Avg).

We execute the pricing logic (e.g. tiered pricing) for the line qty, while considering the before usage, as it reflects the already billed for items.

Corner cases:

  • Graduating tiered prices cannot be billed mid-billing period (always arrears, as the calculation cannot be split into multiple items)
  • Min, Avg meters are always billed arrears as we cannot calculate the delta.
Detailed line persisting

In order for the calculation logic, to not to have to deal with the contents of the database, it is (mostly) the adapter layer's responsibility to understand what have changed and persist only that data to the database.

In practice the high level rules are the following (see adapter/invoicelinediff_test.go for examples):

  • If an entity has an ID then it will be updated
  • If an entity has changed compared to the database fetch, it will be updated
  • If a child line, discount gets removed, it will be removed from the database (in case of lines with all sub-entities)
  • If an entity doesn't have an ID a new entity will be generated by the database

For idempotent entity sources (detailed lines and discounts for now), we have also added a field called ChildUniqueReferenceID which can be used to detect entities serving the same purpose.

ChildUniqueReferenceID example

Let's say we have an usage-based line whose detailed lines are persisted to the database, but then we would want to change the quantity of the line.

First we load the existing detailed lines from the database, and save the database versions of the entities in memory.

We execute the calculation for the new quantity that yields new detailed lines without database IDs.

The entity's ChildrenWithIDReuse call can be used to facilitate the line reuse by assigning the known IDs to the yielded lines where the ChildUniqueReferenceID is set.

Then the adapter layer will use those IDs to make decisions if they want to persist or recreate the records.

We could do the same logic in the adapter layer, but this approach makes it more flexible on the calculation layer if we want to generate new lines or not. If this becomes a burden we can do the same matching logic as part of the upsert logic in adapter.

Subscription adapter

The subscription adapter is responsible for feeding the billing with line items during the subscription's lifecycle. The generation of items is event-driven, new items are yielded when:

  • A subscription is created
  • A new invoice is created
  • A subscription is modified
  • Upgrade/Downgrade is handled as a subscription create/cancel

Documentation

Index

Constants

View Source
const (
	EntityCustomerOverride = "BillingCustomerOverride"
	EntityCustomer         = "Customer"
	EntityDefaultProfile   = "DefaultBillingProfile"
	EntityInvoice          = "Invoice"
	EntityInvoiceLine      = "InvoiceLine"
)
View Source
const (
	CustomerUsageAttributionTypeVersion = "customer_usage_attribution.v1"
)
View Source
const (
	DefaultMeterResolution = time.Minute
)
View Source
const (
	// LineMaximumSpendReferenceID is a discount applied due to maximum spend.
	LineMaximumSpendReferenceID = "line_maximum_spend"
)

Variables

View Source
var (
	ErrDefaultProfileAlreadyExists  = NewValidationError("default_profile_exists", "default profile already exists")
	ErrDefaultProfileNotFound       = NewValidationError("default_profile_not_found", "default profile not found")
	ErrProfileNotFound              = NewValidationError("profile_not_found", "profile not found")
	ErrProfileAlreadyDeleted        = NewValidationError("profile_already_deleted", "profile already deleted")
	ErrProfileReferencedByOverrides = NewValidationError("profile_referenced", "profile is referenced by customer overrides")

	ErrCustomerOverrideNotFound       = NewValidationError("customer_override_not_found", "customer override not found")
	ErrCustomerOverrideAlreadyDeleted = NewValidationError("customer_override_deleted", "customer override already deleted")
	ErrCustomerNotFound               = NewValidationError("customer_not_found", "customer not found")
	ErrCustomerDeleted                = NewValidationError("customer_deleted", "customer has been deleted")

	ErrFieldRequired             = NewValidationError("field_required", "field is required")
	ErrFieldMustBePositive       = NewValidationError("field_must_be_positive", "field must be positive")
	ErrFieldMustBePositiveOrZero = NewValidationError("field_must_be_positive_or_zero", "field must be positive or zero")

	ErrInvoiceCannotAdvance      = NewValidationError("invoice_cannot_advance", "invoice cannot advance")
	ErrInvoiceCannotBeEdited     = NewValidationError("invoice_cannot_be_edited", "invoice cannot be edited in the current state")
	ErrInvoiceActionNotAvailable = NewValidationError("invoice_action_not_available", "invoice action not available")
	ErrInvoiceLinesNotBillable   = NewValidationError("invoice_lines_not_billable", "some invoice lines are not billable")
	ErrInvoiceEmpty              = NewValidationError("invoice_empty", "invoice is empty")
	ErrInvoiceDeleteFailed       = NewValidationError("invoice_delete_failed", "invoice delete failed")

	ErrInvoiceLineFeatureHasNoMeters             = NewValidationError("invoice_line_feature_has_no_meters", "usage based invoice line: feature has no meters")
	ErrInvoiceLineVolumeSplitNotSupported        = NewValidationError("invoice_line_graduated_split_not_supported", "graduated tiered pricing is not supported for split periods")
	ErrInvoiceLineNoTiers                        = NewValidationError("invoice_line_no_tiers", "usage based invoice line: no tiers found")
	ErrInvoiceLineMissingOpenEndedTier           = NewValidationError("invoice_line_missing_open_ended_tier", "usage based invoice line: missing open ended tier")
	ErrInvoiceLineDeleteInvalidStatus            = NewValidationError("invoice_line_delete_invalid_status", "invoice line cannot be deleted in the current state (only valid lines can be deleted)")
	ErrInvoiceCreateNoLines                      = NewValidationError("invoice_create_no_lines", "the new invoice would have no lines")
	ErrInvoiceCreateUBPLineCustomerHasNoSubjects = NewValidationError("invoice_create_ubp_line_customer_has_no_subjects", "creating an usage based line: customer has no subjects")
	ErrInvoiceCreateUBPLinePeriodIsEmpty         = NewValidationError("invoice_create_ubp_line_period_is_empty", "creating an usage based line: truncated period is empty")
	ErrInvoiceLineCurrencyMismatch               = NewValidationError("invoice_line_currency_mismatch", "invoice line currency mismatch")
)
View Source
var DefaultWorkflowConfig = WorkflowConfig{
	Collection: CollectionConfig{
		Alignment: AlignmentKindSubscription,
		Interval:  lo.Must(datex.ISOString("PT2H").Parse()),
	},
	Invoicing: InvoicingConfig{
		AutoAdvance: true,
		DraftPeriod: lo.Must(datex.ISOString("P1D").Parse()),
		DueAfter:    lo.Must(datex.ISOString("P1W").Parse()),
	},
	Payment: PaymentConfig{
		CollectionMethod: CollectionMethodChargeAutomatically,
	},
}
View Source
var InvoiceExpandAll = InvoiceExpand{
	Preceding:    true,
	WorkflowApps: true,
	Lines:        true,
	DeletedLines: false,
	SplitLines:   false,
}
View Source
var ProfileExpandAll = ProfileExpand{
	Apps: true,
}

Functions

func EncodeValidationIssues

func EncodeValidationIssues[T error](err T) map[string]interface{}

func ValidationWithComponent

func ValidationWithComponent(component ComponentName, err error) error

ValidationWithComponent wraps an error with a component name, if error is nil, it returns nil This can be used to add context to an error when we are crossing service boundaries.

func ValidationWithFieldPrefix

func ValidationWithFieldPrefix(prefix string, err error) error

ValidationWithFieldPrefix wraps an error with a field prefix, if error is nil, it returns nil This can be used to delegate validation duties to a sub-entity. (e.g. lines don't need to know about the path in the invoice they are residing at)

Types

type AdvanceInvoiceInput

type AdvanceInvoiceInput = InvoiceID

type AlignmentKind

type AlignmentKind string

AlignmentKind specifies what governs when an invoice is issued

const (
	// AlignmentKindSubscription specifies that the invoice is issued based on the subscription period (
	// e.g. whenever a due line item is added, it will trigger an invoice generation after the collection period)
	AlignmentKindSubscription AlignmentKind = "subscription"
)

func (AlignmentKind) Values

func (k AlignmentKind) Values() []string

type AppReference

type AppReference struct {
	ID   string                `json:"id"`
	Type appentitybase.AppType `json:"type"`
}

func (AppReference) Validate

func (a AppReference) Validate() error

type ApproveInvoiceInput

type ApproveInvoiceInput = InvoiceID

type AssociateLinesToInvoiceAdapterInput

type AssociateLinesToInvoiceAdapterInput struct {
	Invoice InvoiceID

	LineIDs []string
}

func (AssociateLinesToInvoiceAdapterInput) Validate

type AssociatedLineCountsAdapterInput

type AssociatedLineCountsAdapterInput = genericMultiInvoiceInput

type AssociatedLineCountsAdapterResponse

type AssociatedLineCountsAdapterResponse struct {
	Counts map[InvoiceID]int64
}

type BaseProfile

type BaseProfile struct {
	ID        string `json:"id"`
	Namespace string `json:"namespace"`

	Name        string  `json:"name"`
	Description *string `json:"description,omitempty"`

	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`

	WorkflowConfig WorkflowConfig `json:"workflow"`

	Supplier SupplierContact `json:"supplier"`

	Default  bool     `json:"default"`
	Metadata Metadata `json:"metadata"`

	AppReferences *ProfileAppReferences `json:"appReferences,omitempty"`
}

func (BaseProfile) Validate

func (p BaseProfile) Validate() error

type CollectionConfig

type CollectionConfig struct {
	Alignment AlignmentKind `json:"alignment"`
	Interval  datex.Period  `json:"period,omitempty"`
}

CollectionConfig groups fields related to item collection.

func (*CollectionConfig) Validate

func (c *CollectionConfig) Validate() error

type CollectionMethod

type CollectionMethod string
const (
	// CollectionMethodChargeAutomatically charges the customer automatically based on previously saved card data
	CollectionMethodChargeAutomatically CollectionMethod = "charge_automatically"
	// CollectionMethodSendInvoice sends an invoice to the customer along with the payment instructions/links
	CollectionMethodSendInvoice CollectionMethod = "send_invoice"
)

func (CollectionMethod) Values

func (c CollectionMethod) Values() []string

type CollectionOverrideConfig

type CollectionOverrideConfig struct {
	Alignment *AlignmentKind `json:"alignment,omitempty"`
	Interval  *datex.Period  `json:"interval,omitempty"`
}

func (*CollectionOverrideConfig) Validate

func (c *CollectionOverrideConfig) Validate() error

type ComponentName

type ComponentName string

func AppTypeCapabilityToComponent

func AppTypeCapabilityToComponent(appType appentitybase.AppType, cap appentitybase.CapabilityType, op string) ComponentName

type ConflictError

type ConflictError struct {
	ID     string
	Entity string
	Err    error
}

func (ConflictError) Error

func (e ConflictError) Error() string

func (ConflictError) Unwrap

func (e ConflictError) Unwrap() error

type CreateCustomerOverrideInput

type CreateCustomerOverrideInput struct {
	Namespace string `json:"namespace"`

	CustomerID string `json:"customerID"`
	ProfileID  string `json:"billingProfile,omitempty"`

	Collection CollectionOverrideConfig `json:"collection"`
	Invoicing  InvoicingOverrideConfig  `json:"invoicing"`
	Payment    PaymentOverrideConfig    `json:"payment"`
}

func (CreateCustomerOverrideInput) Validate

func (c CreateCustomerOverrideInput) Validate() error

type CreateInvoiceAdapterInput

type CreateInvoiceAdapterInput struct {
	Namespace string
	Customer  customerentity.Customer
	Profile   Profile
	Currency  currencyx.Code
	Status    InvoiceStatus
	Metadata  map[string]string
	IssuedAt  time.Time

	Type        InvoiceType
	Description *string
	DueAt       *time.Time

	Totals Totals
}

func (CreateInvoiceAdapterInput) Validate

func (c CreateInvoiceAdapterInput) Validate() error

type CreateInvoiceAdapterRespone

type CreateInvoiceAdapterRespone = Invoice

type CreateInvoiceLinesInput

type CreateInvoiceLinesInput struct {
	Namespace string
	Lines     []LineWithCustomer
}

func (CreateInvoiceLinesInput) Validate

func (c CreateInvoiceLinesInput) Validate() error

type CreateProfileAppsInput

type CreateProfileAppsInput = ProfileAppReferences

type CreateProfileInput

type CreateProfileInput struct {
	Namespace   string            `json:"namespace"`
	Name        string            `json:"name"`
	Description *string           `json:"description,omitempty"`
	Metadata    map[string]string `json:"metadata"`
	Supplier    SupplierContact   `json:"supplier"`
	Default     bool              `json:"default"`

	WorkflowConfig WorkflowConfig         `json:"workflowConfig"`
	Apps           CreateProfileAppsInput `json:"apps"`
}

func (CreateProfileInput) Validate

func (i CreateProfileInput) Validate() error

type CreateWorkflowConfigInput

type CreateWorkflowConfigInput struct {
	WorkflowConfig
}

type CustomerMetadata

type CustomerMetadata struct {
	Name string `json:"name"`
}

type CustomerOverride

type CustomerOverride struct {
	Namespace string `json:"namespace"`
	ID        string `json:"id"`

	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`

	CustomerID string   `json:"customerID"`
	Profile    *Profile `json:"billingProfile,omitempty"`

	Collection CollectionOverrideConfig `json:"collection"`
	Invoicing  InvoicingOverrideConfig  `json:"invoicing"`
	Payment    PaymentOverrideConfig    `json:"payment"`
}

func (CustomerOverride) Validate

func (c CustomerOverride) Validate() error

type CustomerOverrideAdapter

type CustomerOverrideAdapter interface {
	CreateCustomerOverride(ctx context.Context, input CreateCustomerOverrideInput) (*CustomerOverride, error)
	GetCustomerOverride(ctx context.Context, input GetCustomerOverrideAdapterInput) (*CustomerOverride, error)
	UpdateCustomerOverride(ctx context.Context, input UpdateCustomerOverrideAdapterInput) (*CustomerOverride, error)
	DeleteCustomerOverride(ctx context.Context, input DeleteCustomerOverrideInput) error

	// UpsertCustomerOverride upserts a customer override ignoring the transactional context, the override
	// will be empty.
	UpsertCustomerOverride(ctx context.Context, input UpsertCustomerOverrideAdapterInput) error
	LockCustomerForUpdate(ctx context.Context, input LockCustomerForUpdateAdapterInput) error

	GetCustomerOverrideReferencingProfile(ctx context.Context, input HasCustomerOverrideReferencingProfileAdapterInput) ([]customerentity.CustomerID, error)
}

type CustomerOverrideService

type CustomerOverrideService interface {
	CreateCustomerOverride(ctx context.Context, input CreateCustomerOverrideInput) (*CustomerOverride, error)
	UpdateCustomerOverride(ctx context.Context, input UpdateCustomerOverrideInput) (*CustomerOverride, error)
	GetCustomerOverride(ctx context.Context, input GetCustomerOverrideInput) (*CustomerOverride, error)
	DeleteCustomerOverride(ctx context.Context, input DeleteCustomerOverrideInput) error

	GetProfileWithCustomerOverride(ctx context.Context, input GetProfileWithCustomerOverrideInput) (*ProfileWithCustomerDetails, error)
}

type DeleteCustomerOverrideInput

type DeleteCustomerOverrideInput namespacedCustomerID

func (DeleteCustomerOverrideInput) Validate

func (d DeleteCustomerOverrideInput) Validate() error

type DeleteInvoiceInput

type DeleteInvoiceInput = InvoiceID

type DeleteInvoiceLineInput

type DeleteInvoiceLineInput = LineID

type DeleteInvoicesAdapterInput

type DeleteInvoicesAdapterInput = genericMultiInvoiceInput

type DeleteProfileInput

type DeleteProfileInput genericNamespaceID

func (DeleteProfileInput) Validate

func (i DeleteProfileInput) Validate() error

type FinalizeInvoiceResult

type FinalizeInvoiceResult struct {
	// contains filtered or unexported fields
}

func NewFinalizeInvoiceResult

func NewFinalizeInvoiceResult() *FinalizeInvoiceResult

func (*FinalizeInvoiceResult) GetPaymentExternalID

func (f *FinalizeInvoiceResult) GetPaymentExternalID() (string, bool)

func (*FinalizeInvoiceResult) SetPaymentExternalID

func (f *FinalizeInvoiceResult) SetPaymentExternalID(paymentExternalID string)

type FlatFeeCategory

type FlatFeeCategory string
const (
	// FlatFeeCategoryRegular is a regular flat fee, that is based on the usage or a subscription.
	FlatFeeCategoryRegular FlatFeeCategory = "regular"
	// FlatFeeCategoryCommitment is a flat fee that is based on a commitment such as min spend.
	FlatFeeCategoryCommitment FlatFeeCategory = "commitment"
)

func (FlatFeeCategory) Values

func (FlatFeeCategory) Values() []string

type FlatFeeLine

type FlatFeeLine struct {
	ConfigID      string                         `json:"configId"`
	PerUnitAmount alpacadecimal.Decimal          `json:"perUnitAmount"`
	PaymentTerm   productcatalog.PaymentTermType `json:"paymentTerm"`
	Category      FlatFeeCategory                `json:"category"`

	Quantity alpacadecimal.Decimal `json:"quantity"`
}

func (FlatFeeLine) Clone

func (i FlatFeeLine) Clone() *FlatFeeLine

func (FlatFeeLine) Equal

func (i FlatFeeLine) Equal(other *FlatFeeLine) bool

type GetCustomerOverrideAdapterInput

type GetCustomerOverrideAdapterInput struct {
	Customer customerentity.CustomerID

	IncludeDeleted bool
}

func (GetCustomerOverrideAdapterInput) Validate

type GetCustomerOverrideInput

type GetCustomerOverrideInput namespacedCustomerID

func (GetCustomerOverrideInput) Validate

func (g GetCustomerOverrideInput) Validate() error

type GetDefaultProfileInput

type GetDefaultProfileInput struct {
	Namespace string
}

func (GetDefaultProfileInput) Validate

func (i GetDefaultProfileInput) Validate() error

type GetInvoiceByIdInput

type GetInvoiceByIdInput struct {
	Invoice InvoiceID
	Expand  InvoiceExpand
}

func (GetInvoiceByIdInput) Validate

func (i GetInvoiceByIdInput) Validate() error

type GetInvoiceLineAdapterInput

type GetInvoiceLineAdapterInput = LineID

type GetInvoiceLineInput

type GetInvoiceLineInput = LineID

type GetInvoiceLineOwnershipAdapterInput

type GetInvoiceLineOwnershipAdapterInput = LineID

type GetInvoiceOwnershipAdapterInput

type GetInvoiceOwnershipAdapterInput = InvoiceID

type GetLinesForSubscriptionInput

type GetLinesForSubscriptionInput struct {
	Namespace      string
	SubscriptionID string
}

func (GetLinesForSubscriptionInput) Validate

func (i GetLinesForSubscriptionInput) Validate() error

type GetOwnershipAdapterResponse

type GetOwnershipAdapterResponse struct {
	Namespace  string
	InvoiceID  string
	CustomerID string
}

type GetProfileInput

type GetProfileInput struct {
	Profile models.NamespacedID
	Expand  ProfileExpand
}

func (GetProfileInput) Validate

func (i GetProfileInput) Validate() error

type GetProfileWithCustomerOverrideInput

type GetProfileWithCustomerOverrideInput namespacedCustomerID

func (GetProfileWithCustomerOverrideInput) Validate

type GranularityResolution

type GranularityResolution string
const (
	// GranularityResolutionDay provides line items for metered data per day
	GranularityResolutionDay GranularityResolution = "day"
	// GranularityResolutionPeriod provides one line item per period
	GranularityResolutionPeriod GranularityResolution = "period"
)

func (GranularityResolution) Values

func (r GranularityResolution) Values() []string

type HasCustomerOverrideReferencingProfileAdapterInput

type HasCustomerOverrideReferencingProfileAdapterInput genericNamespaceID

func (HasCustomerOverrideReferencingProfileAdapterInput) Validate

type Invoice

type Invoice struct {
	InvoiceBase `json:",inline"`

	// Line items
	Lines            LineChildren     `json:"lines,omitempty"`
	ValidationIssues ValidationIssues `json:"validationIssues,omitempty"`
	Totals           Totals           `json:"totals"`

	// private fields required by the service
	ExpandedFields InvoiceExpand `json:"-"`
}

func (Invoice) Clone

func (i Invoice) Clone() Invoice

func (*Invoice) FlattenLinesByID

func (i *Invoice) FlattenLinesByID() map[string]*Line

func (*Invoice) HasCriticalValidationIssues

func (i *Invoice) HasCriticalValidationIssues() bool

func (Invoice) InvoiceID

func (i Invoice) InvoiceID() InvoiceID

func (*Invoice) MergeValidationIssues

func (i *Invoice) MergeValidationIssues(errIn error, reportingComponent ComponentName) error

func (Invoice) RemoveCircularReferences

func (i Invoice) RemoveCircularReferences() Invoice

func (Invoice) RemoveMetaForCompare

func (i Invoice) RemoveMetaForCompare() Invoice

RemoveMetaForCompare returns a copy of the invoice without the fields that are not relevant for higher level tests that compare invoices. What gets removed: - Line's DB state - Line's dependencies are marked as resolved - Parent pointers are removed

type InvoiceAction

type InvoiceAction string
const (
	InvoiceActionAdvance InvoiceAction = "advance"
	InvoiceActionApprove InvoiceAction = "approve"
	InvoiceActionDelete  InvoiceAction = "delete"
	InvoiceActionRetry   InvoiceAction = "retry"
	InvoiceActionVoid    InvoiceAction = "void"
)

type InvoiceAdapter

type InvoiceAdapter interface {
	CreateInvoice(ctx context.Context, input CreateInvoiceAdapterInput) (CreateInvoiceAdapterRespone, error)
	GetInvoiceById(ctx context.Context, input GetInvoiceByIdInput) (Invoice, error)
	LockInvoicesForUpdate(ctx context.Context, input LockInvoicesForUpdateInput) error
	DeleteInvoices(ctx context.Context, input DeleteInvoicesAdapterInput) error
	ListInvoices(ctx context.Context, input ListInvoicesInput) (ListInvoicesResponse, error)
	AssociatedLineCounts(ctx context.Context, input AssociatedLineCountsAdapterInput) (AssociatedLineCountsAdapterResponse, error)
	UpdateInvoice(ctx context.Context, input UpdateInvoiceAdapterInput) (Invoice, error)

	GetInvoiceOwnership(ctx context.Context, input GetInvoiceOwnershipAdapterInput) (GetOwnershipAdapterResponse, error)
}

type InvoiceBase

type InvoiceBase struct {
	Namespace string `json:"namespace"`
	ID        string `json:"id"`

	Number      *string `json:"number,omitempty"`
	Description *string `json:"description,omitempty"`

	Type InvoiceType `json:"type"`

	Metadata map[string]string `json:"metadata"`

	Currency      currencyx.Code       `json:"currency,omitempty"`
	Timezone      timezone.Timezone    `json:"timezone,omitempty"`
	Status        InvoiceStatus        `json:"status"`
	StatusDetails InvoiceStatusDetails `json:"statusDetail,omitempty"`

	Period *Period `json:"period,omitempty"`

	DueAt *time.Time `json:"dueDate,omitempty"`

	CreatedAt  time.Time  `json:"createdAt"`
	UpdatedAt  time.Time  `json:"updatedAt"`
	VoidedAt   *time.Time `json:"voidedAt,omitempty"`
	DraftUntil *time.Time `json:"draftUntil,omitempty"`
	IssuedAt   *time.Time `json:"issuedAt,omitempty"`
	DeletedAt  *time.Time `json:"deletedAt,omitempty"`

	// Customer is either a snapshot of the contact information of the customer at the time of invoice being sent
	// or the data from the customer entity (draft state)
	// This is required so that we are not modifying the invoice after it has been sent to the customer.
	Customer InvoiceCustomer  `json:"customer"`
	Supplier SupplierContact  `json:"supplier"`
	Workflow *InvoiceWorkflow `json:"workflow,omitempty"`

	ExternalIDs InvoiceExternalIDs `json:"externalIds,omitempty"`
}

type InvoiceCustomer

type InvoiceCustomer struct {
	CustomerID string `json:"customerId,omitempty"`

	Name             string                   `json:"name"`
	BillingAddress   *models.Address          `json:"billingAddress,omitempty"`
	Timezone         *timezone.Timezone       `json:"timezone,omitempty"`
	UsageAttribution CustomerUsageAttribution `json:"usageAttribution"`
}

func (*InvoiceCustomer) Validate

func (i *InvoiceCustomer) Validate() error

type InvoiceExpand

type InvoiceExpand struct {
	Preceding    bool
	WorkflowApps bool
	Lines        bool
	DeletedLines bool
	SplitLines   bool
	// GatheringTotals is used to calculate the totals of the invoice when gathering, this is temporary
	// until we implement the full progressive billing stack.
	GatheringTotals bool
}

func (InvoiceExpand) SetDeletedLines

func (e InvoiceExpand) SetDeletedLines(v bool) InvoiceExpand

func (InvoiceExpand) SetGatheringTotals

func (e InvoiceExpand) SetGatheringTotals(v bool) InvoiceExpand

func (InvoiceExpand) SetLines

func (e InvoiceExpand) SetLines(v bool) InvoiceExpand

func (InvoiceExpand) SetSplitLines

func (e InvoiceExpand) SetSplitLines(v bool) InvoiceExpand

func (InvoiceExpand) Validate

func (e InvoiceExpand) Validate() error

type InvoiceExternalIDs

type InvoiceExternalIDs struct {
	Invoicing string `json:"invoicing,omitempty"`
	Payment   string `json:"payment,omitempty"`
}

type InvoiceID

type InvoiceID models.NamespacedID

func (InvoiceID) Validate

func (i InvoiceID) Validate() error

type InvoiceLineAdapter

type InvoiceLineAdapter interface {
	UpsertInvoiceLines(ctx context.Context, input UpsertInvoiceLinesAdapterInput) ([]*Line, error)
	ListInvoiceLines(ctx context.Context, input ListInvoiceLinesAdapterInput) ([]*Line, error)
	AssociateLinesToInvoice(ctx context.Context, input AssociateLinesToInvoiceAdapterInput) ([]*Line, error)
	GetInvoiceLine(ctx context.Context, input GetInvoiceLineAdapterInput) (*Line, error)
	GetLinesForSubscription(ctx context.Context, input GetLinesForSubscriptionInput) ([]*Line, error)

	GetInvoiceLineOwnership(ctx context.Context, input GetInvoiceLineOwnershipAdapterInput) (GetOwnershipAdapterResponse, error)
}

type InvoiceLineService

type InvoiceLineService interface {
	CreatePendingInvoiceLines(ctx context.Context, input CreateInvoiceLinesInput) ([]*Line, error)
	GetInvoiceLine(ctx context.Context, input GetInvoiceLineInput) (*Line, error)
	GetLinesForSubscription(ctx context.Context, input GetLinesForSubscriptionInput) ([]*Line, error)
	UpdateInvoiceLine(ctx context.Context, input UpdateInvoiceLineInput) (*Line, error)

	DeleteInvoiceLine(ctx context.Context, input DeleteInvoiceLineInput) error
}

type InvoiceLineStatus

type InvoiceLineStatus string
const (
	// InvoiceLineStatusValid is a valid invoice line.
	InvoiceLineStatusValid InvoiceLineStatus = "valid"
	// InvoiceLineStatusSplit is a split invoice line (the child lines will have this set as parent).
	InvoiceLineStatusSplit InvoiceLineStatus = "split"
	// InvoiceLineStatusDetailed is a detailed invoice line.
	InvoiceLineStatusDetailed InvoiceLineStatus = "detailed"
)

func (InvoiceLineStatus) Values

func (InvoiceLineStatus) Values() []string

type InvoiceLineType

type InvoiceLineType string
const (
	// InvoiceLineTypeFee is an item that represents a single charge without meter backing.
	InvoiceLineTypeFee InvoiceLineType = "flat_fee"
	// InvoiceLineTypeUsageBased is an item that is added to the invoice and is usage based.
	InvoiceLineTypeUsageBased InvoiceLineType = "usage_based"
)

func (InvoiceLineType) Values

func (InvoiceLineType) Values() []string

type InvoicePendingLinesInput

type InvoicePendingLinesInput struct {
	Customer customerentity.CustomerID

	IncludePendingLines mo.Option[[]string]
	AsOf                *time.Time
}

func (InvoicePendingLinesInput) Validate

func (i InvoicePendingLinesInput) Validate() error

type InvoiceService

type InvoiceService interface {
	ListInvoices(ctx context.Context, input ListInvoicesInput) (ListInvoicesResponse, error)
	GetInvoiceByID(ctx context.Context, input GetInvoiceByIdInput) (Invoice, error)
	InvoicePendingLines(ctx context.Context, input InvoicePendingLinesInput) ([]Invoice, error)
	// AdvanceInvoice advances the invoice to the next stage, the advancement is stopped until:
	// - an error is occurred
	// - the invoice is in a state that cannot be advanced (e.g. waiting for draft period to expire)
	// - the invoice is advanced to the final state
	AdvanceInvoice(ctx context.Context, input AdvanceInvoiceInput) (Invoice, error)
	ApproveInvoice(ctx context.Context, input ApproveInvoiceInput) (Invoice, error)
	RetryInvoice(ctx context.Context, input RetryInvoiceInput) (Invoice, error)
	DeleteInvoice(ctx context.Context, input DeleteInvoiceInput) error

	// UpdateInvoiceLinesInternal updates the specified invoice lines and ensures that invoice states are properly syncronized
	// This method is intended to be used by OpenMeter internal services only, as it allows for updating invoice line values,
	// that are not allowed to be updated by external services.
	//
	// The call also ensures that the invoice's state is properly updated and invoice immutability is also considered.
	UpdateInvoiceLinesInternal(ctx context.Context, input UpdateInvoiceLinesInternalInput) error
}

type InvoiceStatus

type InvoiceStatus string
const (
	// InvoiceStatusGathering is the status of an invoice that is gathering the items to be invoiced.
	InvoiceStatusGathering InvoiceStatus = "gathering"

	InvoiceStatusDraftCreated              InvoiceStatus = "draft_created"
	InvoiceStatusDraftUpdating             InvoiceStatus = "draft_updating"
	InvoiceStatusDraftManualApprovalNeeded InvoiceStatus = "draft_manual_approval_needed"
	InvoiceStatusDraftValidating           InvoiceStatus = "draft_validating"
	InvoiceStatusDraftInvalid              InvoiceStatus = "draft_invalid"
	InvoiceStatusDraftSyncing              InvoiceStatus = "draft_syncing"
	InvoiceStatusDraftSyncFailed           InvoiceStatus = "draft_sync_failed"
	InvoiceStatusDraftWaitingAutoApproval  InvoiceStatus = "draft_waiting_auto_approval"
	InvoiceStatusDraftReadyToIssue         InvoiceStatus = "draft_ready_to_issue"

	InvoiceStatusDeleteInProgress InvoiceStatus = "delete_in_progress"
	InvoiceStatusDeleteSyncing    InvoiceStatus = "delete_syncing"
	InvoiceStatusDeleteFailed     InvoiceStatus = "delete_failed"
	InvoiceStatusDeleted          InvoiceStatus = "deleted"

	InvoiceStatusIssuing           InvoiceStatus = "issuing_syncing"
	InvoiceStatusIssuingSyncFailed InvoiceStatus = "issuing_sync_failed"

	// InvoiceStatusIssued is the status of an invoice that has been issued.
	InvoiceStatusIssued InvoiceStatus = "issued"
)

func (InvoiceStatus) IsFailed

func (s InvoiceStatus) IsFailed() bool

func (InvoiceStatus) ShortStatus

func (s InvoiceStatus) ShortStatus() string

func (InvoiceStatus) Validate

func (s InvoiceStatus) Validate() error

func (InvoiceStatus) Values

func (s InvoiceStatus) Values() []string

type InvoiceStatusDetails

type InvoiceStatusDetails struct {
	Immutable        bool            `json:"immutable"`
	Failed           bool            `json:"failed"`
	AvailableActions []InvoiceAction `json:"availableActions"`
}

type InvoiceType

type InvoiceType string
const (
	InvoiceTypeStandard   InvoiceType = InvoiceType(bill.InvoiceTypeStandard)
	InvoiceTypeCreditNote InvoiceType = InvoiceType(bill.InvoiceTypeCreditNote)
)

func (InvoiceType) Validate

func (t InvoiceType) Validate() error

func (InvoiceType) Values

func (t InvoiceType) Values() []string

type InvoiceWorkflow

type InvoiceWorkflow struct {
	AppReferences          ProfileAppReferences `json:"appReferences"`
	Apps                   *ProfileApps         `json:"apps,omitempty"`
	SourceBillingProfileID string               `json:"sourceBillingProfileId,omitempty"`
	Config                 WorkflowConfig       `json:"config"`
}

type InvoicingApp

type InvoicingApp interface {
	// ValidateInvoice validates if the app can run for the given invoice
	ValidateInvoice(ctx context.Context, invoice Invoice) error

	// UpsertInvoice upserts the invoice on the remote system, the invoice is read-only, the app should not modify it
	// the recommended behavior is that the invoices FlattenLinesByID is used to get all lines, then the app should
	// syncronize all the fee lines and store the external IDs in the result.
	UpsertInvoice(ctx context.Context, invoice Invoice) (*UpsertInvoiceResult, error)

	// FinalizeInvoice finalizes the invoice on the remote system, starts the payment flow. It is safe to assume
	// that the state machine have already performed an upsert as part of this state transition.
	//
	// If the payment is handled by a decoupled implementation (different app or app has strict separation of concerns)
	// then the payment app will be called with FinalizePayment and that should return the external ID of the payment. (later)
	FinalizeInvoice(ctx context.Context, invoice Invoice) (*FinalizeInvoiceResult, error)

	// DeleteInvoice deletes the invoice on the remote system, the invoice is read-only, the app should not modify it
	// the invoice deletion is only invoked for non-finalized invoices.
	DeleteInvoice(ctx context.Context, invoice Invoice) error
}

type InvoicingConfig

type InvoicingConfig struct {
	AutoAdvance bool         `json:"autoAdvance,omitempty"`
	DraftPeriod datex.Period `json:"draftPeriod,omitempty"`
	DueAfter    datex.Period `json:"dueAfter,omitempty"`
}

InvoiceConfig groups fields related to invoice settings.

func (*InvoicingConfig) Validate

func (c *InvoicingConfig) Validate() error

type InvoicingOverrideConfig

type InvoicingOverrideConfig struct {
	AutoAdvance *bool         `json:"autoAdvance,omitempty"`
	DraftPeriod *datex.Period `json:"draftPeriod,omitempty"`
	DueAfter    *datex.Period `json:"dueAfter,omitempty"`
}

func (*InvoicingOverrideConfig) Validate

func (c *InvoicingOverrideConfig) Validate() error

type Line

type Line struct {
	LineBase `json:",inline"`

	// TODO[OM-1060]: Make it a proper union type instead of having both fields as public
	FlatFee    *FlatFeeLine    `json:"flatFee,omitempty"`
	UsageBased *UsageBasedLine `json:"usageBased,omitempty"`

	Children   LineChildren `json:"children,omitempty"`
	ParentLine *Line        `json:"parent,omitempty"`

	Discounts LineDiscounts `json:"discounts,omitempty"`

	DBState *Line `json:"-"`
}

func (Line) ChildrenWithIDReuse

func (c Line) ChildrenWithIDReuse(l []*Line) LineChildren

ChildrenWithIDReuse returns a new LineChildren instance with the given lines. If the line has a child with a unique reference ID, it will try to retain the database ID of the existing child to avoid a delete/create.

func (Line) Clone

func (i Line) Clone() *Line

func (Line) CloneWithoutDependencies

func (i Line) CloneWithoutDependencies() *Line

CloneWithoutDependencies returns a clone of the line without any external dependencies. Could be used for creating a new line without any references to the parent or children (or config IDs).

func (*Line) DisassociateChildren

func (i *Line) DisassociateChildren()

DissacociateChildren removes the Children both from the DBState and the current line, so that the line can be safely persisted/managed without the children.

The childrens receive DBState objects, so that they can be safely persisted/managed without the parent.

func (Line) LineID

func (i Line) LineID() LineID

func (Line) RemoveCircularReferences

func (i Line) RemoveCircularReferences() *Line

func (Line) RemoveMetaForCompare

func (i Line) RemoveMetaForCompare() *Line

RemoveMetaForCompare returns a copy of the invoice without the fields that are not relevant for higher level tests that compare invoices. What gets removed: - Line's DB state - Line's dependencies are marked as resolved - Parent pointers are removed

func (*Line) SaveDBSnapshot

func (i *Line) SaveDBSnapshot()

func (Line) Validate

func (i Line) Validate() error

func (Line) ValidateFee

func (i Line) ValidateFee() error

func (Line) ValidateUsageBased

func (i Line) ValidateUsageBased() error

func (Line) WithoutDBState

func (i Line) WithoutDBState() *Line

type LineBase

type LineBase struct {
	Namespace string `json:"namespace"`
	ID        string `json:"id"`

	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`

	Metadata    map[string]string `json:"metadata"`
	Name        string            `json:"name"`
	Type        InvoiceLineType   `json:"type"`
	Description *string           `json:"description,omitempty"`

	InvoiceID string         `json:"invoiceID,omitempty"`
	Currency  currencyx.Code `json:"currency"`

	// Lifecycle
	Period    Period    `json:"period"`
	InvoiceAt time.Time `json:"invoiceAt"`

	// Relationships
	ParentLineID *string `json:"parentLine,omitempty"`

	Status                 InvoiceLineStatus `json:"status"`
	ChildUniqueReferenceID *string           `json:"childUniqueReferenceID,omitempty"`

	TaxConfig *TaxConfig `json:"taxOverrides,omitempty"`

	ExternalIDs  LineExternalIDs        `json:"externalIDs,omitempty"`
	Subscription *SubscriptionReference `json:"subscription,omitempty"`

	Totals Totals `json:"totals"`
}

LineBase represents the common fields for an invoice item.

func (LineBase) Clone

func (i LineBase) Clone(line *Line) LineBase

func (LineBase) Equal

func (i LineBase) Equal(other LineBase) bool

func (LineBase) Validate

func (i LineBase) Validate() error

type LineChildren

type LineChildren struct {
	mo.Option[[]*Line]
}

TODO[OM-1016]: For events we need a json marshaler

func NewLineChildren

func NewLineChildren(children []*Line) LineChildren

func (*LineChildren) Append

func (c *LineChildren) Append(l ...*Line)

func (LineChildren) Clone

func (c LineChildren) Clone() LineChildren

func (LineChildren) GetByID

func (c LineChildren) GetByID(id string) *Line

func (LineChildren) Map

func (c LineChildren) Map(fn func(*Line) *Line) LineChildren

func (*LineChildren) RemoveByID

func (c *LineChildren) RemoveByID(id string) bool

func (*LineChildren) ReplaceByID

func (c *LineChildren) ReplaceByID(id string, newLine *Line) bool

type LineDiscount

type LineDiscount struct {
	ID        string     `json:"id"`
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`

	Amount                 alpacadecimal.Decimal `json:"amount"`
	Description            *string               `json:"description,omitempty"`
	ChildUniqueReferenceID *string               `json:"childUniqueReferenceId,omitempty"`
}

func (LineDiscount) Equal

func (i LineDiscount) Equal(other LineDiscount) bool

type LineDiscounts

type LineDiscounts struct {
	mo.Option[[]LineDiscount]
}

TODO[OM-1016]: For events we need a json marshaler

func NewLineDiscounts

func NewLineDiscounts(discounts []LineDiscount) LineDiscounts

func (LineDiscounts) ChildrenWithIDReuse

func (c LineDiscounts) ChildrenWithIDReuse(l LineDiscounts) LineDiscounts

func (LineDiscounts) Map

type LineExternalIDs

type LineExternalIDs struct {
	Invoicing string `json:"invoicing,omitempty"`
}

type LineID

type LineID models.NamespacedID

func (LineID) Validate

func (i LineID) Validate() error

type LineWithCustomer

type LineWithCustomer struct {
	Line

	CustomerID string
}

func (LineWithCustomer) Validate

func (l LineWithCustomer) Validate() error

type ListInvoiceLinesAdapterInput

type ListInvoiceLinesAdapterInput struct {
	Namespace string

	CustomerID                 string
	InvoiceIDs                 []string
	InvoiceStatuses            []InvoiceStatus
	InvoiceAtBefore            *time.Time
	IncludeDeleted             bool
	ParentLineIDs              []string
	ParentLineIDsIncludeParent bool
	Statuses                   []InvoiceLineStatus

	LineIDs []string
}

func (ListInvoiceLinesAdapterInput) Validate

func (g ListInvoiceLinesAdapterInput) Validate() error

type ListInvoicesInput

type ListInvoicesInput struct {
	pagination.Page

	Namespace string
	IDs       []string
	Customers []string
	// Statuses searches by short InvoiceStatus (e.g. draft, issued)
	Statuses []string
	// ExtendedStatuses searches by exact InvoiceStatus
	ExtendedStatuses []InvoiceStatus
	Currencies       []currencyx.Code

	IssuedAfter  *time.Time
	IssuedBefore *time.Time

	Expand InvoiceExpand

	OrderBy api.InvoiceOrderBy
	Order   sortx.Order
}

func (ListInvoicesInput) Validate

func (i ListInvoicesInput) Validate() error

type ListInvoicesResponse

type ListInvoicesResponse = pagination.PagedResponse[Invoice]

type ListProfilesInput

type ListProfilesInput struct {
	pagination.Page

	Expand ProfileExpand

	Namespace       string
	IncludeArchived bool
	OrderBy         api.BillingProfileOrderBy
	Order           sortx.Order
}

func (ListProfilesInput) Validate

func (i ListProfilesInput) Validate() error

type ListProfilesResult

type ListProfilesResult = pagination.PagedResponse[Profile]

type LockCustomerForUpdateAdapterInput

type LockCustomerForUpdateAdapterInput = customerentity.CustomerID

type LockInvoicesForUpdateInput

type LockInvoicesForUpdateInput = genericMultiInvoiceInput

type Metadata

type Metadata map[string]string

type NotFoundError

type NotFoundError struct {
	ID     string
	Entity string
	Err    error
}

func (NotFoundError) Error

func (e NotFoundError) Error() string

func (NotFoundError) Unwrap

func (e NotFoundError) Unwrap() error

type PaymentConfig

type PaymentConfig struct {
	CollectionMethod CollectionMethod
}

func (*PaymentConfig) Validate

func (c *PaymentConfig) Validate() error

type PaymentOverrideConfig

type PaymentOverrideConfig struct {
	CollectionMethod *CollectionMethod
}

func (*PaymentOverrideConfig) Validate

func (c *PaymentOverrideConfig) Validate() error

type Period

type Period struct {
	Start time.Time `json:"start"`
	End   time.Time `json:"end"`
}

Period represents a time period, in billing the time period is always interpreted as [from, to) (i.e. from is inclusive, to is exclusive).

func (Period) Contains

func (p Period) Contains(t time.Time) bool

func (Period) Duration

func (p Period) Duration() time.Duration

func (Period) Equal

func (p Period) Equal(other Period) bool

func (Period) IsEmpty

func (p Period) IsEmpty() bool

func (Period) Truncate

func (p Period) Truncate(resolution time.Duration) Period

func (Period) Validate

func (p Period) Validate() error

type Price

type Price = productcatalog.Price

type Profile

type Profile struct {
	BaseProfile

	// Optionaly expanded fields
	Apps *ProfileApps `json:"-"`
}

func (Profile) Merge

func (p Profile) Merge(o *CustomerOverride) Profile

func (Profile) Validate

func (p Profile) Validate() error

type ProfileAdapter

type ProfileAdapter interface {
	CreateProfile(ctx context.Context, input CreateProfileInput) (*BaseProfile, error)
	ListProfiles(ctx context.Context, input ListProfilesInput) (pagination.PagedResponse[BaseProfile], error)
	GetProfile(ctx context.Context, input GetProfileInput) (*BaseProfile, error)
	GetDefaultProfile(ctx context.Context, input GetDefaultProfileInput) (*BaseProfile, error)
	DeleteProfile(ctx context.Context, input DeleteProfileInput) error
	UpdateProfile(ctx context.Context, input UpdateProfileAdapterInput) (*BaseProfile, error)
}

type ProfileAppReferences

type ProfileAppReferences struct {
	Tax       AppReference `json:"tax"`
	Invoicing AppReference `json:"invoicing"`
	Payment   AppReference `json:"payment"`
}

func (ProfileAppReferences) Validate

func (i ProfileAppReferences) Validate() error

type ProfileApps

type ProfileApps struct {
	Tax       appentity.App `json:"tax"`
	Invoicing appentity.App `json:"invoicing"`
	Payment   appentity.App `json:"payment"`
}

type ProfileExpand

type ProfileExpand struct {
	Apps bool
}

func (ProfileExpand) Validate

func (e ProfileExpand) Validate() error

type ProfileService

type ProfileService interface {
	CreateProfile(ctx context.Context, param CreateProfileInput) (*Profile, error)
	GetDefaultProfile(ctx context.Context, input GetDefaultProfileInput) (*Profile, error)
	GetProfile(ctx context.Context, input GetProfileInput) (*Profile, error)
	ListProfiles(ctx context.Context, input ListProfilesInput) (ListProfilesResult, error)
	DeleteProfile(ctx context.Context, input DeleteProfileInput) error
	UpdateProfile(ctx context.Context, input UpdateProfileInput) (*Profile, error)
	ProvisionDefaultBillingProfile(ctx context.Context, namespace string) error
}

type ProfileWithCustomerDetails

type ProfileWithCustomerDetails struct {
	Profile  Profile                 `json:"profile"`
	Customer customerentity.Customer `json:"customer"`
}

func (ProfileWithCustomerDetails) Validate

func (p ProfileWithCustomerDetails) Validate() error

type RetryInvoiceInput

type RetryInvoiceInput = InvoiceID

type SubscriptionReference

type SubscriptionReference struct {
	SubscriptionID string `json:"subscriptionID"`
	PhaseID        string `json:"phaseID"`
	ItemID         string `json:"itemID"`
}

type SupplierContact

type SupplierContact struct {
	ID      string         `json:"id"`
	Name    string         `json:"name"`
	Address models.Address `json:"address"`
	TaxCode *string        `json:"taxCode,omitempty"`
}

func (SupplierContact) Validate

func (c SupplierContact) Validate() error

Validate checks if the supplier contact is valid for invoice generation (e.g. Country is required)

type TaxConfig

type TaxConfig = productcatalog.TaxConfig

type Totals

type Totals struct {
	// Amount is the total amount value of the line before taxes, discounts and commitments
	Amount alpacadecimal.Decimal `json:"amount"`
	// ChargesTotal is the amount of value of the line that are due to additional charges
	ChargesTotal alpacadecimal.Decimal `json:"chargesTotal"`
	// DiscountsTotal is the amount of value of the line that are due to discounts
	DiscountsTotal alpacadecimal.Decimal `json:"discountsTotal"`

	// TaxesInclusiveTotal is the total amount of taxes that are included in the line
	TaxesInclusiveTotal alpacadecimal.Decimal `json:"taxesInclusiveTotal"`
	// TaxesExclusiveTotal is the total amount of taxes that are excluded from the line
	TaxesExclusiveTotal alpacadecimal.Decimal `json:"taxesExclusiveTotal"`
	// TaxesTotal is the total amount of taxes that are included in the line
	TaxesTotal alpacadecimal.Decimal `json:"taxesTotal"`

	// Total is the total amount value of the line after taxes, discounts and commitments
	Total alpacadecimal.Decimal `json:"total"`
}

func (Totals) Add

func (t Totals) Add(others ...Totals) Totals

func (Totals) CalculateTotal

func (t Totals) CalculateTotal() alpacadecimal.Decimal

func (Totals) Validate

func (t Totals) Validate() error

type UpdateAfterDeleteError

type UpdateAfterDeleteError genericError

func (UpdateAfterDeleteError) Error

func (e UpdateAfterDeleteError) Error() string

func (UpdateAfterDeleteError) Unwrap

func (e UpdateAfterDeleteError) Unwrap() error

type UpdateCustomerOverrideAdapterInput

type UpdateCustomerOverrideAdapterInput struct {
	UpdateCustomerOverrideInput

	ResetDeletedAt bool
}

func (UpdateCustomerOverrideAdapterInput) Validate

type UpdateCustomerOverrideInput

type UpdateCustomerOverrideInput struct {
	Namespace  string `json:"namespace"`
	CustomerID string `json:"customerID"`

	UpdatedAt time.Time `json:"updatedAt"`

	ProfileID string `json:"billingProfileID"`

	Collection CollectionOverrideConfig `json:"collection"`
	Invoicing  InvoicingOverrideConfig  `json:"invoicing"`
	Payment    PaymentOverrideConfig    `json:"payment"`
}

func (UpdateCustomerOverrideInput) Validate

func (u UpdateCustomerOverrideInput) Validate() error

type UpdateInvoiceAdapterInput

type UpdateInvoiceAdapterInput = Invoice

type UpdateInvoiceLineAdapterInput

type UpdateInvoiceLineAdapterInput Line

type UpdateInvoiceLineBaseInput

type UpdateInvoiceLineBaseInput struct {
	InvoiceAt mo.Option[time.Time]

	Metadata  mo.Option[map[string]string]
	Name      mo.Option[string]
	Period    mo.Option[Period]
	TaxConfig mo.Option[*TaxConfig]
}

func (UpdateInvoiceLineBaseInput) Apply

func (UpdateInvoiceLineBaseInput) Validate

func (u UpdateInvoiceLineBaseInput) Validate() error

type UpdateInvoiceLineFlatFeeInput

type UpdateInvoiceLineFlatFeeInput struct {
	PerUnitAmount mo.Option[alpacadecimal.Decimal]
	Quantity      mo.Option[alpacadecimal.Decimal]
	PaymentTerm   mo.Option[productcatalog.PaymentTermType]
}

func (UpdateInvoiceLineFlatFeeInput) Apply

func (UpdateInvoiceLineFlatFeeInput) Validate

func (u UpdateInvoiceLineFlatFeeInput) Validate() error

type UpdateInvoiceLineInput

type UpdateInvoiceLineInput struct {
	// Mandatory fields for update
	Line LineID
	Type InvoiceLineType

	LineBase   UpdateInvoiceLineBaseInput
	UsageBased UpdateInvoiceLineUsageBasedInput
	FlatFee    UpdateInvoiceLineFlatFeeInput
}

func (UpdateInvoiceLineInput) Apply

func (u UpdateInvoiceLineInput) Apply(l *Line) (*Line, error)

func (UpdateInvoiceLineInput) Validate

func (u UpdateInvoiceLineInput) Validate() error

type UpdateInvoiceLineUsageBasedInput

type UpdateInvoiceLineUsageBasedInput struct {
	Price *Price
}

func (UpdateInvoiceLineUsageBasedInput) Apply

func (UpdateInvoiceLineUsageBasedInput) Validate

type UpdateInvoiceLinesInternalInput

type UpdateInvoiceLinesInternalInput struct {
	Namespace  string
	CustomerID string
	Lines      []*Line
}

func (UpdateInvoiceLinesInternalInput) Validate

type UpdateProfileAdapterInput

type UpdateProfileAdapterInput struct {
	TargetState      BaseProfile
	WorkflowConfigID string
}

func (UpdateProfileAdapterInput) Validate

func (i UpdateProfileAdapterInput) Validate() error

type UpdateProfileInput

type UpdateProfileInput BaseProfile

func (UpdateProfileInput) Validate

func (i UpdateProfileInput) Validate() error

type UpsertCustomerOverrideAdapterInput

type UpsertCustomerOverrideAdapterInput = customerentity.CustomerID

type UpsertInvoiceLinesAdapterInput

type UpsertInvoiceLinesAdapterInput struct {
	Namespace string
	Lines     []*Line
}

func (UpsertInvoiceLinesAdapterInput) Validate

type UpsertInvoiceResult

type UpsertInvoiceResult = UpsertResults

func NewUpsertInvoiceResult

func NewUpsertInvoiceResult() *UpsertInvoiceResult

type UpsertResults

type UpsertResults struct {
	// contains filtered or unexported fields
}

func NewUpsertResults

func NewUpsertResults() *UpsertResults

func (*UpsertResults) AddLineExternalID

func (u *UpsertResults) AddLineExternalID(lineID string, externalID string)

func (*UpsertResults) GetExternalID

func (u *UpsertResults) GetExternalID() (string, bool)

func (*UpsertResults) GetInvoiceNumber

func (u *UpsertResults) GetInvoiceNumber() (string, bool)

func (*UpsertResults) GetLineExternalID

func (u *UpsertResults) GetLineExternalID(lineID string) (string, bool)

func (*UpsertResults) GetLineExternalIDs

func (u *UpsertResults) GetLineExternalIDs() map[string]string

func (*UpsertResults) SetExternalID

func (u *UpsertResults) SetExternalID(externalID string)

func (*UpsertResults) SetInvoiceNumber

func (u *UpsertResults) SetInvoiceNumber(invoiceNumber string)

type UsageBasedLine

type UsageBasedLine struct {
	ConfigID string `json:"configId"`

	// Price is the price of the usage based line. Note: this should be a pointer or marshaling will fail for
	// empty prices.
	Price                 *Price                 `json:"price"`
	FeatureKey            string                 `json:"featureKey"`
	Quantity              *alpacadecimal.Decimal `json:"quantity"`
	PreLinePeriodQuantity *alpacadecimal.Decimal `json:"preLinePeriodQuantity,omitempty"`
}

func (UsageBasedLine) Clone

func (i UsageBasedLine) Clone() *UsageBasedLine

func (UsageBasedLine) Equal

func (i UsageBasedLine) Equal(other *UsageBasedLine) bool

func (UsageBasedLine) Validate

func (i UsageBasedLine) Validate() error

type ValidationError

type ValidationError genericError

func (ValidationError) Error

func (e ValidationError) Error() string

func (ValidationError) Unwrap

func (e ValidationError) Unwrap() error

type ValidationIssue

type ValidationIssue struct {
	ID        string     `json:"id,omitempty"`
	CreatedAt time.Time  `json:"createdAt,omitempty"`
	UpdatedAt time.Time  `json:"updatedAt,omitempty"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`

	Severity  ValidationIssueSeverity `json:"severity"`
	Message   string                  `json:"message"`
	Code      string                  `json:"code,omitempty"`
	Component ComponentName           `json:"component,omitempty"`
	Path      string                  `json:"path,omitempty"`
}

func NewValidationError

func NewValidationError(code, message string) ValidationIssue

func NewValidationWarning

func NewValidationWarning(code, message string) ValidationIssue

func (ValidationIssue) EncodeAsErrorExtension

func (i ValidationIssue) EncodeAsErrorExtension() map[string]interface{}

func (ValidationIssue) Error

func (i ValidationIssue) Error() string

type ValidationIssueSeverity

type ValidationIssueSeverity string
const (
	ValidationIssueSeverityCritical ValidationIssueSeverity = "critical"
	ValidationIssueSeverityWarning  ValidationIssueSeverity = "warning"

	ValidationComponentOpenMeter = "openmeter"
)

func (ValidationIssueSeverity) Values

func (ValidationIssueSeverity) Values() []string

type ValidationIssues

type ValidationIssues []ValidationIssue

func ToValidationIssues

func ToValidationIssues(errIn error) (ValidationIssues, error)

ToValidationIssues converts an error into a list of validation issues If the error is nil, it returns nil If any error in the error tree is not wrapped in ValidationWithComponent or ValidationWithFieldPrefix and not an instance of ValidationIssue, it will return an error. This behavior allows us to have critical errors that are not validation issues.

func (ValidationIssues) AsError

func (v ValidationIssues) AsError() error

func (ValidationIssues) Clone

func (ValidationIssues) Map

func (ValidationIssues) RemoveMetaForCompare

func (v ValidationIssues) RemoveMetaForCompare() ValidationIssues

type VersionedCustomerUsageAttribution

type VersionedCustomerUsageAttribution struct {
	CustomerUsageAttribution `json:",inline"`
	Type                     string `json:"type"`
}

type WorkflowConfig

type WorkflowConfig struct {
	ID string `json:"id"`

	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt"`

	Timezone *timezone.Timezone `json:"timezone,omitempty"`

	Collection CollectionConfig `json:"collection"`
	Invoicing  InvoicingConfig  `json:"invoicing"`
	Payment    PaymentConfig    `json:"payment"`
}

func (WorkflowConfig) Validate

func (c WorkflowConfig) Validate() error

Directories

Path Synopsis
lineservice
lineservice package contains the implementation of the LineAdapter interface which acts as a adapter between the specific line types and the billing service.
lineservice package contains the implementation of the LineAdapter interface which acts as a adapter between the specific line types and the billing service.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL