@fayz-ai/storefront 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { setShopCarrierQuotesResolver, setShopAccessTokenResolver, setShopShippi
6
6
  import { normalizePostalCode, lookupPostalCode, defaultRouterAdapter, defineLoader, staticRouterAdapter, isLoaderParamRef, isLoaderPropRef, resolveLoader, dataNeedKey, getBlockMeta, defaultPropsFromSettings, getBlockErrorVisibility, isBlockEnabledOn, checkBlockConstraints, checkBlockContext, repairBlockTree, renderBlocks, clearExtensionPoints, defineExtensionPoint, runExtensionPoint, runEscapeValve, breadcrumbJsonLd, hasEntityDeclaration, resolveEntityDeclaration, defineBlock, formatPostalCode, defineEntity, listEntityDeclarations, resolveHandle, seoOrigin, RESERVED_ENTITY_FIELDS, ENTITY_FIELD_TYPES, handleNameFor, registerHandle, hasHandle, listHandles, blockRegistry, loaderRegistry, listLoaders } from '@fayz-ai/core';
7
7
  import React4, { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore, useMemo, useCallback } from 'react';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
- import { isStorageImageUrl, imageVariantUrl, imageSrcSet, IMAGE_SRCSET_WIDTHS, validateDiscount, documentDigits, isChargeableProvider, formatDocument, isValidDocument } from '@fayz-ai/shop';
9
+ import { isStorageImageUrl, imageVariantUrl, imageSrcSet, IMAGE_SRCSET_WIDTHS, validateDiscount, documentDigits, isChargeableProvider, supportsCard, formatDocument, isValidDocument, gatewayLabel } from '@fayz-ai/shop';
10
10
  import { ChevronLeft, ChevronRight, ShoppingBag, X, Minus, Plus, Send, MessageCircle, Mail, Truck, RefreshCcw, ShieldCheck, ChevronDown, Check, Search, Trash2, Lock, CreditCard, Star, MapPin, Phone, User, UserCircle, Package, LogOut, Menu, QrCode, Copy, XCircle, RotateCcw, Clock, PackageCheck, Info, AlertCircle } from 'lucide-react';
11
11
  import { getLucideIcon } from '@fayz-ai/ui/icons';
12
12
  import { storefrontDiscountToPromotion, previewCouponPercent, discountToPromotion, PROMOTION_TYPES, PROMOTION_STATUSES, APPLICATION_METHOD_TYPES, APPLICATION_METHOD_TARGET_TYPES, APPLICATION_METHOD_ALLOCATIONS, hasRuleAttribute, listRuleAttributeKeys, RULE_OPERATORS, resolveRuleAttribute } from '@fayz-ai/shop/rules';
@@ -1136,10 +1136,12 @@ var TID = {
1136
1136
  authTabSignup: "auth-tab-signup",
1137
1137
  signinEmail: "signin-email",
1138
1138
  signinPassword: "signin-password",
1139
+ signinPasswordConfirm: "signin-password-confirm",
1139
1140
  signinName: "signin-name",
1140
1141
  signinSubmit: "signin-submit",
1141
1142
  authError: "auth-error",
1142
1143
  authNotice: "auth-notice",
1144
+ checkoutExpired: "checkout-expired",
1143
1145
  purchasesList: "purchases-list",
1144
1146
  purchaseItem: "purchase-item",
1145
1147
  purchasesEmpty: "purchases-empty",
@@ -4006,7 +4008,7 @@ function ProductGallery({ product, images, primaryImage, order = "primary" }) {
4006
4008
  src: current?.url ?? productPlaceholder(product.name),
4007
4009
  alt: current?.altText ?? product.name,
4008
4010
  "data-testid": TID.pdpGalleryImage,
4009
- className: "h-full w-full object-contain",
4011
+ className: "h-full w-full object-cover",
4010
4012
  variant: { sizes: "(min-width: 1024px) 50vw, 100vw" }
4011
4013
  },
4012
4014
  current?.id ?? "placeholder"
@@ -7466,6 +7468,41 @@ async function placeStorefrontOrder({
7466
7468
  rememberLocalOrder(order.id);
7467
7469
  return { order, customerId: customerId ?? order.customerId ?? "" };
7468
7470
  }
7471
+ function useMyOrders() {
7472
+ const customerId = useSessionStore((s2) => s2.customerId);
7473
+ const email = useSessionStore((s2) => s2.email);
7474
+ const [orders, setOrders] = useState([]);
7475
+ const [loading, setLoading] = useState(true);
7476
+ const [tick, setTick] = useState(0);
7477
+ const refresh = useCallback(() => setTick((t) => t + 1), []);
7478
+ useEffect(() => {
7479
+ let cancelled = false;
7480
+ const local = listLocalOrderIds();
7481
+ if (!customerId && !email) {
7482
+ if (local.length === 0) {
7483
+ setOrders([]);
7484
+ setLoading(false);
7485
+ return;
7486
+ }
7487
+ }
7488
+ setLoading(true);
7489
+ const query = customerId ? { customerId, limit: 50 } : { customerEmail: email, limit: 50 };
7490
+ getShopProvider().listOrders(query).then((data) => {
7491
+ if (!cancelled) setOrders(data);
7492
+ }).catch(async () => {
7493
+ const mine = await Promise.all(
7494
+ listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null))
7495
+ );
7496
+ if (!cancelled) setOrders(mine.filter((o) => !!o));
7497
+ }).finally(() => {
7498
+ if (!cancelled) setLoading(false);
7499
+ });
7500
+ return () => {
7501
+ cancelled = true;
7502
+ };
7503
+ }, [customerId, email, tick]);
7504
+ return { orders, loading, refresh };
7505
+ }
7469
7506
  function SignInModal({ defaultEmail = "", onClose, onSignedIn }) {
7470
7507
  const [email, setEmail] = useState(defaultEmail);
7471
7508
  const [password, setPassword] = useState("");
@@ -7653,12 +7690,75 @@ function PulseDot({ className = "" }) {
7653
7690
  ] });
