@hook-sdk/template 0.8.1 → 0.9.1

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
@@ -62,7 +62,7 @@ module.exports = __toCommonJS(index_exports);
62
62
 
63
63
  // src/AppRoot.tsx
64
64
  var import_react15 = require("react");
65
- var import_sdk9 = require("@hook-sdk/sdk");
65
+ var import_sdk10 = require("@hook-sdk/sdk");
66
66
 
67
67
  // src/internal/TemplateConfigContext.tsx
68
68
  var import_react = require("react");
@@ -157,50 +157,66 @@ function AuthGate({ Login, Signup, Forgot, Reset, children }) {
157
157
  var import_react3 = require("react");
158
158
  var import_sdk2 = require("@hook-sdk/sdk");
159
159
  function usePaywallState() {
160
- const { subscription } = (0, import_sdk2.useHook)();
160
+ const { subscription, plan } = (0, import_sdk2.useHook)();
161
161
  const [opening, setOpening] = (0, import_react3.useState)(false);
162
162
  const [error, setError] = (0, import_react3.useState)(null);
163
163
  const [pixPending, setPixPending] = (0, import_react3.useState)(null);
164
164
  const status = subscription.status();
165
165
  const daysLeftInTrial = subscription.daysLeftInTrial();
166
166
  const initialLoadComplete = subscription.initialLoadComplete;
167
+ const availableMethods = (0, import_react3.useMemo)(
168
+ () => ["card", "pix-auto"],
169
+ []
170
+ );
171
+ const priceCents = plan.data?.priceCents ?? 0;
172
+ const yearlyPriceCents = plan.data?.yearlyPriceCents ?? null;
173
+ const monthlyEquivalent = (0, import_react3.useCallback)(
174
+ (cycle) => {
175
+ if (cycle === "YEARLY" && yearlyPriceCents) {
176
+ return Math.round(yearlyPriceCents / 12);
177
+ }
178
+ return priceCents;
179
+ },
180
+ [priceCents, yearlyPriceCents]
181
+ );
167
182
  const checkout = (0, import_react3.useCallback)(
168
183
  async (args) => {
169
184
  setOpening(true);
170
185
  setError(null);
171
186
  setPixPending(null);
172
- const method = args.method ?? "card";
173
- const cycle = args.cycle ?? "MONTHLY";
174
187
  try {
175
- if (method === "card") {
176
- const result = await subscription.checkoutCard({ cpf: args.cpf, cycle });
177
- window.location.href = result.invoiceUrl;
178
- return;
179
- }
180
- if (method === "pix-auto") {
181
- const result = await subscription.checkoutPixAuto({ cpf: args.cpf, cycle });
182
- setPixPending({
183
- method: "pix-auto",
184
- qrCodePayload: result.qrCodePayload,
185
- qrCodeBase64: result.qrCodeBase64,
186
- expiresAt: null,
187
- paid: false
188
+ if (args.method === "card") {
189
+ if (!args.card || !args.holderInfo) {
190
+ throw new Error('card and holderInfo are required when method is "card"');
191
+ }
192
+ await subscription.checkout({
193
+ method: "card",
194
+ cycle: args.cycle,
195
+ cpf: args.cpf,
196
+ card: args.card,
197
+ holderInfo: args.holderInfo,
198
+ ...args.remoteIp ? { remoteIp: args.remoteIp } : {}
188
199
  });
200
+ await subscription.refresh();
189
201
  setOpening(false);
190
202
  return;
191
203
  }
192
- if (method === "pix-once") {
193
- const result = await subscription.checkoutPixOnce({ cpf: args.cpf, cycle });
194
- setPixPending({
195
- method: "pix-once",
196
- qrCodePayload: result.qrCodePayload,
197
- qrCodeBase64: result.qrCodeBase64,
198
- expiresAt: result.expiresAt,
199
- paid: false
200
- });
201
- setOpening(false);
202
- return;
204
+ const result = await subscription.checkout({
205
+ method: "pix-auto",
206
+ cycle: args.cycle,
207
+ cpf: args.cpf
208
+ });
209
+ if (result.method !== "pix-auto") {
210
+ throw new Error(`unexpected checkout result method: ${result.method}`);
203
211
  }
212
+ setPixPending({
213
+ method: "pix-auto",
214
+ qrCodePayload: result.qrCodePayload,
215
+ qrCodeBase64: result.qrCodeBase64,
216
+ expiresAt: null,
217
+ paid: false
218
+ });
219
+ setOpening(false);
204
220
  } catch (err) {
205
221
  setError(err);
206
222
  setOpening(false);
@@ -253,7 +269,9 @@ function usePaywallState() {
253
269
  opening,
254
270
  error,
255
271
  pixPending,
256
- dismissPix
272
+ dismissPix,
273
+ availableMethods,
274
+ monthlyEquivalent
257
275
  };
258
276
  }
259
277
 
@@ -2198,43 +2216,485 @@ function DefaultResetScreen({ onNavigate }) {
2198
2216
 
2199
2217
  // src/defaults/DefaultPaywall.tsx
2200
2218
  var import_react14 = require("react");
2219
+
2220
+ // src/hooks/usePlan.ts
2221
+ var import_sdk9 = require("@hook-sdk/sdk");
2222
+ function usePlan() {
2223
+ const { plan } = (0, import_sdk9.useHook)();
2224
+ return plan;
2225
+ }
2226
+
2227
+ // src/utils/price.ts
2228
+ function formatBRL(cents) {
2229
+ if (cents === null || cents === void 0) return "";
2230
+ const reais = cents / 100;
2231
+ return new Intl.NumberFormat("pt-BR", {
2232
+ style: "currency",
2233
+ currency: "BRL"
2234
+ }).format(reais);
2235
+ }
2236
+ function monthlyFromYearly(yearlyCents) {
2237
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2238
+ return Math.round(yearlyCents / 12);
2239
+ }
2240
+ function dailyFromYearly(yearlyCents) {
2241
+ if (yearlyCents === null || yearlyCents === void 0) return 0;
2242
+ return Math.round(yearlyCents / 365);
2243
+ }
2244
+ function computeAnchorCents(baseCents, multiplier) {
2245
+ if (multiplier === null || multiplier === void 0) return null;
2246
+ if (!Number.isFinite(multiplier)) return null;
2247
+ if (multiplier <= 1) return null;
2248
+ return Math.round(baseCents * multiplier);
2249
+ }
2250
+ function discountPercent(anchorCents, realCents) {
2251
+ if (anchorCents <= realCents) return 0;
2252
+ return Math.floor((anchorCents - realCents) / anchorCents * 100);
2253
+ }
2254
+
2255
+ // src/defaults/DefaultPaywall.tsx
2201
2256
  var import_jsx_runtime24 = require("react/jsx-runtime");
2202
2257
  function DefaultPaywall() {
2203
2258
  const config = useTemplateConfig();
2204
- const { checkout, opening, error } = usePaywallState();
2259
+ const plan = usePlan();
2260
+ const {
2261
+ checkout,
2262
+ opening,
2263
+ error,
2264
+ availableMethods,
2265
+ monthlyEquivalent,
2266
+ pixPending,
2267
+ dismissPix
2268
+ } = usePaywallState();
2205
2269
  const p = config.subscription.paywall_config;
2270
+ const paywallCfg = plan.data?.paywallConfig ?? {};
2271
+ const anchorMultiplier = paywallCfg.anchorMultiplier ?? p.anchorMultiplier;
2272
+ const anchorPriceCents = paywallCfg.anchorPriceCents ?? p.anchorPriceCents;
2273
+ const [cycle, setCycle] = (0, import_react14.useState)("MONTHLY");
2274
+ const [method, setMethod] = (0, import_react14.useState)("card");
2206
2275
  const [cpf, setCpf] = (0, import_react14.useState)("");
2276
+ const [cardNumber, setCardNumber] = (0, import_react14.useState)("");
2277
+ const [cardHolderName, setCardHolderName] = (0, import_react14.useState)("");
2278
+ const [cardExpiryMonth, setCardExpiryMonth] = (0, import_react14.useState)("");
2279
+ const [cardExpiryYear, setCardExpiryYear] = (0, import_react14.useState)("");
2280
+ const [cardCcv, setCardCcv] = (0, import_react14.useState)("");
2281
+ const [holderName, setHolderName] = (0, import_react14.useState)("");
2282
+ const [holderEmail, setHolderEmail] = (0, import_react14.useState)("");
2283
+ const [holderPostalCode, setHolderPostalCode] = (0, import_react14.useState)("");
2284
+ const [holderAddressNumber, setHolderAddressNumber] = (0, import_react14.useState)("");
2285
+ const [holderPhone, setHolderPhone] = (0, import_react14.useState)("");
2286
+ const trialDays = plan.data?.trialDays ?? 0;
2287
+ const monthlyCents = plan.data?.priceCents ?? 0;
2288
+ const yearlyCents = plan.data?.yearlyPriceCents ?? null;
2289
+ const activeCents = cycle === "YEARLY" && yearlyCents ? monthlyEquivalent("YEARLY") : monthlyCents;
2290
+ const cycleValueCents = cycle === "YEARLY" && yearlyCents ? yearlyCents : monthlyCents;
2291
+ const cycleLabel = cycle === "YEARLY" ? "por ano" : "por m\xEAs";
2292
+ const pct = (0, import_react14.useMemo)(() => {
2293
+ if (!yearlyCents || !monthlyCents) return 0;
2294
+ const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2295
+ return Math.max(0, derived);
2296
+ }, [monthlyCents, yearlyCents]);
2297
+ const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2298
+ const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2299
+ const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2207
2300
  const cpfDigits = cpf.replace(/\D/g, "");
2208
- const canCheckout = cpfDigits.length === 11 && !opening;
2209
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 440, margin: "0 auto", textAlign: "center" }, children: [
2301
+ const cardFieldsFilled = cardNumber.replace(/\s/g, "").length >= 13 && cardHolderName.trim().length > 0 && cardExpiryMonth.length === 2 && cardExpiryYear.length >= 2 && cardCcv.length >= 3 && holderName.trim().length > 0 && /.+@.+\..+/.test(holderEmail) && holderPostalCode.replace(/\D/g, "").length === 8 && holderAddressNumber.trim().length > 0;
2302
+ const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2303
+ const ctaLabel = (0, import_react14.useMemo)(() => {
2304
+ if (opening) return "Abrindo\u2026";
2305
+ if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2306
+ return p.cta ?? "Assinar agora";
2307
+ }, [opening, trialDays, p.cta]);
2308
+ const footer = (0, import_react14.useMemo)(() => {
2309
+ if (trialDays > 0) {
2310
+ return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2311
+ }
2312
+ return "Cobran\xE7a imediata. Cancele quando quiser.";
2313
+ }, [trialDays]);
2314
+ const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2315
+ const phoneDigits = holderPhone.replace(/\D/g, "");
2316
+ const postalDigits = holderPostalCode.replace(/\D/g, "");
2317
+ const submit = () => {
2318
+ if (method === "card") {
2319
+ void checkout({
2320
+ cpf: cpfDigits,
2321
+ cycle,
2322
+ method: "card",
2323
+ card: {
2324
+ number: cardNumber.replace(/\s/g, ""),
2325
+ holderName: cardHolderName.trim(),
2326
+ expiryMonth: cardExpiryMonth,
2327
+ expiryYear: yearSuffix,
2328
+ ccv: cardCcv
2329
+ },
2330
+ holderInfo: {
2331
+ name: holderName.trim(),
2332
+ email: holderEmail.trim(),
2333
+ cpfCnpj: cpfDigits,
2334
+ postalCode: postalDigits,
2335
+ addressNumber: holderAddressNumber.trim(),
2336
+ ...phoneDigits ? { phone: phoneDigits } : {}
2337
+ }
2338
+ });
2339
+ return;
2340
+ }
2341
+ void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2342
+ };
2343
+ const inputStyle = {
2344
+ width: "100%",
2345
+ padding: 10,
2346
+ fontSize: 14,
2347
+ borderRadius: 8,
2348
+ border: "1px solid #ccc",
2349
+ boxSizing: "border-box"
2350
+ };
2351
+ const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2352
+ const fieldGroup = { marginBottom: 10, textAlign: "left" };
2353
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2210
2354
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h1", { style: { marginBottom: 8 }, children: p.title }),
2211
2355
  p.subtitle && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7, marginBottom: 24 }, children: p.subtitle }),
2212
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", marginBottom: 24 }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2356
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2357
+ "div",
2358
+ {
2359
+ role: "group",
2360
+ "aria-label": "Per\xEDodo de cobran\xE7a",
2361
+ style: { display: "flex", gap: 8, marginBottom: 16 },
2362
+ children: [
2363
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2364
+ "button",
2365
+ {
2366
+ type: "button",
2367
+ "aria-pressed": cycle === "MONTHLY",
2368
+ onClick: () => setCycle("MONTHLY"),
2369
+ style: {
2370
+ flex: 1,
2371
+ padding: 12,
2372
+ borderRadius: 8,
2373
+ border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2374
+ background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2375
+ cursor: "pointer"
2376
+ },
2377
+ children: "Mensal"
2378
+ }
2379
+ ),
2380
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2381
+ "button",
2382
+ {
2383
+ type: "button",
2384
+ "aria-pressed": cycle === "YEARLY",
2385
+ onClick: () => setCycle("YEARLY"),
2386
+ disabled: !yearlyCents,
2387
+ style: {
2388
+ flex: 1,
2389
+ padding: 12,
2390
+ borderRadius: 8,
2391
+ border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2392
+ background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2393
+ cursor: yearlyCents ? "pointer" : "not-allowed",
2394
+ opacity: yearlyCents ? 1 : 0.5
2395
+ },
2396
+ children: [
2397
+ "Anual",
2398
+ pct > 0 ? ` \u2212${pct}%` : ""
2399
+ ]
2400
+ }
2401
+ )
2402
+ ]
2403
+ }
2404
+ ),
2405
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { marginBottom: 8 }, children: [
2406
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2407
+ formatBRL(activeCents),
2408
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2409
+ ] }),
2410
+ anchorCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2411
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2412
+ anchorDiscount > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { marginLeft: 6 }, children: [
2413
+ "\u2212",
2414
+ anchorDiscount,
2415
+ "%"
2416
+ ] })
2417
+ ] }),
2418
+ cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2419
+ "Cobrado ",
2420
+ formatBRL(yearlyCents),
2421
+ " por ano"
2422
+ ] })
2423
+ ] }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { style: { listStyle: "none", padding: 0, textAlign: "left", margin: "24px 0" }, children: p.benefits.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("li", { style: { padding: "8px 0", display: "flex", alignItems: "center" }, children: [
2213
2425
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2214
2426
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: b })
2215
2427
  ] }, b)) }),
