File size: 9,894 Bytes
5a22efd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | package billing
import (
"fmt"
"github.com/openmeterio/openmeter/openmeter/app"
)
var (
// We want to return the invoice regardless of the validation issues, as invoices can have issues at numerous
// levels (e.g. missing default profile, missing customer override, missing billing profile, provider issues, etc.).
//
// We also return the validation issues. By adding codes to the validation issues, it will be easier for the clients (e.g. frontend) to handle the specific errors
// without having to resort to string matching.
//
// Given that invoicing depends on the billing and customer override service, we need to have these error types in place for
// all.
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")
ErrInvoiceProgressiveBillingNotSupported = NewValidationError("invoice_progressive_billing_not_supported", "progressive billing is not supported")
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")
ErrInvoiceCannotDeleteGathering = NewValidationError("invoice_cannot_delete_gathering", "gathering invoices cannot be deleted, please delete the upcoming lines instead")
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")
ErrInvoiceLineNoPeriodChangeForSubscriptionManagedLine = NewValidationError("invoice_line_no_period_change_for_subscription_managed_line", "invoice line period cannot be changed for subscription managed lines")
ErrInvoiceLineProgressiveBillingUsageDiscountUpdateForbidden = NewValidationError("invoice_line_progressive_billing_usage_discount_update_forbidden", "usage discount cannot be updated on a partially invoiced line")
ErrInvoiceCreateNoLines = NewValidationError("invoice_create_no_lines", "the new invoice would have no lines")
ErrInvoiceCreateUBPLineCustomerUsageAttributionInvalid = NewValidationError("invoice_create_ubp_line_customer_has_no_subjects", "creating an usage based line: customer usage attribution is invalid")
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")
ErrInvoiceLineSnapshotFailed = NewValidationError("invoice_line_snapshot_failed", "invoice line snapshot failed")
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")
ErrNamespaceLocked = NewValidationError("namespace_locked", "namespace is locked")
ErrInvoiceLineCreditsNotConsumedFully = NewValidationError("invoice_line_credits_not_consumed_fully", "credits not consumed fully")
ErrCannotUpdateChargeManagedLine = NewValidationError("cannot_update_charge_managed_line", "cannot update charge managed lines, please update the charge instead")
ErrCannotEditProgressivelyBilledUsageBasedLine = NewValidationError("cannot_edit_progressively_billed_usagebased_line", "cannot edit usagebased items that are progressively billed, update the charge instead")
ErrInvoiceLineFeatureKeyEditNotSupported = NewValidationError("invoice_line_feature_key_edit_not_supported", "feature key editing is not supported for charge-managed invoice lines, please delete the charge instead")
ErrInvoiceLineTaxConfigEditNotSupported = NewValidationError("invoice_line_tax_config_edit_not_supported", "tax config editing is not supported for charge-managed invoice lines, please delete the charge instead")
ErrInvoiceLineZeroAmountDeleteInstead = NewValidationError("invoice_line_zero_amount_delete_instead", "invoice line amount cannot be updated to zero, please delete the line instead")
ErrInvoiceLineZeroAmountCreate = NewValidationError("invoice_line_zero_amount_create", "creating zero amount flat fee invoice lines is not supported")
)
const (
ImmutableInvoiceHandlingNotSupportedErrorCode = "immutable_invoice_handling_not_supported"
)
var _ error = (*NotFoundError)(nil)
const (
EntityCustomerOverride = "BillingCustomerOverride"
EntityCustomer = "Customer"
EntityDefaultProfile = "DefaultBillingProfile"
EntityInvoice = "Invoice"
EntityInvoiceLine = "InvoiceLine"
)
type NotFoundError struct {
ID string
Entity string
Err error
}
func (e NotFoundError) Error() string {
// ID can be empty for default profiles
if e.ID == "" {
return e.Err.Error()
}
return fmt.Sprintf("%s [%s/%s]", e.Err, e.Entity, e.ID)
}
func (e NotFoundError) Unwrap() error {
return e.Err
}
type genericError struct {
Err error
}
func EncodeValidationIssues[T error](err T) map[string]interface{} {
validationIssues, _ := ToValidationIssues(err)
if len(validationIssues) == 0 {
return map[string]interface{}{}
}
// For HTTP calls we are usually interested in the first issue
// so we return it as the main error
out := validationIssues[0].EncodeAsErrorExtension()
if len(validationIssues) > 1 {
out["additionalIssues"] = validationIssues[1:]
}
return out
}
var _ error = (*ValidationError)(nil)
type ValidationError genericError
func (e ValidationError) Error() string {
return e.Err.Error()
}
func (e ValidationError) Unwrap() error {
return e.Err
}
var _ error = (*UpdateAfterDeleteError)(nil)
type UpdateAfterDeleteError genericError
func (e UpdateAfterDeleteError) Error() string {
return e.Err.Error()
}
func (e UpdateAfterDeleteError) Unwrap() error {
return e.Err
}
// AppError represents an error when interacting with an app.
var _ error = (*AppError)(nil)
type AppError struct {
AppID app.AppID
AppType app.AppType
Err error
}
func (e AppError) Error() string {
return fmt.Sprintf("app %s type with id %s in namespace %s: %s", e.AppType, e.AppID.ID, e.AppID.Namespace, e.Err.Error())
}
// ErrSnapshotInvalidDatabaseState is returned when the database state is invalid for snapshotting the line quantity.
// This can happen if the feature or meter is not found. In such cases we should transition the invoice to
// draft.invalid state.
type ErrSnapshotInvalidDatabaseState struct {
Err error
}
func (e ErrSnapshotInvalidDatabaseState) Error() string {
return fmt.Sprintf("snapshotting line quantity: %s", e.Err.Error())
}
func (e ErrSnapshotInvalidDatabaseState) Unwrap() error {
return e.Err
}
|