7654
7691
  }
7655
7692
  var POLL_MS = 3e3;
7656
- function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7693
+ var RESUME_KEY = "fz.checkout.resume";
7694
+ function useExpiry(expiresAt) {
7695
+ const target = React4.useMemo(() => {
7696
+ if (!expiresAt) return null;
7697
+ const t = new Date(expiresAt).getTime();
7698
+ return Number.isFinite(t) ? t : null;
7699
+ }, [expiresAt]);
7700
+ const [now, setNow] = React4.useState(() => Date.now());
7701
+ React4.useEffect(() => {
7702
+ if (target === null) return;
7703
+ const id = setInterval(() => setNow(Date.now()), 1e3);
7704
+ return () => clearInterval(id);
7705
+ }, [target]);
7706
+ if (target === null) return { secondsLeft: null, expired: false };
7707
+ const secondsLeft = Math.max(0, Math.round((target - now) / 1e3));
7708
+ return { secondsLeft, expired: secondsLeft === 0 };
7709
+ }
7710
+ function formatCountdown(seconds) {
7711
+ const m = Math.floor(seconds / 60);
7712
+ const s2 = seconds % 60;
7713
+ return `${m}:${String(s2).padStart(2, "0")}`;
7714
+ }
7715
+ async function copyText(text) {
7716
+ try {
7717
+ if (navigator.clipboard?.writeText) {
7718
+ await navigator.clipboard.writeText(text);
7719
+ return true;
7720
+ }
7721
+ } catch {
7722
+ }
7723
+ try {
7724
+ const area = document.createElement("textarea");
7725
+ area.value = text;
7726
+ area.setAttribute("readonly", "");
7727
+ area.style.position = "fixed";
7728
+ area.style.top = "-1000px";
7729
+ area.style.opacity = "0";
7730
+ document.body.appendChild(area);
7731
+ area.select();
7732
+ area.setSelectionRange(0, text.length);
7733
+ const ok = document.execCommand("copy");
7734
+ document.body.removeChild(area);
7735
+ return ok;
7736
+ } catch {
7737
+ return false;
7738
+ }
7739
+ }
7740
+ function rememberSession(sessionId, provider) {
7741
+ try {
7742
+ sessionStorage.setItem(RESUME_KEY, JSON.stringify({ sessionId, provider }));
7743
+ } catch {
7744
+ }
7745
+ }
7746
+ function recallSessionProvider(sessionId) {
7747
+ try {
7748
+ const raw = JSON.parse(sessionStorage.getItem(RESUME_KEY) ?? "null");
7749
+ return raw?.sessionId === sessionId ? raw.provider ?? null : null;
7750
+ } catch {
7751
+ return null;
7752
+ }
7753
+ }
7754
+ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable, resume }) {
7657
7755
  const [phase, setPhase] = useState("opening");
7658
7756
  const [charge, setCharge] = useState(null);
7757
+ const expiry = useExpiry(charge?.expiresAt);
7659
7758
  const [total, setTotal] = useState(null);
7660
- const [sessionId, setSessionId] = useState(null);
7661
- const [provider, setProvider] = useState(null);
7759
+ const [redirectUrl, setRedirectUrl] = useState(null);
7760
+ const [sessionId, setSessionId] = useState(resume?.sessionId ?? null);
7761
+ const [provider, setProvider] = useState(resume?.provider ?? null);
7662
7762
  const [error, setError] = useState(null);
7663
7763
  const [copied, setCopied] = useState(false);
7664
7764
  const [checking, setChecking] = useState(false);
@@ -7674,15 +7774,27 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7674
7774
  setPhase("opening");
7675
7775
  setError(null);
7676
7776
  try {
7677
- const session = await shop.openCheckoutSession(input);
7777
+ const session = await shop.openCheckoutSession({
7778
+ ...input,
7779
+ // Para onde o adquirente devolve o comprador. É esta página, com o id da
7780
+ // sessão pendurado: quem volta volta em página nova, sem estado nenhum.
7781
+ returnUrl: typeof window === "undefined" ? null : window.location.href.split("?")[0]
7782
+ });
7678
7783
  setSessionId(session.sessionId);
7679
7784
  setProvider(session.provider ?? input.provider ?? null);
7680
7785
  setTotal(session.total);
7681
7786
  setCharge(session.charge);
7787
+ if (session.paymentUrl && !session.charge) {
7788
+ setRedirectUrl(session.paymentUrl);
7789
+ setPhase("redirect");
7790
+ return;
7791
+ }
7682
7792
  setPhase(session.charge ? "waiting" : "error");
7683
7793
  if (!session.charge) setError("N\xE3o conseguimos abrir a cobran\xE7a agora.");
7684
7794
  } catch (err) {
7685
- if (err?.status === 404) {
7795
+ const status = err?.status;
7796
+ const code = err?.code;
7797
+ if (status === 404 || code === "method_unavailable") {
7686
7798
  onUnavailable?.();
7687
7799
  return;
7688
7800
  }
@@ -7693,14 +7805,18 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7693
7805
  useEffect(() => {
7694
7806
  if (opened.current) return;
7695
7807
  opened.current = true;
7808
+ if (resume?.sessionId) {
7809
+ setPhase("waiting");
7810
+ return;
7811
+ }
7696
7812
  void open();
7697
- }, [open]);
7813
+ }, [open, resume?.sessionId]);
7698
7814
  const check = useCallback(async () => {
7699
7815
  if (!sessionId || settled.current) return false;
7700
7816
  const shop = getShopProvider();
7701
7817
  if (!shop.settleCheckoutSession) return false;
7702
7818
  try {
7703
- const result = await shop.settleCheckoutSession(sessionId, provider);
7819
+ const result = await shop.settleCheckoutSession(sessionId, provider, resume?.chargeToken ?? null);
7704
7820
  if (settled.current) return false;
7705
7821
  if (result.state === "paid" && result.orderId) {
7706
7822
  settled.current = true;
@@ -7718,7 +7834,7 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7718
7834
  } catch {
7719
7835
  return false;
7720
7836
  }
7721
- }, [sessionId, provider, onPaid]);
7837
+ }, [sessionId, provider, resume?.chargeToken, onPaid]);
7722
7838
  useEffect(() => {
7723
7839
  if (phase !== "waiting" || !sessionId) return;
7724
7840
  let stopped = false;
@@ -7737,13 +7853,52 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7737
7853
  }
7738
7854
  async function copy() {
7739
7855
  if (!charge?.emv) return;
7740
- try {
7741
- await navigator.clipboard.writeText(charge.emv);
7856
+ if (await copyText(charge.emv)) {
7742
7857
  setCopied(true);
7743
7858
  setTimeout(() => setCopied(false), 2400);
7744
- } catch {
7745
- toast.error("Copie o c\xF3digo manualmente");
7859
+ return;
7746
7860
  }
7861
+ toast.error("Copie o c\xF3digo manualmente");
7862
+ }
7863
+ if (phase === "redirect" && redirectUrl) {
7864
+ const acquirer = gatewayLabel(provider);
7865
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-4 rounded-xl border bg-card p-6", children: [
7866
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7867
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-5 w-5 text-primary" }),
7868
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: acquirer ? `Pague com cart\xE3o na p\xE1gina da ${acquirer}` : "Pague com cart\xE3o" })
7869
+ ] }),
7870
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: acquirer ? `Os dados do cart\xE3o s\xE3o digitados no ambiente seguro da ${acquirer} \u2014 a loja n\xE3o v\xEA o n\xFAmero. Ao terminar, voc\xEA volta para c\xE1 e o pedido \xE9 criado.` : "Os dados do cart\xE3o s\xE3o digitados no ambiente seguro do provedor \u2014 a loja n\xE3o v\xEA o n\xFAmero. Ao terminar, voc\xEA volta para c\xE1 e o pedido \xE9 criado." }),
7871
+ total != null && /* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
7872
+ "Valor: ",
7873
+ /* @__PURE__ */ jsx("strong", { "data-testid": TID.checkoutPaymentTotal, children: money(total) })
7874
+ ] }),
7875
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
7876
+ /* @__PURE__ */ jsxs(
7877
+ "button",
7878
+ {
7879
+ type: "button",
7880
+ onClick: () => {
7881
+ if (sessionId) rememberSession(sessionId, provider);
7882
+ window.location.assign(redirectUrl);
7883
+ },
7884
+ className: "inline-flex min-h-11 items-center gap-2 rounded-lg bg-primary px-5 py-2.5 text-sm font-semibold text-primary-foreground transition hover:opacity-90",
7885
+ children: [
7886
+ /* @__PURE__ */ jsx(Lock, { className: "h-4 w-4" }),
7887
+ acquirer ? `Pagar com ${acquirer}` : "Pagar com cart\xE3o"
7888
+ ]
7889
+ }
7890
+ ),
7891
+ /* @__PURE__ */ jsx(
7892
+ "button",
7893
+ {
7894
+ type: "button",
7895
+ onClick: onBack,
7896
+ className: "inline-flex min-h-11 items-center rounded-lg border px-4 py-2.5 text-sm font-medium transition hover:bg-muted",
7897
+ children: "Voltar"
7898
+ }
7899
+ )
7900
+ ] })
7901
+ ] });
7747
7902
  }