2216
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { textAlign: "left", marginBottom: 16 }, children: [
2217
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { style: { display: "block", fontSize: 14, opacity: 0.7, marginBottom: 4 }, children: "Seu CPF (pra emiss\xE3o de recibo)" }),
2428
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "tablist", "aria-label": "M\xE9todo de pagamento", style: { display: "flex", gap: 8, marginBottom: 16 }, children: availableMethods.map((m) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2429
+ "button",
2430
+ {
2431
+ type: "button",
2432
+ role: "tab",
2433
+ "aria-selected": method === m,
2434
+ onClick: () => setMethod(m),
2435
+ style: {
2436
+ flex: 1,
2437
+ padding: 10,
2438
+ borderRadius: 8,
2439
+ border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2440
+ background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2441
+ cursor: "pointer"
2442
+ },
2443
+ children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2444
+ },
2445
+ m
2446
+ )) }),
2447
+ method === "card" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2448
+ "fieldset",
2449
+ {
2450
+ "data-testid": "paywall-card-form",
2451
+ style: { border: "none", padding: 0, marginBottom: 16 },
2452
+ children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2455
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2457
+ "input",
2458
+ {
2459
+ id: "pw-card-number",
2460
+ "data-testid": "pw-card-number",
2461
+ type: "text",
2462
+ inputMode: "numeric",
2463
+ autoComplete: "cc-number",
2464
+ placeholder: "0000 0000 0000 0000",
2465
+ value: cardNumber,
2466
+ onChange: (e) => setCardNumber(e.target.value),
2467
+ style: inputStyle
2468
+ }
2469
+ )
2470
+ ] }),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2472
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2473
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2474
+ "input",
2475
+ {
2476
+ id: "pw-card-holder",
2477
+ "data-testid": "pw-card-holder",
2478
+ type: "text",
2479
+ autoComplete: "cc-name",
2480
+ placeholder: "NOME SOBRENOME",
2481
+ value: cardHolderName,
2482
+ onChange: (e) => setCardHolderName(e.target.value),
2483
+ style: inputStyle
2484
+ }
2485
+ )
2486
+ ] }),
2487
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2488
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2489
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2490
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2491
+ "input",
2492
+ {
2493
+ id: "pw-card-exp-m",
2494
+ "data-testid": "pw-card-exp-m",
2495
+ type: "text",
2496
+ inputMode: "numeric",
2497
+ autoComplete: "cc-exp-month",
2498
+ placeholder: "MM",
2499
+ maxLength: 2,
2500
+ value: cardExpiryMonth,
2501
+ onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2502
+ style: inputStyle
2503
+ }
2504
+ )
2505
+ ] }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2507
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2508
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2509
+ "input",
2510
+ {
2511
+ id: "pw-card-exp-y",
2512
+ "data-testid": "pw-card-exp-y",
2513
+ type: "text",
2514
+ inputMode: "numeric",
2515
+ autoComplete: "cc-exp-year",
2516
+ placeholder: "AA",
2517
+ maxLength: 4,
2518
+ value: cardExpiryYear,
2519
+ onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2520
+ style: inputStyle
2521
+ }
2522
+ )
2523
+ ] }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2525
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2526
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2527
+ "input",
2528
+ {
2529
+ id: "pw-card-cvv",
2530
+ "data-testid": "pw-card-cvv",
2531
+ type: "text",
2532
+ inputMode: "numeric",
2533
+ autoComplete: "cc-csc",
2534
+ placeholder: "123",
2535
+ maxLength: 4,
2536
+ value: cardCcv,
2537
+ onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2538
+ style: inputStyle
2539
+ }
2540
+ )
2541
+ ] })
2542
+ ] }),
2543
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2544
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2545
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2546
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2547
+ "input",
2548
+ {
2549
+ id: "pw-holder-name",
2550
+ "data-testid": "pw-holder-name",
2551
+ type: "text",
2552
+ autoComplete: "name",
2553
+ placeholder: "Nome Sobrenome",
2554
+ value: holderName,
2555
+ onChange: (e) => setHolderName(e.target.value),
2556
+ style: inputStyle
2557
+ }
2558
+ )
2559
+ ] }),
2560
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2561
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2563
+ "input",
2564
+ {
2565
+ id: "pw-holder-email",
2566
+ "data-testid": "pw-holder-email",
2567
+ type: "email",
2568
+ autoComplete: "email",
2569
+ placeholder: "voce@email.com",
2570
+ value: holderEmail,
2571
+ onChange: (e) => setHolderEmail(e.target.value),
2572
+ style: inputStyle
2573
+ }
2574
+ )
2575
+ ] }),
2576
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2577
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2578
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2579
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2580
+ "input",
2581
+ {
2582
+ id: "pw-holder-cep",
2583
+ "data-testid": "pw-holder-cep",
2584
+ type: "text",
2585
+ inputMode: "numeric",
2586
+ autoComplete: "postal-code",
2587
+ placeholder: "00000-000",
2588
+ value: holderPostalCode,
2589
+ onChange: (e) => setHolderPostalCode(e.target.value),
2590
+ style: inputStyle
2591
+ }
2592
+ )
2593
+ ] }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2595
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2596
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2597
+ "input",
2598
+ {
2599
+ id: "pw-holder-addr-n",
2600
+ "data-testid": "pw-holder-addr-n",
2601
+ type: "text",
2602
+ inputMode: "numeric",
2603
+ placeholder: "123",
2604
+ value: holderAddressNumber,
2605
+ onChange: (e) => setHolderAddressNumber(e.target.value),
2606
+ style: inputStyle
2607
+ }
2608
+ )
2609
+ ] })
2610
+ ] }),
2611
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2612
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2614
+ "input",
2615
+ {
2616
+ id: "pw-holder-phone",
2617
+ "data-testid": "pw-holder-phone",
2618
+ type: "tel",
2619
+ inputMode: "tel",
2620
+ autoComplete: "tel",
2621
+ placeholder: "(11) 99999-9999",
2622
+ value: holderPhone,
2623
+ onChange: (e) => setHolderPhone(e.target.value),
2624
+ style: inputStyle
2625
+ }
2626
+ )
2627
+ ] })
2628
+ ]
2629
+ }
2630
+ ),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2218
2633
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2219
2634
  "input",
