@asteby/storefront-react 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/payments.cjs CHANGED
@@ -146,6 +146,7 @@ function sanitizeMercadoPagoFormData(data) {
146
146
  function mercadoPagoBricksAdapter(options = {}) {
147
147
  const allowed = options.allowedSdkOrigins ?? ["https://sdk.mercadopago.com"];
148
148
  const getSdk = options.getSdk ?? (() => typeof window !== "undefined" ? window.MercadoPago : void 0);
149
+ const retryDelay = options.collectRetryDelayMs ?? 400;
149
150
  const getDevice = options.getDeviceSessionId ?? (() => typeof window !== "undefined" ? window.MP_DEVICE_SESSION_ID : void 0);
150
151
  return {
151
152
  kind: MERCADOPAGO_BRICKS_KIND,
@@ -187,9 +188,21 @@ function mercadoPagoBricksAdapter(options = {}) {
187
188
  return {
188
189
  async collect() {
189
190
  if (gone) throw new Error("payment form is unmounted");
190
- const raw = await controller.getFormData();
191
- const provider_data = sanitizeMercadoPagoFormData(raw);
192
- if (!provider_data.token) throw new Error("payment form incomplete");
191
+ const read = async () => {
192
+ const raw = await controller.getFormData();
193
+ const data = sanitizeMercadoPagoFormData(raw);
194
+ if (!data.token) throw new Error("payment form incomplete");
195
+ return data;
196
+ };
197
+ let provider_data;
198
+ try {
199
+ provider_data = await read();
200
+ } catch (first) {
201
+ if (gone) throw first;
202
+ await new Promise((r) => setTimeout(r, retryDelay));
203
+ if (gone) throw first;
204
+ provider_data = await read();
205
+ }
193
206
  const device = getDevice();
194
207
  return { provider_data, ...typeof device === "string" && device ? { device_session_id: device } : {} };
195
208
  },
@@ -621,7 +634,7 @@ function useEmbeddedPayment(options) {
621
634
  autoRetries = 2,
622
635
  retryDelayMs = 600,
623
636
  pollIntervalMs = 2e3,
624
- pollTimeoutMs = 6e4,
637
+ pollTimeoutMs = 3e5,
625
638
  verifyTimeoutMs = 12e4,
626
639
  verifyIntervalMs = 2e3,
627
640
  scope = "default"
@@ -650,6 +663,7 @@ function useEmbeddedPayment(options) {
650
663
  const alive = react.useRef(true);
651
664
  const generation = react.useRef(0);
652
665
  const statusRef = react.useRef("idle");
666
+ const leftChallenge = react.useRef(false);
653
667
  statusRef.current = status;
654
668
  errorRef.current = error;
655
669
  const amountKey = `${amount.amount}|${amount.currency_code}`;
@@ -833,6 +847,7 @@ function useEmbeddedPayment(options) {
833
847
  (result) => {
834
848
  const o = storefront.paymentOutcome(result, live.current.locale, live.current.messages);
835
849
  pendingData.current = null;
850
+ leftChallenge.current = false;
836
851
  setOutcome(o);
837
852
  setError(null);
838
853
  setAttemptsLeft(o.attempts_left);
@@ -917,15 +932,21 @@ function useEmbeddedPayment(options) {
917
932
  setError(null);
918
933
  setOutcome(null);
919
934
  emit({ type: "submit" });
935
+ const withdrawn = () => !alive.current || form.current !== m;
936
+ const settle = () => {
937
+ if (alive.current) setStatus((s) => s === "processing" ? "idle" : s);
938
+ };
920
939
  let data;
921
940
  try {
922
941
  data = await m.collect();
923
942
  } catch {
943
+ if (withdrawn()) return settle();
924
944
  emit({ type: "tokenize_error" });
925
945
  setError({ kind: "generic", message: live.current.msgs["error.card_data"], retrySameAttempt: false, canRetry: true });
926
946
  setStatus("ready");
927
947
  return;
928
948
  }
949
+ if (withdrawn()) return settle();
929
950
  const attempt = readAttempt();
930
951
  setAttemptId(attempt);
931
952
  await send(data, attempt, total);
@@ -993,7 +1014,7 @@ function useEmbeddedPayment(options) {
993
1014
  }
994
1015
  let stop = false;
995
1016
  const ctl = new AbortController();
996
- const until = Date.now() + verifyTimeoutMs;
1017
+ const until = Date.now() + (leftChallenge.current ? Math.max(verifyTimeoutMs, pollTimeoutMs) : verifyTimeoutMs);
997
1018
  setVerifying(true);
998
1019
  (async () => {
999
1020
  let wait = verifyIntervalMs;
@@ -1005,6 +1026,8 @@ function useEmbeddedPayment(options) {
1005
1026
  const r = await transport.refresh({ signal: ctl.signal });
1006
1027
  if (stop || !alive.current) return;
1007
1028
  if (isResolvedAttempt(r)) {
1029
+ const settled = r.payment.status === "paid" || r.payment.status === "failed" || r.payment.status === "expired" || r.payment.status === "cancelled";
1030
+ if (leftChallenge.current && !settled && r.payment.action?.type === "challenge") continue;
1008
1031
  setVerifying(false);
1009
1032
  finish(r);
1010
1033
  return;
@@ -1019,11 +1042,18 @@ function useEmbeddedPayment(options) {
1019
1042
  ctl.abort();
1020
1043
  setVerifying(false);
1021
1044
  };
1022
- }, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, finish]);
1045
+ }, [unknownOutcome, transport, verifyTimeoutMs, verifyIntervalMs, pollTimeoutMs, finish]);
1023
1046
  const dismiss = react.useCallback(() => {
1024
1047
  setError(null);
1025
1048
  setOutcome((o) => o?.kind === "rejected" ? null : o);
1026
1049
  }, []);
1050
+ const cancelChallenge = react.useCallback(() => {
1051
+ if (statusRef.current !== "challenge") return;
1052
+ leftChallenge.current = true;
1053
+ setOutcome(null);
1054
+ setError({ kind: "payment_in_progress", message: live.current.msgs["challenge.left"], retrySameAttempt: false, canRetry: false });
1055
+ setStatus("blocked");
1056
+ }, []);
1027
1057
  const message = status === "processing" ? msgs["state.processing"] : status === "fallback" ? msgs["state.fallback"] : error?.message ?? outcome?.message ?? (status === "loading" ? msgs["state.loading"] : null);
1028
1058
  return {
1029
1059
  status,
@@ -1039,7 +1069,8 @@ function useEmbeddedPayment(options) {
1039
1069
  containerId,
1040
1070
  submit,
1041
1071
  retry,
1042
- dismiss
1072
+ dismiss,
1073
+ cancelChallenge
1043
1074
  };
1044
1075
  }
1045
1076
  function VoucherCard({ outcome, locale, messages, className }) {
@@ -1076,15 +1107,55 @@ function VoucherCard({ outcome, locale, messages, className }) {
1076
1107
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: m["voucher.email"] })
1077
1108
  ] });
1078
1109
  }
1079
- function PaymentChallenge({ outcome, locale, messages, render, onCancel }) {
1110
+ function PaymentChallenge({ outcome, locale, messages, render, onCancel, escapeAfterMs = 12e4 }) {
1080
1111
  const m = storefront.paymentMessages(locale, messages);
1081
- const { ref } = useModalDialog({ open: true, onClose: () => onCancel?.() });
1112
+ const { ref } = useModalDialog({ open: true, onClose: () => {
1113
+ } });
1114
+ const [canEscape, setCanEscape] = react.useState(false);
1115
+ const [closed, setClosed] = react.useState(false);
1116
+ const cancelRef = react.useRef(onCancel);
1117
+ cancelRef.current = onCancel;
1118
+ const hasCancel = !!onCancel;
1119
+ react.useEffect(() => {
1120
+ if (!hasCancel) return;
1121
+ const t = setTimeout(() => setCanEscape(true), escapeAfterMs);
1122
+ return () => clearTimeout(t);
1123
+ }, [hasCancel, escapeAfterMs]);
1124
+ react.useEffect(() => {
1125
+ const el = ref.current;
1126
+ if (!el) return;
1127
+ const onClosed = () => setClosed(true);
1128
+ el.addEventListener("close", onClosed);
1129
+ return () => el.removeEventListener("close", onClosed);
1130
+ }, [ref]);
1131
+ const reopen = () => {
1132
+ const el = ref.current;
1133
+ if (!el) return;
1134
+ try {
1135
+ if (typeof el.showModal === "function") el.showModal();
1136
+ else el.setAttribute("open", "");
1137
+ } catch {
1138
+ el.setAttribute("open", "");
1139
+ }
1140
+ setClosed(false);
1141
+ el.querySelector("[data-sf-autofocus]")?.focus({ preventScroll: true });
1142
+ };
1082
1143
  const url = outcome.challenge?.url;
1083
- return /* @__PURE__ */ jsxRuntime.jsx("dialog", { ref, "aria-labelledby": "sf-challenge-title", "data-sf-challenge": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1084
- /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "sf-challenge-title", "data-sf-autofocus": true, tabIndex: -1, children: m["challenge.title"] }),
1085
- render ? render(outcome) : url ? /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: url, title: m["challenge.title"], style: { width: "100%", minHeight: 420, border: 0 } }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: m["state.challenge"] }),
1086
- onCancel && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, style: { minWidth: 44, minHeight: 44 }, children: m["action.cancel"] })
1087
- ] }) });
1144
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1145
+ closed && /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "status", "data-sf-challenge-closed": true, children: [
1146
+ m["state.challenge"],
1147
+ " ",
1148
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: reopen, "data-sf-challenge-reopen": true, style: { minWidth: 44, minHeight: 44 }, children: m["challenge.title"] })
1149
+ ] }),
1150
+ /* @__PURE__ */ jsxRuntime.jsx("dialog", { ref, "aria-labelledby": "sf-challenge-title", "data-sf-challenge": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-sf-challenge-body": true, children: [
1151
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "sf-challenge-title", "data-sf-autofocus": true, tabIndex: -1, children: m["challenge.title"] }),
1152
+ render ? render(outcome) : url ? /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: url, title: m["challenge.title"], "data-sf-challenge-frame": true, style: { width: "100%", minHeight: 420, border: 0 } }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: m["state.challenge"] }),
1153
+ hasCancel && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-sf-challenge-escape": true, "aria-live": "polite", children: canEscape && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1154
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: m["challenge.escape_hint"] }),
1155
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => cancelRef.current?.(), style: { minWidth: 44, minHeight: 44 }, children: m["challenge.escape"] })
1156
+ ] }) })
1157
+ ] }) })
1158
+ ] });
1088
1159
  }
