@cartbase/storefront 0.20.0 → 0.21.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.
- package/LICENSE +21 -21
- package/package.json +4 -1
- package/src/api/auth.ts +108 -108
- package/src/api/carts.ts +523 -523
- package/src/api/categories.ts +184 -184
- package/src/api/checkout.ts +526 -526
- package/src/api/collections.ts +130 -130
- package/src/api/consent.ts +75 -75
- package/src/api/content.ts +125 -125
- package/src/api/customers.ts +303 -303
- package/src/api/gift-cards.ts +112 -112
- package/src/api/http.ts +181 -180
- package/src/api/index.ts +30 -30
- package/src/api/menus.ts +77 -77
- package/src/api/metaobjects.ts +136 -136
- package/src/api/orders.ts +290 -290
- package/src/api/redirects.ts +37 -37
- package/src/api/regions.ts +200 -200
- package/src/api/search.ts +163 -163
- package/src/api/store.ts +56 -35
- package/src/api/types.ts +91 -91
- package/src/cart-drawer/context.tsx +778 -778
- package/src/cart-drawer/cross-sell-carousel.tsx +211 -211
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -158
- package/src/cart-drawer/gift-wrap.tsx +82 -82
- package/src/cart-drawer/item/index.tsx +162 -162
- package/src/cart-drawer/item/upsell.tsx +110 -110
- package/src/cart-drawer/labels.ts +123 -123
- package/src/cart-drawer/mutation-queue.ts +78 -78
- package/src/cart-drawer/notes.tsx +131 -131
- package/src/cart-drawer/payment-badges.tsx +36 -96
- package/src/cart-drawer/sticky-footer.tsx +73 -73
- package/src/cart-drawer/summary-breakdown.tsx +197 -197
- package/src/checkout/address-error-copy.ts +117 -117
- package/src/checkout/boxnow-locker-selector.tsx +410 -410
- package/src/checkout/compare-addresses.ts +40 -40
- package/src/checkout/discount-section.tsx +218 -218
- package/src/checkout/error-copy-codes.ts +63 -63
- package/src/checkout/geocode.ts +154 -154
- package/src/checkout/gift-card-section.tsx +224 -224
- package/src/checkout/index.ts +72 -72
- package/src/checkout/labels.ts +495 -495
- package/src/checkout/payment-button.tsx +372 -372
- package/src/checkout/payment-error-copy.ts +154 -154
- package/src/checkout/promotion-error-copy.ts +91 -91
- package/src/common/country-flag.tsx +52 -52
- package/src/common/country-select.tsx +11 -11
- package/src/common/icons/cartbase-mark.ts +9 -0
- package/src/common/icons/payment-marks.ts +73 -0
- package/src/common/icons/social-marks.ts +61 -0
- package/src/common/index.ts +35 -20
- package/src/common/market-select.tsx +57 -57
- package/src/common/payment-icons.tsx +54 -0
- package/src/common/powered-by-cartbase.tsx +47 -0
- package/src/common/social-links.tsx +65 -0
- package/src/index.ts +12 -12
- package/src/lib/country-name.ts +59 -59
- package/src/lib/get-product-price.ts +133 -133
- package/src/lib/media-image.tsx +39 -39
- package/src/lib/payment-constants.ts +53 -53
- package/src/lib/platform.ts +13 -13
- package/src/lib/price.tsx +39 -39
- package/src/lib/store-api-error.ts +36 -36
- package/src/lib/variant-caption.ts +32 -32
- package/src/locales/bg.ts +3 -0
- package/src/locales/context.ts +37 -37
- package/src/locales/en.ts +26 -26
- package/src/locales/es.ts +3 -0
- package/src/locales/index.ts +19 -19
- package/src/locales/provider.tsx +59 -59
- package/src/locales/types.ts +77 -77
- package/src/order/index.ts +62 -62
- package/src/order/labels.ts +79 -79
- package/src/order/order-totals.tsx +250 -250
- package/src/primitives/select-field.tsx +93 -93
- package/src/products/image-gallery.tsx +43 -43
- package/src/products/option-select.tsx +67 -67
- package/src/products/product-info.tsx +5 -5
- package/src/products/product-price.tsx +69 -69
- package/src/products/product-promises.tsx +61 -61
- package/src/products/product-specs.tsx +65 -65
- package/src/products/product-tabs.tsx +123 -123
- package/src/products/purchase-options.tsx +130 -130
- package/src/products/sets.ts +110 -110
- package/src/products/variant-matching.ts +71 -71
- package/src/products/variant-url.ts +74 -74
- package/src/reviews-ui/lightbox-state.ts +46 -46
- package/src/reviews-ui/review-header.tsx +166 -166
- package/src/reviews-ui/review-lightbox.tsx +271 -271
- package/src/reviews-ui/review-list.tsx +193 -193
- package/src/reviews-ui/review-widget.tsx +219 -218
- package/src/reviews-ui/widget-options.ts +55 -55
- package/src/store/category-template.tsx +136 -136
- package/src/store/index.ts +40 -40
- package/src/store/labels.ts +10 -0
- package/src/tracking/chatgpt-pixel.tsx +99 -99
- package/src/tracking/consent-init.tsx +62 -62
- package/src/tracking/events.ts +348 -348
- package/src/tracking/ga4.tsx +93 -93
- package/src/tracking/google-ads.ts +84 -84
- package/src/tracking/gtm.tsx +60 -60
- package/src/tracking/inline-script.ts +49 -49
- package/src/tracking/oaiq.ts +206 -206
- package/src/tracking/tiktok-pixel.tsx +91 -91
- package/src/tracking/track-init.tsx +56 -56
- package/src/tracking/track-order-purchase.tsx +122 -122
- package/src/tracking/ttq.ts +180 -180
- package/src/tracking/use-tracking-config.ts +54 -54
- package/theme/index.css +25 -25
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect } from "react"
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
captureClickIdsFromUrl,
|
|
7
|
-
captureUtmsFromUrl,
|
|
8
|
-
getOrCreateAnonId,
|
|
9
|
-
setTrackingDefaults,
|
|
10
|
-
} from "./attribution"
|
|
11
|
-
import { initEngagementTime } from "./use-engagement-time"
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* TrackInit — mounts once in the root layout and does the four things
|
|
15
|
-
* that must happen on the FIRST page of a visit, not at checkout:
|
|
16
|
-
*
|
|
17
|
-
* 1. Capture UTM parameters into the first-touch and last-touch
|
|
18
|
-
* cookies. First-touch is written once and survives a year, so the
|
|
19
|
-
* campaign that acquired the visitor is still attributable when they
|
|
20
|
-
* convert months later.
|
|
21
|
-
* 2. Capture the ad-click identifiers — TikTok's `ttclid`, Google's
|
|
22
|
-
* `gclid` and the two iOS variants `gbraid` / `wbraid`. These arrive
|
|
23
|
-
* ONLY on the landing URL of an ad click. Miss them there and they
|
|
24
|
-
* are gone: the visitor navigates, the parameter disappears, and
|
|
25
|
-
* neither platform can ever tie the eventual order back to the click
|
|
26
|
-
* that paid for it.
|
|
27
|
-
* 3. Create the anonymous visitor id, so events before signup can be
|
|
28
|
-
* joined to the customer afterwards.
|
|
29
|
-
* 4. Start the engagement clock, so time-on-session is measured from
|
|
30
|
-
* the landing page rather than lazily from the first checkout call.
|
|
31
|
-
*
|
|
32
|
-
* All four are idempotent and safe on every mount: a URL with no
|
|
33
|
-
* parameters leaves every existing cookie untouched, so an organic visit
|
|
34
|
-
* never clears the click that acquired the visitor.
|
|
35
|
-
*
|
|
36
|
-
* Cookies only — nothing is sent from here. The server reads them at
|
|
37
|
-
* checkout through `getTrackingAttribution()` and writes them onto the
|
|
38
|
-
* cart, which carries them to the order, which is where the platform's
|
|
39
|
-
* server-side Purchase events read them.
|
|
40
|
-
*
|
|
41
|
-
* `country` is the storefront's default market, used when an event has no
|
|
42
|
-
* address to derive one from (a product view by an anonymous visitor).
|
|
43
|
-
* Passing it lifts country coverage from the fraction of events that
|
|
44
|
-
* carry an address to effectively all of them.
|
|
45
|
-
*/
|
|
46
|
-
export function TrackInit({ country }: { country?: string } = {}) {
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (country) setTrackingDefaults({ country })
|
|
49
|
-
captureUtmsFromUrl()
|
|
50
|
-
captureClickIdsFromUrl()
|
|
51
|
-
getOrCreateAnonId()
|
|
52
|
-
initEngagementTime()
|
|
53
|
-
}, [country])
|
|
54
|
-
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
captureClickIdsFromUrl,
|
|
7
|
+
captureUtmsFromUrl,
|
|
8
|
+
getOrCreateAnonId,
|
|
9
|
+
setTrackingDefaults,
|
|
10
|
+
} from "./attribution"
|
|
11
|
+
import { initEngagementTime } from "./use-engagement-time"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* TrackInit — mounts once in the root layout and does the four things
|
|
15
|
+
* that must happen on the FIRST page of a visit, not at checkout:
|
|
16
|
+
*
|
|
17
|
+
* 1. Capture UTM parameters into the first-touch and last-touch
|
|
18
|
+
* cookies. First-touch is written once and survives a year, so the
|
|
19
|
+
* campaign that acquired the visitor is still attributable when they
|
|
20
|
+
* convert months later.
|
|
21
|
+
* 2. Capture the ad-click identifiers — TikTok's `ttclid`, Google's
|
|
22
|
+
* `gclid` and the two iOS variants `gbraid` / `wbraid`. These arrive
|
|
23
|
+
* ONLY on the landing URL of an ad click. Miss them there and they
|
|
24
|
+
* are gone: the visitor navigates, the parameter disappears, and
|
|
25
|
+
* neither platform can ever tie the eventual order back to the click
|
|
26
|
+
* that paid for it.
|
|
27
|
+
* 3. Create the anonymous visitor id, so events before signup can be
|
|
28
|
+
* joined to the customer afterwards.
|
|
29
|
+
* 4. Start the engagement clock, so time-on-session is measured from
|
|
30
|
+
* the landing page rather than lazily from the first checkout call.
|
|
31
|
+
*
|
|
32
|
+
* All four are idempotent and safe on every mount: a URL with no
|
|
33
|
+
* parameters leaves every existing cookie untouched, so an organic visit
|
|
34
|
+
* never clears the click that acquired the visitor.
|
|
35
|
+
*
|
|
36
|
+
* Cookies only — nothing is sent from here. The server reads them at
|
|
37
|
+
* checkout through `getTrackingAttribution()` and writes them onto the
|
|
38
|
+
* cart, which carries them to the order, which is where the platform's
|
|
39
|
+
* server-side Purchase events read them.
|
|
40
|
+
*
|
|
41
|
+
* `country` is the storefront's default market, used when an event has no
|
|
42
|
+
* address to derive one from (a product view by an anonymous visitor).
|
|
43
|
+
* Passing it lifts country coverage from the fraction of events that
|
|
44
|
+
* carry an address to effectively all of them.
|
|
45
|
+
*/
|
|
46
|
+
export function TrackInit({ country }: { country?: string } = {}) {
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (country) setTrackingDefaults({ country })
|
|
49
|
+
captureUtmsFromUrl()
|
|
50
|
+
captureClickIdsFromUrl()
|
|
51
|
+
getOrCreateAnonId()
|
|
52
|
+
initEngagementTime()
|
|
53
|
+
}, [country])
|
|
54
|
+
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef } from "react"
|
|
4
|
-
|
|
5
|
-
import type { CartLineItem, CompletedOrder } from "../api/carts"
|
|
6
|
-
import { trackOrderPurchase, type TrackedLine } from "./events"
|
|
7
|
-
import type { TrackingConfig } from "./types"
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* TrackOrderPurchase — fires the purchase event to every configured
|
|
11
|
-
* vendor from the order confirmation page.
|
|
12
|
-
*
|
|
13
|
-
* A component rather than four calls in the app, because purchase is the
|
|
14
|
-
* one event nobody may get wrong and it is the one with the most ways to
|
|
15
|
-
* go wrong: the dedup keys must match the server's exactly, the ids must
|
|
16
|
-
* be the CATALOGUE's, and every vendor must fire from the same place.
|
|
17
|
-
* A storefront mounts this and is done.
|
|
18
|
-
*
|
|
19
|
-
* FIRES ONCE PER ORDER, per browser. The guard is real, not defensive
|
|
20
|
-
* tidiness: this page is linked from every order email, so a buyer opens
|
|
21
|
-
* it again days later, and React strict mode double-mounts effects in
|
|
22
|
-
* development. The vendors dedupe too (Meta and TikTok on the shared
|
|
23
|
-
* event id, GA4 and Google Ads on the transaction id) and the platform's
|
|
24
|
-
* server side carries its own idempotency flag, so this is the third of
|
|
25
|
-
* three layers — but it is the only one that stops the request being made
|
|
26
|
-
* at all.
|
|
27
|
-
*
|
|
28
|
-
* `customerType` comes from `order.metadata.customer_type`, which the
|
|
29
|
-
* platform's order.placed forwarder computes ONCE and persists. Reading
|
|
30
|
-
* it from the order rather than deriving it in the browser is what keeps
|
|
31
|
-
* the browser and the server from telling Google and TikTok different
|
|
32
|
-
* things about the same buyer.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
const FIRED_KEY_PREFIX = "_cb_purchase_tracked_"
|
|
36
|
-
|
|
37
|
-
function alreadyFired(key: string): boolean {
|
|
38
|
-
try {
|
|
39
|
-
return sessionStorage.getItem(key) === "1"
|
|
40
|
-
} catch {
|
|
41
|
-
// Private mode / blocked storage: fall back to the in-memory ref
|
|
42
|
-
// only. Worst case is a second event the vendors then dedupe.
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function markFired(key: string): void {
|
|
48
|
-
try {
|
|
49
|
-
sessionStorage.setItem(key, "1")
|
|
50
|
-
} catch {
|
|
51
|
-
// Non-fatal — see above.
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function TrackOrderPurchase({
|
|
56
|
-
order,
|
|
57
|
-
items,
|
|
58
|
-
config,
|
|
59
|
-
}: {
|
|
60
|
-
order: CompletedOrder
|
|
61
|
-
/** The cart lines as completed. Carry product_id — it is the catalogue
|
|
62
|
-
* key Meta and TikTok match on, and the flattened order items may not
|
|
63
|
-
* have it. */
|
|
64
|
-
items: CartLineItem[]
|
|
65
|
-
/** The store's tracking config. Google Ads fires only when it carries
|
|
66
|
-
* both the account id and the purchase label. */
|
|
67
|
-
config?: TrackingConfig
|
|
68
|
-
}) {
|
|
69
|
-
const firedRef = useRef(false)
|
|
70
|
-
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
const displayId = order.display_id
|
|
73
|
-
// No display id means no dedup key, and an event that cannot dedupe
|
|
74
|
-
// against the server's is worse than no event: it double-counts.
|
|
75
|
-
if (displayId === undefined || displayId === null) return
|
|
76
|
-
|
|
77
|
-
const key = `${FIRED_KEY_PREFIX}${displayId}`
|
|
78
|
-
if (firedRef.current || alreadyFired(key)) return
|
|
79
|
-
firedRef.current = true
|
|
80
|
-
markFired(key)
|
|
81
|
-
|
|
82
|
-
const lines: TrackedLine[] = items.map((item) => ({
|
|
83
|
-
productId: item.product_id || item.variant_id || item.id,
|
|
84
|
-
variantId: item.variant_id ?? undefined,
|
|
85
|
-
title: item.product_title || item.title || "",
|
|
86
|
-
quantity: Number(item.quantity) || 1,
|
|
87
|
-
price: Number(item.unit_price) || 0,
|
|
88
|
-
}))
|
|
89
|
-
|
|
90
|
-
const summary = (Array.isArray(order.summary)
|
|
91
|
-
? order.summary[0]
|
|
92
|
-
: order.summary) as Record<string, unknown> | null
|
|
93
|
-
const totals = (summary?.totals ?? summary ?? {}) as Record<string, unknown>
|
|
94
|
-
const num = (v: unknown): number | undefined =>
|
|
95
|
-
typeof v === "number" ? v : undefined
|
|
96
|
-
|
|
97
|
-
const value =
|
|
98
|
-
num(totals.total) ??
|
|
99
|
-
lines.reduce((sum, line) => sum + line.price * line.quantity, 0)
|
|
100
|
-
|
|
101
|
-
const metadata = (order.metadata ?? {}) as Record<string, unknown>
|
|
102
|
-
const customerType =
|
|
103
|
-
metadata.customer_type === "new" || metadata.customer_type === "returning"
|
|
104
|
-
? metadata.customer_type
|
|
105
|
-
: undefined
|
|
106
|
-
|
|
107
|
-
trackOrderPurchase(
|
|
108
|
-
{
|
|
109
|
-
displayId,
|
|
110
|
-
currency: (order.currency_code || "eur").toUpperCase(),
|
|
111
|
-
value,
|
|
112
|
-
tax: num(totals.tax_total),
|
|
113
|
-
shipping: num(totals.shipping_total),
|
|
114
|
-
lines,
|
|
115
|
-
customerType,
|
|
116
|
-
},
|
|
117
|
-
config
|
|
118
|
-
)
|
|
119
|
-
}, [order, items, config])
|
|
120
|
-
|
|
121
|
-
return null
|
|
122
|
-
}
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react"
|
|
4
|
+
|
|
5
|
+
import type { CartLineItem, CompletedOrder } from "../api/carts"
|
|
6
|
+
import { trackOrderPurchase, type TrackedLine } from "./events"
|
|
7
|
+
import type { TrackingConfig } from "./types"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* TrackOrderPurchase — fires the purchase event to every configured
|
|
11
|
+
* vendor from the order confirmation page.
|
|
12
|
+
*
|
|
13
|
+
* A component rather than four calls in the app, because purchase is the
|
|
14
|
+
* one event nobody may get wrong and it is the one with the most ways to
|
|
15
|
+
* go wrong: the dedup keys must match the server's exactly, the ids must
|
|
16
|
+
* be the CATALOGUE's, and every vendor must fire from the same place.
|
|
17
|
+
* A storefront mounts this and is done.
|
|
18
|
+
*
|
|
19
|
+
* FIRES ONCE PER ORDER, per browser. The guard is real, not defensive
|
|
20
|
+
* tidiness: this page is linked from every order email, so a buyer opens
|
|
21
|
+
* it again days later, and React strict mode double-mounts effects in
|
|
22
|
+
* development. The vendors dedupe too (Meta and TikTok on the shared
|
|
23
|
+
* event id, GA4 and Google Ads on the transaction id) and the platform's
|
|
24
|
+
* server side carries its own idempotency flag, so this is the third of
|
|
25
|
+
* three layers — but it is the only one that stops the request being made
|
|
26
|
+
* at all.
|
|
27
|
+
*
|
|
28
|
+
* `customerType` comes from `order.metadata.customer_type`, which the
|
|
29
|
+
* platform's order.placed forwarder computes ONCE and persists. Reading
|
|
30
|
+
* it from the order rather than deriving it in the browser is what keeps
|
|
31
|
+
* the browser and the server from telling Google and TikTok different
|
|
32
|
+
* things about the same buyer.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const FIRED_KEY_PREFIX = "_cb_purchase_tracked_"
|
|
36
|
+
|
|
37
|
+
function alreadyFired(key: string): boolean {
|
|
38
|
+
try {
|
|
39
|
+
return sessionStorage.getItem(key) === "1"
|
|
40
|
+
} catch {
|
|
41
|
+
// Private mode / blocked storage: fall back to the in-memory ref
|
|
42
|
+
// only. Worst case is a second event the vendors then dedupe.
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function markFired(key: string): void {
|
|
48
|
+
try {
|
|
49
|
+
sessionStorage.setItem(key, "1")
|
|
50
|
+
} catch {
|
|
51
|
+
// Non-fatal — see above.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function TrackOrderPurchase({
|
|
56
|
+
order,
|
|
57
|
+
items,
|
|
58
|
+
config,
|
|
59
|
+
}: {
|
|
60
|
+
order: CompletedOrder
|
|
61
|
+
/** The cart lines as completed. Carry product_id — it is the catalogue
|
|
62
|
+
* key Meta and TikTok match on, and the flattened order items may not
|
|
63
|
+
* have it. */
|
|
64
|
+
items: CartLineItem[]
|
|
65
|
+
/** The store's tracking config. Google Ads fires only when it carries
|
|
66
|
+
* both the account id and the purchase label. */
|
|
67
|
+
config?: TrackingConfig
|
|
68
|
+
}) {
|
|
69
|
+
const firedRef = useRef(false)
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const displayId = order.display_id
|
|
73
|
+
// No display id means no dedup key, and an event that cannot dedupe
|
|
74
|
+
// against the server's is worse than no event: it double-counts.
|
|
75
|
+
if (displayId === undefined || displayId === null) return
|
|
76
|
+
|
|
77
|
+
const key = `${FIRED_KEY_PREFIX}${displayId}`
|
|
78
|
+
if (firedRef.current || alreadyFired(key)) return
|
|
79
|
+
firedRef.current = true
|
|
80
|
+
markFired(key)
|
|
81
|
+
|
|
82
|
+
const lines: TrackedLine[] = items.map((item) => ({
|
|
83
|
+
productId: item.product_id || item.variant_id || item.id,
|
|
84
|
+
variantId: item.variant_id ?? undefined,
|
|
85
|
+
title: item.product_title || item.title || "",
|
|
86
|
+
quantity: Number(item.quantity) || 1,
|
|
87
|
+
price: Number(item.unit_price) || 0,
|
|
88
|
+
}))
|
|
89
|
+
|
|
90
|
+
const summary = (Array.isArray(order.summary)
|
|
91
|
+
? order.summary[0]
|
|
92
|
+
: order.summary) as Record<string, unknown> | null
|
|
93
|
+
const totals = (summary?.totals ?? summary ?? {}) as Record<string, unknown>
|
|
94
|
+
const num = (v: unknown): number | undefined =>
|
|
95
|
+
typeof v === "number" ? v : undefined
|
|
96
|
+
|
|
97
|
+
const value =
|
|
98
|
+
num(totals.total) ??
|
|
99
|
+
lines.reduce((sum, line) => sum + line.price * line.quantity, 0)
|
|
100
|
+
|
|
101
|
+
const metadata = (order.metadata ?? {}) as Record<string, unknown>
|
|
102
|
+
const customerType =
|
|
103
|
+
metadata.customer_type === "new" || metadata.customer_type === "returning"
|
|
104
|
+
? metadata.customer_type
|
|
105
|
+
: undefined
|
|
106
|
+
|
|
107
|
+
trackOrderPurchase(
|
|
108
|
+
{
|
|
109
|
+
displayId,
|
|
110
|
+
currency: (order.currency_code || "eur").toUpperCase(),
|
|
111
|
+
value,
|
|
112
|
+
tax: num(totals.tax_total),
|
|
113
|
+
shipping: num(totals.shipping_total),
|
|
114
|
+
lines,
|
|
115
|
+
customerType,
|
|
116
|
+
},
|
|
117
|
+
config
|
|
118
|
+
)
|
|
119
|
+
}, [order, items, config])
|
|
120
|
+
|
|
121
|
+
return null
|
|
122
|
+
}
|