@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.cjs CHANGED
@@ -169,6 +169,7 @@ var PaywallNonFreeSchema = import_zod.z.object({
169
169
  }).optional(),
170
170
  checkoutMethods: import_zod.z.array(import_zod.z.enum(["card", "pix-auto", "pix-once"])).min(1),
171
171
  requiresCpf: import_zod.z.boolean(),
172
+ collectCep: import_zod.z.boolean().optional(),
172
173
  cancelWindowDays: import_zod.z.number().int().nonnegative().optional(),
173
174
  errorMessages: import_zod.z.enum(["default", "custom"])
174
175
  });
@@ -2972,15 +2973,17 @@ function mapSdkError(err) {
2972
2973
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2973
2974
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2974
2975
  var CHECK_DEBOUNCE_MS = 400;
2975
- function useCheckoutForm(args) {
2976
+ function useCheckoutForm(hookArgs) {
2976
2977
  const { auth } = (0, import_sdk13.useHook)();
2977
2978
  const [name, setName] = (0, import_react18.useState)("");
2978
2979
  const [email, setEmail] = (0, import_react18.useState)("");
2979
2980
  const [emailConfirm, setEmailConfirm] = (0, import_react18.useState)("");
2980
2981
  const [phone, setPhone] = (0, import_react18.useState)("");
2981
2982
  const [cpf, setCpf] = (0, import_react18.useState)("");
2982
- const [method, setMethod] = (0, import_react18.useState)(args.defaultMethod);
2983
- const [cycle, setCycle] = (0, import_react18.useState)(args.defaultCycle);
2983
+ const [postalCode, setPostalCode] = (0, import_react18.useState)("");
2984
+ const [addressNumber, setAddressNumber] = (0, import_react18.useState)("");
2985
+ const [method, setMethod] = (0, import_react18.useState)(hookArgs.defaultMethod);
2986
+ const [cycle, setCycle] = (0, import_react18.useState)(hookArgs.defaultCycle);
2984
2987
  const [card, setCardState] = (0, import_react18.useState)({
2985
2988
  number: "",
2986
2989
  expiryMonth: "",
@@ -2996,6 +2999,8 @@ function useCheckoutForm(args) {
2996
2999
  const [touchedEmailConfirm, setTouchedEmailConfirm] = (0, import_react18.useState)(false);
2997
3000
  const [touchedPhone, setTouchedPhone] = (0, import_react18.useState)(false);
2998
3001
  const [touchedCpf, setTouchedCpf] = (0, import_react18.useState)(false);
3002
+ const [touchedPostalCode, setTouchedPostalCode] = (0, import_react18.useState)(false);
3003
+ const [touchedAddressNumber, setTouchedAddressNumber] = (0, import_react18.useState)(false);
2999
3004
  const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react18.useState)(false);
3000
3005
  const [submitting, setSubmitting] = (0, import_react18.useState)(false);
3001
3006
  const [error, setError] = (0, import_react18.useState)(null);
@@ -3049,13 +3054,46 @@ function useCheckoutForm(args) {
3049
3054
  const ok = mod11(digits, 9) === digits[9] && mod11(digits, 10) === digits[10];
3050
3055
  return ok ? null : "CPF inv\xE1lido.";
3051
3056
  }, [cpf]);
3057
+ const validatePostalCode = (0, import_react18.useMemo)(() => {
3058
+ if (!hookArgs.collectCep) return null;
3059
+ if (postalCode.length === 0) return null;
3060
+ const digits = postalCode.replace(/\D/g, "");
3061
+ if (digits.length !== 8) return "CEP deve ter 8 d\xEDgitos.";
3062
+ return null;
3063
+ }, [hookArgs.collectCep, postalCode]);
3064
+ const validateAddressNumber = (0, import_react18.useMemo)(() => {
3065
+ if (!hookArgs.collectCep) return null;
3066
+ if (addressNumber.length === 0) return null;
3067
+ if (addressNumber.trim().length === 0) return "N\xFAmero obrigat\xF3rio.";
3068
+ return null;
3069
+ }, [hookArgs.collectCep, addressNumber]);
3052
3070
  const nameError = touchedName || formSubmitAttempted ? validateName : null;
3053
3071
  const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3054
3072
  const emailConfirmError = touchedEmailConfirm || formSubmitAttempted ? validateEmailConfirm : null;
3055
3073
  const phoneError = touchedPhone || formSubmitAttempted ? validatePhone : null;
3056
3074
  const cpfError = touchedCpf || formSubmitAttempted ? validateCpf : null;
3075
+ const postalCodeError = touchedPostalCode || formSubmitAttempted ? validatePostalCode : null;
3076
+ const addressNumberError = touchedAddressNumber || formSubmitAttempted ? validateAddressNumber : null;
3077
+ const cardExpiryError = (0, import_react18.useMemo)(() => {
3078
+ if (method !== "card") return null;
3079
+ const mm = card.expiryMonth.replace(/\D/g, "");
3080
+ const yy = card.expiryYear.replace(/\D/g, "");
3081
+ if (mm.length < 2 || yy.length < 2) return null;
3082
+ const monthNum = parseInt(mm, 10);
3083
+ if (monthNum < 1 || monthNum > 12) return "M\xEAs inv\xE1lido (use 01-12).";
3084
+ const yearNum = 2e3 + parseInt(yy, 10);
3085
+ const now = /* @__PURE__ */ new Date();
3086
+ const currentMonth = now.getMonth() + 1;
3087
+ const currentYear = now.getFullYear();
3088
+ if (yearNum < currentYear || yearNum === currentYear && monthNum < currentMonth) {
3089
+ return "Cart\xE3o vencido.";
3090
+ }
3091
+ if (yearNum > currentYear + 20) return "Ano muito distante.";
3092
+ return null;
3093
+ }, [method, card.expiryMonth, card.expiryYear]);
3057
3094
  const phoneOk = method === "pix-auto" ? phone === "" || PHONE_RE.test(phone) : PHONE_RE.test(phone);
3058
- 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);
3095
+ const cepGateOk = !hookArgs.collectCep || method !== "card" || postalCode.replace(/\D/g, "").length === 8 && addressNumber.trim().length > 0;
3096
+ 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);
3059
3097
  const submit = (0, import_react18.useCallback)(async () => {
3060
3098
  setFormSubmitAttempted(true);
3061
3099
  setError(null);
@@ -3064,9 +3102,9 @@ function useCheckoutForm(args) {
3064
3102
  if (!canSubmit) return null;
3065
3103
  setSubmitting(true);
3066
3104
  try {
3067
- let args2;
3105
+ let args;
3068
3106
  if (method === "card") {
3069
- args2 = {
3107
+ args = {
3070
3108
  method: "card",
3071
3109
  name: name.trim(),
3072
3110
  email,
@@ -3085,21 +3123,17 @@ function useCheckoutForm(args) {
3085
3123
  name: card.holderName || name.trim(),
3086
3124
  email,
3087
3125
  cpfCnpj: cpf.replace(/\D/g, ""),
3088
- // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
3089
- // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
3090
- // ("O CEP informado é inválido."). The default CheckoutPage
3091
- // doesn't collect CEP from the user (reference design skipped
3092
- // it), so ship a known-valid placeholder. Apps that need real
3093
- // customer addresses must override CheckoutPageDefault and
3094
- // collect CEP — see personalburn's PaywallStepPagamento for
3095
- // the pattern.
3096
- postalCode: "01001000",
3097
- addressNumber: "100",
3126
+ // collectCep=true CEP/número reais do form. Senão placeholder
3127
+ // válido (Asaas rejeita all-zeros, então shipping 01001000/100
3128
+ // que são válidos mas anonimizam). Apps com NF-e ou antifraude
3129
+ // refinada devem setar paywall.collectCep no app.config.json.
3130
+ postalCode: hookArgs.collectCep ? postalCode.replace(/\D/g, "") : "01001000",
3131
+ addressNumber: hookArgs.collectCep ? addressNumber.trim() : "100",
3098
3132
  phone
3099
3133
  }
3100
3134
  };
3101
3135
  } else {
3102
- args2 = {
3136
+ args = {
3103
3137
  method: "pix-auto",
3104
3138
  name: name.trim(),
3105
3139
  email,
@@ -3109,7 +3143,7 @@ function useCheckoutForm(args) {
3109
3143
  cycle
3110
3144
  };
3111
3145
  }
3112
- const result = await auth.subscribeAnonymous(args2);
3146
+ const result = await auth.subscribeAnonymous(args);
3113
3147
  return result;
3114
3148
  } catch (err) {
3115
3149
  if (err instanceof import_sdk13.EmailTakenError) {
@@ -3135,6 +3169,10 @@ function useCheckoutForm(args) {
3135
3169
  setPhone,
3136
3170
  cpf,
3137
3171
  setCpf,
3172
+ postalCode,
3173
+ setPostalCode,
3174
+ addressNumber,
3175
+ setAddressNumber,
3138
3176
  method,
3139
3177
  setMethod,
3140
3178
  cycle,
@@ -3146,11 +3184,16 @@ function useCheckoutForm(args) {
3146
3184
  emailConfirmError,
3147
3185
  phoneError,
3148
3186
  cpfError,
3187
+ postalCodeError,
3188
+ addressNumberError,
3189
+ cardExpiryError,
3149
3190
  markNameTouched: () => setTouchedName(true),
3150
3191
  markEmailTouched: () => setTouchedEmail(true),
3151
3192
  markEmailConfirmTouched: () => setTouchedEmailConfirm(true),
3152
3193
  markPhoneTouched: () => setTouchedPhone(true),
3153
3194
  markCpfTouched: () => setTouchedCpf(true),
3195
+ markPostalCodeTouched: () => setTouchedPostalCode(true),
3196
+ markAddressNumberTouched: () => setTouchedAddressNumber(true),
3154
3197
  emailStatus,
3155
3198
  submit,
3156
3199
  submitting,
@@ -3977,10 +4020,12 @@ function detectCardBrand(num) {
3977
4020
  function CheckoutPageDefault() {
3978
4021
  const navigate = (0, import_react_router_dom3.useNavigate)();
3979
4022
  const plan = usePlan();
4023
+ const config = useAppConfig();
3980
4024
  const intent = (0, import_react23.useMemo)(readIntent, []);
3981
4025
  const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3982
4026
  const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3983
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
4027
+ const collectCep = config.paywall.mode !== "free" && config.paywall.collectCep === true;
4028
+ const form = useCheckoutForm({ defaultMethod, defaultCycle, collectCep });
3984
4029
  const [expiryMmAa, setExpiryMmAa] = (0, import_react23.useState)("");
3985
4030
  (0, import_react23.useEffect)(() => {
3986
4031
  const { month, year } = parseExpiryMmAa(expiryMmAa);
@@ -4109,11 +4154,11 @@ function CheckoutPageDefault() {
4109
4154
  value: form.email,
4110
4155
  onChange: form.setEmail,
4111
4156
  onBlur: form.markEmailTouched,
4112
- error: form.emailError,
4157
+ error: form.emailError ?? (form.emailStatus === "exists" ? "Este email j\xE1 tem conta. Faz login pra continuar." : null),
4113
4158
  valid: form.emailStatus === "available"
4114
4159
  }
4115
4160
  ),
4116
- !form.emailError && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" })
4161
+ !form.emailError && form.emailStatus !== "exists" && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: form.emailStatus === "checking" ? "Verificando\u2026" : form.emailStatus === "available" ? "\u2713 Dispon\xEDvel" : "Voc\xEA vai usar este email para entrar no app" })
4117
4162
  ] }),
4118
4163
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4119
4164
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "Nome completo" }),
@@ -4164,6 +4209,45 @@ function CheckoutPageDefault() {
4164
4209
  }
4165
4210
  ),
4166
4211
  !form.phoneError && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4212
+ ] }) : null,
4213
+ collectCep && form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4214
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4215
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "CEP" }),
4216
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4217
+ FieldInput,
4218
+ {
4219
+ type: "text",
4220
+ inputMode: "numeric",
4221
+ autoComplete: "postal-code",
4222
+ placeholder: "00000-000",
4223
+ value: form.postalCode,
4224
+ onChange: (v) => {
4225
+ const digits = v.replace(/\D/g, "").slice(0, 8);
4226
+ const formatted = digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
4227
+ form.setPostalCode(formatted);
4228
+ },
4229
+ onBlur: form.markPostalCodeTouched,
4230
+ error: form.postalCodeError,
4231
+ valid: !!form.postalCode && !form.postalCodeError
4232
+ }
4233
+ )
4234
+ ] }),
4235
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4236
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "N\xFAmero" }),
4237
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4238
+ FieldInput,
4239
+ {
4240
+ type: "text",
4241
+ inputMode: "numeric",
4242
+ placeholder: "100",
4243
+ value: form.addressNumber,
4244
+ onChange: form.setAddressNumber,
4245
+ onBlur: form.markAddressNumberTouched,
4246
+ error: form.addressNumberError,
4247
+ valid: !!form.addressNumber && !form.addressNumberError
4248
+ }
4249
+ )
4250
+ ] })
4167
4251
  ] }) : null
4168
4252
  ] })
4169
4253
  ] }),
@@ -4225,7 +4309,8 @@ function CheckoutPageDefault() {
4225
4309
  autoComplete: "cc-exp",
4226
4310
  placeholder: "MM/AA",
4227
4311
  value: expiryMmAa,
4228
- onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v))
4312
+ onChange: (v) => setExpiryMmAa(formatExpiryMmAa(v)),
4313
+ error: form.cardExpiryError
4229
4314
  }
4230
4315
  )
4231
4316
  ] }),