Skip to content

Commit 813b0fa

Browse files
authored
feat(billing): add customer billing profile selection endpoint (#5357)
1 parent 02013cc commit 813b0fa

25 files changed

Lines changed: 1988 additions & 1134 deletions

‎api/spec/packages/aip-client-javascript/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ The full call path, HTTP route, and a short description are listed below.
292292
| `client.customers.upsert` | `PUT /openmeter/customers/{customerId}` | Upsert customer |
293293
| `client.customers.delete` | `DELETE /openmeter/customers/{customerId}` | Delete customer |
294294
| `client.customers.billing.get` | `GET /openmeter/customers/{customerId}/billing` | Get customer billing data |
295-
| `client.customers.billing.update` | `PUT /openmeter/customers/{customerId}/billing` | Update customer billing data |
295+
| `client.customers.billing.update` | `PUT /openmeter/customers/{customerId}/billing` | Update billing data while preserving the current profile selection when billing_profile is omitted or null. Use the dedicated billing profile endpoint to remove the selection. |
296+
| `client.customers.billing.updateProfile` | `PUT /openmeter/customers/{customerId}/billing/profile` | Set the customer's explicit billing profile selection. Omitting billing_profile or setting it to null removes the selection and uses the current namespace default. |
296297
| `client.customers.billing.updateAppData` | `PUT /openmeter/customers/{customerId}/billing/app-data` | Update customer billing app data |
297298
| `client.customers.billing.createStripeCheckoutSession` | `POST /openmeter/customers/{customerId}/billing/stripe/checkout-sessions` | Create a [Stripe Checkout Session](https://docs.stripe.com/payments/checkout) for the customer. Creates a Checkout Session for collecting payment method information from customers. The session operates in "setup" mode, which collects payment details without charging the customer immediately. The collected payment method can be used for future subscription billing. For hosted checkout sessions, redirect customers to the returned URL. For embedded sessions, use the client_secret to initialize Stripe.js in your application. |
298299
| `client.customers.billing.createStripePortalSession` | `POST /openmeter/customers/{customerId}/billing/stripe/portal-sessions` | Create Stripe Customer Portal Session. Useful to redirect the customer to the Stripe Customer Portal to manage their payment methods, change their billing address and access their invoice history. Only returns URL if the customer billing profile is linked to a stripe app and customer. |

‎api/spec/packages/aip-client-javascript/src/funcs/customers.ts‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import type {
2727
GetCustomerBillingResponse,
2828
UpdateCustomerBillingRequest,
2929
UpdateCustomerBillingResponse,
30+
UpdateCustomerBillingProfileRequest,
31+
UpdateCustomerBillingProfileResponse,
3032
UpdateCustomerBillingAppDataRequest,
3133
UpdateCustomerBillingAppDataResponse,
3234
CreateCustomerStripeCheckoutSessionRequest,
@@ -289,6 +291,10 @@ export function getCustomerBilling(
289291
/**
290292
* Update customer billing data
291293
*
294+
* Update billing data while preserving the current profile selection when
295+
* billing_profile is omitted or null. Use the dedicated billing profile endpoint
296+
* to remove the selection.
297+
*
292298
* PUT /openmeter/customers/{customerId}/billing
293299
*/
294300
export function updateCustomerBilling(
@@ -328,6 +334,58 @@ export function updateCustomerBilling(
328334
})
329335
}
330336

337+
/**
338+
* Update customer billing profile
339+
*
340+
* Set the customer's explicit billing profile selection. Omitting billing_profile
341+
* or setting it to null removes the selection and uses the current namespace
342+
* default.
343+
*
344+
* PUT /openmeter/customers/{customerId}/billing/profile
345+
*/
346+
export function updateCustomerBillingProfile(
347+
client: Client,
348+
req: UpdateCustomerBillingProfileRequest,
349+
options?: RequestOptions,
350+
): Promise<Result<UpdateCustomerBillingProfileResponse>> {
351+
return request(() => {
352+
const pathParamsInput = {
353+
customerId: req.customerId,
354+
}
355+
const pathParams = client._options.validate
356+
? toPathWire(
357+
pathParamsInput,
358+
schemas.updateCustomerBillingProfilePathParams,
359+
)
360+
: pathParamsInput
361+
if (client._options.validate) {
362+
assertValid(
363+
schemas.updateCustomerBillingProfilePathParamsWire,
364+
pathParams,
365+
)
366+
}
367+
const path = `openmeter/customers/${(() => {
368+
if (pathParams.customerId === undefined) {
369+
throw new Error('missing path parameter: customerId')
370+
}
371+
return encodeURIComponent(String(pathParams.customerId))
372+
})()}/billing/profile`
373+
const body = toWire(req.body, schemas.updateCustomerBillingProfileBody)
374+
if (client._options.validate) {
375+
assertValid(schemas.updateCustomerBillingProfileBodyWire, body)
376+
}
377+
return http(client)
378+
.put(path, { ...options, json: body })
379+
.json()
380+
.then((data) => {
381+
if (client._options.validate) {
382+
assertValid(schemas.updateCustomerBillingProfileResponseWire, data)
383+
}
384+
return fromWire(data, schemas.updateCustomerBillingProfileResponse)
385+
})
386+
})
387+
}
388+
331389
/**
332390
* Update customer billing app data
333391
*

‎api/spec/packages/aip-client-javascript/src/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ export type {
282282
ListFeatureParamsFilter,
283283
ListAddonsParamsFilter,
284284
ListPlanAddonsParamsFilter,
285+
UpsertCustomerBillingProfileRequest,
286+
CustomerProfile,
285287
CreateTaxCodeConfig,
286288
CreditBalance,
287289
CreditTransaction,

‎api/spec/packages/aip-client-javascript/src/models/operations/customers.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
Customer,
2424
CustomerData,
2525
CustomerPagePaginatedResponse,
26+
CustomerProfile,
2627
CustomerStripeCreateCheckoutSessionRequestInput,
2728
CustomerStripeCreateCustomerPortalSessionRequest,
2829
Entitlement,
@@ -43,6 +44,7 @@ import type {
4344
UpdateCreditGrantExternalSettlementRequest as UpdateCreditGrantExternalSettlementRequestBody,
4445
UpsertAppCustomerDataRequest,
4546
UpsertCustomerBillingDataRequest,
47+
UpsertCustomerBillingProfileRequest,
4648
UpsertCustomerRequest as UpsertCustomerRequestBody,
4749
VoidCreditGrantRequestInput,
4850
} from '../types.js'
@@ -102,6 +104,12 @@ export type UpdateCustomerBillingRequest = AcceptDateStrings<{
102104
}>
103105
export type UpdateCustomerBillingResponse = CustomerData
104106

107+
export type UpdateCustomerBillingProfileRequest = AcceptDateStrings<{
108+
customerId: string
109+
body: UpsertCustomerBillingProfileRequest
110+
}>
111+
export type UpdateCustomerBillingProfileResponse = CustomerProfile
112+
105113
export type UpdateCustomerBillingAppDataRequest = AcceptDateStrings<{
106114
customerId: string
107115
body: UpsertAppCustomerDataRequest

‎api/spec/packages/aip-client-javascript/src/models/schemas.ts‎

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4279,6 +4279,31 @@ export const listPlanAddonsParamsFilter = z
42794279
})
42804280
.describe('Filter options for listing plan add-ons.')
42814281

4282+
export const upsertCustomerBillingProfileRequest = z
4283+
.object({
4284+
billingProfile: profileReference
4285+
.nullable()
4286+
.optional()
4287+
4288+
.describe(
4289+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
4290+
),
4291+
})
4292+
.describe('CustomerBillingProfile upsert request.')
4293+
4294+
export const customerProfile = z
4295+
.object({
4296+
billingProfile: profileReference
4297+
.nullable()
4298+
.optional()
4299+
4300+
.describe(
4301+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
4302+
),
4303+
effectiveBillingProfile: profileReference,
4304+
})
4305+
.describe('Customer billing profile selection.')
4306+
42824307
export const createChargeCostBasis = z
42834308
.discriminatedUnion('type', [
42844309
createChargeCostBasisDynamic,
@@ -5851,15 +5876,27 @@ export const notificationEventDeliveryStatus = z
58515876

58525877
export const customerData = z
58535878
.object({
5854-
billingProfile: profileReference.optional(),
5855-
appData: appCustomerData.optional(),
5879+
billingProfile: profileReference
5880+
.nullable()
5881+
.optional()
5882+
5883+
.describe(
5884+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
5885+
),
58565886
effectiveBillingProfile: profileReference,
5887+
appData: appCustomerData.optional(),
58575888
})
58585889
.describe('Billing customer data.')
58595890

58605891
export const upsertCustomerBillingDataRequest = z
58615892
.object({
5862-
billingProfile: profileReference.optional(),
5893+
billingProfile: profileReference
5894+
.nullable()
5895+
.optional()
5896+
5897+
.describe(
5898+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
5899+
),
58635900
appData: appCustomerData.optional(),
58645901
})
58655902
.describe('CustomerBillingData upsert request.')
@@ -8535,6 +8572,15 @@ export const updateCustomerBillingBody = upsertCustomerBillingDataRequest
85358572

85368573
export const updateCustomerBillingResponse = customerData
85378574

8575+
export const updateCustomerBillingProfilePathParams = z.object({
8576+
customerId: ulid,
8577+
})
8578+
8579+
export const updateCustomerBillingProfileBody =
8580+
upsertCustomerBillingProfileRequest
8581+
8582+
export const updateCustomerBillingProfileResponse = customerProfile
8583+
85388584
export const updateCustomerBillingAppDataPathParams = z.object({
85398585
customerId: ulid,
85408586
})
@@ -14042,6 +14088,31 @@ export const listPlanAddonsParamsFilterWire = z
1404214088
})
1404314089
.describe('Filter options for listing plan add-ons.')
1404414090

14091+
export const upsertCustomerBillingProfileRequestWire = z
14092+
.strictObject({
14093+
billing_profile: profileReferenceWire
14094+
.nullable()
14095+
.optional()
14096+
14097+
.describe(
14098+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
14099+
),
14100+
})
14101+
.describe('CustomerBillingProfile upsert request.')
14102+
14103+
export const customerProfileWire = z
14104+
.strictObject({
14105+
billing_profile: profileReferenceWire
14106+
.nullable()
14107+
.optional()
14108+
14109+
.describe(
14110+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
14111+
),
14112+
effective_billing_profile: profileReferenceWire,
14113+
})
14114+
.describe('Customer billing profile selection.')
14115+
1404514116
export const createChargeCostBasisWire = z
1404614117
.discriminatedUnion('type', [
1404714118
createChargeCostBasisDynamicWire,
@@ -15605,15 +15676,27 @@ export const notificationEventDeliveryStatusWire = z
1560515676

1560615677
export const customerDataWire = z
1560715678
.strictObject({
15608-
billing_profile: profileReferenceWire.optional(),
15609-
app_data: appCustomerDataWire.optional(),
15679+
billing_profile: profileReferenceWire
15680+
.nullable()
15681+
.optional()
15682+
15683+
.describe(
15684+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
15685+
),
1561015686
effective_billing_profile: profileReferenceWire,
15687+
app_data: appCustomerDataWire.optional(),
1561115688
})
1561215689
.describe('Billing customer data.')
1561315690

1561415691
export const upsertCustomerBillingDataRequestWire = z
1561515692
.strictObject({
15616-
billing_profile: profileReferenceWire.optional(),
15693+
billing_profile: profileReferenceWire
15694+
.nullable()
15695+
.optional()
15696+
15697+
.describe(
15698+
'The billing profile explicitly selected for the customer. Unpinned customers use the current namespace default. Selecting the current default explicitly pins the customer to that profile.',
15699+
),
1561715700
app_data: appCustomerDataWire.optional(),
1561815701
})
1561915702
.describe('CustomerBillingData upsert request.')
@@ -18303,6 +18386,15 @@ export const updateCustomerBillingBodyWire =
1830318386

1830418387
export const updateCustomerBillingResponseWire = customerDataWire
1830518388

18389+
export const updateCustomerBillingProfilePathParamsWire = z.object({
18390+
customerId: ulidWire,
18391+
})
18392+
18393+
export const updateCustomerBillingProfileBodyWire =
18394+
upsertCustomerBillingProfileRequestWire
18395+
18396+
export const updateCustomerBillingProfileResponseWire = customerProfileWire
18397+
1830618398
export const updateCustomerBillingAppDataPathParamsWire = z.object({
1830718399
customerId: ulidWire,
1830818400
})

‎api/spec/packages/aip-client-javascript/src/models/types.ts‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,6 +3137,33 @@ export interface ListPlanAddonsParamsFilter {
31373137
planCurrency?: StringFieldFilter
31383138
}
31393139

3140+
/** CustomerBillingProfile upsert request. */
3141+
export interface UpsertCustomerBillingProfileRequest {
3142+
/**
3143+
* The billing profile explicitly selected for the customer.
3144+
*
3145+
* Unpinned customers use the current namespace default. Selecting the current
3146+
* default explicitly pins the customer to that profile.
3147+
*/
3148+
billingProfile?: ProfileReference | null
3149+
}
3150+
3151+
/** Customer billing profile selection. */
3152+
export interface CustomerProfile {
3153+
/**
3154+
* The billing profile explicitly selected for the customer.
3155+
*
3156+
* Unpinned customers use the current namespace default. Selecting the current
3157+
* default explicitly pins the customer to that profile.
3158+
*/
3159+
billingProfile?: ProfileReference | null
3160+
/**
3161+
* The effective billing profile for the customer. Uses the explicitly selected
3162+
* billing profile when pinned, otherwise the current default billing profile.
3163+
*/
3164+
effectiveBillingProfile: ProfileReference
3165+
}
3166+
31403167
/**
31413168
* Tax configuration for a billable resource.
31423169
*
@@ -4833,26 +4860,28 @@ export interface CustomerData {
48334860
/**
48344861
* The billing profile explicitly selected for the customer.
48354862
*
4836-
* If not provided, the default billing profile will be used.
4863+
* Unpinned customers use the current namespace default. Selecting the current
4864+
* default explicitly pins the customer to that profile.
48374865
*/
4838-
billingProfile?: ProfileReference
4839-
/** App customer data. */
4840-
appData?: AppCustomerData
4866+
billingProfile?: ProfileReference | null
48414867
/**
48424868
* The effective billing profile for the customer. Uses the explicitly selected
48434869
* billing profile when pinned, otherwise the current default billing profile.
48444870
*/
48454871
effectiveBillingProfile: ProfileReference
4872+
/** App customer data. */
4873+
appData?: AppCustomerData
48464874
}
48474875

48484876
/** CustomerBillingData upsert request. */
48494877
export interface UpsertCustomerBillingDataRequest {
48504878
/**
48514879
* The billing profile explicitly selected for the customer.
48524880
*
4853-
* If not provided, the default billing profile will be used.
4881+
* Unpinned customers use the current namespace default. Selecting the current
4882+
* default explicitly pins the customer to that profile.
48544883
*/
4855-
billingProfile?: ProfileReference
4884+
billingProfile?: ProfileReference | null
48564885
/** App customer data. */
48574886
appData?: AppCustomerData
48584887
}

0 commit comments

Comments
 (0)