billing

package
v1.0.0-beta.189 Latest Latest
Warning

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

Go to latest
Published: Feb 11, 2025 License: Apache-2.0 Imports: 25 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 (
	EventSubsystem metadata.EventSubsystem = "billing"
)
View Source
const (
	ImmutableInvoiceHandlingNotSupportedErrorCode = "immutable_invoice_handling_not_supported"
)
View Source
const (
	// LineMaximumSpendReferenceID is a discount applied due to maximum spend.
	LineMaximumSpendReferenceID = "line_maximum_spend"
)

Variables

View Source
var (
	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")
	ErrDefaultProfileCannotBeDeleted = NewValidationError("default_profile_cannot_be_deleted", "default profile cannot be deleted")
	ErrDefaultProfileCannotBeUnset   = NewValidationError("default_profile_cannot_be_unset", "default profile cannot be unset")

	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", "invoice lines are not billable")
	ErrInvoiceEmpty              = NewValidationError("invoice_empty", "invoice is empty")
	ErrInvoiceNotFound           = NewValidationError("invoice_not_found", "invoice not found")
	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)")
	ErrInvoiceLineNoPeriodChangeForSplitLine     = NewValidationError("invoice_line_no_period_change_for_split_line", "invoice line period cannot be changed for split lines")
	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")

	ErrInvoiceDiscountInvalidLineReference                  = NewValidationError("invoice_discount_invalid_line_reference", "invoice discount references non-existing line")
	ErrInvoiceDiscountNoWildcardDiscountOnGatheringInvoices = NewValidationError("invoice_discount_no_wildcard_discount_on_gathering_invoices", "wildcard discount on gathering invoices is not allowed")
)
View Source
var (
	GatheringInvoiceSequenceNumber = SequenceDefinition{
		Template: "GATHER-{{.CustomerPrefix}}-{{.Currency}}-{{.NextSequenceNumber}}",
		Scope:    "invoices/gathering",
	}
	DraftInvoiceSequenceNumber = SequenceDefinition{
		Template: "DRAFT-{{.CustomerPrefix}}-{{.NextSequenceNumber}}",
		Scope:    "invoices/draft",
	}
)
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()),
		ProgressiveBilling: false,
		DefaultTaxConfig:   nil,
	},
	Payment: PaymentConfig{
		CollectionMethod: CollectionMethodChargeAutomatically,
	},
}
View Source
var InvoiceExpandAll = InvoiceExpand{
	Discounts:    true,
	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 MergeUpsertInvoiceResult

func MergeUpsertInvoiceResult(invoice *Invoice, result *UpsertInvoiceResult) error

MergeUpsertInvoiceResult merges the upsert invoice result into the invoice.

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 AdapterGetProfileResponse

type AdapterGetProfileResponse struct {
	BaseProfile

	WorkflowConfigID string `json:"workflowConfigId"`
}

func (*AdapterGetProfileResponse) BaseProfileOrEmpty

func (r *AdapterGetProfileResponse) BaseProfileOrEmpty() *BaseProfile

type AdvanceInvoiceEvent

type AdvanceInvoiceEvent struct {
	Invoice    InvoiceID `json:"invoice"`
	CustomerID string    `json:"customer_id"`
}

func (AdvanceInvoiceEvent) EventMetadata

func (e AdvanceInvoiceEvent) EventMetadata() metadata.EventMetadata

func (AdvanceInvoiceEvent) EventName

func (e AdvanceInvoiceEvent) EventName() string

func (AdvanceInvoiceEvent) Validate

func (e AdvanceInvoiceEvent) Validate() error

type AdvanceInvoiceInput

type AdvanceInvoiceInput = InvoiceID

type AdvancementStrategy

type AdvancementStrategy string
const (
	// ForgegroundAdvancementStrategy is the strategy where the invoice is advanced immediately as part
	// of the same transaction. Should be used in workers as advancement might take long time and we don't want
	// to block a HTTP request for that long.
	ForegroundAdvancementStrategy AdvancementStrategy = "foreground"
	// QueuedAdvancementStrategy is the strategy where the invoice is advanced in a separate worker (billing-worker).
	// This is useful for cases where the advancement might take a long time and we don't want to block the current
	// HTTP request.
	QueuedAdvancementStrategy AdvancementStrategy = "queued"
)

func (AdvancementStrategy) Validate

func (s AdvancementStrategy) Validate() error

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 AppError

type AppError struct {
	AppID   app.AppID
	AppType app.AppType
	Err     error
}

func (AppError) Error

func (e AppError) Error() string

type AppReference

type AppReference struct {
	ID   string      `json:"id"`
	Type app.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) ProfileID

func (p BaseProfile) ProfileID() ProfileID

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 app.AppType, cap app.CapabilityType, op InvoiceOperation) ComponentName

