@revenexx/cover 0.1.0 → 0.1.1

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 (41) hide show
  1. package/app/app.config.ts +1 -1
  2. package/app/components/account/address/AccountAddressCard.vue +69 -88
  3. package/app/components/auth/AuthRegisterPanel.vue +122 -194
  4. package/app/formkit.config.ts +21 -0
  5. package/app/interfaces/auth.ts +0 -2
  6. package/app/interfaces/validation.ts +1 -1
  7. package/app/validations/formValidationConfig.ts +0 -30
  8. package/nuxt.config.ts +0 -3
  9. package/package.json +2 -2
  10. package/server/api/account/orders.get.ts +6 -5
  11. package/server/api/account/profile.get.ts +3 -3
  12. package/server/api/account/profile.put.ts +6 -4
  13. package/server/api/auth/login.post.ts +2 -5
  14. package/server/api/auth/recovery.post.ts +5 -13
  15. package/server/api/auth/recovery.put.ts +5 -14
  16. package/server/api/auth/register.post.ts +21 -44
  17. package/server/api/orders/index.post.ts +28 -24
  18. package/server/api/payment/methods.post.ts +5 -4
  19. package/server/api/shipping/rates.post.ts +6 -4
  20. package/server/interfaces/auth.ts +1 -1
  21. package/server/services/ApiAccountService.ts +44 -0
  22. package/server/services/ApiAuthService.ts +15 -14
  23. package/server/services/ApiCartService.ts +45 -27
  24. package/server/services/ApiMarketService.ts +3 -5
  25. package/server/utils/accountService.ts +4 -5
  26. package/server/utils/authService.ts +0 -3
  27. package/server/utils/liveCatalog.ts +22 -13
  28. package/server/utils/liveInventories.ts +4 -4
  29. package/server/utils/liveOrders.ts +5 -3
  30. package/server/utils/livePrices.ts +10 -9
  31. package/server/utils/revenexxSdk.ts +162 -0
  32. package/app/validations/companyName.ts +0 -18
  33. package/app/validations/maxLength.ts +0 -12
  34. package/app/validations/optionalCompanyName.ts +0 -23
  35. package/app/validations/phoneNumber.ts +0 -19
  36. package/app/validations/termsRequired.ts +0 -4
  37. package/app/validations/zipCode.ts +0 -15
  38. package/server/services/SdkAccountService.ts +0 -56
  39. package/server/services/SdkAuthService.ts +0 -83
  40. package/server/utils/revenexxApi.ts +0 -136
  41. package/server/utils/shopSdk.ts +0 -88
@@ -1,6 +1,6 @@
1
1
  import type { CrossFieldValidator, SimpleValidator } from "../validations/types";
2
2
 
3
- export type FormKey = "login" | "register" | "forgotPassword" | "resetPassword" | "accountProfile" | "accountAddress";
3
+ export type FormKey = "login" | "forgotPassword" | "resetPassword";
4
4
 
5
5
  export interface FieldValidationRule {
6
6
  readonly kind: "simple" | "cross";
@@ -1,30 +1,15 @@
1
1
  import type { FieldConfig, FormKey } from "../interfaces/validation";
2
2
 
3
- import { companyName } from "./companyName";
4
3
  import { emailFormat } from "./emailFormat";
5
- import { maxLength } from "./maxLength";
6
4
  import { minLength } from "./minLength";
7
- import { optionalCompanyName } from "./optionalCompanyName";
8
5
  import { passwordsMatch } from "./passwordsMatch";
9
- import { phoneNumber } from "./phoneNumber";
10
6
  import { required } from "./required";
11
- import { termsRequired } from "./termsRequired";
12
- import { zipCode } from "./zipCode";
13
7
 
14
8
  export const formValidationConfig: Record<FormKey, FieldConfig[]> = {
15
9
  login: [
16
10
  { field: "email", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: emailFormat }] },
17
11
  { field: "password", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
18
12
  ],
19
- register: [
20
- { field: "email", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: emailFormat }] },
21
- { field: "password", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
22
- { field: "passwordConfirm", rules: [{ kind: "cross", fn: passwordsMatch }] },
23
- { field: "firstName", rules: [{ kind: "simple", fn: required }] },
24
- { field: "lastName", rules: [{ kind: "simple", fn: required }] },
25
- { field: "company", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: companyName }] },
26
- { field: "acceptTerms", rules: [{ kind: "simple", fn: termsRequired }] },
27
- ],
28
13
  forgotPassword: [
29
14
  { field: "email", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: emailFormat }] },