2220
2635
  {
2636
+ id: "pw-cpf",
2221
2637
  "data-testid": "paywall-cpf",
2222
2638
  type: "text",
2223
2639
  inputMode: "numeric",
2224
2640
  placeholder: "000.000.000-00",
2225
2641
  value: cpf,
2226
2642
  onChange: (e) => setCpf(e.target.value),
2227
- style: { width: "100%", padding: 10, fontSize: 14, borderRadius: 8, border: "1px solid #ccc" }
2643
+ style: inputStyle
2228
2644
  }
2229
2645
  )
2230
2646
  ] }),
2231
2647
  error && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { role: "alert", style: { color: "#c00", marginBottom: 12 }, children: error.message }),
2648
+ method === "pix-auto" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2649
+ "div",
2650
+ {
2651
+ "data-testid": "paywall-pix-callout",
2652
+ style: {
2653
+ background: "rgba(0,0,0,0.04)",
2654
+ borderLeft: "3px solid var(--hook-color-primary)",
2655
+ padding: "10px 12px",
2656
+ marginBottom: 12,
2657
+ borderRadius: 4,
2658
+ fontSize: 12,
2659
+ lineHeight: 1.45,
2660
+ textAlign: "left"
2661
+ },
2662
+ children: [
2663
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "Como funciona:" }),
2664
+ " no app do seu banco vai aparecer uma cobran\xE7a de",
2665
+ " ",
2666
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "R$ 0,01" }),
2667
+ " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2668
+ trialDays > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2669
+ " ",
2670
+ "Depois, ",
2671
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("strong", { children: [
2672
+ trialDays,
2673
+ " dias gr\xE1tis"
2674
+ ] }),
2675
+ "; a cobran\xE7a de",
2676
+ " ",
2677
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2678
+ " ",
2679
+ cycleLabel,
2680
+ " s\xF3 come\xE7a depois."
2681
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2682
+ " ",
2683
+ "A cobran\xE7a de ",
2684
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2685
+ " ",
2686
+ cycleLabel,
2687
+ " vem em seguida."
2688
+ ] })
2689
+ ]
2690
+ }
2691
+ ),
2232
2692
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2233
2693
  "button",