type ConfigIntrospectionService

type ConfigIntrospectionService interface {
	GetAdvancementStrategy() AdvancementStrategy
}

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  customer.Customer
	Profile   Profile
	Number    string
	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) ([]customer.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 CustomerUsageAttribution

type CustomerUsageAttribution = customer.CustomerUsageAttribution

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 = ProfileID

type EventInvoice

type EventInvoice Invoice

func NewEventInvoice

func NewEventInvoice(invoice Invoice) EventInvoice

type ExternalIDType

type ExternalIDType string
const (
	InvoicingExternalIDType ExternalIDType = "invoicing"
	PaymentExternalIDType   ExternalIDType = "payment"
	TaxExternalIDType       ExternalIDType = "tax"
)

func (ExternalIDType) Validate

func (t ExternalIDType) Validate() error

type FinalizeInvoiceResult

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

func NewFinalizeInvoiceResult

func NewFinalizeInvoiceResult() *FinalizeInvoiceResult

func (*FinalizeInvoiceResult) GetInvoiceNumber

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

func (*FinalizeInvoiceResult) GetPaymentExternalID

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

func (*FinalizeInvoiceResult) GetSentToCustomerAt

func (f *FinalizeInvoiceResult) GetSentToCustomerAt() (time.Time, bool)

func (*FinalizeInvoiceResult) SetInvoiceNumber

func (f *FinalizeInvoiceResult) SetInvoiceNumber(invoiceNumber string) *FinalizeInvoiceResult

func (*FinalizeInvoiceResult) SetPaymentExternalID

func (f *FinalizeInvoiceResult) SetPaymentExternalID(paymentExternalID string) *FinalizeInvoiceResult

func (*FinalizeInvoiceResult) SetSentToCustomerAt

func (f *FinalizeInvoiceResult) SetSentToCustomerAt(sentToCustomerAt time.Time) *FinalizeInvoiceResult

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 customer.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 ProfileID
	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 = ProfileID

type Invoice

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

	// Entities external to the invoice itself
	Lines            LineChildren     `json:"lines,omitempty"`
	ValidationIssues ValidationIssues `json:"validationIssues,omitempty"`
	Discounts        InvoiceDiscounts `json:"discounts,omitempty"`

	Totals Totals `json:"totals"`

	// private fields required by the service
	ExpandedFields InvoiceExpand `json:"-"`
	// contains filtered or unexported fields
}

func (Invoice) Clone

func (i Invoice) Clone() Invoice

func (*Invoice) FlattenLinesByID

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

func (*Invoice) GetDiscountSnapshot

func (i *Invoice) GetDiscountSnapshot() InvoiceDiscounts

func (*Invoice) GetLeafLinesWithConsolidatedTaxBehavior

func (i *Invoice) GetLeafLinesWithConsolidatedTaxBehavior() []*Line

GetLeafLinesWithConsolidatedTaxBehavior returns the leaf lines with the tax behavior set to the invoice's tax behavior unless the line already has a tax behavior set.

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

func (*Invoice) Snapshot

func (i *Invoice) Snapshot()

func (Invoice) Validate

func (i Invoice) Validate() error

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 InvoiceAppAdapter

type InvoiceAppAdapter interface {
	UpdateInvoiceFields(ctx context.Context, input UpdateInvoiceFieldsInput) error
}

type InvoiceAppService

