@hook-sdk/template 0.28.9 → 0.28.11

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,16 @@ 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;
527
+ cardExpiryError: string | null;
514
528
  markNameTouched: () => void;
515
529
  markEmailTouched: () => void;
516
530
  markEmailConfirmTouched: () => void;
517
531
  markPhoneTouched: () => void;
518
532
  markCpfTouched: () => void;
533
+ markPostalCodeTouched: () => void;
534
+ markAddressNumberTouched: () => void;
519
535
  /**
520
536
  * Result of the debounced server-side email-exists check, used to swap
521
537
  * the CTA into "Email já cadastrado · Entrar" on the checkout form.
@@ -539,8 +555,15 @@ interface UseCheckoutFormResult {
539
555
  interface UseCheckoutFormArgs {
540
556
  defaultMethod: CheckoutFormMethod;
541
557
  defaultCycle: CheckoutFormCycle;
558
+ /**
559
+ * Quando true, o submit envia postalCode + addressNumber reais coletados
560
+ * via setPostalCode/setAddressNumber. Quando false (default), envia
561
+ * placeholder `01001000`/`100`. CheckoutPageDefault liga via config
562
+ * `paywall.collectCep`.
563
+ */
564
+ collectCep?: boolean;
542
565
  }
543
- declare function useCheckoutForm(args: UseCheckoutFormArgs): UseCheckoutFormResult;
566
+ declare function useCheckoutForm(hookArgs: UseCheckoutFormArgs): UseCheckoutFormResult;
544
567
 
545
568
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
546
569
  type PaymentMethod = CheckoutMethod$1;
@@ -1082,6 +1105,7 @@ declare const AppConfigSchema: z.ZodObject<{
1082
1105
  "pix-once": "pix-once";
1083
1106
  }>>;
1084
1107
  requiresCpf: z.ZodBoolean;
1108
+ collectCep: z.ZodOptional<z.ZodBoolean>;
1085
1109
  cancelWindowDays: z.ZodOptional<z.ZodNumber>;
1086
1110
  errorMessages: z.ZodEnum<{
1087
1111
  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,16 @@ 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;
527
+ cardExpiryError: string | null;
514
528
  markNameTouched: () => void;
515
529
  markEmailTouched: () => void;
516
530
  markEmailConfirmTouched: () => void;
517
531
  markPhoneTouched: () => void;
518
532
  markCpfTouched: () => void;
533
+ markPostalCodeTouched: () => void;
534
+ markAddressNumberTouched: () => void;
519
535
  /**
520
536
  * Result of the debounced server-side email-exists check, used to swap
521
537
  * the CTA into "Email já cadastrado · Entrar" on the checkout form.
@@ -539,8 +555,15 @@ interface UseCheckoutFormResult {
539
555
  interface UseCheckoutFormArgs {
540
556
  defaultMethod: CheckoutFormMethod;
541
557
  defaultCycle: CheckoutFormCycle;
558
+ /**
559
+ * Quando true, o submit envia postalCode + addressNumber reais coletados
560
+ * via setPostalCode/setAddressNumber. Quando false (default), envia
561
+ * placeholder `01001000`/`100`. CheckoutPageDefault liga via config
562
+ * `paywall.collectCep`.
563
+ */
564
+ collectCep?: boolean;
542
565
  }
543
- declare function useCheckoutForm(args: UseCheckoutFormArgs): UseCheckoutFormResult;
566
+ declare function useCheckoutForm(hookArgs: UseCheckoutFormArgs): UseCheckoutFormResult;
544
567
 
545
568
  type SubscriptionStatus = 'active' | 'trialing' | 'expired' | 'canceled' | 'past_due' | 'pending' | 'none';
546
569
  type PaymentMethod = CheckoutMethod$1;
@@ -1082,6 +1105,7 @@ declare const AppConfigSchema: z.ZodObject<{
1082
1105
  "pix-once": "pix-once";
1083
1106
  }>>;
1084
1107
  requiresCpf: z.ZodBoolean;