30
15
  ],
@@ -32,19 +17,4 @@ export const formValidationConfig: Record<FormKey, FieldConfig[]> = {
32
17
  { field: "password", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
33
18
  { field: "passwordConfirm", rules: [{ kind: "cross", fn: passwordsMatch }] },
34
19
  ],
35
- accountProfile: [
36
- { field: "phone", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: maxLength, options: { max: 16 } }, { kind: "simple", fn: phoneNumber }, { kind: "simple", fn: minLength, options: { min: 8 } }] },
37
- { field: "company", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: maxLength, options: { max: 64 } }, { kind: "simple", fn: minLength, options: { min: 3 } }, { kind: "simple", fn: companyName }] },
38
- ],
39
- accountAddress: [
40
- { field: "label", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 2 } }] },
41
- { field: "firstName", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 2 } }] },
42
- { field: "lastName", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 2 } }] },
43
- { field: "street", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 2 } }] },
44
- { field: "streetNumber", rules: [{ kind: "simple", fn: required }] },
45
- { field: "city", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: minLength, options: { min: 2 } }] },
46
- { field: "zip", rules: [{ kind: "simple", fn: required }, { kind: "simple", fn: zipCode }] },
47
- { field: "country", rules: [{ kind: "simple", fn: required }] },
48
- { field: "company", rules: [{ kind: "simple", fn: optionalCompanyName, options: { min: 3, max: 64 } }] },
49
- ],
50
20
  };