type InvoiceAppService interface {
	// TriggerInvoice triggers the invoice state machine to start processing the invoice
	TriggerInvoice(ctx context.Context, input InvoiceTriggerServiceInput) error

	// UpdateInvoiceFields updates the fields of an invoice which are not managed by the state machine
	// These are usually metadata fields settable after the invoice has been finalized
	UpdateInvoiceFields(ctx context.Context, input UpdateInvoiceFieldsInput) error
}

type InvoiceAvailableActionDetails

type InvoiceAvailableActionDetails struct {
	ResultingState InvoiceStatus `json:"resultingState"`
}

type InvoiceAvailableActionInvoiceDetails

type InvoiceAvailableActionInvoiceDetails struct{}

type InvoiceAvailableActions

type InvoiceAvailableActions struct {
	Advance *InvoiceAvailableActionDetails        `json:"advance,omitempty"`
	Approve *InvoiceAvailableActionDetails        `json:"approve,omitempty"`
	Delete  *InvoiceAvailableActionDetails        `json:"delete,omitempty"`
	Retry   *InvoiceAvailableActionDetails        `json:"retry,omitempty"`
	Void    *InvoiceAvailableActionDetails        `json:"void,omitempty"`
	Invoice *InvoiceAvailableActionInvoiceDetails `json:"invoice,omitempty"`
}

type InvoiceAvailableActionsFilter

type InvoiceAvailableActionsFilter string
const (
	InvoiceAvailableActionsFilterAdvance InvoiceAvailableActionsFilter = "advance"
	InvoiceAvailableActionsFilterApprove InvoiceAvailableActionsFilter = "approve"
)

func (InvoiceAvailableActionsFilter) Validate

func (f InvoiceAvailableActionsFilter) Validate() error

func (InvoiceAvailableActionsFilter) Values

type InvoiceBase

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

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

	Type InvoiceType `json:"type"`

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

	Currency      currencyx.Code       `json:"currency,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"`
	SentToCustomerAt *time.Time `json:"sentToCustomerAt,omitempty"`

	CollectionAt *time.Time `json:"collectionAt,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"`
}

func (InvoiceBase) Validate

func (i InvoiceBase) Validate() error

type InvoiceCreatedEvent

type InvoiceCreatedEvent struct {
	EventInvoice `json:",inline"`
}

func NewInvoiceCreatedEvent

func NewInvoiceCreatedEvent(invoice Invoice) InvoiceCreatedEvent

func (InvoiceCreatedEvent) EventMetadata

func (e InvoiceCreatedEvent) EventMetadata() metadata.EventMetadata

func (InvoiceCreatedEvent) EventName

func (e InvoiceCreatedEvent) EventName() string

func (InvoiceCreatedEvent) Validate

func (e InvoiceCreatedEvent) Validate() error

type InvoiceCustomer

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

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

func (*InvoiceCustomer) Validate

func (i *InvoiceCustomer) Validate() error

type InvoiceDiscount

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

func NewInvoiceDiscountFrom

func NewInvoiceDiscountFrom[T InvoiceDiscountPercentage](v T) InvoiceDiscount

func (*InvoiceDiscount) AsPercentage

func (d *InvoiceDiscount) AsPercentage() (InvoiceDiscountPercentage, error)

func (*InvoiceDiscount) Clone

func (d *InvoiceDiscount) Clone() InvoiceDiscount

func (*InvoiceDiscount) DiscountBase

func (d *InvoiceDiscount) DiscountBase() (InvoiceDiscountBase, error)

func (*InvoiceDiscount) Equals

func (d *InvoiceDiscount) Equals(other InvoiceDiscount) bool

func (*InvoiceDiscount) FromPercentage

func (d *InvoiceDiscount) FromPercentage(v InvoiceDiscountPercentage)

func (*InvoiceDiscount) MarshalJSON

func (d *InvoiceDiscount) MarshalJSON() ([]byte, error)

func (*InvoiceDiscount) Type

func (*InvoiceDiscount) UnmarshalJSON

func (d *InvoiceDiscount) UnmarshalJSON(bytes []byte) error

