@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.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
  });
@@ -2115,33 +2116,47 @@ function SessionExpiredBanner() {
2115
2116
  localStorage.setItem(DISMISS_KEY, String(Date.now() + DISMISS_TTL_MS));
2116
2117
  setShow(false);
2117
2118
  }
2118
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("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: [
2119
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { children: [
2120
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "Sua sess\xE3o expirou." }),
2121
- " Fa\xE7a login novamente para continuar."
2122
- ] }),
2123
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
2124
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2125
- "button",
2126
- {
2127
- type: "button",
2128
- onClick: dismiss,
2129
- className: "px-3 py-1 bg-white text-red-700 rounded text-xs font-medium hover:bg-red-50",
2130
- children: "Fazer login"
2131
- }
2132
- ),
2133
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2134
- "button",
2135
- {
2136
- type: "button",
2137
- onClick: dismiss,
2138
- "aria-label": "Fechar",
2139
- className: "px-2 py-1 text-white/80 hover:text-white text-xs",
2140
- children: "Fechar"
2141
- }
2142
- )
2143
- ] })
2144
- ] });
2119
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2120
+ "div",
2121
+ {
2122
+ role: "alert",
2123
+ className: "fixed top-0 inset-x-0 px-4 py-3 flex items-center justify-between gap-3 text-sm font-medium shadow-lg",
2124
+ style: {
2125
+ zIndex: 10001,
2126
+ backgroundColor: "#991b1b",
2127
+ color: "#ffffff"
2128
+ },
2129
+ children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { children: [
2131
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { className: "font-bold", children: "Sua sess\xE3o expirou." }),
2132
+ " Fa\xE7a login novamente para continuar."
2133
+ ] }),
2134
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", children: [
2135
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2136
+ "button",
2137
+ {
2138
+ type: "button",
2139
+ onClick: dismiss,
2140
+ className: "px-3 py-1.5 rounded text-xs font-semibold",
2141
+ style: { backgroundColor: "#ffffff", color: "#991b1b" },
2142
+ children: "Fazer login"
2143
+ }
2144
+ ),
2145
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2146
+ "button",
2147
+ {
2148
+ type: "button",
2149
+ onClick: dismiss,
2150
+ "aria-label": "Fechar",
2151
+ className: "px-2 py-1 text-xs",
2152
+ style: { color: "#ffffff" },
2153
+ children: "Fechar"
2154
+ }
2155
+ )
2156
+ ] })
2157
+ ]
2158
+ }
2159
+ );
2145
2160
  }
2146
2161
 
2147
2162
  // src/internal/EmailVerifyBanner.tsx
