@hook-sdk/template 0.28.8 → 0.28.10

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/index.d.cts CHANGED
@@ -61,6 +61,13 @@ type PaywallConfig = {
61
61
  };
62
62
  checkoutMethods: CheckoutMethod[];
63
63
  requiresCpf: boolean;
64
+ /**
65
+ * When true, CheckoutPageDefault coleta CEP + número do endereço (card-path)
66
+ * e envia ambos como `cardHolderInfo.postalCode`/`addressNumber`.
67
+ * Default false → ship placeholder `01001000`/`100` (legacy behavior).
68
+ * Apps que precisam de NF-e ou antifraude refinada devem ativar.
69
+ */
70
+ collectCep?: boolean;
64
71
  cancelWindowDays?: number;
65
72
  errorMessages: 'default' | 'custom';
66
73
  };
@@ -500,6 +507,10 @@ interface UseCheckoutFormResult {
500
507
  setPhone: (v: string) => void;
501
508
  cpf: string;
502
509
  setCpf: (v: string) => void;
510
+ postalCode: string;
511
+ setPostalCode: (v: string) => void;
512
+ addressNumber: string;
513
+ setAddressNumber: (v: string) => void;
503
514
  method: CheckoutFormMethod;
504
515
  setMethod: (m: CheckoutFormMethod) => void;
505
516
  cycle: CheckoutFormCycle;
@@ -511,11 +522,15 @@ interface UseCheckoutFormResult {
511
522
  emailConfirmError: string | null;
512
523
  phoneError: string | null;
513
524
  cpfError: string | null;
525
+ postalCodeError: string | null;
526
+ addressNumberError: string | null;
514
527
  markNameTouched: () => void;
515
528
  markEmailTouched: () => void;
516
529
  markEmailConfirmTouched: () => void;
517
530
  markPhoneTouched: () => void;
518
531
  markCpfTouched: () => void;
532
+ markPostalCodeTouched: () => void;
533
+ markAddressNumberTouched: () => void;
519
534
  /**
520
535
  * Result of the debounced server-side email-exists check, used to swap
521
536
  * the CTA into "Email já cadastrado · Entrar" on the checkout form.
@@ -539,8 +554,15 @@ interface UseCheckoutFormResult {
539
554
  interface UseCheckoutFormArgs {
540
555
  defaultMethod: CheckoutFormMethod;
541
556
  defaultCycle: CheckoutFormCycle;
557
+ /**
558
+ * Quando true, o submit envia postalCode + addressNumber reais coletados
559
+ * via setPostalCode/setAddressNumber. Quando false (default), envia
560
+ * placeholder `01001000`/`100`. CheckoutPageDefault liga via config
561
+ * `paywall.collectCep`.
562
+ */
563
+ collectCep?: boolean;
542
564
  }
543
- declare function useCheckoutForm(args: UseCheckoutFormArgs): UseCheckoutFormResult;
565
+ declare function useCheckoutForm(hookArgs: UseCheckoutFormArgs): UseCheckoutFormResult;
544
566
 
545
567
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
546
568
  type PaymentMethod = CheckoutMethod$1;
@@ -1082,6 +1104,7 @@ declare const AppConfigSchema: z.ZodObject<{
1082
1104
  "pix-once": "pix-once";
1083
1105
  }>>;
1084
1106
  requiresCpf: z.ZodBoolean;
1107
+ collectCep: z.ZodOptional<z.ZodBoolean>;
1085
1108
  cancelWindowDays: z.ZodOptional<z.ZodNumber>;
1086
1109
  errorMessages: z.ZodEnum<{
1087
1110
  default: "default";
package/dist/index.d.ts CHANGED
@@ -61,6 +61,13 @@ type PaywallConfig = {
61
61
  };
62
62
  checkoutMethods: CheckoutMethod[];
63
63
  requiresCpf: boolean;
64
+ /**
65
+ * When true, CheckoutPageDefault coleta CEP + número do endereço (card-path)
66
+ * e envia ambos como `cardHolderInfo.postalCode`/`addressNumber`.
67
+ * Default false → ship placeholder `01001000`/`100` (legacy behavior).
68
+ * Apps que precisam de NF-e ou antifraude refinada devem ativar.
69
+ */
70
+ collectCep?: boolean;
64
71
  cancelWindowDays?: number;
65
72
  errorMessages: 'default' | 'custom';
66
73
  };
@@ -500,6 +507,10 @@ interface UseCheckoutFormResult {
500
507
  setPhone: (v: string) => void;
501
508
  cpf: string;
502
509
  setCpf: (v: string) => void;
510
+ postalCode: string;
511
+ setPostalCode: (v: string) => void;
512
+ addressNumber: string;
513
+ setAddressNumber: (v: string) => void;
503
514
  method: CheckoutFormMethod;
504
515
  setMethod: (m: CheckoutFormMethod) => void;
505
516
  cycle: CheckoutFormCycle;
@@ -511,11 +522,15 @@ interface UseCheckoutFormResult {
511
522
  emailConfirmError: string | null;
512
523
  phoneError: string | null;
513
524
  cpfError: string | null;
525
+ postalCodeError: string | null;
526
+ addressNumberError: string | null;
514
527
  markNameTouched: () => void;
515
528
  markEmailTouched: () => void;
516
529
  markEmailConfirmTouched: () => void;
517
530
  markPhoneTouched: () => void;
518
531
  markCpfTouched: () => void;
532
+ markPostalCodeTouched: () => void;
533
+ markAddressNumberTouched: () => void;
519
534
  /**
520
535
  * Result of the debounced server-side email-exists check, used to swap
521
536
  * the CTA into "Email já cadastrado · Entrar" on the checkout form.
@@ -539,8 +554,15 @@ interface UseCheckoutFormResult {
539
554
  interface UseCheckoutFormArgs {
540
555
  defaultMethod: CheckoutFormMethod;
541
556
  defaultCycle: CheckoutFormCycle;
557
+ /**
558
+ * Quando true, o submit envia postalCode + addressNumber reais coletados
559
+ * via setPostalCode/setAddressNumber. Quando false (default), envia
560
+ * placeholder `01001000`/`100`. CheckoutPageDefault liga via config
561
+ * `paywall.collectCep`.
562
+ */
563
+ collectCep?: boolean;
542
564
  }
543
- declare function useCheckoutForm(args: UseCheckoutFormArgs): UseCheckoutFormResult;
565
+ declare function useCheckoutForm(hookArgs: UseCheckoutFormArgs): UseCheckoutFormResult;
544
566
 
545
567
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
546
568
  type PaymentMethod = CheckoutMethod$1;
@@ -1082,6 +1104,7 @@ declare const AppConfigSchema: z.ZodObject<{
1082
1104
  "pix-once": "pix-once";
1083
1105
  }>>;
1084
1106
  requiresCpf: z.ZodBoolean;
1107
+ collectCep: z.ZodOptional<z.ZodBoolean>;
1085
1108
  cancelWindowDays: z.ZodOptional<z.ZodNumber>;
1086
1109
  errorMessages: z.ZodEnum<{
1087
1110
  default: "default";
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ var PaywallNonFreeSchema = z.object({
58
58
  }).optional(),
59
59
  checkoutMethods: z.array(z.enum(["card", "pix-auto", "pix-once"])).min(1),
60
60
  requiresCpf: z.boolean(),
61
+ collectCep: z.boolean().optional(),
61
62
  cancelWindowDays: z.number().int().nonnegative().optional(),
62
63
  errorMessages: z.enum(["default", "custom"])
63
64
  });
@@ -2004,33 +2005,47 @@ function SessionExpiredBanner() {
2004
2005
  localStorage.setItem(DISMISS_KEY, String(Date.now() + DISMISS_TTL_MS));
2005
2006
  setShow(false);
2006
2007
  }
2007
- return /* @__PURE__ */ jsxs11("div", { role: "alert", className: "fixed top-0 inset-x-0 bg-red-600 text-white px-4 py-2 flex items-center justify-between gap-3 text-sm shadow", style: { zIndex: 10001 }, children: [
2008
- /* @__PURE__ */ jsxs11("span", { children: [
2009
- /* @__PURE__ */ jsx17("strong", { children: "Sua sess\xE3o expirou." }),
2010
- " Fa\xE7a login novamente para continuar."
2011
- ] }),
2012
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
2013
- /* @__PURE__ */ jsx17(
2014
- "button",
2015
- {
2016
- type: "button",
2017
- onClick: dismiss,
2018
- className: "px-3 py-1 bg-white text-red-700 rounded text-xs font-medium hover:bg-red-50",
2019
- children: "Fazer login"
2020
- }
2021
- ),
2022
- /* @__PURE__ */ jsx17(
2023
- "button",
2024
- {
2025
- type: "button",
2026
- onClick: dismiss,
2027
- "aria-label": "Fechar",
2028
- className: "px-2 py-1 text-white/80 hover:text-white text-xs",
2029
- children: "Fechar"
2030
- }
2031
- )
2032
- ] })
2033
- ] });
2008
+ return /* @__PURE__ */ jsxs11(
2009
+ "div",
2010
+ {
2011
+ role: "alert",
2012
+ className: "fixed top-0 inset-x-0 px-4 py-3 flex items-center justify-between gap-3 text-sm font-medium shadow-lg",
2013
+ style: {
2014
+ zIndex: 10001,
2015
+ backgroundColor: "#991b1b",
2016
+ color: "#ffffff"
2017
+ },
2018
+ children: [
2019
+ /* @__PURE__ */ jsxs11("span", { children: [
2020
+ /* @__PURE__ */ jsx17("strong", { className: "font-bold", children: "Sua sess\xE3o expirou." }),
2021
+ " Fa\xE7a login novamente para continuar."
2022
+ ] }),
2023
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
2024
+ /* @__PURE__ */ jsx17(
2025
+ "button",
2026
+ {
2027
+ type: "button",
2028
+ onClick: dismiss,
2029
+ className: "px-3 py-1.5 rounded text-xs font-semibold",
2030
+ style: { backgroundColor: "#ffffff", color: "#991b1b" },
2031
+ children: "Fazer login"
2032
+ }
2033
+ ),
2034
+ /* @__PURE__ */ jsx17(
2035
+ "button",
2036
+ {
2037
+ type: "button",
2038
+ onClick: dismiss,
2039
+ "aria-label": "Fechar",
2040
+ className: "px-2 py-1 text-xs",
2041
+ style: { color: "#ffffff" },
2042
+ children: "Fechar"
2043
+ }
2044
+ )
2045
+ ] })
2046
+ ]
2047
+ }
2048
+ );
2034
2049
  }
