@decocms/apps-vtex 7.43.0 → 7.43.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.
- package/package.json +5 -5
- package/src/utils/buildOfferShelf.test.ts +59 -0
- package/src/utils/transform.ts +22 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-vtex",
|
|
3
|
-
"version": "7.43.
|
|
3
|
+
"version": "7.43.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco commerce app: VTEX integration",
|
|
6
6
|
"repository": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"lint:unused": "knip"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@decocms/blocks": "7.43.
|
|
55
|
-
"@decocms/apps-commerce": "7.43.
|
|
56
|
-
"@decocms/apps-website": "7.43.
|
|
57
|
-
"@decocms/tanstack": "7.43.
|
|
54
|
+
"@decocms/blocks": "7.43.1",
|
|
55
|
+
"@decocms/apps-commerce": "7.43.1",
|
|
56
|
+
"@decocms/apps-website": "7.43.1",
|
|
57
|
+
"@decocms/tanstack": "7.43.1"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@tanstack/react-query": ">=5.0.0",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { AggregateOffer, Offer, UnitPriceSpecification } from "@decocms/apps-commerce/types";
|
|
3
|
+
import { useOffer } from "@decocms/apps-commerce/sdk/useOffer";
|
|
4
|
+
import { buildOfferShelf } from "./transform";
|
|
5
|
+
|
|
6
|
+
const installment = (
|
|
7
|
+
over: Partial<UnitPriceSpecification>,
|
|
8
|
+
): UnitPriceSpecification =>
|
|
9
|
+
({
|
|
10
|
+
"@type": "UnitPriceSpecification",
|
|
11
|
+
priceType: "https://schema.org/SalePrice",
|
|
12
|
+
priceComponentType: "https://schema.org/Installment",
|
|
13
|
+
...over,
|
|
14
|
+
}) as UnitPriceSpecification;
|
|
15
|
+
|
|
16
|
+
const offerWith = (specs: UnitPriceSpecification[]): Offer =>
|
|
17
|
+
({
|
|
18
|
+
"@type": "Offer",
|
|
19
|
+
price: 100,
|
|
20
|
+
priceSpecification: [
|
|
21
|
+
{ "@type": "UnitPriceSpecification", priceType: "https://schema.org/ListPrice", price: 120 },
|
|
22
|
+
{ "@type": "UnitPriceSpecification", priceType: "https://schema.org/SalePrice", price: 100 },
|
|
23
|
+
...specs,
|
|
24
|
+
],
|
|
25
|
+
}) as Offer;
|
|
26
|
+
|
|
27
|
+
const pickInstallment = (offer: Offer) =>
|
|
28
|
+
useOffer({ "@type": "AggregateOffer", offers: [offer] } as AggregateOffer).installment;
|
|
29
|
+
|
|
30
|
+
describe("buildOfferShelf installment selection", () => {
|
|
31
|
+
it("keeps the same installment useOffer picks on PLP/PDP (shelf == detail)", () => {
|
|
32
|
+
// 12x whose stated total (100) is the lowest and duration the highest, but VTEX
|
|
33
|
+
// rounding makes 12 * 8.34 = 100.08 — the old 1-cent isNoInterest check rejected
|
|
34
|
+
// it and the shelf fell back to 10x, diverging from the detail page's 12x.
|
|
35
|
+
const full = offerWith([
|
|
36
|
+
installment({ name: "PIX", billingDuration: 1, billingIncrement: 100, price: 100 }),
|
|
37
|
+
installment({ name: "Visa", billingDuration: 10, billingIncrement: 10, price: 100 }),
|
|
38
|
+
installment({ name: "Visa", billingDuration: 12, billingIncrement: 8.34, price: 100 }),
|
|
39
|
+
installment({ name: "Visa", billingDuration: 6, billingIncrement: 17.67, price: 106 }),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const lean = buildOfferShelf(full);
|
|
43
|
+
|
|
44
|
+
expect(pickInstallment(lean)).toEqual(pickInstallment(full));
|
|
45
|
+
expect(pickInstallment(lean)?.billingDuration).toBe(12);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("keeps the lowest-total plan even when every installment has interest", () => {
|
|
49
|
+
const full = offerWith([
|
|
50
|
+
installment({ name: "Visa", billingDuration: 3, billingIncrement: 34, price: 102 }),
|
|
51
|
+
installment({ name: "Visa", billingDuration: 6, billingIncrement: 18, price: 108 }),
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const lean = buildOfferShelf(full);
|
|
55
|
+
|
|
56
|
+
expect(pickInstallment(lean)).toEqual(pickInstallment(full));
|
|
57
|
+
expect(pickInstallment(lean)?.price).toBe(102);
|
|
58
|
+
});
|
|
59
|
+
});
|
package/src/utils/transform.ts
CHANGED
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
SiteNavigationElement,
|
|
21
21
|
UnitPriceSpecification,
|
|
22
22
|
} from "@decocms/apps-commerce/types";
|
|
23
|
+
import { bestInstallment } from "@decocms/apps-commerce/sdk/useOffer";
|
|
23
24
|
import { DEFAULT_IMAGE } from "@decocms/apps-commerce/utils/constants";
|
|
24
25
|
import { formatRange } from "@decocms/apps-commerce/utils/filters";
|
|
25
26
|
import { pick } from "./pickAndOmit";
|
|
@@ -509,61 +510,48 @@ export const toProduct = <P extends LegacyProductVTEX | ProductVTEX>(
|
|
|
509
510
|
};
|
|
510
511
|
};
|
|
511
512
|
|
|
512
|
-
/**
|
|
513
|
-
* Determines if an installment has no interest by checking if
|
|
514
|
-
* billingDuration * billingIncrement ≈ total price (within 1 cent tolerance).
|
|
515
|
-
*/
|
|
516
|
-
const isNoInterest = (spec: UnitPriceSpecification): boolean => {
|
|
517
|
-
if (spec.billingDuration == null || spec.billingIncrement == null || spec.price == null) {
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
return Math.abs(spec.billingDuration * spec.billingIncrement - spec.price) < 0.01;
|
|
521
|
-
};
|
|
522
|
-
|
|
523
513
|
/**
|
|
524
514
|
* Build a lean offer for shelf display. Keeps only:
|
|
525
515
|
* - ListPrice, SalePrice, SRP price types
|
|
526
516
|
* - PIX installment (name?.toUpperCase() === "PIX")
|
|
527
|
-
* - Best
|
|
517
|
+
* - Best installment, chosen with the SAME `bestInstallment` heuristic `useOffer`
|
|
518
|
+
* applies on PLP/PDP (lowest total price, tie-broken by highest billingDuration).
|
|
519
|
+
* Reusing it means shelf cards and detail pages always show the same installment.
|
|
528
520
|
* Drops: inventoryLevel, giftSkuIds, priceValidUntil
|
|
529
521
|
*/
|
|
530
|
-
const buildOfferShelf = (offer: Offer): Offer => {
|
|
522
|
+
export const buildOfferShelf = (offer: Offer): Offer => {
|
|
531
523
|
const leanSpecs: UnitPriceSpecification[] = [];
|
|
532
|
-
|
|
533
|
-
let bestNoInterest: UnitPriceSpecification | null = null;
|
|
524
|
+
const installmentSpecs: UnitPriceSpecification[] = [];
|
|
534
525
|
|
|
535
526
|
for (const spec of offer.priceSpecification ?? []) {
|
|
536
|
-
// Keep base price types
|
|
527
|
+
// Keep base (non-installment) price types
|
|
537
528
|
if (
|
|
538
|
-
spec.priceType === SCHEMA_LIST_PRICE ||
|
|
539
|
-
|
|
540
|
-
|
|
529
|
+
(spec.priceType === SCHEMA_LIST_PRICE ||
|
|
530
|
+
spec.priceType === SCHEMA_SALE_PRICE ||
|
|
531
|
+
spec.priceType === SCHEMA_SRP) &&
|
|
532
|
+
spec.priceComponentType !== SCHEMA_INSTALLMENT
|
|
541
533
|
) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
534
|
+
leanSpecs.push(spec);
|
|
535
|
+
continue;
|
|
546
536
|
}
|
|
547
537
|
|
|
548
|
-
// Keep PIX installment
|
|
538
|
+
// Keep PIX installment (also feeds the bestInstallment reducer below via leanSpecs)
|
|
549
539
|
if (spec.priceComponentType === SCHEMA_INSTALLMENT && spec.name?.toUpperCase() === "PIX") {
|
|
550
540
|
leanSpecs.push(spec);
|
|
551
541
|
continue;
|
|
552
542
|
}
|
|
553
543
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
spec.priceComponentType === SCHEMA_INSTALLMENT &&
|
|
557
|
-
isNoInterest(spec) &&
|
|
558
|
-
(bestNoInterest == null ||
|
|
559
|
-
(spec.billingDuration ?? 0) > (bestNoInterest.billingDuration ?? 0))
|
|
560
|
-
) {
|
|
561
|
-
bestNoInterest = spec;
|
|
544
|
+
if (spec.priceComponentType === SCHEMA_INSTALLMENT) {
|
|
545
|
+
installmentSpecs.push(spec);
|
|
562
546
|
}
|
|
563
547
|
}
|
|
564
548
|
|
|
565
|
-
|
|
566
|
-
|
|
549
|
+
// Keep the single installment useOffer would pick from the full list. PIX is kept
|
|
550
|
+
// separately above, so useOffer on the lean offer still reduces over {PIX, best}
|
|
551
|
+
// and matches the detail page's reduce over the full set.
|
|
552
|
+
const best = installmentSpecs.reduce(bestInstallment, null as UnitPriceSpecification | null);
|
|
553
|
+
if (best) {
|
|
554
|
+
leanSpecs.push(best);
|
|
567
555
|
}
|
|
568
556
|
|
|
569
557
|
return {
|