@ominity/api-typescript 1.4.8 → 1.6.0

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 (94) hide show
  1. package/dist/commonjs/funcs/commerce/cartItemsCreate.js +1 -1
  2. package/dist/commonjs/funcs/commerce/cartItemsCreate.js.map +1 -1
  3. package/dist/commonjs/funcs/commerce/customerSubscriptions.d.ts +6 -0
  4. package/dist/commonjs/funcs/commerce/customerSubscriptions.d.ts.map +1 -1
  5. package/dist/commonjs/funcs/commerce/customerSubscriptions.js +13 -1
  6. package/dist/commonjs/funcs/commerce/customerSubscriptions.js.map +1 -1
  7. package/dist/commonjs/lib/config.d.ts +3 -3
  8. package/dist/commonjs/lib/config.js +1 -1
  9. package/dist/commonjs/models/commerce/cart-item.d.ts +19 -14
  10. package/dist/commonjs/models/commerce/cart-item.d.ts.map +1 -1
  11. package/dist/commonjs/models/commerce/cart-item.js +28 -23
  12. package/dist/commonjs/models/commerce/cart-item.js.map +1 -1
  13. package/dist/commonjs/models/commerce/index.d.ts +1 -0
  14. package/dist/commonjs/models/commerce/index.d.ts.map +1 -1
  15. package/dist/commonjs/models/commerce/index.js +1 -0
  16. package/dist/commonjs/models/commerce/index.js.map +1 -1
  17. package/dist/commonjs/models/commerce/product.d.ts +3 -0
  18. package/dist/commonjs/models/commerce/product.d.ts.map +1 -1
  19. package/dist/commonjs/models/commerce/product.js +2 -0
  20. package/dist/commonjs/models/commerce/product.js.map +1 -1
  21. package/dist/commonjs/models/commerce/subscription-change.d.ts +72 -0
  22. package/dist/commonjs/models/commerce/subscription-change.d.ts.map +1 -0
  23. package/dist/commonjs/models/commerce/subscription-change.js +96 -0
  24. package/dist/commonjs/models/commerce/subscription-change.js.map +1 -0
  25. package/dist/commonjs/models/commerce/subscription.d.ts +2 -0
  26. package/dist/commonjs/models/commerce/subscription.d.ts.map +1 -1
  27. package/dist/commonjs/models/commerce/subscription.js +1 -0
  28. package/dist/commonjs/models/commerce/subscription.js.map +1 -1
  29. package/dist/commonjs/models/errors/error-response.d.ts +6 -0
  30. package/dist/commonjs/models/errors/error-response.d.ts.map +1 -1
  31. package/dist/commonjs/models/errors/error-response.js +8 -1
  32. package/dist/commonjs/models/errors/error-response.js.map +1 -1
  33. package/dist/commonjs/models/operations/commerce-customer-subscriptions.d.ts +61 -0
  34. package/dist/commonjs/models/operations/commerce-customer-subscriptions.d.ts.map +1 -1
  35. package/dist/commonjs/models/operations/commerce-customer-subscriptions.js +17 -1
  36. package/dist/commonjs/models/operations/commerce-customer-subscriptions.js.map +1 -1
  37. package/dist/commonjs/sdk/commerce/customer-subscriptions.d.ts +15 -0
  38. package/dist/commonjs/sdk/commerce/customer-subscriptions.d.ts.map +1 -1
  39. package/dist/commonjs/sdk/commerce/customer-subscriptions.js +15 -0
  40. package/dist/commonjs/sdk/commerce/customer-subscriptions.js.map +1 -1
  41. package/dist/esm/funcs/commerce/cartItemsCreate.js +1 -1
  42. package/dist/esm/funcs/commerce/cartItemsCreate.js.map +1 -1
  43. package/dist/esm/funcs/commerce/customerSubscriptions.d.ts +6 -0
  44. package/dist/esm/funcs/commerce/customerSubscriptions.d.ts.map +1 -1
  45. package/dist/esm/funcs/commerce/customerSubscriptions.js +6 -0
  46. package/dist/esm/funcs/commerce/customerSubscriptions.js.map +1 -1
  47. package/dist/esm/lib/config.d.ts +3 -3
  48. package/dist/esm/lib/config.js +1 -1
  49. package/dist/esm/models/commerce/cart-item.d.ts +19 -14
  50. package/dist/esm/models/commerce/cart-item.d.ts.map +1 -1
  51. package/dist/esm/models/commerce/cart-item.js +27 -22
  52. package/dist/esm/models/commerce/cart-item.js.map +1 -1
  53. package/dist/esm/models/commerce/index.d.ts +1 -0
  54. package/dist/esm/models/commerce/index.d.ts.map +1 -1
  55. package/dist/esm/models/commerce/index.js +1 -0
  56. package/dist/esm/models/commerce/index.js.map +1 -1
  57. package/dist/esm/models/commerce/product.d.ts +3 -0
  58. package/dist/esm/models/commerce/product.d.ts.map +1 -1
  59. package/dist/esm/models/commerce/product.js +2 -0
  60. package/dist/esm/models/commerce/product.js.map +1 -1
  61. package/dist/esm/models/commerce/subscription-change.d.ts +72 -0
  62. package/dist/esm/models/commerce/subscription-change.d.ts.map +1 -0
  63. package/dist/esm/models/commerce/subscription-change.js +60 -0
  64. package/dist/esm/models/commerce/subscription-change.js.map +1 -0
  65. package/dist/esm/models/commerce/subscription.d.ts +2 -0
  66. package/dist/esm/models/commerce/subscription.d.ts.map +1 -1
  67. package/dist/esm/models/commerce/subscription.js +1 -0
  68. package/dist/esm/models/commerce/subscription.js.map +1 -1
  69. package/dist/esm/models/errors/error-response.d.ts +6 -0
  70. package/dist/esm/models/errors/error-response.d.ts.map +1 -1
  71. package/dist/esm/models/errors/error-response.js +8 -1
  72. package/dist/esm/models/errors/error-response.js.map +1 -1
  73. package/dist/esm/models/operations/commerce-customer-subscriptions.d.ts +61 -0
  74. package/dist/esm/models/operations/commerce-customer-subscriptions.d.ts.map +1 -1
  75. package/dist/esm/models/operations/commerce-customer-subscriptions.js +16 -0
  76. package/dist/esm/models/operations/commerce-customer-subscriptions.js.map +1 -1
  77. package/dist/esm/sdk/commerce/customer-subscriptions.d.ts +15 -0
  78. package/dist/esm/sdk/commerce/customer-subscriptions.d.ts.map +1 -1
  79. package/dist/esm/sdk/commerce/customer-subscriptions.js +16 -1
  80. package/dist/esm/sdk/commerce/customer-subscriptions.js.map +1 -1
  81. package/jsr.json +1 -1
  82. package/package.json +1 -1
  83. package/src/funcs/commerce/cartItemsCreate.ts +1 -1
  84. package/src/funcs/commerce/customerSubscriptions.ts +6 -0
  85. package/src/lib/config.ts +1 -1
  86. package/src/models/commerce/cart-item.ts +42 -27
  87. package/src/models/commerce/index.ts +1 -0
  88. package/src/models/commerce/product.ts +4 -0
  89. package/src/models/commerce/subscription-change.ts +137 -0
  90. package/src/models/commerce/subscription.ts +3 -0
  91. package/src/models/errors/error-response.ts +13 -1
  92. package/src/models/operations/commerce-customer-subscriptions.ts +37 -0
  93. package/src/sdk/commerce/customer-subscriptions.ts +16 -1
  94. package/tests/core-operations.test.mjs +187 -3
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import * as api from "../dist/esm/index.js";
4
4
  import { ErrorResponse } from "../dist/esm/models/errors/index.js";