7748
7903
  if (phase === "opening") {
7749
7904
  return (
@@ -7805,6 +7960,30 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7805
7960
  ] })
7806
7961
  ] });
7807
7962
  }
7963
+ if (!charge) {
7964
+ return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-3 rounded-xl border bg-card p-6", "aria-live": "polite", children: [
7965
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
7966
+ /* @__PURE__ */ jsx(PulseDot, {}),
7967
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: "Confirmando seu pagamento\u2026" })
7968
+ ] }),
7969
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Assim que o pagamento for confirmado, seu pedido aparece aqui. Pode deixar esta tela aberta." }),
7970
+ /* @__PURE__ */ jsxs(
7971
+ "button",
7972
+ {
7973
+ type: "button",
7974
+ onClick: () => {
7975
+ void confirmPaid();
7976
+ },
7977
+ disabled: checking,
7978
+ className: "inline-flex min-h-11 items-center gap-2 rounded-lg border border-primary px-4 py-2.5 text-sm font-semibold text-primary transition hover:bg-primary/5 disabled:opacity-60",
7979
+ children: [
7980
+ checking ? /* @__PURE__ */ jsx(PulseDot, {}) : /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
7981
+ checking ? "Verificando\u2026" : "J\xE1 paguei"
7982
+ ]
7983
+ }
7984
+ )
7985
+ ] });
7986
+ }
7808
7987
  return /* @__PURE__ */ jsxs("div", { "data-testid": TID.checkoutPayment, className: "space-y-4", "aria-live": "polite", children: [
7809
7988
  /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card p-6", children: [
7810
7989
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -7813,12 +7992,14 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7813
7992
  ] }),
