Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5848,6 +5848,7 @@ export const customerData = z
.object({
billingProfile: profileReference.optional(),
appData: appCustomerData.optional(),
effectiveBillingProfile: profileReference,
})
.describe('Billing customer data.')

Expand Down Expand Up @@ -15595,6 +15596,7 @@ export const customerDataWire = z
.strictObject({
billing_profile: profileReferenceWire.optional(),
app_data: appCustomerDataWire.optional(),
effective_billing_profile: profileReferenceWire,
})
.describe('Billing customer data.')

Expand Down
9 changes: 7 additions & 2 deletions api/spec/packages/aip-client-javascript/src/models/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4815,19 +4815,24 @@ export interface NotificationEventDeliveryStatus {
/** Billing customer data. */
export interface CustomerData {
/**
* The billing profile for the customer.
* The billing profile explicitly selected for the customer.
*
* If not provided, the default billing profile will be used.
*/
billingProfile?: ProfileReference
/** App customer data. */
appData?: AppCustomerData
/**
* The effective billing profile for the customer. Uses the explicitly selected
* billing profile when pinned, otherwise the current default billing profile.
*/
effectiveBillingProfile: ProfileReference
}

/** CustomerBillingData upsert request. */
export interface UpsertCustomerBillingDataRequest {
/**
* The billing profile for the customer.
* The billing profile explicitly selected for the customer.
*
* If not provided, the default billing profile will be used.
*/
Expand Down
10 changes: 9 additions & 1 deletion api/spec/packages/aip/src/customers/billing.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Customers;
@friendlyName("BillingCustomerData")
model CustomerBillingData {
/**
* The billing profile for the customer.
* The billing profile explicitly selected for the customer.
*
* If not provided, the default billing profile will be used.
*/
Expand All @@ -23,4 +23,12 @@ model CustomerBillingData {
@visibility(Lifecycle.Read, Lifecycle.Create, Lifecycle.Update)
@summary("App customer data")
app_data?: Apps.AppCustomerData;

/**
* The effective billing profile for the customer. Uses the explicitly selected
* billing profile when pinned, otherwise the current default billing profile.
*/
@visibility(Lifecycle.Read)
@summary("Effective billing profile")
effective_billing_profile: Common.BillingRoot.BillingProfileReference;
}
2,129 changes: 1,067 additions & 1,062 deletions api/v3/api.gen.go

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions api/v3/client/models_customers.go

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

12 changes: 10 additions & 2 deletions api/v3/handlers/customers/billing/get_billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,16 @@ func (h *handler) GetCustomerBilling() GetCustomerBillingHandler {
return resp, err
}

return GetCustomerBillingResponse{
BillingProfile: &api.BillingProfileReference{
var billingProfileReference *api.BillingProfileReference
if override.CustomerOverride != nil && override.CustomerOverride.Profile != nil {
billingProfileReference = &api.BillingProfileReference{
Id: override.CustomerOverride.Profile.ID,
}
}

return api.BillingCustomerData{
BillingProfile: billingProfileReference,
EffectiveBillingProfile: api.BillingProfileReference{
Id: override.MergedProfile.ID,
},
AppData: apiAppData,
Expand Down
38 changes: 29 additions & 9 deletions api/v3/handlers/customers/billing/update_billing.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,21 @@ func (h *handler) UpdateCustomerBilling() UpdateCustomerBillingHandler {
resp := UpdateCustomerBillingResponse{}

var billingProfile *billing.Profile
var customerOverride billing.CustomerOverrideWithDetails
var err error
// If the profile ID is not provided, we use the default profile
// App data must be written through the same effective profile used by GET.
if request.ProfileID == nil {
billingProfile, err = h.billingService.GetDefaultProfile(ctx, billing.GetDefaultProfileInput{
Namespace: request.CustomerID.Namespace,
customerOverride, err = h.billingService.GetCustomerOverride(ctx, billing.GetCustomerOverrideInput{
Customer: request.CustomerID,
Expand: billing.CustomerOverrideExpand{
Apps: true,
},
})
if err != nil {
return resp, err
return resp, fmt.Errorf("failed to resolve effective billing profile: %w", err)
}

billingProfile = &customerOverride.MergedProfile
} else {
// Get the billing profile by the provided profile ID
billingProfile, err = h.billingService.GetProfile(ctx, billing.GetProfileInput{
Expand All @@ -101,10 +107,6 @@ func (h *handler) UpdateCustomerBilling() UpdateCustomerBillingHandler {
}
}

resp.BillingProfile = &api.BillingProfileReference{
Id: billingProfile.ID,
}

if billingProfile.Apps == nil {
return resp, apierrors.NewInternalError(ctx, fmt.Errorf("apps are not expanded in billing profile"))
}
Expand All @@ -124,14 +126,32 @@ func (h *handler) UpdateCustomerBilling() UpdateCustomerBillingHandler {

// Override the billing profile if an ID was provided
if request.ProfileID != nil {
_, err = h.billingService.UpsertCustomerOverride(ctx, billing.UpsertCustomerOverrideInput{
customerOverride, err = h.billingService.UpsertCustomerOverride(ctx, billing.UpsertCustomerOverrideInput{
Namespace: request.CustomerID.Namespace,
CustomerID: request.CustomerID.ID,
ProfileID: billingProfile.ID,
})
if err != nil {
return resp, fmt.Errorf("failed to update billing profile: %w", err)
}

if customerOverride.CustomerOverride == nil || customerOverride.CustomerOverride.Profile == nil {
return resp, apierrors.NewInternalError(ctx, errors.New("customer override is not found"))
}

resp.BillingProfile = &api.BillingProfileReference{
Id: customerOverride.CustomerOverride.Profile.ID,
}
} else {
if customerOverride.CustomerOverride != nil && customerOverride.CustomerOverride.Profile != nil {
resp.BillingProfile = &api.BillingProfileReference{
Id: customerOverride.CustomerOverride.Profile.ID,
}
}
}

resp.EffectiveBillingProfile = api.BillingProfileReference{
Id: customerOverride.MergedProfile.ID,
}

return resp, nil
Expand Down
14 changes: 12 additions & 2 deletions api/v3/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8705,12 +8705,14 @@ components:
have access to features.
BillingCustomerData:
type: object
required:
- effective_billing_profile
properties:
billing_profile:
allOf:
- $ref: '#/components/schemas/BillingProfileReference'
description: |-
The billing profile for the customer.
The billing profile explicitly selected for the customer.

If not provided, the default billing profile will be used.
title: Billing profile
Expand All @@ -8719,6 +8721,14 @@ components:
- $ref: '#/components/schemas/BillingAppCustomerData'
description: App customer data.
title: App customer data
effective_billing_profile:
allOf:
- $ref: '#/components/schemas/BillingProfileReference'
description: |-
The effective billing profile for the customer. Uses the explicitly selected
billing profile when pinned, otherwise the current default billing profile.
title: Effective billing profile
readOnly: true
additionalProperties: false
description: Billing customer data.
BillingCustomerStripeCreateCheckoutSessionRequest:
Expand Down Expand Up @@ -19793,7 +19803,7 @@ components:
allOf:
- $ref: '#/components/schemas/BillingProfileReference'
description: |-
The billing profile for the customer.
The billing profile explicitly selected for the customer.

If not provided, the default billing profile will be used.
title: Billing profile
Expand Down
5 changes: 4 additions & 1 deletion openmeter/billing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ described in [Charges](charges/README.md).
A billing profile selects the workflow and the apps responsible for tax,
invoicing, and payment. A namespace has one default profile. A customer
override may select another profile and replace supported parts of its
workflow.
workflow. Without an explicit profile selection, the customer follows the current
namespace default, even when it has workflow overrides. The v3 customer billing
GET and PUT responses return an explicit selection as `billing_profile` (omitted
when unpinned) and the resolved profile as `effective_billing_profile`.

Installing a Custom Invoicing app with automatic profile creation uses Cloud's
initial Auto Collection preset: the installed app handles tax, invoicing, and
Expand Down
17 changes: 0 additions & 17 deletions openmeter/billing/adapter/customeroverride.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,6 @@ func (a *adapter) GetCustomerOverride(ctx context.Context, input billing.GetCust
return nil, err
}

if dbCustomerOverride.BillingProfileID == nil {
// Let's fetch the default billing profile
dbDefaultProfile, err := tx.db.BillingProfile.Query().
Where(billingprofile.Namespace(input.Customer.Namespace)).
Where(billingprofile.Default(true)).
Where(billingprofile.DeletedAtIsNil()).
WithWorkflowConfig(workflowConfigWithTaxCode(input.Customer.Namespace)).
Only(ctx)
if err != nil {
if !db.IsNotFound(err) {
return nil, err
}
}

dbCustomerOverride.Edges.BillingProfile = dbDefaultProfile
}

return tx.mapCustomerOverrideFromDB(ctx, dbCustomerOverride)
})
}
Expand Down
13 changes: 9 additions & 4 deletions openmeter/billing/customeroverride.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ type CustomerOverride struct {
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`

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

// Profile is the explicitly selected billing profile. If nil, the customer
// uses the current namespace's default profile.
Profile *Profile `json:"billingProfile,omitempty"`

Collection CollectionOverrideConfig `json:"collection"`
Invoicing InvoicingOverrideConfig `json:"invoicing"`
Expand Down Expand Up @@ -207,8 +210,10 @@ func (g GetCustomerAppInput) Validate() error {
}

if g.AppType == "" {
errs = append(errs, models.NewGenericPreConditionFailedError(
fmt.Errorf("app type is required")),
errs = append(
errs, models.NewGenericPreConditionFailedError(
fmt.Errorf("app type is required"),
),
)
}

Expand Down
Loading
Loading