@porulle/core 0.22.0 → 0.23.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.
@@ -37,6 +37,13 @@ export declare class CartService {
37
37
  lineItems: CartLineItem[];
38
38
  }>>;
39
39
  addItem(input: AddCartItemInput, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<CartLineItem>>;
40
+ /**
41
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
42
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
43
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
44
+ * defect this replaces was substituting a literal for exactly that.
45
+ */
46
+ private resolveUnitPrice;
40
47
  removeItem(cartId: string, itemId: string, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<void>>;
41
48
  updateQuantity(input: UpdateCartItemInput, actor?: Actor | null, ctx?: TxContext, presentedSecret?: string): Promise<Result<CartLineItem>>;
42
49
  merge(sourceCartId: string, targetCartId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<void>>;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/cart/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/cart/service.ts"],"names":[],"mappings":"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"}
@@ -191,12 +191,35 @@ export class CartService {
191
191
  item = updated;
192
192
  }
193
193
  else {
194
+ // A new line needs a price, and a price the system cannot determine is refused rather than
195
+ // invented. This used to read `processed.unitPriceSnapshot ?? 1000`: a silent default in a
196
+ // money path, which nothing in the response, the logs or the schema disclosed. An integrator
197
+ // found it by comparing a deployed cart reading 1000 against a catalog priced 14500-22800.
198
+ //
199
+ // A hook still wins when it supplies one, so bespoke pricing keeps its seam; otherwise the
200
+ // pricing step answers — the SAME step `resolveCurrentPrices` uses at checkout, so a cart and
201
+ // its order agree by construction rather than by an integrator remembering to install a hook.
202
+ const currency = processed.currency ?? cart.currency;
203
+ let unitPriceSnapshot = processed.unitPriceSnapshot;
204
+ if (unitPriceSnapshot === undefined) {
205
+ const resolved = await this.resolveUnitPrice({
206
+ entityId: processed.entityId,
207
+ currency,
208
+ quantity,
209
+ ...(processed.variantId != null
210
+ ? { variantId: processed.variantId }
211
+ : {}),
212
+ }, actor ?? null, ctx);
213
+ if (!resolved.ok)
214
+ return resolved;
215
+ unitPriceSnapshot = resolved.value;
216
+ }
194
217
  item = await this.repo.createLineItem({
195
218
  cartId: input.cartId,
196
219
  entityId: processed.entityId,
197
220
  quantity,
198
- unitPriceSnapshot: processed.unitPriceSnapshot ?? 1000,
199
- currency: processed.currency ?? cart.currency,
221
+ unitPriceSnapshot,
222
+ currency,
200
223
  metadata: processed.metadata ?? {},
201
224
  ...(processed.variantId !== undefined
202
225
  ? { variantId: processed.variantId }
@@ -206,6 +229,23 @@ export class CartService {
206
229
  await runAfterHooks(afterHooks, null, item, "addItem", context);
207
230
  return Ok(item);
208
231
  }
232
+ /**
233
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
234
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
235
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
236
+ * defect this replaces was substituting a literal for exactly that.
237
+ */
238
+ async resolveUnitPrice(input, actor, ctx) {
239
+ const pricing = this.deps.services.pricing;
240
+ if (typeof pricing?.resolve !== "function") {
241
+ return Err(new CommerceValidationError(`Cannot resolve a unit price for ${input.entityId}: no pricing service is configured.`));
242
+ }
243
+ const resolved = await pricing.resolve(input, actor, ctx);
244
+ if (!resolved.ok) {
245
+ return Err(new CommerceValidationError(`Cannot resolve a unit price for ${input.entityId} (${input.currency}). Configure a price for it, or supply unitPriceSnapshot from a cart.beforeAddItem hook.`));
246
+ }
247
+ return Ok(resolved.value.finalAmount);
248
+ }
209
249
  async removeItem(cartId, itemId, actor, ctx, presentedSecret) {
210
250
  try {
211
251
  assertPermission(actor ?? null, "cart:update");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -318,13 +318,39 @@ export class CartService {
318
318
  );
319
319
  item = updated!;
320
320
  } else {
321
+ // A new line needs a price, and a price the system cannot determine is refused rather than
322
+ // invented. This used to read `processed.unitPriceSnapshot ?? 1000`: a silent default in a
323
+ // money path, which nothing in the response, the logs or the schema disclosed. An integrator
324
+ // found it by comparing a deployed cart reading 1000 against a catalog priced 14500-22800.
325
+ //
326
+ // A hook still wins when it supplies one, so bespoke pricing keeps its seam; otherwise the
327
+ // pricing step answers — the SAME step `resolveCurrentPrices` uses at checkout, so a cart and
328
+ // its order agree by construction rather than by an integrator remembering to install a hook.
329
+ const currency = processed.currency ?? cart.currency;
330
+ let unitPriceSnapshot = processed.unitPriceSnapshot;
331
+ if (unitPriceSnapshot === undefined) {
332
+ const resolved = await this.resolveUnitPrice(
333
+ {
334
+ entityId: processed.entityId,
335
+ currency,
336
+ quantity,
337
+ ...(processed.variantId != null
338
+ ? { variantId: processed.variantId }
339
+ : {}),
340
+ },
341
+ actor ?? null,
342
+ ctx,
343
+ );
344
+ if (!resolved.ok) return resolved;
345
+ unitPriceSnapshot = resolved.value;
346
+ }
321
347
  item = await this.repo.createLineItem(
322
348
  {
323
349
  cartId: input.cartId,
324
350
  entityId: processed.entityId,
325
351
  quantity,
326
- unitPriceSnapshot: processed.unitPriceSnapshot ?? 1000,
327
- currency: processed.currency ?? cart.currency,
352
+ unitPriceSnapshot,
353
+ currency,
328
354
  metadata: processed.metadata ?? {},
329
355
  ...(processed.variantId !== undefined
330
356
  ? { variantId: processed.variantId }
@@ -339,6 +365,56 @@ export class CartService {
339
365
  return Ok(item);
340
366
  }
341
367
 
368
+ /**
369
+ * The unit price for a new cart line, from the pricing step. Refuses — naming the entity and the
370
+ * currency — when no price is configured, when the pricing service is absent, or when resolution
371
+ * fails for any other reason: every one of those is a value the system cannot determine, and the
372
+ * defect this replaces was substituting a literal for exactly that.
373
+ */
374
+ private async resolveUnitPrice(
375
+ input: {
376
+ entityId: string;
377
+ currency: string;
378
+ quantity: number;
379
+ variantId?: string;
380
+ },
381
+ actor: Actor | null,
382
+ ctx?: TxContext,
383
+ ): Promise<Result<number>> {
384
+ const pricing = this.deps.services.pricing as
385
+ | {
386
+ resolve(
387
+ params: {
388
+ entityId: string;
389
+ currency: string;
390
+ quantity: number;
391
+ variantId?: string;
392
+ },
393
+ actor?: Actor | null,
394
+ ctx?: TxContext,
395
+ ): Promise<Result<{ finalAmount: number }>>;
396
+ }
397
+ | undefined;
398
+
399
+ if (typeof pricing?.resolve !== "function") {
400
+ return Err(
401
+ new CommerceValidationError(
402
+ `Cannot resolve a unit price for ${input.entityId}: no pricing service is configured.`,
403
+ ),
404
+ );
405
+ }
406
+
407
+ const resolved = await pricing.resolve(input, actor, ctx);
408
+ if (!resolved.ok) {
409
+ return Err(
410
+ new CommerceValidationError(
411
+ `Cannot resolve a unit price for ${input.entityId} (${input.currency}). Configure a price for it, or supply unitPriceSnapshot from a cart.beforeAddItem hook.`,
412
+ ),
413
+ );
414
+ }
415
+ return Ok(resolved.value.finalAmount);
416
+ }
417
+
342
418
  async removeItem(
343
419
  cartId: string,
344
420
  itemId: string,