1089
1160
  var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
1090
1161
  const { submitLabel, hideAmountInButton, renderVoucher, renderChallenge, minHeight = 360, className, style, ...hookOptions } = props;
@@ -1122,7 +1193,7 @@ var PaymentBrick = react.forwardRef(function PaymentBrick2(props, ref) {
1122
1193
  state.status === "network_error" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void state.retry(), style: { minHeight: 44 }, children: m["action.retry"] })
1123
1194
  ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", "aria-live": "polite", "data-sf-pay-message": true, children: state.message }),
1124
1195
  outcome?.kind === "voucher" && (renderVoucher ? renderVoucher(outcome) : /* @__PURE__ */ jsxRuntime.jsx(VoucherCard, { outcome, locale, ...props.messages ? { messages: props.messages } : {} })),
1125
- outcome?.kind === "challenge" && /* @__PURE__ */ jsxRuntime.jsx(PaymentChallenge, { outcome, locale, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
1196
+ outcome?.kind === "challenge" && /* @__PURE__ */ jsxRuntime.jsx(PaymentChallenge, { outcome, locale, onCancel: state.cancelChallenge, ...props.messages ? { messages: props.messages } : {}, ...renderChallenge ? { render: renderChallenge } : {} }),
1126
1197
  submitLabel !== void 0 && showForm && /* @__PURE__ */ jsxRuntime.jsxs(
1127
1198
  "button",
1128
1199
  {