5
- import { Subscription$inboundSchema } from "../dist/esm/models/index.js";
5
+ import { CartItem$inboundSchema, Product$inboundSchema, Subscription$inboundSchema } from "../dist/esm/models/index.js";
6
6
 
7
7
  const noRetries = { retries: { strategy: "none" } };
8
8
 
@@ -43,6 +43,12 @@ const operationCases = [
43
43
  ["customerSubscriptionsRenew", { customerId: 1, id: 2 }, "POST", "/commerce/customers/1/subscriptions/2/renew"],
44
44
  ["customerSubscriptionsListTransitionProducts", { customerId: 1, subscriptionId: 2 }, "GET", "/commerce/customers/1/subscriptions/2/transition"],
45
45
  ["customerSubscriptionsGetTransitionProduct", { customerId: 1, subscriptionId: 2, productId: 3 }, "GET", "/commerce/customers/1/subscriptions/2/transition/3"],
46
+ ["customerSubscriptionsListTransitions", { customerId: 1, subscriptionId: 2 }, "GET", "/commerce/customers/1/subscriptions/2/transitions"],
47
+ ["customerSubscriptionsQuoteChange", { customerId: 1, subscriptionId: 2, data: { toOfferId: 12 } }, "POST", "/commerce/customers/1/subscriptions/2/changes/quote"],
48
+ ["customerSubscriptionsConfirmChange", { customerId: 1, subscriptionId: 2, data: { quoteId: "change-id" } }, "POST", "/commerce/customers/1/subscriptions/2/changes"],
49
+ ["customerSubscriptionsCancelChange", { customerId: 1, subscriptionId: 2, changeId: "change-id" }, "DELETE", "/commerce/customers/1/subscriptions/2/changes/change-id"],
50
+ ["customerSubscriptionsCancel", { customerId: 1, subscriptionId: 2 }, "POST", "/commerce/customers/1/subscriptions/2/cancellation"],
51
+ ["customerSubscriptionsRevokeCancellation", { customerId: 1, subscriptionId: 2 }, "DELETE", "/commerce/customers/1/subscriptions/2/cancellation"],
46
52
  ["shippingClassesList", {}, "GET", "/commerce/shipping-classes"],
47
53
  ["shippingClassesGet", { id: 1 }, "GET", "/commerce/shipping-classes/1"],
48
54
  ["invoicesCreate", { data: {} }, "POST", "/commerce/invoices"],
@@ -100,7 +106,7 @@ test("all added core operations construct the registered API method and path", a
100
106
  }
101
107
 
102
108
  assert.equal(seen.length, operationCases.length);
103
- assert.equal(operationCases.length, 69);
109
+ assert.equal(operationCases.length, 75);
104
110
  });