2035
2050
 
2036
2051
  // src/internal/EmailVerifyBanner.tsx
@@ -2850,15 +2865,17 @@ function mapSdkError(err) {
2850
2865
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2851
2866
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2852
2867
  var CHECK_DEBOUNCE_MS = 400;
2853
- function useCheckoutForm(args) {
2868
+ function useCheckoutForm(hookArgs) {
2854
2869
  const { auth } = useHook10();
2855
2870
  const [name, setName] = useState9("");
2856
2871
  const [email, setEmail] = useState9("");
2857
2872
  const [emailConfirm, setEmailConfirm] = useState9("");
2858
2873
  const [phone, setPhone] = useState9("");
2859
2874
  const [cpf, setCpf] = useState9("");
2860
- const [method, setMethod] = useState9(args.defaultMethod);
2861
- const [cycle, setCycle] = useState9(args.defaultCycle);
2875
+ const [postalCode, setPostalCode] = useState9("");
2876
+ const [addressNumber, setAddressNumber] = useState9("");
2877
+ const [method, setMethod] = useState9(hookArgs.defaultMethod);
2878
+ const [cycle, setCycle] = useState9(hookArgs.defaultCycle);
2862
2879
  const [card, setCardState] = useState9({
2863
2880
  number: "",
2864
2881
  expiryMonth: "",
@@ -2874,6 +2891,8 @@ function useCheckoutForm(args) {
2874
2891
  const [touchedEmailConfirm, setTouchedEmailConfirm] = useState9(false);
2875
2892
  const [touchedPhone, setTouchedPhone] = useState9(false);
2876
2893
  const [touchedCpf, setTouchedCpf] = useState9(false);
2894
+ const [touchedPostalCode, setTouchedPostalCode] = useState9(false);
2895
+ const [touchedAddressNumber, setTouchedAddressNumber] = useState9(false);
2877
2896
  const [formSubmitAttempted, setFormSubmitAttempted] = useState9(false);
2878
2897
  const [submitting, setSubmitting] = useState9(false);
2879
2898
  const [error, setError] = useState9(null);
@@ -2927,13 +2946,29 @@ function useCheckoutForm(args) {
2927
2946
  const ok = mod11(digits, 9) === digits[9] && mod11(digits, 10) === digits[10];
2928
2947
  return ok ? null : "CPF inv\xE1lido.";
2929
2948
  }, [cpf]);
2949
+ const validatePostalCode = useMemo4(() => {
2950
+ if (!hookArgs.collectCep) return null;
2951
+ if (postalCode.length === 0) return null;
2952
+ const digits = postalCode.replace(/\D/g, "");
2953
+ if (digits.length !== 8) return "CEP deve ter 8 d\xEDgitos.";
2954
+ return null;
2955
+ }, [hookArgs.collectCep, postalCode]);
2956
+ const validateAddressNumber = useMemo4(() => {
2957
+ if (!hookArgs.collectCep) return null;
2958
+ if (addressNumber.length === 0) return null;
2959
+ if (addressNumber.trim().length === 0) return "N\xFAmero obrigat\xF3rio.";
2960
+ return null;
2961
+ }, [hookArgs.collectCep, addressNumber]);
2930
2962
  const nameError = touchedName || formSubmitAttempted ? validateName : null;
2931
2963
  const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2932
2964
  const emailConfirmError = touchedEmailConfirm || formSubmitAttempted ? validateEmailConfirm : null;
2933
2965
  const phoneError = touchedPhone || formSubmitAttempted ? validatePhone : null;
2934
2966
  const cpfError = touchedCpf || formSubmitAttempted ? validateCpf : null;
2967
+ const postalCodeError = touchedPostalCode || formSubmitAttempted ? validatePostalCode : null;
2968
+ const addressNumberError = touchedAddressNumber || formSubmitAttempted ? validateAddressNumber : null;
2935
2969
  const phoneOk = method === "pix-auto" ? phone === "" || PHONE_RE.test(phone) : PHONE_RE.test(phone);
2936
- const canSubmit = name.trim().length >= 2 && EMAIL_RE.test(email) && (emailConfirm === "" || emailConfirm === email) && phoneOk && validateCpf === null && cpf.replace(/\D/g, "").length === 11 && emailStatus !== "exists" && !submitting && (method !== "card" || card.number.length >= 12 && card.ccv.length >= 3 && card.expiryMonth.length >= 1 && card.expiryYear.length >= 2 && card.holderName.length >= 1);
2970
+ const cepGateOk = !hookArgs.collectCep || method !== "card" || postalCode.replace(/\D/g, "").length === 8 && addressNumber.trim().length > 0;
2971
+ const canSubmit = name.trim().length >= 2 && EMAIL_RE.test(email) && (emailConfirm === "" || emailConfirm === email) && phoneOk && validateCpf === null && cpf.replace(/\D/g, "").length === 11 && emailStatus !== "exists" && !submitting && cepGateOk && (method !== "card" || card.number.length >= 12 && card.ccv.length >= 3 && card.expiryMonth.length >= 1 && card.expiryYear.length >= 2 && card.holderName.length >= 1);
2937
2972
  const submit = useCallback6(async () => {
2938
2973
  setFormSubmitAttempted(true);
2939
2974
  setError(null);
@@ -2942,9 +2977,9 @@ function useCheckoutForm(args) {
2942
2977
  if (!canSubmit) return null;
2943
2978
  setSubmitting(true);
2944
2979
  try {
2945
- let args2;
2980
+ let args;
2946
2981
  if (method === "card") {
2947
- args2 = {
2982
+ args = {
2948
2983
  method: "card",
2949
2984
  name: name.trim(),
2950
2985
  email,
@@ -2963,21 +2998,17 @@ function useCheckoutForm(args) {
2963
2998
  name: card.holderName || name.trim(),
2964
2999
  email,
2965
3000
  cpfCnpj: cpf.replace(/\D/g, ""),
2966
- // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
2967
- // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
2968
- // ("O CEP informado é inválido."). The default CheckoutPage
2969
- // doesn't collect CEP from the user (reference design skipped
2970
- // it), so ship a known-valid placeholder. Apps that need real
2971
- // customer addresses must override CheckoutPageDefault and
2972
- // collect CEP — see personalburn's PaywallStepPagamento for
2973
- // the pattern.
2974
- postalCode: "01001000",
2975
- addressNumber: "100",
3001
+ // collectCep=true CEP/número reais do form. Senão placeholder
3002
+ // válido (Asaas rejeita all-zeros, então shipping 01001000/100
3003
+ // que são válidos mas anonimizam). Apps com NF-e ou antifraude
3004
+ // refinada devem setar paywall.collectCep no app.config.json.
3005
+ postalCode: hookArgs.collectCep ? postalCode.replace(/\D/g, "") : "01001000",
3006
+ addressNumber: hookArgs.collectCep ? addressNumber.trim() : "100",
2976
3007
  phone
2977
3008
  }
2978
3009
  };
2979
3010
  } else {
2980
- args2 = {
3011
+ args = {
2981
3012
  method: "pix-auto",
2982
3013
  name: name.trim(),
2983
3014
  email,
@@ -2987,7 +3018,7 @@ function useCheckoutForm(args) {
2987
3018
  cycle
2988
3019
  };
2989
3020
  }
2990
- const result = await auth.subscribeAnonymous(args2);
3021
+ const result = await auth.subscribeAnonymous(args);
2991
3022
  return result;
2992
3023
  } catch (err) {
2993
3024
  if (err instanceof EmailTakenError) {
@@ -3013,6 +3044,10 @@ function useCheckoutForm(args) {
3013
3044
  setPhone,
3014
3045
  cpf,
3015
3046
  setCpf,
3047
+ postalCode,
3048
+ setPostalCode,
3049
+ addressNumber,
3050
+ setAddressNumber,
3016
3051
  method,
3017
3052
  setMethod,
3018
3053
  cycle,
@@ -3024,11 +3059,15 @@ function useCheckoutForm(args) {
3024
3059
  emailConfirmError,
3025
3060
  phoneError,
3026
3061
  cpfError,
3062
+ postalCodeError,
3063
+ addressNumberError,
3027
3064
  markNameTouched: () => setTouchedName(true),
3028
3065
  markEmailTouched: () => setTouchedEmail(true),
3029
3066
  markEmailConfirmTouched: () => setTouchedEmailConfirm(true),
3030
3067
  markPhoneTouched: () => setTouchedPhone(true),
3031
3068
  markCpfTouched: () => setTouchedCpf(true),
3069
+ markPostalCodeTouched: () => setTouchedPostalCode(true),
3070
+ markAddressNumberTouched: () => setTouchedAddressNumber(true),
3032
3071
  emailStatus,
3033
3072
  submit,
3034
3073
  submitting,
@@ -3855,10 +3894,12 @@ function detectCardBrand(num) {
3855
3894
  function CheckoutPageDefault() {
3856
3895
  const navigate = useNavigate2();
3857
3896
  const plan = usePlan();
3897
+ const config = useAppConfig();
3858
3898
  const intent = useMemo6(readIntent, []);
3859
3899
  const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3860
3900
  const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3861
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
3901
+ const collectCep = config.paywall.mode !== "free" && config.paywall.collectCep === true;
3902
+ const form = useCheckoutForm({ defaultMethod, defaultCycle, collectCep });
3862
3903
  const [expiryMmAa, setExpiryMmAa] = useState11("");
3863
3904
  useEffect13(() => {
3864
3905
  const { month, year } = parseExpiryMmAa(expiryMmAa);
@@ -4042,6 +4083,45 @@ function CheckoutPageDefault() {
4042
4083
  }
4043
4084
  ),
4044
4085
  !form.phoneError && /* @__PURE__ */ jsx48(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4086
+ ] }) : null,
4087
+ collectCep && form.method === "card" ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
4088
+ /* @__PURE__ */ jsxs29("div", { children: [
4089
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "CEP" }),
4090
+ /* @__PURE__ */ jsx48(
4091
+ FieldInput,
4092
+ {
4093
+ type: "text",
4094
+ inputMode: "numeric",
4095
+ autoComplete: "postal-code",
4096
+ placeholder: "00000-000",
4097
+ value: form.postalCode,
4098
+ onChange: (v) => {
4099
+ const digits = v.replace(/\D/g, "").slice(0, 8);
4100
+ const formatted = digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
4101
+ form.setPostalCode(formatted);
4102
+ },
4103
+ onBlur: form.markPostalCodeTouched,
4104
+ error: form.postalCodeError,
4105
+ valid: !!form.postalCode && !form.postalCodeError
4106
+ }
4107
+ )
4108
+ ] }),
4109
+ /* @__PURE__ */ jsxs29("div", { children: [
4110
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "N\xFAmero" }),
4111
+ /* @__PURE__ */ jsx48(
4112
+ FieldInput,
4113
+ {
4114
+ type: "text",
4115
+ inputMode: "numeric",
4116
+ placeholder: "100",
4117
+ value: form.addressNumber,
4118
+ onChange: form.setAddressNumber,
4119
+ onBlur: form.markAddressNumberTouched,
4120
+ error: form.addressNumberError,
4121
+ valid: !!form.addressNumber && !form.addressNumberError
4122
+ }
4123
+ )
4124
+ ] })
4045
4125
  ] }) : null
4046
4126
  ] })
4047
4127
  ] }),