@dev.smartpricing/platform-layer 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/_shared/billingCreditNotes.ts +24 -0
  2. package/_shared/billingDocumentSubscriptions.ts +36 -0
  3. package/_shared/billingDocuments.ts +32 -0
  4. package/_shared/billingInvoices.ts +24 -0
  5. package/_shared/billingLegalEntities.ts +109 -0
  6. package/_shared/billingOverview.ts +15 -0
  7. package/_shared/billingPaymentMethods.ts +89 -0
  8. package/_shared/billingSubscriptions.ts +54 -0
  9. package/_shared/common.ts +34 -0
  10. package/_shared/crossSelling.ts +21 -0
  11. package/_shared/index.ts +22 -0
  12. package/_shared/meContext.ts +15 -0
  13. package/_shared/members.ts +23 -0
  14. package/_shared/mfa.ts +75 -0
  15. package/_shared/organizationContext.ts +18 -0
  16. package/_shared/organizations.ts +46 -0
  17. package/_shared/password.ts +22 -0
  18. package/_shared/permissions.ts +16 -0
  19. package/_shared/products.ts +49 -0
  20. package/_shared/properties.ts +16 -0
  21. package/_shared/subscriptionCancellation.ts +17 -0
  22. package/_shared/unitTypes.ts +16 -0
  23. package/_shared/user.ts +77 -0
  24. package/app/components/.gitkeep +0 -0
  25. package/app/composables/apiClient.composable.ts +25 -0
  26. package/app/composables/useAuth.composable.ts +17 -0
  27. package/app/mutations/useAccountingLogin.mutation.ts +10 -0
  28. package/app/mutations/useActivate.mutation.ts +10 -0
  29. package/app/mutations/useChangePassword.mutation.ts +10 -0
  30. package/app/mutations/useCreatePaymentMethod.mutation.ts +13 -0
  31. package/app/mutations/useCreateSetupIntent.mutation.ts +13 -0
  32. package/app/mutations/useLogin.mutation.ts +10 -0
  33. package/app/mutations/useLogout.mutation.ts +7 -0
  34. package/app/mutations/useMfaDisable.mutation.ts +10 -0
  35. package/app/mutations/useMfaFinalize.mutation.ts +10 -0
  36. package/app/mutations/useMfaRegenerateRecoveryCodes.mutation.ts +10 -0
  37. package/app/mutations/useMfaSetup.mutation.ts +10 -0
  38. package/app/mutations/useMfaStepUp.mutation.ts +10 -0
  39. package/app/mutations/useRefresh.mutation.ts +7 -0
  40. package/app/mutations/useRemovePaymentMethod.mutation.ts +11 -0
  41. package/app/mutations/useRequestLegalEntityChange.mutation.ts +13 -0
  42. package/app/mutations/useRequestPasswordReset.mutation.ts +10 -0
  43. package/app/mutations/useRequestSubscriptionCancellation.mutation.ts +13 -0
  44. package/app/mutations/useResetPassword.mutation.ts +10 -0
  45. package/app/mutations/useSetPrimaryPaymentMethod.mutation.ts +13 -0
  46. package/app/mutations/useUpdateLegalEntity.mutation.ts +13 -0
  47. package/app/mutations/useUpdateProfile.mutation.ts +10 -0
  48. package/app/queries/useBillingDocumentPdf.query.ts +22 -0
  49. package/app/queries/useBillingDocuments.query.ts +35 -0
  50. package/app/queries/useBillingOverview.query.ts +17 -0
  51. package/app/queries/useCrossSelling.query.ts +14 -0
  52. package/app/queries/useLegalEntity.query.ts +20 -0
  53. package/app/queries/useOrganization.query.ts +17 -0
  54. package/app/queries/usePermissions.query.ts +14 -0
  55. package/app/queries/useSession.query.ts +14 -0
  56. package/app/utils/apiClient.utils.ts +134 -0
  57. package/nuxt.config.ts +50 -0
  58. package/package.json +34 -0
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod'
2
+ import { BillingDocumentSubscriptionSchema } from './billingDocumentSubscriptions.js'
3
+ import { UuidSchema } from './common.js'
4
+
5
+ export const CreditNoteSchema = z.object({
6
+ id: UuidSchema,
7
+ customerId: UuidSchema,
8
+ invoiceId: UuidSchema.nullable(),
9
+ /** Generic upstream status string. */
10
+ status: z.string(),
11
+ date: z.string().nullable(),
12
+ total: z.number(),
13
+ currencyId: UuidSchema,
14
+ pdfUrl: z.string().nullable(),
15
+ subscriptions: z.array(BillingDocumentSubscriptionSchema),
16
+ })
17
+
18
+ export const ListCreditNotesResponseSchema = z.object({
19
+ items: z.array(CreditNoteSchema),
20
+ })
21
+
22
+ export type CreditNote = z.infer<typeof CreditNoteSchema>
23
+
24
+ export type ListCreditNotesResponse = z.infer<typeof ListCreditNotesResponseSchema>
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+
4
+ export const BillingDocumentProductSchema = z.object({
5
+ id: UuidSchema,
6
+ })
7
+
8
+ export const BillingDocumentItemProductSchema = z.object({
9
+ product: BillingDocumentProductSchema,
10
+ })
11
+
12
+ export const BillingDocumentItemSchema = z.object({
13
+ id: UuidSchema,
14
+ itemProducts: z.array(BillingDocumentItemProductSchema),
15
+ })
16
+
17
+ export const BillingDocumentItemPriceSchema = z.object({
18
+ id: UuidSchema,
19
+ item: BillingDocumentItemSchema,
20
+ })
21
+
22
+ export const BillingDocumentSubscriptionItemSchema = z.object({
23
+ itemPrice: BillingDocumentItemPriceSchema,
24
+ })
25
+
26
+ export const BillingDocumentSubscriptionSchema = z.object({
27
+ id: UuidSchema,
28
+ subscriptionItems: z.array(BillingDocumentSubscriptionItemSchema),
29
+ })
30
+
31
+ export type BillingDocumentProduct = z.infer<typeof BillingDocumentProductSchema>
32
+ export type BillingDocumentItemProduct = z.infer<typeof BillingDocumentItemProductSchema>
33
+ export type BillingDocumentItem = z.infer<typeof BillingDocumentItemSchema>
34
+ export type BillingDocumentItemPrice = z.infer<typeof BillingDocumentItemPriceSchema>
35
+ export type BillingDocumentSubscriptionItem = z.infer<typeof BillingDocumentSubscriptionItemSchema>
36
+ export type BillingDocumentSubscription = z.infer<typeof BillingDocumentSubscriptionSchema>
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+
4
+ export const BillingDocumentTypeSchema = z.enum(['invoice', 'credit_note'])
5
+
6
+ export const BillingDocumentRowSchema = z.object({
7
+ type: BillingDocumentTypeSchema,
8
+ id: UuidSchema,
9
+ customerId: UuidSchema,
10
+ status: z.string(),
11
+ date: z.string().nullable(),
12
+ total: z.number(),
13
+ /** Resolved currency code (e.g. "EUR"); null when it can't be resolved. */
14
+ currency: z.string().nullable(),
15
+ pdfUrl: z.string().nullable(),
16
+ /** Subscription IDs linked to this document via Core invoice/credit-note relations. */
17
+ subscriptionIds: z.array(UuidSchema),
18
+ })
19
+
20
+ export const ListBillingDocumentsResponseSchema = z.object({
21
+ items: z.array(BillingDocumentRowSchema),
22
+ })
23
+
24
+ /** Short-lived signed URL pointing at the document's PDF in object storage. */
25
+ export const BillingDocumentPdfResponseSchema = z.object({
26
+ url: z.string(),
27
+ })
28
+
29
+ export type BillingDocumentType = z.infer<typeof BillingDocumentTypeSchema>
30
+ export type BillingDocumentRow = z.infer<typeof BillingDocumentRowSchema>
31
+ export type ListBillingDocumentsResponse = z.infer<typeof ListBillingDocumentsResponseSchema>
32
+ export type BillingDocumentPdfResponse = z.infer<typeof BillingDocumentPdfResponseSchema>
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod'
2
+ import { BillingDocumentSubscriptionSchema } from './billingDocumentSubscriptions.js'
3
+ import { UuidSchema } from './common.js'
4
+
5
+ export const InvoiceSchema = z.object({
6
+ id: UuidSchema,
7
+ customerId: UuidSchema,
8
+ /** Generic upstream status string. */
9
+ status: z.string(),
10
+ date: z.string().nullable(),
11
+ dueDate: z.string().nullable(),
12
+ total: z.number(),
13
+ currencyId: UuidSchema,
14
+ pdfUrl: z.string().nullable(),
15
+ subscriptions: z.array(BillingDocumentSubscriptionSchema),
16
+ })
17
+
18
+ export const ListInvoicesResponseSchema = z.object({
19
+ items: z.array(InvoiceSchema),
20
+ })
21
+
22
+ export type Invoice = z.infer<typeof InvoiceSchema>
23
+
24
+ export type ListInvoicesResponse = z.infer<typeof ListInvoicesResponseSchema>
@@ -0,0 +1,109 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+
4
+ export const LegalEntityTypeSchema = z.enum(['natural_person', 'legal_person'])
5
+
6
+ /**
7
+ * Identifier for the Smartness billing entity that issues the invoice for a
8
+ * given legal entity (Helium S.r.l., Ciaobooking S.r.l., ...). Only relevant
9
+ * on the create-legal-entity request body — the response does not surface it.
10
+ */
11
+ export const BillingEntityCodeSchema = z.enum(['helium', 'ciaobooking'])
12
+
13
+ export const LegalEntitySchema = z.object({
14
+ id: UuidSchema,
15
+ organizationId: UuidSchema,
16
+ entityType: LegalEntityTypeSchema,
17
+ firstName: z.string().nullable(),
18
+ lastName: z.string().nullable(),
19
+ companyName: z.string().nullable(),
20
+ email: z.string().email(),
21
+ countryCode: z.string().length(2),
22
+ city: z.string(),
23
+ })
24
+
25
+ export const ListLegalEntitiesResponseSchema = z.object({
26
+ items: z.array(LegalEntitySchema),
27
+ })
28
+
29
+ /**
30
+ * Full legal-entity shape used by the edit form. Unlike {@link LegalEntitySchema}
31
+ * (the trimmed list/overview projection) this carries every editable billing field
32
+ * plus the read-only identity/audit columns, so the modal can seed every input.
33
+ */
34
+ export const LegalEntityDetailSchema = z.object({
35
+ id: UuidSchema,
36
+ organizationId: UuidSchema,
37
+ entityType: LegalEntityTypeSchema,
38
+ firstName: z.string().nullable(),
39
+ lastName: z.string().nullable(),
40
+ companyName: z.string().nullable(),
41
+ email: z.string().email(),
42
+ pec: z.string().nullable(),
43
+ phoneCountryCode: z.string().nullable(),
44
+ phone: z.string().nullable(),
45
+ address: z.string(),
46
+ postcode: z.string(),
47
+ city: z.string(),
48
+ state: z.string(),
49
+ countryCode: z.string(),
50
+ vatNumberPrefix: z.string().nullable(),
51
+ vatNumber: z.string().nullable(),
52
+ vatNumberStatus: z.string().nullable(),
53
+ taxCode: z.string().nullable(),
54
+ einvoiceRoutingCode: z.string(),
55
+ createdAt: z.string(),
56
+ updatedAt: z.string(),
57
+ })
58
+
59
+ /**
60
+ * PATCH body for editing a legal entity. Every field is optional (partial update).
61
+ * Identity-defining columns (`entityType`, `organizationId`) are intentionally absent:
62
+ * core-api rejects them as immutable, and a large edit to a protected field
63
+ * (company / first / last name, VAT, tax code) is bounced to the subentro flow.
64
+ */
65
+ export const UpdateLegalEntityRequestSchema = z.object({
66
+ firstName: z.string().optional(),
67
+ lastName: z.string().optional(),
68
+ companyName: z.string().optional(),
69
+ email: z.string().email().optional(),
70
+ pec: z.string().nullable().optional(),
71
+ phoneCountryCode: z.string().nullable().optional(),
72
+ phone: z.string().nullable().optional(),
73
+ address: z.string().optional(),
74
+ postcode: z.string().optional(),
75
+ city: z.string().optional(),
76
+ state: z.string().optional(),
77
+ countryCode: z.string().optional(),
78
+ vatNumberPrefix: z.string().optional(),
79
+ vatNumber: z.string().optional(),
80
+ taxCode: z.string().optional(),
81
+ einvoiceRoutingCode: z.string().optional(),
82
+ })
83
+
84
+ /**
85
+ * "Subentro" request: raised when core-api rejects a too-large edit to a protected
86
+ * field. Carries the field-level before/after the user proposed so ops can action the
87
+ * change (a name/VAT/tax-code change is effectively a different entity, not a typo fix).
88
+ */
89
+ export const RequestLegalEntityChangeRequestSchema = z.object({
90
+ changes: z
91
+ .array(
92
+ z.object({
93
+ field: z.string(),
94
+ from: z.string().nullable(),
95
+ to: z.string().nullable(),
96
+ })
97
+ )
98
+ .min(1),
99
+ note: z.string().optional(),
100
+ })
101
+
102
+ export type LegalEntityType = z.infer<typeof LegalEntityTypeSchema>
103
+ export type BillingEntityCode = z.infer<typeof BillingEntityCodeSchema>
104
+ export type LegalEntity = z.infer<typeof LegalEntitySchema>
105
+
106
+ export type ListLegalEntitiesResponse = z.infer<typeof ListLegalEntitiesResponseSchema>
107
+ export type LegalEntityDetail = z.infer<typeof LegalEntityDetailSchema>
108
+ export type UpdateLegalEntityRequest = z.infer<typeof UpdateLegalEntityRequestSchema>
109
+ export type RequestLegalEntityChangeRequest = z.infer<typeof RequestLegalEntityChangeRequestSchema>
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+ import { LegalEntityDetailSchema } from './billingLegalEntities.js'
4
+ import { PaymentMethodSchema } from './billingPaymentMethods.js'
5
+ import { SubscriptionSchema } from './billingSubscriptions.js'
6
+
7
+ export const BillingOverviewResponseSchema = z.object({
8
+ // Full per-entity projection: the list already fetches every billing field from core-api, so
9
+ // the overview surfaces them (the details card renders address/tax id/PEC/phone without a refetch).
10
+ legalEntities: z.array(LegalEntityDetailSchema),
11
+ subscriptions: z.array(SubscriptionSchema),
12
+ paymentMethodsByLegalEntityId: z.record(UuidSchema, z.array(PaymentMethodSchema)),
13
+ })
14
+
15
+ export type BillingOverviewResponse = z.infer<typeof BillingOverviewResponseSchema>
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+
4
+ export const PaymentMethodTypeSchema = z.enum(['card', 'direct_debit'])
5
+
6
+ export const PaymentMethodSchema = z.object({
7
+ id: UuidSchema,
8
+ customerId: UuidSchema,
9
+ type: PaymentMethodTypeSchema,
10
+ /** Brand for cards (e.g. "Visa"); bank name for direct debit. */
11
+ label: z.string(),
12
+ /** Last 4 digits of the card or IBAN tail. */
13
+ last4: z.string(),
14
+ /** Cards only. */
15
+ expiryMonth: z.number().int().min(1).max(12).nullable(),
16
+ expiryYear: z.number().int().nullable(),
17
+ status: z.string(),
18
+ /** True when this is the customer's primary payment source (from the overview). */
19
+ isPrimary: z.boolean(),
20
+ })
21
+
22
+ export const ListPaymentMethodsResponseSchema = z.object({
23
+ items: z.array(PaymentMethodSchema),
24
+ })
25
+
26
+ export const SetPrimaryPaymentMethodRequestSchema = z.object({
27
+ customerId: UuidSchema,
28
+ paymentMethodId: UuidSchema,
29
+ })
30
+
31
+ export const SetPrimaryPaymentMethodResponseSchema = z.object({
32
+ customerId: UuidSchema,
33
+ primaryPaymentSourceId: UuidSchema,
34
+ })
35
+
36
+ /**
37
+ * Card add (step 1): the frontend tokenizes the card with Stripe Elements, then
38
+ * exchanges the PaymentMethod id for a SetupIntent client_secret to confirm (SCA).
39
+ */
40
+ export const CreateSetupIntentRequestSchema = z.object({
41
+ customerId: UuidSchema,
42
+ stripePaymentMethodId: z.string().min(1),
43
+ })
44
+
45
+ export const CreateSetupIntentResponseSchema = z.object({
46
+ stripeCustomerId: z.string(),
47
+ stripePaymentMethodId: z.string(),
48
+ clientSecret: z.string(),
49
+ })
50
+
51
+ /** Card add (step 2): create the source from the confirmed SetupIntent. Targets one customer. */
52
+ export const CreateCardPaymentMethodRequestSchema = z.object({
53
+ type: z.literal('card'),
54
+ customerId: UuidSchema,
55
+ setupIntentId: z.string().min(1),
56
+ stripePaymentMethodId: z.string().min(1),
57
+ })
58
+
59
+ /** IBAN/SEPA add: targets the legal entity; the backend fans out to its eligible EUR customers. */
60
+ export const CreateDirectDebitPaymentMethodRequestSchema = z.object({
61
+ type: z.literal('direct_debit'),
62
+ legalEntityId: UuidSchema,
63
+ iban: z.string().min(1),
64
+ firstName: z.string().min(1),
65
+ lastName: z.string().min(1),
66
+ email: z.string().optional(),
67
+ })
68
+
69
+ export const CreatePaymentMethodRequestSchema = z.discriminatedUnion('type', [
70
+ CreateCardPaymentMethodRequestSchema,
71
+ CreateDirectDebitPaymentMethodRequestSchema,
72
+ ])
73
+
74
+ /** One item for a card, one per customer for the IBAN fan-out, plus per-customer failures. */
75
+ export const CreatePaymentMethodsResponseSchema = z.object({
76
+ items: z.array(PaymentMethodSchema),
77
+ failed: z.array(z.object({ customerId: UuidSchema, reason: z.string() })),
78
+ })
79
+
80
+ export type PaymentMethodType = z.infer<typeof PaymentMethodTypeSchema>
81
+ export type PaymentMethod = z.infer<typeof PaymentMethodSchema>
82
+
83
+ export type ListPaymentMethodsResponse = z.infer<typeof ListPaymentMethodsResponseSchema>
84
+ export type SetPrimaryPaymentMethodRequest = z.infer<typeof SetPrimaryPaymentMethodRequestSchema>
85
+ export type SetPrimaryPaymentMethodResponse = z.infer<typeof SetPrimaryPaymentMethodResponseSchema>
86
+ export type CreateSetupIntentRequest = z.infer<typeof CreateSetupIntentRequestSchema>
87
+ export type CreateSetupIntentResponse = z.infer<typeof CreateSetupIntentResponseSchema>
88
+ export type CreatePaymentMethodRequest = z.infer<typeof CreatePaymentMethodRequestSchema>
89
+ export type CreatePaymentMethodsResponse = z.infer<typeof CreatePaymentMethodsResponseSchema>
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod'
2
+ import { UuidSchema } from './common.js'
3
+
4
+ export const BillingPeriodUnitSchema = z.enum(['day', 'week', 'month', 'year'])
5
+
6
+ export const SubscriptionPropertySchema = z.object({
7
+ id: UuidSchema,
8
+ name: z.string(),
9
+ })
10
+
11
+ export const SubscriptionSchema = z.object({
12
+ id: UuidSchema,
13
+ customerId: UuidSchema,
14
+ /**
15
+ * The anagrafica (legal entity) that owns this subscription, resolved upstream via
16
+ * subscription → customer → legal_entity. This is the authoritative grouping key for
17
+ * the billing UI; never re-derive the link from payment methods. Nullable only for the
18
+ * pathological deleted-customer case core-api leaves unresolved.
19
+ */
20
+ legalEntityId: UuidSchema.nullable(),
21
+ productName: z.string(),
22
+ itemPriceName: z.string(),
23
+ /** Generic upstream status string ("active", "in_trial", "cancelled", ...). */
24
+ status: z.string(),
25
+ billingPeriodUnit: BillingPeriodUnitSchema.nullable(),
26
+ billingPeriod: z.number().int().nullable(),
27
+ mrr: z.number().nullable(),
28
+ currencyId: UuidSchema,
29
+ /** Resolved currency code (e.g. "EUR"); null when it can't be resolved. Used to gate SEPA. */
30
+ currency: z.string().nullable(),
31
+ unitsCount: z.number().int().nullable(),
32
+ nextBillingAt: z.string().nullable(),
33
+ startedAt: z.string().nullable(),
34
+ properties: z.array(SubscriptionPropertySchema),
35
+ })
36
+
37
+ export const ListSubscriptionsResponseSchema = z.object({
38
+ items: z.array(SubscriptionSchema),
39
+ })
40
+
41
+ export const AttachSubscriptionPropertyResponseSchema = z.object({
42
+ subscriptionId: UuidSchema,
43
+ propertyId: UuidSchema,
44
+ createdAt: z.string(),
45
+ })
46
+
47
+ export type BillingPeriodUnit = z.infer<typeof BillingPeriodUnitSchema>
48
+ export type SubscriptionProperty = z.infer<typeof SubscriptionPropertySchema>
49
+ export type Subscription = z.infer<typeof SubscriptionSchema>
50
+
51
+ export type ListSubscriptionsResponse = z.infer<typeof ListSubscriptionsResponseSchema>
52
+ export type AttachSubscriptionPropertyResponse = z.infer<
53
+ typeof AttachSubscriptionPropertyResponseSchema
54
+ >
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod'
2
+
3
+ export const UuidSchema = z.string().min(1)
4
+
5
+ export const CurrencySchema = z.enum(['EUR', 'USD', 'GBP'])
6
+
7
+ /** ISO-8601 date (YYYY-MM-DD). */
8
+ export const IsoDateSchema = z.iso.date()
9
+
10
+ /** ISO-8601 timestamp (YYYY-MM-DDTHH:mm:ss.sssZ). */
11
+ export const IsoDateTimeSchema = z.iso.datetime()
12
+
13
+ /**
14
+ * Membership role inside an organization. Shared across the
15
+ * organizations and members contexts so the two domains can never drift.
16
+ */
17
+ export const RoleSchema = z.enum(['owner', 'admin', 'manager', 'viewer'])
18
+
19
+ export const ApiErrorSchema = z.object({
20
+ code: z.string(),
21
+ message: z.string(),
22
+ details: z.record(z.string(), z.unknown()).optional(),
23
+ })
24
+
25
+ export function listResponseSchema<T extends z.ZodTypeAny>(item: T) {
26
+ return z.object({ items: z.array(item) })
27
+ }
28
+
29
+ export type Uuid = z.infer<typeof UuidSchema>
30
+ export type Currency = z.infer<typeof CurrencySchema>
31
+ export type IsoDate = z.infer<typeof IsoDateSchema>
32
+ export type IsoDateTime = z.infer<typeof IsoDateTimeSchema>
33
+ export type Role = z.infer<typeof RoleSchema>
34
+ export type ApiError = z.infer<typeof ApiErrorSchema>
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod'
2
+
3
+ export const ModulePlanSchema = z.enum(['STANDARD', 'PREMIUM', 'ULTRA']).nullable()
4
+
5
+ export const CrossSellModulePlansSchema = z.object({
6
+ pricing: ModulePlanSchema,
7
+ connect: ModulePlanSchema,
8
+ pms: ModulePlanSchema,
9
+ chat: ModulePlanSchema,
10
+ site: ModulePlanSchema,
11
+ })
12
+
13
+ export const GetCrossSellingResponseSchema = z.object({
14
+ availablePlans: CrossSellModulePlansSchema,
15
+ isUsingPmsGoldPartner: z.boolean(),
16
+ isUsingPricingGoldPartner: z.boolean(),
17
+ })
18
+
19
+ export type ModulePlan = z.infer<typeof ModulePlanSchema>
20
+ export type CrossSellModulePlans = z.infer<typeof CrossSellModulePlansSchema>
21
+ export type GetCrossSellingResponse = z.infer<typeof GetCrossSellingResponseSchema>
@@ -0,0 +1,22 @@
1
+ export * from './common.js'
2
+ export * from './password.js'
3
+ export * from './user.js'
4
+ export * from './meContext.js'
5
+ export * from './organizationContext.js'
6
+ export * from './organizations.js'
7
+ export * from './members.js'
8
+ export * from './properties.js'
9
+ export * from './unitTypes.js'
10
+ export * from './products.js'
11
+ export * from './billingSubscriptions.js'
12
+ export * from './billingDocumentSubscriptions.js'
13
+ export * from './billingInvoices.js'
14
+ export * from './billingCreditNotes.js'
15
+ export * from './billingPaymentMethods.js'
16
+ export * from './billingLegalEntities.js'
17
+ export * from './billingOverview.js'
18
+ export * from './billingDocuments.js'
19
+ export * from './crossSelling.js'
20
+ export * from './permissions.js'
21
+ export * from './mfa.js'
22
+ export * from './subscriptionCancellation.js'
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod'
2
+ import { ListOrganizationsResponseSchema } from './organizations.js'
3
+ import { UserSchema } from './user.js'
4
+
5
+ export const AuthStateSchema = z.enum(['pwd_authenticated', 'totp_verified'])
6
+ export type AuthStateType = z.infer<typeof AuthStateSchema>
7
+
8
+ export const MeContextResponseSchema = z.object({
9
+ user: UserSchema,
10
+ organizations: ListOrganizationsResponseSchema.shape.items,
11
+ /** Current session authentication state. `pwd_authenticated` = password only; `totp_verified` = TOTP step completed. */
12
+ auth_state: AuthStateSchema,
13
+ })
14
+
15
+ export type MeContextResponse = z.infer<typeof MeContextResponseSchema>
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod'
2
+ import type { Role } from './common.js'
3
+ import { IsoDateTimeSchema, RoleSchema, UuidSchema } from './common.js'
4
+
5
+ export const MemberSchema = z.object({
6
+ id: UuidSchema,
7
+ organizationId: UuidSchema,
8
+ firstName: z.string(),
9
+ lastName: z.string(),
10
+ email: z.string().email(),
11
+ role: RoleSchema,
12
+ lastActiveAt: IsoDateTimeSchema,
13
+ })
14
+
15
+ export const ListMembersResponseSchema = z.object({
16
+ items: z.array(MemberSchema),
17
+ })
18
+
19
+ /** Re-exported under the members namespace for ergonomics. */
20
+ export type MemberRole = Role
21
+ export type Member = z.infer<typeof MemberSchema>
22
+
23
+ export type ListMembersResponse = z.infer<typeof ListMembersResponseSchema>
package/_shared/mfa.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod'
2
+
3
+ // ── Step-up (OTP verification) ──────────────────────────────────────
4
+
5
+ export const MfaStepUpRequestSchema = z.union([
6
+ z.object({ totpCode: z.string() }),
7
+ z.object({ recoveryCode: z.string() }),
8
+ ])
9
+
10
+ /**
11
+ * Returns `{ enrolled: false }` when the user has no MFA enrollment,
12
+ * or an empty object on successful verification.
13
+ */
14
+ export const MfaStepUpResponseSchema = z.union([
15
+ z.object({ enrolled: z.literal(false) }),
16
+ z.object({}),
17
+ ])
18
+
19
+ // ── Setup ───────────────────────────────────────────────────────────
20
+
21
+ export const MfaSetupResponseSchema = z.object({
22
+ enrollment_id: z.string(),
23
+ secret: z.string(),
24
+ qr_dataurl: z.string(),
25
+ })
26
+
27
+ // ── Finalize ────────────────────────────────────────────────────────
28
+
29
+ export const MfaFinalizeRequestSchema = z.object({
30
+ enrollmentId: z.string(),
31
+ totpCode: z.string(),
32
+ })
33
+
34
+ export const MfaFinalizeResponseSchema = z.object({
35
+ recovery_codes: z.array(z.string()),
36
+ })
37
+
38
+ // ── Disable ─────────────────────────────────────────────────────────
39
+
40
+ /** Exactly one verification method must be provided; the backend enforces the constraint. */
41
+ export const MfaDisableRequestSchema = z.object({
42
+ totpCode: z.string().optional(),
43
+ recoveryCode: z.string().optional(),
44
+ password: z.string().optional(),
45
+ })
46
+
47
+ export const MfaDisableResponseSchema = z.object({
48
+ status: z.literal('disabled'),
49
+ })
50
+
51
+ // ── Regenerate recovery codes ───────────────────────────────────────
52
+
53
+ export const MfaRegenerateRecoveryCodesRequestSchema = z.object({
54
+ totpCode: z.string(),
55
+ })
56
+
57
+ export const MfaRegenerateRecoveryCodesResponseSchema = z.object({
58
+ recovery_codes: z.array(z.string()),
59
+ })
60
+
61
+ // ── Types ───────────────────────────────────────────────────────────
62
+
63
+ export type MfaStepUpRequest = z.infer<typeof MfaStepUpRequestSchema>
64
+ export type MfaStepUpResponse = z.infer<typeof MfaStepUpResponseSchema>
65
+
66
+ export type MfaSetupResponse = z.infer<typeof MfaSetupResponseSchema>
67
+
68
+ export type MfaFinalizeRequest = z.infer<typeof MfaFinalizeRequestSchema>
69
+ export type MfaFinalizeResponse = z.infer<typeof MfaFinalizeResponseSchema>
70
+
71
+ export type MfaDisableRequest = z.infer<typeof MfaDisableRequestSchema>
72
+ export type MfaDisableResponse = z.infer<typeof MfaDisableResponseSchema>
73
+
74
+ export type MfaRegenerateRecoveryCodesRequest = z.infer<typeof MfaRegenerateRecoveryCodesRequestSchema>
75
+ export type MfaRegenerateRecoveryCodesResponse = z.infer<typeof MfaRegenerateRecoveryCodesResponseSchema>
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod'
2
+ import { MemberSchema } from './members.js'
3
+ import { OrgProductRowSchema } from './products.js'
4
+ import { PropertySchema } from './properties.js'
5
+ import { UnitTypeSchema } from './unitTypes.js'
6
+
7
+ export const PropertyWithUnitTypesSchema = PropertySchema.extend({
8
+ unitTypes: z.array(UnitTypeSchema),
9
+ })
10
+
11
+ export const OrganizationContextResponseSchema = z.object({
12
+ products: z.array(OrgProductRowSchema),
13
+ members: z.array(MemberSchema),
14
+ properties: z.array(PropertyWithUnitTypesSchema),
15
+ })
16
+
17
+ export type PropertyWithUnitTypes = z.infer<typeof PropertyWithUnitTypesSchema>
18
+ export type OrganizationContextResponse = z.infer<typeof OrganizationContextResponseSchema>
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+ import type { Role } from './common.js'
3
+ import { UuidSchema } from './common.js'
4
+
5
+ export const OrganizationSchema = z.object({
6
+ id: UuidSchema,
7
+ name: z.string(),
8
+ })
9
+
10
+ export const OrganizationRoleSchema = z.object({
11
+ id: UuidSchema,
12
+ name: z.string(),
13
+ })
14
+
15
+ /**
16
+ * Re-exported under the organizations namespace for ergonomics; downstream
17
+ * consumers (org switcher, etc.) keep using the same `Role` enum.
18
+ */
19
+ export type OrganizationRole = Role
20
+
21
+ /**
22
+ * Org-level role assignment for a user. Returned by
23
+ * `GET /core/v3/users/{id}/organizations` and drives the org switcher.
24
+ */
25
+ export const OrganizationMembershipSchema = z.object({
26
+ organizationId: UuidSchema,
27
+ organizationName: z.string(),
28
+ role: OrganizationRoleSchema,
29
+ productId: UuidSchema.nullable(),
30
+ productName: z.string().nullable(),
31
+ })
32
+
33
+ export const ListOrganizationsResponseSchema = z.object({
34
+ items: z.array(OrganizationMembershipSchema),
35
+ })
36
+
37
+ export const SetActiveOrganizationRequestSchema = z.object({
38
+ organizationId: UuidSchema,
39
+ })
40
+
41
+ export type Organization = z.infer<typeof OrganizationSchema>
42
+ export type OrganizationRoleRef = z.infer<typeof OrganizationRoleSchema>
43
+ export type OrganizationMembership = z.infer<typeof OrganizationMembershipSchema>
44
+
45
+ export type ListOrganizationsResponse = z.infer<typeof ListOrganizationsResponseSchema>
46
+ export type SetActiveOrganizationRequest = z.infer<typeof SetActiveOrganizationRequestSchema>
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod'
2
+
3
+ export const PASSWORD_MIN_LENGTH = 10
4
+ export const PASSWORD_MAX_LENGTH = 128
5
+
6
+ /**
7
+ * Shared password policy used by the BFF backend (authoritative) and the SPA
8
+ * (UX gating). Only enforces what can be checked synchronously without external
9
+ * services: length bounds. Strength scoring (zxcvbn) and breach lookups (HIBP)
10
+ * run server-side inside the password use-cases — they can fail-open on outage
11
+ * without affecting the client validation contract.
12
+ */
13
+ export const PasswordSchema = z
14
+ .string()
15
+ .min(PASSWORD_MIN_LENGTH)
16
+ .max(PASSWORD_MAX_LENGTH)
17
+
18
+ export type PasswordPolicyCode =
19
+ | 'password.too_short'
20
+ | 'password.too_long'
21
+ | 'password.too_weak'
22
+ | 'password.breached'