@@ -2958,15 +2973,17 @@ function mapSdkError(err) {
2958
2973
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2959
2974
  var PHONE_RE = /^[0-9()+\-\s]{8,20}$/;
2960
2975
  var CHECK_DEBOUNCE_MS = 400;
2961
- function useCheckoutForm(args) {
2976
+ function useCheckoutForm(hookArgs) {
2962
2977
  const { auth } = (0, import_sdk13.useHook)();
2963
2978
  const [name, setName] = (0, import_react18.useState)("");
2964
2979
  const [email, setEmail] = (0, import_react18.useState)("");
2965
2980
  const [emailConfirm, setEmailConfirm] = (0, import_react18.useState)("");
2966
2981
  const [phone, setPhone] = (0, import_react18.useState)("");
2967
2982
  const [cpf, setCpf] = (0, import_react18.useState)("");
2968
- const [method, setMethod] = (0, import_react18.useState)(args.defaultMethod);
2969
- 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);
2970
2987
  const [card, setCardState] = (0, import_react18.useState)({
2971
2988
  number: "",
2972
2989
  expiryMonth: "",
@@ -2982,6 +2999,8 @@ function useCheckoutForm(args) {
2982
2999
  const [touchedEmailConfirm, setTouchedEmailConfirm] = (0, import_react18.useState)(false);
2983
3000
  const [touchedPhone, setTouchedPhone] = (0, import_react18.useState)(false);
2984
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);
2985
3004
  const [formSubmitAttempted, setFormSubmitAttempted] = (0, import_react18.useState)(false);
2986
3005
  const [submitting, setSubmitting] = (0, import_react18.useState)(false);
2987
3006
  const [error, setError] = (0, import_react18.useState)(null);
@@ -3035,13 +3054,29 @@ function useCheckoutForm(args) {
3035
3054
  const ok = mod11(digits, 9) === digits[9] && mod11(digits, 10) === digits[10];
3036
3055
  return ok ? null : "CPF inv\xE1lido.";
3037
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]);
3038
3070
  const nameError = touchedName || formSubmitAttempted ? validateName : null;
3039
3071
  const emailError = touchedEmail || formSubmitAttempted ? validateEmail : null;
3040
3072
  const emailConfirmError = touchedEmailConfirm || formSubmitAttempted ? validateEmailConfirm : null;
3041
3073
  const phoneError = touchedPhone || formSubmitAttempted ? validatePhone : null;
3042
3074
  const cpfError = touchedCpf || formSubmitAttempted ? validateCpf : null;
3075
+ const postalCodeError = touchedPostalCode || formSubmitAttempted ? validatePostalCode : null;
3076
+ const addressNumberError = touchedAddressNumber || formSubmitAttempted ? validateAddressNumber : null;
3043
3077
  const phoneOk = method === "pix-auto" ? phone === "" || PHONE_RE.test(phone) : PHONE_RE.test(phone);
3044
- 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);
3078
+ const cepGateOk = !hookArgs.collectCep || method !== "card" || postalCode.replace(/\D/g, "").length === 8 && addressNumber.trim().length > 0;
3079
+ 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);
3045
3080
  const submit = (0, import_react18.useCallback)(async () => {
3046
3081
  setFormSubmitAttempted(true);
3047
3082
  setError(null);
@@ -3050,9 +3085,9 @@ function useCheckoutForm(args) {
3050
3085
  if (!canSubmit) return null;
3051
3086
  setSubmitting(true);
3052
3087
  try {
3053
- let args2;
3088
+ let args;
3054
3089
  if (method === "card") {
3055
- args2 = {
3090
+ args = {
3056
3091
  method: "card",
3057
3092
  name: name.trim(),
3058
3093
  email,
@@ -3071,21 +3106,17 @@ function useCheckoutForm(args) {
3071
3106
  name: card.holderName || name.trim(),
3072
3107
  email,
3073
3108
  cpfCnpj: cpf.replace(/\D/g, ""),
3074
- // Plan-V 0.28.4 Asaas's `creditCardHolderInfo.postalCode`
3075
- // rejects all-zeros with `tokenize_failed:invalid_holderInfo`
3076
- // ("O CEP informado é inválido."). The default CheckoutPage
3077
- // doesn't collect CEP from the user (reference design skipped
3078
- // it), so ship a known-valid placeholder. Apps that need real
3079
- // customer addresses must override CheckoutPageDefault and
3080
- // collect CEP — see personalburn's PaywallStepPagamento for
3081
- // the pattern.
3082
- postalCode: "01001000",
3083
- addressNumber: "100",
3109
+ // collectCep=true CEP/número reais do form. Senão placeholder
3110
+ // válido (Asaas rejeita all-zeros, então shipping 01001000/100
3111
+ // que são válidos mas anonimizam). Apps com NF-e ou antifraude
3112
+ // refinada devem setar paywall.collectCep no app.config.json.
3113
+ postalCode: hookArgs.collectCep ? postalCode.replace(/\D/g, "") : "01001000",
3114
+ addressNumber: hookArgs.collectCep ? addressNumber.trim() : "100",
3084
3115
  phone
3085
3116
  }
3086
3117
  };
3087
3118
  } else {
3088
- args2 = {
3119
+ args = {
3089
3120
  method: "pix-auto",
3090
3121
  name: name.trim(),
3091
3122
  email,
@@ -3095,7 +3126,7 @@ function useCheckoutForm(args) {
3095
3126
  cycle
3096
3127
  };
3097
3128
  }
3098
- const result = await auth.subscribeAnonymous(args2);
3129
+ const result = await auth.subscribeAnonymous(args);
3099
3130
  return result;
3100
3131
  } catch (err) {
3101
3132
  if (err instanceof import_sdk13.EmailTakenError) {
@@ -3121,6 +3152,10 @@ function useCheckoutForm(args) {
3121
3152
  setPhone,
3122
3153
  cpf,
3123
3154
  setCpf,
3155
+ postalCode,
3156
+ setPostalCode,
3157
+ addressNumber,
3158
+ setAddressNumber,
3124
3159
  method,
3125
3160
  setMethod,
3126
3161
  cycle,
@@ -3132,11 +3167,15 @@ function useCheckoutForm(args) {
3132
3167
  emailConfirmError,
3133
3168
  phoneError,
3134
3169
  cpfError,
3170
+ postalCodeError,
3171
+ addressNumberError,
3135
3172
  markNameTouched: () => setTouchedName(true),
3136
3173
  markEmailTouched: () => setTouchedEmail(true),
3137
3174
  markEmailConfirmTouched: () => setTouchedEmailConfirm(true),
3138
3175
  markPhoneTouched: () => setTouchedPhone(true),
3139
3176
  markCpfTouched: () => setTouchedCpf(true),
3177
+ markPostalCodeTouched: () => setTouchedPostalCode(true),
3178
+ markAddressNumberTouched: () => setTouchedAddressNumber(true),
3140
3179
  emailStatus,
3141
3180
  submit,
3142
3181
  submitting,
@@ -3963,10 +4002,12 @@ function detectCardBrand(num) {
3963
4002
  function CheckoutPageDefault() {
3964
4003
  const navigate = (0, import_react_router_dom3.useNavigate)();
3965
4004
  const plan = usePlan();
4005
+ const config = useAppConfig();
3966
4006
  const intent = (0, import_react23.useMemo)(readIntent, []);
3967
4007
  const defaultMethod = intent.method === "pix-auto" ? "pix-auto" : "card";
3968
4008
  const defaultCycle = intent.cycle === "MONTHLY" ? "MONTHLY" : "YEARLY";
3969
- const form = useCheckoutForm({ defaultMethod, defaultCycle });
4009
+ const collectCep = config.paywall.mode !== "free" && config.paywall.collectCep === true;
4010
+ const form = useCheckoutForm({ defaultMethod, defaultCycle, collectCep });
3970
4011
  const [expiryMmAa, setExpiryMmAa] = (0, import_react23.useState)("");
3971
4012
  (0, import_react23.useEffect)(() => {
3972
4013
  const { month, year } = parseExpiryMmAa(expiryMmAa);
@@ -4150,6 +4191,45 @@ function CheckoutPageDefault() {
4150
4191
  }
4151
4192
  ),
4152
4193
  !form.phoneError && /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldHint, { children: "Usado pra confirmar pagamento e tratar disputas." })
4194
+ ] }) : null,
4195
+ collectCep && form.method === "card" ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
4196
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4197
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "CEP" }),
4198
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4199
+ FieldInput,
4200
+ {
4201
+ type: "text",
4202
+ inputMode: "numeric",
4203
+ autoComplete: "postal-code",
4204
+ placeholder: "00000-000",
4205
+ value: form.postalCode,
4206
+ onChange: (v) => {
4207
+ const digits = v.replace(/\D/g, "").slice(0, 8);
4208
+ const formatted = digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
4209
+ form.setPostalCode(formatted);
4210
+ },
4211
+ onBlur: form.markPostalCodeTouched,
4212
+ error: form.postalCodeError,
4213
+ valid: !!form.postalCode && !form.postalCodeError
4214
+ }
4215
+ )
4216
+ ] }),
4217
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { children: [
4218
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(FieldLabel, { children: "N\xFAmero" }),
4219
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4220
+ FieldInput,
4221
+ {
4222
+ type: "text",
4223
+ inputMode: "numeric",
4224
+ placeholder: "100",
4225
+ value: form.addressNumber,
4226
+ onChange: form.setAddressNumber,
4227
+ onBlur: form.markAddressNumberTouched,
4228
+ error: form.addressNumberError,
4229
+ valid: !!form.addressNumber && !form.addressNumberError
4230
+ }
4231
+ )
4232
+ ] })
4153
4233
  ] }) : null
4154
4234
  ] })
4155
4235
  ] }),