7814
7993
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Seu pedido \xE9 criado assim que o pagamento entrar. Pode deixar esta tela aberta." }),
7815
7994
  /* @__PURE__ */ jsxs("div", { className: "mt-5 grid gap-5 sm:grid-cols-[auto_minmax(0,1fr)] sm:items-start", children: [
7816
- charge?.qrcodeBase64 && /* @__PURE__ */ jsx(
7995
+ (charge?.qrcodeBase64 || charge?.qrcodeUrl) && /* @__PURE__ */ jsx(
7817
7996
  "img",
7818
7997
  {
7819
- src: `data:image/png;base64,${charge.qrcodeBase64}`,
7998
+ src: charge.qrcodeBase64 ? `data:image/png;base64,${charge.qrcodeBase64}` : charge.qrcodeUrl,
7820
7999
  alt: "QR code do Pix",
7821
- className: "mx-auto h-44 w-44 rounded-lg border bg-white p-2"
8000
+ width: 276,
8001
+ height: 276,
8002
+ className: "mx-auto box-content h-[276px] w-[276px] max-w-full rounded-lg border bg-white p-4"
7822
8003
  }
7823
8004
  ),
7824
8005
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
@@ -7835,7 +8016,28 @@ function CheckoutPayment({ input, onPaid, onBack, money, onUnavailable }) {
7835
8016
  children: charge?.emv
7836
8017
  }
7837
8018
  ),
7838
- /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", children: [
8019
+ expiry.secondsLeft !== null && !expiry.expired && /* @__PURE__ */ jsxs("p", { className: "mt-2 text-xs text-muted-foreground", children: [
8020
+ "Este c\xF3digo vale por mais",
8021
+ " ",
8022
+ /* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: formatCountdown(expiry.secondsLeft) })
8023
+ ] }),
8024
+ expiry.expired && /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
8025
+ /* @__PURE__ */ jsx("p", { "data-testid": TID.checkoutExpired, className: "text-sm text-muted-foreground", children: "O prazo deste c\xF3digo acabou. Gere outro para pagar." }),
8026
+ /* @__PURE__ */ jsx(
8027
+ "button",
8028
+ {
8029
+ type: "button",
8030
+ onClick: () => {
8031
+ opened.current = false;
8032
+ setCharge(null);
8033
+ void open();
8034
+ },
8035
+ className: "inline-flex min-h-11 items-center gap-2 rounded-lg bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground transition hover:opacity-90",
8036
+ children: "Gerar novo c\xF3digo"
8037
+ }
8038
+ )
8039
+ ] }),
8040
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-3", hidden: expiry.expired, children: [
7839
8041
  /* @__PURE__ */ jsxs(
7840
8042
  "button",
7841
8043
  {
@@ -7914,8 +8116,52 @@ function SuccessMark() {
7914
8116
  ) })
7915
8117
  ] });
7916
8118
  }