func (*InvoiceDiscount) Validate

func (d *InvoiceDiscount) Validate() error

type InvoiceDiscountBase

type InvoiceDiscountBase struct {
	models.ManagedResource

	InvoiceID string              `json:"invoice_id"`
	Type      InvoiceDiscountType `json:"type"`
	LineIDs   []string            `json:"line_ids"`
}

func (InvoiceDiscountBase) DiscountID

func (d InvoiceDiscountBase) DiscountID() InvoiceDiscountID

func (InvoiceDiscountBase) Equals

func (*InvoiceDiscountBase) Validate

func (d *InvoiceDiscountBase) Validate() error

type InvoiceDiscountID

type InvoiceDiscountID models.NamespacedID

type InvoiceDiscountPercentage

type InvoiceDiscountPercentage struct {
	InvoiceDiscountBase

	Percentage alpacadecimal.Decimal `json:"percentage"`
}

func (InvoiceDiscountPercentage) Clone

func (*InvoiceDiscountPercentage) Equals

func (*InvoiceDiscountPercentage) Validate

func (d *InvoiceDiscountPercentage) Validate() error

type InvoiceDiscountType

type InvoiceDiscountType string
const (
	PercentageDiscountType InvoiceDiscountType = "percentage"
)

func (InvoiceDiscountType) Values

func (d InvoiceDiscountType) Values() []string

type InvoiceDiscounts

type InvoiceDiscounts struct {
	mo.Option[[]InvoiceDiscount]
}

func NewInvoiceDiscounts

func NewInvoiceDiscounts(v []InvoiceDiscount) InvoiceDiscounts

func (*InvoiceDiscounts) Append

func (d *InvoiceDiscounts) Append(discounts ...InvoiceDiscount)

func (InvoiceDiscounts) Clone

func (InvoiceDiscounts) Validate

func (d InvoiceDiscounts) Validate() error

type InvoiceExpand

type InvoiceExpand struct {
	Discounts    bool
	Preceding    bool
	WorkflowApps bool

	Lines        bool
	DeletedLines bool
	SplitLines   bool

	// RecalculateGatheringInvoice is used to calculate the totals and status details of the invoice when gathering,
	// this is temporary until we implement the full progressive billing stack, including gathering invoice recalculations.
	RecalculateGatheringInvoice bool
}

func (InvoiceExpand) SetDeletedLines

func (e InvoiceExpand) SetDeletedLines(v bool) InvoiceExpand

func (InvoiceExpand) SetLines

func (e InvoiceExpand) SetLines(v bool) InvoiceExpand

func (InvoiceExpand) SetRecalculateGatheringInvoice

func (e InvoiceExpand) SetRecalculateGatheringInvoice(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"`
}

func (*InvoiceExternalIDs) GetInvoicingOrEmpty

func (i *InvoiceExternalIDs) GetInvoicingOrEmpty() string

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)
	GetLinesForSubscription(ctx context.Context, input GetLinesForSubscriptionInput) ([]*Line, error)
}

type InvoiceLineManagedBy

type InvoiceLineManagedBy string
const (
	// SubscriptionManagedLine is a line that is managed by a subscription.
	SubscriptionManagedLine InvoiceLineManagedBy = "subscription"
	// SystemManagedLine is a line that is managed by the system (non editable, detailed lines)
	SystemManagedLine InvoiceLineManagedBy = "system"
	// ManuallyManagedLine is a line that is managed manually (e.g. overridden by our API users)
	ManuallyManagedLine InvoiceLineManagedBy = "manual"
)

func (InvoiceLineManagedBy) Values

func (InvoiceLineManagedBy) Values() []string

type InvoiceLineService