package/nuxt.config.ts CHANGED
@@ -62,9 +62,6 @@ export default defineNuxtConfig({
62
62
  // Defaults for the BFF's live backends — consuming apps override these
63
63
  // via NUXT_* environment variables (web SDK identity, Typesense search).
64
64
  runtimeConfig: {
65
- webSdkApiUrl: "",
66
- webSdkProject: "",
67
- webSdkDevKey: "",
68
65
  // Public revenexx API (gateway) — the live "api" service registry
69
66
  // implementations. Stand-in for the generated web SDKs; the key is a
70
67
  // tenant API key and stays server-side only.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/cover",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Cover \u2014 revenexx design system for Nuxt. Distributed as a Nuxt layer: generic UI components, theming tokens and stores shared by the demo shop, custom storefronts and the Blokkli theme.",
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -38,7 +38,7 @@
38
38
  "@formkit/vue": "^1.6.0",
39
39
  "@iconify-json/lucide": "^1.2.111",
40
40
  "@internationalized/date": "^3.12.0",
41
- "@revenexx/sdk": "^0.0.2",
41
+ "@revenexx/sdk": "^0.0.5",
42
42
  "@vueuse/core": "^14.3.0",
43
43
  "typesense": "^3.0.5"
44
44
  },
@@ -10,15 +10,16 @@ export default defineEventHandler(async (event): Promise<AccountOrderListRespons
10
10
  return { orders: [] };
11
11
  }
12
12
  try {
13
- const api = useRevenexxApi();
14
- const { items } = await api.get<{ items: LiveOrder[] }>("/v1/orders", {
15
- contact_id: refs.contact_id,
16
- limit: LIVE_HISTORY_LIMIT,
13
+ const sdk = useRevenexxSdk();
14
+ // Raw call: orders.list does not declare its query parameters in
15
+ // the contract yet, so ordersList() cannot filter by contact.
16
+ const { items } = await sdk.call<{ items: LiveOrder[] }>("GET", "/v1/orders", {
17
+ query: { contact_id: refs.contact_id, limit: LIVE_HISTORY_LIMIT },
17
18
  });
18
19
  // The list rows carry no positions — load the aggregates (the
19
20
  // history is capped, so this stays a bounded fan-out).
20
21
  const aggregates = await Promise.all(
21
- items.map(row => api.get<LiveOrder>(`/v1/orders/${row.id}`)),
22
+ items.map(async row => await sdk.orders.ordersGet({ id: row.id }) as unknown as LiveOrder),
22
23
  );
23
24
  const orders = aggregates
24
25
  .sort((a, b) => (b.placed_at ?? b.created_at).localeCompare(a.placed_at ?? a.created_at))
@@ -33,13 +33,13 @@ export default defineEventHandler(async (event): Promise<AccountProfileResponse>
33
33
  deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
34
34
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
35
35
  }
36
- if (!session.fallbackCookie && !session.personaId) {
36
+ if (!session.personaId && !session.userId) {
37
37
  deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
38
38
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
39
39
  }
40
40
 
41
- // Identity comes from the registry: "mock" serves the demo user, "sdk"
42
- // resolves the real account through the platform.
41
+ // Identity comes from the registry: "mock" serves the demo user, "api"
42
+ // resolves the real customer through the public API.
43
43
  const user = await getAccountService(event).getUser(event);
44
44
 
45
45
  // Profile extras (phone, company) live in the mutable demo store until a
@@ -73,15 +73,17 @@ export default defineEventHandler(async (event): Promise<AccountProfileResponse>
73
73
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
74
74
  }
75
75
 
76
- if (!session.fallbackCookie) {
76
+ if (!session.personaId && !session.userId) {
77
77
  deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
78
78
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
79
79
  }
80
80
 
81
- let user: { $id: string; name: string; email: string };
81
+ // Identity comes from the registry: "mock" serves the demo user, "api"
82
+ // resolves the real customer through the public API.
83
+ let user: { $id: string | number; name: string; email: string };
82
84
  try {
83
- const accountUser = await useAuthenticatedAccount(session.fallbackCookie).accountGet();
84
- user = { $id: accountUser.$id, name: accountUser.name, email: accountUser.email };
85
+ const accountUser = await getAccountService(event).getUser(event);
86
+ user = { $id: accountUser.id, name: accountUser.name, email: accountUser.email };
85
87
  }
86
88
  catch {
87
89
  deleteCookie(event, SESSION_COOKIE_NAME, sessionCookieOptions());
@@ -22,16 +22,13 @@ export default defineEventHandler(async (event) => {
22
22
  return result;
23
23
  }
24
24
  catch (err) {
25
- if (isSdkUserError(err)) {
26
- throw createError({ statusCode: err.code, data: { type: err.type } });
27
- }
28
25
  if (isApiUserError(err)) {
29
- throw createError({ statusCode: err.statusCode, data: { type: apiAuthErrorType(err) } });
26
+ throw createError({ statusCode: err.code, data: { type: apiAuthErrorType(err) } });
30
27
  }
31
28
  if (isError(err)) {
32
29
  throw err;
33
30
  }
34
- getLogService().error("Auth request failed: login", sdkErrorContext(err));
31
+ getLogService().error("Auth request failed: login", apiErrorContext(err));
35
32
  throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
36
33
  }
37
34
  });
@@ -6,27 +6,19 @@ export default defineEventHandler(async (event) => {
6
6
  // what the reset-password page expects.
7
7
  if (resolveAuthServiceKey(event) === "api") {
8
8
  try {
9
- await useRevenexxApi().post("/v1/customers/auth/recovery", { email, url });
9
+ await useRevenexxSdk().customers.customersAuthRecovery({ email, url });
10
10
  return { ok: true };
11
11
  }
12
12
  catch (err) {
13
13
  if (isApiUserError(err)) {
14
- throw createError({ statusCode: err.statusCode, data: { type: apiAuthErrorType(err) } });
14
+ throw createError({ statusCode: err.code, data: { type: apiAuthErrorType(err) } });
15
15
  }
16
16
  getLogService().error("API request failed: recovery", apiErrorContext(err));
17
17
  throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
18
18
  }
19
19
  }
20
20
 
21
- try {
22
- await useShopSdkAccount().accountCreateRecovery({ email, url });
23
- return { ok: true };
24
- }
25
- catch (err) {
26
- if (isSdkUserError(err)) {
27
- throw createError({ statusCode: err.code, data: { type: err.type } });
28
- }
29
- getLogService().error("SDK request failed: recovery", sdkErrorContext(err));
30
- throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
31
- }
21
+ // Mock mode: no mail infrastructure — acknowledge so the demo flow
22
+ // (form → success state) stays walkable offline.
23
+ return { ok: true };
32
24
  });
@@ -7,8 +7,8 @@ export default defineEventHandler(async (event) => {
7
7
  // Public-API mode: confirm the recovery through the customers app.
8
8
  if (resolveAuthServiceKey(event) === "api") {
9
9
  try {
10
- await useRevenexxApi().put("/v1/customers/auth/recovery", {
11
- user_id: userId,
10
+ await useRevenexxSdk().customers.customersAuthRecoveryConfirm({
11
+ userId,
12
12
  secret,
13
13
  password,
14
14
  });
@@ -16,22 +16,13 @@ export default defineEventHandler(async (event) => {
16
16
  }
17
17
  catch (err) {
18
18
  if (isApiUserError(err)) {
19
- throw createError({ statusCode: err.statusCode, data: { type: apiAuthErrorType(err) } });
19
+ throw createError({ statusCode: err.code, data: { type: apiAuthErrorType(err) } });
20
20
  }
21
21
  getLogService().error("API request failed: recovery-confirm", apiErrorContext(err));
22
22
  throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
23
23
  }
24
24
  }
25
25
 
26
- try {
27
- await useShopSdkAccount().accountUpdateRecovery({ userId, secret, password });
28
- return { ok: true };
29
- }
30
- catch (err) {
31
- if (isSdkUserError(err)) {
32
- throw createError({ statusCode: err.code, data: { type: err.type } });
33
- }
34
- getLogService().error("SDK request failed: recovery-confirm", sdkErrorContext(err));
35
- throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
36
- }
26
+ // Mock mode: accept the reset so the demo flow completes offline.
27
+ return { ok: true };
37
28
  });
@@ -1,5 +1,3 @@
1
- import { ID } from "@revenexx/sdk";
2
-
3
1
  interface RegistrationProfilePayload {
4
2
  mode: "new" | "existing";
5
3
  company: string;
@@ -68,59 +66,38 @@ export default defineEventHandler(async (event) => {
68
66
  // Public-API mode: the customers app is the system of record. A company
69
67
  // registration creates the organization (and its platform team) in the
70
68
  // same call; the contact mirrors to a platform user behind the scenes.
71
- if (serviceKey === "api") {
72
- const fullName = (name ?? "").trim();
73
- const splitAt = fullName.lastIndexOf(" ");
74
- const firstName = splitAt > 0 ? fullName.slice(0, splitAt) : fullName;
75
- const lastName = splitAt > 0 ? fullName.slice(splitAt + 1) : "";
76
-
77
- try {
78
- const { contact, user_id: userId } = await useRevenexxApi().post<{
79
- contact: { id: string; email: string };
80
- user_id: string;
81
- }>("/v1/customers/auth/register", {
82
- email,
83
- password,
84
- first_name: firstName || undefined,
85
- last_name: lastName || undefined,
86
- organization_name: profile?.company || undefined,
87
- locale: resolveLocale(event),
88
- });
89
-
90
- const user = { $id: userId, name: fullName || email, email: contact.email };
91
- if (profile?.company) {
92
- await persistRegistrationRequest(user, profile);
93
- }
94
- return user;
95
- }
96
- catch (err) {
97
- if (isApiUserError(err)) {
98
- throw createError({ statusCode: err.statusCode, data: { type: apiAuthErrorType(err) } });
99
- }
100
- getLogService().error("API request failed: register", apiErrorContext(err));
101
- throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
102
- }
103
- }
69
+ // Public-API mode: the customers app is the system of record. A company
70
+ // registration creates the organization (and its platform team) in the
71
+ // same call; the contact mirrors to a platform user behind the scenes.
72
+ const fullName = (name ?? "").trim();
73
+ const splitAt = fullName.lastIndexOf(" ");
74
+ const firstName = splitAt > 0 ? fullName.slice(0, splitAt) : fullName;
75
+ const lastName = splitAt > 0 ? fullName.slice(splitAt + 1) : "";
104
76
 
105
77
  try {
106
- const user = await useShopSdkAccount().accountCreate({
107
- userId: ID.unique(),
78
+ const { contact, user_id: userId } = await useRevenexxSdk().customers.customersAuthRegister({
108
79
  email,
109
80
  password,
110
- name,
111
- });
81
+ ...(firstName ? { firstName } : {}),
82
+ ...(lastName ? { lastName } : {}),
83
+ ...(profile?.company ? { organizationName: profile.company } : {}),
84
+ locale: resolveLocale(event),
85
+ }) as unknown as {
86
+ contact: { id: string; email: string };
87
+ user_id: string;
88
+ };
112
89
 
90
+ const user = { $id: userId, name: fullName || email, email: contact.email };
113
91
  if (profile?.company) {
114
92
  await persistRegistrationRequest(user, profile);
115
93
  }
116
-
117
- return { $id: user.$id, name: user.name, email: user.email };
94
+ return user;
118
95
  }
119
96
  catch (err) {
120
- if (isSdkUserError(err)) {
121
- throw createError({ statusCode: err.code, data: { type: err.type } });
97
+ if (isApiUserError(err)) {
98
+ throw createError({ statusCode: err.code, data: { type: apiAuthErrorType(err) } });
122
99
  }
123
- getLogService().error("SDK request failed: register", sdkErrorContext(err));
100
+ getLogService().error("API request failed: register", apiErrorContext(err));
124
101
  throw createError({ statusCode: 503, data: { type: "service_unavailable" } });
125
102
  }
126
103
  });
@@ -1,3 +1,6 @@
1
+ import { OrderPaymentStatus } from "@revenexx/sdk";
2
+ import type { Models } from "@revenexx/sdk";
3
+
1
4
  // Method codes are generic — live mode validates them against the
2
5
  // payments app; the demo-only methods keep their extra fields.
3
6
  interface OrderPayment {
@@ -140,11 +143,11 @@ export default defineEventHandler(async (event) => {
140
143
  if (liveShipping && body.deliveryMethod) {
141
144
  const address = body.address as Record<string, unknown> | undefined;
142
145
  try {
143
- const { rates } = await useRevenexxApi().post<{ rates: Array<{ code: string; price: number }> }>("/v1/shipping/rates", {
144
- order_value: calculation.totals.find(row => row.key === "subtotal")?.amount
146
+ const { rates } = await useRevenexxSdk().shipping.shippingRates({
147
+ orderValue: calculation.totals.find(row => row.key === "subtotal")?.amount
145
148
  ?? calculation.totals.find(row => row.key === "total")?.amount ?? 0,
146
149
  country: String(address?.country ?? ""),
147
- });
150
+ }) as unknown as { rates: Array<{ code: string; price: number }> };
148
151
  const rate = rates.find(r => r.code === body.deliveryMethod);
149
152
  if (!rate) {
150
153
  throw createError({ status: 422, message: `Delivery method '${body.deliveryMethod}' is not available for this order` });
@@ -177,13 +180,15 @@ export default defineEventHandler(async (event) => {
177
180
  const totalRow = calculation.totals.find(row => row.key === "total");
178
181
  const shippingRow = calculation.totals.find(row => row.key === "shipping");
179
182
  try {
180
- const placed = await useRevenexxApi().post<{ id: string; number: string }>("/v1/orders/place", {
181
- ...sessionOrderRefs(event),
183
+ const refs = sessionOrderRefs(event);
184
+ const placed = await useRevenexxSdk().orders.ordersPlace({
185
+ ...(refs.contact_id ? { contactId: refs.contact_id } : {}),
186
+ ...(refs.organization_id ? { organizationId: refs.organization_id } : {}),
182
187
  currency: calculation.currency,
183
- ...(body.orderNumber ? { customer_order_number: body.orderNumber } : {}),
188
+ ...(body.orderNumber ? { customerOrderNumber: body.orderNumber } : {}),
184
189
  buyer: user ? { name: user.name, email: user.email } : { email: body.contactEmail ?? null },
185
- billing_address: billing ?? null,
186
- shipping_address: address ?? null,
190
+ billingAddress: (billing ?? null) as object,
191
+ shippingAddress: (address ?? null) as object,
187
192
  payment: { method: payment.method },
188
193
  shipping: {
189
194
  method: body.deliveryMethod ?? "standard",
@@ -191,7 +196,7 @@ export default defineEventHandler(async (event) => {
191
196
  },
192
197
  // The order carries what the customer saw (and the payment
193
198
  // charges): the checkout calculation's total.
194
- grand_total: Math.max(0, Math.round(((totalRow?.amount ?? 0) + liveShippingAdjustment) * 100) / 100),
199
+ grandTotal: Math.max(0, Math.round(((totalRow?.amount ?? 0) + liveShippingAdjustment) * 100) / 100),
195
200
  items: (body.items as Array<Record<string, unknown>>).map((item, index) => ({
196
201
  product_id: String(item.id),
197
202
  sku: String(item.sku ?? "") || undefined,
@@ -204,19 +209,19 @@ export default defineEventHandler(async (event) => {
204
209
  ...(item.categorySlug ? { categorySlug: String(item.categorySlug) } : {}),
205
210
  ...(item.subcategorySlug ? { subcategorySlug: String(item.subcategorySlug) } : {}),
206
211
  },
207
- })),
208
- user_data: {
212
+ })) as unknown as Models.OrderItemCreateRequest[],
213
+ userData: {
209
214
  ...(body.deliveryNote ? { delivery_note: body.deliveryNote } : {}),
210
215
  ...(body.orderNote ? { order_note: body.orderNote } : {}),
211
216
  ...(body.partialDelivery !== undefined ? { partial_delivery: body.partialDelivery } : {}),
212
217
  ...(body.requestedDate ? { requested_date: body.requestedDate } : {}),
213
218
  },
214
- });
219
+ }) as unknown as { id: string; number: string };
215
220
  orderId = placed.number;
216
221
  liveOrderUuid = placed.id;
217
222
  }
218
223
  catch (err) {
219
- if (err instanceof RevenexxApiError && (err.statusCode === 400 || err.statusCode === 422)) {
224
+ if (err instanceof RevenexxException && (err.code === 400 || err.code === 422)) {
220
225
  throw createError({ status: 422, message: err.message });
221
226
  }
222
227
  getLogService().error("Order placement failed", apiErrorContext(err));
@@ -237,19 +242,18 @@ export default defineEventHandler(async (event) => {
237
242
  : address;
238
243
  const country = String(body.billingCountry ?? billing?.country ?? "");
239
244
  try {
240
- const created = await useRevenexxApi().post<{ id: string; status: string; error_message?: string | null }>("/v1/payments", {
241
- method_code: payment.method,
245
+ const created = await useRevenexxSdk().payments.paymentsCreate({
246
+ methodCode: payment.method,
242
247
  amount: Math.max(0, Math.round(((totalRow?.amount ?? 0) + liveShippingAdjustment) * 100) / 100),
243
248
  currency: calculation.currency,
244
249
  country,
245
- order_ref: orderId,
246
- contact_id: undefined,
247
- idempotency_key: body.checkoutSessionToken,
250
+ orderRef: orderId,
251
+ idempotencyKey: body.checkoutSessionToken,
248
252
  metadata: {
249
253
  ...(payment.poNumber ? { po_number: payment.poNumber } : {}),
250
254
  ...(payment.costCenter ? { cost_center: payment.costCenter } : {}),
251
255
  },
252
- });
256
+ }) as unknown as { id: string; status: string; error_message?: string | null };
253
257
  if (created.status === "failed") {
254
258
  throw createError({ status: 402, message: created.error_message ?? "Payment was declined" });
255
259
  }
@@ -259,7 +263,7 @@ export default defineEventHandler(async (event) => {
259
263
  // A placed live order must not survive a failed payment.
260
264
  if (liveOrderUuid) {
261
265
  try {
262
- await useRevenexxApi().post(`/v1/orders/${liveOrderUuid}/cancel`, { reason: "payment failed" });
266
+ await useRevenexxSdk().orders.ordersCancel({ id: liveOrderUuid, reason: "payment failed" });
263
267
  }
264
268
  catch (cancelErr) {
265
269
  getLogService().error("Order cancel after payment failure failed", apiErrorContext(cancelErr));
@@ -268,7 +272,7 @@ export default defineEventHandler(async (event) => {
268
272
  if (isError(err)) {
269
273
  throw err;
270
274
  }
271
- if (err instanceof RevenexxApiError && (err.statusCode === 400 || err.statusCode === 422)) {
275
+ if (err instanceof RevenexxException && (err.code === 400 || err.code === 422)) {
272
276
  throw createError({ status: 422, message: err.message });
273
277
  }
274
278
  getLogService().error("Payment creation failed", apiErrorContext(err));
@@ -279,10 +283,10 @@ export default defineEventHandler(async (event) => {
279
283
  // The payment dimension of the live order follows the payment outcome.
280
284
  if (liveOrderUuid && livePaymentStatus) {
281
285
  const mapped = livePaymentStatus === "succeeded" || livePaymentStatus === "paid"
282
- ? "paid"
283
- : livePaymentStatus === "authorized" ? "authorized" : "pending";
286
+ ? OrderPaymentStatus.Paid
287
+ : livePaymentStatus === "authorized" ? OrderPaymentStatus.Authorized : OrderPaymentStatus.Pending;
284
288
  try {
285
- await useRevenexxApi().post(`/v1/orders/${liveOrderUuid}/payment-status`, { status: mapped });
289
+ await useRevenexxSdk().orders.ordersPaymentStatusUpdate({ id: liveOrderUuid, status: mapped });
286
290
  }
287
291
  catch (err) {
288
292
  getLogService().error("Order payment-status sync failed", apiErrorContext(err));
@@ -38,10 +38,11 @@ export default defineEventHandler(async (event) => {
38
38
 
39
39
  const locale: Locale = resolveLocale(event);
40
40
  try {
41
- const { methods } = await useRevenexxApi().post<{ methods: ApiEligibleMethod[] }>(
42
- "/v1/payments/methods/eligible",
43
- { amount, country, currency },
44
- );
41
+ const { methods } = await useRevenexxSdk().payments.paymentsMethodsEligible({
42
+ amount,
43
+ country,
44
+ currency,
45
+ }) as unknown as { methods: ApiEligibleMethod[] };
45
46
 
46
47
  const options: PaymentOption[] = methods.map(m => ({
47
48
  method: m.code,
@@ -45,10 +45,12 @@ export default defineEventHandler(async (event) => {
45
45
 
46
46
  const locale: Locale = resolveLocale(event);
47
47
  try {
48
- const { rates } = await useRevenexxApi().post<{ rates: ApiShippingRate[] }>(
49
- "/v1/shipping/rates",
50
- { order_value: amount, country, weight, quantity },
51
- );
48
+ const { rates } = await useRevenexxSdk().shipping.shippingRates({
49
+ orderValue: amount,
50
+ country,
51
+ ...(weight !== undefined ? { weight } : {}),
52
+ ...(quantity !== undefined ? { quantity } : {}),
53
+ }) as unknown as { rates: ApiShippingRate[] };
52
54
 
53
55
  const methods: DeliveryOption[] = rates.map(rate => ({
54
56
  method: rate.code,
@@ -21,7 +21,7 @@ export interface AuthLoginResult {
21
21
  * Service contract for authentication.
22
22
  * - "mock" signs B2B demo personas in without any external dependency
23
23
  * (see server/config/account/personas.json)
24
- * - "sdk" authenticates against the platform via the revenexx web SDK
24
+ * - "api" authenticates against the platform via the public revenexx API
25
25
  * Both implementations manage the same session cookie, so the client-side
26
26
  * auth flow (store, guards, middleware) is identical.
27
27
  */
@@ -0,0 +1,44 @@
1
+ import type { H3Event } from "h3";
2
+
3
+ import { ApiAuthService } from "./ApiAuthService";
4
+ import type { AccountUser, IAccountService } from "../interfaces/account";
5
+
6
+ function createInitials(name: string): string {
7
+ const parts = name
8
+ .trim()
9
+ .split(/\s+/)
10
+ .filter(Boolean);
11
+ if (parts.length === 0) {
12
+ return "";
13
+ }
14
+ return parts
15
+ .slice(0, 2)
16
+ .map(part => part[0]?.toUpperCase() ?? "")
17
+ .join("");
18
+ }
19
+
20
+ /**
21
+ * Live identity via the public revenexx API (customers app): resolves
22
+ * the current user from the request session through the same
23
+ * `/v1/customers/auth/me` path the auth service uses.
24
+ * Register via app.config → accountService: "api".
25
+ */
26
+ export class ApiAccountService implements IAccountService {
27
+ private readonly auth = new ApiAuthService();
28
+
29
+ async getUser(event?: H3Event): Promise<AccountUser> {
30
+ if (!event) {
31
+ throw createError({ statusCode: 500, statusMessage: "ApiAccountService requires the request event" });
32
+ }
33
+ const user = await this.auth.me(event);
34
+ if (!user) {
35
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
36
+ }
37
+ return {
38
+ id: user.$id,
39
+ name: user.name,
40
+ email: user.email,
41
+ initials: createInitials(user.name),
42
+ };
43
+ }
44
+ }
@@ -34,9 +34,9 @@ function contactName(contact: ApiContact | null, fallback: string): string {
34
34
 
35
35
  /**
36
36
  * Live authentication via the public revenexx API (customers app):
37
- * POST /v1/customers/auth/login → platform session (incl. secret) + contact
38
- * POST /v1/customers/auth/me → platform user + contact
39
- * POST /v1/customers/auth/logout → revokes the platform session
37
+ * customersAuthLogin → platform session (incl. secret) + contact
38
+ * auth/me (raw call) → platform user + contact
39
+ * customersAuthLogout → revokes the platform session
40
40
  *
41
41
  * Session state lives in the same cookie as the other implementations, so
42
42
  * the client-side auth flow (store, guards, middleware) is identical. The
@@ -44,11 +44,10 @@ function contactName(contact: ApiContact | null, fallback: string): string {
44
44
  */
45
45
  export class ApiAuthService implements IAuthService {
46
46
  async login(event: H3Event, email: string, password: string): Promise<AuthLoginResult> {
47
- const api = useRevenexxApi();
48
- const { session, contact } = await api.post<{ session: ApiSession; contact: ApiContact | null }>(
49
- "/v1/customers/auth/login",
50
- { email, password },
51
- );
47
+ const { session, contact } = await useRevenexxSdk().customers.customersAuthLogin({ email, password }) as unknown as {
48
+ session: ApiSession;
49
+ contact: ApiContact | null;
50
+ };
52
51
 
53
52
  const storedSession: StoredSession = {
54
53
  id: session.$id,
@@ -94,11 +93,13 @@ export class ApiAuthService implements IAuthService {
94
93
 
95
94
  try {
96
95
  // session_id makes the customers app validate the session is still
97
- // alive — a logged-out/revoked session answers 401 here.
98
- const { user, contact } = await useRevenexxApi().post<{
96
+ // alive — a logged-out/revoked session answers 401 here. Raw call:
97
+ // the AuthMeRequest contract does not declare session_id yet, so
98
+ // the generated customersAuthMe() would drop the session check.
99
+ const { user, contact } = await useRevenexxSdk().call<{
99
100
  user: { $id: string; name?: string; email: string };
100
101
  contact: ApiContact | null;
101
- }>("/v1/customers/auth/me", { user_id: parsed.userId, session_id: parsed.id });
102
+ }>("POST", "/v1/customers/auth/me", { body: { user_id: parsed.userId, session_id: parsed.id } });
102
103
 
103
104
  return {
104
105
  $id: user.$id,
@@ -122,9 +123,9 @@ export class ApiAuthService implements IAuthService {
122
123
  try {
123
124
  const parsed = JSON.parse(raw) as StoredSession;
124
125
  if (parsed.userId && parsed.id) {
125
- await useRevenexxApi().post("/v1/customers/auth/logout", {
126
- user_id: parsed.userId,
127
- session_id: parsed.id,
126
+ await useRevenexxSdk().customers.customersAuthLogout({
127
+ userId: parsed.userId,
128
+ sessionId: parsed.id,
128
129
  });
129
130
  }
130
131
  }