2234
2694
  {
2235
2695
  "data-testid": "paywall-cta",
2236
2696
  type: "button",
2237
- onClick: () => void checkout({ cpf: cpfDigits }),
2697
+ onClick: submit,
2238
2698
  disabled: !canCheckout,
2239
2699
  style: {
2240
2700
  width: "100%",
@@ -2245,13 +2705,125 @@ function DefaultPaywall() {
2245
2705
  borderRadius: 8,
2246
2706
  opacity: canCheckout ? 1 : 0.5,
2247
2707
  fontSize: 16,
2248
- fontWeight: 600
2708
+ fontWeight: 600,
2709
+ cursor: canCheckout ? "pointer" : "not-allowed"
2249
2710
  },
2250
- children: opening ? "Abrindo..." : p.cta
2711
+ children: ctaLabel
2251
2712
  }
2252
2713
  ),
2253
- p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 12 }, children: p.priceHint }),
2254
- p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 16, fontSize: 12 }, children: p.footerNote })
2714
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2715
+ p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2716
+ p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2717
+ pixPending && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2718
+ "div",
2719
+ {
2720
+ "data-testid": "paywall-pix-modal",
2721
+ role: "dialog",
2722
+ "aria-label": "Pagamento Pix pendente",
2723
+ style: {
2724
+ position: "fixed",
2725
+ inset: 0,
2726
+ background: "rgba(0,0,0,0.6)",
2727
+ display: "flex",
2728
+ alignItems: "center",
2729
+ justifyContent: "center",
2730
+ padding: 24,
2731
+ zIndex: 1e3
2732
+ },
2733
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: {
2734
+ background: "#fff",
2735
+ borderRadius: 12,
2736
+ padding: 24,
2737
+ maxWidth: 360,
2738
+ width: "100%",
2739
+ textAlign: "center"
2740
+ }, children: [
2741
+ pixPending.paid ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2742
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2743
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2744
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2745
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2746
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 13, opacity: 0.75 }, children: "Escaneie o QR Code no app do seu banco pra autorizar o d\xE9bito recorrente." }),
2747
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2748
+ "p",
2749
+ {
2750
+ "data-testid": "pix-modal-explain",
2751
+ style: { fontSize: 12, opacity: 0.65, marginTop: 8, lineHeight: 1.4 },
2752
+ children: [
2753
+ "Seu banco vai mostrar uma cobran\xE7a de ",
2754
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "R$ 0,01" }),
2755
+ " \u2014 \xE9 simb\xF3lica, s\xF3 pra ativar o PIX Autom\xE1tico.",
2756
+ trialDays > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2757
+ " ",
2758
+ "A cobran\xE7a de ",
2759
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2760
+ " ",
2761
+ cycleLabel,
2762
+ " come\xE7a depois dos ",
2763
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("strong", { children: [
2764
+ trialDays,
2765
+ " dias gr\xE1tis"
2766
+ ] }),
2767
+ ", automaticamente."
2768
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2769
+ " ",
2770
+ "A cobran\xE7a de ",
2771
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: formatBRL(cycleValueCents) }),
2772
+ " ",
2773
+ cycleLabel,
2774
+ " vem logo em seguida."
2775
+ ] })
2776
+ ]
2777
+ }
2778
+ ),
2779
+ pixPending.qrCodeBase64 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2780
+ "img",
2781
+ {
2782
+ "data-testid": "pix-qr-image",
2783
+ alt: "QR Code Pix",
2784
+ src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2785
+ style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2786
+ }
2787
+ ),
2788
+ pixPending.qrCodePayload && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2789
+ "textarea",
2790
+ {
2791
+ "data-testid": "pix-qr-payload",
2792
+ readOnly: true,
2793
+ value: pixPending.qrCodePayload,
2794
+ style: {
2795
+ width: "100%",
2796
+ minHeight: 72,
2797
+ padding: 8,
2798
+ fontSize: 11,
2799
+ fontFamily: "monospace",
2800
+ borderRadius: 6,
2801
+ border: "1px solid #ccc",
2802
+ resize: "none"
2803
+ }
2804
+ }
2805
+ ),
2806
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { fontSize: 11, opacity: 0.55, marginTop: 12 }, children: "Aguardando confirma\xE7\xE3o do banco\u2026 Pode levar alguns segundos." })
2807
+ ] }),
2808
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2809
+ "button",
2810
+ {
2811
+ type: "button",
2812
+ onClick: dismissPix,
2813
+ style: {
2814
+ marginTop: 16,
2815
+ padding: "8px 16px",
2816
+ border: "1px solid #ccc",
2817
+ borderRadius: 6,
2818
+ background: "white",
2819
+ cursor: "pointer"
2820
+ },
2821
+ children: "Fechar"
2822
+ }
2823
+ )
2824
+ ] })
2825
+ }
2826
+ )
2255
2827
  ] });