105
111
 
106
112
  test("new operations encode deep filters, parse pagination, JSON bodies, and bytes", async () => {
@@ -137,6 +143,13 @@ test("new operations encode deep filters, parse pagination, JSON bodies, and byt
137
143
  assert.deepEqual(JSON.parse(seen[3].body), { source_language: "en", target_languages: ["nl"], fields: [{ key: "title", source: "Hello" }] });
138
144
  });
139
145
 
146
+ const amount = (value) => ({ value, currency: "EUR" });
147
+ const cartItemPayload = (extra = {}) => ({
148
+ resource: "cartitem", id: "item-1", cartId: "cart-1", productId: 6, productOfferId: 9, quantity: 1,
149
+ unitAmount: amount("19.99"), discountAmount: amount("0.00"), taxAmount: amount("3.47"), totalAmount: amount("19.99"),
150
+ isShippingRequired: true, updatedAt: "2026-10-29T00:00:00Z", createdAt: "2026-10-29T00:00:00Z", ...extra,
151
+ });
152
+
140
153
  test("cart item creation sends only the camelCase request body", async () => {
141
154
  let forwardedRequest;
142
155
  const sdk = new api.Ominity({
@@ -144,7 +157,7 @@ test("cart item creation sends only the camelCase request body", async () => {
144
157
  httpClient: new api.HTTPClient({
145
158
  fetcher: async (request) => {
146
159
  forwardedRequest = request.clone();
147
- return new Response(JSON.stringify({ id: "item-1" }), {
160
+ return new Response(JSON.stringify(cartItemPayload()), {
148
161
  status: 201,
149
162
  headers: { "content-type": "application/hal+json" },
150
163
  });
@@ -163,6 +176,21 @@ test("cart item creation sends only the camelCase request body", async () => {
163
176
  });
164
177
  });
165
178
 
179
+ test("adding a product already in the cart accepts the 200 merge response", async () => {
180
+ const sdk = new api.Ominity({
181
+ serverURL: "https://example.test/api",
182
+ httpClient: new api.HTTPClient({
183
+ fetcher: async () => new Response(JSON.stringify(cartItemPayload({ quantity: 2 })), {
184
+ status: 200,
185
+ headers: { "content-type": "application/hal+json" },
186
+ }),
187
+ }),
188
+ });
189
+
190
+ const item = await sdk.commerce.cartItems.create("cart-1", "6", 1, noRetries);
191
+ assert.equal(item.quantity, 2);
192
+ });
193
+
166
194
  test("Subscription parses the corrected productId field", () => {
167
195
  const parsed = Subscription$inboundSchema.parse({
168
196
  resource: "subscription", id: 1, customerId: 2, productId: 3, intervalId: 4, status: "active",
@@ -171,6 +199,124 @@ test("Subscription parses the corrected productId field", () => {
171
199
  updatedAt: "2026-09-13T00:00:00Z", createdAt: "2026-09-13T00:00:00Z",
172
200
  });
173
201
  assert.equal(parsed.productId, 3);
202
+ assert.equal(parsed.reference, undefined);
203
+ });
204
+
205
+ const subscriptionChange = (overrides = {}) => ({
206
+ resource: "subscription_change",
207
+ id: "9d3c6a1e-0000-4000-8000-000000000001",
208
+ subscriptionId: 2,
209
+ type: "change",
210
+ direction: "upgrade",
211
+ status: "quoted",
212
+ timing: "immediate",
213
+ effectiveAt: "2026-10-28T00:00:00+00:00",
214
+ from: { productId: 3, offerId: 10, intervalId: 1, recurringAmount: { value: "10.00", currency: "EUR" }, quantity: 1 },
215
+ to: { productId: 4, offerId: 12, intervalId: 1, recurringAmount: { value: "20.00", currency: "EUR" }, quantity: 1 },
216
+ quote: {
217
+ lines: [
218
+ { type: "charge", description: "rest_of_period", amount: { value: "6.45", currency: "EUR" }, tax: { value: "1.35", currency: "EUR" }, taxRate: 21 },
219
+ { type: "credit", description: "unused_current_period", amount: { value: "3.23", currency: "EUR" }, tax: { value: "0.68", currency: "EUR" }, taxRate: 21 },
220
+ ],
221
+ charge: { value: "6.45", currency: "EUR" },
222
+ credit: { value: "3.23", currency: "EUR" },
223
+ settlement: "next_invoice",
224
+ },
225
+ warnings: [],
226
+ quoteExpiresAt: "2026-10-28T01:00:00+00:00",
227
+ orderId: null,
228
+ createdAt: "2026-10-28T00:00:00+00:00",
229
+ _links: { subscription: { href: "https://example.test/api/v1/commerce/customers/1/subscriptions/2", type: "application/hal+json" } },
230
+ ...overrides,
231
+ });
232
+
233
+ test("subscription plan changes and cancellation follow the quote/confirm flow", async () => {
234
+ const seen = [];
235
+ const product = {
236
+ resource: "product", id: 4, sku: "PRO", ean: null, mpn: null, asin: null, title: "Pro", shortTitle: null,
237
+ coverImage: null, additionalImages: [], shortDescription: null, description: null, bulletpoints: [], boxContent: null,
238
+ type: "virtual", condition: "new", categoryId: 1, stock: null, isBackorderAllowed: false, routes: {}, searches: [],
239
+ customFields: [], publishedAt: null, updatedAt: "2026-10-28T00:00:00Z", createdAt: "2026-10-28T00:00:00Z",
240
+ };
241
+ const json = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
242
+ const responses = [
243
+ json(200, { count: 1, _embedded: { transitions: [{
244
+ productId: 4, offerId: 12, direction: "upgrade", timing: "immediate", effectiveAt: "2026-10-28T00:00:00+00:00",
245
+ allowed: true, reasons: [], charge: { value: "6.45", currency: "EUR" }, product,
246
+ }] } }),
247
+ json(201, subscriptionChange()),
248
+ json(200, subscriptionChange({
249
+ status: "awaiting_payment",
250
+ orderId: 55,
251
+ _links: { checkout: { href: "https://example.test/api/v1/commerce/customers/1/orders/55", type: "application/hal+json" } },
252
+ })),
253
+ new Response(null, { status: 204 }),
254
+ json(201, subscriptionChange({ type: "cancel", direction: "cancel", timing: "period_end", to: { productId: null, offerId: null, intervalId: null, recurringAmount: null, quantity: null }, warnings: ["notice_period_applies"] })),
255
+ json(200, subscriptionChange({ type: "cancel", status: "pending" })),
256
+ new Response(null, { status: 204 }),
257
+ ];
258
+ const sdk = new api.Ominity({
259
+ serverURL: "https://example.test/api",
260
+ httpClient: new api.HTTPClient({ fetcher: async (request) => {
261
+ seen.push({ path: new URL(request.url).pathname, method: request.method, body: await request.clone().text() });
262
+ return responses.shift();
263
+ } }),
264
+ });
265
+ const subscriptions = sdk.commerce.customerSubscriptions;
266
+
267
+ const transitions = await subscriptions.listTransitions({ customerId: 1, subscriptionId: 2 });
268
+ assert.equal(transitions.count, 1);
269
+ assert.equal(transitions.items[0].offerId, 12);
270
+ assert.equal(transitions.items[0].product.id, 4);
271
+
272
+ const quote = await subscriptions.quoteChange({ customerId: 1, subscriptionId: 2, data: { toOfferId: 12, timing: "immediate" } });
273
+ assert.equal(quote.status, "quoted");
274
+ assert.equal(quote.quote.lines.length, 2);
275
+ assert.equal(quote.links?.subscription?.href, "https://example.test/api/v1/commerce/customers/1/subscriptions/2");
276
+ assert.deepEqual(JSON.parse(seen[1].body), { toOfferId: 12, timing: "immediate" });
277
+
278
+ const confirmed = await subscriptions.confirmChange({ customerId: 1, subscriptionId: 2, data: { quoteId: quote.id } });
279
+ assert.equal(confirmed.orderId, 55);
280
+ assert.equal(confirmed.links?.checkout?.href, "https://example.test/api/v1/commerce/customers/1/orders/55");
281
+ assert.deepEqual(JSON.parse(seen[2].body), { quoteId: quote.id });
282
+
283
+ await subscriptions.cancelChange({ customerId: 1, subscriptionId: 2, changeId: quote.id });
284
+ assert.equal(seen[3].path, `/api/v1/commerce/customers/1/subscriptions/2/changes/${quote.id}`);
285
+
286
+ const cancellation = await subscriptions.cancel({ customerId: 1, subscriptionId: 2 });
287
+ assert.equal(cancellation.type, "cancel");
288
+ assert.equal(cancellation.to.recurringAmount, null);
289
+ assert.deepEqual(JSON.parse(seen[4].body), {});
290
+
291
+ await subscriptions.cancel({ customerId: 1, subscriptionId: 2, data: { quoteId: cancellation.id } });
292
+ assert.deepEqual(JSON.parse(seen[5].body), { quoteId: cancellation.id });
293
+
294
+ await subscriptions.revokeCancellation({ customerId: 1, subscriptionId: 2 });
295
+ assert.deepEqual(seen.map((request) => request.method), ["GET", "POST", "POST", "DELETE", "POST", "POST", "DELETE"]);
296
+ });
297
+
298
+ test("a refused subscription change exposes the reason code and field messages", async () => {
299
+ const sdk = new api.Ominity({
300
+ serverURL: "https://example.test/api",
301
+ httpClient: new api.HTTPClient({
302
+ fetcher: async () => new Response(JSON.stringify({
303
+ status: 422,
304
+ title: "Unprocessable Content",
305
+ detail: "Validation failed for the request",
306
+ code: "subscription.next_period_locked",
307
+ errors: { change: ["The next period is already being prepared."] },
308
+ _links: { documentation: { href: "https://docs.ominity.com/overview/handling-errors", type: "text/html" } },
309
+ }), { status: 422, headers: { "content-type": "application/json" } }),
310
+ }),
311
+ });
312
+
313
+ await assert.rejects(
314
+ sdk.commerce.customerSubscriptions.confirmChange({ customerId: 1, subscriptionId: 2, data: { quoteId: "change-id" } }, noRetries),
315
+ (error) => error instanceof ErrorResponse
316
+ && error.status === 422
317
+ && error.code === "subscription.next_period_locked"
318
+ && error.fields?.change?.[0] === "The next period is already being prepared.",
319
+ );
174
320
  });
175
321
 
176
322
  test("user action endpoints parse application/json responses and errors", async () => {
@@ -308,3 +454,41 @@ test("user customer membership failures preserve the API status and detail", asy
308
454
  && error.detail === "Could not authorize request",
309
455
  );
310
456
  });
457
+
458
+ test("Product keeps the offers requested with include=offers", () => {
459
+ const product = {
460
+ resource: "product", id: 4, sku: "PRO", ean: null, mpn: null, asin: null, title: "Pro", shortTitle: null,
461
+ coverImage: null, additionalImages: [], shortDescription: null, description: null, bulletpoints: [], boxContent: null,
462
+ type: "virtual", condition: "new", categoryId: 1, stock: null, isBackorderAllowed: false, routes: {}, searches: [],
463
+ customFields: [], publishedAt: null, updatedAt: "2026-10-28T00:00:00Z", createdAt: "2026-10-28T00:00:00Z",
464
+ };
465
+
466
+ assert.equal(Product$inboundSchema.parse(product).offers, undefined);
467
+ const parsed = Product$inboundSchema.parse({
468
+ ...product,
469
+ offers: [{
470
+ resource: "product_offer", id: 9, productId: 4, type: "once", intervalId: null, quantity: 1,
471
+ prices: { EUR: { amount: 19.99, formatted: "€ 19,99" } },
472
+ _links: { self: { href: "https://example.test/api/v1/commerce/products/4/offers/9", type: "application/hal+json" } },
473
+ }],
474
+ });
475
+ assert.equal(parsed.offers?.[0]?.id, 9);
476
+ assert.equal(parsed.offers?.[0]?.links?.self?.href, "https://example.test/api/v1/commerce/products/4/offers/9");
477
+ });
478
+
479
+ test("CartItem is typed and keeps the embedded product", () => {
480
+ const item = CartItem$inboundSchema.parse(cartItemPayload({
481
+ _embedded: { product: {
482
+ resource: "product", id: 6, sku: "PRO", ean: null, mpn: null, asin: null, title: "Pro", shortTitle: null,
483
+ coverImage: null, additionalImages: [], shortDescription: null, description: null, bulletpoints: [], boxContent: null,
484
+ type: "physical", condition: "new", categoryId: 1, stock: null, isBackorderAllowed: false, routes: {}, searches: [],
485
+ customFields: [], publishedAt: null, updatedAt: "2026-10-29T00:00:00Z", createdAt: "2026-10-29T00:00:00Z",
486
+ } },
487
+ _links: { cart: { href: "https://example.test/api/v1/commerce/carts/cart-1", type: "application/hal+json" } },
488
+ }));
489
+
490
+ assert.equal(item.product?.title, "Pro");
491
+ assert.equal(item.totalAmount.value, "19.99");
492
+ assert.equal(item.links?.cart?.href, "https://example.test/api/v1/commerce/carts/cart-1");
493
+ assert.equal("_embedded" in item, false);
494
+ });