@monkeyplus/payscope 1.0.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/dist/THIRD-PARTY-LICENSES.md +41 -0
- package/dist/_chunks/auth.d.mts +707 -0
- package/dist/_chunks/database.mjs +831 -0
- package/dist/_chunks/db.d.mts +7100 -0
- package/dist/_chunks/index.d.mts +178 -0
- package/dist/_chunks/lib.mjs +3073 -0
- package/dist/_chunks/libs/better-call.d.mts +478 -0
- package/dist/_chunks/libs/postgres.d.mts +1 -0
- package/dist/_chunks/rolldown-runtime.mjs +11 -0
- package/dist/server/db.d.mts +2 -0
- package/dist/server/db.mjs +108 -0
- package/dist/server/env.d.mts +21 -0
- package/dist/server/env.mjs +22 -0
- package/dist/server/lib.d.mts +362 -0
- package/dist/server/lib.mjs +2 -0
- package/dist/server/router.d.mts +1218 -0
- package/dist/server/router.mjs +1157 -0
- package/dist/server/schemas/auth.d.mts +2 -0
- package/dist/server/schemas/auth.mjs +62 -0
- package/package.json +58 -0
- package/storefront/Readme.md +0 -0
- package/storefront/auth.ts +29 -0
- package/storefront/cart/ResumeCart.vue +217 -0
- package/storefront/cart/ResumeCartSelect.vue +32 -0
- package/storefront/cart/ShoppinCart.vue +100 -0
- package/storefront/cart/ShoppinCartItem.vue +99 -0
- package/storefront/checkout/App.vue +36 -0
- package/storefront/checkout/AppCart.vue +72 -0
- package/storefront/checkout/AppCartDiscount.vue +74 -0
- package/storefront/checkout/AppCartTotals.vue +72 -0
- package/storefront/checkout/AppLoading.vue +55 -0
- package/storefront/checkout/composables.ts +28 -0
- package/storefront/checkout/constants.ts +0 -0
- package/storefront/checkout/main.ts +11 -0
- package/storefront/checkout/pages/Address/Address.vue +95 -0
- package/storefront/checkout/pages/Info/Info.vue +94 -0
- package/storefront/checkout/pages/Info/InfoUser.vue +38 -0
- package/storefront/checkout/pages/Pay/Pay.vue +115 -0
- package/storefront/checkout/pages/Pay/Providers/BancoEconomico/BancoEconomico.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/Cybersource/Cybersource.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/Datafast/Datafast.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/Multipago/Multipago.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/Pagomedios/Pagomedios.vue +93 -0
- package/storefront/checkout/pages/Pay/Providers/Pagomedios/composable.ts +23 -0
- package/storefront/checkout/pages/Pay/Providers/Paypal/Paypal.vue +168 -0
- package/storefront/checkout/pages/Pay/Providers/Paypal/composable.ts +33 -0
- package/storefront/checkout/pages/Pay/Providers/Placetopay/Placetopay.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/Wabi/Wabi.vue +9 -0
- package/storefront/checkout/pages/Pay/Providers/composable.ts +30 -0
- package/storefront/checkout/pages/Payment/Payment.vue +19 -0
- package/storefront/checkout/pages/Payment/PaymentStatus.vue +187 -0
- package/storefront/checkout/pages/Payment/PaymentStatusDetail.vue +77 -0
- package/storefront/checkout/pages/Payment/composable.ts +81 -0
- package/storefront/checkout/pages/Shipping/Shipping.vue +67 -0
- package/storefront/checkout/pages/StepInfo.vue +37 -0
- package/storefront/checkout/router.ts +59 -0
- package/storefront/index.ts +3 -0
- package/storefront/login/App.vue +9 -0
- package/storefront/login/main.ts +10 -0
- package/storefront/login/pages/SignIn/Login.vue +82 -0
- package/storefront/login/pages/SignUp/SignUp.vue +99 -0
- package/storefront/login/router.ts +15 -0
- package/storefront/product/AddProduct.vue +303 -0
- package/storefront/product/AddProductNumber.vue +62 -0
- package/storefront/product/AddProductVariant.vue +66 -0
- package/storefront/profile/App.vue +88 -0
- package/storefront/profile/main.ts +10 -0
- package/storefront/profile/pages/Addresses/Addresses.vue +79 -0
- package/storefront/profile/pages/Addresses/AddressesForm.vue +95 -0
- package/storefront/profile/pages/Addresses/AddressesModal.vue +24 -0
- package/storefront/profile/pages/Buys/Buys.vue +8 -0
- package/storefront/profile/pages/Me/Me.vue +15 -0
- package/storefront/profile/pages/Me/MeBilling.vue +79 -0
- package/storefront/profile/pages/Me/MeBillingForm.vue +66 -0
- package/storefront/profile/pages/Me/MeBillingModal.vue +24 -0
- package/storefront/profile/pages/Me/MeInfo.vue +75 -0
- package/storefront/profile/pages/Me/MePassword.vue +53 -0
- package/storefront/profile/pages/Me/MeSubscriptions.vue +15 -0
- package/storefront/profile/pages/Returns/Returns.vue +8 -0
- package/storefront/profile/pages/Whislist/Whislist.vue +8 -0
- package/storefront/profile/router.ts +32 -0
- package/storefront/stores.ts +320 -0
|
@@ -0,0 +1,1157 @@
|
|
|
1
|
+
import { checkouts, customerAddresses, customerBillings, customerWhislists } from "../_chunks/database.mjs";
|
|
2
|
+
import { GRAPHQL_TOKEN, TASKS_ENDPOINT, URL } from "./env.mjs";
|
|
3
|
+
import { createGraphql, createPayment } from "../_chunks/lib.mjs";
|
|
4
|
+
import { eq } from "drizzle-orm";
|
|
5
|
+
import dayjs from "dayjs";
|
|
6
|
+
import { HTTPError, createError, eventHandler, getCookie, getQuery, getRequestIP, getRouterParam, readBody, redirect, setCookie } from "h3";
|
|
7
|
+
import { joinURL } from "ufo";
|
|
8
|
+
import { defu } from "defu";
|
|
9
|
+
function useUser(event) {
|
|
10
|
+
return event.context.user;
|
|
11
|
+
}
|
|
12
|
+
function registerAccount(app, db, { path, storeId }) {
|
|
13
|
+
app.post(path("/account/billings"), eventHandler(async (event) => {
|
|
14
|
+
const { id: customerId } = useUser(event);
|
|
15
|
+
const data = await readBody(event);
|
|
16
|
+
return await db.insert(customerBillings).values({
|
|
17
|
+
storeId,
|
|
18
|
+
customerId,
|
|
19
|
+
data
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
app.get(path("/account/billings"), eventHandler(async (event) => {
|
|
23
|
+
const { id: customerId } = useUser(event);
|
|
24
|
+
const items = await db.query.customerBillings.findMany({ where: {
|
|
25
|
+
storeId,
|
|
26
|
+
customerId
|
|
27
|
+
} });
|
|
28
|
+
const formatted = (data) => {
|
|
29
|
+
return { formatted: [
|
|
30
|
+
data.identification,
|
|
31
|
+
data.fullname,
|
|
32
|
+
data.email
|
|
33
|
+
].filter((v) => !!v).join("/ ") };
|
|
34
|
+
};
|
|
35
|
+
return items.map((item) => ({
|
|
36
|
+
...item,
|
|
37
|
+
...item.data,
|
|
38
|
+
...formatted(item.data),
|
|
39
|
+
data: void 0
|
|
40
|
+
}));
|
|
41
|
+
}));
|
|
42
|
+
app.patch(path("/account/billings/:id"), eventHandler(async (event) => {
|
|
43
|
+
const id = getRouterParam(event, "id");
|
|
44
|
+
const data = await readBody(event);
|
|
45
|
+
return await db.update(customerBillings).set({ data }).where(eq(customerBillings.id, Number(id))).returning();
|
|
46
|
+
}));
|
|
47
|
+
app.delete(path("/account/billings/:id"), eventHandler(async (event) => {
|
|
48
|
+
const id = getRouterParam(event, "id");
|
|
49
|
+
return await db.delete(customerBillings).where(eq(customerBillings.id, Number(id)));
|
|
50
|
+
}));
|
|
51
|
+
app.post(path("/account/addresses"), eventHandler(async (event) => {
|
|
52
|
+
const { id: customerId } = useUser(event);
|
|
53
|
+
const data = await readBody(event);
|
|
54
|
+
return await db.insert(customerAddresses).values({
|
|
55
|
+
storeId,
|
|
56
|
+
customerId,
|
|
57
|
+
data
|
|
58
|
+
});
|
|
59
|
+
}));
|
|
60
|
+
app.get(path("/account/addresses"), eventHandler(async (event) => {
|
|
61
|
+
const { id: customerId } = useUser(event);
|
|
62
|
+
const items = await db.query.customerAddresses.findMany({ where: {
|
|
63
|
+
storeId,
|
|
64
|
+
customerId
|
|
65
|
+
} });
|
|
66
|
+
const formatted = (data) => {
|
|
67
|
+
return {
|
|
68
|
+
name: [data.firstName, data.lastName].filter((v) => !!v).join(" "),
|
|
69
|
+
formatted: [
|
|
70
|
+
data.address1,
|
|
71
|
+
data.address2,
|
|
72
|
+
data.zip,
|
|
73
|
+
data.city,
|
|
74
|
+
data.country
|
|
75
|
+
].filter((v) => !!v).join(", ")
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
return items.map((item) => ({
|
|
79
|
+
...item,
|
|
80
|
+
...item.data,
|
|
81
|
+
...formatted(item.data),
|
|
82
|
+
data: void 0
|
|
83
|
+
}));
|
|
84
|
+
}));
|
|
85
|
+
app.patch(path("/account/addresses/:id"), eventHandler(async (event) => {
|
|
86
|
+
const id = getRouterParam(event, "id");
|
|
87
|
+
const data = await readBody(event);
|
|
88
|
+
return await db.update(customerAddresses).set({ data }).where(eq(customerAddresses.id, Number(id))).returning();
|
|
89
|
+
}));
|
|
90
|
+
app.delete(path("/account/addresses/:id"), eventHandler(async (event) => {
|
|
91
|
+
const id = getRouterParam(event, "id");
|
|
92
|
+
return await db.delete(customerAddresses).where(eq(customerAddresses.id, Number(id)));
|
|
93
|
+
}));
|
|
94
|
+
app.post(path("/account/wishlist"), eventHandler(async (event) => {
|
|
95
|
+
const { id: customerId } = useUser(event);
|
|
96
|
+
const data = await readBody(event);
|
|
97
|
+
return await db.insert(customerWhislists).values({
|
|
98
|
+
storeId,
|
|
99
|
+
customerId,
|
|
100
|
+
productId: data?.productId,
|
|
101
|
+
data: data?.data
|
|
102
|
+
});
|
|
103
|
+
}));
|
|
104
|
+
app.get(path("/account/wishlist"), eventHandler(async (event) => {
|
|
105
|
+
const { id: customerId } = useUser(event);
|
|
106
|
+
return await db.query.customerWhislists.findMany({ where: {
|
|
107
|
+
storeId,
|
|
108
|
+
customerId
|
|
109
|
+
} });
|
|
110
|
+
}));
|
|
111
|
+
app.patch(path("/account/wishlist/:id"), eventHandler(async (event) => {
|
|
112
|
+
const id = getRouterParam(event, "id");
|
|
113
|
+
const data = await readBody(event);
|
|
114
|
+
return await db.update(customerWhislists).set({ data }).where(eq(customerWhislists.id, Number(id))).returning();
|
|
115
|
+
}));
|
|
116
|
+
app.delete(path("/account/wishlist/:id"), eventHandler(async (event) => {
|
|
117
|
+
const id = getRouterParam(event, "id");
|
|
118
|
+
return await db.delete(customerWhislists).where(eq(customerWhislists.id, Number(id)));
|
|
119
|
+
}));
|
|
120
|
+
app.get(path("/account/returns"), eventHandler(async (event) => {
|
|
121
|
+
const { id: customerId } = useUser(event);
|
|
122
|
+
return await db.query.customerReturns.findMany({ where: {
|
|
123
|
+
storeId,
|
|
124
|
+
customerId
|
|
125
|
+
} });
|
|
126
|
+
}));
|
|
127
|
+
app.get(path("/account/orders"), eventHandler(async (event) => {
|
|
128
|
+
const { id: customerId } = useUser(event);
|
|
129
|
+
return await db.query.orders.findMany({ where: {
|
|
130
|
+
storeId,
|
|
131
|
+
customerId
|
|
132
|
+
} });
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
function useGuardSession(auth, options) {
|
|
136
|
+
return eventHandler(async (event) => {
|
|
137
|
+
console.log(`${event.method}::${event.path}`);
|
|
138
|
+
if (options.pathsToExclude.some((p) => event.path.startsWith(p))) return;
|
|
139
|
+
const session = await auth.api.getSession({ headers: event.req.headers });
|
|
140
|
+
if (!session?.user) return createError({
|
|
141
|
+
statusCode: 401,
|
|
142
|
+
statusMessage: "Unauthorized"
|
|
143
|
+
});
|
|
144
|
+
event.context.user = session.user;
|
|
145
|
+
event.context.session = session.session;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function registerAuthMiddleware(app, options) {
|
|
149
|
+
app.use(options.guard?.path || "/**", useGuardSession(options.auth, { pathsToExclude: [
|
|
150
|
+
"/api/auth/",
|
|
151
|
+
"/api/session",
|
|
152
|
+
...options.guard?.excludePaths || []
|
|
153
|
+
] }));
|
|
154
|
+
}
|
|
155
|
+
function registerAuth(app, auth) {
|
|
156
|
+
app.use("/api/auth/**", eventHandler(async (event) => {
|
|
157
|
+
const r = await auth.handler(event.req);
|
|
158
|
+
if (r.status === 302) return redirect(r.headers.get("location"));
|
|
159
|
+
r.headers.forEach((value, key) => {
|
|
160
|
+
event.res.errHeaders.set(key, value);
|
|
161
|
+
});
|
|
162
|
+
return r;
|
|
163
|
+
}));
|
|
164
|
+
app.get("/api/session/me", eventHandler(async (event) => {
|
|
165
|
+
return await auth.api.getSession({ headers: event.headers });
|
|
166
|
+
}));
|
|
167
|
+
}
|
|
168
|
+
function getTax(store) {
|
|
169
|
+
const tax = store?.storeTaxes?.default;
|
|
170
|
+
const taxes = store.country.taxesList;
|
|
171
|
+
const def = taxes.find((e) => String(e.id) === tax);
|
|
172
|
+
const def0 = taxes.find((e) => +e.value === 0);
|
|
173
|
+
return def || def0;
|
|
174
|
+
}
|
|
175
|
+
function normalizeProduct(opts) {
|
|
176
|
+
const calcPrice = (price, discount) => {
|
|
177
|
+
const p = +price;
|
|
178
|
+
return (p - p * +discount / 100).toFixed(2);
|
|
179
|
+
};
|
|
180
|
+
const theTax = getTax(opts.store);
|
|
181
|
+
const taxIncluded = opts.store?.storeTaxes?.included;
|
|
182
|
+
const getTaxPercentage = () => {
|
|
183
|
+
return Number(theTax?.value || 0);
|
|
184
|
+
};
|
|
185
|
+
const priceWithTaxt = (price = 0) => {
|
|
186
|
+
price = +price;
|
|
187
|
+
const tax = getTaxPercentage();
|
|
188
|
+
if (taxIncluded) return price.toFixed(2);
|
|
189
|
+
return (price + price * tax / 100).toFixed(2);
|
|
190
|
+
};
|
|
191
|
+
return (product) => {
|
|
192
|
+
const image = product?.images?.[0];
|
|
193
|
+
const discount = product?.discount || 0;
|
|
194
|
+
const options = product?.variants?.options?.map((option) => {
|
|
195
|
+
return {
|
|
196
|
+
id: option?.option?.name?.toLowerCase(),
|
|
197
|
+
name: option?.option?.name?.toLowerCase(),
|
|
198
|
+
values: option.values,
|
|
199
|
+
type: option?.option?.select
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
const variants = product.variants?.variants?.map((variant) => {
|
|
203
|
+
const imageVariant = variant?.images?.[0];
|
|
204
|
+
let price = variant.price || product.price;
|
|
205
|
+
if (discount) price = calcPrice(price, discount);
|
|
206
|
+
return {
|
|
207
|
+
id: variant.id,
|
|
208
|
+
productId: product.id,
|
|
209
|
+
sku: variant?.sku || product?.sku || "",
|
|
210
|
+
image: imageVariant || image,
|
|
211
|
+
price,
|
|
212
|
+
showPrice: priceWithTaxt(price),
|
|
213
|
+
priceWithTax: priceWithTaxt(price),
|
|
214
|
+
comparedPrice: product.comparedPrice,
|
|
215
|
+
title: product.title,
|
|
216
|
+
available: true,
|
|
217
|
+
selectedOptions: Object.entries(variant?.options || {}).map(([key, value]) => {
|
|
218
|
+
return {
|
|
219
|
+
name: key,
|
|
220
|
+
value
|
|
221
|
+
};
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
let price = product.price;
|
|
226
|
+
if (discount) price = calcPrice(price, discount);
|
|
227
|
+
const taxes = product.taxes?.length ? product.taxes.map((tax) => {
|
|
228
|
+
return {
|
|
229
|
+
...tax.tax,
|
|
230
|
+
included: tax.included
|
|
231
|
+
};
|
|
232
|
+
}) : theTax ? [{
|
|
233
|
+
...theTax,
|
|
234
|
+
included: taxIncluded
|
|
235
|
+
}] : [];
|
|
236
|
+
return {
|
|
237
|
+
...product,
|
|
238
|
+
price,
|
|
239
|
+
showPrice: priceWithTaxt(price),
|
|
240
|
+
priceWithTax: priceWithTaxt(price),
|
|
241
|
+
comparedPrice: product.comparedPrice || priceWithTaxt(+(product.price || 0)),
|
|
242
|
+
image,
|
|
243
|
+
taxes,
|
|
244
|
+
variants: variants || [{
|
|
245
|
+
id: product.sku || product.id,
|
|
246
|
+
productId: product.id,
|
|
247
|
+
comparedPrice: product.comparedPrice,
|
|
248
|
+
priceWithTax: priceWithTaxt(price),
|
|
249
|
+
image,
|
|
250
|
+
price: product.price,
|
|
251
|
+
selectedOptions: [{
|
|
252
|
+
name: product.sku ? "sku" : "item",
|
|
253
|
+
value: product.sku ? String(product.sku) : String(product.id)
|
|
254
|
+
}],
|
|
255
|
+
title: product.title,
|
|
256
|
+
available: true
|
|
257
|
+
}],
|
|
258
|
+
noVariants: !variants?.length,
|
|
259
|
+
options: options || [{
|
|
260
|
+
id: "1",
|
|
261
|
+
name: product.sku ? "sku" : "item",
|
|
262
|
+
values: [product.sku ? String(product.sku) : String(product.id)]
|
|
263
|
+
}]
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const cache = /* @__PURE__ */ new Map();
|
|
268
|
+
function useProducts(db) {
|
|
269
|
+
const getStore = async (storeId) => {
|
|
270
|
+
const _cache = cache.get(`store:${storeId}`);
|
|
271
|
+
if (_cache && _cache.expires > Date.now()) return _cache.data;
|
|
272
|
+
const store = await db.query.stores.findFirst({
|
|
273
|
+
where: { id: storeId },
|
|
274
|
+
with: { country: { with: { taxesList: { with: { type: true } } } } }
|
|
275
|
+
});
|
|
276
|
+
cache.set(`store:${storeId}`, {
|
|
277
|
+
expires: Date.now() + 3600 * 1e3,
|
|
278
|
+
data: store
|
|
279
|
+
});
|
|
280
|
+
return store;
|
|
281
|
+
};
|
|
282
|
+
const getProducts = async (storeId) => {
|
|
283
|
+
const _cache = cache.get(`products:${storeId}`);
|
|
284
|
+
if (_cache && _cache.expires > Date.now()) return _cache.data;
|
|
285
|
+
const items = await db.query.products.findMany({
|
|
286
|
+
limit: 1e3,
|
|
287
|
+
columns: {
|
|
288
|
+
id: true,
|
|
289
|
+
title: true,
|
|
290
|
+
variants: true,
|
|
291
|
+
extras: true,
|
|
292
|
+
discount: true
|
|
293
|
+
},
|
|
294
|
+
with: { taxes: {
|
|
295
|
+
columns: {
|
|
296
|
+
id: true,
|
|
297
|
+
included: true
|
|
298
|
+
},
|
|
299
|
+
with: { tax: {
|
|
300
|
+
columns: {
|
|
301
|
+
id: true,
|
|
302
|
+
code: true,
|
|
303
|
+
value: true
|
|
304
|
+
},
|
|
305
|
+
with: { type: { columns: {
|
|
306
|
+
code: true,
|
|
307
|
+
name: true
|
|
308
|
+
} } }
|
|
309
|
+
} }
|
|
310
|
+
} }
|
|
311
|
+
});
|
|
312
|
+
cache.set(`products:${storeId}`, {
|
|
313
|
+
expires: Date.now() + 3600 * 1e3,
|
|
314
|
+
data: items
|
|
315
|
+
});
|
|
316
|
+
return items;
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
getProducts: async (storeId) => {
|
|
320
|
+
const store = await getStore(storeId);
|
|
321
|
+
return (await getProducts(storeId)).map((p) => normalizeProduct({ store })(p));
|
|
322
|
+
},
|
|
323
|
+
getStore
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function registerCache(app, db, { path, storeId }) {
|
|
327
|
+
const { getProducts } = useProducts(db);
|
|
328
|
+
app.get(path("/cache/products"), eventHandler(async () => {
|
|
329
|
+
return await getProducts(storeId);
|
|
330
|
+
}));
|
|
331
|
+
app.get(path("/cache/products/:id/stock"), eventHandler(async (event) => {
|
|
332
|
+
const productId = getRouterParam(event, "id");
|
|
333
|
+
const items = await db.query.productStocks.findMany({ where: {
|
|
334
|
+
productId: +productId,
|
|
335
|
+
variant: { ne: "" }
|
|
336
|
+
} });
|
|
337
|
+
const query = getQuery(event);
|
|
338
|
+
if (typeof query.selectedOptions === "string") query.selectedOptions = JSON.parse(query.selectedOptions || "[]");
|
|
339
|
+
const variantId = {
|
|
340
|
+
selectedOptions: [],
|
|
341
|
+
...query
|
|
342
|
+
}.selectedOptions.map((e) => e.value).join("/");
|
|
343
|
+
const quantityAvailable = items.find((e) => e.variant === variantId)?.quantity || 0;
|
|
344
|
+
const firstOption = items.reduce((acc, stock) => {
|
|
345
|
+
if (!stock?.variant) return { ...acc };
|
|
346
|
+
const [option] = stock?.variant?.split("/");
|
|
347
|
+
const v = (stock.quantity || 0) + (acc[option] || 0);
|
|
348
|
+
return {
|
|
349
|
+
...acc,
|
|
350
|
+
[option]: v
|
|
351
|
+
};
|
|
352
|
+
}, {});
|
|
353
|
+
const total = Object.values(firstOption).reduce((acc, e) => acc + e, 0);
|
|
354
|
+
return {
|
|
355
|
+
variant: {
|
|
356
|
+
availableForSale: !!quantityAvailable,
|
|
357
|
+
quantityAvailable
|
|
358
|
+
},
|
|
359
|
+
stocks: items,
|
|
360
|
+
firstOption,
|
|
361
|
+
total
|
|
362
|
+
};
|
|
363
|
+
}));
|
|
364
|
+
app.get(path("/cache/products/:id/verify"), eventHandler(async (event) => {
|
|
365
|
+
const productId = getRouterParam(event, "id");
|
|
366
|
+
const items = await db.query.productStocks.findMany({ where: {
|
|
367
|
+
productId: +productId,
|
|
368
|
+
variant: { ne: "" }
|
|
369
|
+
} });
|
|
370
|
+
const query = getQuery(event);
|
|
371
|
+
if (typeof query.selectedOptions === "string") query.selectedOptions = JSON.parse(query.selectedOptions || "[]");
|
|
372
|
+
const variantId = {
|
|
373
|
+
selectedOptions: [],
|
|
374
|
+
...query
|
|
375
|
+
}.selectedOptions.map((e) => e.value).join("/");
|
|
376
|
+
return { ok: !!(items.find((e) => e.variant === variantId)?.quantity || 0) };
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
async function getLineItems(cart, getProducts) {
|
|
380
|
+
const products = await getProducts();
|
|
381
|
+
const keys = Object.keys(cart);
|
|
382
|
+
const allVariants = products.filter((product) => product.variants.some((variant) => {
|
|
383
|
+
return keys.includes(`${product.id}:${variant.id}`);
|
|
384
|
+
})).map((product) => {
|
|
385
|
+
return product.variants.map((variant) => {
|
|
386
|
+
return {
|
|
387
|
+
id: `${product.id}:${variant.id}`,
|
|
388
|
+
title: product.title,
|
|
389
|
+
quantity: cart[`${product.id}:${variant.id}`],
|
|
390
|
+
variant,
|
|
391
|
+
taxes: product.taxes,
|
|
392
|
+
category: product.extras?.category || "none",
|
|
393
|
+
originalPrice: variant?.originalPrice || product?.originalPrice || 0,
|
|
394
|
+
originalDiscount: product?.discount || 0
|
|
395
|
+
};
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
return [].concat(...allVariants).filter((line) => keys.includes(`${line.id}`));
|
|
399
|
+
}
|
|
400
|
+
async function returnCart(event, cart, getProducts) {
|
|
401
|
+
const expires = new Date(dayjs().add(20, "minutes").format());
|
|
402
|
+
const start = Date.now();
|
|
403
|
+
const lineItems = await getLineItems(cart, getProducts);
|
|
404
|
+
console.log("getLineItems", `${Date.now() - start}ms`);
|
|
405
|
+
setCookie(event, "eCart", JSON.stringify(cart), { expires });
|
|
406
|
+
return { lineItems };
|
|
407
|
+
}
|
|
408
|
+
function registerCart(app, { getProducts, path }) {
|
|
409
|
+
const useCart = (event) => {
|
|
410
|
+
const rawCart = getCookie(event, "eCart");
|
|
411
|
+
if (!rawCart) return {};
|
|
412
|
+
return JSON.parse(rawCart);
|
|
413
|
+
};
|
|
414
|
+
app.get(path("/cart"), eventHandler(async (event) => {
|
|
415
|
+
const cart = useCart(event);
|
|
416
|
+
if (!Object.keys(cart).length) return { lineItems: [] };
|
|
417
|
+
return await returnCart(event, cart, getProducts);
|
|
418
|
+
}));
|
|
419
|
+
const getCartItem = async (event) => {
|
|
420
|
+
const body = await readBody(event);
|
|
421
|
+
return {
|
|
422
|
+
id: getQuery(event).id || body?.id,
|
|
423
|
+
quantity: body?.quantity
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
app.post(path("/cart"), eventHandler(async (event) => {
|
|
427
|
+
let cart = useCart(event);
|
|
428
|
+
const payload = await getCartItem(event);
|
|
429
|
+
cart = {
|
|
430
|
+
...cart,
|
|
431
|
+
[payload.id]: payload.quantity
|
|
432
|
+
};
|
|
433
|
+
return await returnCart(event, cart, getProducts);
|
|
434
|
+
}));
|
|
435
|
+
app.delete(path("/cart"), eventHandler((event) => {
|
|
436
|
+
setCookie(event, "eCart", "{}");
|
|
437
|
+
return { lineItems: [] };
|
|
438
|
+
}));
|
|
439
|
+
app.delete(path("/cart/**:id"), eventHandler(async (event) => {
|
|
440
|
+
const cart = useCart(event);
|
|
441
|
+
let id = getRouterParam(event, "id");
|
|
442
|
+
id = decodeURIComponent(id);
|
|
443
|
+
delete cart[id];
|
|
444
|
+
return await returnCart(event, cart, getProducts);
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
function roundNum(v, r = 2) {
|
|
448
|
+
return Number(Number(v).toFixed(r));
|
|
449
|
+
}
|
|
450
|
+
function calcPercentaje(value, percentaje, hasInclude) {
|
|
451
|
+
const val = +value;
|
|
452
|
+
const per = +percentaje;
|
|
453
|
+
if (hasInclude) return roundNum(val * per / (100 + per));
|
|
454
|
+
else return roundNum(val / 100 * per);
|
|
455
|
+
}
|
|
456
|
+
function calcDiscount(amount, discount) {
|
|
457
|
+
if ("percent" in discount) return {
|
|
458
|
+
amount: calcPercentaje(amount, discount.percent),
|
|
459
|
+
percent: +discount.percent
|
|
460
|
+
};
|
|
461
|
+
else return { amount: +discount.amount };
|
|
462
|
+
}
|
|
463
|
+
const sum = (arr) => arr.reduce((a, b) => a + b, 0);
|
|
464
|
+
const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);
|
|
465
|
+
function groupBy(fn, arr) {
|
|
466
|
+
return arr.reduce((acc, value) => {
|
|
467
|
+
const key = fn(value);
|
|
468
|
+
if (!acc[key]) acc[key] = [];
|
|
469
|
+
acc[key].push(value);
|
|
470
|
+
return acc;
|
|
471
|
+
}, {});
|
|
472
|
+
}
|
|
473
|
+
function calcAmount(_itemDiscount, _parentDiscount, taxIncluded) {
|
|
474
|
+
return (price) => {
|
|
475
|
+
if (!taxIncluded) {
|
|
476
|
+
const itemDiscount = calcDiscount(price, _itemDiscount);
|
|
477
|
+
const parentDiscount = calcDiscount(price - itemDiscount.amount, _parentDiscount);
|
|
478
|
+
const unitBase = price - (itemDiscount.amount + parentDiscount.amount);
|
|
479
|
+
return {
|
|
480
|
+
price,
|
|
481
|
+
discounts: {
|
|
482
|
+
itemDiscount,
|
|
483
|
+
parentDiscount
|
|
484
|
+
},
|
|
485
|
+
unitAmount: unitBase,
|
|
486
|
+
unitBase
|
|
487
|
+
};
|
|
488
|
+
} else {
|
|
489
|
+
const parentDiscount = calcDiscount(price, _parentDiscount);
|
|
490
|
+
const itemDiscount = calcDiscount(price - parentDiscount.amount, _itemDiscount);
|
|
491
|
+
return {
|
|
492
|
+
price,
|
|
493
|
+
discounts: {
|
|
494
|
+
itemDiscount,
|
|
495
|
+
parentDiscount
|
|
496
|
+
},
|
|
497
|
+
unitAmount: price - (itemDiscount.amount + parentDiscount.amount),
|
|
498
|
+
unitBase: 0
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function calcSingleItem(parentDiscount = { amount: 0 }) {
|
|
504
|
+
return (item) => {
|
|
505
|
+
const quantity = +item.quantity;
|
|
506
|
+
const taxes = item.taxes || [];
|
|
507
|
+
const calcRawTax = (base, rate) => base * rate / 100;
|
|
508
|
+
const calculateTaxesForward = (originalBase, finalBase, raw = false) => {
|
|
509
|
+
const firstTaxes = [];
|
|
510
|
+
const latestTaxes = [];
|
|
511
|
+
let totalFirstTaxesAmount = 0;
|
|
512
|
+
let totalLatestTaxesAmount = 0;
|
|
513
|
+
for (const t of taxes) if (t.beforeTaxes) {
|
|
514
|
+
const baseToUse = t.excludeDiscount ? originalBase : finalBase;
|
|
515
|
+
const amount = raw ? calcRawTax(baseToUse, +t.value) : calcPercentaje(baseToUse, t.value, false);
|
|
516
|
+
firstTaxes.push({
|
|
517
|
+
...t,
|
|
518
|
+
base: baseToUse,
|
|
519
|
+
amount,
|
|
520
|
+
totalBase: baseToUse * quantity,
|
|
521
|
+
totalAmount: amount * quantity
|
|
522
|
+
});
|
|
523
|
+
totalFirstTaxesAmount += amount;
|
|
524
|
+
}
|
|
525
|
+
for (const t of taxes) if (!t.beforeTaxes) {
|
|
526
|
+
const baseToUse = (t.excludeDiscount ? originalBase : finalBase) + totalFirstTaxesAmount;
|
|
527
|
+
const amount = raw ? calcRawTax(baseToUse, +t.value) : calcPercentaje(baseToUse, t.value, false);
|
|
528
|
+
latestTaxes.push({
|
|
529
|
+
...t,
|
|
530
|
+
base: baseToUse,
|
|
531
|
+
amount,
|
|
532
|
+
totalBase: baseToUse * quantity,
|
|
533
|
+
totalAmount: amount * quantity
|
|
534
|
+
});
|
|
535
|
+
totalLatestTaxesAmount += amount;
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
firstTaxes,
|
|
539
|
+
latestTaxes,
|
|
540
|
+
totalTaxes: totalFirstTaxesAmount + totalLatestTaxesAmount,
|
|
541
|
+
totalPrice: finalBase + totalFirstTaxesAmount + totalLatestTaxesAmount
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
if (!item.taxIncluded) {
|
|
545
|
+
const amounts = calcAmount(item.discount, parentDiscount, false)(+item.price);
|
|
546
|
+
const originalBase = amounts.price;
|
|
547
|
+
const finalBase = amounts.unitBase;
|
|
548
|
+
const result = calculateTaxesForward(originalBase, finalBase, false);
|
|
549
|
+
amounts.total = amounts.unitBase * quantity + result.totalTaxes * quantity;
|
|
550
|
+
return {
|
|
551
|
+
id: item.id,
|
|
552
|
+
title: item.title,
|
|
553
|
+
taxIncluded: item.taxIncluded,
|
|
554
|
+
item: amounts,
|
|
555
|
+
taxes: [...result.firstTaxes, ...result.latestTaxes],
|
|
556
|
+
quantity,
|
|
557
|
+
product: item.product,
|
|
558
|
+
variant: item.variant,
|
|
559
|
+
discountAllocations: item.discountAllocations
|
|
560
|
+
};
|
|
561
|
+
} else {
|
|
562
|
+
const amounts = calcAmount(item.discount, parentDiscount, true)(+item.price);
|
|
563
|
+
const originalPriceWithTaxes = amounts.price;
|
|
564
|
+
const finalPriceWithTaxes = amounts.unitAmount;
|
|
565
|
+
const T1_1 = calculateTaxesForward(1, 1, true).totalPrice;
|
|
566
|
+
const originalBaseRaw = originalPriceWithTaxes / (T1_1 === 0 ? 1 : T1_1);
|
|
567
|
+
const T0 = calculateTaxesForward(originalBaseRaw, 0, true).totalPrice;
|
|
568
|
+
const M = calculateTaxesForward(originalBaseRaw, 1, true).totalPrice - T0;
|
|
569
|
+
const result = calculateTaxesForward(originalBaseRaw, M === 0 ? finalPriceWithTaxes - T0 : (finalPriceWithTaxes - T0) / M, false);
|
|
570
|
+
const adjustedFinalBase = finalPriceWithTaxes - result.totalTaxes;
|
|
571
|
+
amounts.unitBase = adjustedFinalBase;
|
|
572
|
+
amounts.total = finalPriceWithTaxes * quantity;
|
|
573
|
+
for (const t of result.firstTaxes) if (!t.excludeDiscount) {
|
|
574
|
+
t.base = adjustedFinalBase;
|
|
575
|
+
t.totalBase = adjustedFinalBase * quantity;
|
|
576
|
+
}
|
|
577
|
+
const firstTotal = sum(result.firstTaxes.map((t) => t.amount));
|
|
578
|
+
for (const t of result.latestTaxes) if (!t.excludeDiscount) {
|
|
579
|
+
t.base = adjustedFinalBase + firstTotal;
|
|
580
|
+
t.totalBase = t.base * quantity;
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
id: item.id,
|
|
584
|
+
title: item.title,
|
|
585
|
+
taxIncluded: item.taxIncluded,
|
|
586
|
+
item: amounts,
|
|
587
|
+
taxes: [...result.firstTaxes, ...result.latestTaxes],
|
|
588
|
+
quantity,
|
|
589
|
+
product: item.product,
|
|
590
|
+
variant: item.variant,
|
|
591
|
+
discountAllocations: item.discountAllocations
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function calcItems(items, parentDiscount = { amount: 0 }) {
|
|
597
|
+
return items.map(calcSingleItem(parentDiscount));
|
|
598
|
+
}
|
|
599
|
+
function buildInvoice(items) {
|
|
600
|
+
const base = sum(items.map((item) => item.item.unitBase * item.quantity));
|
|
601
|
+
const lineItemsSubtotalPrice = sum(items.filter((item) => !["_envio"].includes(item.id)).map((item) => item.item.price * item.quantity));
|
|
602
|
+
const shipping = sum(items.filter((item) => ["_envio"].includes(item.id)).map((item) => item.item.price * item.quantity));
|
|
603
|
+
const totalDiscount = sum(items.map((item) => {
|
|
604
|
+
const discounts = item.item.discounts;
|
|
605
|
+
if (!discounts) return 0;
|
|
606
|
+
return ((discounts.itemDiscount?.amount || 0) + (discounts.parentDiscount?.amount || 0)) * item.quantity;
|
|
607
|
+
}));
|
|
608
|
+
const imp = groupBy((tax) => {
|
|
609
|
+
return `${tax.type.code}_${tax.code}`;
|
|
610
|
+
}, flatten(items.map((item) => item.taxes)));
|
|
611
|
+
const _taxes = Object.values(imp).map((taxes) => {
|
|
612
|
+
let taxBase = 0;
|
|
613
|
+
let value = 0;
|
|
614
|
+
const type = taxes[0].type.code;
|
|
615
|
+
const title = taxes[0].type.name;
|
|
616
|
+
const code = taxes[0].code;
|
|
617
|
+
const rate = +taxes[0].value;
|
|
618
|
+
for (const iterator of taxes) {
|
|
619
|
+
taxBase += +iterator.totalBase;
|
|
620
|
+
value += +iterator.totalAmount;
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
type,
|
|
624
|
+
code,
|
|
625
|
+
base: taxBase,
|
|
626
|
+
value,
|
|
627
|
+
rate,
|
|
628
|
+
title
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
return {
|
|
632
|
+
total: base + sum(_taxes.map((tax) => tax.value)),
|
|
633
|
+
totalTax: sum(_taxes.map((t) => t.value)),
|
|
634
|
+
totalDiscount,
|
|
635
|
+
base,
|
|
636
|
+
items,
|
|
637
|
+
taxes: _taxes,
|
|
638
|
+
lineItemsSubtotalPrice,
|
|
639
|
+
shipping
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
function calculateFormula(expression, variables) {
|
|
643
|
+
if (!/^[\w+\-*/().\s<>=?:&|"']+$/.test(expression)) throw new TypeError("Formula contains invalid characters");
|
|
644
|
+
const context = { ...variables };
|
|
645
|
+
try {
|
|
646
|
+
let processedExpression = expression;
|
|
647
|
+
for (const [key, value] of Object.entries(context)) {
|
|
648
|
+
const regex = new RegExp(`\\b${key}\\b`, "g");
|
|
649
|
+
const safeValue = typeof value === "string" ? `"${value}"` : String(value);
|
|
650
|
+
processedExpression = processedExpression.replace(regex, safeValue);
|
|
651
|
+
}
|
|
652
|
+
const result = new Function(`return ${processedExpression}`)();
|
|
653
|
+
if (typeof result !== "number" || Number.isNaN(result)) throw new TypeError("Formula result is not a valid number");
|
|
654
|
+
return result;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
657
|
+
throw new Error(`Error evaluating formula "${expression}": ${errorMessage}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function getCheckoutId(event) {
|
|
661
|
+
const cookie = getCookie(event, "eCheckout");
|
|
662
|
+
if (!cookie) throw new HTTPError({
|
|
663
|
+
statusCode: 404,
|
|
664
|
+
status: 404,
|
|
665
|
+
message: "Checkout not found"
|
|
666
|
+
});
|
|
667
|
+
return +cookie;
|
|
668
|
+
}
|
|
669
|
+
function registerCheckout(app, db, { path, storeId }) {
|
|
670
|
+
const { getStore } = useProducts(db);
|
|
671
|
+
app.post(path("/checkout"), eventHandler(async (event) => {
|
|
672
|
+
const payload = await readBody(event);
|
|
673
|
+
if (!payload.items) return new HTTPError({
|
|
674
|
+
status: 400,
|
|
675
|
+
statusCode: 400,
|
|
676
|
+
message: "Items not found"
|
|
677
|
+
});
|
|
678
|
+
const checkout = await db.insert(checkouts).values({
|
|
679
|
+
taxes: [],
|
|
680
|
+
billingInfo: {},
|
|
681
|
+
shippingInfo: {},
|
|
682
|
+
...payload
|
|
683
|
+
}).returning();
|
|
684
|
+
setCookie(event, "eCheckout", `${checkout[0].id}`);
|
|
685
|
+
return { id: checkout[0].id };
|
|
686
|
+
}));
|
|
687
|
+
app.get(path("/checkout"), eventHandler(async (event) => {
|
|
688
|
+
const id = getCheckoutId(event);
|
|
689
|
+
const checkout = await db.query.checkouts.findFirst({ where: { id } });
|
|
690
|
+
if (!checkout) return;
|
|
691
|
+
const store = await getStore(storeId);
|
|
692
|
+
if (!store) return;
|
|
693
|
+
const theDiscount = (checkout.discount || [])[0]?.options || {};
|
|
694
|
+
const theTax = getTax(store);
|
|
695
|
+
const TAX_INCLUDED = store.storeTaxes?.included ?? true;
|
|
696
|
+
console.log(theTax, TAX_INCLUDED);
|
|
697
|
+
const items = checkout.items.filter((el) => !el?.id.startsWith("_")).map((el) => {
|
|
698
|
+
const discount = theDiscount?.type === "percentage" ? { percent: theDiscount?.value } : theDiscount?.type === "amount" ? { amount: theDiscount?.value } : { amount: 0 };
|
|
699
|
+
console.log("el.taxes", el.taxes);
|
|
700
|
+
const taxIncluded = el.taxes?.some((tax) => tax.included) ?? TAX_INCLUDED;
|
|
701
|
+
const taxes = el.taxes && el.taxes.length ? el.taxes : theTax ? [{
|
|
702
|
+
...theTax,
|
|
703
|
+
included: taxIncluded
|
|
704
|
+
}] : [];
|
|
705
|
+
console.log("taxes", taxes);
|
|
706
|
+
return {
|
|
707
|
+
productId: el.variant?.productId,
|
|
708
|
+
discount,
|
|
709
|
+
id: el.variant.id,
|
|
710
|
+
price: el.variant.price,
|
|
711
|
+
quantity: el.quantity,
|
|
712
|
+
product: el.product,
|
|
713
|
+
variant: el.variant,
|
|
714
|
+
discountAllocations: el.discountAllocations || [],
|
|
715
|
+
taxes,
|
|
716
|
+
taxIncluded,
|
|
717
|
+
title: `${el.variant?.title} - ${el.variant?.id}`,
|
|
718
|
+
category: el.category || "none",
|
|
719
|
+
originalPrice: el.originalPrice || 0,
|
|
720
|
+
originalDiscount: el.originalDiscount || 0
|
|
721
|
+
};
|
|
722
|
+
});
|
|
723
|
+
if (checkout.shippingType && checkout.shippingType?.id) {
|
|
724
|
+
const taxShipping = checkout.shippingType.addTax ? {
|
|
725
|
+
code: "4",
|
|
726
|
+
description: "Envio",
|
|
727
|
+
included: false,
|
|
728
|
+
value: 15,
|
|
729
|
+
type: {
|
|
730
|
+
code: "2",
|
|
731
|
+
name: "IVA"
|
|
732
|
+
}
|
|
733
|
+
} : {
|
|
734
|
+
code: "0",
|
|
735
|
+
description: "Envio",
|
|
736
|
+
included: true,
|
|
737
|
+
value: 0,
|
|
738
|
+
type: {
|
|
739
|
+
code: "2",
|
|
740
|
+
name: "IVA"
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
const taxIncluded = !checkout.shippingType.addTax;
|
|
744
|
+
items.push({
|
|
745
|
+
discount: { amount: 0 },
|
|
746
|
+
price: +(checkout.shippingType.priceV2?.amount || checkout.shippingType.price),
|
|
747
|
+
id: "_envio",
|
|
748
|
+
quantity: 1,
|
|
749
|
+
taxIncluded,
|
|
750
|
+
title: "envio",
|
|
751
|
+
taxes: [taxShipping]
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
const itemsInvoice = calcItems(items);
|
|
755
|
+
const invoice = buildInvoice(itemsInvoice);
|
|
756
|
+
await db.update(checkouts).set({
|
|
757
|
+
items: items.map((el) => {
|
|
758
|
+
const item = itemsInvoice.find((e) => e.id === el.id);
|
|
759
|
+
return {
|
|
760
|
+
...el,
|
|
761
|
+
item: item?.item,
|
|
762
|
+
lineItem: item
|
|
763
|
+
};
|
|
764
|
+
}),
|
|
765
|
+
total: invoice.total,
|
|
766
|
+
base: invoice.base,
|
|
767
|
+
taxes: invoice.taxes,
|
|
768
|
+
shippingType: checkout.shippingType || {}
|
|
769
|
+
}).where(eq(checkouts.id, id));
|
|
770
|
+
return {
|
|
771
|
+
invoice: {
|
|
772
|
+
...invoice,
|
|
773
|
+
discounts: checkout.discount || []
|
|
774
|
+
},
|
|
775
|
+
lineItems: checkout.items.filter((el) => !el?.id.startsWith("_")),
|
|
776
|
+
address: checkout.shippingInfo,
|
|
777
|
+
billing: checkout.billingInfo,
|
|
778
|
+
shipping: checkout.shippingType
|
|
779
|
+
};
|
|
780
|
+
}));
|
|
781
|
+
app.post(path("/checkout/address"), eventHandler(async (event) => {
|
|
782
|
+
const id = getCheckoutId(event);
|
|
783
|
+
const payload = await readBody(event);
|
|
784
|
+
await db.update(checkouts).set({ shippingInfo: { ...payload } }).where(eq(checkouts.id, id));
|
|
785
|
+
return { ok: true };
|
|
786
|
+
}));
|
|
787
|
+
app.post(path("/checkout/billing"), eventHandler(async (event) => {
|
|
788
|
+
const id = getCheckoutId(event);
|
|
789
|
+
const payload = await readBody(event);
|
|
790
|
+
await db.update(checkouts).set({ billingInfo: { ...payload } }).where(eq(checkouts.id, id));
|
|
791
|
+
return { ok: true };
|
|
792
|
+
}));
|
|
793
|
+
app.get(path("/checkout/shippings"), eventHandler(async (event) => {
|
|
794
|
+
const store = await getStore(storeId);
|
|
795
|
+
const id = getCheckoutId(event);
|
|
796
|
+
const shippings = store.storeShippings || store?.storeSettings?.shippingType || [];
|
|
797
|
+
const type = store?.storeShippingsOptions?.type || "simple";
|
|
798
|
+
const checkout = await db.query.checkouts.findFirst({ where: { id } });
|
|
799
|
+
if (!checkout) return new HTTPError({
|
|
800
|
+
statusCode: 404,
|
|
801
|
+
statusMessage: "Checkout not found"
|
|
802
|
+
});
|
|
803
|
+
if (type === "complex") {
|
|
804
|
+
const allFormulates = store.storeShippingsOptions?.formulate || [];
|
|
805
|
+
const normalizeText = (text) => {
|
|
806
|
+
return text.toLowerCase().replace(/ /g, "-").replace(/[^a-z0-9-]/g, "");
|
|
807
|
+
};
|
|
808
|
+
const state = normalizeText(checkout.shippingInfo?.state || "TUNGURAHUA");
|
|
809
|
+
const city = normalizeText(checkout.shippingInfo?.city || "");
|
|
810
|
+
const item = shippings.find((e) => {
|
|
811
|
+
const _city = normalizeText(e.regionCity || "");
|
|
812
|
+
const _state = normalizeText(e.regionState || "");
|
|
813
|
+
return _city === city && _state === state;
|
|
814
|
+
});
|
|
815
|
+
console.log("item", item);
|
|
816
|
+
const pickup = {
|
|
817
|
+
_raw: [{
|
|
818
|
+
name: "Pickup - Retiro en tienda(Quito o Guayaquil)",
|
|
819
|
+
value: "0.00"
|
|
820
|
+
}],
|
|
821
|
+
items: [{
|
|
822
|
+
title: "Pickup - Retiro en tienda",
|
|
823
|
+
handle: "shopify-Pickup-0.00",
|
|
824
|
+
priceV2: {
|
|
825
|
+
amount: "0.00",
|
|
826
|
+
currencyCode: "USD"
|
|
827
|
+
},
|
|
828
|
+
price: "USD 0.00",
|
|
829
|
+
id: "shopify-Pickup-0.00",
|
|
830
|
+
name: "Pickup - Retiro en tienda (Sucursal Quito o Guayaquil)"
|
|
831
|
+
}]
|
|
832
|
+
};
|
|
833
|
+
if (item) {
|
|
834
|
+
let stocks = await ofetch(`${store.endpoint}/storefront/cache/the-stocks/${checkout.items.map((e) => e.productId || e.id).join("-")}`, { params: { ids: checkout.items.map((e) => e.productId || e.id).join(",") } }).catch(() => []);
|
|
835
|
+
stocks = stocks.map((v) => {
|
|
836
|
+
return {
|
|
837
|
+
...v,
|
|
838
|
+
category: checkout.items.find((e) => {
|
|
839
|
+
return String(e.id) === String(v.sku) || String(e.productId) === String(v.sku);
|
|
840
|
+
})?.category || "none",
|
|
841
|
+
quantity: checkout.items.find((e) => {
|
|
842
|
+
return String(e.id) === String(v.sku) || String(e.productId) === String(v.sku);
|
|
843
|
+
})?.quantity || 1
|
|
844
|
+
};
|
|
845
|
+
});
|
|
846
|
+
console.log("stocks", stocks);
|
|
847
|
+
const variables = store.storeShippingsOptions.variables.reduce((acc, curr) => {
|
|
848
|
+
return {
|
|
849
|
+
...acc,
|
|
850
|
+
[curr.name]: curr.default
|
|
851
|
+
};
|
|
852
|
+
}, {});
|
|
853
|
+
let sumTax = false;
|
|
854
|
+
const prices = stocks.filter(({ id }) => !id.startsWith("_")).map((curr) => {
|
|
855
|
+
console.log("curr", curr);
|
|
856
|
+
let quantity = curr.quantity || 1;
|
|
857
|
+
const _formulate = allFormulates.find((f) => f.key === curr.category) || allFormulates.find((f) => f.key === "default") || item.formulate[0];
|
|
858
|
+
if (_formulate.noQuantity) quantity = 1;
|
|
859
|
+
const weight = Number(curr.weight || 0) * Number(quantity || 1);
|
|
860
|
+
const vars = {
|
|
861
|
+
...variables,
|
|
862
|
+
...item,
|
|
863
|
+
weight,
|
|
864
|
+
sku: curr.sku,
|
|
865
|
+
quantity
|
|
866
|
+
};
|
|
867
|
+
const formulate = _formulate.value;
|
|
868
|
+
if (_formulate.key !== "default") sumTax = _formulate.tax || false;
|
|
869
|
+
console.log("formulate", curr, formulate, vars);
|
|
870
|
+
return calculateFormula(formulate, vars);
|
|
871
|
+
});
|
|
872
|
+
console.log("prices", prices);
|
|
873
|
+
if (prices.some((p) => p < 0)) return {
|
|
874
|
+
_raw: [],
|
|
875
|
+
items: [],
|
|
876
|
+
message: "Elemento sin covertura"
|
|
877
|
+
};
|
|
878
|
+
const title = `${item?.regionState || ""} - ${item?.regionCity || ""}`;
|
|
879
|
+
const total = prices.reduce((acc, curr) => acc + curr, 0).toFixed(2);
|
|
880
|
+
return {
|
|
881
|
+
_raw: [{
|
|
882
|
+
name: title,
|
|
883
|
+
value: total
|
|
884
|
+
}, ...pickup._raw],
|
|
885
|
+
items: [{
|
|
886
|
+
title,
|
|
887
|
+
handle: title,
|
|
888
|
+
priceV2: {
|
|
889
|
+
amount: total,
|
|
890
|
+
currencyCode: "USD"
|
|
891
|
+
},
|
|
892
|
+
notify: item.correos || "",
|
|
893
|
+
notify2: item.facturacion || "",
|
|
894
|
+
addTax: sumTax,
|
|
895
|
+
item,
|
|
896
|
+
price: `USD ${total}`,
|
|
897
|
+
id: title,
|
|
898
|
+
name: title
|
|
899
|
+
}, ...pickup.items]
|
|
900
|
+
};
|
|
901
|
+
} else return {
|
|
902
|
+
...pickup,
|
|
903
|
+
code: "no-shipping"
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (!shippings.length) return {
|
|
907
|
+
_raw: [{
|
|
908
|
+
name: "Standard",
|
|
909
|
+
value: "0.00"
|
|
910
|
+
}],
|
|
911
|
+
items: [{
|
|
912
|
+
title: "Standard",
|
|
913
|
+
handle: "shopify-Standard-0.00",
|
|
914
|
+
priceV2: {
|
|
915
|
+
amount: "0.0",
|
|
916
|
+
currencyCode: "USD"
|
|
917
|
+
},
|
|
918
|
+
price: "USD 0.0",
|
|
919
|
+
id: "shopify-Standard-0.00",
|
|
920
|
+
name: "Standard"
|
|
921
|
+
}]
|
|
922
|
+
};
|
|
923
|
+
return {
|
|
924
|
+
_raw: shippings,
|
|
925
|
+
items: shippings.map((item) => {
|
|
926
|
+
const id = `${item.name}-${item.value}`;
|
|
927
|
+
return {
|
|
928
|
+
_raw: item,
|
|
929
|
+
title: item.name,
|
|
930
|
+
handle: id,
|
|
931
|
+
priceV2: {
|
|
932
|
+
amount: item.value,
|
|
933
|
+
currencyCode: "USD"
|
|
934
|
+
},
|
|
935
|
+
price: (+item.value).toFixed(2),
|
|
936
|
+
id,
|
|
937
|
+
name: item.name
|
|
938
|
+
};
|
|
939
|
+
})
|
|
940
|
+
};
|
|
941
|
+
}));
|
|
942
|
+
app.post(path("/checkout/shippings"), eventHandler(async (event) => {
|
|
943
|
+
const id = getCheckoutId(event);
|
|
944
|
+
const payload = await readBody(event);
|
|
945
|
+
await db.update(checkouts).set({ shippingType: { ...payload } }).where(eq(checkouts.id, id));
|
|
946
|
+
return { ok: true };
|
|
947
|
+
}));
|
|
948
|
+
app.post(path("/checkout/discount"), eventHandler(async (event) => {
|
|
949
|
+
const id = getCheckoutId(event);
|
|
950
|
+
const store = await getStore(storeId);
|
|
951
|
+
const { coupon } = await readBody(event);
|
|
952
|
+
const coupons = store?.storeDiscounts || [];
|
|
953
|
+
const user = useUser(event);
|
|
954
|
+
const item = coupons.filter((e) => e.select.toLowerCase() === "codigo").find((c) => c.value === coupon);
|
|
955
|
+
if (!item) return {
|
|
956
|
+
message: "Cupón no válido",
|
|
957
|
+
code: 0
|
|
958
|
+
};
|
|
959
|
+
const condition = {
|
|
960
|
+
storeId,
|
|
961
|
+
customerId: user.id,
|
|
962
|
+
code: coupon
|
|
963
|
+
};
|
|
964
|
+
if (await db.query.discountCoupons.findFirst({ where: condition }) && item.options.apply === "once") return {
|
|
965
|
+
message: "Cupón ya utilizado",
|
|
966
|
+
code: 0
|
|
967
|
+
};
|
|
968
|
+
if (item) await db.update(checkouts).set({ discount: [{
|
|
969
|
+
...item,
|
|
970
|
+
coupon,
|
|
971
|
+
customerId: user.id
|
|
972
|
+
}] }).where(eq(checkouts.id, id));
|
|
973
|
+
return {
|
|
974
|
+
message: "Cupón aplicado",
|
|
975
|
+
code: 1
|
|
976
|
+
};
|
|
977
|
+
}));
|
|
978
|
+
app.delete(path("/checkout/discount"), eventHandler(async (event) => {
|
|
979
|
+
const id = getCheckoutId(event);
|
|
980
|
+
await db.update(checkouts).set({ discount: [] }).where(eq(checkouts.id, id));
|
|
981
|
+
return { ok: true };
|
|
982
|
+
}));
|
|
983
|
+
}
|
|
984
|
+
const safeParams = (v) => v.split("?")[0];
|
|
985
|
+
const TASKS = {
|
|
986
|
+
notify: {
|
|
987
|
+
type: "external",
|
|
988
|
+
endpoint: `${URL}/storefront/webhooks/notify/success`
|
|
989
|
+
},
|
|
990
|
+
stock: {
|
|
991
|
+
type: "internal",
|
|
992
|
+
task: "updateStocks",
|
|
993
|
+
payload: { omit: "tranference" }
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const presets = { ecommerce: { prepare: { preHandle: async (event, payment) => {
|
|
997
|
+
const checkoutId = getCheckoutId(event);
|
|
998
|
+
const _body = await readBody(event);
|
|
999
|
+
const user = useUser(event);
|
|
1000
|
+
const { customer, shippingIdentification, extras } = _body;
|
|
1001
|
+
const { card, ...body } = _body;
|
|
1002
|
+
const session = {
|
|
1003
|
+
email: user.email,
|
|
1004
|
+
identification: customer?.billingInfo?.identification || shippingIdentification || "0000000000",
|
|
1005
|
+
customer: {
|
|
1006
|
+
givenName: customer?.billingInfo?.fullname || "",
|
|
1007
|
+
surname: "",
|
|
1008
|
+
billingInfo: customer?.billingInfo,
|
|
1009
|
+
...user
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const theSession = await payment.session.create(session);
|
|
1013
|
+
const store = await payment.getStore();
|
|
1014
|
+
const successTasks = [];
|
|
1015
|
+
if (store.storeTasks?.success?.notify) successTasks.push(TASKS.notify);
|
|
1016
|
+
if (store.storeTasks?.success?.stock) successTasks.push(TASKS.stock);
|
|
1017
|
+
store.storeTasks?.successTasks?.forEach((el) => {
|
|
1018
|
+
const endpoint = el.url.startsWith("/") ? `${URL}${el.url}` : el.url;
|
|
1019
|
+
successTasks.push({
|
|
1020
|
+
type: "external",
|
|
1021
|
+
task: String(el.id),
|
|
1022
|
+
endpoint
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
const success = successTasks.map((el) => ({
|
|
1026
|
+
...el,
|
|
1027
|
+
payload: {
|
|
1028
|
+
...body,
|
|
1029
|
+
...el.payload
|
|
1030
|
+
}
|
|
1031
|
+
}));
|
|
1032
|
+
const failed = (store.storeSettings?.flow?.failed || [{
|
|
1033
|
+
type: "external",
|
|
1034
|
+
endpoint: `${URL}/storefront/webhooks/notify/failed`
|
|
1035
|
+
}]).map((el) => ({
|
|
1036
|
+
...el,
|
|
1037
|
+
payload: body
|
|
1038
|
+
}));
|
|
1039
|
+
await payment.checkout.associate(checkoutId, theSession.id);
|
|
1040
|
+
const provider = getRouterParam(event, "provider");
|
|
1041
|
+
console.log("Preparing payment for provider:", provider);
|
|
1042
|
+
event.context.body = {
|
|
1043
|
+
sessionId: theSession.id,
|
|
1044
|
+
checkoutId,
|
|
1045
|
+
description: "Compra",
|
|
1046
|
+
ip: getRequestIP(event),
|
|
1047
|
+
endpoints: { verify: { endpoint: `${URL}/payment/verify/${provider}` } },
|
|
1048
|
+
tasks: {
|
|
1049
|
+
success,
|
|
1050
|
+
failed
|
|
1051
|
+
},
|
|
1052
|
+
extras
|
|
1053
|
+
};
|
|
1054
|
+
event.context.card = card;
|
|
1055
|
+
} } } };
|
|
1056
|
+
function registerPayment(app, db, { path, storeId, providers, preset, providerOptions, redirect: globalRedirect }) {
|
|
1057
|
+
const instance = createPayment({
|
|
1058
|
+
graphql: createGraphql(db),
|
|
1059
|
+
storeId,
|
|
1060
|
+
providers,
|
|
1061
|
+
secret: "secret",
|
|
1062
|
+
tasks: {
|
|
1063
|
+
url: TASKS_ENDPOINT,
|
|
1064
|
+
headers: { Authorization: `Bearer ${GRAPHQL_TOKEN}` }
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
const extend = typeof preset === "string" ? presets[preset] || {} : preset;
|
|
1068
|
+
const payHandle = eventHandler(async (event) => {
|
|
1069
|
+
const { payment } = instance;
|
|
1070
|
+
console.log(event, event.headers);
|
|
1071
|
+
const query = getQuery(event);
|
|
1072
|
+
const { uid } = query;
|
|
1073
|
+
const redirect$1 = query.redirect || globalRedirect;
|
|
1074
|
+
const provider = safeParams(event.context.params?.provider || "");
|
|
1075
|
+
let body = {};
|
|
1076
|
+
if (event.req.method === "POST") body = await readBody(event);
|
|
1077
|
+
const r = await payment.pay(provider, {
|
|
1078
|
+
...query,
|
|
1079
|
+
uid,
|
|
1080
|
+
body
|
|
1081
|
+
});
|
|
1082
|
+
const { provider: _1, uid: _2, id: _3, ...rest } = query;
|
|
1083
|
+
const params = Object.entries(rest).map(([key, value]) => `${key}=${value}`).join("&");
|
|
1084
|
+
const pathRedirect = joinURL(URL, `${redirect$1}?provider=${provider}&id=${r.id}&uid=${uid}&${params}`);
|
|
1085
|
+
console.log("pathredirect", pathRedirect);
|
|
1086
|
+
if (redirect$1 !== "0" || redirect$1.startsWith("/")) return redirect(pathRedirect, 301);
|
|
1087
|
+
return {
|
|
1088
|
+
redirect: pathRedirect,
|
|
1089
|
+
payload: r
|
|
1090
|
+
};
|
|
1091
|
+
});
|
|
1092
|
+
app.post(path("/pay/:provider"), payHandle);
|
|
1093
|
+
app.get(path("/pay/:provider"), payHandle);
|
|
1094
|
+
app.post(path("/post-prepare/:provider"), eventHandler(async (event) => {
|
|
1095
|
+
const { payment } = instance;
|
|
1096
|
+
const hooks = extend?.postPrepare;
|
|
1097
|
+
if (hooks?.preHandle) await hooks.preHandle(event, instance);
|
|
1098
|
+
let body = event.context.body;
|
|
1099
|
+
if (!body) body = await readBody(event);
|
|
1100
|
+
const { options } = body;
|
|
1101
|
+
const provider = safeParams(getRouterParam(event, "provider") || "");
|
|
1102
|
+
const defaultProviderOptions = defu(providerOptions?.[provider] || {}, options || {});
|
|
1103
|
+
const { options: _opt, ...theBody } = body;
|
|
1104
|
+
try {
|
|
1105
|
+
const r = await payment.postPrepare(provider, theBody, {
|
|
1106
|
+
...defaultProviderOptions,
|
|
1107
|
+
card: event.context.card || void 0,
|
|
1108
|
+
returnUrl: event.context.returnUrl || void 0,
|
|
1109
|
+
transactionMode: event.context.transactionMode || void 0,
|
|
1110
|
+
deviceInformation: event.context.deviceInformation || void 0
|
|
1111
|
+
});
|
|
1112
|
+
if (hooks?.postHandle) await hooks.postHandle(event, r);
|
|
1113
|
+
return r;
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
console.log(error?.response?.body || error);
|
|
1116
|
+
throw createError({ statusCode: 500 });
|
|
1117
|
+
}
|
|
1118
|
+
}));
|
|
1119
|
+
app.post(path("/prepare/:provider"), eventHandler(async (event) => {
|
|
1120
|
+
const _payment = instance;
|
|
1121
|
+
const { payment } = _payment;
|
|
1122
|
+
const hooks = extend?.prepare;
|
|
1123
|
+
if (hooks?.preHandle) await hooks.preHandle(event, _payment);
|
|
1124
|
+
let body = event.context.body;
|
|
1125
|
+
if (!body) body = await readBody(event);
|
|
1126
|
+
const { options } = body;
|
|
1127
|
+
const provider = safeParams(getRouterParam(event, "provider") || "");
|
|
1128
|
+
const defaultProviderOptions = defu(providerOptions?.[provider] || {}, options || {});
|
|
1129
|
+
const { options: _opt, ...theBody } = body;
|
|
1130
|
+
try {
|
|
1131
|
+
const r = await payment.prepare(provider, theBody, {
|
|
1132
|
+
...defaultProviderOptions,
|
|
1133
|
+
card: event.context.card || void 0
|
|
1134
|
+
});
|
|
1135
|
+
if (hooks?.postHandle) await hooks.postHandle(event, r);
|
|
1136
|
+
return r;
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
console.log(error?.response?.body || error);
|
|
1139
|
+
throw createError({ statusCode: 500 });
|
|
1140
|
+
}
|
|
1141
|
+
}));
|
|
1142
|
+
app.post(path("/verify/:provider"), eventHandler(async (event) => {
|
|
1143
|
+
const { payment } = instance;
|
|
1144
|
+
const { uid } = await readBody(event);
|
|
1145
|
+
const provider = safeParams(event.context.params?.provider || "");
|
|
1146
|
+
const query = getQuery(event);
|
|
1147
|
+
return await payment.verify(provider, uid, !!query.override);
|
|
1148
|
+
}));
|
|
1149
|
+
app.post(path("/webhook/:provider"), eventHandler(async (event) => {
|
|
1150
|
+
const { payment } = instance;
|
|
1151
|
+
const body = await readBody(event);
|
|
1152
|
+
const { reference: uid } = body;
|
|
1153
|
+
const provider = safeParams(event.context.params?.provider || "");
|
|
1154
|
+
return await payment?.webhook?.(provider, uid, body);
|
|
1155
|
+
}));
|
|
1156
|
+
}
|
|
1157
|
+
export { getCheckoutId, getTax, registerAccount, registerAuth, registerAuthMiddleware, registerCache, registerCart, registerCheckout, registerPayment, safeParams, useProducts, useUser };
|