2256
2828
  }
2257
2829
 
@@ -2259,7 +2831,7 @@ function DefaultPaywall() {
2259
2831
  var import_jsx_runtime25 = require("react/jsx-runtime");
2260
2832
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2261
2833
  function PaymentReturnHandler({ children }) {
2262
- const { subscription } = (0, import_sdk9.useHook)();
2834
+ const { subscription } = (0, import_sdk10.useHook)();
2263
2835
  const subRef = (0, import_react15.useRef)(subscription);
2264
2836
  subRef.current = subscription;
2265
2837
  const runIdRef = (0, import_react15.useRef)(0);
@@ -2368,7 +2940,7 @@ function AppRoot({
2368
2940
 
2369
2941
  // src/hooks/usePush.ts
2370
2942
  var import_react16 = require("react");
2371
- var import_sdk10 = require("@hook-sdk/sdk");
2943
+ var import_sdk11 = require("@hook-sdk/sdk");
2372
2944
  function detectIosNeedsInstall() {
2373
2945
  if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2374
2946
  const ua = navigator.userAgent || "";
@@ -2394,7 +2966,7 @@ function deriveState(push) {
2394
2966
  return { kind: "prompt" };
2395
2967
  }
2396
2968
  function usePush() {
2397
- const { push } = (0, import_sdk10.useHook)();
2969
+ const { push } = (0, import_sdk11.useHook)();
2398
2970
  const [state, setState] = (0, import_react16.useState)(() => deriveState(push));
2399
2971
  (0, import_react16.useEffect)(() => {
2400
2972
  setState(deriveState(push));
@@ -2476,41 +3048,6 @@ function EmptyState({ title, description, action }) {
2476
3048
  ] });
2477
3049
  }
2478
3050
 
2479
- // src/hooks/usePlan.ts
2480
- var import_sdk11 = require("@hook-sdk/sdk");
2481
- function usePlan() {
2482
- const { plan } = (0, import_sdk11.useHook)();
2483
- return plan;
2484
- }
2485
-
2486
- // src/utils/price.ts
2487
- function formatBRL(cents) {
2488
- if (cents === null || cents === void 0) return "";
2489
- const reais = cents / 100;
2490
- return new Intl.NumberFormat("pt-BR", {
2491
- style: "currency",
2492
- currency: "BRL"
2493
- }).format(reais);
2494
- }
2495
- function monthlyFromYearly(yearlyCents) {
2496
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2497
- return Math.round(yearlyCents / 12);
2498
- }
2499
- function dailyFromYearly(yearlyCents) {
2500
- if (yearlyCents === null || yearlyCents === void 0) return 0;
2501
- return Math.round(yearlyCents / 365);
2502
- }
2503
- function computeAnchorCents(baseCents, multiplier) {
2504
- if (multiplier === null || multiplier === void 0) return null;
2505
- if (!Number.isFinite(multiplier)) return null;
2506
- if (multiplier <= 1) return null;
2507
- return Math.round(baseCents * multiplier);
2508
- }
2509
- function discountPercent(anchorCents, realCents) {
2510
- if (anchorCents <= realCents) return 0;
2511
- return Math.floor((anchorCents - realCents) / anchorCents * 100);
2512
- }
2513
-
2514
3051
  // src/hooks/useAuthPrimitives.ts
2515
3052
  var import_react17 = require("react");
2516
3053
  var import_sdk12 = require("@hook-sdk/sdk");