@replohq/sdk 2.18.2 → 2.19.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/_vendor/replo-utils/lib/crypto.d.ts +5 -0
- package/_vendor/schemas/generated/consent.d.ts +13 -1
- package/analytics/analytics-provider.js +3 -1
- package/analytics/get-analytics-sinks.js +7 -4
- package/analytics/replo-pixel-script.d.ts +5 -5
- package/analytics/replo-pixel-script.js +23 -22
- package/cart/cart-actions.js +42 -13
- package/cart/checkout-actions.js +1 -1
- package/cart/checkout-session-actions.js +2 -2
- package/cart/gateways/cart-gateway.d.ts +29 -8
- package/cart/gateways/cookie-cart-gateway.d.ts +4 -2
- package/cart/gateways/cookie-cart-gateway.js +4 -4
- package/cart/gateways/resolver-cart-gateway.js +28 -16
- package/cart/gateways/stripe-cart-gateway.js +1 -1
- package/cart/start-stripe-checkout.js +1 -1
- package/consent/consent-platform.d.ts +20 -5
- package/consent/consent-platform.js +94 -11
- package/consent/consent-store.d.ts +7 -2
- package/consent/consent-store.js +16 -4
- package/consent/inject-script-descriptors.d.ts +8 -1
- package/consent/inject-script-descriptors.js +23 -0
- package/consent/replo-scripts.d.ts +6 -2
- package/consent/replo-scripts.js +35 -27
- package/consent/script-registration-store.d.ts +2 -0
- package/consent/script-snippets.d.ts +1 -1
- package/consent/types.d.ts +18 -4
- package/consent/usercentrics-consent.d.ts +15 -0
- package/consent/usercentrics-consent.js +96 -0
- package/consent/window-api.js +1 -1
- package/gateways/canopy-api.d.ts +21 -15
- package/gateways/canopy-api.js +105 -54
- package/lib/buildMetadata.js +3 -3
- package/lib/buyer-ip.d.ts +6 -0
- package/lib/buyer-ip.js +7 -0
- package/loaders/invoke-loader-action.js +6 -1
- package/loaders/invoke-loader-server.js +5 -1
- package/loaders/invoke-loader.js +5 -1
- package/loaders/loader-output.d.ts +2 -1
- package/loaders/loader-output.js +7 -3
- package/package.json +4 -4
|
@@ -22,4 +22,9 @@ export declare function getHmacSha256Hex({ rawBody, signingKey, }: {
|
|
|
22
22
|
rawBody: string;
|
|
23
23
|
signingKey: string;
|
|
24
24
|
}): Promise<string>;
|
|
25
|
+
export declare function isValidHmacSha256Hex({ rawBody, signingKey, signature, }: {
|
|
26
|
+
rawBody: string;
|
|
27
|
+
signingKey: string;
|
|
28
|
+
signature: string;
|
|
29
|
+
}): Promise<boolean>;
|
|
25
30
|
export declare function deterministicUUID(value: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Mixpanel" | "HubSpot" | "Attentive" | "Alia" | "Cookiebot" | "snippet" | "custom" | "consentPlatform";
|
|
1
|
+
export type ReploScriptType = "GA4" | "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "X" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "Mixpanel" | "HubSpot" | "Attentive" | "Alia" | "Cookiebot" | "Usercentrics" | "snippet" | "custom" | "consentPlatform";
|
|
2
2
|
export type ConsentCategory = "necessary" | "analytics" | "marketing" | "preferences" | "sale_of_data";
|
|
3
3
|
export type ConsentMode = "off" | "simple" | "per-category";
|
|
4
4
|
export type ScriptTagDescriptor = {
|
|
@@ -30,37 +30,49 @@ export type ReploScriptEntry = {
|
|
|
30
30
|
type: "GoogleTagManager" | "Meta" | "TikTok" | "Pinterest" | "Reddit" | "Snapchat" | "Hotjar" | "MicrosoftClarity" | "Contentsquare" | "Segment" | "Northbeam" | "Gorgias" | "Converge" | "HubSpot" | "Attentive" | "Alia";
|
|
31
31
|
identifier: string;
|
|
32
32
|
requiredConsent?: ConsentCategory[];
|
|
33
|
+
usercentricsService?: string;
|
|
33
34
|
} | {
|
|
34
35
|
type: "GA4";
|
|
35
36
|
identifier: string;
|
|
36
37
|
requiredConsent?: ConsentCategory[];
|
|
37
38
|
runtimeOwner?: ScriptRuntimeOwner;
|
|
39
|
+
usercentricsService?: string;
|
|
38
40
|
} | {
|
|
39
41
|
type: "X";
|
|
40
42
|
identifier: string;
|
|
41
43
|
events?: Partial<XPixelEvents>;
|
|
42
44
|
requiredConsent?: ConsentCategory[];
|
|
45
|
+
usercentricsService?: string;
|
|
43
46
|
} | {
|
|
44
47
|
type: "Mixpanel";
|
|
45
48
|
identifier: string;
|
|
46
49
|
region?: MixpanelRegion;
|
|
47
50
|
requiredConsent?: ConsentCategory[];
|
|
51
|
+
usercentricsService?: string;
|
|
48
52
|
} | {
|
|
49
53
|
type: "Cookiebot";
|
|
50
54
|
identifier: string;
|
|
51
55
|
requiredConsent?: ConsentCategory[];
|
|
56
|
+
} | {
|
|
57
|
+
type: "Usercentrics";
|
|
58
|
+
identifier: string;
|
|
59
|
+
loaderVersion?: "v2" | "v3";
|
|
60
|
+
runtimeOwner?: ScriptRuntimeOwner;
|
|
61
|
+
reploUsercentricsService?: string;
|
|
52
62
|
} | {
|
|
53
63
|
type: "snippet";
|
|
54
64
|
id: string;
|
|
55
65
|
body: string;
|
|
56
66
|
module?: boolean;
|
|
57
67
|
requiredConsent: ConsentCategory[];
|
|
68
|
+
usercentricsService?: string;
|
|
58
69
|
} | {
|
|
59
70
|
type: "custom";
|
|
60
71
|
id: string;
|
|
61
72
|
requiredConsent: ConsentCategory[];
|
|
62
73
|
src?: string;
|
|
63
74
|
body?: string;
|
|
75
|
+
usercentricsService?: string;
|
|
64
76
|
} | {
|
|
65
77
|
type: "consentPlatform";
|
|
66
78
|
id: string;
|
|
@@ -37,7 +37,8 @@ function AnalyticsProvider({
|
|
|
37
37
|
cmp: consent.cmp,
|
|
38
38
|
mode: consent.mode,
|
|
39
39
|
categories: consent.categories,
|
|
40
|
-
decidedAt: consent.decidedAt
|
|
40
|
+
decidedAt: consent.decidedAt,
|
|
41
|
+
grantedUsercentricsServices: consent.grantedUsercentricsServices
|
|
41
42
|
},
|
|
42
43
|
registeredScripts,
|
|
43
44
|
checkoutProvider
|
|
@@ -49,6 +50,7 @@ function AnalyticsProvider({
|
|
|
49
50
|
consent.mode,
|
|
50
51
|
consent.categories,
|
|
51
52
|
consent.decidedAt,
|
|
53
|
+
consent.grantedUsercentricsServices,
|
|
52
54
|
registeredScripts,
|
|
53
55
|
checkoutProvider,
|
|
54
56
|
additionalSinks
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { findDelegatedPlatform } from "../consent/consent-platform";
|
|
1
2
|
import { isConsentAllowed } from "../consent/consent-store";
|
|
2
3
|
import { snippetScriptId } from "../consent/script-registration-store";
|
|
3
4
|
import { ConvergeSink } from "./sinks/converge-sink";
|
|
@@ -51,11 +52,12 @@ function getAnalyticsSinks({
|
|
|
51
52
|
checkoutProvider
|
|
52
53
|
}) {
|
|
53
54
|
const sinks = [new WindowSink()];
|
|
54
|
-
const
|
|
55
|
+
const isReploPixelAllowed = isConsentAllowed({
|
|
55
56
|
state: consent,
|
|
56
|
-
requiredConsent: ["analytics"]
|
|
57
|
+
requiredConsent: ["analytics"],
|
|
58
|
+
usercentricsService: findDelegatedPlatform()?.reploUsercentricsService ?? null
|
|
57
59
|
});
|
|
58
|
-
if (
|
|
60
|
+
if (isReploPixelAllowed) {
|
|
59
61
|
sinks.push(new ReploSink());
|
|
60
62
|
}
|
|
61
63
|
const shopifyOwnsCheckout = checkoutProvider === "shopify";
|
|
@@ -68,7 +70,8 @@ function getAnalyticsSinks({
|
|
|
68
70
|
}
|
|
69
71
|
if (isConsentAllowed({
|
|
70
72
|
state: consent,
|
|
71
|
-
requiredConsent: script.requiredConsent
|
|
73
|
+
requiredConsent: script.requiredConsent,
|
|
74
|
+
usercentricsService: script.usercentricsService
|
|
72
75
|
})) {
|
|
73
76
|
const sink = factory();
|
|
74
77
|
const gated = shopifyOwnsCheckout && !SHOPIFY_FUNNEL_GATE_EXEMPT_PROVIDERS.has(script.type);
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* events into, so gating it on `analytics` consent keeps the first-party sink
|
|
5
5
|
* consent-correct without the pixel loading unconditionally in `<head>` the way
|
|
6
6
|
* the legacy scaffold does. `analytics` consent here matches the
|
|
7
|
-
* `
|
|
7
|
+
* `isReploPixelAllowed` gate that controls whether `ReploSink` is created at all.
|
|
8
8
|
*
|
|
9
|
-
* Under a consent platform the tag is
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Under a consent platform the tag is injected regardless, with its blocking
|
|
10
|
+
* attributes read in the effect because this renders before `ReploScripts`
|
|
11
|
+
* publishes the platform.
|
|
12
12
|
*/
|
|
13
13
|
export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, siteVersion, }: {
|
|
14
14
|
pixelUrl: string;
|
|
@@ -16,4 +16,4 @@ export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, siteVers
|
|
|
16
16
|
projectId: string;
|
|
17
17
|
/** Publish id of the running site; absent on unpublished or older deploys. */
|
|
18
18
|
siteVersion?: string;
|
|
19
|
-
}):
|
|
19
|
+
}): null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { findDelegatedPlatform } from "../consent/consent-platform";
|
|
4
|
+
import { injectGatedScript } from "../consent/inject-script-descriptors";
|
|
4
5
|
import { useIsBlockedByNativeConsent } from "../consent/use-is-blocked-by-native-consent";
|
|
5
6
|
const PIXEL_SCRIPT_ID = "replo:first-party-pixel";
|
|
6
7
|
const PIXEL_REQUIRED_CONSENT = ["analytics"];
|
|
@@ -13,28 +14,28 @@ function ReploPixelScript({
|
|
|
13
14
|
const isBlockedByNativeConsent = useIsBlockedByNativeConsent(
|
|
14
15
|
PIXEL_REQUIRED_CONSENT
|
|
15
16
|
);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const descriptors = [
|
|
20
|
-
{
|
|
21
|
-
kind: "external",
|
|
22
|
-
src: `${pixelUrl}/latest.js`,
|
|
23
|
-
attributes: {
|
|
24
|
-
"data-api-url": apiUrl,
|
|
25
|
-
"data-project-id": projectId,
|
|
26
|
-
...siteVersion ? { "data-site-version": siteVersion } : {}
|
|
27
|
-
}
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (isBlockedByNativeConsent) {
|
|
19
|
+
return;
|
|
28
20
|
}
|
|
29
|
-
|
|
30
|
-
return /* @__PURE__ */ jsx(
|
|
31
|
-
InjectedScript,
|
|
32
|
-
{
|
|
21
|
+
injectGatedScript({
|
|
33
22
|
baseId: PIXEL_SCRIPT_ID,
|
|
34
|
-
descriptors
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
descriptors: [
|
|
24
|
+
{
|
|
25
|
+
kind: "external",
|
|
26
|
+
src: `${pixelUrl}/latest.js`,
|
|
27
|
+
attributes: {
|
|
28
|
+
"data-api-url": apiUrl,
|
|
29
|
+
"data-project-id": projectId,
|
|
30
|
+
...siteVersion ? { "data-site-version": siteVersion } : {}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
requiredConsent: PIXEL_REQUIRED_CONSENT,
|
|
35
|
+
usercentricsService: findDelegatedPlatform()?.reploUsercentricsService ?? null
|
|
36
|
+
});
|
|
37
|
+
}, [isBlockedByNativeConsent, pixelUrl, apiUrl, projectId, siteVersion]);
|
|
38
|
+
return null;
|
|
38
39
|
}
|
|
39
40
|
export {
|
|
40
41
|
ReploPixelScript
|
package/cart/cart-actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { cookies } from "next/headers";
|
|
3
|
+
import { readBuyerIp } from "../lib/buyer-ip";
|
|
3
4
|
import { getCheckoutProvider } from "../lib/integration-utils";
|
|
4
5
|
import { REPLO_ATTRIBUTION_PROPERTY } from "./cart-types";
|
|
5
6
|
import {
|
|
@@ -23,7 +24,8 @@ async function reconcileExistingCart(cart, country) {
|
|
|
23
24
|
if (country && reconciledCart.buyerIdentity?.country !== country) {
|
|
24
25
|
return await cartGateway.updateCartBuyerIdentity({
|
|
25
26
|
cartId: reconciledCart.id,
|
|
26
|
-
buyerIdentity: { country }
|
|
27
|
+
buyerIdentity: { country },
|
|
28
|
+
buyerIp: await readBuyerIp()
|
|
27
29
|
});
|
|
28
30
|
}
|
|
29
31
|
return reconciledCart;
|
|
@@ -51,9 +53,10 @@ async function getCartAction() {
|
|
|
51
53
|
const cartGateway = await getCartGateway();
|
|
52
54
|
const sharedToken = checkoutProvider === "shopify" ? cookieStore.get(SHARED_SHOPIFY_CART_COOKIE_NAME)?.value : void 0;
|
|
53
55
|
if (sharedToken) {
|
|
54
|
-
const sharedCart = await cartGateway.getCart(
|
|
55
|
-
`${SHOPIFY_CART_ID_PREFIX}${sharedToken}
|
|
56
|
-
|
|
56
|
+
const sharedCart = await cartGateway.getCart({
|
|
57
|
+
cartId: `${SHOPIFY_CART_ID_PREFIX}${sharedToken}`,
|
|
58
|
+
buyerIp: await readBuyerIp()
|
|
59
|
+
});
|
|
57
60
|
if (sharedCart) {
|
|
58
61
|
return sharedCart;
|
|
59
62
|
}
|
|
@@ -62,7 +65,10 @@ async function getCartAction() {
|
|
|
62
65
|
if (!existingCartId) {
|
|
63
66
|
return null;
|
|
64
67
|
}
|
|
65
|
-
const cart = await cartGateway.getCart(
|
|
68
|
+
const cart = await cartGateway.getCart({
|
|
69
|
+
cartId: existingCartId,
|
|
70
|
+
buyerIp: await readBuyerIp()
|
|
71
|
+
});
|
|
66
72
|
if (!cart) {
|
|
67
73
|
try {
|
|
68
74
|
cookieStore.set(SHOPIFY_CART_COOKIE_NAME, "", {
|
|
@@ -92,7 +98,8 @@ async function createCartAction(options) {
|
|
|
92
98
|
lines,
|
|
93
99
|
discountCodes,
|
|
94
100
|
attributes,
|
|
95
|
-
buyerIdentity: country ? { country } : void 0
|
|
101
|
+
buyerIdentity: country ? { country } : void 0,
|
|
102
|
+
buyerIp: await readBuyerIp()
|
|
96
103
|
});
|
|
97
104
|
if (cart && !skipStoringCart) {
|
|
98
105
|
try {
|
|
@@ -117,7 +124,8 @@ async function createCartAction(options) {
|
|
|
117
124
|
}
|
|
118
125
|
async function addToCartAction(lines, cartId) {
|
|
119
126
|
const cartGateway = await getCartGateway();
|
|
120
|
-
const
|
|
127
|
+
const buyerIp = await readBuyerIp();
|
|
128
|
+
const cart = await cartGateway.addToCart({ lines, cartId, buyerIp });
|
|
121
129
|
if (!cart || await getCheckoutProvider() !== "shopify" || cart.attributes?.some(
|
|
122
130
|
(attribute) => attribute.key === REPLO_ATTRIBUTION_PROPERTY.key
|
|
123
131
|
)) {
|
|
@@ -125,29 +133,50 @@ async function addToCartAction(lines, cartId) {
|
|
|
125
133
|
}
|
|
126
134
|
const attributedCart = await cartGateway.replaceCartAttributes({
|
|
127
135
|
cartId: cart.id,
|
|
128
|
-
attributes: [...cart.attributes ?? [], REPLO_ATTRIBUTION_PROPERTY]
|
|
136
|
+
attributes: [...cart.attributes ?? [], REPLO_ATTRIBUTION_PROPERTY],
|
|
137
|
+
buyerIp
|
|
129
138
|
});
|
|
130
139
|
return attributedCart ?? cart;
|
|
131
140
|
}
|
|
132
141
|
async function updateCartLineItemAction(line, cartId) {
|
|
133
142
|
const cartGateway = await getCartGateway();
|
|
134
|
-
return await cartGateway.updateCartLineItem({
|
|
143
|
+
return await cartGateway.updateCartLineItem({
|
|
144
|
+
line,
|
|
145
|
+
cartId,
|
|
146
|
+
buyerIp: await readBuyerIp()
|
|
147
|
+
});
|
|
135
148
|
}
|
|
136
149
|
async function removeCartLineItemsAction(lineIds, cartId) {
|
|
137
150
|
const cartGateway = await getCartGateway();
|
|
138
|
-
return await cartGateway.removeCartLineItems({
|
|
151
|
+
return await cartGateway.removeCartLineItems({
|
|
152
|
+
lineIds,
|
|
153
|
+
cartId,
|
|
154
|
+
buyerIp: await readBuyerIp()
|
|
155
|
+
});
|
|
139
156
|
}
|
|
140
157
|
async function updateCartDiscountCodesAction(discountCodes, cartId) {
|
|
141
158
|
const cartGateway = await getCartGateway();
|
|
142
|
-
return await cartGateway.updateCartDiscountCodes({
|
|
159
|
+
return await cartGateway.updateCartDiscountCodes({
|
|
160
|
+
discountCodes,
|
|
161
|
+
cartId,
|
|
162
|
+
buyerIp: await readBuyerIp()
|
|
163
|
+
});
|
|
143
164
|
}
|
|
144
165
|
async function updateCartNoteAction(note, cartId) {
|
|
145
166
|
const cartGateway = await getCartGateway();
|
|
146
|
-
return await cartGateway.updateCartNote({
|
|
167
|
+
return await cartGateway.updateCartNote({
|
|
168
|
+
note,
|
|
169
|
+
cartId,
|
|
170
|
+
buyerIp: await readBuyerIp()
|
|
171
|
+
});
|
|
147
172
|
}
|
|
148
173
|
async function replaceCartAttributesAction(attributes, cartId) {
|
|
149
174
|
const cartGateway = await getCartGateway();
|
|
150
|
-
return await cartGateway.replaceCartAttributes({
|
|
175
|
+
return await cartGateway.replaceCartAttributes({
|
|
176
|
+
attributes,
|
|
177
|
+
cartId,
|
|
178
|
+
buyerIp: await readBuyerIp()
|
|
179
|
+
});
|
|
151
180
|
}
|
|
152
181
|
export {
|
|
153
182
|
addToCartAction,
|
package/cart/checkout-actions.js
CHANGED
|
@@ -19,7 +19,7 @@ async function getCrossSellsAction(input) {
|
|
|
19
19
|
if (await getCheckoutProvider() !== "stripe") {
|
|
20
20
|
return [];
|
|
21
21
|
}
|
|
22
|
-
return await
|
|
22
|
+
return await getCanopyApiGateway().findCrossSells(input);
|
|
23
23
|
}
|
|
24
24
|
export {
|
|
25
25
|
createCheckoutSessionAction,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { getCanopyApiGateway } from "../gateways/canopy-api";
|
|
3
3
|
async function updateCheckoutShippingAction(input) {
|
|
4
|
-
return await
|
|
4
|
+
return await getCanopyApiGateway().updateCheckoutShipping(input);
|
|
5
5
|
}
|
|
6
6
|
async function addCrossSellAction(input) {
|
|
7
|
-
return await
|
|
7
|
+
return await getCanopyApiGateway().addCrossSell(input);
|
|
8
8
|
}
|
|
9
9
|
export {
|
|
10
10
|
addCrossSellAction,
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, ReplaceCartAttributesPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload, UpdateCartNotePayload } from "../cart-types";
|
|
2
|
+
/** `buyerIp` is the shopper's IP; Shopify carts forward it, others ignore it. */
|
|
2
3
|
export interface CartGateway {
|
|
3
|
-
getCart(
|
|
4
|
+
getCart(request: {
|
|
5
|
+
cartId: string;
|
|
6
|
+
} & {
|
|
7
|
+
buyerIp: string | null;
|
|
8
|
+
}): Promise<Cart | null>;
|
|
4
9
|
/**
|
|
5
10
|
* Bring a stored cart back in line with its source of truth. Cookie carts
|
|
6
11
|
* reprice against the catalog and drop unavailable lines (persisting the
|
|
@@ -8,7 +13,7 @@ export interface CartGateway {
|
|
|
8
13
|
* cart unchanged.
|
|
9
14
|
*/
|
|
10
15
|
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
11
|
-
createCart(options
|
|
16
|
+
createCart(options: {
|
|
12
17
|
lines?: CartLinePayload[];
|
|
13
18
|
discountCodes?: string[];
|
|
14
19
|
attributes?: {
|
|
@@ -16,16 +21,32 @@ export interface CartGateway {
|
|
|
16
21
|
value: string;
|
|
17
22
|
}[];
|
|
18
23
|
buyerIdentity?: CartBuyerIdentity;
|
|
24
|
+
} & {
|
|
25
|
+
buyerIp: string | null;
|
|
26
|
+
}): Promise<Cart | null>;
|
|
27
|
+
addToCart(cartPayload: AddToCartPayload & {
|
|
28
|
+
buyerIp: string | null;
|
|
19
29
|
}): Promise<Cart | null>;
|
|
20
|
-
addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
|
|
21
30
|
updateCartBuyerIdentity(payload: {
|
|
22
31
|
cartId: string;
|
|
23
32
|
buyerIdentity: CartBuyerIdentity;
|
|
33
|
+
} & {
|
|
34
|
+
buyerIp: string | null;
|
|
35
|
+
}): Promise<Cart | null>;
|
|
36
|
+
updateCartLineItem(cartPayload: UpdateCartLineItemPayload & {
|
|
37
|
+
buyerIp: string | null;
|
|
38
|
+
}): Promise<Cart | null>;
|
|
39
|
+
removeCartLineItems(cartPayload: RemoveFromCartPayload & {
|
|
40
|
+
buyerIp: string | null;
|
|
41
|
+
}): Promise<Cart | null>;
|
|
42
|
+
updateCartDiscountCodes(cartPayload: UpdateCartDiscountCodesPayload & {
|
|
43
|
+
buyerIp: string | null;
|
|
44
|
+
}): Promise<Cart | null>;
|
|
45
|
+
updateCartNote(cartPayload: UpdateCartNotePayload & {
|
|
46
|
+
buyerIp: string | null;
|
|
47
|
+
}): Promise<Cart | null>;
|
|
48
|
+
replaceCartAttributes(cartPayload: ReplaceCartAttributesPayload & {
|
|
49
|
+
buyerIp: string | null;
|
|
24
50
|
}): Promise<Cart | null>;
|
|
25
|
-
updateCartLineItem(cartPayload: UpdateCartLineItemPayload): Promise<Cart | null>;
|
|
26
|
-
removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;
|
|
27
|
-
updateCartDiscountCodes(cartPayload: UpdateCartDiscountCodesPayload): Promise<Cart | null>;
|
|
28
|
-
updateCartNote(cartPayload: UpdateCartNotePayload): Promise<Cart | null>;
|
|
29
|
-
replaceCartAttributes(cartPayload: ReplaceCartAttributesPayload): Promise<Cart | null>;
|
|
30
51
|
}
|
|
31
52
|
export declare function getCartGateway(): Promise<CartGateway>;
|
|
@@ -27,8 +27,10 @@ export declare abstract class CookieBasedCartGateway implements CartGateway {
|
|
|
27
27
|
private reconcile;
|
|
28
28
|
private reconcileForWrite;
|
|
29
29
|
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
30
|
-
getCart(cartId
|
|
31
|
-
|
|
30
|
+
getCart({ cartId }: {
|
|
31
|
+
cartId: string;
|
|
32
|
+
}): Promise<Cart | null>;
|
|
33
|
+
createCart({ lines, }: {
|
|
32
34
|
lines?: CartLinePayload[];
|
|
33
35
|
discountCodes?: string[];
|
|
34
36
|
buyerIdentity?: CartBuyerIdentity;
|
|
@@ -51,7 +51,7 @@ class CookieBasedCartGateway {
|
|
|
51
51
|
}
|
|
52
52
|
return reconciledCart;
|
|
53
53
|
}
|
|
54
|
-
async getCart(cartId) {
|
|
54
|
+
async getCart({ cartId }) {
|
|
55
55
|
try {
|
|
56
56
|
const cart = await loadCartFromCookie(this.config.cookieName);
|
|
57
57
|
return cart?.id === cartId ? cart : null;
|
|
@@ -65,7 +65,7 @@ class CookieBasedCartGateway {
|
|
|
65
65
|
}
|
|
66
66
|
async createCart({
|
|
67
67
|
lines
|
|
68
|
-
}
|
|
68
|
+
}) {
|
|
69
69
|
try {
|
|
70
70
|
const cart = await createCartWithPersistence({
|
|
71
71
|
cookieName: this.config.cookieName,
|
|
@@ -146,7 +146,7 @@ class CookieBasedCartGateway {
|
|
|
146
146
|
`${this.config.gatewayName} cart gateway does not yet support discount codes`
|
|
147
147
|
);
|
|
148
148
|
try {
|
|
149
|
-
return await this.getCart(cartPayload.cartId);
|
|
149
|
+
return await this.getCart({ cartId: cartPayload.cartId });
|
|
150
150
|
} catch (error) {
|
|
151
151
|
console.warn("Failed to fetch cart for discount code update:", error);
|
|
152
152
|
return null;
|
|
@@ -155,7 +155,7 @@ class CookieBasedCartGateway {
|
|
|
155
155
|
async updateCartBuyerIdentity({
|
|
156
156
|
cartId
|
|
157
157
|
}) {
|
|
158
|
-
return this.getCart(cartId);
|
|
158
|
+
return this.getCart({ cartId });
|
|
159
159
|
}
|
|
160
160
|
async updateCartNote({
|
|
161
161
|
cartId,
|
|
@@ -4,18 +4,21 @@ class ResolverCartGateway {
|
|
|
4
4
|
async reconcileStoredCart(cart) {
|
|
5
5
|
return cart;
|
|
6
6
|
}
|
|
7
|
-
async getCart(
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
async getCart({
|
|
8
|
+
cartId,
|
|
9
|
+
buyerIp
|
|
10
|
+
}) {
|
|
11
|
+
return await getCanopyApiGateway().cartGet({ cartId, buyerIp });
|
|
10
12
|
}
|
|
11
13
|
async createCart({
|
|
12
14
|
lines,
|
|
13
15
|
discountCodes,
|
|
14
16
|
attributes,
|
|
15
|
-
buyerIdentity
|
|
16
|
-
|
|
17
|
+
buyerIdentity,
|
|
18
|
+
buyerIp
|
|
19
|
+
}) {
|
|
17
20
|
try {
|
|
18
|
-
const gateway =
|
|
21
|
+
const gateway = getCanopyApiGateway();
|
|
19
22
|
return await gateway.cartCreate({
|
|
20
23
|
lines: lines?.map((line) => ({
|
|
21
24
|
merchandiseId: line.id,
|
|
@@ -28,7 +31,8 @@ class ResolverCartGateway {
|
|
|
28
31
|
})),
|
|
29
32
|
discountCodes,
|
|
30
33
|
attributes,
|
|
31
|
-
country: buyerIdentity?.country
|
|
34
|
+
country: buyerIdentity?.country,
|
|
35
|
+
buyerIp
|
|
32
36
|
});
|
|
33
37
|
} catch (error) {
|
|
34
38
|
console.warn("Failed to create cart via canopy-api:", error);
|
|
@@ -37,9 +41,10 @@ class ResolverCartGateway {
|
|
|
37
41
|
}
|
|
38
42
|
async addToCart(cartPayload) {
|
|
39
43
|
try {
|
|
40
|
-
const gateway =
|
|
44
|
+
const gateway = getCanopyApiGateway();
|
|
41
45
|
return await gateway.cartAddLines({
|
|
42
46
|
cartId: cartPayload.cartId,
|
|
47
|
+
buyerIp: cartPayload.buyerIp,
|
|
43
48
|
lines: cartPayload.lines.map((line) => ({
|
|
44
49
|
merchandiseId: line.id,
|
|
45
50
|
quantity: line.quantity,
|
|
@@ -57,9 +62,10 @@ class ResolverCartGateway {
|
|
|
57
62
|
}
|
|
58
63
|
async updateCartLineItem(cartPayload) {
|
|
59
64
|
try {
|
|
60
|
-
const gateway =
|
|
65
|
+
const gateway = getCanopyApiGateway();
|
|
61
66
|
return await gateway.cartUpdateLines({
|
|
62
67
|
cartId: cartPayload.cartId,
|
|
68
|
+
buyerIp: cartPayload.buyerIp,
|
|
63
69
|
lines: [
|
|
64
70
|
{
|
|
65
71
|
lineId: cartPayload.line.id,
|
|
@@ -79,9 +85,10 @@ class ResolverCartGateway {
|
|
|
79
85
|
}
|
|
80
86
|
async removeCartLineItems(cartPayload) {
|
|
81
87
|
try {
|
|
82
|
-
const gateway =
|
|
88
|
+
const gateway = getCanopyApiGateway();
|
|
83
89
|
return await gateway.cartRemoveLines({
|
|
84
90
|
cartId: cartPayload.cartId,
|
|
91
|
+
buyerIp: cartPayload.buyerIp,
|
|
85
92
|
lineIds: cartPayload.lineIds
|
|
86
93
|
});
|
|
87
94
|
} catch (error) {
|
|
@@ -91,9 +98,10 @@ class ResolverCartGateway {
|
|
|
91
98
|
}
|
|
92
99
|
async updateCartDiscountCodes(cartPayload) {
|
|
93
100
|
try {
|
|
94
|
-
const gateway =
|
|
101
|
+
const gateway = getCanopyApiGateway();
|
|
95
102
|
return await gateway.cartUpdateDiscountCodes({
|
|
96
103
|
cartId: cartPayload.cartId,
|
|
104
|
+
buyerIp: cartPayload.buyerIp,
|
|
97
105
|
discountCodes: cartPayload.discountCodes
|
|
98
106
|
});
|
|
99
107
|
} catch (error) {
|
|
@@ -103,13 +111,15 @@ class ResolverCartGateway {
|
|
|
103
111
|
}
|
|
104
112
|
async updateCartBuyerIdentity({
|
|
105
113
|
cartId,
|
|
106
|
-
buyerIdentity
|
|
114
|
+
buyerIdentity,
|
|
115
|
+
buyerIp
|
|
107
116
|
}) {
|
|
108
117
|
try {
|
|
109
|
-
const gateway =
|
|
118
|
+
const gateway = getCanopyApiGateway();
|
|
110
119
|
return await gateway.cartUpdateBuyerIdentity({
|
|
111
120
|
cartId,
|
|
112
|
-
country: buyerIdentity.country
|
|
121
|
+
country: buyerIdentity.country,
|
|
122
|
+
buyerIp
|
|
113
123
|
});
|
|
114
124
|
} catch (error) {
|
|
115
125
|
console.warn(
|
|
@@ -121,9 +131,10 @@ class ResolverCartGateway {
|
|
|
121
131
|
}
|
|
122
132
|
async updateCartNote(cartPayload) {
|
|
123
133
|
try {
|
|
124
|
-
const gateway =
|
|
134
|
+
const gateway = getCanopyApiGateway();
|
|
125
135
|
return await gateway.cartUpdateNote({
|
|
126
136
|
cartId: cartPayload.cartId,
|
|
137
|
+
buyerIp: cartPayload.buyerIp,
|
|
127
138
|
note: cartPayload.note
|
|
128
139
|
});
|
|
129
140
|
} catch (error) {
|
|
@@ -133,9 +144,10 @@ class ResolverCartGateway {
|
|
|
133
144
|
}
|
|
134
145
|
async replaceCartAttributes(cartPayload) {
|
|
135
146
|
try {
|
|
136
|
-
const gateway =
|
|
147
|
+
const gateway = getCanopyApiGateway();
|
|
137
148
|
return await gateway.cartReplaceAttributes({
|
|
138
149
|
cartId: cartPayload.cartId,
|
|
150
|
+
buyerIp: cartPayload.buyerIp,
|
|
139
151
|
attributes: cartPayload.attributes.map((attribute) => ({
|
|
140
152
|
key: attribute.key,
|
|
141
153
|
value: String(attribute.value)
|
|
@@ -19,7 +19,7 @@ class StripeCartGateway extends CookieBasedCartGateway {
|
|
|
19
19
|
cartIdGenerator: stripeCartIdGenerator,
|
|
20
20
|
gatewayName: "Stripe",
|
|
21
21
|
reconcileLines: async (lines) => {
|
|
22
|
-
const gateway =
|
|
22
|
+
const gateway = getCanopyApiGateway();
|
|
23
23
|
return await gateway.reconcileCartLines({ lines });
|
|
24
24
|
}
|
|
25
25
|
};
|
|
@@ -37,7 +37,7 @@ async function startStripeCheckout({
|
|
|
37
37
|
"[Replo] Stripe buy now does not support selling plans or discount codes; those options were ignored."
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
|
-
const outcome = await
|
|
40
|
+
const outcome = await getCanopyApiGateway().createStripeCheckoutSession({
|
|
41
41
|
checkoutAttemptId: checkoutAttemptId ?? uuidv4(),
|
|
42
42
|
lines: lineItems.map((item) => ({
|
|
43
43
|
variantId: item.variantId,
|
|
@@ -7,15 +7,21 @@ export interface ConsentPlatformConfig {
|
|
|
7
7
|
name: string;
|
|
8
8
|
values: Partial<Record<ConsentCategory, string>>;
|
|
9
9
|
};
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/** Presets read the visitor's answer back; a generic platform only reports grants. */
|
|
11
|
+
kind: "cookiebot" | "usercentrics" | "generic";
|
|
12
|
+
/** Usercentrics only: the service that releases Replo's own pixel, or null until one is chosen. */
|
|
13
|
+
reploUsercentricsService: string | null;
|
|
14
|
+
/** The GTM container that loads this platform, which therefore can't wait for it. */
|
|
15
|
+
loaderContainerId: string | null;
|
|
12
16
|
}
|
|
13
17
|
/** Whether this entry connects a consent platform (vs a tracker it would gate). */
|
|
14
|
-
export declare function isConsentPlatformEntry(entry: ReploScriptEntry):
|
|
18
|
+
export declare function isConsentPlatformEntry(entry: ReploScriptEntry): entry is Extract<ReploScriptEntry, {
|
|
19
|
+
type: "Cookiebot" | "Usercentrics" | "consentPlatform";
|
|
20
|
+
}>;
|
|
15
21
|
/** The connected platform's config, or null when Replo's banner owns consent. */
|
|
16
22
|
export declare function findConsentPlatform(scripts: ReploScriptEntry[]): ConsentPlatformConfig | null;
|
|
17
23
|
/** Blocks a gated script; `text/plain` is unconditional so an unmapped category fails closed. */
|
|
18
|
-
export declare function getBlockingAttributes(config: ConsentPlatformConfig, requiredConsent: ConsentCategory[]): Record<string, string>;
|
|
24
|
+
export declare function getBlockingAttributes(config: ConsentPlatformConfig, requiredConsent: ConsentCategory[], usercentricsService: string | null): Record<string, string>;
|
|
19
25
|
/** The bits of Cookiebot's `window.Cookiebot` global we read. */
|
|
20
26
|
interface CookiebotGlobal {
|
|
21
27
|
consent?: Partial<Record<CookiebotCategory, boolean>>;
|
|
@@ -26,6 +32,14 @@ type CookiebotCategory = "necessary" | "preferences" | "statistics" | "marketing
|
|
|
26
32
|
declare global {
|
|
27
33
|
interface Window {
|
|
28
34
|
Cookiebot?: CookiebotGlobal;
|
|
35
|
+
__ucCmp?: {
|
|
36
|
+
getConsentDetails?: () => Promise<unknown>;
|
|
37
|
+
refreshScripts?: () => Promise<unknown>;
|
|
38
|
+
};
|
|
39
|
+
UC_UI?: {
|
|
40
|
+
getServicesBaseInfo?: () => unknown;
|
|
41
|
+
enableScriptsForServicesWithConsent?: () => unknown;
|
|
42
|
+
};
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
45
|
type OptionalConsentCategory = Exclude<ConsentCategory, "necessary">;
|
|
@@ -36,12 +50,13 @@ export declare function findDelegatedPlatform(): ConsentPlatformConfig | null;
|
|
|
36
50
|
export declare function getDelegatedConsent(): {
|
|
37
51
|
categories: ConsentCategories;
|
|
38
52
|
decidedAt: number | null;
|
|
53
|
+
grantedUsercentricsServices?: string[];
|
|
39
54
|
};
|
|
40
55
|
/** Inline marker a generic CMP executes when it releases one consent category. */
|
|
41
56
|
export declare function buildGrantMarker(category: OptionalConsentCategory): ScriptTagDescriptor;
|
|
42
57
|
/** Latches the platform as the consent backend; un-delegating would need a reload, as native revocation already does. */
|
|
43
58
|
export declare function enableConsentDelegation(config: ConsentPlatformConfig): void;
|
|
44
|
-
/**
|
|
59
|
+
/** Re-scan for late tags; a generic platform relies on the loader injecting after every tag. */
|
|
45
60
|
export declare function activateBlockedScripts(): void;
|
|
46
61
|
/** Subscribe to delegated-consent changes. Returns an unsubscribe function. */
|
|
47
62
|
export declare function subscribeToConsentDelegation(listener: () => void): () => void;
|