type InvoiceLineService interface {
	CreatePendingInvoiceLines(ctx context.Context, input CreateInvoiceLinesInput) ([]*Line, error)
	GetLinesForSubscription(ctx context.Context, input GetLinesForSubscriptionInput) ([]*Line, error)
	// SnapshotLineQuantity returns an updated line with the quantity snapshoted from meters
	// the invoice is used as contextual information to the call.
	SnapshotLineQuantity(ctx context.Context, input SnapshotLineQuantityInput) (*Line, 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 InvoiceOperation

type InvoiceOperation string
const (
	InvoiceOpValidate        InvoiceOperation = "validate"
	InvoiceOpSync            InvoiceOperation = "sync"
	InvoiceOpDelete          InvoiceOperation = "delete"
	InvoiceOpFinalize        InvoiceOperation = "finalize"
	InvoiceOpInitiatePayment InvoiceOperation = "initiate_payment"

	InvoiceOpPostAdvanceHook InvoiceOperation = "post_advance_hook"
	InvoiceOpTriggerInvoice  InvoiceOperation = "trigger_invoice"
)

func (InvoiceOperation) Validate

func (o InvoiceOperation) Validate() error

type InvoicePendingLinesInput

type InvoicePendingLinesInput struct {
	Customer customer.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
	// UpdateInvoice updates an invoice as a whole
	UpdateInvoice(ctx context.Context, input UpdateInvoiceInput) (Invoice, error)

	// SimulateInvoice generates an invoice based on the provided input, but does not persist it
	// can be used to execute the invoice generation logic without actually creating an invoice in the database
	SimulateInvoice(ctx context.Context, input SimulateInvoiceInput) (Invoice, error)
	// UpsertValidationIssues upserts validation errors to the invoice bypassing the state machine, can only be
	// used on invoices in immutable state.
	UpsertValidationIssues(ctx context.Context, input UpsertValidationIssuesInput) 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"

	InvoiceStatusIssuingSyncing    InvoiceStatus = "issuing.syncing"
	InvoiceStatusIssuingSyncFailed InvoiceStatus = "issuing.failed"

	InvoiceStatusIssued InvoiceStatus = "issued"

	InvoiceStatusPaymentProcessingPending        InvoiceStatus = "payment_processing.pending"
	InvoiceStatusPaymentProcessingFailed         InvoiceStatus = "payment_processing.failed"
	InvoiceStatusPaymentProcessingActionRequired InvoiceStatus = "payment_processing.action_required"

	InvoiceStatusOverdue InvoiceStatus = "overdue"

	InvoiceStatusPaid InvoiceStatus = "paid"

	InvoiceStatusUncollectible InvoiceStatus = "uncollectible"

	InvoiceStatusVoided InvoiceStatus = "voided"
)

func (InvoiceStatus) IsFailed

func (s InvoiceStatus) IsFailed() bool

func (InvoiceStatus) Matches

func (s InvoiceStatus) Matches(statuses ...InvoiceStatusMatcher) bool

func (InvoiceStatus) MatchesInvoiceStatus

func (s InvoiceStatus) MatchesInvoiceStatus(status InvoiceStatus) 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 InvoiceStatusCategory

type InvoiceStatusCategory string
const (
	InvoiceStatusCategoryGathering         InvoiceStatusCategory = "gathering"
	InvoiceStatusCategoryDraft             InvoiceStatusCategory = "draft"
	InvoiceStatusCategoryDelete            InvoiceStatusCategory = "delete"
	InvoiceStatusCategoryDeleted           InvoiceStatusCategory = "deleted"
	InvoiceStatusCategoryIssuing           InvoiceStatusCategory = "issuing"
	InvoiceStatusCategoryIssued            InvoiceStatusCategory = "issued"
	InvoiceStatusCategoryPaymentProcessing InvoiceStatusCategory = "payment_processing"
	InvoiceStatusCategoryOverdue           InvoiceStatusCategory = "overdue"
	InvoiceStatusCategoryPaid              InvoiceStatusCategory = "paid"
	InvoiceStatusCategoryUncollectible     InvoiceStatusCategory = "uncollectible"
	InvoiceStatusCategoryVoided            InvoiceStatusCategory = "voided"
)

func (InvoiceStatusCategory) MatchesInvoiceStatus

func (s InvoiceStatusCategory) MatchesInvoiceStatus(status InvoiceStatus) bool

type InvoiceStatusDetails

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

type InvoiceStatusMatcher

type InvoiceStatusMatcher interface {
	MatchesInvoiceStatus(InvoiceStatus) bool
}

type InvoiceTrigger

type InvoiceTrigger = stateless.Trigger
var (
	// TriggerRetry is used to retry a state transition that failed, used by the end user to invoke it manually
	TriggerRetry InvoiceTrigger = "trigger_retry"
	// TriggerApprove is used to approve a state manually
	TriggerApprove InvoiceTrigger = "trigger_approve"
	// TriggerNext is used to advance the invoice to the next state if automatically possible
	TriggerNext InvoiceTrigger = "trigger_next"
	// TriggerFailed is used to trigger the failure state transition associated with the current state
	TriggerFailed InvoiceTrigger = "trigger_failed"
	// TriggerUpdated is used to trigger a change in the invoice (we are using this to calculate the immutable states
	// and trigger re-validation)
	TriggerUpdated InvoiceTrigger = "trigger_updated"
	// triggerDelete is used to delete the invoice
	TriggerDelete InvoiceTrigger = "trigger_delete"

	// TriggerPaid is used to signify that the invoice has been paid
	TriggerPaid InvoiceTrigger = "trigger_paid"
	// TriggerActionRequired is used to signify that the invoice requires action
	TriggerActionRequired InvoiceTrigger = "trigger_action_required"

	// TriggerPaymentUncollectible is used to signify that the invoice is uncollectible
	TriggerPaymentUncollectible InvoiceTrigger = "trigger_payment_uncollectible"
	// TriggerPaymentOverdue is used to signify that the invoice is overdue
	TriggerPaymentOverdue InvoiceTrigger = "trigger_payment_overdue"

	// TriggerVoid is used to signify that the invoice has been voided (e.g. created by mistake)
	TriggerVoid InvoiceTrigger = "trigger_void"
)

type InvoiceTriggerInput

type InvoiceTriggerInput struct {
	Invoice InvoiceID
	// Trigger specifies the trigger that caused the invoice to be changed, only triggerPaid and triggerPayment* are allowed
	Trigger InvoiceTrigger

	ValidationErrors *InvoiceTriggerValidationInput
}

func (InvoiceTriggerInput) Validate

func (i InvoiceTriggerInput) Validate() error

type InvoiceTriggerServiceInput

type InvoiceTriggerServiceInput struct {
	InvoiceTriggerInput

	// AppType is the type of the app that triggered the invoice
	AppType app.AppType
	// Capability is the capability of the app that was processing this trigger
	Capability app.CapabilityType
}

func (InvoiceTriggerServiceInput) Validate

func (i InvoiceTriggerServiceInput) Validate() error

type InvoiceTriggerValidationInput

type InvoiceTriggerValidationInput struct {
	// Operation specifies the operation that yielded the validation errors
	// previous validation errors from this operation will be replaced by this one
	Operation InvoiceOperation
	Errors    []error
}

func (InvoiceTriggerValidationInput) Validate

func (i InvoiceTriggerValidationInput) Validate() error

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
}

Warning: The received invoice is

  • read-only (e.g. any changes made to it are lost to prevent manipulation of the invoice state)
  • reflects the current in memory state of the invoice, thus if you fetched from the db an earlier version of the invoice will be passed, thus do not call any billingService methods from these callbacks.

func GetApp

func GetApp(app app.App) (InvoicingApp, error)

GetApp returns the app from the app entity

type InvoicingAppPostAdvanceHook

type InvoicingAppPostAdvanceHook interface {
	// PostAdvanceInvoiceHook is called after the invoice has been advanced to the next stable state
	// (e.g. no next trigger is available)
	//
	// Can be used by the app to perform additional actions in case there are some post-processing steps
	// required on the invoice.
	PostAdvanceInvoiceHook(ctx context.Context, invoice Invoice) (*PostAdvanceHookResult, error)
}

type InvoicingConfig

type InvoicingConfig struct {
	AutoAdvance        bool                      `json:"autoAdvance,omitempty"`
	DraftPeriod        datex.Period              `json:"draftPeriod,omitempty"`
	DueAfter           datex.Period              `json:"dueAfter,omitempty"`
	ProgressiveBilling bool                      `json:"progressiveBilling,omitempty"`
	DefaultTaxConfig   *productcatalog.TaxConfig `json:"defaultTaxConfig,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"`
	ProgressiveBilling *bool                     `json:"progressiveBilling,omitempty"`
	DefaultTaxConfig   *productcatalog.TaxConfig `json:"defaultTaxConfig,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) CloneWithoutChildren

func (i Line) CloneWithoutChildren() *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) FlattenDiscountsByID

func (i Line) FlattenDiscountsByID() map[string]LineDiscount

FlattenDiscountsByID returns all discounts for the line and its children.

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"`
	ManagedBy   InvoiceLineManagedBy `json:"managedBy"`
	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() 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) NonDeletedLineCount

func (c LineChildren) NonDeletedLineCount() int

func (*LineChildren) RemoveByID

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

func (*LineChildren) ReplaceByID

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

func (LineChildren) Validate

func (c LineChildren) Validate() error

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"`
	ExternalIDs            LineExternalIDs       `json:"externalIDs,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 ListInvoicesExternalIDFilter

type ListInvoicesExternalIDFilter struct {
	Type ExternalIDType
	IDs  []string
}

func (ListInvoicesExternalIDFilter) Validate

func (f ListInvoicesExternalIDFilter) Validate() error

type ListInvoicesInput

type ListInvoicesInput struct {
	pagination.Page

	Namespaces []string
	IDs        []string
	Customers  []string
	// Statuses searches by short InvoiceStatus (e.g. draft, issued)
	Statuses []string

	HasAvailableAction []InvoiceAvailableActionsFilter

	// ExtendedStatuses searches by exact InvoiceStatus
	ExtendedStatuses []InvoiceStatus
	Currencies       []currencyx.Code

	IssuedAfter  *time.Time
	IssuedBefore *time.Time

	IncludeDeleted bool

	// DraftUtil allows to filter invoices which have their draft state expired based on the provided time.
	// Invoice is expired if the time defined by Invoice.DraftUntil is in the past compared to ListInvoicesInput.DraftUntil.
	DraftUntil *time.Time

	// CollectionAt allows to filter invoices which have their collection_at attribute is in the past compared
	// to the time provided in CollectionAt parameter.
	CollectionAt *time.Time

	Expand InvoiceExpand

	ExternalIDs *ListInvoicesExternalIDFilter

	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 = customer.CustomerID

type LockInvoicesForUpdateInput

type LockInvoicesForUpdateInput = genericMultiInvoiceInput

type Metadata

type Metadata map[string]string

type NextSequenceNumberInput

type NextSequenceNumberInput struct {
	Namespace string
	Scope     string
}

func (NextSequenceNumberInput) Validate

func (n NextSequenceNumberInput) Validate() error

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 `json:"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). TODO: Lets merge this with recurrence.Period

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 PostAdvanceHookResult

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

func NewPostAdvanceHookResult

func NewPostAdvanceHookResult() *PostAdvanceHookResult

func (*PostAdvanceHookResult) GetTriggerToInvoke

func (p *PostAdvanceHookResult) GetTriggerToInvoke() *InvoiceTriggerInput

func (*PostAdvanceHookResult) InvokeTrigger

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) (*AdapterGetProfileResponse, error)
	GetDefaultProfile(ctx context.Context, input GetDefaultProfileInput) (*AdapterGetProfileResponse, error)
	DeleteProfile(ctx context.Context, input DeleteProfileInput) error
	UpdateProfile(ctx context.Context, input UpdateProfileAdapterInput) (*BaseProfile, error)
	UnsetDefaultProfile(ctx context.Context, input UnsetDefaultProfileInput) error
	IsAppUsed(ctx context.Context, appID app.AppID) (bool, 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       app.App `json:"tax"`
	Invoicing app.App `json:"invoicing"`
	Payment   app.App `json:"payment"`
}