8119
+ function useKnownCustomer() {
8120
+ const customerId = useSessionStore((s2) => s2.customerId);
8121
+ const { orders, loading: ordersLoading } = useMyOrders();
8122
+ const [contact, setContact] = useState(null);
8123
+ const [checked, setChecked] = useState(false);
8124
+ useEffect(() => {
8125
+ if (!customerId) {
8126
+ setContact(null);
8127
+ setChecked(true);
8128
+ return;
8129
+ }
8130
+ let cancelled = false;
8131
+ const shop = getShopProvider();
8132
+ Promise.resolve(shop.getCustomer?.(customerId)).then((customer) => {
8133
+ if (cancelled) return;
8134
+ setContact(customer ? { phone: customer.phone ?? null, document: customer.document ?? null } : null);
8135
+ }).catch(() => {
8136
+ if (!cancelled) setContact(null);
8137
+ }).finally(() => {
8138
+ if (!cancelled) setChecked(true);
8139
+ });
8140
+ return () => {
8141
+ cancelled = true;
8142
+ };
8143
+ }, [customerId]);
8144
+ const lastAddress = pickLastAddress(orders);
8145
+ return {
8146
+ phone: contact?.phone ?? null,
8147
+ document: contact?.document ?? null,
8148
+ address: lastAddress,
8149
+ ready: checked && !ordersLoading
8150
+ };
8151
+ }
8152
+ function pickLastAddress(orders) {
8153
+ for (const order of orders) {
8154
+ const address = order.shippingAddress;
8155
+ if (address && (address.postalCode || address.street)) return address;
8156
+ }
8157
+ return null;
8158
+ }
7917
8159
  var PAYMENT_LABELS = {
7918
8160
  pix: { label: "Pix", hint: "Voc\xEA recebe a chave para pagar ap\xF3s confirmar o pedido" },
8161
+ // O cartão tem DOIS significados agora, e o texto muda com a loja: sem
8162
+ // adquirente é a maquininha do entregador; com adquirente é a página segura
8163
+ // dele. Ver PAYMENT_HINTS abaixo — prometer "maquininha" para quem vai ser
8164
+ // redirecionado é a loja mentindo sobre a própria entrega.
7919
8165
  credit_card: { label: "Cart\xE3o de cr\xE9dito", hint: "Maquininha na entrega" },
7920
8166
  debit_card: { label: "Cart\xE3o de d\xE9bito", hint: "Maquininha na entrega" },
7921
8167
  boleto: { label: "Boleto", hint: "Enviado por e-mail ap\xF3s a confirma\xE7\xE3o" },
@@ -7964,12 +8210,26 @@ function CheckoutPage() {
7964
8210
  const fieldPolicy = policy?.checkoutFields ?? {};
7965
8211
  const gatewayCheckout = config.payments.mode === "gateway" || !!policy?.paymentProvider;
7966
8212
  const shows = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true : fieldPolicy[key]?.visible !== false;
7967
- const requires = (key) => key === "document" ? gatewayCheckout || fieldPolicy.document?.visible === true && fieldPolicy.document?.required === true : fieldPolicy[key]?.visible !== false && fieldPolicy[key]?.required === true;
8213
+ const requires = (key) => key === "document" || key === "phone" ? gatewayCheckout || fieldPolicy[key]?.visible === true && fieldPolicy[key]?.required === true : fieldPolicy[key]?.visible !== false && fieldPolicy[key]?.required === true;
7968
8214
  const [selectedAddressId, setSelectedAddressId] = useState("new");
7969
8215
  const [savedAddresses, setSavedAddresses] = useState([]);
7970
8216
  const [addressesLoading, setAddressesLoading] = useState(false);
7971
8217
  const [addressMode, setAddressMode] = useState("new");
7972
- const [paymentMethod, setPaymentMethod] = useState(paymentMethods[0] ?? "pix");
8218
+ const [resume] = useState(() => {
8219
+ if (typeof window === "undefined") return null;
8220
+ const params = new URLSearchParams(window.location.search);
8221
+ const sessionId = params.get("vindi_session") ?? params.get("pay_session");
8222
+ if (!sessionId) return null;
8223
+ return {
8224
+ sessionId,
8225
+ provider: recallSessionProvider(sessionId),
8226
+ chargeToken: params.get("token_transaction")
8227
+ };
8228
+ });
8229
+ const [paymentMethod, setPaymentMethod] = useState(
8230
+ resume ? "credit_card" : paymentMethods[0] ?? "pix"
8231
+ );
8232
+ const known = useKnownCustomer();
7973
8233
  const [form, setForm] = useState({
7974
8234
  email: session.email ?? "",
7975
8235
  name: session.name ?? "",
@@ -7983,7 +8243,22 @@ function CheckoutPage() {
7983
8243
  district: "",
7984
8244
  state: ""
7985
8245
  });
7986
- const [step, setStep] = useState(1);
8246
+ React4.useEffect(() => {
8247
+ if (!known.ready) return;
8248
+ setForm((current) => ({
8249
+ ...current,
8250
+ phone: current.phone || known.phone || "",
8251
+ document: current.document || known.document || "",
8252
+ zip: current.zip || known.address?.postalCode || "",
8253
+ street: current.street || known.address?.street || "",
8254
+ number: current.number || known.address?.number || "",
8255
+ complement: current.complement || known.address?.complement || "",
8256
+ district: current.district || known.address?.district || "",
8257
+ city: current.city || known.address?.city || "",
8258
+ state: current.state || known.address?.state || ""
8259
+ }));
8260
+ }, [known.ready, known.phone, known.document, known.address]);
8261
+ const [step, setStep] = useState(resume ? 3 : 1);
7987
8262
  const [paidOrderId, setPaidOrderId] = useState(null);
7988
8263
  const [error, setError] = useState(null);
7989
8264
  const [discountCode, setDiscountCode] = useState("");
@@ -8006,7 +8281,26 @@ function CheckoutPage() {
8006
8281
  quantity: line.quantity,
8007
8282
  optionsLabel: line.optionsLabel ?? null
8008
8283
  })),
8009
- customer: { name: form.name.trim(), email: form.email.trim() },
8284
+ // The document and the phone travel WITH the buyer, not only inside the
8285
+ // address.
8286
+ //
8287
+ // The acquirer charges a PERSON: Vindi refuses a charge with no document
8288
+ // ("Informe o CPF ou CNPJ do comprador"), and AbacatePay's customer block is
8289
+ // all-or-nothing — name, email, taxId and cellphone, or no customer at all.
8290
+ // With two of the four it had been opening charges while registering nobody,
8291
+ // which is the "no client was created" the store saw in the provider account.
8292
+ //
8293
+ // The field was already asked for on screen: with an acquirer connected,
8294
+ // `requires('document')` is true and nobody gets past step 1 without filling
8295
+ // it in. What was missing was forwarding it to whoever charges. On the ORDER
8296
+ // it still travels inside the address, because of shop_place_order's
8297
+ // signature — different roads.
8298
+ customer: {
8299
+ name: form.name.trim(),
8300
+ email: form.email.trim(),
8301
+ document: documentDigits(form.document) || void 0,
8302
+ phone: form.phone.trim() || void 0
8303
+ },
8010
8304
  shippingAddress: {
8011
8305
  postalCode: form.zip.trim(),
8012
8306
  street: form.street.trim(),
@@ -8026,12 +8320,21 @@ function CheckoutPage() {
8026
8320
  provider: policy?.paymentProvider ?? null
8027
8321
  // eslint-disable-next-line react-hooks/exhaustive-deps
8028
8322
  }), [cart.lines, cart.discountCode, form, paymentMethod, policy?.paymentProvider]);
