@hook-sdk/template 0.8.1 → 0.9.0

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,33 +2216,429 @@ 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 pct = (0, import_react14.useMemo)(() => {
2291
+ if (!yearlyCents || !monthlyCents) return 0;
2292
+ const derived = Math.round((1 - yearlyCents / 12 / monthlyCents) * 100);
2293
+ return Math.max(0, derived);
2294
+ }, [monthlyCents, yearlyCents]);
2295
+ const anchorBaseCents = cycle === "YEARLY" && yearlyCents ? monthlyFromYearly(yearlyCents) : monthlyCents;
2296
+ const anchorCents = computeAnchorCents(anchorBaseCents, anchorMultiplier) ?? (anchorPriceCents && anchorPriceCents > anchorBaseCents ? anchorPriceCents : null);
2297
+ const anchorDiscount = anchorCents ? discountPercent(anchorCents, activeCents) : 0;
2207
2298
  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: [
2299
+ 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;
2300
+ const canCheckout = cpfDigits.length === 11 && !opening && (method === "pix-auto" || cardFieldsFilled);
2301
+ const ctaLabel = (0, import_react14.useMemo)(() => {
2302
+ if (opening) return "Abrindo\u2026";
2303
+ if (trialDays > 0) return `Comece trial de ${trialDays} dias gr\xE1tis`;
2304
+ return p.cta ?? "Assinar agora";
2305
+ }, [opening, trialDays, p.cta]);
2306
+ const footer = (0, import_react14.useMemo)(() => {
2307
+ if (trialDays > 0) {
2308
+ return `Sem cobran\xE7a agora. Cobran\xE7a autom\xE1tica em ${trialDays} dias. Cancele quando quiser.`;
2309
+ }
2310
+ return "Cobran\xE7a imediata. Cancele quando quiser.";
2311
+ }, [trialDays]);
2312
+ const yearSuffix = cardExpiryYear.length === 2 ? `20${cardExpiryYear}` : cardExpiryYear;
2313
+ const phoneDigits = holderPhone.replace(/\D/g, "");
2314
+ const postalDigits = holderPostalCode.replace(/\D/g, "");
2315
+ const submit = () => {
2316
+ if (method === "card") {
2317
+ void checkout({
2318
+ cpf: cpfDigits,
2319
+ cycle,
2320
+ method: "card",
2321
+ card: {
2322
+ number: cardNumber.replace(/\s/g, ""),
2323
+ holderName: cardHolderName.trim(),
2324
+ expiryMonth: cardExpiryMonth,
2325
+ expiryYear: yearSuffix,
2326
+ ccv: cardCcv
2327
+ },
2328
+ holderInfo: {
2329
+ name: holderName.trim(),
2330
+ email: holderEmail.trim(),
2331
+ cpfCnpj: cpfDigits,
2332
+ postalCode: postalDigits,
2333
+ addressNumber: holderAddressNumber.trim(),
2334
+ ...phoneDigits ? { phone: phoneDigits } : {}
2335
+ }
2336
+ });
2337
+ return;
2338
+ }
2339
+ void checkout({ cpf: cpfDigits, cycle, method: "pix-auto" });
2340
+ };
2341
+ const inputStyle = {
2342
+ width: "100%",
2343
+ padding: 10,
2344
+ fontSize: 14,
2345
+ borderRadius: 8,
2346
+ border: "1px solid #ccc",
2347
+ boxSizing: "border-box"
2348
+ };
2349
+ const labelStyle = { display: "block", fontSize: 13, opacity: 0.75, marginBottom: 4 };
2350
+ const fieldGroup = { marginBottom: 10, textAlign: "left" };
2351
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("main", { style: { padding: 24, maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
2210
2352
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h1", { style: { marginBottom: 8 }, children: p.title }),
2211
2353
  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: [
2354
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2355
+ "div",
2356
+ {
2357
+ role: "group",
2358
+ "aria-label": "Per\xEDodo de cobran\xE7a",
2359
+ style: { display: "flex", gap: 8, marginBottom: 16 },
2360
+ children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2362
+ "button",
2363
+ {
2364
+ type: "button",
2365
+ "aria-pressed": cycle === "MONTHLY",
2366
+ onClick: () => setCycle("MONTHLY"),
2367
+ style: {
2368
+ flex: 1,
2369
+ padding: 12,
2370
+ borderRadius: 8,
2371
+ border: cycle === "MONTHLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2372
+ background: cycle === "MONTHLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2373
+ cursor: "pointer"
2374
+ },
2375
+ children: "Mensal"
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2379
+ "button",
2380
+ {
2381
+ type: "button",
2382
+ "aria-pressed": cycle === "YEARLY",
2383
+ onClick: () => setCycle("YEARLY"),
2384
+ disabled: !yearlyCents,
2385
+ style: {
2386
+ flex: 1,
2387
+ padding: 12,
2388
+ borderRadius: 8,
2389
+ border: cycle === "YEARLY" ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2390
+ background: cycle === "YEARLY" ? "var(--hook-color-primary-soft, #eef)" : "white",
2391
+ cursor: yearlyCents ? "pointer" : "not-allowed",
2392
+ opacity: yearlyCents ? 1 : 0.5
2393
+ },
2394
+ children: [
2395
+ "Anual",
2396
+ pct > 0 ? ` \u2212${pct}%` : ""
2397
+ ]
2398
+ }
2399
+ )
2400
+ ]
2401
+ }
2402
+ ),
2403
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { marginBottom: 8 }, children: [
2404
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 32, fontWeight: 700, lineHeight: 1 }, children: [
2405
+ formatBRL(activeCents),
2406
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { fontSize: 16, fontWeight: 400, opacity: 0.7 }, children: "/m\xEAs" })
2407
+ ] }),
2408
+ anchorCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 14, opacity: 0.6, marginTop: 4 }, children: [
2409
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { style: { textDecoration: "line-through" }, children: formatBRL(anchorCents) }),
2410
+ anchorDiscount > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { marginLeft: 6 }, children: [
2411
+ "\u2212",
2412
+ anchorDiscount,
2413
+ "%"
2414
+ ] })
2415
+ ] }),
2416
+ cycle === "YEARLY" && yearlyCents && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { fontSize: 12, opacity: 0.6, marginTop: 4 }, children: [
2417
+ "Cobrado ",
2418
+ formatBRL(yearlyCents),
2419
+ " por ano"
2420
+ ] })
2421
+ ] }),
2422
+ /* @__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
2423
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "aria-hidden": true, style: { marginRight: 8 }, children: "\u2713" }),
2214
2424
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { children: b })
2215
2425
  ] }, 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)" }),
2426
+ /* @__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)(
2427
+ "button",
2428
+ {
2429
+ type: "button",
2430
+ role: "tab",
2431
+ "aria-selected": method === m,
2432
+ onClick: () => setMethod(m),
2433
+ style: {
2434
+ flex: 1,
2435
+ padding: 10,
2436
+ borderRadius: 8,
2437
+ border: method === m ? "2px solid var(--hook-color-primary)" : "1px solid #ccc",
2438
+ background: method === m ? "var(--hook-color-primary-soft, #eef)" : "white",
2439
+ cursor: "pointer"
2440
+ },
2441
+ children: m === "card" ? "\u{1F4B3} Cart\xE3o" : "\u{1F4F1} Pix Autom\xE1tico"
2442
+ },
2443
+ m
2444
+ )) }),
2445
+ method === "card" && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2446
+ "fieldset",
2447
+ {
2448
+ "data-testid": "paywall-card-form",
2449
+ style: { border: "none", padding: 0, marginBottom: 16 },
2450
+ children: [
2451
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8 }, children: "Dados do cart\xE3o" }),
2452
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-number", style: labelStyle, children: "N\xFAmero do cart\xE3o" }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2455
+ "input",
2456
+ {
2457
+ id: "pw-card-number",
2458
+ "data-testid": "pw-card-number",
2459
+ type: "text",
2460
+ inputMode: "numeric",
2461
+ autoComplete: "cc-number",
2462
+ placeholder: "0000 0000 0000 0000",
2463
+ value: cardNumber,
2464
+ onChange: (e) => setCardNumber(e.target.value),
2465
+ style: inputStyle
2466
+ }
2467
+ )
2468
+ ] }),
2469
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2470
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-holder", style: labelStyle, children: "Nome impresso no cart\xE3o" }),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2472
+ "input",
2473
+ {
2474
+ id: "pw-card-holder",
2475
+ "data-testid": "pw-card-holder",
2476
+ type: "text",
2477
+ autoComplete: "cc-name",
2478
+ placeholder: "NOME SOBRENOME",
2479
+ value: cardHolderName,
2480
+ onChange: (e) => setCardHolderName(e.target.value),
2481
+ style: inputStyle
2482
+ }
2483
+ )
2484
+ ] }),
2485
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2486
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2487
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-m", style: labelStyle, children: "M\xEAs" }),
2488
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2489
+ "input",
2490
+ {
2491
+ id: "pw-card-exp-m",
2492
+ "data-testid": "pw-card-exp-m",
2493
+ type: "text",
2494
+ inputMode: "numeric",
2495
+ autoComplete: "cc-exp-month",
2496
+ placeholder: "MM",
2497
+ maxLength: 2,
2498
+ value: cardExpiryMonth,
2499
+ onChange: (e) => setCardExpiryMonth(e.target.value.replace(/\D/g, "")),
2500
+ style: inputStyle
2501
+ }
2502
+ )
2503
+ ] }),
2504
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2505
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-exp-y", style: labelStyle, children: "Ano" }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2507
+ "input",
2508
+ {
2509
+ id: "pw-card-exp-y",
2510
+ "data-testid": "pw-card-exp-y",
2511
+ type: "text",
2512
+ inputMode: "numeric",
2513
+ autoComplete: "cc-exp-year",
2514
+ placeholder: "AA",
2515
+ maxLength: 4,
2516
+ value: cardExpiryYear,
2517
+ onChange: (e) => setCardExpiryYear(e.target.value.replace(/\D/g, "")),
2518
+ style: inputStyle
2519
+ }
2520
+ )
2521
+ ] }),
2522
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-card-cvv", style: labelStyle, children: "CVV" }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2525
+ "input",
2526
+ {
2527
+ id: "pw-card-cvv",
2528
+ "data-testid": "pw-card-cvv",
2529
+ type: "text",
2530
+ inputMode: "numeric",
2531
+ autoComplete: "cc-csc",
2532
+ placeholder: "123",
2533
+ maxLength: 4,
2534
+ value: cardCcv,
2535
+ onChange: (e) => setCardCcv(e.target.value.replace(/\D/g, "")),
2536
+ style: inputStyle
2537
+ }
2538
+ )
2539
+ ] })
2540
+ ] }),
2541
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("legend", { style: { fontSize: 13, opacity: 0.75, marginBottom: 8, marginTop: 8 }, children: "Dados do titular" }),
2542
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2543
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-name", style: labelStyle, children: "Nome completo" }),
2544
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2545
+ "input",
2546
+ {
2547
+ id: "pw-holder-name",
2548
+ "data-testid": "pw-holder-name",
2549
+ type: "text",
2550
+ autoComplete: "name",
2551
+ placeholder: "Nome Sobrenome",
2552
+ value: holderName,
2553
+ onChange: (e) => setHolderName(e.target.value),
2554
+ style: inputStyle
2555
+ }
2556
+ )
2557
+ ] }),
2558
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2559
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-email", style: labelStyle, children: "E-mail" }),
2560
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2561
+ "input",
2562
+ {
2563
+ id: "pw-holder-email",
2564
+ "data-testid": "pw-holder-email",
2565
+ type: "email",
2566
+ autoComplete: "email",
2567
+ placeholder: "voce@email.com",
2568
+ value: holderEmail,
2569
+ onChange: (e) => setHolderEmail(e.target.value),
2570
+ style: inputStyle
2571
+ }
2572
+ )
2573
+ ] }),
2574
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 10 }, children: [
2575
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2576
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-cep", style: labelStyle, children: "CEP" }),
2577
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2578
+ "input",
2579
+ {
2580
+ id: "pw-holder-cep",
2581
+ "data-testid": "pw-holder-cep",
2582
+ type: "text",
2583
+ inputMode: "numeric",
2584
+ autoComplete: "postal-code",
2585
+ placeholder: "00000-000",
2586
+ value: holderPostalCode,
2587
+ onChange: (e) => setHolderPostalCode(e.target.value),
2588
+ style: inputStyle
2589
+ }
2590
+ )
2591
+ ] }),
2592
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: { flex: 1, textAlign: "left" }, children: [
2593
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-addr-n", style: labelStyle, children: "N\xFAmero" }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2595
+ "input",
2596
+ {
2597
+ id: "pw-holder-addr-n",
2598
+ "data-testid": "pw-holder-addr-n",
2599
+ type: "text",
2600
+ inputMode: "numeric",
2601
+ placeholder: "123",
2602
+ value: holderAddressNumber,
2603
+ onChange: (e) => setHolderAddressNumber(e.target.value),
2604
+ style: inputStyle
2605
+ }
2606
+ )
2607
+ ] })
2608
+ ] }),
2609
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2610
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-holder-phone", style: labelStyle, children: "Telefone (opcional)" }),
2611
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2612
+ "input",
2613
+ {
2614
+ id: "pw-holder-phone",
2615
+ "data-testid": "pw-holder-phone",
2616
+ type: "tel",
2617
+ inputMode: "tel",
2618
+ autoComplete: "tel",
2619
+ placeholder: "(11) 99999-9999",
2620
+ value: holderPhone,
2621
+ onChange: (e) => setHolderPhone(e.target.value),
2622
+ style: inputStyle
2623
+ }
2624
+ )
2625
+ ] })
2626
+ ]
2627
+ }
2628
+ ),
2629
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: fieldGroup, children: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: "pw-cpf", style: labelStyle, children: "Seu CPF" }),
2218
2631
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2219
2632
  "input",
2220
2633
  {
2634
+ id: "pw-cpf",
2221
2635
  "data-testid": "paywall-cpf",
2222
2636
  type: "text",
2223
2637
  inputMode: "numeric",
2224
2638
  placeholder: "000.000.000-00",
2225
2639
  value: cpf,
2226
2640
  onChange: (e) => setCpf(e.target.value),
2227
- style: { width: "100%", padding: 10, fontSize: 14, borderRadius: 8, border: "1px solid #ccc" }
2641
+ style: inputStyle
2228
2642
  }
2229
2643
  )
2230
2644
  ] }),
@@ -2234,7 +2648,7 @@ function DefaultPaywall() {
2234
2648
  {
2235
2649
  "data-testid": "paywall-cta",
2236
2650
  type: "button",
2237
- onClick: () => void checkout({ cpf: cpfDigits }),
2651
+ onClick: submit,
2238
2652
  disabled: !canCheckout,
2239
2653
  style: {
2240
2654
  width: "100%",
@@ -2245,13 +2659,93 @@ function DefaultPaywall() {
2245
2659
  borderRadius: 8,
2246
2660
  opacity: canCheckout ? 1 : 0.5,
2247
2661
  fontSize: 16,
2248
- fontWeight: 600
2662
+ fontWeight: 600,
2663
+ cursor: canCheckout ? "pointer" : "not-allowed"
2249
2664
  },
2250
- children: opening ? "Abrindo..." : p.cta
2665
+ children: ctaLabel
2251
2666
  }
2252
2667
  ),
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 })
2668
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.55, marginTop: 16, fontSize: 12 }, children: footer }),
2669
+ p.priceHint && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.6, marginTop: 8, fontSize: 12 }, children: p.priceHint }),
2670
+ p.footerNote && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.5, marginTop: 8, fontSize: 12 }, children: p.footerNote }),
2671
+ pixPending && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2672
+ "div",
2673
+ {
2674
+ "data-testid": "paywall-pix-modal",
2675
+ role: "dialog",
2676
+ "aria-label": "Pagamento Pix pendente",
2677
+ style: {
2678
+ position: "fixed",
2679
+ inset: 0,
2680
+ background: "rgba(0,0,0,0.6)",
2681
+ display: "flex",
2682
+ alignItems: "center",
2683
+ justifyContent: "center",
2684
+ padding: 24,
2685
+ zIndex: 1e3
2686
+ },
2687
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { style: {
2688
+ background: "#fff",
2689
+ borderRadius: 12,
2690
+ padding: 24,
2691
+ maxWidth: 360,
2692
+ width: "100%",
2693
+ textAlign: "center"
2694
+ }, children: [
2695
+ pixPending.paid ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2696
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0 }, children: "Pagamento confirmado!" }),
2697
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { style: { opacity: 0.7 }, children: "Liberando acesso\u2026" })
2698
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
2699
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { style: { marginTop: 0, fontSize: 18 }, children: "Pague com Pix Autom\xE1tico" }),
2700
+ /* @__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." }),
2701
+ pixPending.qrCodeBase64 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2702
+ "img",
2703
+ {
2704
+ "data-testid": "pix-qr-image",
2705
+ alt: "QR Code Pix",
2706
+ src: `data:image/png;base64,${pixPending.qrCodeBase64}`,
2707
+ style: { width: "100%", maxWidth: 240, margin: "12px auto", display: "block" }
2708
+ }
2709
+ ),
2710
+ pixPending.qrCodePayload && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2711
+ "textarea",
2712
+ {
2713
+ "data-testid": "pix-qr-payload",
2714
+ readOnly: true,
2715
+ value: pixPending.qrCodePayload,
2716
+ style: {
2717
+ width: "100%",
2718
+ minHeight: 72,
2719
+ padding: 8,
2720
+ fontSize: 11,
2721
+ fontFamily: "monospace",
2722
+ borderRadius: 6,
2723
+ border: "1px solid #ccc",
2724
+ resize: "none"
2725
+ }
2726
+ }
2727
+ ),
2728
+ /* @__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." })
2729
+ ] }),
2730
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2731
+ "button",
2732
+ {
2733
+ type: "button",
2734
+ onClick: dismissPix,
2735
+ style: {
2736
+ marginTop: 16,
2737
+ padding: "8px 16px",
2738
+ border: "1px solid #ccc",
2739
+ borderRadius: 6,
2740
+ background: "white",
2741
+ cursor: "pointer"
2742
+ },
2743
+ children: "Fechar"
2744
+ }
2745
+ )
2746
+ ] })
2747
+ }
2748
+ )
2255
2749
  ] });
2256
2750
  }
2257
2751
 
@@ -2259,7 +2753,7 @@ function DefaultPaywall() {
2259
2753
  var import_jsx_runtime25 = require("react/jsx-runtime");
2260
2754
  var BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 4e4];
2261
2755
  function PaymentReturnHandler({ children }) {
2262
- const { subscription } = (0, import_sdk9.useHook)();
2756
+ const { subscription } = (0, import_sdk10.useHook)();
2263
2757
  const subRef = (0, import_react15.useRef)(subscription);
2264
2758
  subRef.current = subscription;
2265
2759
  const runIdRef = (0, import_react15.useRef)(0);
@@ -2368,7 +2862,7 @@ function AppRoot({
2368
2862
 
2369
2863
  // src/hooks/usePush.ts
2370
2864
  var import_react16 = require("react");
2371
- var import_sdk10 = require("@hook-sdk/sdk");
2865
+ var import_sdk11 = require("@hook-sdk/sdk");
2372
2866
  function detectIosNeedsInstall() {
2373
2867
  if (typeof navigator === "undefined" || typeof window === "undefined") return false;
2374
2868
  const ua = navigator.userAgent || "";
@@ -2394,7 +2888,7 @@ function deriveState(push) {
2394
2888
  return { kind: "prompt" };
2395
2889
  }
2396
2890
  function usePush() {
2397
- const { push } = (0, import_sdk10.useHook)();
2891
+ const { push } = (0, import_sdk11.useHook)();
2398
2892
  const [state, setState] = (0, import_react16.useState)(() => deriveState(push));
2399
2893
  (0, import_react16.useEffect)(() => {
2400
2894
  setState(deriveState(push));
@@ -2476,41 +2970,6 @@ function EmptyState({ title, description, action }) {
2476
2970
  ] });
2477
2971
  }
2478
2972
 
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
2973
  // src/hooks/useAuthPrimitives.ts
2515
2974
  var import_react17 = require("react");
2516
2975
  var import_sdk12 = require("@hook-sdk/sdk");