type ProfileExpand

type ProfileExpand struct {
	Apps bool
}

func (ProfileExpand) Validate

func (e ProfileExpand) Validate() error

type ProfileID

type ProfileID models.NamespacedID

func (ProfileID) Validate

func (p ProfileID) 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
	IsAppUsed(ctx context.Context, appID app.AppID) (bool, error)
}

type ProfileWithCustomerDetails

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

func (ProfileWithCustomerDetails) Validate

func (p ProfileWithCustomerDetails) Validate() error

type RetryInvoiceInput

type RetryInvoiceInput = InvoiceID

type SequenceAdapter

type SequenceAdapter interface {
	NextSequenceNumber(ctx context.Context, input NextSequenceNumberInput) (alpacadecimal.Decimal, error)
}

type SequenceDefinition

type SequenceDefinition struct {
	Template string
	Scope    string
}

func (SequenceDefinition) Validate

func (d SequenceDefinition) Validate() error

type SequenceGenerationInput

type SequenceGenerationInput struct {
	Namespace    string
	CustomerName string
	Currency     currencyx.Code
}

func (SequenceGenerationInput) Validate

func (i SequenceGenerationInput) Validate() error

type SequenceService

type SequenceService interface {
	GenerateInvoiceSequenceNumber(ctx context.Context, in SequenceGenerationInput, def SequenceDefinition) (string, error)
}