8029
- const [acquirerMissing, setAcquirerMissing] = useState(false);
8030
- const chargesOnline = paymentMethod === "pix" && !acquirerMissing && (!policy?.paymentProvider || isChargeableProvider(policy.paymentProvider));
8323
+ const [unavailable, setUnavailable] = useState([]);
8324
+ const cardIsCharged = !unavailable.includes("credit_card") && (!policy?.paymentProvider || isChargeableProvider(policy.paymentProvider)) && (!policy?.paymentProvider || supportsCard(policy.paymentProvider));
8325
+ const acquirerMissing = unavailable.includes(paymentMethod);
8326
+ const chargesOnline = (
8327
+ // Pix é cobrado dentro da loja; cartão é pago na página do adquirente e
8328
+ // volta para cá. Débito, dinheiro e "combinar" nunca foram cobrança online.
8329
+ // `cardIsCharged` rather than a bare `credit_card`: card is charged online
8330
+ // only where the acquirer charges cards. Where it does not, this screen
8331
+ // offers the order without a charge — the card-reader road, as it always was.
8332
+ (paymentMethod === "pix" || paymentMethod === "credit_card" && cardIsCharged) && !acquirerMissing && (!policy?.paymentProvider || isChargeableProvider(policy.paymentProvider))
8333
+ );
8031
8334
  const money = (value) => formatMoney(value, config.currency, config.locale);
