@replohq/sdk 2.5.0 → 2.7.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/json.mjs +4 -4
- package/_vendor/replo-utils/lib/misc.mjs +726 -4
- package/_vendor/replo-utils/lib/object.mjs +3 -3
- package/_vendor/schemas/contentCollection.d.ts +2 -2
- package/_vendor/schemas/contentEntry.mjs +3 -3
- package/_vendor/schemas/contentManifest.mjs +6 -6
- package/_vendor/schemas/routing/locale.d.ts +1 -1
- package/_vendor/schemas/routing/locale.mjs +0 -5
- package/_vendor/schemas/routing/rules.d.ts +1 -1
- package/_vendor/schemas/routing/rules.mjs +0 -5
- package/analytics/pixel-meta.d.ts +14 -0
- package/analytics/pixel-meta.js +28 -0
- package/analytics/replo-first-party-pixel.d.ts +5 -4
- package/analytics/replo-first-party-pixel.js +7 -3
- package/analytics/replo-pixel-from-meta.d.ts +7 -0
- package/analytics/replo-pixel-from-meta.js +27 -0
- package/analytics/replo-pixel-script.d.ts +3 -1
- package/analytics/replo-pixel-script.js +4 -2
- package/cart/buy-now-action.d.ts +2 -13
- package/cart/buy-now-action.js +11 -75
- package/cart/checkout-actions.d.ts +34 -0
- package/cart/checkout-actions.js +27 -0
- package/cart/checkout-appearance.d.ts +35 -0
- package/cart/checkout-appearance.js +131 -0
- package/cart/checkout-session-actions.d.ts +20 -0
- package/cart/checkout-session-actions.js +12 -0
- package/cart/checkout.d.ts +110 -0
- package/cart/checkout.js +420 -0
- package/cart/start-stripe-checkout.d.ts +40 -0
- package/cart/start-stripe-checkout.js +75 -0
- package/{chunk-TTEBDCWT.mjs → chunk-4YTXURVN.mjs} +2 -2
- package/{chunk-O4QBR4QC.mjs → chunk-DMJ5VUD6.mjs} +2 -2
- package/{chunk-OPKMOW43.mjs → chunk-GNIXARC7.mjs} +1 -1
- package/{chunk-XWWFRIXV.mjs → chunk-N4WC2Z43.mjs} +1 -1
- package/{chunk-PSYP2DYM.mjs → chunk-QYGRVXWQ.mjs} +1 -1
- package/env.d.ts +1 -0
- package/env.js +1 -0
- package/gateways/canopy-api.d.ts +97 -0
- package/gateways/canopy-api.js +120 -0
- package/lib/buildMetadata.js +3 -3
- package/package.json +14 -4
- package/_vendor/replo-utils/lib/errors.d.ts +0 -84
- package/_vendor/schemas/errors.d.ts +0 -450
- package/_vendor/schemas/generated/errors.d.ts +0 -64
- package/chunk-XGXJOHUY.mjs +0 -724
- package/chunk-XSMYSAKY.mjs +0 -78
- /package/{chunk-ZPDXVFCR.mjs → chunk-JBCOJHZN.mjs} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { deepEqual, filterUndefined, forEachPrimitiveValue, omit, shallowEqual, toNullableIfDefined } from '../../../chunk-
|
|
2
|
-
import '../../../chunk-
|
|
3
|
-
import '../../../chunk-
|
|
1
|
+
export { deepEqual, filterUndefined, forEachPrimitiveValue, omit, shallowEqual, toNullableIfDefined } from '../../../chunk-DMJ5VUD6.mjs';
|
|
2
|
+
import '../../../chunk-GNIXARC7.mjs';
|
|
3
|
+
import '../../../chunk-JBCOJHZN.mjs';
|
|
4
4
|
import '../../../chunk-UJCSKKID.mjs';
|
|
@@ -86,6 +86,7 @@ export declare const contentCollectionFieldTypeSchema: z.ZodEnum<{
|
|
|
86
86
|
}>;
|
|
87
87
|
export type ContentCollectionFieldType = z.infer<typeof contentCollectionFieldTypeSchema>;
|
|
88
88
|
export declare const contentCollectionColorSchema: z.ZodEnum<{
|
|
89
|
+
blue: "blue";
|
|
89
90
|
seashell: "seashell";
|
|
90
91
|
yellow: "yellow";
|
|
91
92
|
lightGreen: "lightGreen";
|
|
@@ -93,7 +94,6 @@ export declare const contentCollectionColorSchema: z.ZodEnum<{
|
|
|
93
94
|
lightPink: "lightPink";
|
|
94
95
|
magenta: "magenta";
|
|
95
96
|
purple: "purple";
|
|
96
|
-
blue: "blue";
|
|
97
97
|
}>;
|
|
98
98
|
export declare const contentCollectionFieldSchema: z.ZodObject<{
|
|
99
99
|
id: z.ZodString;
|
|
@@ -125,6 +125,7 @@ export declare const contentCollectionFileSchema: z.ZodObject<{
|
|
|
125
125
|
description: z.ZodString;
|
|
126
126
|
icon: z.ZodString;
|
|
127
127
|
color: z.ZodEnum<{
|
|
128
|
+
blue: "blue";
|
|
128
129
|
seashell: "seashell";
|
|
129
130
|
yellow: "yellow";
|
|
130
131
|
lightGreen: "lightGreen";
|
|
@@ -132,7 +133,6 @@ export declare const contentCollectionFileSchema: z.ZodObject<{
|
|
|
132
133
|
lightPink: "lightPink";
|
|
133
134
|
magenta: "magenta";
|
|
134
135
|
purple: "purple";
|
|
135
|
-
blue: "blue";
|
|
136
136
|
}>;
|
|
137
137
|
tags: z.ZodArray<z.ZodString>;
|
|
138
138
|
fields: z.ZodArray<z.ZodObject<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, convertLegacyContentEntryDocument, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument } from '../../chunk-
|
|
1
|
+
export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, convertLegacyContentEntryDocument, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument } from '../../chunk-4YTXURVN.mjs';
|
|
2
2
|
import '../../chunk-RQ76VK3L.mjs';
|
|
3
|
-
import '../../chunk-
|
|
4
|
-
import '../../chunk-
|
|
3
|
+
import '../../chunk-N4WC2Z43.mjs';
|
|
4
|
+
import '../../chunk-JBCOJHZN.mjs';
|
|
5
5
|
import '../../chunk-UJCSKKID.mjs';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { parseContentEntryDocument, getContentRenderableEntryValues, getContentEntryFieldValidationIssues } from '../../chunk-
|
|
1
|
+
import { parseContentEntryDocument, getContentRenderableEntryValues, getContentEntryFieldValidationIssues } from '../../chunk-4YTXURVN.mjs';
|
|
2
2
|
import { isValidContentSlug, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_ENTRY_FILE_EXTENSION, contentCollectionFileSchema, CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION } from '../../chunk-RQ76VK3L.mjs';
|
|
3
|
-
import '../../chunk-
|
|
4
|
-
import { parseJsonOrNull } from '../../chunk-
|
|
5
|
-
import '../../chunk-
|
|
6
|
-
import '../../chunk-
|
|
7
|
-
import '../../chunk-
|
|
3
|
+
import '../../chunk-N4WC2Z43.mjs';
|
|
4
|
+
import { parseJsonOrNull } from '../../chunk-QYGRVXWQ.mjs';
|
|
5
|
+
import '../../chunk-DMJ5VUD6.mjs';
|
|
6
|
+
import '../../chunk-GNIXARC7.mjs';
|
|
7
|
+
import '../../chunk-JBCOJHZN.mjs';
|
|
8
8
|
import '../../chunk-UJCSKKID.mjs';
|
|
9
9
|
import * as z from 'zod';
|
|
10
10
|
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import '../../../chunk-XSMYSAKY.mjs';
|
|
2
1
|
import { ReploError } from '../../../chunk-AE5H7AJS.mjs';
|
|
3
|
-
import '../../../chunk-XGXJOHUY.mjs';
|
|
4
|
-
import '../../../chunk-XWWFRIXV.mjs';
|
|
5
|
-
import '../../../chunk-OPKMOW43.mjs';
|
|
6
|
-
import '../../../chunk-ZPDXVFCR.mjs';
|
|
7
2
|
import '../../../chunk-UJCSKKID.mjs';
|
|
8
3
|
import * as z from 'zod';
|
|
9
4
|
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { getParamSegment, getRouteParams, hasRouteParam, matchSource } from '../../../chunk-VPEVERWE.mjs';
|
|
2
|
-
import '../../../chunk-XSMYSAKY.mjs';
|
|
3
2
|
import { ReploError } from '../../../chunk-AE5H7AJS.mjs';
|
|
4
|
-
import '../../../chunk-XGXJOHUY.mjs';
|
|
5
|
-
import '../../../chunk-XWWFRIXV.mjs';
|
|
6
|
-
import '../../../chunk-OPKMOW43.mjs';
|
|
7
|
-
import '../../../chunk-ZPDXVFCR.mjs';
|
|
8
3
|
import '../../../chunk-UJCSKKID.mjs';
|
|
9
4
|
import * as z from 'zod';
|
|
10
5
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
/** Name of the `<meta>` the published site worker appends with the pixel config. */
|
|
3
|
+
export declare const PIXEL_META_NAME = "replo:pixel";
|
|
4
|
+
declare const pixelConfigSchema: v.ObjectSchema<{
|
|
5
|
+
readonly pixelUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
6
|
+
readonly apiUrl: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
7
|
+
readonly projectId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
8
|
+
readonly siteVersion: v.SchemaWithFallback<v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>, undefined>;
|
|
9
|
+
}, undefined>;
|
|
10
|
+
export type PixelConfig = v.InferOutput<typeof pixelConfigSchema>;
|
|
11
|
+
/** Parses the meta's JSON content; anything malformed means no pixel. */
|
|
12
|
+
export declare function parsePixelMetaContent(content: string | null): PixelConfig | null;
|
|
13
|
+
export declare function readPixelMetaContent(): string | null;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { parseJsonOrNull } from "../_vendor/replo-utils/lib/json.mjs";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
const PIXEL_META_NAME = "replo:pixel";
|
|
4
|
+
const optionalNonEmptyString = v.fallback(
|
|
5
|
+
v.optional(v.pipe(v.string(), v.minLength(1))),
|
|
6
|
+
void 0
|
|
7
|
+
);
|
|
8
|
+
const pixelConfigSchema = v.object({
|
|
9
|
+
pixelUrl: v.pipe(v.string(), v.minLength(1)),
|
|
10
|
+
apiUrl: v.pipe(v.string(), v.minLength(1)),
|
|
11
|
+
projectId: v.pipe(v.string(), v.minLength(1)),
|
|
12
|
+
siteVersion: optionalNonEmptyString
|
|
13
|
+
});
|
|
14
|
+
function parsePixelMetaContent(content) {
|
|
15
|
+
if (!content) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const result = v.safeParse(pixelConfigSchema, parseJsonOrNull(content));
|
|
19
|
+
return result.success ? result.output : null;
|
|
20
|
+
}
|
|
21
|
+
function readPixelMetaContent() {
|
|
22
|
+
return document.querySelector(`meta[name="${PIXEL_META_NAME}"]`)?.getAttribute("content") ?? null;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
PIXEL_META_NAME,
|
|
26
|
+
parsePixelMetaContent,
|
|
27
|
+
readPixelMetaContent
|
|
28
|
+
};
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* first-party pixel automatically — mirroring how `ReploSink` is an always-on
|
|
6
6
|
* sink that is never authored in the layout or editable in the miniapp.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Static pages are prerendered at build time, before the publish sets these
|
|
9
|
+
* vars, so without them it defers to `ReploPixelFromMeta`, which reads the
|
|
10
|
+
* config the published site worker adds to the served HTML. With neither (e.g.
|
|
11
|
+
* lean local dev) nothing loads, mirroring the self-disabling consent action.
|
|
11
12
|
*/
|
|
12
|
-
export declare function ReploFirstPartyPixel(): Promise<import("react/jsx-runtime").JSX.Element
|
|
13
|
+
export declare function ReploFirstPartyPixel(): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { getEnv } from "../env";
|
|
3
|
+
import { ReploPixelFromMeta } from "./replo-pixel-from-meta";
|
|
3
4
|
import { ReploPixelScript } from "./replo-pixel-script";
|
|
4
5
|
async function ReploFirstPartyPixel() {
|
|
5
6
|
let pixelUrl;
|
|
6
7
|
let apiUrl;
|
|
7
8
|
let projectId;
|
|
9
|
+
let siteVersion;
|
|
8
10
|
try {
|
|
9
11
|
const env = await getEnv();
|
|
10
12
|
pixelUrl = env.REPLO_ANALYTICS_PIXEL_URL;
|
|
11
13
|
apiUrl = env.ANALYTICS_FIRE_API_URL;
|
|
12
14
|
projectId = env.PROJECT_ID;
|
|
15
|
+
siteVersion = env.REPLO_SITE_VERSION;
|
|
13
16
|
} catch {
|
|
14
|
-
return
|
|
17
|
+
return /* @__PURE__ */ jsx(ReploPixelFromMeta, {});
|
|
15
18
|
}
|
|
16
19
|
if (!pixelUrl || !apiUrl || !projectId) {
|
|
17
|
-
return
|
|
20
|
+
return /* @__PURE__ */ jsx(ReploPixelFromMeta, {});
|
|
18
21
|
}
|
|
19
22
|
return /* @__PURE__ */ jsx(
|
|
20
23
|
ReploPixelScript,
|
|
21
24
|
{
|
|
22
25
|
pixelUrl,
|
|
23
26
|
apiUrl,
|
|
24
|
-
projectId
|
|
27
|
+
projectId,
|
|
28
|
+
siteVersion
|
|
25
29
|
}
|
|
26
30
|
);
|
|
27
31
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client fallback for `ReploFirstPartyPixel` on pages prerendered without the
|
|
3
|
+
* pixel config: mounts the same consent-gated pixel from the `replo:pixel` meta
|
|
4
|
+
* the published site worker adds at serve time. Renders nothing during SSR and
|
|
5
|
+
* hydration, so server and client markup match.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ReploPixelFromMeta(): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
import { parsePixelMetaContent, readPixelMetaContent } from "./pixel-meta";
|
|
5
|
+
import { ReploPixelScript } from "./replo-pixel-script";
|
|
6
|
+
function subscribeToNothing() {
|
|
7
|
+
return () => {
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function getServerSnapshot() {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function ReploPixelFromMeta() {
|
|
14
|
+
const content = useSyncExternalStore(
|
|
15
|
+
subscribeToNothing,
|
|
16
|
+
readPixelMetaContent,
|
|
17
|
+
getServerSnapshot
|
|
18
|
+
);
|
|
19
|
+
const config = parsePixelMetaContent(content);
|
|
20
|
+
if (!config) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return /* @__PURE__ */ jsx(ReploPixelScript, { ...config });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
ReploPixelFromMeta
|
|
27
|
+
};
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* stamps the blocking attributes at mount time, since delegation is not yet
|
|
11
11
|
* set here.
|
|
12
12
|
*/
|
|
13
|
-
export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, }: {
|
|
13
|
+
export declare function ReploPixelScript({ pixelUrl, apiUrl, projectId, siteVersion, }: {
|
|
14
14
|
pixelUrl: string;
|
|
15
15
|
apiUrl: string;
|
|
16
16
|
projectId: string;
|
|
17
|
+
/** Publish id of the running site; absent on unpublished or older deploys. */
|
|
18
|
+
siteVersion?: string;
|
|
17
19
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -7,7 +7,8 @@ const PIXEL_REQUIRED_CONSENT = ["analytics"];
|
|
|
7
7
|
function ReploPixelScript({
|
|
8
8
|
pixelUrl,
|
|
9
9
|
apiUrl,
|
|
10
|
-
projectId
|
|
10
|
+
projectId,
|
|
11
|
+
siteVersion
|
|
11
12
|
}) {
|
|
12
13
|
const isBlockedByNativeConsent = useIsBlockedByNativeConsent(
|
|
13
14
|
PIXEL_REQUIRED_CONSENT
|
|
@@ -21,7 +22,8 @@ function ReploPixelScript({
|
|
|
21
22
|
src: `${pixelUrl}/latest.js`,
|
|
22
23
|
attributes: {
|
|
23
24
|
"data-api-url": apiUrl,
|
|
24
|
-
"data-project-id": projectId
|
|
25
|
+
"data-project-id": projectId,
|
|
26
|
+
...siteVersion ? { "data-site-version": siteVersion } : {}
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
];
|
package/cart/buy-now-action.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
2
2
|
import type { CartLineAttribute } from "./cart-types";
|
|
3
|
+
import type { StripeCheckoutOutcome } from "./start-stripe-checkout";
|
|
3
4
|
/**
|
|
4
5
|
* Server action that handles the full buy-now flow:
|
|
5
6
|
* 1. For Shopify: creates a cart via the canopy-api-backed gateway, returns checkoutUrl.
|
|
@@ -33,16 +34,4 @@ export declare function buyNowWithRecoveryAction(input: {
|
|
|
33
34
|
value: string;
|
|
34
35
|
}[];
|
|
35
36
|
pageUrl: string;
|
|
36
|
-
}): Promise<
|
|
37
|
-
result: "created";
|
|
38
|
-
redirectUrl: string;
|
|
39
|
-
} | {
|
|
40
|
-
result: "restartCheckout";
|
|
41
|
-
message: string;
|
|
42
|
-
} | {
|
|
43
|
-
result: "outOfStock";
|
|
44
|
-
variants: {
|
|
45
|
-
variantId: string;
|
|
46
|
-
available: number;
|
|
47
|
-
}[];
|
|
48
|
-
}>;
|
|
37
|
+
}): Promise<StripeCheckoutOutcome>;
|
package/cart/buy-now-action.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { headers } from "next/headers";
|
|
3
|
-
import { v4 as uuidv4 } from "uuid";
|
|
4
|
-
import * as z from "zod";
|
|
5
3
|
import { getEnv } from "../env";
|
|
6
4
|
import { CanopyError } from "../lib/canopy-error";
|
|
7
5
|
import { getCheckoutProvider } from "../lib/integration-utils";
|
|
8
|
-
import { buildBaseUrl } from "../lib/url-utils";
|
|
9
6
|
import { createCartAction } from "./cart-actions";
|
|
10
7
|
import { REPLO_ATTRIBUTION_PROPERTY } from "./cart-types";
|
|
11
8
|
import { getReturnUrls } from "./return-urls";
|
|
9
|
+
import { startStripeCheckout } from "./start-stripe-checkout";
|
|
12
10
|
const REPLO_PROJECT_ID_KEY = "rpid";
|
|
13
11
|
class BuyNowError extends CanopyError {
|
|
14
12
|
}
|
|
@@ -23,19 +21,27 @@ async function buyNowAction(input) {
|
|
|
23
21
|
}
|
|
24
22
|
async function buyNowWithRecoveryAction(input) {
|
|
25
23
|
const { lineItems, discountCodes, pageUrl } = input;
|
|
24
|
+
if (await getCheckoutProvider() === "stripe") {
|
|
25
|
+
return await startStripeCheckout({
|
|
26
|
+
checkoutAttemptId: input.checkoutAttemptId ?? null,
|
|
27
|
+
lineItems,
|
|
28
|
+
discountCodes,
|
|
29
|
+
pageUrl,
|
|
30
|
+
ui: { mode: "hosted" }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
26
33
|
const headersList = await headers();
|
|
27
34
|
const host = headersList.get("host");
|
|
28
35
|
if (!host) {
|
|
29
36
|
throw new BuyNowError({ message: "Missing host header" });
|
|
30
37
|
}
|
|
31
|
-
const { origin
|
|
38
|
+
const { origin } = getReturnUrls({
|
|
32
39
|
host,
|
|
33
40
|
forwardedHost: headersList.get("x-forwarded-host"),
|
|
34
41
|
forwardedProto: headersList.get("x-forwarded-proto"),
|
|
35
42
|
requestOrigin: headersList.get("origin"),
|
|
36
43
|
pageUrl
|
|
37
44
|
});
|
|
38
|
-
const checkoutProvider = await getCheckoutProvider();
|
|
39
45
|
const cartLines = lineItems.map((item) => {
|
|
40
46
|
const hasReploAttribution = item.properties.some(
|
|
41
47
|
(prop) => prop.key === REPLO_ATTRIBUTION_PROPERTY.key
|
|
@@ -48,76 +54,6 @@ async function buyNowWithRecoveryAction(input) {
|
|
|
48
54
|
properties
|
|
49
55
|
};
|
|
50
56
|
});
|
|
51
|
-
if (checkoutProvider === "stripe") {
|
|
52
|
-
if (discountCodes.length > 0 || lineItems.some((lineItem) => Boolean(lineItem.sellingPlanId))) {
|
|
53
|
-
console.warn(
|
|
54
|
-
"[Replo] Stripe buy now does not support selling plans or discount codes; those options were ignored."
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
const env2 = await getEnv();
|
|
58
|
-
const temporaryCartId = input.checkoutAttemptId ?? uuidv4();
|
|
59
|
-
const body = {
|
|
60
|
-
cartId: temporaryCartId,
|
|
61
|
-
checkoutAttemptId: temporaryCartId,
|
|
62
|
-
lines: cartLines.map((line) => ({
|
|
63
|
-
variantId: line.id,
|
|
64
|
-
quantity: line.quantity,
|
|
65
|
-
properties: line.properties
|
|
66
|
-
})),
|
|
67
|
-
projectId: env2.PROJECT_ID,
|
|
68
|
-
successUrl: `${origin}/checkout/success`,
|
|
69
|
-
cancelUrl
|
|
70
|
-
};
|
|
71
|
-
const canopyApiUrl = `${buildBaseUrl(env2.CANOPY_API_HOST)}/api/v1/stripe-checkout/create-session`;
|
|
72
|
-
const response = await fetch(canopyApiUrl, {
|
|
73
|
-
method: "POST",
|
|
74
|
-
headers: { "Content-Type": "application/json" },
|
|
75
|
-
body: JSON.stringify(body)
|
|
76
|
-
});
|
|
77
|
-
if (!response.ok) {
|
|
78
|
-
if (response.status === 409) {
|
|
79
|
-
const terminalAttempt = z.object({
|
|
80
|
-
error: z.literal("checkoutUnavailable"),
|
|
81
|
-
retry: z.literal("newAttempt"),
|
|
82
|
-
message: z.string().min(1)
|
|
83
|
-
}).safeParse(await response.json());
|
|
84
|
-
if (terminalAttempt.success) {
|
|
85
|
-
return {
|
|
86
|
-
result: "restartCheckout",
|
|
87
|
-
message: terminalAttempt.data.message
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
throw new BuyNowCartCreationError({
|
|
92
|
-
message: "Failed to create Stripe checkout session for buy now",
|
|
93
|
-
additionalData: {
|
|
94
|
-
status: response.status,
|
|
95
|
-
statusText: response.statusText
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const payload = await response.json();
|
|
100
|
-
const outOfStock = z.object({
|
|
101
|
-
result: z.literal("outOfStock"),
|
|
102
|
-
variants: z.array(
|
|
103
|
-
z.object({
|
|
104
|
-
variantId: z.string(),
|
|
105
|
-
available: z.number().int().min(0)
|
|
106
|
-
})
|
|
107
|
-
)
|
|
108
|
-
}).safeParse(payload);
|
|
109
|
-
if (outOfStock.success) {
|
|
110
|
-
return { result: "outOfStock", variants: outOfStock.data.variants };
|
|
111
|
-
}
|
|
112
|
-
const created = z.object({ url: z.string() }).safeParse(payload);
|
|
113
|
-
if (!created.success) {
|
|
114
|
-
throw new BuyNowCartCreationError({
|
|
115
|
-
message: "Stripe checkout session response missing redirect URL",
|
|
116
|
-
cause: created.error
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return { result: "created", redirectUrl: created.data.url };
|
|
120
|
-
}
|
|
121
57
|
const cart = await createCartAction({
|
|
122
58
|
lines: cartLines,
|
|
123
59
|
skipStoringCart: true,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CrossSell } from "../gateways/canopy-api";
|
|
2
|
+
import type { CartLineAttribute } from "./cart-types";
|
|
3
|
+
import type { StripeCheckoutOutcome } from "./start-stripe-checkout";
|
|
4
|
+
/**
|
|
5
|
+
* Starts Stripe checkout on the site's own page: the outcome's `redirectUrl`
|
|
6
|
+
* opens `paymentPagePath` (a page built on `<CheckoutSessionProvider>` and
|
|
7
|
+
* `<StripePaymentForm />`) with the session in the URL fragment. Where canopy can't serve embedded checkout it
|
|
8
|
+
* returns Stripe's hosted page instead, and a store that doesn't check out
|
|
9
|
+
* through Stripe gets `buyNowWithRecoveryAction`'s redirect. Same outcomes as
|
|
10
|
+
* `buyNowWithRecoveryAction`, which keeps redirecting to the hosted page.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createCheckoutSessionAction(input: {
|
|
13
|
+
checkoutAttemptId: string | null;
|
|
14
|
+
lineItems: {
|
|
15
|
+
variantId: string;
|
|
16
|
+
quantity: number;
|
|
17
|
+
sellingPlanId: string | null;
|
|
18
|
+
properties: CartLineAttribute[];
|
|
19
|
+
}[];
|
|
20
|
+
discountCodes: string[];
|
|
21
|
+
pageUrl: string;
|
|
22
|
+
/** Site path of the embedded checkout page, e.g. "/checkout/pay". */
|
|
23
|
+
paymentPagePath: string;
|
|
24
|
+
}): Promise<StripeCheckoutOutcome>;
|
|
25
|
+
/**
|
|
26
|
+
* Up to three live products from the store that aren't in the cart, newest
|
|
27
|
+
* first. Pass the cart's variant ids from a cart drawer, or the session id
|
|
28
|
+
* from embedded checkout (which also leaves out items that session can't
|
|
29
|
+
* take). Empty when the store isn't checking out through Stripe.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getCrossSellsAction(input: {
|
|
32
|
+
excludeVariantIds: string[];
|
|
33
|
+
sessionId: string | null;
|
|
34
|
+
}): Promise<CrossSell[]>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { getCanopyApiGateway } from "../gateways/canopy-api";
|
|
3
|
+
import { getCheckoutProvider } from "../lib/integration-utils";
|
|
4
|
+
import { buyNowWithRecoveryAction } from "./buy-now-action";
|
|
5
|
+
import { startStripeCheckout } from "./start-stripe-checkout";
|
|
6
|
+
async function createCheckoutSessionAction(input) {
|
|
7
|
+
if (await getCheckoutProvider() !== "stripe") {
|
|
8
|
+
return await buyNowWithRecoveryAction({
|
|
9
|
+
...input,
|
|
10
|
+
checkoutAttemptId: input.checkoutAttemptId ?? void 0
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return await startStripeCheckout({
|
|
14
|
+
...input,
|
|
15
|
+
ui: { mode: "form", paymentPagePath: input.paymentPagePath }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function getCrossSellsAction(input) {
|
|
19
|
+
if (await getCheckoutProvider() !== "stripe") {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
return await (await getCanopyApiGateway()).findCrossSells(input);
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
createCheckoutSessionAction,
|
|
26
|
+
getCrossSellsAction
|
|
27
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Appearance, CssFontSource } from "@stripe/stripe-js";
|
|
2
|
+
/**
|
|
3
|
+
* Stripe's Checkout Form takes Appearance variables only (it ignores
|
|
4
|
+
* `rules`), and its color variables reject oklch, rgba and var(). Sites
|
|
5
|
+
* built from Replo templates define their palette as oklch CSS custom
|
|
6
|
+
* properties, so these helpers read the computed tokens and hand Stripe hex.
|
|
7
|
+
*/
|
|
8
|
+
type Rgb = {
|
|
9
|
+
red: number;
|
|
10
|
+
green: number;
|
|
11
|
+
blue: number;
|
|
12
|
+
alpha: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function rgbToHex({ red, green, blue }: Rgb): string;
|
|
15
|
+
export declare function blendOver(color: Rgb, background: Rgb): Rgb;
|
|
16
|
+
export declare function relativeLuminance({ red, green, blue }: Rgb): number;
|
|
17
|
+
/**
|
|
18
|
+
* The site's first named font as a Google Fonts stylesheet. Stripe's form runs
|
|
19
|
+
* in its own frame, so it can only use a font it can load from a URL; a font
|
|
20
|
+
* that isn't on Google Fonts falls back to Stripe's system stack.
|
|
21
|
+
*/
|
|
22
|
+
export declare function googleFontSource(fontFamily: string): {
|
|
23
|
+
family: string;
|
|
24
|
+
source: CssFontSource;
|
|
25
|
+
} | null;
|
|
26
|
+
/**
|
|
27
|
+
* Stripe Appearance built from the site's design tokens (--primary,
|
|
28
|
+
* --background, --foreground, --muted-foreground, --destructive, --border,
|
|
29
|
+
* --radius) and body font, so the form looks like the rest of the store.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readSiteCheckoutAppearance(): {
|
|
32
|
+
appearance: Appearance;
|
|
33
|
+
fonts: CssFontSource[];
|
|
34
|
+
};
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const GENERIC_FONT_FAMILIES = /* @__PURE__ */ new Set([
|
|
2
|
+
"serif",
|
|
3
|
+
"sans-serif",
|
|
4
|
+
"monospace",
|
|
5
|
+
"cursive",
|
|
6
|
+
"fantasy",
|
|
7
|
+
"system-ui",
|
|
8
|
+
"ui-sans-serif",
|
|
9
|
+
"ui-serif",
|
|
10
|
+
"ui-monospace",
|
|
11
|
+
"-apple-system",
|
|
12
|
+
"blinkmacsystemfont"
|
|
13
|
+
]);
|
|
14
|
+
function toHexByte(value) {
|
|
15
|
+
return Math.round(value).toString(16).padStart(2, "0");
|
|
16
|
+
}
|
|
17
|
+
function rgbToHex({ red, green, blue }) {
|
|
18
|
+
return `#${toHexByte(red)}${toHexByte(green)}${toHexByte(blue)}`;
|
|
19
|
+
}
|
|
20
|
+
function blendOver(color, background) {
|
|
21
|
+
const alpha = color.alpha;
|
|
22
|
+
return {
|
|
23
|
+
red: color.red * alpha + background.red * (1 - alpha),
|
|
24
|
+
green: color.green * alpha + background.green * (1 - alpha),
|
|
25
|
+
blue: color.blue * alpha + background.blue * (1 - alpha),
|
|
26
|
+
alpha: 1
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function relativeLuminance({ red, green, blue }) {
|
|
30
|
+
const channel = (value) => {
|
|
31
|
+
const scaled = value / 255;
|
|
32
|
+
return scaled <= 0.03928 ? scaled / 12.92 : ((scaled + 0.055) / 1.055) ** 2.4;
|
|
33
|
+
};
|
|
34
|
+
return 0.2126 * channel(red) + 0.7152 * channel(green) + 0.0722 * channel(blue);
|
|
35
|
+
}
|
|
36
|
+
function googleFontSource(fontFamily) {
|
|
37
|
+
const family = fontFamily.split(",").map((name) => name.trim().replace(/^["']|["']$/g, "")).find((name) => {
|
|
38
|
+
return name.length > 0 && !name.startsWith("__") && !name.toLowerCase().endsWith(" fallback") && !GENERIC_FONT_FAMILIES.has(name.toLowerCase());
|
|
39
|
+
});
|
|
40
|
+
if (!family) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const query = encodeURIComponent(family).replace(/%20/g, "+");
|
|
44
|
+
return {
|
|
45
|
+
family,
|
|
46
|
+
source: {
|
|
47
|
+
cssSrc: `https://fonts.googleapis.com/css2?family=${query}:wght@400;500;600&display=swap`
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const UNPARSED_SENTINEL = "#010203";
|
|
52
|
+
function parseCssColor(value, context) {
|
|
53
|
+
if (!value) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
context.clearRect(0, 0, 1, 1);
|
|
57
|
+
context.fillStyle = UNPARSED_SENTINEL;
|
|
58
|
+
context.fillStyle = value;
|
|
59
|
+
if (context.fillStyle === UNPARSED_SENTINEL && value.trim().toLowerCase() !== UNPARSED_SENTINEL) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
context.fillRect(0, 0, 1, 1);
|
|
63
|
+
const [red = 0, green = 0, blue = 0, alpha = 0] = context.getImageData(
|
|
64
|
+
0,
|
|
65
|
+
0,
|
|
66
|
+
1,
|
|
67
|
+
1
|
|
68
|
+
).data;
|
|
69
|
+
return { red, green, blue, alpha: alpha / 255 };
|
|
70
|
+
}
|
|
71
|
+
function cssLengthToPixels(value, rootFontSizePx) {
|
|
72
|
+
const match = /^(-?\d*\.?\d+)(rem|px)?$/.exec(value.trim());
|
|
73
|
+
if (!match) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const amount = Number(match[1]);
|
|
77
|
+
return match[2] === "rem" ? amount * rootFontSizePx : amount;
|
|
78
|
+
}
|
|
79
|
+
function readSiteCheckoutAppearance() {
|
|
80
|
+
const root = document.documentElement;
|
|
81
|
+
const styles = getComputedStyle(root);
|
|
82
|
+
const canvas = document.createElement("canvas");
|
|
83
|
+
canvas.width = 1;
|
|
84
|
+
canvas.height = 1;
|
|
85
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
86
|
+
const token = (name) => styles.getPropertyValue(name).trim();
|
|
87
|
+
const background = context ? parseCssColor(token("--background"), context) : null;
|
|
88
|
+
const opaqueBackground = background ? blendOver(background, { red: 255, green: 255, blue: 255, alpha: 1 }) : { red: 255, green: 255, blue: 255, alpha: 1 };
|
|
89
|
+
const hex = (name) => {
|
|
90
|
+
const color = context ? parseCssColor(token(name), context) : null;
|
|
91
|
+
return color ? rgbToHex(blendOver(color, opaqueBackground)) : void 0;
|
|
92
|
+
};
|
|
93
|
+
const rootFontSizePx = Number.parseFloat(styles.fontSize) || 16;
|
|
94
|
+
const radiusPx = cssLengthToPixels(token("--radius"), rootFontSizePx);
|
|
95
|
+
const font = googleFontSource(getComputedStyle(document.body).fontFamily);
|
|
96
|
+
const colors = [
|
|
97
|
+
["colorPrimary", hex("--primary")],
|
|
98
|
+
["buttonColorBackground", hex("--primary")],
|
|
99
|
+
["buttonColorText", hex("--primary-foreground")],
|
|
100
|
+
["colorBackground", rgbToHex(opaqueBackground)],
|
|
101
|
+
["colorText", hex("--foreground")],
|
|
102
|
+
["colorTextSecondary", hex("--muted-foreground")],
|
|
103
|
+
["colorDanger", hex("--destructive")],
|
|
104
|
+
["inputColorBorder", hex("--border")]
|
|
105
|
+
];
|
|
106
|
+
const variables = {
|
|
107
|
+
fontSizeBase: "16px",
|
|
108
|
+
...radiusPx === null ? {} : { borderRadius: `${radiusPx}px` },
|
|
109
|
+
...font ? { fontFamily: `${font.family}, system-ui, sans-serif` } : {}
|
|
110
|
+
};
|
|
111
|
+
for (const [name, value] of colors) {
|
|
112
|
+
if (value !== void 0) {
|
|
113
|
+
variables[name] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
appearance: {
|
|
118
|
+
theme: relativeLuminance(opaqueBackground) < 0.4 ? "night" : "stripe",
|
|
119
|
+
inputs: "spaced",
|
|
120
|
+
variables
|
|
121
|
+
},
|
|
122
|
+
fonts: font ? [font.source] : []
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
blendOver,
|
|
127
|
+
googleFontSource,
|
|
128
|
+
readSiteCheckoutAppearance,
|
|
129
|
+
relativeLuminance,
|
|
130
|
+
rgbToHex
|
|
131
|
+
};
|