type SimulateInvoiceInput

type SimulateInvoiceInput struct {
	CustomerID customer.CustomerID

	Number   *string
	Currency currencyx.Code
	Lines    LineChildren
}

func (SimulateInvoiceInput) Validate

func (i SimulateInvoiceInput) Validate() error

type SnapshotLineQuantityInput

type SnapshotLineQuantityInput struct {
	Invoice *Invoice
	Line    *Line
}

func (SnapshotLineQuantityInput) Validate

func (i SnapshotLineQuantityInput) Validate() error

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 UnsetDefaultProfileInput

type UnsetDefaultProfileInput = ProfileID

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 UpdateInvoiceFieldsInput

type UpdateInvoiceFieldsInput struct {
	Invoice          InvoiceID
	SentToCustomerAt mo.Option[*time.Time]
}

func (UpdateInvoiceFieldsInput) Validate

func (i UpdateInvoiceFieldsInput) Validate() error

type UpdateInvoiceInput

type UpdateInvoiceInput struct {
	Invoice InvoiceID
	EditFn  func(*Invoice) error
	// IncludeDeletedLines signals the update to populate the deleted lines into the lines field, for the edit function
	IncludeDeletedLines bool
}

func (UpdateInvoiceInput) Validate

func (i UpdateInvoiceInput) Validate() error

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]
	ManagedBy mo.Option[InvoiceLineManagedBy]
	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) ProfileID