8032
8335
  useEffect(() => {
8033
- if (cart.lines.length === 0 && !paidOrderId && !leaving.current) navigateTo(config.catalogPath);
8034
- }, [cart.lines.length, config.catalogPath, paidOrderId]);
8336
+ if (cart.lines.length === 0 && !paidOrderId && !leaving.current && !resume) navigateTo(config.catalogPath);
8337
+ }, [cart.lines.length, config.catalogPath, paidOrderId, resume]);
8035
8338
  const set = (key) => (value) => {
8036
8339
  setError(null);
8037
8340
  setForm((current) => ({ ...current, [key]: value }));
@@ -8218,8 +8521,13 @@ function CheckoutPage() {
8218
8521
  if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" });
8219
8522
  }
8220
8523
  function advance() {
8221
- if (step === 1 && validateContact()) goToStep(2);
8222
- else if (step === 2 && validateDelivery()) goToStep(3);
8524
+ if (step === 1 && validateContact()) {
8525
+ void getShopProvider().saveCustomerContact?.({
8526
+ phone: form.phone.trim() || null,
8527
+ document: documentDigits(form.document) || null
8528
+ });
8529
+ goToStep(2);
8530
+ } else if (step === 2 && validateDelivery()) goToStep(3);
8223
8531
  }
8224
8532
  async function confirmWithoutCharge() {
8225
8533
  if (!validate()) return;
@@ -8491,7 +8799,8 @@ function CheckoutPage() {
8491
8799
  step === 3 && !placing && /* @__PURE__ */ jsxs("section", { children: [
8492
8800
  /* @__PURE__ */ jsx("h2", { className: "mb-3 text-xl font-semibold tracking-tight", children: "Pagamento" }),
8493
8801
  /* @__PURE__ */ jsx("div", { className: "mb-3 grid gap-3", role: "radiogroup", "aria-label": "Forma de pagamento", children: paymentMethods.map((method) => {
8494
- const copy = PAYMENT_LABELS[method];
8802
+ const base = PAYMENT_LABELS[method];
8803
+ const copy = method === "credit_card" && cardIsCharged ? { ...base, hint: "Voc\xEA paga na p\xE1gina segura do provedor e volta para a loja" } : base;
8495
8804
  const selected = paymentMethod === method;
8496
8805
  return (
8497
8806
  // Cada forma de pagamento abre o que ELA pede, embaixo dela
@@ -8525,7 +8834,8 @@ function CheckoutPage() {
8525
8834
  input: checkoutSessionInput,
8526
8835
  money,
8527
8836
  onBack: () => goToStep(2),
8528
- onUnavailable: () => setAcquirerMissing(true),
8837
+ resume,
8838
+ onUnavailable: () => setUnavailable((list) => list.includes(method) ? list : [...list, method]),
8529
8839
  onPaid: (orderId) => {
8530
8840
  leaving.current = true;
8531
8841
  setPaidOrderId(orderId);
@@ -9133,45 +9443,11 @@ function OrderConfirmationPage({ orderId }) {
9133
9443
  }
9134
9444
  );
9135
9445
  }
9136
- function useMyOrders() {
9137
- const customerId = useSessionStore((s2) => s2.customerId);
9138
- const email = useSessionStore((s2) => s2.email);
9139
- const [orders, setOrders] = useState([]);
9140
- const [loading, setLoading] = useState(true);
9141
- const [tick, setTick] = useState(0);
9142
- const refresh = useCallback(() => setTick((t) => t + 1), []);
9143
- useEffect(() => {
9144
- let cancelled = false;
9145
- const local = listLocalOrderIds();
9146
- if (!customerId && !email) {
9147
- if (local.length === 0) {
9148
- setOrders([]);
9149
- setLoading(false);
9150
- return;
9151
- }
9152
- }
9153
- setLoading(true);
9154
- const query = customerId ? { customerId, limit: 50 } : { customerEmail: email, limit: 50 };
9155
- getShopProvider().listOrders(query).then((data) => {
9156
- if (!cancelled) setOrders(data);
9157
- }).catch(async () => {
9158
- const mine = await Promise.all(
9159
- listLocalOrderIds().map((id) => getShopProvider().getOrder(id).catch(() => null))
9160
- );
9161
- if (!cancelled) setOrders(mine.filter((o) => !!o));
9162
- }).finally(() => {
9163
- if (!cancelled) setLoading(false);
9164
- });
9165
- return () => {
9166
- cancelled = true;
9167
- };
9168
- }, [customerId, email, tick]);
9169
- return { orders, loading, refresh };
9170
- }
9171
9446
  function AuthForm() {
9172
9447
  const [mode, setMode] = useState("signin");
9173
9448
  const [email, setEmail] = useState("");
9174
9449
  const [password, setPassword] = useState("");
9450
+ const [confirmPassword, setConfirmPassword] = useState("");
9175
9451
  const [name, setName] = useState("");
9176
9452
  const [busy, setBusy] = useState(false);
9177
9453
  const [error, setError] = useState(null);
@@ -9185,6 +9461,10 @@ function AuthForm() {
9185
9461
  setError("Informe seu nome.");
9186
9462
  return;
9187
9463
  }
9464
+ if (mode === "signup" && password !== confirmPassword) {
9465
+ setError("As senhas n\xE3o s\xE3o iguais.");
9466
+ return;
9467
+ }
9188
9468
  setBusy(true);
9189
9469
  try {
9190
9470
  if (mode === "signup") await signUpCustomer(email, password, name.trim());
@@ -9260,6 +9540,20 @@ function AuthForm() {
9260
9540
  style: { borderRadius: "var(--sf-radius-input)" }
9261
9541
  }
9262
9542
  ),
9543
+ mode === "signup" && /* @__PURE__ */ jsx(
9544
+ "input",
9545
+ {
9546
+ "data-testid": TID.signinPasswordConfirm,
9547
+ type: "password",
9548
+ required: true,
9549
+ placeholder: "Confirmar senha",
9550
+ value: confirmPassword,
9551
+ onChange: (e) => setConfirmPassword(e.target.value),
9552
+ "aria-invalid": confirmPassword.length > 0 && password !== confirmPassword,
9553
+ className: "w-full border bg-background px-3 py-2.5 text-sm",
9554
+ style: { borderRadius: "var(--sf-radius-input)" }
9555
+ }
9556
+ ),
9263
9557
  notice && /* @__PURE__ */ jsx("p", { "data-testid": TID.authNotice, className: "text-sm font-medium text-emerald-700", children: notice }),
9264
9558
  error && /* @__PURE__ */ jsx("p", { "data-testid": TID.authError, className: "text-sm text-destructive", children: error }),
9265
9559
  /* @__PURE__ */ jsx(
@@ -11123,5 +11417,5 @@ ${formatErrors(report.errors)}`;
11123
11417
  }
11124
11418
 
11125
11419
  export { BEFORE_PLACE_ORDER_VALVE, BLOCK_STRUCTURAL_KEYS, BenefitsRow, BlockDataError, CART_LINE_ANNOTATION_POINT, CATALOG_BODY_BLOCK, CHECKOUT_FIELD_POINT, CartDrawer, CatalogPage, CategoryShowcase, CheckoutPage, CollectionCta, CollectionHero, ContentPage, CountdownBand, EmailConfirmationRequiredError, EntityListBlock, FaqSection, FiltersPanel, FormBlock, HeroSection, ImageTiles, Link, ManifestoBlock, MediaCarousel, MotifStrip, MyPurchasesPage, NewsletterBand, ORDER_METADATA_POINT, OrderConfirmationPage, OrderTrackingTimeline, PAGE_DATA_SCRIPT_ID, PLACEHOLDER_ASPECTS, PRODUCT_BODY_BLOCK, PRODUCT_CARD_BADGE_POINT, PageDataScope, PaymentTerms, Price, ProductCard, ProductDetailPage, ProductEnquiryForm, ProductGallery, ProductGrid, ProductOptionSelector, ProductRail, ProductReviews, ProductSlider, ProductSpecs, ProductSpotlight, PromoBanner, QuantityInput, Reveal, STOREFRONT_COMPONENT_KEYS, STOREFRONT_PAGE_KINDS, STOREFRONT_SLOTS, STORE_BACKEND_PROVIDERS, STORE_COMMERCE_MODES, STORE_DOCUMENT_KIND, STORE_PAYMENT_METHOD_KINDS, STORE_PAYMENT_MODES, STORE_ROUTE_CHROMES, STORE_ROUTE_KINDS, STORE_SECTIONS, STORE_SECTION_NAMES, SealsBand, SearchOverlay, Slot, SmoothImage, StepsSection, StorefrontConfigHost, StorefrontConfigProvider, StorefrontFooter, StorefrontHeader, StorefrontPage, StorefrontRouterProvider, StorefrontShell, StorefrontThemeStyle, StoryQuote, THEME_SCALE_FIELDS, THEME_SCALE_VAR_PREFIX, TID, Testimonials, TierCards, applyStoreSettings, bannerPlaceholder, blockInstanceId, buildEnquiryFromForm, checkPageBlocks, collectDataNeeds, collectOrderMetadata, contentPagePath, createStorefront, createStorefrontApp, currentStaticRenderPass, defaultPageBlocks, entityListBlockMeta, establishCustomerSession, exportStore, formatMoney, formatProductOptionSelection, getCartLineAnnotations, getCheckoutFieldContributions, getCustomerAuthAdapter, getProductCardBadges, getProductOptionGroups, hydrateStoreDocument, hydratedEntriesFor, initStorefrontRuntime, isJsonValue, isKnownPath, isNestedBlockRef, isPublicPath, isRecord, listSlots, matchContentPage, matchPath, matchesFacets, mergeCollectionFacet, mergePageTheme, navigateTo, nextMilestone, normalizeProductOptionSelection, orderGalleryImages, overrideComponent, overrideProps, pageSeo, pageThemeSelector, pickProductCardImage, placeStorefrontOrder, prefersReducedMotion, primaryImageUrl, productCardComponentContract, productJsonLd, productOptionSelectionKey, productPlaceholder, productSeo, readHydratedPageData, registerStorefrontBlocks, registerStorefrontEntities, registerStorefrontExtensionPoints, registerStorefrontLoaders, resetPageDiagnostics, resetProductOptionsDeprecationWarnings, resetStorefrontExtensionPoints, resolveAuthAdapter, resolveConfig, resolveDataParams, resolveOverride, resolvePageData, resolvePageDataNeed, resolvePaymentTerms, resolveStorefrontProvider, resolveStorefrontRoute, resolveVariantForSelection, roundCents, routePathParams, runBeforePlaceOrderValve, sectionToJsonSchema, sectionsToBlocks, selectCount, selectDiscountTotal, selectRequiresShipping, selectShipping, selectSubtotal, selectTotal, selectionPrice, serializePageData, shellRouterAdapter, signInByEmail, signOutCustomer, signUpCustomer, slugifyStoreName, storefrontComponentContracts, storefrontPaths, themeToCss, toBlockNodes, toFacetMap, useBlockData, useCartStore, useCatalogStore, useCategories, useDeliveryStore, useDiscountValidator, useEnquiry, useHashPath, useInView, useLoader, useMyOrders, useNavigate, usePopOnChange, useProduct, useProducts, useRoutePath, useRouterAdapter, useScrollToTopOnNavigate, useScrolled, useSessionStore, useSlotContext, useStorefrontActions, useStorefrontConfig, useStorefrontConfigOptional, useStorefrontHead, useStorefrontPage, useStorefrontPageOptional, usedSlots, validateStore, withFacet, withStaticRenderPass };
11126
- //# sourceMappingURL=chunk-LTAB2QMM.js.map
11127
- //# sourceMappingURL=chunk-LTAB2QMM.js.map
11420
+ //# sourceMappingURL=chunk-AEV2QRRH.js.map
11421
+ //# sourceMappingURL=chunk-AEV2QRRH.js.map