1108
+ collectCep: z.ZodOptional<z.ZodBoolean>;
1085
1109
  cancelWindowDays: z.ZodOptional<z.ZodNumber>;
1086
1110
  errorMessages: z.ZodEnum<{
1087
1111
  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
  });
@@ -2864,15 +2865,17 @@ function mapSdkError(err) {
2864
2865
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2865
2866
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2866
2867
  var CHECK_DEBOUNCE_MS = 400;
2867
- function useCheckoutForm(args) {
2868
+ function useCheckoutForm(hookArgs) {
2868
2869
  const { auth } = useHook10();
2869
2870
  const [name, setName] = useState9("");
2870
2871
  const [email, setEmail] = useState9("");
2871
2872
  const [emailConfirm, setEmailConfirm] = useState9("");
2872
2873
  const [phone, setPhone] = useState9("");
2873
2874
  const [cpf, setCpf] = useState9("");
2874
- const [method, setMethod] = useState9(args.defaultMethod);
2875
- 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);
2876
2879
  const [card, setCardState] = useState9({
2877
2880
  number: "",
2878
2881
  expiryMonth: "",
@@ -2888,6 +2891,8 @@ function useCheckoutForm(args) {
2888
2891
  const [touchedEmailConfirm, setTouchedEmailConfirm] = useState9(false);
2889
2892
  const [touchedPhone, setTouchedPhone] = useState9(false);
2890
2893
  const [touchedCpf, setTouchedCpf] = useState9(false);
2894
+ const [touchedPostalCode, setTouchedPostalCode] = useState9(false);
2895
+ const [touchedAddressNumber, setTouchedAddressNumber] = useState9(false);
2891
2896
  const [formSubmitAttempted, setFormSubmitAttempted] = useState9(false);
2892
2897
  const [submitting, setSubmitting] = useState9(false);
2893
2898
  const [error, setError] = useState9(null);
@@ -2941,13 +2946,46 @@ function useCheckoutForm(args) {
2941
2946
  const ok = mod11(digits, 9) === digits[9] && mod11(digits, 10) === digits[10];
2942
2947
  return ok ? null : "CPF inv\xE1lido.";
2943
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]);
2944
2962
  const nameError = touchedName || formSubmitAttempted ? validateName : null;
2945
2963
  const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
2946
2964
  const emailConfirmError = touchedEmailConfirm || formSubmitAttempted ? validateEmailConfirm : null;
2947
2965
  const phoneError = touchedPhone || formSubmitAttempted ? validatePhone : null;
2948
2966
  const cpfError = touchedCpf || formSubmitAttempted ? validateCpf : null;
2967
+ const postalCodeError = touchedPostalCode || formSubmitAttempted ? validatePostalCode : null;
2968
+ const addressNumberError = touchedAddressNumber || formSubmitAttempted ? validateAddressNumber : null;
2969
+ const cardExpiryError = useMemo4(() => {
2970
+ if (method !== "card") return null;
2971
+ const mm = card.expiryMonth.replace(/\D/g, "");
2972
+ const yy = card.expiryYear.replace(/\D/g, "");
2973
+ if (mm.length < 2 || yy.length < 2) return null;
2974
+ const monthNum = parseInt(mm, 10);
2975
+ if (monthNum < 1 || monthNum > 12) return "M\xEAs inv\xE1lido (use 01-12).";
2976
+ const yearNum = 2e3 + parseInt(yy, 10);
2977
+ const now = /* @__PURE__ */ new Date();
2978
+ const currentMonth = now.getMonth() + 1;
2979
+ const currentYear = now.getFullYear();
2980
+ if (yearNum < currentYear || yearNum === currentYear && monthNum < currentMonth) {
2981
+ return "Cart\xE3o vencido.";
2982
+ }
2983
+ if (yearNum > currentYear + 20) return "Ano muito distante.";
2984
+ return null;
2985
+ }, [method, card.expiryMonth, card.expiryYear]);
2949
2986
  const phoneOk = method === "pix-auto" ? phone === "" || PHONE_RE.test(phone) : PHONE_RE.test(phone);
2950
- 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);
2987
+ const cepGateOk = !hookArgs.collectCep || method !== "card" || postalCode.replace(/\D/g, "").length === 8 && addressNumber.trim().length > 0;
2988
+ 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 && cardExpiryError === null);
2951
2989
  const submit = useCallback6(async () => {
2952
2990
  setFormSubmitAttempted(true);
2953
2991
  setError(null);
@@ -2956,9 +2994,9 @@ function useCheckoutForm(args) {
2956
2994
  if (!canSubmit) return null;
2957
2995
  setSubmitting(true);
2958
2996
  try {
2959
- let args2;
2997
+ let args;
2960
2998
  if (method === "card") {
2961
- args2 = {
2999
+ args = {
2962
3000
  method: "card",
2963
3001
  name: name.trim(),
2964
3002
  email,
@@ -2977,21 +3015,17 @@ function useCheckoutForm(args) {
2977
3015
  name: card.holderName || name.trim(),
2978
3016
  email,
2979
3017
  cpfCnpj: cpf.replace(/\D/g, ""),
2980
- // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
2981
- // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
2982
- // ("O CEP informado é inválido."). The default CheckoutPage
2983
- // doesn't collect CEP from the user (reference design skipped
2984
- // it), so ship a known-valid placeholder. Apps that need real
2985
- // customer addresses must override CheckoutPageDefault and
2986
- // collect CEP — see personalburn's PaywallStepPagamento for
2987
- // the pattern.
2988
- postalCode: "01001000",
2989
- addressNumber: "100",
3018
+ // collectCep=true CEP/número reais do form. Senão placeholder
3019
+ // válido (Asaas rejeita all-zeros, então shipping 01001000/100
3020
+ // que são válidos mas anonimizam). Apps com NF-e ou antifraude
3021
+ // refinada devem setar paywall.collectCep no app.config.json.
3022
+ postalCode: hookArgs.collectCep ? postalCode.replace(/\D/g, "") : "01001000",
3023
+ addressNumber: hookArgs.collectCep ? addressNumber.trim() : "100",
2990
3024
  phone
2991
3025
  }
2992
3026
  };
2993
3027
  } else {
2994
- args2 = {
3028
+ args = {
2995
3029
  method: "pix-auto",
2996
3030
  name: name.trim(),
2997
3031
  email,
@@ -3001,7 +3035,7 @@ function useCheckoutForm(args) {
3001
3035
  cycle
3002
3036
  };
3003
3037
  }
3004
- const result = await auth.subscribeAnonymous(args2);
3038
+ const result = await auth.subscribeAnonymous(args);
3005
3039
  return result;
3006
3040
  } catch (err) {
3007
3041
  if (err instanceof EmailTakenError) {
@@ -3027,6 +3061,10 @@ function useCheckoutForm(args) {
3027
3061
  setPhone,
3028
3062
  cpf,
3029
3063
  setCpf,
3064
+ postalCode,
3065
+ setPostalCode,
3066
+ addressNumber,
3067
+ setAddressNumber,
3030
3068
  method,
3031
3069
  setMethod,
3032
3070
  cycle,
@@ -3038,11 +3076,16 @@ function useCheckoutForm(args) {
3038
3076
  emailConfirmError,
3039
3077
  phoneError,
3040
3078
  cpfError,
3079
+ postalCodeError,
3080
+ addressNumberError,
3081
+ cardExpiryError,
3041
3082
  markNameTouched: () => setTouchedName(true),
3042
3083
  markEmailTouched: () => setTouchedEmail(true),
3043
3084
  markEmailConfirmTouched: () => setTouchedEmailConfirm(true),
3044
3085
  markPhoneTouched: () => setTouchedPhone(true),
3045
3086
  markCpfTouched: () => setTouchedCpf(true),
3087
+ markPostalCodeTouched: () => setTouchedPostalCode(true),
3088
+ markAddressNumberTouched: () => setTouchedAddressNumber(true),
3046
3089
  emailStatus,
3047
3090
  submit,
3048
3091
  submitting,
@@ -3869,10 +3912,12 @@ function detectCardBrand(num) {
3869
3912
  function CheckoutPageDefault() {
3870
3913
  const navigate = useNavigate2();
3871
3914
  const plan = usePlan();
3915
+ const config = useAppConfig();
3872
3916
  const intent = useMemo6(readIntent, []);
3873
3917
  const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3874
3918
  const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3875
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
3919
+ const collectCep = config.paywall.mode !== "free" && config.paywall.collectCep === true;
3920
+ const form = useCheckoutForm({ defaultMethod, defaultCycle, collectCep });
3876
3921
  const [expiryMmAa, setExpiryMmAa] = useState11("");
3877
3922
  useEffect13(() => {
3878
3923
  const { month, year } = parseExpiryMmAa(expiryMmAa);
@@ -4001,11 +4046,11 @@ function CheckoutPageDefault() {
4001
4046
  value: form.email,
4002
4047
  onChange: form.setEmail,
4003
4048
  onBlur: form.markEmailTouched,
4004
- error: form.emailError,
4049
+ error: form.emailError ?? (form.emailStatus === "exists" ? "Este email j\xE1 tem conta. Faz login pra continuar." : null),
4005
4050
  valid: form.emailStatus === "available"
4006
4051
  }
4007
4052
  ),
4008
- !form.emailError && /* @__PURE__ */ jsx48(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" })
4053
+ !form.emailError && form.emailStatus !== "exists" && /* @__PURE__ */ jsx48(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" })
4009
4054
  ] }),
4010
4055
  /* @__PURE__ */ jsxs29("div", { children: [
4011
4056
  /* @__PURE__ */ jsx48(FieldLabel, { children: "Nome completo" }),
@@ -4056,6 +4101,45 @@ function CheckoutPageDefault() {
4056
4101
  }
4057
4102
  ),
4058
4103
  !form.phoneError && /* @__PURE__ */ jsx48(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4104
+ ] }) : null,
4105
+ collectCep && form.method === "card" ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
4106
+ /* @__PURE__ */ jsxs29("div", { children: [
4107
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "CEP" }),
4108
+ /* @__PURE__ */ jsx48(
4109
+ FieldInput,
4110
+ {
4111
+ type: "text",
4112
+ inputMode: "numeric",
4113
+ autoComplete: "postal-code",
4114
+ placeholder: "00000-000",
4115
+ value: form.postalCode,
4116
+ onChange: (v) => {
4117
+ const digits = v.replace(/\D/g, "").slice(0, 8);
4118
+ const formatted = digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
4119
+ form.setPostalCode(formatted);
4120
+ },
4121
+ onBlur: form.markPostalCodeTouched,
4122
+ error: form.postalCodeError,
4123
+ valid: !!form.postalCode && !form.postalCodeError
4124
+ }
4125
+ )
4126
+ ] }),
4127
+ /* @__PURE__ */ jsxs29("div", { children: [
4128
+ /* @__PURE__ */ jsx48(FieldLabel, { children: "N\xFAmero" }),
4129
+ /* @__PURE__ */ jsx48(
4130
+ FieldInput,
4131
+ {
4132
+ type: "text",
4133
+ inputMode: "numeric",
4134
+ placeholder: "100",
4135
+ value: form.addressNumber,
4136
+ onChange: form.setAddressNumber,
4137
+ onBlur: form.markAddressNumberTouched,
4138
+ error: form.addressNumberError,
4139
+ valid: !!form.addressNumber && !form.addressNumberError
4140
+ }
4141
+ )
4142
+ ] })
4059
4143
  ] }) : null
4060
4144
  ] })
4061
4145
  ] }),
@@ -4117,7 +4201,8 @@ function CheckoutPageDefault() {
4117
4201
  autoComplete: "cc-exp",
4118
4202
  placeholder: "MM/AA",
4119
4203
  value: expiryMmAa,
4120
- onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
4204
+ onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v)),
4205
+ error: form.cardExpiryError
4121
4206
  }
4122
4207
  )
4123
4208
  ] }),