@bacano/sdk 1.2.7 → 1.2.8
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/CATALOG_MANAGEMENT.md +261 -0
- package/README.md +14 -5
- package/STOREFRONT_FORMS.md +38 -0
- package/STOREFRONT_LEADS.md +5 -1
- package/STOREFRONT_PAYMENTS.md +49 -2
- package/catalog-draft-types.d.ts +145 -0
- package/catalog-management-types.d.ts +143 -0
- package/catalog-management.cjs +125 -0
- package/catalog-management.d.ts +37 -0
- package/catalog-management.js +104 -0
- package/index.cjs +153 -63
- package/index.js +153 -63
- package/modules/checkout-contact.d.ts +4 -0
- package/package.json +6 -1
- package/react.cjs +153 -63
- package/react.js +153 -63
- package/skills/bacano-web/SKILL.md +10 -3
- package/skills/bacano-web/references/common.md +5 -0
- package/skills/bacano-web/references/forms.md +2 -0
- package/skills/bacano-web/references/storefront.md +15 -0
- package/skills/bacano-web/scripts/cli.mjs +28 -5
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export interface CatalogCapabilities {
|
|
2
|
+
references: true;
|
|
3
|
+
drafts: boolean;
|
|
4
|
+
images: boolean;
|
|
5
|
+
content: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface CatalogManagementContext {
|
|
8
|
+
companyId: string;
|
|
9
|
+
grantId: string;
|
|
10
|
+
capabilities: CatalogCapabilities;
|
|
11
|
+
allProducts: boolean;
|
|
12
|
+
productIds: string[];
|
|
13
|
+
branchIds: string[];
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
/** Only draft writes; never confirmation or direct product edits. */
|
|
16
|
+
writesAvailable: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface CatalogReferences {
|
|
19
|
+
contractVersion: 1;
|
|
20
|
+
company: {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
country_code: string;
|
|
24
|
+
is_vat_responsible: boolean;
|
|
25
|
+
sales_prices_include_tax_default: boolean;
|
|
26
|
+
};
|
|
27
|
+
priceTypes: Array<{
|
|
28
|
+
price_type: string;
|
|
29
|
+
is_enabled: boolean;
|
|
30
|
+
is_enabled_for_sales: boolean;
|
|
31
|
+
sort_order: number;
|
|
32
|
+
}>;
|
|
33
|
+
taxProfiles: Array<{
|
|
34
|
+
code: string;
|
|
35
|
+
name: string;
|
|
36
|
+
category: string;
|
|
37
|
+
rate: number;
|
|
38
|
+
}>;
|
|
39
|
+
branches: Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
chains_warehouses: Array<{
|
|
43
|
+
warehouse: {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
};
|
|
47
|
+
}>;
|
|
48
|
+
}>;
|
|
49
|
+
websites: Array<{
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
chain_id: string | null;
|
|
53
|
+
is_active: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
categories: Array<{
|
|
56
|
+
name: string;
|
|
57
|
+
product_subcategories: Array<{
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
}>;
|
|
61
|
+
}>;
|
|
62
|
+
measurementUnits: Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
}>;
|
|
65
|
+
rules: {
|
|
66
|
+
saleMustBeLowerThanClient: true;
|
|
67
|
+
existingProductsContentOnly: true;
|
|
68
|
+
humanConfirmationRequired: true;
|
|
69
|
+
inventoryRequiresExistingWarehouse: true;
|
|
70
|
+
taxonomyEndpoint: string;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export interface CatalogAttributeRule {
|
|
74
|
+
attribute_id: string;
|
|
75
|
+
sort_order: number;
|
|
76
|
+
usage_scope: string;
|
|
77
|
+
is_required: boolean;
|
|
78
|
+
is_filterable: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface CatalogTaxonomy {
|
|
81
|
+
attribute_categories: Array<CatalogAttributeRule & {
|
|
82
|
+
category: string;
|
|
83
|
+
}>;
|
|
84
|
+
attribute_subcategories: Array<CatalogAttributeRule & {
|
|
85
|
+
subcategory_id: string;
|
|
86
|
+
}>;
|
|
87
|
+
attribute_subcategory_exclusions: Array<{
|
|
88
|
+
subcategory_id: string;
|
|
89
|
+
attribute_id: string;
|
|
90
|
+
reason: string | null;
|
|
91
|
+
}>;
|
|
92
|
+
attributes: Array<{
|
|
93
|
+
id: string;
|
|
94
|
+
name: string;
|
|
95
|
+
is_multiple: boolean;
|
|
96
|
+
tooltip: string | null;
|
|
97
|
+
values: unknown;
|
|
98
|
+
attribute_values: Array<{
|
|
99
|
+
id: string;
|
|
100
|
+
code: string;
|
|
101
|
+
sort_order: number;
|
|
102
|
+
metadata: unknown;
|
|
103
|
+
}>;
|
|
104
|
+
attribute_value_sets: Array<{
|
|
105
|
+
id: string;
|
|
106
|
+
code: string;
|
|
107
|
+
category: string;
|
|
108
|
+
attribute_id: string;
|
|
109
|
+
product_subcategory_attribute_value_sets: Array<{
|
|
110
|
+
subcategory_id: string;
|
|
111
|
+
position: number;
|
|
112
|
+
}>;
|
|
113
|
+
attribute_value_set_values: Array<{
|
|
114
|
+
position: number;
|
|
115
|
+
attribute_value: {
|
|
116
|
+
id: string;
|
|
117
|
+
code: string;
|
|
118
|
+
metadata: unknown;
|
|
119
|
+
};
|
|
120
|
+
}>;
|
|
121
|
+
}>;
|
|
122
|
+
}>;
|
|
123
|
+
}
|
|
124
|
+
export interface CatalogProductContent {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
sku: string | null;
|
|
128
|
+
description: string | null;
|
|
129
|
+
long_description: unknown;
|
|
130
|
+
seo_title: string | null;
|
|
131
|
+
seo_description: string | null;
|
|
132
|
+
updated_at: string | null;
|
|
133
|
+
}
|
|
134
|
+
export interface CatalogProductPage {
|
|
135
|
+
items: CatalogProductContent[];
|
|
136
|
+
hasMore: boolean;
|
|
137
|
+
nextOffset: number | null;
|
|
138
|
+
}
|
|
139
|
+
export interface CatalogManagementConfig {
|
|
140
|
+
apiUrl: string;
|
|
141
|
+
accessToken: string | (() => string | Promise<string>);
|
|
142
|
+
fetch?: typeof fetch;
|
|
143
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// libs/sdk/src/catalog-management.ts
|
|
21
|
+
var catalog_management_exports = {};
|
|
22
|
+
__export(catalog_management_exports, {
|
|
23
|
+
CatalogManagementError: () => CatalogManagementError,
|
|
24
|
+
createCatalogManagementClient: () => createCatalogManagementClient
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(catalog_management_exports);
|
|
27
|
+
var CatalogManagementError = class extends Error {
|
|
28
|
+
constructor(code, status, message, details) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.details = details;
|
|
33
|
+
this.name = "CatalogManagementError";
|
|
34
|
+
}
|
|
35
|
+
code;
|
|
36
|
+
status;
|
|
37
|
+
details;
|
|
38
|
+
};
|
|
39
|
+
function createCatalogManagementClient(config) {
|
|
40
|
+
const base = config.apiUrl.trim().replace(/\/+$/, "");
|
|
41
|
+
const url = new URL(base);
|
|
42
|
+
if (url.username || url.password || url.search || url.hash || !(url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
43
|
+
throw new CatalogManagementError(
|
|
44
|
+
"CATALOG_CONFIG_INVALID",
|
|
45
|
+
0,
|
|
46
|
+
"Usa HTTPS o localhost para el acceso temporal."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const fetcher = config.fetch ?? globalThis.fetch;
|
|
50
|
+
async function request(path, input) {
|
|
51
|
+
const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
|
|
52
|
+
if (!token.trim())
|
|
53
|
+
throw new CatalogManagementError(
|
|
54
|
+
"CATALOG_ACCESS_INVALID",
|
|
55
|
+
401,
|
|
56
|
+
"Falta el acceso temporal."
|
|
57
|
+
);
|
|
58
|
+
const response = await fetcher(`${base}/api/v1/catalog-management${path}`, {
|
|
59
|
+
method: input ? "POST" : "GET",
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${token.trim()}`,
|
|
62
|
+
Accept: "application/json",
|
|
63
|
+
...input ? {
|
|
64
|
+
"Content-Type": input.bytes ? "application/octet-stream" : "application/json"
|
|
65
|
+
} : {}
|
|
66
|
+
},
|
|
67
|
+
...input ? { body: input.bytes ?? JSON.stringify(input.json) } : {},
|
|
68
|
+
cache: "no-store",
|
|
69
|
+
redirect: "error",
|
|
70
|
+
credentials: "omit"
|
|
71
|
+
});
|
|
72
|
+
const payload = await response.json().catch(() => ({}));
|
|
73
|
+
if (!response.ok || payload.success !== true || payload.data === void 0) {
|
|
74
|
+
throw new CatalogManagementError(
|
|
75
|
+
payload.error?.code ?? "CATALOG_REQUEST_FAILED",
|
|
76
|
+
response.status,
|
|
77
|
+
payload.error?.message ?? payload.message ?? "No fue posible consultar el cat\xE1logo.",
|
|
78
|
+
payload.error?.details
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return payload.data;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
createBatch: (input) => request("/batches", { json: input }),
|
|
85
|
+
getBatch: (batchId, input = {}) => {
|
|
86
|
+
const query = new URLSearchParams();
|
|
87
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
88
|
+
if (input.after !== void 0) query.set("after", input.after);
|
|
89
|
+
return request(
|
|
90
|
+
`/batches/${encodeURIComponent(batchId)}${query.size ? `?${query}` : ""}`
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
getDraft: (batchId, itemId) => request(
|
|
94
|
+
`/batches/${encodeURIComponent(batchId)}/items/${encodeURIComponent(itemId)}`
|
|
95
|
+
),
|
|
96
|
+
sendProposals: (batchId, items) => request(
|
|
97
|
+
`/batches/${encodeURIComponent(batchId)}/items`,
|
|
98
|
+
{ json: { items } }
|
|
99
|
+
),
|
|
100
|
+
uploadImage: (input) => {
|
|
101
|
+
const query = new URLSearchParams({
|
|
102
|
+
source_key: input.source_key,
|
|
103
|
+
expected_revision: String(input.expected_revision),
|
|
104
|
+
request_id: input.request_id
|
|
105
|
+
});
|
|
106
|
+
return request(
|
|
107
|
+
`/batches/${encodeURIComponent(input.batchId)}/items/${encodeURIComponent(input.itemId)}/images?${query}`,
|
|
108
|
+
{ bytes: input.bytes }
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
getContext: () => request("/context"),
|
|
112
|
+
getReferences: () => request("/references"),
|
|
113
|
+
getTaxonomy: (category) => request(
|
|
114
|
+
`/references/taxonomy?category=${encodeURIComponent(category)}`
|
|
115
|
+
),
|
|
116
|
+
listProducts: (input = {}) => {
|
|
117
|
+
const query = new URLSearchParams();
|
|
118
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
119
|
+
if (input.offset !== void 0) query.set("offset", String(input.offset));
|
|
120
|
+
return request(
|
|
121
|
+
`/products${query.size ? `?${query}` : ""}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CatalogManagementConfig, CatalogManagementContext, CatalogProductPage, CatalogReferences, CatalogTaxonomy } from "./catalog-management-types";
|
|
2
|
+
export * from "./catalog-draft-types";
|
|
3
|
+
export * from "./catalog-management-types";
|
|
4
|
+
import type { CatalogDraftBatch, CatalogDraftBatchPage, CatalogDraftChunkResult, CatalogDraftImageResult, CatalogDraftResult, CatalogDraftWrite } from "./catalog-draft-types";
|
|
5
|
+
export declare class CatalogManagementError extends Error {
|
|
6
|
+
readonly code: string;
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly details?: unknown | undefined;
|
|
9
|
+
constructor(code: string, status: number, message: string, details?: unknown | undefined);
|
|
10
|
+
}
|
|
11
|
+
export declare function createCatalogManagementClient(config: CatalogManagementConfig): {
|
|
12
|
+
createBatch: (input: {
|
|
13
|
+
request_id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}) => Promise<CatalogDraftBatch>;
|
|
16
|
+
getBatch: (batchId: string, input?: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
after?: string;
|
|
19
|
+
}) => Promise<CatalogDraftBatchPage>;
|
|
20
|
+
getDraft: (batchId: string, itemId: string) => Promise<CatalogDraftResult>;
|
|
21
|
+
sendProposals: (batchId: string, items: CatalogDraftWrite[]) => Promise<CatalogDraftChunkResult>;
|
|
22
|
+
uploadImage: (input: {
|
|
23
|
+
batchId: string;
|
|
24
|
+
itemId: string;
|
|
25
|
+
source_key: string;
|
|
26
|
+
expected_revision: number;
|
|
27
|
+
request_id: string;
|
|
28
|
+
bytes: Blob;
|
|
29
|
+
}) => Promise<CatalogDraftImageResult>;
|
|
30
|
+
getContext: () => Promise<CatalogManagementContext>;
|
|
31
|
+
getReferences: () => Promise<CatalogReferences>;
|
|
32
|
+
getTaxonomy: (category: string) => Promise<CatalogTaxonomy>;
|
|
33
|
+
listProducts: (input?: {
|
|
34
|
+
limit?: number;
|
|
35
|
+
offset?: number;
|
|
36
|
+
}) => Promise<CatalogProductPage>;
|
|
37
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// libs/sdk/src/catalog-management.ts
|
|
2
|
+
var CatalogManagementError = class extends Error {
|
|
3
|
+
constructor(code, status, message, details) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.details = details;
|
|
8
|
+
this.name = "CatalogManagementError";
|
|
9
|
+
}
|
|
10
|
+
code;
|
|
11
|
+
status;
|
|
12
|
+
details;
|
|
13
|
+
};
|
|
14
|
+
function createCatalogManagementClient(config) {
|
|
15
|
+
const base = config.apiUrl.trim().replace(/\/+$/, "");
|
|
16
|
+
const url = new URL(base);
|
|
17
|
+
if (url.username || url.password || url.search || url.hash || !(url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
|
|
18
|
+
throw new CatalogManagementError(
|
|
19
|
+
"CATALOG_CONFIG_INVALID",
|
|
20
|
+
0,
|
|
21
|
+
"Usa HTTPS o localhost para el acceso temporal."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const fetcher = config.fetch ?? globalThis.fetch;
|
|
25
|
+
async function request(path, input) {
|
|
26
|
+
const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
|
|
27
|
+
if (!token.trim())
|
|
28
|
+
throw new CatalogManagementError(
|
|
29
|
+
"CATALOG_ACCESS_INVALID",
|
|
30
|
+
401,
|
|
31
|
+
"Falta el acceso temporal."
|
|
32
|
+
);
|
|
33
|
+
const response = await fetcher(`${base}/api/v1/catalog-management${path}`, {
|
|
34
|
+
method: input ? "POST" : "GET",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${token.trim()}`,
|
|
37
|
+
Accept: "application/json",
|
|
38
|
+
...input ? {
|
|
39
|
+
"Content-Type": input.bytes ? "application/octet-stream" : "application/json"
|
|
40
|
+
} : {}
|
|
41
|
+
},
|
|
42
|
+
...input ? { body: input.bytes ?? JSON.stringify(input.json) } : {},
|
|
43
|
+
cache: "no-store",
|
|
44
|
+
redirect: "error",
|
|
45
|
+
credentials: "omit"
|
|
46
|
+
});
|
|
47
|
+
const payload = await response.json().catch(() => ({}));
|
|
48
|
+
if (!response.ok || payload.success !== true || payload.data === void 0) {
|
|
49
|
+
throw new CatalogManagementError(
|
|
50
|
+
payload.error?.code ?? "CATALOG_REQUEST_FAILED",
|
|
51
|
+
response.status,
|
|
52
|
+
payload.error?.message ?? payload.message ?? "No fue posible consultar el cat\xE1logo.",
|
|
53
|
+
payload.error?.details
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return payload.data;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
createBatch: (input) => request("/batches", { json: input }),
|
|
60
|
+
getBatch: (batchId, input = {}) => {
|
|
61
|
+
const query = new URLSearchParams();
|
|
62
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
63
|
+
if (input.after !== void 0) query.set("after", input.after);
|
|
64
|
+
return request(
|
|
65
|
+
`/batches/${encodeURIComponent(batchId)}${query.size ? `?${query}` : ""}`
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
getDraft: (batchId, itemId) => request(
|
|
69
|
+
`/batches/${encodeURIComponent(batchId)}/items/${encodeURIComponent(itemId)}`
|
|
70
|
+
),
|
|
71
|
+
sendProposals: (batchId, items) => request(
|
|
72
|
+
`/batches/${encodeURIComponent(batchId)}/items`,
|
|
73
|
+
{ json: { items } }
|
|
74
|
+
),
|
|
75
|
+
uploadImage: (input) => {
|
|
76
|
+
const query = new URLSearchParams({
|
|
77
|
+
source_key: input.source_key,
|
|
78
|
+
expected_revision: String(input.expected_revision),
|
|
79
|
+
request_id: input.request_id
|
|
80
|
+
});
|
|
81
|
+
return request(
|
|
82
|
+
`/batches/${encodeURIComponent(input.batchId)}/items/${encodeURIComponent(input.itemId)}/images?${query}`,
|
|
83
|
+
{ bytes: input.bytes }
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
getContext: () => request("/context"),
|
|
87
|
+
getReferences: () => request("/references"),
|
|
88
|
+
getTaxonomy: (category) => request(
|
|
89
|
+
`/references/taxonomy?category=${encodeURIComponent(category)}`
|
|
90
|
+
),
|
|
91
|
+
listProducts: (input = {}) => {
|
|
92
|
+
const query = new URLSearchParams();
|
|
93
|
+
if (input.limit !== void 0) query.set("limit", String(input.limit));
|
|
94
|
+
if (input.offset !== void 0) query.set("offset", String(input.offset));
|
|
95
|
+
return request(
|
|
96
|
+
`/products${query.size ? `?${query}` : ""}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
CatalogManagementError,
|
|
103
|
+
createCatalogManagementClient
|
|
104
|
+
};
|
package/index.cjs
CHANGED
|
@@ -3338,6 +3338,78 @@ function hasRecoverableCartSessionError(errors) {
|
|
|
3338
3338
|
);
|
|
3339
3339
|
}
|
|
3340
3340
|
|
|
3341
|
+
// libs/sdk/src/modules/checkout-contact.ts
|
|
3342
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3343
|
+
var PHONE_PATTERN = /^\+?\d{7,15}$/;
|
|
3344
|
+
function validEmail(value) {
|
|
3345
|
+
if (value.length > 320 || !EMAIL_PATTERN.test(value)) return false;
|
|
3346
|
+
const [local, domain] = value.split("@");
|
|
3347
|
+
if (!local || !domain || local.length > 64 || domain.length > 255 || local.startsWith(".") || local.endsWith(".") || local.includes("..")) {
|
|
3348
|
+
return false;
|
|
3349
|
+
}
|
|
3350
|
+
return domain.split(".").every((label) => /^[a-z\d](?:[a-z\d-]*[a-z\d])?$/i.test(label));
|
|
3351
|
+
}
|
|
3352
|
+
function normalizePhone(value) {
|
|
3353
|
+
return value.trim().replace(/[\s().-]/g, "");
|
|
3354
|
+
}
|
|
3355
|
+
function invalidContact(field) {
|
|
3356
|
+
const messages = {
|
|
3357
|
+
email: "Revisa el correo electr\xF3nico: debe estar completo y sin espacios.",
|
|
3358
|
+
phone: "Revisa el n\xFAmero de tel\xE9fono: usa entre 7 y 15 d\xEDgitos.",
|
|
3359
|
+
contact: "Ingresa un correo electr\xF3nico o un n\xFAmero de tel\xE9fono v\xE1lido."
|
|
3360
|
+
};
|
|
3361
|
+
return new WebsiteError(
|
|
3362
|
+
messages[field],
|
|
3363
|
+
`INVALID_CHECKOUT_${field.toUpperCase()}`
|
|
3364
|
+
);
|
|
3365
|
+
}
|
|
3366
|
+
function normalizeCheckoutContact(contact, mode) {
|
|
3367
|
+
const email = contact.email?.trim() || void 0;
|
|
3368
|
+
const phone = contact.phone ? normalizePhone(contact.phone) : void 0;
|
|
3369
|
+
const cellphone = contact.cellphone ? normalizePhone(contact.cellphone) : void 0;
|
|
3370
|
+
const emailValid = !email || validEmail(email);
|
|
3371
|
+
const phoneValid = !phone || PHONE_PATTERN.test(phone);
|
|
3372
|
+
const cellphoneValid = !cellphone || PHONE_PATTERN.test(cellphone);
|
|
3373
|
+
if (mode === "submit") {
|
|
3374
|
+
if (!emailValid) throw invalidContact("email");
|
|
3375
|
+
if (!phoneValid || !cellphoneValid) throw invalidContact("phone");
|
|
3376
|
+
}
|
|
3377
|
+
const normalized = {
|
|
3378
|
+
...contact,
|
|
3379
|
+
name: contact.name?.trim() || void 0,
|
|
3380
|
+
firstName: contact.firstName?.trim() || void 0,
|
|
3381
|
+
lastName: contact.lastName?.trim() || void 0,
|
|
3382
|
+
email: emailValid ? email : void 0,
|
|
3383
|
+
phone: phoneValid ? phone : void 0,
|
|
3384
|
+
cellphone: cellphoneValid ? cellphone : void 0
|
|
3385
|
+
};
|
|
3386
|
+
if ((normalized.name?.length ?? 0) > 160 || (normalized.firstName?.length ?? 0) > 80 || (normalized.lastName?.length ?? 0) > 80) {
|
|
3387
|
+
throw new WebsiteError(
|
|
3388
|
+
"Revisa los datos de contacto: el nombre es demasiado largo.",
|
|
3389
|
+
"INVALID_CHECKOUT_CONTACT"
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
if (!normalized.email && !normalized.phone && !normalized.cellphone) {
|
|
3393
|
+
throw invalidContact(
|
|
3394
|
+
!emailValid ? "email" : !phoneValid || !cellphoneValid ? "phone" : "contact"
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
return normalized;
|
|
3398
|
+
}
|
|
3399
|
+
function explainCheckoutRequestError(error) {
|
|
3400
|
+
if (error instanceof GraphQLRequestError && error.graphqlErrors.some(
|
|
3401
|
+
(item) => /invalid request payload|hay un error en los datos del pedido/i.test(
|
|
3402
|
+
item.message
|
|
3403
|
+
)
|
|
3404
|
+
)) {
|
|
3405
|
+
throw new WebsiteError(
|
|
3406
|
+
"Hay un error en los datos del pedido. Rev\xEDsalos e intenta de nuevo.",
|
|
3407
|
+
"INVALID_CHECKOUT_DATA"
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
throw error;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3341
3413
|
// libs/sdk/src/modules/cart.ts
|
|
3342
3414
|
function mapCartItem(raw) {
|
|
3343
3415
|
return {
|
|
@@ -3413,6 +3485,15 @@ function requireCartSession(storage) {
|
|
|
3413
3485
|
return session;
|
|
3414
3486
|
}
|
|
3415
3487
|
function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
|
|
3488
|
+
let pendingCartSessionOperation = Promise.resolve();
|
|
3489
|
+
function enqueueCartSessionOperation(run) {
|
|
3490
|
+
const operation = pendingCartSessionOperation.then(run, run);
|
|
3491
|
+
pendingCartSessionOperation = operation.then(
|
|
3492
|
+
() => void 0,
|
|
3493
|
+
() => void 0
|
|
3494
|
+
);
|
|
3495
|
+
return operation;
|
|
3496
|
+
}
|
|
3416
3497
|
async function fetchCart(orderId, cartToken) {
|
|
3417
3498
|
const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
|
|
3418
3499
|
const result = data.websiteGetCart;
|
|
@@ -3435,72 +3516,80 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
|
|
|
3435
3516
|
}
|
|
3436
3517
|
return {
|
|
3437
3518
|
async getOrCreate(contact, fulfillmentFamily) {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
CREATE_CART,
|
|
3449
|
-
{
|
|
3450
|
-
websiteId: ctx.websiteId,
|
|
3451
|
-
cartToken: existing?.cartToken,
|
|
3452
|
-
contact,
|
|
3453
|
-
fulfillmentFamily
|
|
3454
|
-
},
|
|
3455
|
-
getHeaders
|
|
3456
|
-
);
|
|
3457
|
-
const result = data.websiteCreateCart;
|
|
3458
|
-
assertActionSuccess(result, "CART_CREATE_ERROR");
|
|
3459
|
-
cartSessionStorage.set({
|
|
3460
|
-
orderId: result.order_id,
|
|
3461
|
-
cartToken: result.cart_token
|
|
3462
|
-
});
|
|
3463
|
-
return mapCart(result.cart, result.cart_token);
|
|
3464
|
-
},
|
|
3465
|
-
async startCheckout(input) {
|
|
3466
|
-
const ctx = getContext();
|
|
3467
|
-
if (!ctx.chainId) {
|
|
3468
|
-
throw new WebsiteError(
|
|
3469
|
-
"This website has no chain configured. Checkout requires a chain_id.",
|
|
3470
|
-
"NO_CHAIN"
|
|
3471
|
-
);
|
|
3472
|
-
}
|
|
3473
|
-
const requestStartCheckout = async (cartToken) => {
|
|
3519
|
+
return enqueueCartSessionOperation(async () => {
|
|
3520
|
+
const normalizedContact = contact ? normalizeCheckoutContact(contact, "draft") : void 0;
|
|
3521
|
+
const ctx = getContext();
|
|
3522
|
+
if (!ctx.chainId) {
|
|
3523
|
+
throw new WebsiteError(
|
|
3524
|
+
"This website has no chain configured. Cart requires a chain_id.",
|
|
3525
|
+
"NO_CHAIN"
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
const existing = cartSessionStorage.get();
|
|
3474
3529
|
const data = await executeGraphQL(
|
|
3475
3530
|
graphqlUrl,
|
|
3476
|
-
|
|
3531
|
+
CREATE_CART,
|
|
3477
3532
|
{
|
|
3478
3533
|
websiteId: ctx.websiteId,
|
|
3479
|
-
cartToken,
|
|
3480
|
-
contact:
|
|
3481
|
-
fulfillmentFamily
|
|
3482
|
-
items: input.items.map((item) => ({
|
|
3483
|
-
product_variant_id: item.productVariantId,
|
|
3484
|
-
quantity: item.quantity
|
|
3485
|
-
})),
|
|
3486
|
-
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
|
|
3534
|
+
cartToken: existing?.cartToken,
|
|
3535
|
+
contact: normalizedContact,
|
|
3536
|
+
fulfillmentFamily
|
|
3487
3537
|
},
|
|
3488
3538
|
getHeaders
|
|
3489
|
-
);
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
result
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3539
|
+
).catch(explainCheckoutRequestError);
|
|
3540
|
+
const result = data.websiteCreateCart;
|
|
3541
|
+
assertActionSuccess(result, "CART_CREATE_ERROR");
|
|
3542
|
+
cartSessionStorage.set({
|
|
3543
|
+
orderId: result.order_id,
|
|
3544
|
+
cartToken: result.cart_token
|
|
3545
|
+
});
|
|
3546
|
+
return mapCart(result.cart, result.cart_token);
|
|
3547
|
+
});
|
|
3548
|
+
},
|
|
3549
|
+
async startCheckout(input) {
|
|
3550
|
+
return enqueueCartSessionOperation(async () => {
|
|
3551
|
+
const contact = normalizeCheckoutContact(input.contact, "draft");
|
|
3552
|
+
const ctx = getContext();
|
|
3553
|
+
if (!ctx.chainId) {
|
|
3554
|
+
throw new WebsiteError(
|
|
3555
|
+
"This website has no chain configured. Checkout requires a chain_id.",
|
|
3556
|
+
"NO_CHAIN"
|
|
3557
|
+
);
|
|
3558
|
+
}
|
|
3559
|
+
const requestStartCheckout = async (cartToken) => {
|
|
3560
|
+
const data = await executeGraphQL(
|
|
3561
|
+
graphqlUrl,
|
|
3562
|
+
START_GUEST_CHECKOUT,
|
|
3563
|
+
{
|
|
3564
|
+
websiteId: ctx.websiteId,
|
|
3565
|
+
cartToken,
|
|
3566
|
+
contact,
|
|
3567
|
+
fulfillmentFamily: input.fulfillmentFamily,
|
|
3568
|
+
items: input.items.map((item) => ({
|
|
3569
|
+
product_variant_id: item.productVariantId,
|
|
3570
|
+
quantity: item.quantity
|
|
3571
|
+
})),
|
|
3572
|
+
marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(
|
|
3573
|
+
input.marketingAttribution
|
|
3574
|
+
) : void 0
|
|
3575
|
+
},
|
|
3576
|
+
getHeaders
|
|
3577
|
+
).catch(explainCheckoutRequestError);
|
|
3578
|
+
return data.websiteStartGuestCheckout;
|
|
3579
|
+
};
|
|
3580
|
+
const existing = cartSessionStorage.get();
|
|
3581
|
+
let result = await requestStartCheckout(existing?.cartToken);
|
|
3582
|
+
if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
|
|
3583
|
+
cartSessionStorage.clear();
|
|
3584
|
+
result = await requestStartCheckout();
|
|
3585
|
+
}
|
|
3586
|
+
assertActionSuccess(result, "CHECKOUT_START_ERROR");
|
|
3587
|
+
cartSessionStorage.set({
|
|
3588
|
+
orderId: result.order_id,
|
|
3589
|
+
cartToken: result.cart_token
|
|
3590
|
+
});
|
|
3591
|
+
return mapCart(result.cart, result.cart_token);
|
|
3502
3592
|
});
|
|
3503
|
-
return mapCart(result.cart, result.cart_token);
|
|
3504
3593
|
},
|
|
3505
3594
|
async get() {
|
|
3506
3595
|
const session = cartSessionStorage.get();
|
|
@@ -5599,6 +5688,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
5599
5688
|
return mapCouponResult(data.websiteRemoveCoupon);
|
|
5600
5689
|
},
|
|
5601
5690
|
async submit(input) {
|
|
5691
|
+
const contact = normalizeCheckoutContact(input.contact, "submit");
|
|
5602
5692
|
const session = requireCartSession2(cartSessionStorage);
|
|
5603
5693
|
const data = await executeGraphQL(
|
|
5604
5694
|
graphqlUrl,
|
|
@@ -5606,7 +5696,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
5606
5696
|
{
|
|
5607
5697
|
orderId: session.orderId,
|
|
5608
5698
|
cartToken: session.cartToken,
|
|
5609
|
-
contact
|
|
5699
|
+
contact,
|
|
5610
5700
|
fulfillmentFamily: input.fulfillmentFamily,
|
|
5611
5701
|
address: input.address,
|
|
5612
5702
|
shippingQuoteId: input.shippingQuoteId,
|
|
@@ -5626,7 +5716,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
5626
5716
|
} : void 0
|
|
5627
5717
|
},
|
|
5628
5718
|
getHeaders
|
|
5629
|
-
);
|
|
5719
|
+
).catch(explainCheckoutRequestError);
|
|
5630
5720
|
const result = data.websiteCheckout;
|
|
5631
5721
|
if (!result.success && result.errors && result.errors.length > 0) {
|
|
5632
5722
|
throw new CheckoutError(result.errors);
|
|
@@ -5667,7 +5757,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
5667
5757
|
returnUrl: input.returnUrl
|
|
5668
5758
|
},
|
|
5669
5759
|
getHeaders
|
|
5670
|
-
);
|
|
5760
|
+
).catch(explainCheckoutRequestError);
|
|
5671
5761
|
const result = data.websiteCreatePaymentSession;
|
|
5672
5762
|
if (result.success && result.payment_session_id && (result.integration_mode !== "redirect" || !result.redirect_url)) {
|
|
5673
5763
|
throw new WebsiteError(
|
|
@@ -5702,7 +5792,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
|
|
|
5702
5792
|
gatewayTransactionId: input.gatewayTransactionId
|
|
5703
5793
|
},
|
|
5704
5794
|
getHeaders
|
|
5705
|
-
);
|
|
5795
|
+
).catch(explainCheckoutRequestError);
|
|
5706
5796
|
const result = data.websiteReconcilePaymentSession;
|
|
5707
5797
|
return {
|
|
5708
5798
|
success: result.success,
|