@ecomiq/storefront 1.1.0 → 1.1.2
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/{chunk-W372BFMG.js → chunk-6TMU4OXP.js} +26 -28
- package/dist/chunk-6TMU4OXP.js.map +1 -0
- package/dist/{chunk-3C4ZLHSU.js → chunk-YSD62KQM.js} +22 -27
- package/dist/chunk-YSD62KQM.js.map +1 -0
- package/dist/chunks/client.css +811 -0
- package/dist/chunks/client.css.map +1 -0
- package/dist/chunks/client.d.ts +1533 -9
- package/dist/chunks/client.js +2874 -27
- package/dist/chunks/client.js.map +1 -1
- package/dist/chunks/config.d.ts +6 -5
- package/dist/chunks/config.js +1 -1
- package/dist/chunks/server-nextjs.d.ts +4 -5
- package/dist/chunks/server-nextjs.js +3 -3
- package/dist/chunks/server-nextjs.js.map +1 -1
- package/dist/chunks/server-tanstack.d.ts +2 -3
- package/dist/chunks/server-tanstack.js +3 -3
- package/dist/chunks/server-tanstack.js.map +1 -1
- package/dist/{ecomiq-oauth-password-plugin-DJCOOij3.d.ts → ecomiq-oauth-password-plugin-gw2dPyYb.d.ts} +12 -0
- package/dist/nextjs.d.ts +1 -0
- package/dist/tanstack.d.ts +1 -0
- package/package.json +1 -1
- package/dist/chunk-3C4ZLHSU.js.map +0 -1
- package/dist/chunk-W372BFMG.js.map +0 -1
package/dist/chunks/client.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { QueryClient, hydrate, QueryClientProvider, useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
|
4
|
-
import React2, { createContext,
|
|
4
|
+
import React2, { createContext, forwardRef, useRef, useState, useCallback, useMemo, useImperativeHandle, useEffect, useContext } from 'react';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
6
|
import { toast } from 'sonner';
|
|
7
7
|
import { create } from 'zustand';
|
|
8
8
|
import { persist } from 'zustand/middleware';
|
|
9
|
+
import { useShallow } from 'zustand/shallow';
|
|
9
10
|
|
|
10
11
|
// src/core/client/store-api-errors.ts
|
|
11
12
|
function isCartNotActiveApiError(error) {
|
|
@@ -57,7 +58,10 @@ var StoreApiClient = class {
|
|
|
57
58
|
const pathNorm = path.replace(/^\/+/, "");
|
|
58
59
|
const url = path.startsWith("http") ? path : `${this.baseUrl}/${pathNorm}`;
|
|
59
60
|
const headers = new Headers(init.headers ?? {});
|
|
60
|
-
|
|
61
|
+
const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
|
|
62
|
+
if (!isFormData) {
|
|
63
|
+
headers.set("Content-Type", "application/json");
|
|
64
|
+
}
|
|
61
65
|
headers.set("Accept-Encoding", "identity");
|
|
62
66
|
const explicitStoreId = options?.xStoreId?.trim();
|
|
63
67
|
const runtimeStoreId = explicitStoreId || (typeof window === "undefined" ? serverStoreIdGetter : clientStoreIdGetter);
|
|
@@ -121,6 +125,10 @@ var StoreApiClient = class {
|
|
|
121
125
|
options
|
|
122
126
|
);
|
|
123
127
|
}
|
|
128
|
+
/** POST multipart (no fuerza `Content-Type`; el runtime añade el boundary). */
|
|
129
|
+
postForm(path, form, options) {
|
|
130
|
+
return this.request(path, { method: "POST", body: form }, options);
|
|
131
|
+
}
|
|
124
132
|
patch(path, data, options) {
|
|
125
133
|
return this.request(path, {
|
|
126
134
|
method: "PATCH",
|
|
@@ -475,6 +483,9 @@ var CartItemRequestSchema = z.object({
|
|
|
475
483
|
quantity: z.number().int().positive(),
|
|
476
484
|
modifiers: z.array(CartItemModifierRequestSchema).optional()
|
|
477
485
|
});
|
|
486
|
+
var CartItemUpdateRequestSchema = z.object({
|
|
487
|
+
quantity: z.number().int().positive()
|
|
488
|
+
});
|
|
478
489
|
var CartCouponRequestSchema = z.object({
|
|
479
490
|
code: z.string().min(1)
|
|
480
491
|
});
|
|
@@ -544,6 +555,8 @@ var CartResponseSchema = z.object({
|
|
|
544
555
|
firstName: z.string().nullable().optional(),
|
|
545
556
|
lastName: z.string().nullable().optional(),
|
|
546
557
|
currencyCode: z.string(),
|
|
558
|
+
/** Estado del carrito en backend (`inProgress`, `completed`, etc.). */
|
|
559
|
+
status: z.string().nullable().optional(),
|
|
547
560
|
completedAt: z.string().nullable().optional(),
|
|
548
561
|
shippingAddress: z.unknown().nullable().optional(),
|
|
549
562
|
billingAddress: z.unknown().nullable().optional(),
|
|
@@ -702,6 +715,26 @@ async function addCartItem(data, cartId) {
|
|
|
702
715
|
});
|
|
703
716
|
return CartResponseSchema.parse(response);
|
|
704
717
|
}
|
|
718
|
+
async function updateCartItem(cartId, itemId, data) {
|
|
719
|
+
if (!cartId) throw new Error("cartId is required");
|
|
720
|
+
if (!itemId.trim()) throw new Error("itemId is required");
|
|
721
|
+
const payload = CartItemUpdateRequestSchema.parse(data);
|
|
722
|
+
const client = await getStoreApiClient();
|
|
723
|
+
const response = await client.patch(
|
|
724
|
+
`carts/${cartId}/items/${encodeURIComponent(itemId.trim())}`,
|
|
725
|
+
payload
|
|
726
|
+
);
|
|
727
|
+
return CartResponseSchema.parse(response);
|
|
728
|
+
}
|
|
729
|
+
async function deleteCartItem(cartId, itemId) {
|
|
730
|
+
if (!cartId) throw new Error("cartId is required");
|
|
731
|
+
if (!itemId.trim()) throw new Error("itemId is required");
|
|
732
|
+
const client = await getStoreApiClient();
|
|
733
|
+
const response = await client.delete(
|
|
734
|
+
`carts/${cartId}/items/${encodeURIComponent(itemId.trim())}`
|
|
735
|
+
);
|
|
736
|
+
return CartResponseSchema.parse(response);
|
|
737
|
+
}
|
|
705
738
|
async function patchCartBuyer(data, cartId) {
|
|
706
739
|
if (!cartId) throw new Error("cartId is required");
|
|
707
740
|
const payload = CartBuyerRequestSchema.parse(data);
|
|
@@ -753,32 +786,15 @@ async function checkoutCart(data, cartId) {
|
|
|
753
786
|
}
|
|
754
787
|
async function uploadPaymentReceipt(cartId, file) {
|
|
755
788
|
if (!cartId) throw new Error("cartId is required");
|
|
756
|
-
const
|
|
789
|
+
const client = await getStoreApiClient();
|
|
757
790
|
const form = new FormData();
|
|
758
791
|
form.set("file", file);
|
|
759
|
-
const response = await
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
body: form,
|
|
764
|
-
...typeof window === "undefined" && { cache: "no-store" }
|
|
765
|
-
}
|
|
792
|
+
const response = await client.postForm(
|
|
793
|
+
`carts/${cartId}/payment-receipt`,
|
|
794
|
+
form,
|
|
795
|
+
typeof window === "undefined" ? { noStore: true } : void 0
|
|
766
796
|
);
|
|
767
|
-
|
|
768
|
-
let body;
|
|
769
|
-
try {
|
|
770
|
-
const errorText = await response.text();
|
|
771
|
-
body = errorText ? JSON.parse(errorText) : void 0;
|
|
772
|
-
} catch {
|
|
773
|
-
body = void 0;
|
|
774
|
-
}
|
|
775
|
-
const message = typeof body === "object" && body !== null && "title" in body && typeof body.title === "string" ? body.title : response.statusText || "Store API error";
|
|
776
|
-
const error = new Error(message);
|
|
777
|
-
error.status = response.status;
|
|
778
|
-
error.body = body;
|
|
779
|
-
throw error;
|
|
780
|
-
}
|
|
781
|
-
return CartPaymentReceiptResponseSchema.parse(await response.json());
|
|
797
|
+
return CartPaymentReceiptResponseSchema.parse(response);
|
|
782
798
|
}
|
|
783
799
|
async function getCartCheckout(cartId) {
|
|
784
800
|
if (!cartId) throw new Error("cartId is required");
|
|
@@ -1366,6 +1382,7 @@ function useStorePages() {
|
|
|
1366
1382
|
useRefreshStorefrontPages
|
|
1367
1383
|
};
|
|
1368
1384
|
}
|
|
1385
|
+
var CART_PERSIST_KEY = "cart-storage";
|
|
1369
1386
|
var useCart = create()(
|
|
1370
1387
|
persist(
|
|
1371
1388
|
(set) => ({
|
|
@@ -1421,6 +1438,14 @@ var useCart = create()(
|
|
|
1421
1438
|
const count = Math.max(0, cart.reduce((sum, i) => sum + Math.max(0, i.quantity), 0));
|
|
1422
1439
|
return { count, cart };
|
|
1423
1440
|
}),
|
|
1441
|
+
replaceCartItems: (items) => set(() => {
|
|
1442
|
+
const cart = items.map((item) => ({
|
|
1443
|
+
...item,
|
|
1444
|
+
quantity: Math.max(0, item.quantity)
|
|
1445
|
+
}));
|
|
1446
|
+
const count = cart.reduce((sum, i) => sum + Math.max(0, i.quantity), 0);
|
|
1447
|
+
return { count, cart };
|
|
1448
|
+
}),
|
|
1424
1449
|
clearCartSession: () => set(() => ({
|
|
1425
1450
|
count: 0,
|
|
1426
1451
|
cart: [],
|
|
@@ -1429,9 +1454,15 @@ var useCart = create()(
|
|
|
1429
1454
|
checkoutCustomerLocal: null
|
|
1430
1455
|
}))
|
|
1431
1456
|
}),
|
|
1432
|
-
{ name:
|
|
1457
|
+
{ name: CART_PERSIST_KEY }
|
|
1433
1458
|
)
|
|
1434
1459
|
);
|
|
1460
|
+
if (typeof window !== "undefined") {
|
|
1461
|
+
window.addEventListener("storage", (event) => {
|
|
1462
|
+
if (event.key !== CART_PERSIST_KEY) return;
|
|
1463
|
+
void useCart.persist.rehydrate();
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1435
1466
|
|
|
1436
1467
|
// src/react/shell/hooks/use-store-cart.ts
|
|
1437
1468
|
function useStoreCart() {
|
|
@@ -1460,6 +1491,12 @@ function useStoreCart() {
|
|
|
1460
1491
|
const useAddCartItem = () => useMutation({
|
|
1461
1492
|
mutationFn: ({ cartId, ...data }) => addCartItem(data, cartId)
|
|
1462
1493
|
});
|
|
1494
|
+
const useUpdateCartItem = () => useMutation({
|
|
1495
|
+
mutationFn: ({ cartId, itemId, data }) => updateCartItem(cartId, itemId, data)
|
|
1496
|
+
});
|
|
1497
|
+
const useDeleteCartItem = () => useMutation({
|
|
1498
|
+
mutationFn: ({ cartId, itemId }) => deleteCartItem(cartId, itemId)
|
|
1499
|
+
});
|
|
1463
1500
|
const useCheckoutCart = () => useMutation({
|
|
1464
1501
|
mutationFn: ({ data, cartId }) => checkoutCart(data, cartId)
|
|
1465
1502
|
});
|
|
@@ -1544,6 +1581,8 @@ function useStoreCart() {
|
|
|
1544
1581
|
useUpdateCartBuyer,
|
|
1545
1582
|
useUpdateCartAddresses,
|
|
1546
1583
|
useAddCartItem,
|
|
1584
|
+
useUpdateCartItem,
|
|
1585
|
+
useDeleteCartItem,
|
|
1547
1586
|
useCheckoutCart,
|
|
1548
1587
|
useUploadPaymentReceipt,
|
|
1549
1588
|
useGetCartPaymentMethods,
|
|
@@ -1913,6 +1952,2814 @@ function formatCurrency(amount, currency = "USD") {
|
|
|
1913
1952
|
currency
|
|
1914
1953
|
}).format(amount);
|
|
1915
1954
|
}
|
|
1955
|
+
var CHECKOUT_FIELD_FOCUS_ORDER = [
|
|
1956
|
+
"email",
|
|
1957
|
+
"firstName",
|
|
1958
|
+
"lastName",
|
|
1959
|
+
"countryCode",
|
|
1960
|
+
"address",
|
|
1961
|
+
"department",
|
|
1962
|
+
"province-geo",
|
|
1963
|
+
"district",
|
|
1964
|
+
"city",
|
|
1965
|
+
"province-region",
|
|
1966
|
+
"phone",
|
|
1967
|
+
"checkout-shipping-method",
|
|
1968
|
+
"checkout-payment-method"
|
|
1969
|
+
];
|
|
1970
|
+
var zodPathToFieldId = {
|
|
1971
|
+
email: "email",
|
|
1972
|
+
firstName: "firstName",
|
|
1973
|
+
lastName: "lastName",
|
|
1974
|
+
countryCode: "countryCode",
|
|
1975
|
+
address: "address",
|
|
1976
|
+
phone: "phone",
|
|
1977
|
+
departmentCode: "department",
|
|
1978
|
+
provinceCode: "province-geo",
|
|
1979
|
+
districtCode: "district",
|
|
1980
|
+
city: "city",
|
|
1981
|
+
province: "province-region",
|
|
1982
|
+
selectedShippingKey: "checkout-shipping-method",
|
|
1983
|
+
selectedPaymentCode: "checkout-payment-method"
|
|
1984
|
+
};
|
|
1985
|
+
function buildCheckoutFormValidationSchema(currency) {
|
|
1986
|
+
const phoneDigits = currency === "PEN" ? 9 : 10;
|
|
1987
|
+
return z.object({
|
|
1988
|
+
email: z.string().trim().min(1, "El email es requerido").email("Ingresa un email v\xE1lido"),
|
|
1989
|
+
firstName: z.string().trim().min(1, "El nombre es requerido").min(2, "El nombre debe tener al menos 2 caracteres"),
|
|
1990
|
+
lastName: z.string().trim().min(1, "El apellido es requerido").min(2, "El apellido debe tener al menos 2 caracteres"),
|
|
1991
|
+
countryCode: z.string().trim().min(2, "Selecciona un pa\xEDs"),
|
|
1992
|
+
address: z.string().trim().min(1, "La direcci\xF3n es requerida").min(5, "La direcci\xF3n debe tener al menos 5 caracteres"),
|
|
1993
|
+
phone: z.string().trim().min(1, "El tel\xE9fono es requerido").regex(/^\d+$/, "El tel\xE9fono solo debe contener n\xFAmeros").length(
|
|
1994
|
+
phoneDigits,
|
|
1995
|
+
`El tel\xE9fono debe tener ${phoneDigits} d\xEDgitos`
|
|
1996
|
+
),
|
|
1997
|
+
departmentCode: z.string(),
|
|
1998
|
+
provinceCode: z.string(),
|
|
1999
|
+
districtCode: z.string(),
|
|
2000
|
+
city: z.string(),
|
|
2001
|
+
province: z.string(),
|
|
2002
|
+
selectedShippingKey: z.string().nullable(),
|
|
2003
|
+
shippingOptionsCount: z.number().int().nonnegative(),
|
|
2004
|
+
selectedPaymentCode: z.string().nullable(),
|
|
2005
|
+
paymentMethodsCount: z.number().int().nonnegative()
|
|
2006
|
+
}).superRefine((data, ctx) => {
|
|
2007
|
+
const country = data.countryCode.toUpperCase();
|
|
2008
|
+
if (country === "PE") {
|
|
2009
|
+
if (!data.departmentCode.trim()) {
|
|
2010
|
+
ctx.addIssue({
|
|
2011
|
+
code: z.ZodIssueCode.custom,
|
|
2012
|
+
path: ["departmentCode"],
|
|
2013
|
+
message: "Selecciona un departamento"
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
if (!data.provinceCode.trim()) {
|
|
2017
|
+
ctx.addIssue({
|
|
2018
|
+
code: z.ZodIssueCode.custom,
|
|
2019
|
+
path: ["provinceCode"],
|
|
2020
|
+
message: "Selecciona una provincia"
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
if (!data.districtCode.trim()) {
|
|
2024
|
+
ctx.addIssue({
|
|
2025
|
+
code: z.ZodIssueCode.custom,
|
|
2026
|
+
path: ["districtCode"],
|
|
2027
|
+
message: "Selecciona un distrito"
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
} else {
|
|
2031
|
+
if (!data.city.trim()) {
|
|
2032
|
+
ctx.addIssue({
|
|
2033
|
+
code: z.ZodIssueCode.custom,
|
|
2034
|
+
path: ["city"],
|
|
2035
|
+
message: "La ciudad es requerida"
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
if (!data.province.trim()) {
|
|
2039
|
+
ctx.addIssue({
|
|
2040
|
+
code: z.ZodIssueCode.custom,
|
|
2041
|
+
path: ["province"],
|
|
2042
|
+
message: "La regi\xF3n es requerida"
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (data.shippingOptionsCount > 0 && !data.selectedShippingKey?.trim()) {
|
|
2047
|
+
ctx.addIssue({
|
|
2048
|
+
code: z.ZodIssueCode.custom,
|
|
2049
|
+
path: ["selectedShippingKey"],
|
|
2050
|
+
message: "Selecciona un m\xE9todo de env\xEDo"
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
if (data.paymentMethodsCount > 0 && !data.selectedPaymentCode?.trim()) {
|
|
2054
|
+
ctx.addIssue({
|
|
2055
|
+
code: z.ZodIssueCode.custom,
|
|
2056
|
+
path: ["selectedPaymentCode"],
|
|
2057
|
+
message: "Selecciona un m\xE9todo de pago"
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
function checkoutValidationInputFromContext(ctx) {
|
|
2063
|
+
const { contact } = ctx;
|
|
2064
|
+
return {
|
|
2065
|
+
email: contact.email,
|
|
2066
|
+
firstName: contact.firstName,
|
|
2067
|
+
lastName: contact.lastName,
|
|
2068
|
+
countryCode: contact.countryCode,
|
|
2069
|
+
address: contact.address,
|
|
2070
|
+
phone: contact.phone,
|
|
2071
|
+
departmentCode: contact.departmentCode,
|
|
2072
|
+
provinceCode: contact.provinceCode,
|
|
2073
|
+
districtCode: contact.districtCode,
|
|
2074
|
+
city: contact.city,
|
|
2075
|
+
province: contact.province,
|
|
2076
|
+
selectedShippingKey: ctx.selectedShippingKey,
|
|
2077
|
+
shippingOptionsCount: ctx.shippingOptionsCount,
|
|
2078
|
+
selectedPaymentCode: ctx.selectedPaymentCode,
|
|
2079
|
+
paymentMethodsCount: ctx.paymentMethodsCount
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
function mapZodIssuesToFieldErrors(error) {
|
|
2083
|
+
const out = {};
|
|
2084
|
+
for (const issue of error.issues) {
|
|
2085
|
+
const key = issue.path[0];
|
|
2086
|
+
if (typeof key !== "string") continue;
|
|
2087
|
+
const fieldId = zodPathToFieldId[key];
|
|
2088
|
+
if (!fieldId || out[fieldId]) continue;
|
|
2089
|
+
out[fieldId] = issue.message;
|
|
2090
|
+
}
|
|
2091
|
+
return out;
|
|
2092
|
+
}
|
|
2093
|
+
function firstInvalidFieldId(errors) {
|
|
2094
|
+
for (const id of CHECKOUT_FIELD_FOCUS_ORDER) {
|
|
2095
|
+
if (errors[id]) return id;
|
|
2096
|
+
}
|
|
2097
|
+
return null;
|
|
2098
|
+
}
|
|
2099
|
+
function focusCheckoutField(fieldId) {
|
|
2100
|
+
if (typeof document === "undefined") return;
|
|
2101
|
+
const el = document.getElementById(fieldId);
|
|
2102
|
+
if (el) {
|
|
2103
|
+
el.focus({ preventScroll: false });
|
|
2104
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (fieldId === "checkout-shipping-method") {
|
|
2108
|
+
const radio = document.querySelector(
|
|
2109
|
+
'input[name="shipping"]'
|
|
2110
|
+
);
|
|
2111
|
+
radio?.focus();
|
|
2112
|
+
radio?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
if (fieldId === "checkout-payment-method") {
|
|
2116
|
+
const radio = document.querySelector(
|
|
2117
|
+
'input[name="payment"]'
|
|
2118
|
+
);
|
|
2119
|
+
radio?.focus();
|
|
2120
|
+
radio?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/react/pages/checkout-page/hooks/use-checkout-form-validation.ts
|
|
2125
|
+
function useCheckoutFormValidation(currency = "PEN") {
|
|
2126
|
+
const schema = useMemo(
|
|
2127
|
+
() => buildCheckoutFormValidationSchema(currency),
|
|
2128
|
+
[currency]
|
|
2129
|
+
);
|
|
2130
|
+
const [fieldErrors, setFieldErrors] = useState({});
|
|
2131
|
+
const [validationError, setValidationError] = useState(null);
|
|
2132
|
+
const clearFieldError = useCallback((fieldId) => {
|
|
2133
|
+
setFieldErrors((prev) => {
|
|
2134
|
+
if (!prev[fieldId]) return prev;
|
|
2135
|
+
const next = { ...prev };
|
|
2136
|
+
delete next[fieldId];
|
|
2137
|
+
return next;
|
|
2138
|
+
});
|
|
2139
|
+
setValidationError(null);
|
|
2140
|
+
}, []);
|
|
2141
|
+
const getFieldError = useCallback(
|
|
2142
|
+
(fieldId) => fieldErrors[fieldId],
|
|
2143
|
+
[fieldErrors]
|
|
2144
|
+
);
|
|
2145
|
+
const validateCheckoutForm = useCallback(
|
|
2146
|
+
(context) => {
|
|
2147
|
+
const result = schema.safeParse(
|
|
2148
|
+
checkoutValidationInputFromContext(context)
|
|
2149
|
+
);
|
|
2150
|
+
if (result.success) {
|
|
2151
|
+
setFieldErrors({});
|
|
2152
|
+
setValidationError(null);
|
|
2153
|
+
return true;
|
|
2154
|
+
}
|
|
2155
|
+
const errors = mapZodIssuesToFieldErrors(result.error);
|
|
2156
|
+
setFieldErrors(errors);
|
|
2157
|
+
const firstId = firstInvalidFieldId(errors);
|
|
2158
|
+
if (firstId) {
|
|
2159
|
+
focusCheckoutField(firstId);
|
|
2160
|
+
setValidationError(errors[firstId] ?? null);
|
|
2161
|
+
} else {
|
|
2162
|
+
setValidationError("Completa los campos requeridos.");
|
|
2163
|
+
}
|
|
2164
|
+
return false;
|
|
2165
|
+
},
|
|
2166
|
+
[schema]
|
|
2167
|
+
);
|
|
2168
|
+
return {
|
|
2169
|
+
fieldErrors,
|
|
2170
|
+
validationError,
|
|
2171
|
+
setValidationError,
|
|
2172
|
+
validateCheckoutForm,
|
|
2173
|
+
clearFieldError,
|
|
2174
|
+
getFieldError
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/core/lib/storefront-geo-inei.ts
|
|
2179
|
+
function toGeoItems(data) {
|
|
2180
|
+
if (!Array.isArray(data)) return [];
|
|
2181
|
+
return data.map((raw) => {
|
|
2182
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2183
|
+
const item = raw;
|
|
2184
|
+
return {
|
|
2185
|
+
code: String(item.code ?? ""),
|
|
2186
|
+
name: String(item.name ?? "")
|
|
2187
|
+
};
|
|
2188
|
+
}).filter((x) => !!x && !!x.code && !!x.name);
|
|
2189
|
+
}
|
|
2190
|
+
var departmentsCache = null;
|
|
2191
|
+
var departmentsRequest = null;
|
|
2192
|
+
function loadStorefrontGeoDepartments(headers) {
|
|
2193
|
+
if (departmentsCache) return Promise.resolve(departmentsCache);
|
|
2194
|
+
if (departmentsRequest) return departmentsRequest;
|
|
2195
|
+
departmentsRequest = fetch(
|
|
2196
|
+
`/api/store/geo/departments?countryCode=PE&source=inei&limit=200`,
|
|
2197
|
+
{ cache: "no-store", headers }
|
|
2198
|
+
).then((r) => r.json()).then((data) => {
|
|
2199
|
+
const items = toGeoItems(data);
|
|
2200
|
+
departmentsCache = items;
|
|
2201
|
+
return items;
|
|
2202
|
+
}).catch(() => {
|
|
2203
|
+
departmentsCache = [];
|
|
2204
|
+
return [];
|
|
2205
|
+
}).finally(() => {
|
|
2206
|
+
departmentsRequest = null;
|
|
2207
|
+
});
|
|
2208
|
+
return departmentsRequest;
|
|
2209
|
+
}
|
|
2210
|
+
async function fetchStorefrontGeoProvinces(headers, countryCode, departmentCode) {
|
|
2211
|
+
const res = await fetch(
|
|
2212
|
+
`/api/store/geo/provinces?countryCode=${encodeURIComponent(
|
|
2213
|
+
countryCode
|
|
2214
|
+
)}&departmentCode=${encodeURIComponent(departmentCode)}&source=inei&limit=200`,
|
|
2215
|
+
{ headers }
|
|
2216
|
+
);
|
|
2217
|
+
const data = await res.json();
|
|
2218
|
+
return toGeoItems(data);
|
|
2219
|
+
}
|
|
2220
|
+
async function fetchStorefrontGeoDistricts(headers, countryCode, provinceCode) {
|
|
2221
|
+
const res = await fetch(
|
|
2222
|
+
`/api/store/geo/districts?countryCode=${encodeURIComponent(
|
|
2223
|
+
countryCode
|
|
2224
|
+
)}&provinceCode=${encodeURIComponent(provinceCode)}&source=inei`,
|
|
2225
|
+
{ headers }
|
|
2226
|
+
);
|
|
2227
|
+
const data = await res.json();
|
|
2228
|
+
return toGeoItems(data);
|
|
2229
|
+
}
|
|
2230
|
+
async function resolveIneiCodesFromLabels(storeId, labels, countryCode = "PE") {
|
|
2231
|
+
const id = storeId.trim();
|
|
2232
|
+
if (!id) return null;
|
|
2233
|
+
const headers = { "X-Store-Id": id };
|
|
2234
|
+
const departments = await loadStorefrontGeoDepartments(headers);
|
|
2235
|
+
const dept = departments.find((d) => d.name.trim() === labels.department.trim());
|
|
2236
|
+
if (!dept) return null;
|
|
2237
|
+
const provinces = await fetchStorefrontGeoProvinces(headers, countryCode, dept.code);
|
|
2238
|
+
const prov = provinces.find((p) => p.name.trim() === labels.province.trim());
|
|
2239
|
+
if (!prov) {
|
|
2240
|
+
return { departmentCode: dept.code, provinceCode: "", districtCode: "" };
|
|
2241
|
+
}
|
|
2242
|
+
const districts = await fetchStorefrontGeoDistricts(headers, countryCode, prov.code);
|
|
2243
|
+
const dist = districts.find((d) => d.name.trim() === labels.district.trim());
|
|
2244
|
+
return {
|
|
2245
|
+
departmentCode: dept.code,
|
|
2246
|
+
provinceCode: prov.code,
|
|
2247
|
+
districtCode: dist?.code ?? ""
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// src/react/pages/checkout-page/hooks/use-checkout-delivery-form.ts
|
|
2252
|
+
function useCheckoutDeliveryForm({
|
|
2253
|
+
storeId,
|
|
2254
|
+
contact,
|
|
2255
|
+
onContactChange
|
|
2256
|
+
}) {
|
|
2257
|
+
const [departments, setDepartments] = useState([]);
|
|
2258
|
+
const [provinces, setProvinces] = useState([]);
|
|
2259
|
+
const [districts, setDistricts] = useState([]);
|
|
2260
|
+
const [loadingDepartments, setLoadingDepartments] = useState(false);
|
|
2261
|
+
const [loadingProvinces, setLoadingProvinces] = useState(false);
|
|
2262
|
+
const [loadingDistricts, setLoadingDistricts] = useState(false);
|
|
2263
|
+
const prevDepartmentCode = useRef(void 0);
|
|
2264
|
+
const prevProvinceCode = useRef(void 0);
|
|
2265
|
+
const resolvedLabelsRef = useRef(false);
|
|
2266
|
+
const onChangeRef = useRef(onContactChange);
|
|
2267
|
+
onChangeRef.current = onContactChange;
|
|
2268
|
+
const patchContact = useCallback((patch) => {
|
|
2269
|
+
onChangeRef.current(patch);
|
|
2270
|
+
}, []);
|
|
2271
|
+
const countryCode = (contact.countryCode || "PE").toUpperCase();
|
|
2272
|
+
const isPeru = countryCode === "PE";
|
|
2273
|
+
const storeRequestHeaders = useMemo(() => {
|
|
2274
|
+
const id = storeId.trim();
|
|
2275
|
+
if (!id) return null;
|
|
2276
|
+
return { "X-Store-Id": id };
|
|
2277
|
+
}, [storeId]);
|
|
2278
|
+
useEffect(() => {
|
|
2279
|
+
if (!isPeru || !storeRequestHeaders) {
|
|
2280
|
+
setDepartments([]);
|
|
2281
|
+
setLoadingDepartments(false);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
let active = true;
|
|
2285
|
+
setLoadingDepartments(true);
|
|
2286
|
+
loadStorefrontGeoDepartments(storeRequestHeaders).then((items) => {
|
|
2287
|
+
if (active) setDepartments(items);
|
|
2288
|
+
}).finally(() => {
|
|
2289
|
+
if (active) setLoadingDepartments(false);
|
|
2290
|
+
});
|
|
2291
|
+
return () => {
|
|
2292
|
+
active = false;
|
|
2293
|
+
};
|
|
2294
|
+
}, [isPeru, storeRequestHeaders]);
|
|
2295
|
+
useEffect(() => {
|
|
2296
|
+
if (!isPeru || !storeId.trim()) return;
|
|
2297
|
+
if (contact.departmentCode) return;
|
|
2298
|
+
if (resolvedLabelsRef.current) return;
|
|
2299
|
+
if (!contact.departmentName.trim()) return;
|
|
2300
|
+
if (!contact.province && !contact.city) return;
|
|
2301
|
+
resolvedLabelsRef.current = true;
|
|
2302
|
+
let cancelled = false;
|
|
2303
|
+
void resolveIneiCodesFromLabels(storeId, {
|
|
2304
|
+
department: contact.departmentName,
|
|
2305
|
+
province: contact.province,
|
|
2306
|
+
district: contact.city
|
|
2307
|
+
}).then((codes) => {
|
|
2308
|
+
if (cancelled || !codes) return;
|
|
2309
|
+
patchContact({
|
|
2310
|
+
departmentCode: codes.departmentCode,
|
|
2311
|
+
provinceCode: codes.provinceCode,
|
|
2312
|
+
districtCode: codes.districtCode
|
|
2313
|
+
});
|
|
2314
|
+
});
|
|
2315
|
+
return () => {
|
|
2316
|
+
cancelled = true;
|
|
2317
|
+
};
|
|
2318
|
+
}, [
|
|
2319
|
+
contact.city,
|
|
2320
|
+
contact.departmentCode,
|
|
2321
|
+
contact.departmentName,
|
|
2322
|
+
contact.province,
|
|
2323
|
+
isPeru,
|
|
2324
|
+
patchContact,
|
|
2325
|
+
storeId
|
|
2326
|
+
]);
|
|
2327
|
+
useEffect(() => {
|
|
2328
|
+
const dept = contact.departmentCode;
|
|
2329
|
+
const prev = prevDepartmentCode.current;
|
|
2330
|
+
const departmentChanged = prev !== void 0 && prev !== dept;
|
|
2331
|
+
prevDepartmentCode.current = dept;
|
|
2332
|
+
if (!isPeru || !storeRequestHeaders) {
|
|
2333
|
+
setProvinces([]);
|
|
2334
|
+
if (!dept) {
|
|
2335
|
+
setDistricts([]);
|
|
2336
|
+
prevProvinceCode.current = "";
|
|
2337
|
+
}
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
if (!dept) {
|
|
2341
|
+
setProvinces([]);
|
|
2342
|
+
setDistricts([]);
|
|
2343
|
+
prevProvinceCode.current = "";
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (departmentChanged) {
|
|
2347
|
+
patchContact({
|
|
2348
|
+
provinceCode: "",
|
|
2349
|
+
provinceName: "",
|
|
2350
|
+
districtCode: "",
|
|
2351
|
+
districtName: "",
|
|
2352
|
+
province: "",
|
|
2353
|
+
city: "",
|
|
2354
|
+
postalCode: ""
|
|
2355
|
+
});
|
|
2356
|
+
setDistricts([]);
|
|
2357
|
+
prevProvinceCode.current = "";
|
|
2358
|
+
}
|
|
2359
|
+
const deptName = departments.find((d) => d.code === dept)?.name ?? "";
|
|
2360
|
+
if (deptName && deptName !== contact.departmentName) {
|
|
2361
|
+
patchContact({ departmentName: deptName });
|
|
2362
|
+
}
|
|
2363
|
+
let active = true;
|
|
2364
|
+
setLoadingProvinces(true);
|
|
2365
|
+
fetchStorefrontGeoProvinces(storeRequestHeaders, countryCode, dept).then((items) => {
|
|
2366
|
+
if (active) setProvinces(items);
|
|
2367
|
+
}).catch(() => {
|
|
2368
|
+
if (active) setProvinces([]);
|
|
2369
|
+
}).finally(() => {
|
|
2370
|
+
if (active) setLoadingProvinces(false);
|
|
2371
|
+
});
|
|
2372
|
+
return () => {
|
|
2373
|
+
active = false;
|
|
2374
|
+
};
|
|
2375
|
+
}, [
|
|
2376
|
+
contact.departmentCode,
|
|
2377
|
+
contact.departmentName,
|
|
2378
|
+
countryCode,
|
|
2379
|
+
departments,
|
|
2380
|
+
isPeru,
|
|
2381
|
+
patchContact,
|
|
2382
|
+
storeRequestHeaders
|
|
2383
|
+
]);
|
|
2384
|
+
useEffect(() => {
|
|
2385
|
+
const prov = contact.provinceCode;
|
|
2386
|
+
const prev = prevProvinceCode.current;
|
|
2387
|
+
const provinceChanged = prev !== void 0 && prev !== prov;
|
|
2388
|
+
prevProvinceCode.current = prov;
|
|
2389
|
+
if (!isPeru || !storeRequestHeaders) {
|
|
2390
|
+
setDistricts([]);
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
if (!prov) {
|
|
2394
|
+
setDistricts([]);
|
|
2395
|
+
prevProvinceCode.current = "";
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
if (provinceChanged) {
|
|
2399
|
+
patchContact({
|
|
2400
|
+
districtCode: "",
|
|
2401
|
+
districtName: "",
|
|
2402
|
+
city: "",
|
|
2403
|
+
postalCode: ""
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
const provName = provinces.find((p) => p.code === prov)?.name ?? "";
|
|
2407
|
+
if (provName && provName !== contact.provinceName) {
|
|
2408
|
+
patchContact({ provinceName: provName, province: provName });
|
|
2409
|
+
}
|
|
2410
|
+
let active = true;
|
|
2411
|
+
setLoadingDistricts(true);
|
|
2412
|
+
fetchStorefrontGeoDistricts(storeRequestHeaders, countryCode, prov).then((items) => {
|
|
2413
|
+
if (active) setDistricts(items);
|
|
2414
|
+
}).catch(() => {
|
|
2415
|
+
if (active) setDistricts([]);
|
|
2416
|
+
}).finally(() => {
|
|
2417
|
+
if (active) setLoadingDistricts(false);
|
|
2418
|
+
});
|
|
2419
|
+
return () => {
|
|
2420
|
+
active = false;
|
|
2421
|
+
};
|
|
2422
|
+
}, [
|
|
2423
|
+
contact.provinceCode,
|
|
2424
|
+
contact.provinceName,
|
|
2425
|
+
countryCode,
|
|
2426
|
+
isPeru,
|
|
2427
|
+
patchContact,
|
|
2428
|
+
provinces,
|
|
2429
|
+
storeRequestHeaders
|
|
2430
|
+
]);
|
|
2431
|
+
useEffect(() => {
|
|
2432
|
+
if (!contact.districtCode) return;
|
|
2433
|
+
const distName = districts.find((d) => d.code === contact.districtCode)?.name ?? "";
|
|
2434
|
+
const patch = {};
|
|
2435
|
+
if (distName && distName !== contact.districtName) {
|
|
2436
|
+
patch.districtName = distName;
|
|
2437
|
+
patch.city = distName;
|
|
2438
|
+
}
|
|
2439
|
+
if (contact.postalCode !== contact.districtCode) {
|
|
2440
|
+
patch.postalCode = contact.districtCode;
|
|
2441
|
+
}
|
|
2442
|
+
if (Object.keys(patch).length > 0) patchContact(patch);
|
|
2443
|
+
}, [
|
|
2444
|
+
contact.districtCode,
|
|
2445
|
+
contact.districtName,
|
|
2446
|
+
contact.postalCode,
|
|
2447
|
+
districts,
|
|
2448
|
+
patchContact
|
|
2449
|
+
]);
|
|
2450
|
+
const handleDepartmentChange = useCallback(
|
|
2451
|
+
(code) => {
|
|
2452
|
+
const name = departments.find((d) => d.code === code)?.name ?? "";
|
|
2453
|
+
prevDepartmentCode.current = code;
|
|
2454
|
+
prevProvinceCode.current = "";
|
|
2455
|
+
patchContact({
|
|
2456
|
+
departmentCode: code,
|
|
2457
|
+
departmentName: name,
|
|
2458
|
+
provinceCode: "",
|
|
2459
|
+
provinceName: "",
|
|
2460
|
+
districtCode: "",
|
|
2461
|
+
districtName: "",
|
|
2462
|
+
province: "",
|
|
2463
|
+
city: "",
|
|
2464
|
+
postalCode: ""
|
|
2465
|
+
});
|
|
2466
|
+
},
|
|
2467
|
+
[departments, patchContact]
|
|
2468
|
+
);
|
|
2469
|
+
const handleProvinceChange = useCallback(
|
|
2470
|
+
(code) => {
|
|
2471
|
+
const name = provinces.find((p) => p.code === code)?.name ?? "";
|
|
2472
|
+
prevProvinceCode.current = code;
|
|
2473
|
+
patchContact({
|
|
2474
|
+
provinceCode: code,
|
|
2475
|
+
provinceName: name,
|
|
2476
|
+
province: name,
|
|
2477
|
+
districtCode: "",
|
|
2478
|
+
districtName: "",
|
|
2479
|
+
city: "",
|
|
2480
|
+
postalCode: ""
|
|
2481
|
+
});
|
|
2482
|
+
},
|
|
2483
|
+
[patchContact, provinces]
|
|
2484
|
+
);
|
|
2485
|
+
const handleDistrictChange = useCallback(
|
|
2486
|
+
(code) => {
|
|
2487
|
+
const name = districts.find((d) => d.code === code)?.name ?? "";
|
|
2488
|
+
patchContact({
|
|
2489
|
+
districtCode: code,
|
|
2490
|
+
districtName: name,
|
|
2491
|
+
city: name,
|
|
2492
|
+
postalCode: code
|
|
2493
|
+
});
|
|
2494
|
+
},
|
|
2495
|
+
[districts, patchContact]
|
|
2496
|
+
);
|
|
2497
|
+
return {
|
|
2498
|
+
isPeru,
|
|
2499
|
+
departments,
|
|
2500
|
+
provinces,
|
|
2501
|
+
districts,
|
|
2502
|
+
loadingDepartments,
|
|
2503
|
+
loadingProvinces,
|
|
2504
|
+
loadingDistricts,
|
|
2505
|
+
handleDepartmentChange,
|
|
2506
|
+
handleProvinceChange,
|
|
2507
|
+
handleDistrictChange
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
function CheckoutTextField({
|
|
2511
|
+
id,
|
|
2512
|
+
label,
|
|
2513
|
+
value,
|
|
2514
|
+
onChange,
|
|
2515
|
+
onClearError,
|
|
2516
|
+
error,
|
|
2517
|
+
type = "text",
|
|
2518
|
+
optional,
|
|
2519
|
+
half
|
|
2520
|
+
}) {
|
|
2521
|
+
return /* @__PURE__ */ jsxs(
|
|
2522
|
+
"label",
|
|
2523
|
+
{
|
|
2524
|
+
className: `ecomiq-checkout-page__field${half ? " ecomiq-checkout-page__field--half" : ""}`,
|
|
2525
|
+
children: [
|
|
2526
|
+
/* @__PURE__ */ jsxs("span", { className: "ecomiq-checkout-page__label", children: [
|
|
2527
|
+
label,
|
|
2528
|
+
optional ? " (optional)" : ""
|
|
2529
|
+
] }),
|
|
2530
|
+
/* @__PURE__ */ jsx(
|
|
2531
|
+
"input",
|
|
2532
|
+
{
|
|
2533
|
+
id,
|
|
2534
|
+
type,
|
|
2535
|
+
className: `ecomiq-checkout-page__input${error ? " ecomiq-checkout-page__input--error" : ""}`,
|
|
2536
|
+
value,
|
|
2537
|
+
"aria-invalid": error ? true : void 0,
|
|
2538
|
+
"aria-describedby": error ? `${id}-error` : void 0,
|
|
2539
|
+
onChange: (e) => {
|
|
2540
|
+
onClearError?.();
|
|
2541
|
+
onChange(e.target.value);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
),
|
|
2545
|
+
error ? /* @__PURE__ */ jsx("span", { id: `${id}-error`, className: "ecomiq-checkout-page__field-error-inline", role: "alert", children: error }) : null
|
|
2546
|
+
]
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
function CheckoutSelectField({
|
|
2551
|
+
id,
|
|
2552
|
+
label,
|
|
2553
|
+
value,
|
|
2554
|
+
onChange,
|
|
2555
|
+
onClearError,
|
|
2556
|
+
error,
|
|
2557
|
+
disabled,
|
|
2558
|
+
children
|
|
2559
|
+
}) {
|
|
2560
|
+
return /* @__PURE__ */ jsxs("label", { className: "ecomiq-checkout-page__field", children: [
|
|
2561
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__label", children: label }),
|
|
2562
|
+
/* @__PURE__ */ jsx(
|
|
2563
|
+
"select",
|
|
2564
|
+
{
|
|
2565
|
+
id,
|
|
2566
|
+
className: `ecomiq-checkout-page__input${error ? " ecomiq-checkout-page__input--error" : ""}`,
|
|
2567
|
+
value,
|
|
2568
|
+
disabled,
|
|
2569
|
+
"aria-invalid": error ? true : void 0,
|
|
2570
|
+
"aria-describedby": error ? `${id}-error` : void 0,
|
|
2571
|
+
onChange: (e) => {
|
|
2572
|
+
onClearError?.();
|
|
2573
|
+
onChange(e.target.value);
|
|
2574
|
+
},
|
|
2575
|
+
children
|
|
2576
|
+
}
|
|
2577
|
+
),
|
|
2578
|
+
error ? /* @__PURE__ */ jsx("span", { id: `${id}-error`, className: "ecomiq-checkout-page__field-error-inline", role: "alert", children: error }) : null
|
|
2579
|
+
] });
|
|
2580
|
+
}
|
|
2581
|
+
function geoSelect(id, label, value, placeholder, disabled, options, onChange, error, onClearError) {
|
|
2582
|
+
return /* @__PURE__ */ jsxs(
|
|
2583
|
+
CheckoutSelectField,
|
|
2584
|
+
{
|
|
2585
|
+
id,
|
|
2586
|
+
label,
|
|
2587
|
+
value,
|
|
2588
|
+
error,
|
|
2589
|
+
disabled,
|
|
2590
|
+
onClearError,
|
|
2591
|
+
onChange,
|
|
2592
|
+
children: [
|
|
2593
|
+
/* @__PURE__ */ jsx("option", { value: "", children: placeholder }),
|
|
2594
|
+
options.map((item) => /* @__PURE__ */ jsx("option", { value: item.code, children: item.name }, item.code))
|
|
2595
|
+
]
|
|
2596
|
+
}
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
function CheckoutDeliveryForm({
|
|
2600
|
+
storeId,
|
|
2601
|
+
contact,
|
|
2602
|
+
onContactChange,
|
|
2603
|
+
getFieldError,
|
|
2604
|
+
clearFieldError
|
|
2605
|
+
}) {
|
|
2606
|
+
const {
|
|
2607
|
+
isPeru,
|
|
2608
|
+
departments,
|
|
2609
|
+
provinces,
|
|
2610
|
+
districts,
|
|
2611
|
+
loadingDepartments,
|
|
2612
|
+
loadingProvinces,
|
|
2613
|
+
loadingDistricts,
|
|
2614
|
+
handleDepartmentChange,
|
|
2615
|
+
handleProvinceChange,
|
|
2616
|
+
handleDistrictChange
|
|
2617
|
+
} = useCheckoutDeliveryForm({ storeId, contact, onContactChange });
|
|
2618
|
+
return /* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
2619
|
+
/* @__PURE__ */ jsx("h2", { children: "Delivery" }),
|
|
2620
|
+
/* @__PURE__ */ jsxs(
|
|
2621
|
+
CheckoutSelectField,
|
|
2622
|
+
{
|
|
2623
|
+
id: "countryCode",
|
|
2624
|
+
label: "Country/Region",
|
|
2625
|
+
value: contact.countryCode,
|
|
2626
|
+
error: getFieldError("countryCode"),
|
|
2627
|
+
onClearError: () => clearFieldError("countryCode"),
|
|
2628
|
+
onChange: (v) => onContactChange({ countryCode: v }),
|
|
2629
|
+
children: [
|
|
2630
|
+
/* @__PURE__ */ jsx("option", { value: "PE", children: "Peru" }),
|
|
2631
|
+
/* @__PURE__ */ jsx("option", { value: "US", children: "United States" }),
|
|
2632
|
+
/* @__PURE__ */ jsx("option", { value: "MX", children: "Mexico" }),
|
|
2633
|
+
/* @__PURE__ */ jsx("option", { value: "CO", children: "Colombia" })
|
|
2634
|
+
]
|
|
2635
|
+
}
|
|
2636
|
+
),
|
|
2637
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__row", children: [
|
|
2638
|
+
/* @__PURE__ */ jsx(
|
|
2639
|
+
CheckoutTextField,
|
|
2640
|
+
{
|
|
2641
|
+
id: "firstName",
|
|
2642
|
+
label: "First name",
|
|
2643
|
+
half: true,
|
|
2644
|
+
value: contact.firstName,
|
|
2645
|
+
error: getFieldError("firstName"),
|
|
2646
|
+
onClearError: () => clearFieldError("firstName"),
|
|
2647
|
+
onChange: (v) => onContactChange({ firstName: v })
|
|
2648
|
+
}
|
|
2649
|
+
),
|
|
2650
|
+
/* @__PURE__ */ jsx(
|
|
2651
|
+
CheckoutTextField,
|
|
2652
|
+
{
|
|
2653
|
+
id: "lastName",
|
|
2654
|
+
label: "Last name",
|
|
2655
|
+
half: true,
|
|
2656
|
+
value: contact.lastName,
|
|
2657
|
+
error: getFieldError("lastName"),
|
|
2658
|
+
onClearError: () => clearFieldError("lastName"),
|
|
2659
|
+
onChange: (v) => onContactChange({ lastName: v })
|
|
2660
|
+
}
|
|
2661
|
+
)
|
|
2662
|
+
] }),
|
|
2663
|
+
/* @__PURE__ */ jsx(
|
|
2664
|
+
CheckoutTextField,
|
|
2665
|
+
{
|
|
2666
|
+
id: "address",
|
|
2667
|
+
label: "Address",
|
|
2668
|
+
value: contact.address,
|
|
2669
|
+
error: getFieldError("address"),
|
|
2670
|
+
onClearError: () => clearFieldError("address"),
|
|
2671
|
+
onChange: (v) => onContactChange({ address: v })
|
|
2672
|
+
}
|
|
2673
|
+
),
|
|
2674
|
+
isPeru ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__row ecomiq-checkout-page__row--3", children: [
|
|
2675
|
+
geoSelect(
|
|
2676
|
+
"department",
|
|
2677
|
+
"Departamento",
|
|
2678
|
+
contact.departmentCode,
|
|
2679
|
+
loadingDepartments ? "Cargando\u2026" : "Selecciona",
|
|
2680
|
+
loadingDepartments || departments.length === 0,
|
|
2681
|
+
departments,
|
|
2682
|
+
handleDepartmentChange,
|
|
2683
|
+
getFieldError("department"),
|
|
2684
|
+
() => clearFieldError("department")
|
|
2685
|
+
),
|
|
2686
|
+
geoSelect(
|
|
2687
|
+
"province-geo",
|
|
2688
|
+
"Provincia",
|
|
2689
|
+
contact.provinceCode,
|
|
2690
|
+
!contact.departmentCode ? "Selecciona depto." : loadingProvinces ? "Cargando\u2026" : "Selecciona",
|
|
2691
|
+
!contact.departmentCode || loadingProvinces || provinces.length === 0,
|
|
2692
|
+
provinces,
|
|
2693
|
+
handleProvinceChange,
|
|
2694
|
+
getFieldError("province-geo"),
|
|
2695
|
+
() => clearFieldError("province-geo")
|
|
2696
|
+
),
|
|
2697
|
+
geoSelect(
|
|
2698
|
+
"district",
|
|
2699
|
+
"Distrito",
|
|
2700
|
+
contact.districtCode,
|
|
2701
|
+
!contact.provinceCode ? "Selecciona prov." : loadingDistricts ? "Cargando\u2026" : "Selecciona",
|
|
2702
|
+
!contact.provinceCode || loadingDistricts || districts.length === 0,
|
|
2703
|
+
districts,
|
|
2704
|
+
handleDistrictChange,
|
|
2705
|
+
getFieldError("district"),
|
|
2706
|
+
() => clearFieldError("district")
|
|
2707
|
+
)
|
|
2708
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__row ecomiq-checkout-page__row--3", children: [
|
|
2709
|
+
/* @__PURE__ */ jsx(
|
|
2710
|
+
CheckoutTextField,
|
|
2711
|
+
{
|
|
2712
|
+
id: "city",
|
|
2713
|
+
label: "City",
|
|
2714
|
+
value: contact.city,
|
|
2715
|
+
error: getFieldError("city"),
|
|
2716
|
+
onClearError: () => clearFieldError("city"),
|
|
2717
|
+
onChange: (v) => onContactChange({ city: v })
|
|
2718
|
+
}
|
|
2719
|
+
),
|
|
2720
|
+
/* @__PURE__ */ jsx(
|
|
2721
|
+
CheckoutTextField,
|
|
2722
|
+
{
|
|
2723
|
+
id: "province-region",
|
|
2724
|
+
label: "Region",
|
|
2725
|
+
value: contact.province,
|
|
2726
|
+
error: getFieldError("province-region"),
|
|
2727
|
+
onClearError: () => clearFieldError("province-region"),
|
|
2728
|
+
onChange: (v) => onContactChange({ province: v })
|
|
2729
|
+
}
|
|
2730
|
+
)
|
|
2731
|
+
] }),
|
|
2732
|
+
/* @__PURE__ */ jsx(
|
|
2733
|
+
CheckoutTextField,
|
|
2734
|
+
{
|
|
2735
|
+
id: "postalCode",
|
|
2736
|
+
label: "Postal code",
|
|
2737
|
+
optional: true,
|
|
2738
|
+
value: contact.postalCode,
|
|
2739
|
+
onChange: (v) => onContactChange({ postalCode: v })
|
|
2740
|
+
}
|
|
2741
|
+
),
|
|
2742
|
+
/* @__PURE__ */ jsx(
|
|
2743
|
+
CheckoutTextField,
|
|
2744
|
+
{
|
|
2745
|
+
id: "phone",
|
|
2746
|
+
label: "Phone",
|
|
2747
|
+
type: "tel",
|
|
2748
|
+
value: contact.phone,
|
|
2749
|
+
error: getFieldError("phone"),
|
|
2750
|
+
onClearError: () => clearFieldError("phone"),
|
|
2751
|
+
onChange: (v) => onContactChange({ phone: v })
|
|
2752
|
+
}
|
|
2753
|
+
)
|
|
2754
|
+
] });
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
// src/react/components/checkout/payment-providers/checkout-payment-helpers.ts
|
|
2758
|
+
function getPaymentFromCheckout(checkout) {
|
|
2759
|
+
if (!checkout) return null;
|
|
2760
|
+
const raw = checkout.payment;
|
|
2761
|
+
if (!raw || typeof raw !== "object") return null;
|
|
2762
|
+
return raw;
|
|
2763
|
+
}
|
|
2764
|
+
function resolveStripeElementConfig(checkout) {
|
|
2765
|
+
if (!checkout) return null;
|
|
2766
|
+
const payment = getPaymentFromCheckout(checkout);
|
|
2767
|
+
const nestedPk = payment?.action?.publicConfiguration?.publishableKey;
|
|
2768
|
+
const nestedSecret = payment?.action?.clientSecret;
|
|
2769
|
+
const publicKey = (typeof nestedPk === "string" ? nestedPk : void 0) ?? checkout.stripePublicKey ?? checkout.publishableKey ?? checkout.publicKey ?? void 0;
|
|
2770
|
+
const clientSecret = (typeof nestedSecret === "string" ? nestedSecret : void 0) ?? checkout.clientSecret ?? checkout.intentClientSecret ?? void 0;
|
|
2771
|
+
if (!publicKey || !clientSecret) return null;
|
|
2772
|
+
return { publicKey, clientSecret };
|
|
2773
|
+
}
|
|
2774
|
+
function normalizeProviderId(raw) {
|
|
2775
|
+
return (raw ?? "").trim().toLowerCase();
|
|
2776
|
+
}
|
|
2777
|
+
function getProcessingReturnUrl() {
|
|
2778
|
+
if (typeof window === "undefined") return "";
|
|
2779
|
+
return `${window.location.origin}/store/checkout/processing`;
|
|
2780
|
+
}
|
|
2781
|
+
function RedirectPaymentButton({
|
|
2782
|
+
url,
|
|
2783
|
+
label
|
|
2784
|
+
}) {
|
|
2785
|
+
return /* @__PURE__ */ jsx(
|
|
2786
|
+
"a",
|
|
2787
|
+
{
|
|
2788
|
+
href: url,
|
|
2789
|
+
className: "ecomiq-payment-provider__redirect",
|
|
2790
|
+
target: "_blank",
|
|
2791
|
+
rel: "noopener noreferrer",
|
|
2792
|
+
children: label
|
|
2793
|
+
}
|
|
2794
|
+
);
|
|
2795
|
+
}
|
|
2796
|
+
function PaymentPanel({ children }) {
|
|
2797
|
+
return /* @__PURE__ */ jsx("div", { className: "ecomiq-payment-provider__panel", children });
|
|
2798
|
+
}
|
|
2799
|
+
function ExpiresNotice({ expiresAt }) {
|
|
2800
|
+
if (!expiresAt) return null;
|
|
2801
|
+
return /* @__PURE__ */ jsxs("p", { className: "ecomiq-payment-provider__hint ecomiq-payment-provider__hint--sm", children: [
|
|
2802
|
+
"Vence: ",
|
|
2803
|
+
/* @__PURE__ */ jsx("time", { dateTime: expiresAt, children: expiresAt })
|
|
2804
|
+
] });
|
|
2805
|
+
}
|
|
2806
|
+
var PaymentSubmissionContext = createContext(null);
|
|
2807
|
+
function PaymentSubmissionProvider({ children }) {
|
|
2808
|
+
const submitRef = useRef(null);
|
|
2809
|
+
const [hasSubmitHandler, setHasSubmitHandler] = useState(false);
|
|
2810
|
+
const registerSubmit = useCallback((fn) => {
|
|
2811
|
+
submitRef.current = fn;
|
|
2812
|
+
setHasSubmitHandler(fn != null);
|
|
2813
|
+
}, []);
|
|
2814
|
+
const submitPayment = useCallback(async () => {
|
|
2815
|
+
const fn = submitRef.current;
|
|
2816
|
+
if (!fn) {
|
|
2817
|
+
return {
|
|
2818
|
+
kind: "error",
|
|
2819
|
+
code: "no_handler",
|
|
2820
|
+
message: "El m\xE9todo de pago a\xFAn no est\xE1 listo. Espera un momento e int\xE9ntalo de nuevo.",
|
|
2821
|
+
provider: "unknown"
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
return fn();
|
|
2825
|
+
}, []);
|
|
2826
|
+
const value = useMemo(
|
|
2827
|
+
() => ({ registerSubmit, submitPayment, hasSubmitHandler }),
|
|
2828
|
+
[registerSubmit, submitPayment, hasSubmitHandler]
|
|
2829
|
+
);
|
|
2830
|
+
return /* @__PURE__ */ jsx(PaymentSubmissionContext.Provider, { value, children });
|
|
2831
|
+
}
|
|
2832
|
+
function usePaymentSubmission() {
|
|
2833
|
+
const ctx = useContext(PaymentSubmissionContext);
|
|
2834
|
+
if (!ctx) {
|
|
2835
|
+
throw new Error("usePaymentSubmission must be used within PaymentSubmissionProvider");
|
|
2836
|
+
}
|
|
2837
|
+
return ctx;
|
|
2838
|
+
}
|
|
2839
|
+
function useSyncPaymentSubmit(submit) {
|
|
2840
|
+
const { registerSubmit } = usePaymentSubmission();
|
|
2841
|
+
useEffect(() => {
|
|
2842
|
+
registerSubmit(submit);
|
|
2843
|
+
return () => registerSubmit(null);
|
|
2844
|
+
}, [registerSubmit, submit]);
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// src/react/components/checkout/payment-providers/payment-submit-by-provider.ts
|
|
2848
|
+
async function submitMercadoPagoPayment(payment) {
|
|
2849
|
+
const url = payment.action?.url;
|
|
2850
|
+
if (typeof url === "string" && url.length > 0) {
|
|
2851
|
+
return { kind: "redirect", url, provider: "mercadopago" };
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
kind: "error",
|
|
2855
|
+
code: "no_checkout_url",
|
|
2856
|
+
message: "No hay URL de pago de Mercado Pago. Vuelve a iniciar el checkout.",
|
|
2857
|
+
provider: "mercadopago"
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
async function submitPaypalPayment(payment) {
|
|
2861
|
+
const url = payment.action?.url;
|
|
2862
|
+
if (typeof url === "string" && url.length > 0) {
|
|
2863
|
+
return { kind: "redirect", url, provider: "paypal" };
|
|
2864
|
+
}
|
|
2865
|
+
return {
|
|
2866
|
+
kind: "error",
|
|
2867
|
+
code: "no_approval_url",
|
|
2868
|
+
message: "No se recibi\xF3 la URL de PayPal.",
|
|
2869
|
+
provider: "paypal"
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
async function submitCulqiPayment(payment) {
|
|
2873
|
+
const url = payment.action?.url;
|
|
2874
|
+
if (typeof url === "string" && url.length > 0) {
|
|
2875
|
+
return { kind: "redirect", url, provider: "culqi" };
|
|
2876
|
+
}
|
|
2877
|
+
return {
|
|
2878
|
+
kind: "error",
|
|
2879
|
+
code: "culqi_embedded",
|
|
2880
|
+
message: "Completa el pago con Culqi seg\xFAn las instrucciones mostradas; la confirmaci\xF3n embebida se integrar\xE1 aqu\xED cuando corresponda.",
|
|
2881
|
+
provider: "culqi"
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
async function submitYapePayment(payment) {
|
|
2885
|
+
const qr = payment.action?.qrCode;
|
|
2886
|
+
if (typeof qr === "string" && qr.length > 0) {
|
|
2887
|
+
return {
|
|
2888
|
+
kind: "error",
|
|
2889
|
+
code: "manual_qr",
|
|
2890
|
+
message: "Escanea el c\xF3digo QR con Yape y, cuando el banco confirme, actualizaremos tu pedido autom\xE1ticamente.",
|
|
2891
|
+
provider: "yape"
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
return {
|
|
2895
|
+
kind: "error",
|
|
2896
|
+
code: "no_qr",
|
|
2897
|
+
message: "No se recibi\xF3 QR de Yape.",
|
|
2898
|
+
provider: "yape"
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
async function submitPagoEfectivoPayment(payment) {
|
|
2902
|
+
const ref = payment.reference ?? payment.transactionId;
|
|
2903
|
+
if (ref) {
|
|
2904
|
+
return {
|
|
2905
|
+
kind: "error",
|
|
2906
|
+
code: "manual_reference",
|
|
2907
|
+
message: "Paga en efectivo con el c\xF3digo indicado antes de la fecha l\xEDmite. Te avisaremos cuando registremos el pago.",
|
|
2908
|
+
provider: "pagoefectivo"
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
return {
|
|
2912
|
+
kind: "error",
|
|
2913
|
+
code: "no_reference",
|
|
2914
|
+
message: "No hay c\xF3digo de pago en efectivo disponible.",
|
|
2915
|
+
provider: "pagoefectivo"
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
async function submitGenericPayment(payment) {
|
|
2919
|
+
const url = payment.action?.url;
|
|
2920
|
+
if (typeof url === "string" && url.length > 0) {
|
|
2921
|
+
return {
|
|
2922
|
+
kind: "redirect",
|
|
2923
|
+
url,
|
|
2924
|
+
provider: normalizeId(payment.providerId) || "unknown"
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
if (payment.actionType === "manual") {
|
|
2928
|
+
return {
|
|
2929
|
+
kind: "redirect",
|
|
2930
|
+
url: getProcessingReturnUrl(),
|
|
2931
|
+
provider: normalizeId(payment.providerId) || "unknown"
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
return {
|
|
2935
|
+
kind: "error",
|
|
2936
|
+
code: "manual_or_unsupported",
|
|
2937
|
+
message: "Completa el pago con los datos mostrados arriba o elige otro m\xE9todo si est\xE1 disponible.",
|
|
2938
|
+
provider: normalizeId(payment.providerId) || "unknown"
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
function normalizeId(id) {
|
|
2942
|
+
return (id ?? "").trim().toLowerCase();
|
|
2943
|
+
}
|
|
2944
|
+
function CulqiPaymentProvider({ payment }) {
|
|
2945
|
+
const submit = useCallback(() => submitCulqiPayment(payment), [payment]);
|
|
2946
|
+
useSyncPaymentSubmit(submit);
|
|
2947
|
+
const pk = payment.action?.publicConfiguration?.publishableKey;
|
|
2948
|
+
const url = payment.action?.url;
|
|
2949
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
2950
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Culqi" }),
|
|
2951
|
+
typeof url === "string" && url.length > 0 ? /* @__PURE__ */ jsx(RedirectPaymentButton, { url, label: "Pagar con Culqi" }) : pk ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Clave p\xFAblica recibida. Integra Culqi Checkout o JS seg\xFAn tu backend (embedded)." }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Configuraci\xF3n de Culqi incompleta." })
|
|
2952
|
+
] });
|
|
2953
|
+
}
|
|
2954
|
+
function GenericPaymentProvider({ payment }) {
|
|
2955
|
+
const submit = useCallback(() => submitGenericPayment(payment), [payment]);
|
|
2956
|
+
useSyncPaymentSubmit(submit);
|
|
2957
|
+
const url = payment.action?.url;
|
|
2958
|
+
const qr = payment.action?.qrCode;
|
|
2959
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
2960
|
+
/* @__PURE__ */ jsxs("p", { className: "ecomiq-payment-provider__title", children: [
|
|
2961
|
+
"Pago ",
|
|
2962
|
+
payment.providerId ? `(${payment.providerId})` : ""
|
|
2963
|
+
] }),
|
|
2964
|
+
payment.status ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-payment-provider__hint ecomiq-payment-provider__hint--sm", children: [
|
|
2965
|
+
"Estado: ",
|
|
2966
|
+
payment.status
|
|
2967
|
+
] }) : null,
|
|
2968
|
+
typeof url === "string" && url.length > 0 ? /* @__PURE__ */ jsx(RedirectPaymentButton, { url, label: "Abrir p\xE1gina de pago" }) : null,
|
|
2969
|
+
typeof qr === "string" && qr.length > 0 ? /* @__PURE__ */ jsx("div", { className: "ecomiq-payment-provider__qr-wrap", children: /* @__PURE__ */ jsx("img", { src: qr, alt: "C\xF3digo QR", className: "ecomiq-payment-provider__qr" }) }) : null,
|
|
2970
|
+
payment.reference ? /* @__PURE__ */ jsx("p", { className: "ecomiq-payment-provider__hint ecomiq-payment-provider__hint--sm ecomiq-payment-provider__mono", children: payment.reference }) : null,
|
|
2971
|
+
/* @__PURE__ */ jsx(ExpiresNotice, { expiresAt: payment.action?.expiresAt }),
|
|
2972
|
+
/* @__PURE__ */ jsxs("details", { className: "ecomiq-payment-provider__details", children: [
|
|
2973
|
+
/* @__PURE__ */ jsx("summary", { children: "Detalle t\xE9cnico" }),
|
|
2974
|
+
/* @__PURE__ */ jsx("pre", { children: JSON.stringify(payment, null, 2) })
|
|
2975
|
+
] })
|
|
2976
|
+
] });
|
|
2977
|
+
}
|
|
2978
|
+
function MercadoPagoPaymentProvider({ payment }) {
|
|
2979
|
+
const submit = useCallback(() => submitMercadoPagoPayment(payment), [payment]);
|
|
2980
|
+
useSyncPaymentSubmit(submit);
|
|
2981
|
+
const url = payment.action?.url;
|
|
2982
|
+
const mode = payment.action?.publicConfiguration?.checkoutMode;
|
|
2983
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
2984
|
+
/* @__PURE__ */ jsx("p", { className: "ecomiq-payment-provider__title", children: "Mercado Pago" }),
|
|
2985
|
+
mode ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-payment-provider__hint ecomiq-payment-provider__hint--sm", children: [
|
|
2986
|
+
"Modo: ",
|
|
2987
|
+
mode
|
|
2988
|
+
] }) : null,
|
|
2989
|
+
typeof url === "string" && url.length > 0 ? /* @__PURE__ */ jsx(RedirectPaymentButton, { url, label: "Pagar con Mercado Pago" }) : /* @__PURE__ */ jsx("p", { className: "ecomiq-payment-provider__hint", children: "Completa el pago cuando el backend env\xEDe la URL de checkout." }),
|
|
2990
|
+
payment.reference ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-payment-provider__hint ecomiq-payment-provider__hint--sm ecomiq-payment-provider__mono", children: [
|
|
2991
|
+
"Ref.: ",
|
|
2992
|
+
payment.reference
|
|
2993
|
+
] }) : null
|
|
2994
|
+
] });
|
|
2995
|
+
}
|
|
2996
|
+
function PagoEfectivoPaymentProvider({ payment }) {
|
|
2997
|
+
const submit = useCallback(() => submitPagoEfectivoPayment(payment), [payment]);
|
|
2998
|
+
useSyncPaymentSubmit(submit);
|
|
2999
|
+
const reference = payment.reference ?? payment.transactionId;
|
|
3000
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
3001
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Pago en efectivo" }),
|
|
3002
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Usa el c\xF3digo de pago en una agencia o app autorizada antes de que venza." }),
|
|
3003
|
+
reference ? /* @__PURE__ */ jsx("div", { className: "rounded-md bg-muted px-3 py-2 font-mono text-sm break-all", children: reference }) : null,
|
|
3004
|
+
payment.amount != null ? /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
3005
|
+
"Monto a pagar: ",
|
|
3006
|
+
/* @__PURE__ */ jsx("strong", { children: payment.amount })
|
|
3007
|
+
] }) : null,
|
|
3008
|
+
/* @__PURE__ */ jsx(ExpiresNotice, { expiresAt: payment.action?.expiresAt })
|
|
3009
|
+
] });
|
|
3010
|
+
}
|
|
3011
|
+
function PaypalPaymentProvider({ payment }) {
|
|
3012
|
+
const submit = useCallback(() => submitPaypalPayment(payment), [payment]);
|
|
3013
|
+
useSyncPaymentSubmit(submit);
|
|
3014
|
+
const url = payment.action?.url;
|
|
3015
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
3016
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "PayPal" }),
|
|
3017
|
+
typeof url === "string" && url.length > 0 ? /* @__PURE__ */ jsx(RedirectPaymentButton, { url, label: "Continuar a PayPal" }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Esperando URL de aprobaci\xF3n de PayPal desde el servidor." })
|
|
3018
|
+
] });
|
|
3019
|
+
}
|
|
3020
|
+
async function confirmStripePaymentWithElements(params) {
|
|
3021
|
+
const { stripe, elements, returnUrl } = params;
|
|
3022
|
+
const result = await stripe.confirmPayment({
|
|
3023
|
+
elements,
|
|
3024
|
+
confirmParams: {
|
|
3025
|
+
return_url: returnUrl,
|
|
3026
|
+
payment_method_data: {}
|
|
3027
|
+
},
|
|
3028
|
+
redirect: "if_required"
|
|
3029
|
+
});
|
|
3030
|
+
if (result.error) {
|
|
3031
|
+
return {
|
|
3032
|
+
kind: "error",
|
|
3033
|
+
code: result.error.code ?? "stripe_error",
|
|
3034
|
+
message: result.error.message ?? "Error al procesar el pago",
|
|
3035
|
+
provider: "stripe",
|
|
3036
|
+
raw: result.error
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
const paymentIntent = result.paymentIntent;
|
|
3040
|
+
if (paymentIntent?.status === "succeeded") {
|
|
3041
|
+
return {
|
|
3042
|
+
kind: "success",
|
|
3043
|
+
transactionId: paymentIntent.id,
|
|
3044
|
+
provider: "stripe",
|
|
3045
|
+
raw: paymentIntent
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
return {
|
|
3049
|
+
kind: "error",
|
|
3050
|
+
code: "pending",
|
|
3051
|
+
message: "El pago est\xE1 pendiente.",
|
|
3052
|
+
provider: "stripe",
|
|
3053
|
+
raw: paymentIntent
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
function StripePaymentProvider({ checkout }) {
|
|
3057
|
+
const stripeMountRef = useRef(null);
|
|
3058
|
+
const stripePaymentElementRef = useRef(null);
|
|
3059
|
+
const stripeRef = useRef(null);
|
|
3060
|
+
const elementsRef = useRef(null);
|
|
3061
|
+
const { registerSubmit } = usePaymentSubmission();
|
|
3062
|
+
const stripeConfig = resolveStripeElementConfig(checkout);
|
|
3063
|
+
useEffect(() => {
|
|
3064
|
+
if (!stripeConfig) {
|
|
3065
|
+
stripePaymentElementRef.current?.unmount?.();
|
|
3066
|
+
stripePaymentElementRef.current = null;
|
|
3067
|
+
stripeRef.current = null;
|
|
3068
|
+
elementsRef.current = null;
|
|
3069
|
+
registerSubmit(null);
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
let cancelled = false;
|
|
3073
|
+
const ensureStripeScript = () => new Promise((resolve, reject) => {
|
|
3074
|
+
const maybeStripe = globalThis.Stripe;
|
|
3075
|
+
if (maybeStripe) return resolve();
|
|
3076
|
+
const existing = document.querySelector('script[src="https://js.stripe.com/v3/"]');
|
|
3077
|
+
if (existing) {
|
|
3078
|
+
existing.addEventListener("load", () => resolve(), { once: true });
|
|
3079
|
+
existing.addEventListener(
|
|
3080
|
+
"error",
|
|
3081
|
+
() => reject(new Error("Stripe script failed to load")),
|
|
3082
|
+
{ once: true }
|
|
3083
|
+
);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
const script = document.createElement("script");
|
|
3087
|
+
script.src = "https://js.stripe.com/v3/";
|
|
3088
|
+
script.async = true;
|
|
3089
|
+
script.addEventListener("load", () => resolve(), { once: true });
|
|
3090
|
+
script.addEventListener(
|
|
3091
|
+
"error",
|
|
3092
|
+
() => reject(new Error("Stripe script failed to load")),
|
|
3093
|
+
{ once: true }
|
|
3094
|
+
);
|
|
3095
|
+
document.head.appendChild(script);
|
|
3096
|
+
});
|
|
3097
|
+
const mountStripePaymentElement = async () => {
|
|
3098
|
+
await ensureStripeScript();
|
|
3099
|
+
if (cancelled) return;
|
|
3100
|
+
const StripeCtor = globalThis.Stripe;
|
|
3101
|
+
if (!StripeCtor) throw new Error("Stripe is not available");
|
|
3102
|
+
const stripeJs = StripeCtor(stripeConfig.publicKey);
|
|
3103
|
+
stripeRef.current = stripeJs;
|
|
3104
|
+
const elements = stripeJs.elements({ clientSecret: stripeConfig.clientSecret });
|
|
3105
|
+
stripePaymentElementRef.current?.unmount?.();
|
|
3106
|
+
stripePaymentElementRef.current = null;
|
|
3107
|
+
const paymentElement = elements.create("payment");
|
|
3108
|
+
const mountNode = stripeMountRef.current;
|
|
3109
|
+
if (!mountNode) return;
|
|
3110
|
+
paymentElement.mount(mountNode);
|
|
3111
|
+
stripePaymentElementRef.current = paymentElement;
|
|
3112
|
+
elementsRef.current = elements;
|
|
3113
|
+
registerSubmit(async () => {
|
|
3114
|
+
const s = stripeRef.current;
|
|
3115
|
+
const el = elementsRef.current;
|
|
3116
|
+
if (!s || !el) {
|
|
3117
|
+
return {
|
|
3118
|
+
kind: "error",
|
|
3119
|
+
code: "stripe_not_ready",
|
|
3120
|
+
message: "Stripe no est\xE1 listo. Espera un momento e int\xE9ntalo de nuevo.",
|
|
3121
|
+
provider: "stripe"
|
|
3122
|
+
};
|
|
3123
|
+
}
|
|
3124
|
+
const returnUrl = getProcessingReturnUrl();
|
|
3125
|
+
return confirmStripePaymentWithElements({
|
|
3126
|
+
stripe: s,
|
|
3127
|
+
elements: el,
|
|
3128
|
+
returnUrl
|
|
3129
|
+
});
|
|
3130
|
+
});
|
|
3131
|
+
};
|
|
3132
|
+
mountStripePaymentElement().catch(() => {
|
|
3133
|
+
registerSubmit(null);
|
|
3134
|
+
});
|
|
3135
|
+
return () => {
|
|
3136
|
+
cancelled = true;
|
|
3137
|
+
stripePaymentElementRef.current?.unmount?.();
|
|
3138
|
+
stripePaymentElementRef.current = null;
|
|
3139
|
+
stripeRef.current = null;
|
|
3140
|
+
elementsRef.current = null;
|
|
3141
|
+
registerSubmit(null);
|
|
3142
|
+
};
|
|
3143
|
+
}, [stripeConfig?.publicKey, stripeConfig?.clientSecret, registerSubmit]);
|
|
3144
|
+
if (!stripeConfig) {
|
|
3145
|
+
return /* @__PURE__ */ jsx(PaymentPanel, { children: /* @__PURE__ */ jsx("p", { className: "ecomiq-payment-provider__hint", children: "No se recibi\xF3 la configuraci\xF3n de Stripe (publishable key / client secret)." }) });
|
|
3146
|
+
}
|
|
3147
|
+
return /* @__PURE__ */ jsx(PaymentPanel, { children: /* @__PURE__ */ jsx("div", { ref: stripeMountRef }) });
|
|
3148
|
+
}
|
|
3149
|
+
function YapePaymentProvider({ payment }) {
|
|
3150
|
+
const submit = useCallback(() => submitYapePayment(payment), [payment]);
|
|
3151
|
+
useSyncPaymentSubmit(submit);
|
|
3152
|
+
const qr = payment.action?.qrCode;
|
|
3153
|
+
return /* @__PURE__ */ jsxs(PaymentPanel, { children: [
|
|
3154
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Yape" }),
|
|
3155
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Escanea el c\xF3digo QR con la app Yape para completar el pago." }),
|
|
3156
|
+
typeof qr === "string" && qr.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
|
|
3157
|
+
"img",
|
|
3158
|
+
{
|
|
3159
|
+
src: qr,
|
|
3160
|
+
alt: "C\xF3digo QR Yape",
|
|
3161
|
+
className: "max-w-[220px] rounded-md border bg-white p-2"
|
|
3162
|
+
}
|
|
3163
|
+
) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: "No se recibi\xF3 QR en la respuesta." }),
|
|
3164
|
+
payment.amount != null ? /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
3165
|
+
"Monto: ",
|
|
3166
|
+
/* @__PURE__ */ jsx("strong", { children: payment.amount })
|
|
3167
|
+
] }) : null,
|
|
3168
|
+
/* @__PURE__ */ jsx(ExpiresNotice, { expiresAt: payment.action?.expiresAt })
|
|
3169
|
+
] });
|
|
3170
|
+
}
|
|
3171
|
+
var REGISTRY = {
|
|
3172
|
+
stripe: StripePaymentProvider,
|
|
3173
|
+
mercadopago: MercadoPagoPaymentProvider,
|
|
3174
|
+
mercado_pago: MercadoPagoPaymentProvider,
|
|
3175
|
+
paypal: PaypalPaymentProvider,
|
|
3176
|
+
culqi: CulqiPaymentProvider,
|
|
3177
|
+
yape: YapePaymentProvider,
|
|
3178
|
+
pagoefectivo: PagoEfectivoPaymentProvider,
|
|
3179
|
+
pago_efectivo: PagoEfectivoPaymentProvider
|
|
3180
|
+
};
|
|
3181
|
+
function PaymentProviderRouter({ checkout }) {
|
|
3182
|
+
const payment = useMemo(() => getPaymentFromCheckout(checkout), [checkout]);
|
|
3183
|
+
if (!payment) {
|
|
3184
|
+
return null;
|
|
3185
|
+
}
|
|
3186
|
+
const key = normalizeProviderId(payment.providerId);
|
|
3187
|
+
const Provider = key ? REGISTRY[key] : void 0;
|
|
3188
|
+
if (Provider) {
|
|
3189
|
+
return /* @__PURE__ */ jsx(Provider, { payment, checkout });
|
|
3190
|
+
}
|
|
3191
|
+
return /* @__PURE__ */ jsx(GenericPaymentProvider, { payment, checkout });
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/react/pages/checkout-page/lib/checkout-payment-method.ts
|
|
3195
|
+
var PROVIDER_CHECKOUT_UI = /* @__PURE__ */ new Set(["stripe", "mercadopago", "mercado_pago"]);
|
|
3196
|
+
function normalizeProviderKey(raw) {
|
|
3197
|
+
return raw.trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
3198
|
+
}
|
|
3199
|
+
function methodUsesProviderCheckoutUi(method) {
|
|
3200
|
+
if (method.isManual) return false;
|
|
3201
|
+
const keys = [method.providerMethodCode, method.kind, method.code].filter(
|
|
3202
|
+
Boolean
|
|
3203
|
+
);
|
|
3204
|
+
return keys.some((key) => {
|
|
3205
|
+
const normalized = normalizeProviderKey(key);
|
|
3206
|
+
return PROVIDER_CHECKOUT_UI.has(normalized) || normalized.includes("stripe") || normalized.includes("mercado");
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
// src/react/pages/checkout-page/hooks/use-checkout-payment.ts
|
|
3211
|
+
function useCheckoutPayment({
|
|
3212
|
+
paymentMethods,
|
|
3213
|
+
selectedPaymentCode,
|
|
3214
|
+
selectPaymentMethod,
|
|
3215
|
+
providerPaymentCompleted,
|
|
3216
|
+
setProviderPaymentCompleted,
|
|
3217
|
+
checkoutResponse,
|
|
3218
|
+
checkoutMethodCode,
|
|
3219
|
+
loadingCheckout,
|
|
3220
|
+
checkoutError,
|
|
3221
|
+
onUploadReceipt,
|
|
3222
|
+
onPaymentSubmissionResult,
|
|
3223
|
+
onCanProceedToPayChange,
|
|
3224
|
+
clearFieldError
|
|
3225
|
+
}) {
|
|
3226
|
+
const { submitPayment, hasSubmitHandler } = usePaymentSubmission();
|
|
3227
|
+
const activeMethods = useMemo(
|
|
3228
|
+
() => paymentMethods.filter((m) => m.isActive).slice().sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)),
|
|
3229
|
+
[paymentMethods]
|
|
3230
|
+
);
|
|
3231
|
+
const selectedMethod = useMemo(
|
|
3232
|
+
() => activeMethods.find((m) => m.code === selectedPaymentCode) ?? null,
|
|
3233
|
+
[activeMethods, selectedPaymentCode]
|
|
3234
|
+
);
|
|
3235
|
+
const requiresProviderUi = Boolean(
|
|
3236
|
+
selectedMethod && methodUsesProviderCheckoutUi(selectedMethod)
|
|
3237
|
+
);
|
|
3238
|
+
const effectiveCheckout = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutResponse ?? null : null;
|
|
3239
|
+
const activeLoadingCheckout = !!checkoutMethodCode && checkoutMethodCode === selectedMethod?.code && loadingCheckout;
|
|
3240
|
+
const activeCheckoutError = checkoutMethodCode && checkoutMethodCode === selectedMethod?.code ? checkoutError : null;
|
|
3241
|
+
const manualPaymentDetails = useMemo(() => {
|
|
3242
|
+
if (!selectedMethod?.isManual) return null;
|
|
3243
|
+
const action = getPaymentFromCheckout(effectiveCheckout)?.action ?? null;
|
|
3244
|
+
const publicConfiguration = action?.publicConfiguration ?? {};
|
|
3245
|
+
const phoneNumber = selectedMethod.phoneNumber ?? publicConfiguration.phoneNumber ?? null;
|
|
3246
|
+
const accountHolder = selectedMethod.accountHolder ?? publicConfiguration.accountHolder ?? null;
|
|
3247
|
+
const qrImageUrl = action?.qrCode ?? selectedMethod.qrImageUrl ?? publicConfiguration.qrImageUrl ?? null;
|
|
3248
|
+
if (!phoneNumber && !accountHolder && !qrImageUrl) return null;
|
|
3249
|
+
return { phoneNumber, accountHolder, qrImageUrl };
|
|
3250
|
+
}, [selectedMethod, effectiveCheckout]);
|
|
3251
|
+
const needsReceipt = Boolean(selectedMethod?.isManual && manualPaymentDetails);
|
|
3252
|
+
const showProviderPanel = requiresProviderUi && !!selectedMethod && !!effectiveCheckout && !activeLoadingCheckout && !activeCheckoutError;
|
|
3253
|
+
const [receiptFile, setReceiptFile] = useState(null);
|
|
3254
|
+
const [receiptUrl, setReceiptUrl] = useState(null);
|
|
3255
|
+
const [receiptError, setReceiptError] = useState(null);
|
|
3256
|
+
const [confirmingProvider, setConfirmingProvider] = useState(false);
|
|
3257
|
+
const receiptPreviewUrl = useMemo(
|
|
3258
|
+
() => receiptFile ? URL.createObjectURL(receiptFile) : null,
|
|
3259
|
+
[receiptFile]
|
|
3260
|
+
);
|
|
3261
|
+
useEffect(() => {
|
|
3262
|
+
return () => {
|
|
3263
|
+
if (receiptPreviewUrl) URL.revokeObjectURL(receiptPreviewUrl);
|
|
3264
|
+
};
|
|
3265
|
+
}, [receiptPreviewUrl]);
|
|
3266
|
+
useEffect(() => {
|
|
3267
|
+
setReceiptFile(null);
|
|
3268
|
+
setReceiptUrl(null);
|
|
3269
|
+
setReceiptError(null);
|
|
3270
|
+
}, [selectedMethod?.code]);
|
|
3271
|
+
const selectPayment = useCallback(
|
|
3272
|
+
(code) => {
|
|
3273
|
+
clearFieldError?.("checkout-payment-method");
|
|
3274
|
+
selectPaymentMethod(code);
|
|
3275
|
+
},
|
|
3276
|
+
[clearFieldError, selectPaymentMethod]
|
|
3277
|
+
);
|
|
3278
|
+
const handleReceiptChange = useCallback((event) => {
|
|
3279
|
+
const file = event.target.files?.[0] ?? null;
|
|
3280
|
+
setReceiptUrl(null);
|
|
3281
|
+
setReceiptError(null);
|
|
3282
|
+
if (!file) {
|
|
3283
|
+
setReceiptFile(null);
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (!file.type.startsWith("image/")) {
|
|
3287
|
+
setReceiptFile(null);
|
|
3288
|
+
setReceiptError("Adjunta una imagen del comprobante.");
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
setReceiptFile(file);
|
|
3292
|
+
}, []);
|
|
3293
|
+
const confirmProviderPayment = useCallback(async () => {
|
|
3294
|
+
if (!showProviderPanel) return;
|
|
3295
|
+
setConfirmingProvider(true);
|
|
3296
|
+
try {
|
|
3297
|
+
const result = await submitPayment();
|
|
3298
|
+
if (result.kind === "redirect") {
|
|
3299
|
+
onPaymentSubmissionResult?.(result);
|
|
3300
|
+
return;
|
|
3301
|
+
}
|
|
3302
|
+
if (result.kind === "success") {
|
|
3303
|
+
setProviderPaymentCompleted(true);
|
|
3304
|
+
return;
|
|
3305
|
+
}
|
|
3306
|
+
onPaymentSubmissionResult?.(result);
|
|
3307
|
+
} catch {
|
|
3308
|
+
setReceiptError("No se pudo completar el pago. Int\xE9ntalo de nuevo.");
|
|
3309
|
+
} finally {
|
|
3310
|
+
setConfirmingProvider(false);
|
|
3311
|
+
}
|
|
3312
|
+
}, [
|
|
3313
|
+
onPaymentSubmissionResult,
|
|
3314
|
+
setProviderPaymentCompleted,
|
|
3315
|
+
showProviderPanel,
|
|
3316
|
+
submitPayment
|
|
3317
|
+
]);
|
|
3318
|
+
const runPreCheckoutSteps = useCallback(async () => {
|
|
3319
|
+
if (!selectedMethod) return { ok: false };
|
|
3320
|
+
setReceiptError(null);
|
|
3321
|
+
try {
|
|
3322
|
+
if (needsReceipt) {
|
|
3323
|
+
let nextReceiptUrl = receiptUrl;
|
|
3324
|
+
if (!nextReceiptUrl) {
|
|
3325
|
+
if (!receiptFile) {
|
|
3326
|
+
setReceiptError("Adjunta el comprobante para completar la orden.");
|
|
3327
|
+
return { ok: false };
|
|
3328
|
+
}
|
|
3329
|
+
const uploaded = await onUploadReceipt(receiptFile);
|
|
3330
|
+
nextReceiptUrl = uploaded.url;
|
|
3331
|
+
setReceiptUrl(nextReceiptUrl);
|
|
3332
|
+
}
|
|
3333
|
+
return { ok: true, receiptUrl: nextReceiptUrl ?? void 0 };
|
|
3334
|
+
}
|
|
3335
|
+
if (requiresProviderUi) {
|
|
3336
|
+
if (!providerPaymentCompleted) {
|
|
3337
|
+
setReceiptError(
|
|
3338
|
+
"Confirma el pago con Stripe o Mercado Pago antes de finalizar el pedido."
|
|
3339
|
+
);
|
|
3340
|
+
return { ok: false };
|
|
3341
|
+
}
|
|
3342
|
+
return { ok: true, finalizeOnly: true };
|
|
3343
|
+
}
|
|
3344
|
+
return { ok: true };
|
|
3345
|
+
} catch {
|
|
3346
|
+
setReceiptError("No se pudo completar el pago. Int\xE9ntalo de nuevo.");
|
|
3347
|
+
return { ok: false };
|
|
3348
|
+
}
|
|
3349
|
+
}, [
|
|
3350
|
+
needsReceipt,
|
|
3351
|
+
onUploadReceipt,
|
|
3352
|
+
providerPaymentCompleted,
|
|
3353
|
+
receiptFile,
|
|
3354
|
+
receiptUrl,
|
|
3355
|
+
requiresProviderUi,
|
|
3356
|
+
selectedMethod
|
|
3357
|
+
]);
|
|
3358
|
+
const canProceedToPay = useMemo(() => {
|
|
3359
|
+
if (!selectedMethod) return false;
|
|
3360
|
+
if (activeLoadingCheckout || activeCheckoutError) return false;
|
|
3361
|
+
if (requiresProviderUi) {
|
|
3362
|
+
return providerPaymentCompleted;
|
|
3363
|
+
}
|
|
3364
|
+
return (!needsReceipt || !!receiptFile || !!receiptUrl) && (!showProviderPanel || hasSubmitHandler);
|
|
3365
|
+
}, [
|
|
3366
|
+
activeCheckoutError,
|
|
3367
|
+
activeLoadingCheckout,
|
|
3368
|
+
hasSubmitHandler,
|
|
3369
|
+
needsReceipt,
|
|
3370
|
+
providerPaymentCompleted,
|
|
3371
|
+
receiptFile,
|
|
3372
|
+
receiptUrl,
|
|
3373
|
+
requiresProviderUi,
|
|
3374
|
+
selectedMethod,
|
|
3375
|
+
showProviderPanel
|
|
3376
|
+
]);
|
|
3377
|
+
useEffect(() => {
|
|
3378
|
+
onCanProceedToPayChange?.(canProceedToPay);
|
|
3379
|
+
}, [canProceedToPay, onCanProceedToPayChange]);
|
|
3380
|
+
const providerConfirmLabel = useMemo(() => {
|
|
3381
|
+
if (!selectedMethod) return "Confirmar pago";
|
|
3382
|
+
const key = (selectedMethod.providerMethodCode ?? selectedMethod.kind ?? "").toLowerCase().replace(/[\s-]+/g, "_");
|
|
3383
|
+
if (key.includes("mercado")) return "Ir a Mercado Pago";
|
|
3384
|
+
if (key.includes("stripe")) return "Confirmar pago con tarjeta";
|
|
3385
|
+
return "Confirmar pago";
|
|
3386
|
+
}, [selectedMethod]);
|
|
3387
|
+
return {
|
|
3388
|
+
activeMethods,
|
|
3389
|
+
selectedMethod,
|
|
3390
|
+
effectiveCheckout,
|
|
3391
|
+
activeLoadingCheckout,
|
|
3392
|
+
activeCheckoutError,
|
|
3393
|
+
manualPaymentDetails,
|
|
3394
|
+
needsReceipt,
|
|
3395
|
+
requiresProviderUi,
|
|
3396
|
+
showProviderPanel,
|
|
3397
|
+
providerPaymentCompleted,
|
|
3398
|
+
confirmingProvider,
|
|
3399
|
+
providerConfirmLabel,
|
|
3400
|
+
receiptFile,
|
|
3401
|
+
receiptUrl,
|
|
3402
|
+
receiptError,
|
|
3403
|
+
receiptPreviewUrl,
|
|
3404
|
+
selectPayment,
|
|
3405
|
+
handleReceiptChange,
|
|
3406
|
+
confirmProviderPayment,
|
|
3407
|
+
runPreCheckoutSteps,
|
|
3408
|
+
canProceedToPay,
|
|
3409
|
+
hasSubmitHandler
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
function CheckoutPaymentSectionInner({
|
|
3413
|
+
paymentRef,
|
|
3414
|
+
getFieldError,
|
|
3415
|
+
clearFieldError,
|
|
3416
|
+
...paymentArgs
|
|
3417
|
+
}) {
|
|
3418
|
+
const payment = useCheckoutPayment({
|
|
3419
|
+
...paymentArgs,
|
|
3420
|
+
clearFieldError
|
|
3421
|
+
});
|
|
3422
|
+
useImperativeHandle(
|
|
3423
|
+
paymentRef,
|
|
3424
|
+
() => ({
|
|
3425
|
+
runPreCheckoutSteps: payment.runPreCheckoutSteps,
|
|
3426
|
+
canProceedToPay: payment.canProceedToPay
|
|
3427
|
+
}),
|
|
3428
|
+
[payment.canProceedToPay, payment.runPreCheckoutSteps]
|
|
3429
|
+
);
|
|
3430
|
+
const paymentFieldError = getFieldError?.("checkout-payment-method");
|
|
3431
|
+
return /* @__PURE__ */ jsxs(
|
|
3432
|
+
"section",
|
|
3433
|
+
{
|
|
3434
|
+
className: "ecomiq-checkout-page__section",
|
|
3435
|
+
id: "checkout-payment-method",
|
|
3436
|
+
children: [
|
|
3437
|
+
/* @__PURE__ */ jsx("h2", { children: "Payment" }),
|
|
3438
|
+
/* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "All transactions are secure and encrypted." }),
|
|
3439
|
+
paymentFieldError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error-inline", role: "alert", children: paymentFieldError }) : null,
|
|
3440
|
+
/* @__PURE__ */ jsx(
|
|
3441
|
+
"div",
|
|
3442
|
+
{
|
|
3443
|
+
className: `ecomiq-checkout-page__options${paymentFieldError ? " ecomiq-checkout-page__options--error" : ""}`,
|
|
3444
|
+
children: payment.activeMethods.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "No payment methods available." }) : payment.activeMethods.map((method) => /* @__PURE__ */ jsxs(
|
|
3445
|
+
"label",
|
|
3446
|
+
{
|
|
3447
|
+
className: `ecomiq-checkout-page__option${paymentArgs.selectedPaymentCode === method.code ? " is-selected" : ""}`,
|
|
3448
|
+
children: [
|
|
3449
|
+
/* @__PURE__ */ jsx(
|
|
3450
|
+
"input",
|
|
3451
|
+
{
|
|
3452
|
+
type: "radio",
|
|
3453
|
+
name: "payment",
|
|
3454
|
+
checked: paymentArgs.selectedPaymentCode === method.code,
|
|
3455
|
+
onChange: () => payment.selectPayment(method.code)
|
|
3456
|
+
}
|
|
3457
|
+
),
|
|
3458
|
+
/* @__PURE__ */ jsxs("span", { className: "ecomiq-checkout-page__option-body", children: [
|
|
3459
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-title", children: method.name }),
|
|
3460
|
+
method.description ? /* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-sub", children: method.description }) : null
|
|
3461
|
+
] })
|
|
3462
|
+
]
|
|
3463
|
+
},
|
|
3464
|
+
method.code
|
|
3465
|
+
))
|
|
3466
|
+
}
|
|
3467
|
+
),
|
|
3468
|
+
payment.activeLoadingCheckout ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "Cargando configuraci\xF3n del m\xE9todo de pago\u2026" }) : null,
|
|
3469
|
+
payment.activeCheckoutError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error", role: "alert", children: payment.activeCheckoutError }) : null,
|
|
3470
|
+
payment.selectedMethod && payment.manualPaymentDetails ? /* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__manual-payment", children: /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__manual-payment-body", children: [
|
|
3471
|
+
payment.manualPaymentDetails.qrImageUrl ? /* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__manual-payment-qr", children: /* @__PURE__ */ jsx(
|
|
3472
|
+
"img",
|
|
3473
|
+
{
|
|
3474
|
+
src: payment.manualPaymentDetails.qrImageUrl,
|
|
3475
|
+
alt: `${payment.selectedMethod.name} QR`
|
|
3476
|
+
}
|
|
3477
|
+
) }) : null,
|
|
3478
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__manual-payment-info", children: [
|
|
3479
|
+
/* @__PURE__ */ jsxs("p", { className: "ecomiq-checkout-page__manual-payment-title", children: [
|
|
3480
|
+
"Pay with ",
|
|
3481
|
+
payment.selectedMethod.name
|
|
3482
|
+
] }),
|
|
3483
|
+
/* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "Send the payment and keep your receipt. The order will stay pending until it is reviewed." }),
|
|
3484
|
+
payment.manualPaymentDetails.phoneNumber ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-checkout-page__manual-payment-row", children: [
|
|
3485
|
+
/* @__PURE__ */ jsx("span", { children: "Cell phone" }),
|
|
3486
|
+
/* @__PURE__ */ jsx("strong", { children: payment.manualPaymentDetails.phoneNumber })
|
|
3487
|
+
] }) : null,
|
|
3488
|
+
payment.manualPaymentDetails.accountHolder ? /* @__PURE__ */ jsxs("p", { className: "ecomiq-checkout-page__manual-payment-row", children: [
|
|
3489
|
+
/* @__PURE__ */ jsx("span", { children: "Holder" }),
|
|
3490
|
+
/* @__PURE__ */ jsx("strong", { children: payment.manualPaymentDetails.accountHolder })
|
|
3491
|
+
] }) : null,
|
|
3492
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__receipt-upload", children: [
|
|
3493
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__label", children: "Payment receipt" }),
|
|
3494
|
+
/* @__PURE__ */ jsxs("label", { className: "ecomiq-checkout-page__receipt-label", children: [
|
|
3495
|
+
/* @__PURE__ */ jsx(
|
|
3496
|
+
"input",
|
|
3497
|
+
{
|
|
3498
|
+
type: "file",
|
|
3499
|
+
accept: "image/*",
|
|
3500
|
+
className: "ecomiq-checkout-page__receipt-input",
|
|
3501
|
+
onChange: payment.handleReceiptChange
|
|
3502
|
+
}
|
|
3503
|
+
),
|
|
3504
|
+
/* @__PURE__ */ jsx("span", { children: payment.receiptFile?.name ?? (payment.receiptUrl ? "Receipt attached" : "Upload receipt") })
|
|
3505
|
+
] }),
|
|
3506
|
+
payment.receiptPreviewUrl ? /* @__PURE__ */ jsx(
|
|
3507
|
+
"img",
|
|
3508
|
+
{
|
|
3509
|
+
src: payment.receiptPreviewUrl,
|
|
3510
|
+
alt: "Payment receipt",
|
|
3511
|
+
className: "ecomiq-checkout-page__receipt-preview"
|
|
3512
|
+
}
|
|
3513
|
+
) : null,
|
|
3514
|
+
payment.receiptError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error-inline", role: "alert", children: payment.receiptError }) : null
|
|
3515
|
+
] })
|
|
3516
|
+
] })
|
|
3517
|
+
] }) }) : null,
|
|
3518
|
+
payment.showProviderPanel && payment.effectiveCheckout ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__payment-provider", children: [
|
|
3519
|
+
/* @__PURE__ */ jsx(PaymentProviderRouter, { checkout: payment.effectiveCheckout }),
|
|
3520
|
+
!payment.providerPaymentCompleted ? /* @__PURE__ */ jsx(
|
|
3521
|
+
"button",
|
|
3522
|
+
{
|
|
3523
|
+
type: "button",
|
|
3524
|
+
className: "ecomiq-checkout-page__provider-confirm",
|
|
3525
|
+
disabled: payment.confirmingProvider || !payment.hasSubmitHandler,
|
|
3526
|
+
onClick: () => void payment.confirmProviderPayment(),
|
|
3527
|
+
children: payment.confirmingProvider ? "Procesando pago\u2026" : payment.providerConfirmLabel
|
|
3528
|
+
}
|
|
3529
|
+
) : /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", role: "status", children: "Pago confirmado. Pulsa Pay now para finalizar tu pedido." })
|
|
3530
|
+
] }) : null,
|
|
3531
|
+
payment.requiresProviderUi && !payment.showProviderPanel && !payment.activeLoadingCheckout && !payment.activeCheckoutError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "Espera a que cargue la configuraci\xF3n del m\xE9todo de pago." }) : null
|
|
3532
|
+
]
|
|
3533
|
+
}
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
var CheckoutPaymentSection = forwardRef(function CheckoutPaymentSection2(props, ref) {
|
|
3537
|
+
return /* @__PURE__ */ jsx(PaymentSubmissionProvider, { children: /* @__PURE__ */ jsx(CheckoutPaymentSectionInner, { paymentRef: ref, ...props }) });
|
|
3538
|
+
});
|
|
3539
|
+
function CheckoutMainForm({
|
|
3540
|
+
storeId,
|
|
3541
|
+
contact,
|
|
3542
|
+
onContactChange,
|
|
3543
|
+
billingChoice,
|
|
3544
|
+
onBillingChange,
|
|
3545
|
+
shippingOptions,
|
|
3546
|
+
shippingLoading,
|
|
3547
|
+
shippingError,
|
|
3548
|
+
selectedShippingKey,
|
|
3549
|
+
onSelectShipping,
|
|
3550
|
+
paymentMethods,
|
|
3551
|
+
selectedPaymentCode,
|
|
3552
|
+
selectPaymentMethod,
|
|
3553
|
+
providerPaymentCompleted,
|
|
3554
|
+
setProviderPaymentCompleted,
|
|
3555
|
+
checkoutPaymentResponse,
|
|
3556
|
+
checkoutPaymentMethodCode,
|
|
3557
|
+
checkoutPaymentLoading,
|
|
3558
|
+
checkoutPaymentError,
|
|
3559
|
+
onUploadReceipt,
|
|
3560
|
+
onPaymentSubmissionResult,
|
|
3561
|
+
currency,
|
|
3562
|
+
formError,
|
|
3563
|
+
isSaving,
|
|
3564
|
+
onPayNow,
|
|
3565
|
+
signInHref,
|
|
3566
|
+
Link
|
|
3567
|
+
}) {
|
|
3568
|
+
const paymentRef = useRef(null);
|
|
3569
|
+
const [canProceedToPay, setCanProceedToPay] = useState(false);
|
|
3570
|
+
const {
|
|
3571
|
+
validationError,
|
|
3572
|
+
validateCheckoutForm,
|
|
3573
|
+
clearFieldError,
|
|
3574
|
+
getFieldError
|
|
3575
|
+
} = useCheckoutFormValidation(currency);
|
|
3576
|
+
const displayError = validationError ?? formError;
|
|
3577
|
+
const handlePayClick = async () => {
|
|
3578
|
+
const valid = validateCheckoutForm({
|
|
3579
|
+
contact,
|
|
3580
|
+
selectedShippingKey,
|
|
3581
|
+
shippingOptionsCount: shippingOptions.length,
|
|
3582
|
+
selectedPaymentCode,
|
|
3583
|
+
paymentMethodsCount: paymentMethods.length
|
|
3584
|
+
});
|
|
3585
|
+
if (!valid) return;
|
|
3586
|
+
const prep = await paymentRef.current?.runPreCheckoutSteps();
|
|
3587
|
+
if (!prep?.ok) return;
|
|
3588
|
+
await onPayNow({ receiptUrl: prep.receiptUrl });
|
|
3589
|
+
};
|
|
3590
|
+
return /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__form", children: [
|
|
3591
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3592
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__section-head", children: [
|
|
3593
|
+
/* @__PURE__ */ jsx("h2", { children: "Contact" }),
|
|
3594
|
+
signInHref ? /* @__PURE__ */ jsx(Link, { href: signInHref, className: "ecomiq-checkout-page__link", children: "Sign in" }) : null
|
|
3595
|
+
] }),
|
|
3596
|
+
/* @__PURE__ */ jsx(
|
|
3597
|
+
CheckoutTextField,
|
|
3598
|
+
{
|
|
3599
|
+
id: "email",
|
|
3600
|
+
label: "Email",
|
|
3601
|
+
type: "email",
|
|
3602
|
+
value: contact.email,
|
|
3603
|
+
error: getFieldError("email"),
|
|
3604
|
+
onClearError: () => clearFieldError("email"),
|
|
3605
|
+
onChange: (v) => onContactChange({ email: v })
|
|
3606
|
+
}
|
|
3607
|
+
),
|
|
3608
|
+
/* @__PURE__ */ jsxs("label", { className: "ecomiq-checkout-page__checkbox", children: [
|
|
3609
|
+
/* @__PURE__ */ jsx(
|
|
3610
|
+
"input",
|
|
3611
|
+
{
|
|
3612
|
+
type: "checkbox",
|
|
3613
|
+
checked: contact.marketingOptIn,
|
|
3614
|
+
onChange: (e) => onContactChange({ marketingOptIn: e.target.checked })
|
|
3615
|
+
}
|
|
3616
|
+
),
|
|
3617
|
+
/* @__PURE__ */ jsx("span", { children: "Email me with news and offers" })
|
|
3618
|
+
] })
|
|
3619
|
+
] }),
|
|
3620
|
+
/* @__PURE__ */ jsx(
|
|
3621
|
+
CheckoutDeliveryForm,
|
|
3622
|
+
{
|
|
3623
|
+
storeId,
|
|
3624
|
+
contact,
|
|
3625
|
+
onContactChange,
|
|
3626
|
+
getFieldError,
|
|
3627
|
+
clearFieldError
|
|
3628
|
+
}
|
|
3629
|
+
),
|
|
3630
|
+
/* @__PURE__ */ jsxs(
|
|
3631
|
+
"section",
|
|
3632
|
+
{
|
|
3633
|
+
className: "ecomiq-checkout-page__section",
|
|
3634
|
+
id: "checkout-shipping-method",
|
|
3635
|
+
children: [
|
|
3636
|
+
/* @__PURE__ */ jsx("h2", { children: "Shipping method" }),
|
|
3637
|
+
getFieldError("checkout-shipping-method") ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error-inline", role: "alert", children: getFieldError("checkout-shipping-method") }) : null,
|
|
3638
|
+
shippingLoading ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "Loading shipping options\u2026" }) : shippingError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error", children: shippingError }) : shippingOptions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__hint", children: "Enter your postal code to see shipping rates." }) : /* @__PURE__ */ jsx(
|
|
3639
|
+
"div",
|
|
3640
|
+
{
|
|
3641
|
+
className: `ecomiq-checkout-page__options${getFieldError("checkout-shipping-method") ? " ecomiq-checkout-page__options--error" : ""}`,
|
|
3642
|
+
children: shippingOptions.map((opt) => /* @__PURE__ */ jsxs(
|
|
3643
|
+
"label",
|
|
3644
|
+
{
|
|
3645
|
+
className: `ecomiq-checkout-page__option${selectedShippingKey === opt.key ? " is-selected" : ""}`,
|
|
3646
|
+
children: [
|
|
3647
|
+
/* @__PURE__ */ jsx(
|
|
3648
|
+
"input",
|
|
3649
|
+
{
|
|
3650
|
+
type: "radio",
|
|
3651
|
+
name: "shipping",
|
|
3652
|
+
checked: selectedShippingKey === opt.key,
|
|
3653
|
+
onChange: () => {
|
|
3654
|
+
clearFieldError("checkout-shipping-method");
|
|
3655
|
+
onSelectShipping(opt.key);
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
),
|
|
3659
|
+
/* @__PURE__ */ jsxs("span", { className: "ecomiq-checkout-page__option-body", children: [
|
|
3660
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-title", children: opt.title }),
|
|
3661
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-sub", children: opt.subtitle })
|
|
3662
|
+
] }),
|
|
3663
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-price", children: formatCurrency(opt.price, currency) })
|
|
3664
|
+
]
|
|
3665
|
+
},
|
|
3666
|
+
opt.key
|
|
3667
|
+
))
|
|
3668
|
+
}
|
|
3669
|
+
)
|
|
3670
|
+
]
|
|
3671
|
+
}
|
|
3672
|
+
),
|
|
3673
|
+
/* @__PURE__ */ jsx(
|
|
3674
|
+
CheckoutPaymentSection,
|
|
3675
|
+
{
|
|
3676
|
+
ref: paymentRef,
|
|
3677
|
+
paymentMethods,
|
|
3678
|
+
selectedPaymentCode,
|
|
3679
|
+
selectPaymentMethod,
|
|
3680
|
+
providerPaymentCompleted,
|
|
3681
|
+
setProviderPaymentCompleted,
|
|
3682
|
+
checkoutResponse: checkoutPaymentResponse,
|
|
3683
|
+
onCanProceedToPayChange: setCanProceedToPay,
|
|
3684
|
+
checkoutMethodCode: checkoutPaymentMethodCode,
|
|
3685
|
+
loadingCheckout: checkoutPaymentLoading,
|
|
3686
|
+
checkoutError: checkoutPaymentError,
|
|
3687
|
+
onUploadReceipt,
|
|
3688
|
+
onPaymentSubmissionResult,
|
|
3689
|
+
getFieldError,
|
|
3690
|
+
clearFieldError
|
|
3691
|
+
}
|
|
3692
|
+
),
|
|
3693
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3694
|
+
/* @__PURE__ */ jsx("h2", { children: "Billing address" }),
|
|
3695
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__options", children: [
|
|
3696
|
+
/* @__PURE__ */ jsxs(
|
|
3697
|
+
"label",
|
|
3698
|
+
{
|
|
3699
|
+
className: `ecomiq-checkout-page__option${billingChoice === "same_as_shipping" ? " is-selected" : ""}`,
|
|
3700
|
+
children: [
|
|
3701
|
+
/* @__PURE__ */ jsx(
|
|
3702
|
+
"input",
|
|
3703
|
+
{
|
|
3704
|
+
type: "radio",
|
|
3705
|
+
name: "billing",
|
|
3706
|
+
checked: billingChoice === "same_as_shipping",
|
|
3707
|
+
onChange: () => onBillingChange("same_as_shipping")
|
|
3708
|
+
}
|
|
3709
|
+
),
|
|
3710
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-body", children: /* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-title", children: "Same as shipping address" }) })
|
|
3711
|
+
]
|
|
3712
|
+
}
|
|
3713
|
+
),
|
|
3714
|
+
/* @__PURE__ */ jsxs(
|
|
3715
|
+
"label",
|
|
3716
|
+
{
|
|
3717
|
+
className: `ecomiq-checkout-page__option${billingChoice === "different" ? " is-selected" : ""}`,
|
|
3718
|
+
children: [
|
|
3719
|
+
/* @__PURE__ */ jsx(
|
|
3720
|
+
"input",
|
|
3721
|
+
{
|
|
3722
|
+
type: "radio",
|
|
3723
|
+
name: "billing",
|
|
3724
|
+
checked: billingChoice === "different",
|
|
3725
|
+
onChange: () => onBillingChange("different")
|
|
3726
|
+
}
|
|
3727
|
+
),
|
|
3728
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-body", children: /* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__option-title", children: "Use a different billing address" }) })
|
|
3729
|
+
]
|
|
3730
|
+
}
|
|
3731
|
+
)
|
|
3732
|
+
] })
|
|
3733
|
+
] }),
|
|
3734
|
+
displayError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error", role: "alert", children: displayError }) : null,
|
|
3735
|
+
/* @__PURE__ */ jsx(
|
|
3736
|
+
"button",
|
|
3737
|
+
{
|
|
3738
|
+
type: "button",
|
|
3739
|
+
className: "ecomiq-checkout-page__pay",
|
|
3740
|
+
disabled: isSaving || !canProceedToPay,
|
|
3741
|
+
onClick: () => void handlePayClick(),
|
|
3742
|
+
children: isSaving ? "Processing\u2026" : "Pay now"
|
|
3743
|
+
}
|
|
3744
|
+
),
|
|
3745
|
+
/* @__PURE__ */ jsxs("nav", { className: "ecomiq-checkout-page__policies", "aria-label": "Policies", children: [
|
|
3746
|
+
/* @__PURE__ */ jsx("a", { href: "#", children: "Refund policy" }),
|
|
3747
|
+
/* @__PURE__ */ jsx("a", { href: "#", children: "Shipping" }),
|
|
3748
|
+
/* @__PURE__ */ jsx("a", { href: "#", children: "Privacy policy" }),
|
|
3749
|
+
/* @__PURE__ */ jsx("a", { href: "#", children: "Terms of service" })
|
|
3750
|
+
] })
|
|
3751
|
+
] });
|
|
3752
|
+
}
|
|
3753
|
+
function CheckoutOrderSummary({
|
|
3754
|
+
items,
|
|
3755
|
+
currency,
|
|
3756
|
+
subtotal,
|
|
3757
|
+
shipping,
|
|
3758
|
+
discount,
|
|
3759
|
+
tax,
|
|
3760
|
+
total,
|
|
3761
|
+
couponCodes,
|
|
3762
|
+
couponCode,
|
|
3763
|
+
couponError,
|
|
3764
|
+
couponBusy,
|
|
3765
|
+
onCouponCodeChange,
|
|
3766
|
+
onApplyCoupon,
|
|
3767
|
+
onRemoveCoupon
|
|
3768
|
+
}) {
|
|
3769
|
+
const onCouponSubmit = (e) => {
|
|
3770
|
+
e.preventDefault();
|
|
3771
|
+
onApplyCoupon();
|
|
3772
|
+
};
|
|
3773
|
+
return /* @__PURE__ */ jsxs("aside", { className: "ecomiq-checkout-page__summary", children: [
|
|
3774
|
+
/* @__PURE__ */ jsx("ul", { className: "ecomiq-checkout-page__summary-items", children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "ecomiq-checkout-page__summary-item", children: [
|
|
3775
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__summary-thumb", children: [
|
|
3776
|
+
/* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__summary-thumb-image", children: item.image ? /* @__PURE__ */ jsx("img", { src: item.image, alt: "" }) : /* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__summary-thumb-ph" }) }),
|
|
3777
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__summary-qty", children: item.quantity })
|
|
3778
|
+
] }),
|
|
3779
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__summary-item-body", children: [
|
|
3780
|
+
/* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__summary-item-title", children: item.title }),
|
|
3781
|
+
(item.variantLabel || item.size) && /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__summary-item-meta", children: item.variantLabel ?? item.size })
|
|
3782
|
+
] }),
|
|
3783
|
+
/* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__summary-item-price", children: formatCurrency(item.price * item.quantity, currency) })
|
|
3784
|
+
] }, item.id)) }),
|
|
3785
|
+
/* @__PURE__ */ jsxs("form", { className: "ecomiq-checkout-page__discount", onSubmit: onCouponSubmit, children: [
|
|
3786
|
+
/* @__PURE__ */ jsx(
|
|
3787
|
+
"input",
|
|
3788
|
+
{
|
|
3789
|
+
type: "text",
|
|
3790
|
+
className: "ecomiq-checkout-page__input",
|
|
3791
|
+
placeholder: "Discount code or gift card",
|
|
3792
|
+
value: couponCode,
|
|
3793
|
+
onChange: (e) => onCouponCodeChange(e.target.value),
|
|
3794
|
+
"aria-label": "Discount code"
|
|
3795
|
+
}
|
|
3796
|
+
),
|
|
3797
|
+
/* @__PURE__ */ jsx(
|
|
3798
|
+
"button",
|
|
3799
|
+
{
|
|
3800
|
+
type: "submit",
|
|
3801
|
+
className: "ecomiq-checkout-page__discount-apply",
|
|
3802
|
+
disabled: !couponCode.trim() || couponBusy,
|
|
3803
|
+
children: "Apply"
|
|
3804
|
+
}
|
|
3805
|
+
)
|
|
3806
|
+
] }),
|
|
3807
|
+
couponError ? /* @__PURE__ */ jsx("p", { className: "ecomiq-checkout-page__field-error", children: couponError }) : null,
|
|
3808
|
+
couponCodes.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "ecomiq-checkout-page__coupon-tags", children: couponCodes.map((code) => /* @__PURE__ */ jsxs("li", { children: [
|
|
3809
|
+
/* @__PURE__ */ jsx("span", { children: code }),
|
|
3810
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => onRemoveCoupon(code), "aria-label": `Remove ${code}`, children: "\xD7" })
|
|
3811
|
+
] }, code)) }) : null,
|
|
3812
|
+
/* @__PURE__ */ jsxs("dl", { className: "ecomiq-checkout-page__totals", children: [
|
|
3813
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__totals-row", children: [
|
|
3814
|
+
/* @__PURE__ */ jsx("dt", { children: "Subtotal" }),
|
|
3815
|
+
/* @__PURE__ */ jsx("dd", { children: formatCurrency(subtotal, currency) })
|
|
3816
|
+
] }),
|
|
3817
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__totals-row", children: [
|
|
3818
|
+
/* @__PURE__ */ jsx("dt", { children: "Shipping" }),
|
|
3819
|
+
/* @__PURE__ */ jsx("dd", { children: shipping > 0 ? formatCurrency(shipping, currency) : shipping === 0 ? "Free" : "\u2014" })
|
|
3820
|
+
] }),
|
|
3821
|
+
discount > 0 ? /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__totals-row", children: [
|
|
3822
|
+
/* @__PURE__ */ jsx("dt", { children: "Discount" }),
|
|
3823
|
+
/* @__PURE__ */ jsxs("dd", { children: [
|
|
3824
|
+
"-",
|
|
3825
|
+
formatCurrency(discount, currency)
|
|
3826
|
+
] })
|
|
3827
|
+
] }) : null,
|
|
3828
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__totals-row", children: [
|
|
3829
|
+
/* @__PURE__ */ jsx("dt", { children: "Taxes" }),
|
|
3830
|
+
/* @__PURE__ */ jsx("dd", { children: tax > 0 ? formatCurrency(tax, currency) : "\u2014" })
|
|
3831
|
+
] }),
|
|
3832
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__totals-row ecomiq-checkout-page__totals-row--total", children: [
|
|
3833
|
+
/* @__PURE__ */ jsx("dt", { children: "Total" }),
|
|
3834
|
+
/* @__PURE__ */ jsxs("dd", { children: [
|
|
3835
|
+
/* @__PURE__ */ jsx("span", { className: "ecomiq-checkout-page__total-currency", children: currency }),
|
|
3836
|
+
formatCurrency(total, currency)
|
|
3837
|
+
] })
|
|
3838
|
+
] })
|
|
3839
|
+
] })
|
|
3840
|
+
] });
|
|
3841
|
+
}
|
|
3842
|
+
function Block({
|
|
3843
|
+
className = "",
|
|
3844
|
+
style
|
|
3845
|
+
}) {
|
|
3846
|
+
return /* @__PURE__ */ jsx(
|
|
3847
|
+
"div",
|
|
3848
|
+
{
|
|
3849
|
+
className: `ecomiq-checkout-page__skeleton-block${className ? ` ${className}` : ""}`,
|
|
3850
|
+
style,
|
|
3851
|
+
"aria-hidden": true
|
|
3852
|
+
}
|
|
3853
|
+
);
|
|
3854
|
+
}
|
|
3855
|
+
function CheckoutPageSkeleton({ header }) {
|
|
3856
|
+
return /* @__PURE__ */ jsxs(
|
|
3857
|
+
"div",
|
|
3858
|
+
{
|
|
3859
|
+
className: "ecomiq-checkout-page ecomiq-checkout-page--skeleton",
|
|
3860
|
+
"aria-busy": "true",
|
|
3861
|
+
"aria-label": "Loading checkout",
|
|
3862
|
+
children: [
|
|
3863
|
+
header,
|
|
3864
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__layout", children: [
|
|
3865
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__main-wrap", children: [
|
|
3866
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3867
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__section-head", children: [
|
|
3868
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-title" }),
|
|
3869
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-link" })
|
|
3870
|
+
] }),
|
|
3871
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" }),
|
|
3872
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-checkbox" })
|
|
3873
|
+
] }),
|
|
3874
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3875
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-title" }),
|
|
3876
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" }),
|
|
3877
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-row", children: [
|
|
3878
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field ecomiq-checkout-page__skeleton-field--half" }),
|
|
3879
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field ecomiq-checkout-page__skeleton-field--half" })
|
|
3880
|
+
] }),
|
|
3881
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" }),
|
|
3882
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" }),
|
|
3883
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" }),
|
|
3884
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-row", children: [
|
|
3885
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field ecomiq-checkout-page__skeleton-field--third" }),
|
|
3886
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field ecomiq-checkout-page__skeleton-field--third" }),
|
|
3887
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field ecomiq-checkout-page__skeleton-field--third" })
|
|
3888
|
+
] }),
|
|
3889
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-field" })
|
|
3890
|
+
] }),
|
|
3891
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3892
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-title" }),
|
|
3893
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-option" }),
|
|
3894
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-option" })
|
|
3895
|
+
] }),
|
|
3896
|
+
/* @__PURE__ */ jsxs("section", { className: "ecomiq-checkout-page__section", children: [
|
|
3897
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-title" }),
|
|
3898
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-option" }),
|
|
3899
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-option" })
|
|
3900
|
+
] }),
|
|
3901
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-button" })
|
|
3902
|
+
] }),
|
|
3903
|
+
/* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__summary-wrap", children: /* @__PURE__ */ jsxs("aside", { className: "ecomiq-checkout-page__summary", children: [
|
|
3904
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-summary-item", children: [
|
|
3905
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-thumb" }),
|
|
3906
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-summary-body", children: [
|
|
3907
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line" }),
|
|
3908
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line ecomiq-checkout-page__skeleton-line--short" })
|
|
3909
|
+
] }),
|
|
3910
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-price" })
|
|
3911
|
+
] }),
|
|
3912
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-coupon", children: [
|
|
3913
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-coupon-input" }),
|
|
3914
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-coupon-btn" })
|
|
3915
|
+
] }),
|
|
3916
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-totals", children: [
|
|
3917
|
+
Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-total-row", children: [
|
|
3918
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line ecomiq-checkout-page__skeleton-line--short" }),
|
|
3919
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line ecomiq-checkout-page__skeleton-line--xs" })
|
|
3920
|
+
] }, i)),
|
|
3921
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__skeleton-total-row ecomiq-checkout-page__skeleton-total-row--final", children: [
|
|
3922
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line" }),
|
|
3923
|
+
/* @__PURE__ */ jsx(Block, { className: "ecomiq-checkout-page__skeleton-line ecomiq-checkout-page__skeleton-line--price" })
|
|
3924
|
+
] })
|
|
3925
|
+
] })
|
|
3926
|
+
] }) })
|
|
3927
|
+
] })
|
|
3928
|
+
]
|
|
3929
|
+
}
|
|
3930
|
+
);
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
// src/react/pages/checkout-page/lib/cart-mappers.ts
|
|
3934
|
+
function mapApiItemsToCartItems(items) {
|
|
3935
|
+
if (!items?.length) return [];
|
|
3936
|
+
return items.map((raw) => {
|
|
3937
|
+
const item = raw;
|
|
3938
|
+
const price = Number(item.unitPrice);
|
|
3939
|
+
const quantity = Number(item.quantity);
|
|
3940
|
+
const variantId = String(item.variantId ?? "");
|
|
3941
|
+
if (!Number.isFinite(price) || !Number.isFinite(quantity) || !variantId) return null;
|
|
3942
|
+
return {
|
|
3943
|
+
id: item.id,
|
|
3944
|
+
title: item.title,
|
|
3945
|
+
image: String(item.thumbnail ?? ""),
|
|
3946
|
+
size: String(item.variantLabel ?? "\u2014"),
|
|
3947
|
+
price,
|
|
3948
|
+
quantity,
|
|
3949
|
+
variantId,
|
|
3950
|
+
variantSku: item.variantSku ?? void 0,
|
|
3951
|
+
variantLabel: item.variantLabel ?? void 0
|
|
3952
|
+
};
|
|
3953
|
+
}).filter(Boolean);
|
|
3954
|
+
}
|
|
3955
|
+
function shippingValuesFromCartAddress(cartData) {
|
|
3956
|
+
const addr = cartData.shippingAddress;
|
|
3957
|
+
if (!addr) return void 0;
|
|
3958
|
+
const geo = addr.geo;
|
|
3959
|
+
return {
|
|
3960
|
+
address: addr.address1,
|
|
3961
|
+
zipCode: addr.postalCode ?? "",
|
|
3962
|
+
countryCode: addr.countryCode ?? "PE",
|
|
3963
|
+
departmentCode: geo?.departmentCode ?? "",
|
|
3964
|
+
departmentName: "",
|
|
3965
|
+
provinceCode: geo?.provinceCode ?? "",
|
|
3966
|
+
provinceName: addr.province ?? "",
|
|
3967
|
+
districtCode: geo?.districtCode ?? "",
|
|
3968
|
+
districtName: addr.district ?? addr.city ?? ""
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
// src/react/pages/checkout-page/lib/cart-sync.ts
|
|
3973
|
+
var checkoutReadQueryKey = (cartId) => ["storefront", "cart", cartId, "checkout"];
|
|
3974
|
+
function cartItemsSignature(items) {
|
|
3975
|
+
return items.map((i) => `${i.variantId ?? i.id}:${i.quantity}`).sort().join("|");
|
|
3976
|
+
}
|
|
3977
|
+
function apiCartItemsFromResponse(cart) {
|
|
3978
|
+
return mapApiItemsToCartItems(cart?.items);
|
|
3979
|
+
}
|
|
3980
|
+
function groupCartQuantitiesByVariant(items) {
|
|
3981
|
+
const map = /* @__PURE__ */ new Map();
|
|
3982
|
+
for (const item of items) {
|
|
3983
|
+
const variantId = item.variantId ?? item.id;
|
|
3984
|
+
map.set(variantId, (map.get(variantId) ?? 0) + Math.max(0, item.quantity));
|
|
3985
|
+
}
|
|
3986
|
+
return map;
|
|
3987
|
+
}
|
|
3988
|
+
function groupApiLinesByVariant(items) {
|
|
3989
|
+
const map = /* @__PURE__ */ new Map();
|
|
3990
|
+
for (const item of items) {
|
|
3991
|
+
const variantId = item.variantId ?? item.id;
|
|
3992
|
+
const lines = map.get(variantId) ?? [];
|
|
3993
|
+
lines.push(item);
|
|
3994
|
+
map.set(variantId, lines);
|
|
3995
|
+
}
|
|
3996
|
+
return map;
|
|
3997
|
+
}
|
|
3998
|
+
function findCartReconciliationActions(local, api) {
|
|
3999
|
+
const actions = [];
|
|
4000
|
+
const localQty = groupCartQuantitiesByVariant(local);
|
|
4001
|
+
const apiLinesByVariant = groupApiLinesByVariant(api);
|
|
4002
|
+
const variantIds = /* @__PURE__ */ new Set([
|
|
4003
|
+
...localQty.keys(),
|
|
4004
|
+
...apiLinesByVariant.keys()
|
|
4005
|
+
]);
|
|
4006
|
+
for (const variantId of variantIds) {
|
|
4007
|
+
const targetQty = localQty.get(variantId) ?? 0;
|
|
4008
|
+
const apiLines = apiLinesByVariant.get(variantId) ?? [];
|
|
4009
|
+
const apiQty = apiLines.reduce((sum, line) => sum + line.quantity, 0);
|
|
4010
|
+
if (targetQty > apiQty) {
|
|
4011
|
+
actions.push({
|
|
4012
|
+
action: "add",
|
|
4013
|
+
variantId,
|
|
4014
|
+
quantity: targetQty - apiQty
|
|
4015
|
+
});
|
|
4016
|
+
continue;
|
|
4017
|
+
}
|
|
4018
|
+
if (targetQty >= apiQty) continue;
|
|
4019
|
+
let excess = apiQty - targetQty;
|
|
4020
|
+
for (const line of [...apiLines].reverse()) {
|
|
4021
|
+
if (excess <= 0) break;
|
|
4022
|
+
if (line.quantity <= excess) {
|
|
4023
|
+
actions.push({ action: "remove", itemId: line.id });
|
|
4024
|
+
excess -= line.quantity;
|
|
4025
|
+
} else {
|
|
4026
|
+
actions.push({
|
|
4027
|
+
action: "update",
|
|
4028
|
+
itemId: line.id,
|
|
4029
|
+
quantity: line.quantity - excess
|
|
4030
|
+
});
|
|
4031
|
+
excess = 0;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
return actions;
|
|
4036
|
+
}
|
|
4037
|
+
function areCartSignaturesEqual(a, b) {
|
|
4038
|
+
return cartItemsSignature(a) === cartItemsSignature(b);
|
|
4039
|
+
}
|
|
4040
|
+
function cartSessionSignature(cartTokenId, items) {
|
|
4041
|
+
return `${cartTokenId ?? ""}|${cartItemsSignature(items)}`;
|
|
4042
|
+
}
|
|
4043
|
+
function readCartCheckoutStatus(cart) {
|
|
4044
|
+
if (!cart) return null;
|
|
4045
|
+
const status = cart.status ?? null;
|
|
4046
|
+
return typeof status === "string" && status.trim() ? status.trim() : null;
|
|
4047
|
+
}
|
|
4048
|
+
function isCheckoutCartAlreadyPurchased(cart, localCartItemCount) {
|
|
4049
|
+
if (localCartItemCount > 0) return false;
|
|
4050
|
+
const status = readCartCheckoutStatus(cart);
|
|
4051
|
+
if (!status) return false;
|
|
4052
|
+
return status.toLowerCase() === "inprogress";
|
|
4053
|
+
}
|
|
4054
|
+
function useCartListener({
|
|
4055
|
+
cartId,
|
|
4056
|
+
enabled,
|
|
4057
|
+
isReconciling,
|
|
4058
|
+
checkoutReady
|
|
4059
|
+
}) {
|
|
4060
|
+
const localCart = useCart((s) => s.cart);
|
|
4061
|
+
const cartTokenId = useCart((s) => s.cartTokenId);
|
|
4062
|
+
const [cartChangedExternally, setCartChangedExternally] = useState(false);
|
|
4063
|
+
const baselineRef = useRef(null);
|
|
4064
|
+
useEffect(() => {
|
|
4065
|
+
baselineRef.current = null;
|
|
4066
|
+
setCartChangedExternally(false);
|
|
4067
|
+
}, [cartId]);
|
|
4068
|
+
useEffect(() => {
|
|
4069
|
+
if (!enabled || !checkoutReady || isReconciling) return;
|
|
4070
|
+
const sig = cartSessionSignature(cartTokenId ?? cartId, localCart);
|
|
4071
|
+
if (baselineRef.current === null) {
|
|
4072
|
+
baselineRef.current = sig;
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
if (sig !== baselineRef.current) {
|
|
4076
|
+
setCartChangedExternally(true);
|
|
4077
|
+
}
|
|
4078
|
+
}, [
|
|
4079
|
+
cartId,
|
|
4080
|
+
cartTokenId,
|
|
4081
|
+
checkoutReady,
|
|
4082
|
+
enabled,
|
|
4083
|
+
isReconciling,
|
|
4084
|
+
localCart
|
|
4085
|
+
]);
|
|
4086
|
+
const reloadCheckout = useCallback(() => {
|
|
4087
|
+
if (typeof window !== "undefined") {
|
|
4088
|
+
window.location.reload();
|
|
4089
|
+
}
|
|
4090
|
+
}, []);
|
|
4091
|
+
return {
|
|
4092
|
+
cartChangedExternally,
|
|
4093
|
+
reloadCheckout
|
|
4094
|
+
};
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
// src/react/pages/checkout-page/hooks/use-checkout-session.ts
|
|
4098
|
+
function useCheckoutSession(cartId) {
|
|
4099
|
+
const queryClient = useQueryClient();
|
|
4100
|
+
const storeId = useStore((s) => s.data?.store?.id ?? "");
|
|
4101
|
+
const currency = useStore((s) => s.data?.store?.currency ?? "PEN");
|
|
4102
|
+
const { setCartTokenId, clearCartSession, cart: localCart } = useCart(
|
|
4103
|
+
useShallow((s) => ({
|
|
4104
|
+
setCartTokenId: s.setCartTokenId,
|
|
4105
|
+
clearCartSession: s.clearCartSession,
|
|
4106
|
+
cart: s.cart
|
|
4107
|
+
}))
|
|
4108
|
+
);
|
|
4109
|
+
const [checkoutAlreadyPurchased, setCheckoutAlreadyPurchased] = useState(false);
|
|
4110
|
+
const skipCartTokenBindRef = useRef(false);
|
|
4111
|
+
useEffect(() => {
|
|
4112
|
+
skipCartTokenBindRef.current = false;
|
|
4113
|
+
setCheckoutAlreadyPurchased(false);
|
|
4114
|
+
}, [cartId]);
|
|
4115
|
+
const {
|
|
4116
|
+
useGetCartCheckout,
|
|
4117
|
+
useUpdateCartBuyer,
|
|
4118
|
+
useUpdateCartAddresses,
|
|
4119
|
+
useGetShippingOptions,
|
|
4120
|
+
usePutCartShippingMethod,
|
|
4121
|
+
useCheckoutCart,
|
|
4122
|
+
useAddCartCoupon,
|
|
4123
|
+
useRemoveCartCoupon,
|
|
4124
|
+
useAddCartItem,
|
|
4125
|
+
useUpdateCartItem,
|
|
4126
|
+
useDeleteCartItem,
|
|
4127
|
+
useUploadPaymentReceipt
|
|
4128
|
+
} = useStoreCart();
|
|
4129
|
+
const checkoutQuery = useGetCartCheckout(cartId);
|
|
4130
|
+
const updateBuyer = useUpdateCartBuyer();
|
|
4131
|
+
const updateAddresses = useUpdateCartAddresses();
|
|
4132
|
+
const putShippingMethod = usePutCartShippingMethod();
|
|
4133
|
+
const checkoutCartMutation = useCheckoutCart();
|
|
4134
|
+
const addCartCouponMutation = useAddCartCoupon();
|
|
4135
|
+
const removeCartCouponMutation = useRemoveCartCoupon();
|
|
4136
|
+
const addCartItemMutation = useAddCartItem();
|
|
4137
|
+
const updateCartItemMutation = useUpdateCartItem();
|
|
4138
|
+
const deleteCartItemMutation = useDeleteCartItem();
|
|
4139
|
+
const uploadPaymentReceiptMutation = useUploadPaymentReceipt();
|
|
4140
|
+
const cartData = checkoutQuery.data?.cart;
|
|
4141
|
+
const paymentMethods = checkoutQuery.data?.paymentMethods?.methods ?? [];
|
|
4142
|
+
useEffect(() => {
|
|
4143
|
+
if (!checkoutQuery.isSuccess || !cartData) return;
|
|
4144
|
+
if (!isCheckoutCartAlreadyPurchased(cartData, localCart.length)) return;
|
|
4145
|
+
if (skipCartTokenBindRef.current) return;
|
|
4146
|
+
skipCartTokenBindRef.current = true;
|
|
4147
|
+
setCheckoutAlreadyPurchased(true);
|
|
4148
|
+
clearCartSession();
|
|
4149
|
+
}, [
|
|
4150
|
+
cartData,
|
|
4151
|
+
checkoutQuery.isSuccess,
|
|
4152
|
+
clearCartSession,
|
|
4153
|
+
localCart.length
|
|
4154
|
+
]);
|
|
4155
|
+
useEffect(() => {
|
|
4156
|
+
if (skipCartTokenBindRef.current) return;
|
|
4157
|
+
setCartTokenId(cartId);
|
|
4158
|
+
}, [cartId, setCartTokenId]);
|
|
4159
|
+
const [contact, setContact] = useState({
|
|
4160
|
+
email: "",
|
|
4161
|
+
marketingOptIn: true,
|
|
4162
|
+
firstName: "",
|
|
4163
|
+
lastName: "",
|
|
4164
|
+
company: "",
|
|
4165
|
+
address: "",
|
|
4166
|
+
address2: "",
|
|
4167
|
+
city: "",
|
|
4168
|
+
province: "",
|
|
4169
|
+
postalCode: "",
|
|
4170
|
+
phone: "",
|
|
4171
|
+
countryCode: "PE",
|
|
4172
|
+
departmentCode: "",
|
|
4173
|
+
departmentName: "",
|
|
4174
|
+
provinceCode: "",
|
|
4175
|
+
provinceName: "",
|
|
4176
|
+
districtCode: "",
|
|
4177
|
+
districtName: ""
|
|
4178
|
+
});
|
|
4179
|
+
const [billingChoice, setBillingChoice] = useState("same_as_shipping");
|
|
4180
|
+
const [selectedShippingKey, setSelectedShippingKey] = useState(null);
|
|
4181
|
+
const [selectedPaymentCode, setSelectedPaymentCode] = useState(null);
|
|
4182
|
+
const [providerPaymentCompleted, setProviderPaymentCompleted] = useState(false);
|
|
4183
|
+
const [couponCode, setCouponCode] = useState("");
|
|
4184
|
+
const [couponError, setCouponError] = useState(null);
|
|
4185
|
+
const [formError, setFormError] = useState(null);
|
|
4186
|
+
const [isReconciling, setIsReconciling] = useState(false);
|
|
4187
|
+
const hydratedRef = useRef(false);
|
|
4188
|
+
useEffect(() => {
|
|
4189
|
+
if (!cartData || hydratedRef.current) return;
|
|
4190
|
+
hydratedRef.current = true;
|
|
4191
|
+
const ship = shippingValuesFromCartAddress(cartData);
|
|
4192
|
+
setContact((c) => ({
|
|
4193
|
+
...c,
|
|
4194
|
+
email: cartData.email ?? c.email,
|
|
4195
|
+
firstName: cartData.firstName ?? c.firstName,
|
|
4196
|
+
lastName: cartData.lastName ?? c.lastName,
|
|
4197
|
+
phone: cartData.phone ?? c.phone,
|
|
4198
|
+
address: ship?.address ?? c.address,
|
|
4199
|
+
postalCode: ship?.zipCode ?? c.postalCode,
|
|
4200
|
+
province: ship?.provinceName ?? c.province,
|
|
4201
|
+
city: ship?.districtName ?? c.city,
|
|
4202
|
+
countryCode: ship?.countryCode ?? c.countryCode,
|
|
4203
|
+
departmentCode: ship?.departmentCode ?? c.departmentCode,
|
|
4204
|
+
departmentName: ship?.departmentName ?? c.departmentName,
|
|
4205
|
+
provinceCode: ship?.provinceCode ?? c.provinceCode,
|
|
4206
|
+
provinceName: ship?.provinceName ?? c.provinceName,
|
|
4207
|
+
districtCode: ship?.districtCode ?? c.districtCode,
|
|
4208
|
+
districtName: ship?.districtName ?? c.districtName
|
|
4209
|
+
}));
|
|
4210
|
+
}, [cartData]);
|
|
4211
|
+
const postalCodeForRates = contact.postalCode.trim();
|
|
4212
|
+
const shippingOptionsQuery = useGetShippingOptions(
|
|
4213
|
+
cartId,
|
|
4214
|
+
postalCodeForRates || void 0,
|
|
4215
|
+
!!postalCodeForRates
|
|
4216
|
+
);
|
|
4217
|
+
const shippingFlat = useMemo(() => {
|
|
4218
|
+
const out = [];
|
|
4219
|
+
for (const group of shippingOptionsQuery.data?.groups ?? []) {
|
|
4220
|
+
for (const rate of group.rates ?? []) {
|
|
4221
|
+
out.push({
|
|
4222
|
+
key: `${group.sellerId ?? ""}::${rate.rateId}`,
|
|
4223
|
+
group,
|
|
4224
|
+
rate
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
return out;
|
|
4229
|
+
}, [shippingOptionsQuery.data?.groups]);
|
|
4230
|
+
const apiCartItems = useMemo(
|
|
4231
|
+
() => apiCartItemsFromResponse(cartData),
|
|
4232
|
+
[cartData]
|
|
4233
|
+
);
|
|
4234
|
+
const cartsReconciled = useMemo(
|
|
4235
|
+
() => areCartSignaturesEqual(localCart, apiCartItems),
|
|
4236
|
+
[localCart, apiCartItems]
|
|
4237
|
+
);
|
|
4238
|
+
const uiCartItems = useMemo(() => {
|
|
4239
|
+
if (cartsReconciled && apiCartItems.length > 0) return apiCartItems;
|
|
4240
|
+
if (localCart.length > 0) return localCart;
|
|
4241
|
+
return apiCartItems;
|
|
4242
|
+
}, [apiCartItems, cartsReconciled, localCart]);
|
|
4243
|
+
const reconcileAttemptRef = useRef(null);
|
|
4244
|
+
const isReconcilingRef = useRef(false);
|
|
4245
|
+
const localSubtotal = useMemo(
|
|
4246
|
+
() => localCart.reduce((sum, item) => sum + item.price * item.quantity, 0),
|
|
4247
|
+
[localCart]
|
|
4248
|
+
);
|
|
4249
|
+
const subtotal = cartsReconciled ? cartData?.totals?.subtotal ?? localSubtotal : localSubtotal;
|
|
4250
|
+
const shipping = cartsReconciled ? cartData?.totals?.shippingTotal ?? 0 : 0;
|
|
4251
|
+
const discount = cartsReconciled ? cartData?.totals?.discountTotal ?? 0 : 0;
|
|
4252
|
+
const tax = cartsReconciled ? cartData?.totals?.taxTotal ?? 0 : 0;
|
|
4253
|
+
const total = cartsReconciled ? cartData?.totals?.total ?? subtotal + shipping + tax - discount : localSubtotal;
|
|
4254
|
+
const mergeCartIntoCheckoutCache = useCallback(
|
|
4255
|
+
(next) => {
|
|
4256
|
+
queryClient.setQueryData(
|
|
4257
|
+
checkoutReadQueryKey(cartId),
|
|
4258
|
+
(prev) => !prev ? { cart: next, paymentMethods: { methods: [] } } : { ...prev, cart: next }
|
|
4259
|
+
);
|
|
4260
|
+
},
|
|
4261
|
+
[queryClient, cartId]
|
|
4262
|
+
);
|
|
4263
|
+
useEffect(() => {
|
|
4264
|
+
reconcileAttemptRef.current = null;
|
|
4265
|
+
}, [cartId]);
|
|
4266
|
+
useEffect(() => {
|
|
4267
|
+
if (!checkoutQuery.isSuccess || !cartData) return;
|
|
4268
|
+
if (isReconcilingRef.current) return;
|
|
4269
|
+
const apiItems = apiCartItemsFromResponse(cartData);
|
|
4270
|
+
if (localCart.length === 0 && apiItems.length === 0) return;
|
|
4271
|
+
if (areCartSignaturesEqual(localCart, apiItems)) return;
|
|
4272
|
+
const reconcileActions = findCartReconciliationActions(localCart, apiItems);
|
|
4273
|
+
if (reconcileActions.length === 0) return;
|
|
4274
|
+
const attemptKey = `${cartId}::${cartItemsSignature(localCart)}::${cartItemsSignature(apiItems)}`;
|
|
4275
|
+
if (reconcileAttemptRef.current === attemptKey) return;
|
|
4276
|
+
reconcileAttemptRef.current = attemptKey;
|
|
4277
|
+
isReconcilingRef.current = true;
|
|
4278
|
+
setIsReconciling(true);
|
|
4279
|
+
void (async () => {
|
|
4280
|
+
try {
|
|
4281
|
+
const applyCartResponse = (updatedCart) => {
|
|
4282
|
+
mergeCartIntoCheckoutCache(updatedCart);
|
|
4283
|
+
};
|
|
4284
|
+
for (const action of reconcileActions) {
|
|
4285
|
+
if (action.action === "remove" || action.action === "update") {
|
|
4286
|
+
try {
|
|
4287
|
+
const updatedCart = action.action === "remove" ? await deleteCartItemMutation.mutateAsync({
|
|
4288
|
+
cartId,
|
|
4289
|
+
itemId: action.itemId
|
|
4290
|
+
}) : await updateCartItemMutation.mutateAsync({
|
|
4291
|
+
cartId,
|
|
4292
|
+
itemId: action.itemId,
|
|
4293
|
+
data: { quantity: action.quantity }
|
|
4294
|
+
});
|
|
4295
|
+
applyCartResponse(updatedCart);
|
|
4296
|
+
} catch {
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
for (const action of reconcileActions) {
|
|
4301
|
+
if (action.action !== "add") continue;
|
|
4302
|
+
try {
|
|
4303
|
+
const updatedCart = await addCartItemMutation.mutateAsync({
|
|
4304
|
+
cartId,
|
|
4305
|
+
variantId: action.variantId,
|
|
4306
|
+
quantity: action.quantity
|
|
4307
|
+
});
|
|
4308
|
+
applyCartResponse(updatedCart);
|
|
4309
|
+
} catch {
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
await checkoutQuery.refetch();
|
|
4313
|
+
} finally {
|
|
4314
|
+
isReconcilingRef.current = false;
|
|
4315
|
+
setIsReconciling(false);
|
|
4316
|
+
}
|
|
4317
|
+
})();
|
|
4318
|
+
}, [
|
|
4319
|
+
addCartItemMutation,
|
|
4320
|
+
cartData,
|
|
4321
|
+
cartId,
|
|
4322
|
+
checkoutQuery,
|
|
4323
|
+
deleteCartItemMutation,
|
|
4324
|
+
localCart,
|
|
4325
|
+
mergeCartIntoCheckoutCache,
|
|
4326
|
+
updateCartItemMutation
|
|
4327
|
+
]);
|
|
4328
|
+
const saveContactAndDelivery = useCallback(async () => {
|
|
4329
|
+
setFormError(null);
|
|
4330
|
+
const buyerCart = await updateBuyer.mutateAsync({
|
|
4331
|
+
cartId,
|
|
4332
|
+
data: {
|
|
4333
|
+
email: contact.email,
|
|
4334
|
+
firstName: contact.firstName,
|
|
4335
|
+
lastName: contact.lastName,
|
|
4336
|
+
phone: contact.phone
|
|
4337
|
+
}
|
|
4338
|
+
});
|
|
4339
|
+
mergeCartIntoCheckoutCache(buyerCart);
|
|
4340
|
+
const countryCode = (contact.countryCode || "PE").toUpperCase();
|
|
4341
|
+
const districtName = contact.districtName || contact.city;
|
|
4342
|
+
const provinceName = contact.provinceName || contact.province;
|
|
4343
|
+
const baseAddress = {
|
|
4344
|
+
name: contact.address,
|
|
4345
|
+
address1: contact.address,
|
|
4346
|
+
city: districtName,
|
|
4347
|
+
countryCode,
|
|
4348
|
+
company: contact.company || null,
|
|
4349
|
+
address2: contact.address2 || null,
|
|
4350
|
+
province: provinceName,
|
|
4351
|
+
postalCode: contact.postalCode,
|
|
4352
|
+
district: districtName,
|
|
4353
|
+
phone: contact.phone || null,
|
|
4354
|
+
latitude: null,
|
|
4355
|
+
longitude: null,
|
|
4356
|
+
geo: {
|
|
4357
|
+
departmentCode: contact.departmentCode || "",
|
|
4358
|
+
provinceCode: contact.provinceCode || "",
|
|
4359
|
+
districtCode: contact.districtCode || ""
|
|
4360
|
+
}
|
|
4361
|
+
};
|
|
4362
|
+
const updatedCart = await updateAddresses.mutateAsync({
|
|
4363
|
+
cartId,
|
|
4364
|
+
data: {
|
|
4365
|
+
shippingAddress: baseAddress,
|
|
4366
|
+
billingAddress: billingChoice === "same_as_shipping" ? baseAddress : baseAddress
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
mergeCartIntoCheckoutCache(updatedCart);
|
|
4370
|
+
await checkoutQuery.refetch();
|
|
4371
|
+
}, [
|
|
4372
|
+
billingChoice,
|
|
4373
|
+
cartId,
|
|
4374
|
+
contact,
|
|
4375
|
+
mergeCartIntoCheckoutCache,
|
|
4376
|
+
updateAddresses,
|
|
4377
|
+
updateBuyer,
|
|
4378
|
+
checkoutQuery
|
|
4379
|
+
]);
|
|
4380
|
+
const confirmShipping = useCallback(
|
|
4381
|
+
async (selection) => {
|
|
4382
|
+
const updatedCart = await putShippingMethod.mutateAsync({
|
|
4383
|
+
cartId,
|
|
4384
|
+
data: {
|
|
4385
|
+
rateId: selection.rate.rateId,
|
|
4386
|
+
shippingMethodId: selection.rate.shippingMethodId,
|
|
4387
|
+
serviceCode: selection.rate.serviceCode ?? null,
|
|
4388
|
+
sellerId: selection.group.sellerId ?? null,
|
|
4389
|
+
shippingProfileId: selection.group.shippingProfileId ?? null,
|
|
4390
|
+
locationId: null,
|
|
4391
|
+
pickupPointId: null,
|
|
4392
|
+
scheduledDate: null,
|
|
4393
|
+
scheduledTime: null
|
|
4394
|
+
}
|
|
4395
|
+
});
|
|
4396
|
+
mergeCartIntoCheckoutCache(updatedCart);
|
|
4397
|
+
},
|
|
4398
|
+
[cartId, mergeCartIntoCheckoutCache, putShippingMethod]
|
|
4399
|
+
);
|
|
4400
|
+
const handleApplyCoupon = useCallback(async () => {
|
|
4401
|
+
const code = couponCode.trim();
|
|
4402
|
+
if (!code) return;
|
|
4403
|
+
setCouponError(null);
|
|
4404
|
+
try {
|
|
4405
|
+
const nextCart = await addCartCouponMutation.mutateAsync({ cartId, data: { code } });
|
|
4406
|
+
mergeCartIntoCheckoutCache(nextCart);
|
|
4407
|
+
setCouponCode("");
|
|
4408
|
+
} catch (error) {
|
|
4409
|
+
setCouponError(error instanceof Error ? error.message : "No se pudo aplicar el cup\xF3n.");
|
|
4410
|
+
}
|
|
4411
|
+
}, [addCartCouponMutation, cartId, couponCode, mergeCartIntoCheckoutCache]);
|
|
4412
|
+
const handleRemoveCoupon = useCallback(
|
|
4413
|
+
async (code) => {
|
|
4414
|
+
setCouponError(null);
|
|
4415
|
+
try {
|
|
4416
|
+
const nextCart = await removeCartCouponMutation.mutateAsync({ cartId, code });
|
|
4417
|
+
mergeCartIntoCheckoutCache(nextCart);
|
|
4418
|
+
} catch (error) {
|
|
4419
|
+
setCouponError(error instanceof Error ? error.message : "No se pudo remover el cup\xF3n.");
|
|
4420
|
+
}
|
|
4421
|
+
},
|
|
4422
|
+
[cartId, mergeCartIntoCheckoutCache, removeCartCouponMutation]
|
|
4423
|
+
);
|
|
4424
|
+
const externalRef = `checkout-${cartId}`;
|
|
4425
|
+
const checkoutCartMutate = checkoutCartMutation.mutate;
|
|
4426
|
+
const checkoutCartMutateAsync = checkoutCartMutation.mutateAsync;
|
|
4427
|
+
const checkoutPaymentInitKeyRef = useRef(null);
|
|
4428
|
+
useEffect(() => {
|
|
4429
|
+
checkoutPaymentInitKeyRef.current = null;
|
|
4430
|
+
}, [cartId]);
|
|
4431
|
+
const initProviderCheckout = useCallback(
|
|
4432
|
+
(paymentMethodCode) => {
|
|
4433
|
+
const method = paymentMethods.find((m) => m.code === paymentMethodCode);
|
|
4434
|
+
if (!method || !methodUsesProviderCheckoutUi(method)) return;
|
|
4435
|
+
const dedupeKey = `${cartId}:${paymentMethodCode}`;
|
|
4436
|
+
if (checkoutPaymentInitKeyRef.current === dedupeKey) return;
|
|
4437
|
+
checkoutPaymentInitKeyRef.current = dedupeKey;
|
|
4438
|
+
checkoutCartMutate({
|
|
4439
|
+
cartId,
|
|
4440
|
+
data: { paymentMethodCode, externalRef }
|
|
4441
|
+
});
|
|
4442
|
+
},
|
|
4443
|
+
[cartId, checkoutCartMutate, externalRef, paymentMethods]
|
|
4444
|
+
);
|
|
4445
|
+
const selectPaymentMethod = useCallback(
|
|
4446
|
+
(paymentMethodCode) => {
|
|
4447
|
+
setSelectedPaymentCode(paymentMethodCode);
|
|
4448
|
+
setProviderPaymentCompleted(false);
|
|
4449
|
+
initProviderCheckout(paymentMethodCode);
|
|
4450
|
+
},
|
|
4451
|
+
[initProviderCheckout]
|
|
4452
|
+
);
|
|
4453
|
+
const payNow = useCallback(
|
|
4454
|
+
async (options) => {
|
|
4455
|
+
setFormError(null);
|
|
4456
|
+
try {
|
|
4457
|
+
await saveContactAndDelivery();
|
|
4458
|
+
if (selectedShippingKey) {
|
|
4459
|
+
const picked = shippingFlat.find((o) => o.key === selectedShippingKey);
|
|
4460
|
+
if (picked) await confirmShipping({ group: picked.group, rate: picked.rate });
|
|
4461
|
+
}
|
|
4462
|
+
const code = selectedPaymentCode ?? paymentMethods[0]?.code;
|
|
4463
|
+
if (!code) {
|
|
4464
|
+
setFormError("Selecciona un m\xE9todo de pago.");
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
const method = paymentMethods.find((m) => m.code === code);
|
|
4468
|
+
const usesProviderUi = method ? methodUsesProviderCheckoutUi(method) : false;
|
|
4469
|
+
if (usesProviderUi) {
|
|
4470
|
+
if (!providerPaymentCompleted) {
|
|
4471
|
+
setFormError("Completa el pago con Stripe o Mercado Pago antes de continuar.");
|
|
4472
|
+
return;
|
|
4473
|
+
}
|
|
4474
|
+
const existing = checkoutCartMutation.data;
|
|
4475
|
+
const postedCode = checkoutCartMutation.variables?.data.paymentMethodCode ?? null;
|
|
4476
|
+
if (existing && postedCode === code) {
|
|
4477
|
+
return existing;
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
const result = await checkoutCartMutateAsync({
|
|
4481
|
+
cartId,
|
|
4482
|
+
data: {
|
|
4483
|
+
paymentMethodCode: code,
|
|
4484
|
+
externalRef,
|
|
4485
|
+
...options?.receiptUrl ? { receiptUrl: options.receiptUrl } : {}
|
|
4486
|
+
}
|
|
4487
|
+
});
|
|
4488
|
+
return result;
|
|
4489
|
+
} catch (error) {
|
|
4490
|
+
setFormError(error instanceof Error ? error.message : "No se pudo completar el pago.");
|
|
4491
|
+
throw error;
|
|
4492
|
+
}
|
|
4493
|
+
},
|
|
4494
|
+
[
|
|
4495
|
+
checkoutCartMutation.data,
|
|
4496
|
+
checkoutCartMutation.variables?.data.paymentMethodCode,
|
|
4497
|
+
confirmShipping,
|
|
4498
|
+
checkoutCartMutateAsync,
|
|
4499
|
+
cartId,
|
|
4500
|
+
externalRef,
|
|
4501
|
+
paymentMethods,
|
|
4502
|
+
providerPaymentCompleted,
|
|
4503
|
+
saveContactAndDelivery,
|
|
4504
|
+
selectedPaymentCode,
|
|
4505
|
+
selectedShippingKey,
|
|
4506
|
+
shippingFlat
|
|
4507
|
+
]
|
|
4508
|
+
);
|
|
4509
|
+
const checkoutReady = checkoutQuery.isSuccess && !checkoutQuery.isLoading && !isReconciling;
|
|
4510
|
+
const { cartChangedExternally, reloadCheckout } = useCartListener({
|
|
4511
|
+
cartId,
|
|
4512
|
+
enabled: Boolean(cartId.trim()),
|
|
4513
|
+
isReconciling,
|
|
4514
|
+
checkoutReady
|
|
4515
|
+
});
|
|
4516
|
+
return {
|
|
4517
|
+
storeId,
|
|
4518
|
+
currency,
|
|
4519
|
+
cartId,
|
|
4520
|
+
checkoutQuery,
|
|
4521
|
+
cartData,
|
|
4522
|
+
uiCartItems,
|
|
4523
|
+
cartsReconciled,
|
|
4524
|
+
isReconciling,
|
|
4525
|
+
cartChangedExternally,
|
|
4526
|
+
checkoutAlreadyPurchased,
|
|
4527
|
+
reloadCheckout,
|
|
4528
|
+
contact,
|
|
4529
|
+
setContact,
|
|
4530
|
+
billingChoice,
|
|
4531
|
+
setBillingChoice,
|
|
4532
|
+
selectedShippingKey,
|
|
4533
|
+
setSelectedShippingKey,
|
|
4534
|
+
selectedPaymentCode,
|
|
4535
|
+
setSelectedPaymentCode,
|
|
4536
|
+
selectPaymentMethod,
|
|
4537
|
+
providerPaymentCompleted,
|
|
4538
|
+
setProviderPaymentCompleted,
|
|
4539
|
+
payNow,
|
|
4540
|
+
couponCode,
|
|
4541
|
+
setCouponCode,
|
|
4542
|
+
couponError,
|
|
4543
|
+
formError,
|
|
4544
|
+
handleApplyCoupon,
|
|
4545
|
+
handleRemoveCoupon,
|
|
4546
|
+
shippingOptionsQuery,
|
|
4547
|
+
shippingFlat,
|
|
4548
|
+
paymentMethods,
|
|
4549
|
+
subtotal,
|
|
4550
|
+
shipping,
|
|
4551
|
+
discount,
|
|
4552
|
+
tax,
|
|
4553
|
+
total,
|
|
4554
|
+
checkoutCartMutation,
|
|
4555
|
+
checkoutPaymentResponse: checkoutCartMutation.data,
|
|
4556
|
+
checkoutPaymentMethodCode: checkoutCartMutation.variables?.data.paymentMethodCode ?? null,
|
|
4557
|
+
checkoutPaymentLoading: checkoutCartMutation.isPending,
|
|
4558
|
+
checkoutPaymentError: checkoutCartMutation.isError ? "Tuvimos problemas al cargar el m\xE9todo de pago. Int\xE9ntalo de nuevo." : null,
|
|
4559
|
+
uploadPaymentReceipt: uploadPaymentReceiptMutation.mutateAsync,
|
|
4560
|
+
addCartCouponMutation,
|
|
4561
|
+
removeCartCouponMutation,
|
|
4562
|
+
clearCartSession,
|
|
4563
|
+
saveContactAndDelivery,
|
|
4564
|
+
isSaving: isReconciling || updateBuyer.isPending || updateAddresses.isPending || putShippingMethod.isPending || uploadPaymentReceiptMutation.isPending || checkoutCartMutation.isPending
|
|
4565
|
+
};
|
|
4566
|
+
}
|
|
4567
|
+
function DefaultLink({ href, className, children }) {
|
|
4568
|
+
return /* @__PURE__ */ jsx("a", { href, className, children });
|
|
4569
|
+
}
|
|
4570
|
+
function CheckoutPage({
|
|
4571
|
+
cartId,
|
|
4572
|
+
storeName: storeNameProp,
|
|
4573
|
+
logoUrl,
|
|
4574
|
+
logoHref = "/",
|
|
4575
|
+
signInHref,
|
|
4576
|
+
homeHref = "/",
|
|
4577
|
+
onPaymentSuccess,
|
|
4578
|
+
getProcessingUrl,
|
|
4579
|
+
Link: LinkComponent = DefaultLink
|
|
4580
|
+
}) {
|
|
4581
|
+
const Link = LinkComponent;
|
|
4582
|
+
const brandingName = useStore((s) => s.data?.store?.name);
|
|
4583
|
+
const logoFromCms = useStore(
|
|
4584
|
+
(s) => s.data?.cms?.settings?.branding?.logoUrl
|
|
4585
|
+
);
|
|
4586
|
+
const storeName = storeNameProp ?? brandingName ?? "Store";
|
|
4587
|
+
const resolvedLogo = logoUrl ?? logoFromCms;
|
|
4588
|
+
const session = useCheckoutSession(cartId);
|
|
4589
|
+
const {
|
|
4590
|
+
shippingFlat,
|
|
4591
|
+
selectedShippingKey,
|
|
4592
|
+
setSelectedShippingKey,
|
|
4593
|
+
paymentMethods,
|
|
4594
|
+
selectedPaymentCode,
|
|
4595
|
+
selectPaymentMethod
|
|
4596
|
+
} = session;
|
|
4597
|
+
useEffect(() => {
|
|
4598
|
+
if (!shippingFlat.length || selectedShippingKey) return;
|
|
4599
|
+
setSelectedShippingKey(shippingFlat[0].key);
|
|
4600
|
+
}, [shippingFlat, selectedShippingKey, setSelectedShippingKey]);
|
|
4601
|
+
useEffect(() => {
|
|
4602
|
+
if (!paymentMethods.length || selectedPaymentCode) return;
|
|
4603
|
+
const first = paymentMethods.find((m) => m.isDefault) ?? paymentMethods[0];
|
|
4604
|
+
if (first) selectPaymentMethod(first.code);
|
|
4605
|
+
}, [paymentMethods, selectedPaymentCode, selectPaymentMethod]);
|
|
4606
|
+
const shippingOptions = useMemo(
|
|
4607
|
+
() => session.shippingFlat.map((o) => ({
|
|
4608
|
+
key: o.key,
|
|
4609
|
+
title: o.rate.name ?? "Shipping",
|
|
4610
|
+
subtitle: o.rate.description ?? (o.rate.estimatedDeliveryDays != null ? `${o.rate.estimatedDeliveryDays} business days` : "Standard delivery"),
|
|
4611
|
+
price: Number(o.rate.price) || 0
|
|
4612
|
+
})),
|
|
4613
|
+
[session.shippingFlat]
|
|
4614
|
+
);
|
|
4615
|
+
const handlePaymentSubmissionResult = useCallback(
|
|
4616
|
+
(result) => {
|
|
4617
|
+
if (result.kind === "success") {
|
|
4618
|
+
session.clearCartSession();
|
|
4619
|
+
const url = getProcessingUrl?.(cartId, result.transactionId);
|
|
4620
|
+
if (url && typeof window !== "undefined") {
|
|
4621
|
+
window.location.assign(url);
|
|
4622
|
+
}
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
if (result.kind === "redirect") {
|
|
4626
|
+
session.clearCartSession();
|
|
4627
|
+
window.location.assign(result.url);
|
|
4628
|
+
return;
|
|
4629
|
+
}
|
|
4630
|
+
toast.error(result.message);
|
|
4631
|
+
},
|
|
4632
|
+
[cartId, getProcessingUrl, session]
|
|
4633
|
+
);
|
|
4634
|
+
const handlePayNow = async (options) => {
|
|
4635
|
+
try {
|
|
4636
|
+
const result = await session.payNow(options);
|
|
4637
|
+
if (!result) return;
|
|
4638
|
+
session.clearCartSession();
|
|
4639
|
+
if (onPaymentSuccess) {
|
|
4640
|
+
onPaymentSuccess(result);
|
|
4641
|
+
return;
|
|
4642
|
+
}
|
|
4643
|
+
const url = getProcessingUrl?.(
|
|
4644
|
+
cartId,
|
|
4645
|
+
result.payment?.transactionId ?? void 0
|
|
4646
|
+
);
|
|
4647
|
+
if (url && typeof window !== "undefined") {
|
|
4648
|
+
window.location.assign(url);
|
|
4649
|
+
}
|
|
4650
|
+
} catch {
|
|
4651
|
+
}
|
|
4652
|
+
};
|
|
4653
|
+
if (!cartId.trim()) {
|
|
4654
|
+
return /* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page", children: /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__state", children: [
|
|
4655
|
+
/* @__PURE__ */ jsx("h1", { children: "Invalid checkout" }),
|
|
4656
|
+
/* @__PURE__ */ jsx("p", { children: "Missing cart ID in the URL." })
|
|
4657
|
+
] }) });
|
|
4658
|
+
}
|
|
4659
|
+
const checkoutHeader = /* @__PURE__ */ jsx("header", { className: "ecomiq-checkout-page__top", children: /* @__PURE__ */ jsx(Link, { href: logoHref, className: "ecomiq-checkout-page__logo", children: resolvedLogo ? /* @__PURE__ */ jsx("img", { src: resolvedLogo, alt: storeName }) : storeName }) });
|
|
4660
|
+
if (session.checkoutQuery.isLoading || session.isReconciling) {
|
|
4661
|
+
return /* @__PURE__ */ jsx(CheckoutPageSkeleton, { header: checkoutHeader });
|
|
4662
|
+
}
|
|
4663
|
+
if (session.checkoutQuery.isError) {
|
|
4664
|
+
return /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page", children: [
|
|
4665
|
+
checkoutHeader,
|
|
4666
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__state", children: [
|
|
4667
|
+
/* @__PURE__ */ jsx("h1", { children: "Checkout unavailable" }),
|
|
4668
|
+
/* @__PURE__ */ jsx("p", { children: session.checkoutQuery.error?.message ?? "Could not load this cart. It may have expired." }),
|
|
4669
|
+
/* @__PURE__ */ jsx(Link, { href: homeHref, className: "ecomiq-checkout-page__link", children: "Return to store" })
|
|
4670
|
+
] })
|
|
4671
|
+
] });
|
|
4672
|
+
}
|
|
4673
|
+
if (session.checkoutAlreadyPurchased) {
|
|
4674
|
+
return /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page", children: [
|
|
4675
|
+
checkoutHeader,
|
|
4676
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__state", children: [
|
|
4677
|
+
/* @__PURE__ */ jsx("h1", { children: "Pedido ya realizado" }),
|
|
4678
|
+
/* @__PURE__ */ jsx("p", { children: "Este checkout ya fue completado. Tu bolsa se ha reiniciado para que puedas seguir comprando." }),
|
|
4679
|
+
/* @__PURE__ */ jsx(Link, { href: homeHref, className: "ecomiq-checkout-page__link", children: "Volver a la tienda" })
|
|
4680
|
+
] })
|
|
4681
|
+
] });
|
|
4682
|
+
}
|
|
4683
|
+
return /* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page", "data-ecomiq-checkout-page": true, children: [
|
|
4684
|
+
checkoutHeader,
|
|
4685
|
+
session.cartChangedExternally ? /* @__PURE__ */ jsxs(
|
|
4686
|
+
"div",
|
|
4687
|
+
{
|
|
4688
|
+
className: "ecomiq-checkout-page__external-cart-banner",
|
|
4689
|
+
role: "alert",
|
|
4690
|
+
"aria-live": "polite",
|
|
4691
|
+
children: [
|
|
4692
|
+
/* @__PURE__ */ jsx("p", { children: "Tu carrito se acaba de actualizar." }),
|
|
4693
|
+
/* @__PURE__ */ jsx(
|
|
4694
|
+
"button",
|
|
4695
|
+
{
|
|
4696
|
+
type: "button",
|
|
4697
|
+
className: "ecomiq-checkout-page__external-cart-reload",
|
|
4698
|
+
onClick: session.reloadCheckout,
|
|
4699
|
+
children: "Recargar checkout"
|
|
4700
|
+
}
|
|
4701
|
+
)
|
|
4702
|
+
]
|
|
4703
|
+
}
|
|
4704
|
+
) : null,
|
|
4705
|
+
/* @__PURE__ */ jsxs("div", { className: "ecomiq-checkout-page__layout", children: [
|
|
4706
|
+
/* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__main-wrap", children: /* @__PURE__ */ jsx(
|
|
4707
|
+
CheckoutMainForm,
|
|
4708
|
+
{
|
|
4709
|
+
storeId: session.storeId,
|
|
4710
|
+
contact: session.contact,
|
|
4711
|
+
onContactChange: (patch) => session.setContact((c) => ({ ...c, ...patch })),
|
|
4712
|
+
billingChoice: session.billingChoice,
|
|
4713
|
+
onBillingChange: session.setBillingChoice,
|
|
4714
|
+
shippingOptions,
|
|
4715
|
+
shippingLoading: session.shippingOptionsQuery.isLoading,
|
|
4716
|
+
shippingError: session.shippingOptionsQuery.isError ? session.shippingOptionsQuery.error?.message ?? "Could not load shipping options." : null,
|
|
4717
|
+
selectedShippingKey: session.selectedShippingKey,
|
|
4718
|
+
onSelectShipping: session.setSelectedShippingKey,
|
|
4719
|
+
paymentMethods: session.paymentMethods,
|
|
4720
|
+
selectedPaymentCode: session.selectedPaymentCode,
|
|
4721
|
+
selectPaymentMethod: session.selectPaymentMethod,
|
|
4722
|
+
providerPaymentCompleted: session.providerPaymentCompleted,
|
|
4723
|
+
setProviderPaymentCompleted: session.setProviderPaymentCompleted,
|
|
4724
|
+
checkoutPaymentResponse: session.checkoutPaymentResponse,
|
|
4725
|
+
checkoutPaymentMethodCode: session.checkoutPaymentMethodCode,
|
|
4726
|
+
checkoutPaymentLoading: session.checkoutPaymentLoading,
|
|
4727
|
+
checkoutPaymentError: session.checkoutPaymentError,
|
|
4728
|
+
onUploadReceipt: async (file) => {
|
|
4729
|
+
const uploaded = await session.uploadPaymentReceipt({ cartId, file });
|
|
4730
|
+
return { url: uploaded.url };
|
|
4731
|
+
},
|
|
4732
|
+
onPaymentSubmissionResult: handlePaymentSubmissionResult,
|
|
4733
|
+
currency: session.currency,
|
|
4734
|
+
formError: session.formError,
|
|
4735
|
+
isSaving: session.isSaving || session.cartChangedExternally,
|
|
4736
|
+
onPayNow: handlePayNow,
|
|
4737
|
+
signInHref,
|
|
4738
|
+
Link
|
|
4739
|
+
}
|
|
4740
|
+
) }),
|
|
4741
|
+
/* @__PURE__ */ jsx("div", { className: "ecomiq-checkout-page__summary-wrap", children: /* @__PURE__ */ jsx(
|
|
4742
|
+
CheckoutOrderSummary,
|
|
4743
|
+
{
|
|
4744
|
+
items: session.uiCartItems,
|
|
4745
|
+
currency: session.currency,
|
|
4746
|
+
subtotal: session.subtotal,
|
|
4747
|
+
shipping: session.shipping,
|
|
4748
|
+
discount: session.discount,
|
|
4749
|
+
tax: session.tax,
|
|
4750
|
+
total: session.total,
|
|
4751
|
+
couponCodes: session.cartData?.promotionCodes ?? [],
|
|
4752
|
+
couponCode: session.couponCode,
|
|
4753
|
+
couponError: session.couponError,
|
|
4754
|
+
couponBusy: session.addCartCouponMutation.isPending || session.removeCartCouponMutation.isPending,
|
|
4755
|
+
onCouponCodeChange: session.setCouponCode,
|
|
4756
|
+
onApplyCoupon: () => void session.handleApplyCoupon(),
|
|
4757
|
+
onRemoveCoupon: (code) => void session.handleRemoveCoupon(code)
|
|
4758
|
+
}
|
|
4759
|
+
) })
|
|
4760
|
+
] })
|
|
4761
|
+
] });
|
|
4762
|
+
}
|
|
1916
4763
|
function buildOptionGroups(variants) {
|
|
1917
4764
|
const map = /* @__PURE__ */ new Map();
|
|
1918
4765
|
for (const v of variants) {
|
|
@@ -2022,6 +4869,6 @@ function ProductVariantSelector({
|
|
|
2022
4869
|
] });
|
|
2023
4870
|
}
|
|
2024
4871
|
|
|
2025
|
-
export { CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, EcomiqProvider, ProductVariantSelector, QueryProvider, StoreApiClient, StorefrontAuthProvider, addCartCoupon, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPasswordClient, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, isCartNotActiveApiError, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, updateCustomerAddress, uploadPaymentReceipt, useCart, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts, useStorefrontAuthClient, useStorefrontSession };
|
|
4872
|
+
export { CheckoutPage, CustomerAddressSchema, CustomerAddressUpsertSchema, CustomerMeSchema, CustomerOrderDetailSchema, CustomerOrderListItemSchema, CustomerProfilePatchSchema, CustomerRegisterRequestSchema, EcomiqProvider, ProductVariantSelector, QueryProvider, StoreApiClient, StorefrontAuthProvider, addCartCoupon, checkoutCart, createCart, createCustomerAddress, createStoreApiClient, customerMeToProfilePatch, deleteCustomerAddress, ecomiqOAuthPasswordClient, getCart, getCartCheckout, getCartPaymentMethods, getCustomerAddresses, getCustomerMe, getCustomerOrder, getCustomerOrders, getSharedOrderDraft, getShippingOptions, getStoreApiBaseUrl, getStoreApiClient, getStorefrontCollectionBySlug, getStorefrontCollections, getStorefrontPageBySlug, getStorefrontPagePreview, getStorefrontPages, getStorefrontProductBySlug, getStorefrontProducts, isCartNotActiveApiError, patchCartAddresses, patchCartBuyer, patchCustomer, putCartShippingMethod, registerCustomer, removeCartCoupon, setClientStoreId, setServerRequestOrigin, setServerStoreId, signInWithEcomiqCredentials, storeApiClient, storefrontAccessTokenNeedsRefresh, storefrontSessionEndsSoon, updateCustomerAddress, uploadPaymentReceipt, useCart, useCheckoutSession, useStore, useStoreCart, useStoreCollections, useStorePages, useStoreProducts, useStorefrontAuthClient, useStorefrontSession };
|
|
2026
4873
|
//# sourceMappingURL=client.js.map
|
|
2027
4874
|
//# sourceMappingURL=client.js.map
|