func (i UpdateProfileInput) ProfileID() ProfileID

func (UpdateProfileInput) Validate

func (i UpdateProfileInput) Validate() error

type UpsertCustomerOverrideAdapterInput

type UpsertCustomerOverrideAdapterInput = customer.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 {
	LineDiscountExternalIDs map[string]string
	// contains filtered or unexported fields
}

func NewUpsertResults

func NewUpsertResults() *UpsertResults

func (*UpsertResults) AddLineDiscountExternalID

func (u *UpsertResults) AddLineDiscountExternalID(lineDiscountID string, externalID string) *UpsertResults

func (*UpsertResults) AddLineExternalID

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

func (*UpsertResults) GetExternalID

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

func (*UpsertResults) GetInvoiceNumber

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

func (*UpsertResults) GetLineDiscountExternalID

func (u *UpsertResults) GetLineDiscountExternalID(lineDiscountID string) (string, bool)

func (*UpsertResults) GetLineDiscountExternalIDs

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

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) *UpsertResults

func (*UpsertResults) SetInvoiceNumber

func (u *UpsertResults) SetInvoiceNumber(invoiceNumber string) *UpsertResults

type UpsertValidationIssuesInput

type UpsertValidationIssuesInput struct {
	Invoice InvoiceID
	Issues  ValidationIssues
}

func (UpsertValidationIssuesInput) Validate

func (i UpsertValidationIssuesInput) Validate() error

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 {
	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