@appfunnel-dev/sdk 0.7.0 → 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.js CHANGED
@@ -2,10 +2,10 @@ import { useNavigation, useResponses } from './chunk-P4SLDMWY.js';
2
2
  export { useNavigation, useResponse, useResponses } from './chunk-P4SLDMWY.js';
3
3
  import { useFunnelContext } from './chunk-H3KHXZSI.js';
4
4
  export { FunnelProvider, registerIntegration } from './chunk-H3KHXZSI.js';
5
- import { forwardRef, useState, useRef, useEffect, useCallback, useImperativeHandle, useMemo, useSyncExternalStore } from 'react';
5
+ import { forwardRef, useMemo, useRef, useEffect, useCallback, useImperativeHandle, useState, useSyncExternalStore } from 'react';
6
6
  import { loadStripe } from '@stripe/stripe-js';
7
7
  import { useStripe, useElements, PaymentElement, EmbeddedCheckoutProvider, EmbeddedCheckout, Elements } from '@stripe/react-stripe-js';
8
- import { jsxs, jsx } from 'react/jsx-runtime';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
9
9
 
10
10
  // src/config.ts
11
11
  function defineConfig(config) {
@@ -140,7 +140,7 @@ function toISODateWithFormat(input, format) {
140
140
 
141
141
  // src/hooks/useUser.ts
142
142
  function useUser() {
143
- const { variableStore } = useFunnelContext();
143
+ const { variableStore, tracker } = useFunnelContext();
144
144
  const subscribe = useCallback(
145
145
  (cb) => variableStore.subscribe(cb, { prefix: "user." }),
146
146
  [variableStore]
@@ -157,6 +157,7 @@ function useUser() {
157
157
  stripeCustomerId: variables["user.stripeCustomerId"] || "",
158
158
  paddleCustomerId: variables["user.paddleCustomerId"] || "",
159
159
  dateOfBirth: variables["user.dateOfBirth"] || "",
160
+ marketingConsent: variables["user.marketingConsent"] === true,
160
161
  setEmail(email) {
161
162
  variableStore.set("user.email", email);
162
163
  },
@@ -165,9 +166,15 @@ function useUser() {
165
166
  },
166
167
  setDateOfBirth(dateOfBirth) {
167
168
  variableStore.set("user.dateOfBirth", toISODate(dateOfBirth));
169
+ },
170
+ setMarketingConsent(consent) {
171
+ variableStore.set("user.marketingConsent", consent);
172
+ },
173
+ identify(email) {
174
+ tracker.identify(email);
168
175
  }
169
176
  }),
170
- [variables, variableStore]
177
+ [variables, variableStore, tracker]
171
178
  );
172
179
  }
173
180
  function useUserProperty(field) {
@@ -341,25 +348,12 @@ function useTracking() {
341
348
  },
342
349
  [tracker]
343
350
  );
344
- const identify = useCallback(
345
- (email) => {
346
- tracker.identify(email);
347
- },
348
- [tracker]
349
- );
350
- return { track, identify };
351
+ return { track };
351
352
  }
352
- var PAYMENT_KEYS = [
353
- "card.last4",
354
- "card.brand",
355
- "card.expMonth",
356
- "card.expYear",
357
- "payment.loading",
358
- "payment.error",
359
- "payment.customerId"
360
- ];
353
+ var API_BASE_URL = "https://api.appfunnel.net";
354
+ var PAYMENT_KEYS = ["payment.loading", "payment.error"];
361
355
  function usePayment() {
362
- const { variableStore } = useFunnelContext();
356
+ const { variableStore, products, campaignId, tracker } = useFunnelContext();
363
357
  const subscribe = useCallback(
364
358
  (cb) => variableStore.subscribe(cb, { keys: PAYMENT_KEYS }),
365
359
  [variableStore]
@@ -369,20 +363,258 @@ function usePayment() {
369
363
  [variableStore]
370
364
  );
371
365
  const variables = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
372
- return useMemo(() => {
373
- const last4 = variables["card.last4"] || "";
374
- const brand = variables["card.brand"] || "";
375
- const expMonth = variables["card.expMonth"] || 0;
376
- const expYear = variables["card.expYear"] || 0;
377
- const customerId = variables["payment.customerId"] || null;
378
- return {
379
- customerId,
380
- isAuthorized: !!last4,
366
+ const purchase = useCallback(
367
+ async (productId, options) => {
368
+ console.log("[Purchase] Starting purchase for product:", productId);
369
+ if (globalThis.__APPFUNNEL_DEV__) {
370
+ console.log(
371
+ "[Purchase] Dev mode \u2014 simulating success (500ms delay)"
372
+ );
373
+ variableStore.set("payment.loading", true);
374
+ await new Promise((r) => setTimeout(r, 500));
375
+ variableStore.set("payment.error", "");
376
+ console.log("[Purchase] Dev mode \u2014 success");
377
+ options?.onSuccess?.();
378
+ return true;
379
+ }
380
+ const customerId = variableStore.get(
381
+ "user.stripeCustomerId"
382
+ );
383
+ console.log("[Purchase] Customer ID:", customerId || "(none)");
384
+ if (!customerId) {
385
+ const msg = "Please complete payment authorization first";
386
+ console.error("[Purchase] Failed:", msg);
387
+ variableStore.set("payment.error", msg);
388
+ options?.onError?.(msg);
389
+ return false;
390
+ }
391
+ const product = products.find((p) => p.id === productId);
392
+ console.log(
393
+ "[Purchase] Product found:",
394
+ product ? {
395
+ id: product.id,
396
+ name: product.name,
397
+ stripePriceId: product.stripePriceId
398
+ } : "(not found)"
399
+ );
400
+ if (!product?.stripePriceId) {
401
+ const msg = "Product not found or missing Stripe price";
402
+ console.error("[Purchase] Failed:", msg);
403
+ variableStore.set("payment.error", msg);
404
+ options?.onError?.(msg);
405
+ return false;
406
+ }
407
+ const trialPeriodDays = product.hasTrial ? product.trialDays : void 0;
408
+ const trialChargePriceId = product.paidTrial && product.trialStorePriceId ? product.trialStorePriceId : void 0;
409
+ console.log("[Purchase] Trial config:", {
410
+ trialPeriodDays,
411
+ trialChargePriceId,
412
+ hasTrial: product.hasTrial,
413
+ paidTrial: product.paidTrial
414
+ });
415
+ variableStore.set("payment.loading", true);
416
+ variableStore.set("payment.error", "");
417
+ const requestBody = {
418
+ campaignId,
419
+ sessionId: tracker.getSessionId(),
420
+ stripePriceId: product.stripePriceId,
421
+ trialPeriodDays,
422
+ trialChargePriceId
423
+ };
424
+ console.log(
425
+ "[Purchase] Sending request:",
426
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
427
+ requestBody
428
+ );
429
+ try {
430
+ const response = await fetch(
431
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
432
+ {
433
+ method: "POST",
434
+ headers: { "Content-Type": "application/json" },
435
+ body: JSON.stringify(requestBody)
436
+ }
437
+ );
438
+ console.log("[Purchase] Response status:", response.status);
439
+ let result = await response.json();
440
+ console.log("[Purchase] Response body:", result);
441
+ if (!result.success && result.requiresAction) {
442
+ console.log(
443
+ "[Purchase] 3DS authentication required, loading Stripe..."
444
+ );
445
+ const { loadStripe: loadStripe2 } = await import('@stripe/stripe-js');
446
+ const stripeInstance = await loadStripe2(
447
+ result.publishableKey
448
+ );
449
+ if (!stripeInstance) {
450
+ const msg = "Failed to load payment processor";
451
+ console.error("[Purchase] Failed:", msg);
452
+ variableStore.set("payment.error", msg);
453
+ options?.onError?.(msg);
454
+ return false;
455
+ }
456
+ console.log(
457
+ "[Purchase] Confirming card payment with 3DS..."
458
+ );
459
+ const { error: confirmError, paymentIntent: confirmedPi } = await stripeInstance.confirmCardPayment(
460
+ result.clientSecret,
461
+ {
462
+ payment_method: result.paymentMethodId
463
+ }
464
+ );
465
+ if (confirmError || !confirmedPi || confirmedPi.status !== "succeeded") {
466
+ const msg = confirmError?.message || "Payment authentication failed";
467
+ console.error("[Purchase] 3DS failed:", msg);
468
+ variableStore.set("payment.error", msg);
469
+ options?.onError?.(msg);
470
+ return false;
471
+ }
472
+ console.log(
473
+ "[Purchase] 3DS succeeded, retrying purchase with confirmed PI:",
474
+ confirmedPi.id
475
+ );
476
+ const retryResponse = await fetch(
477
+ `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
478
+ {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify({
482
+ campaignId,
483
+ sessionId: tracker.getSessionId(),
484
+ stripePriceId: product.stripePriceId,
485
+ trialPeriodDays,
486
+ trialChargePriceId,
487
+ onSessionPiId: confirmedPi.id
488
+ })
489
+ }
490
+ );
491
+ result = await retryResponse.json();
492
+ console.log("[Purchase] Retry response:", result);
493
+ }
494
+ if (result.success) {
495
+ console.log("[Purchase] Success! Type:", result.type);
496
+ variableStore.set("payment.error", "");
497
+ if (result.type === "validate_only") {
498
+ console.log(
499
+ "[Purchase] Validate-only \u2014 setting card variables"
500
+ );
501
+ if (result.card) {
502
+ variableStore.setMany({
503
+ "card.last4": result.card.last4,
504
+ "card.brand": result.card.brand,
505
+ "card.expMonth": result.card.expMonth,
506
+ "card.expYear": result.card.expYear,
507
+ "card.funding": result.card.funding
508
+ });
509
+ }
510
+ if (result.eventId) {
511
+ console.log(
512
+ "[Purchase] Tracking purchase.complete (validate_only), eventId:",
513
+ result.eventId
514
+ );
515
+ tracker.track("purchase.complete", {
516
+ eventId: result.eventId,
517
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
518
+ currency: product.currencyCode || "USD"
519
+ });
520
+ }
521
+ } else if (result.type === "one_time") {
522
+ console.log(
523
+ "[Purchase] One-time charge \u2014 paymentIntentId:",
524
+ result.paymentIntentId
525
+ );
526
+ variableStore.set(
527
+ "stripe.paymentIntentId",
528
+ result.paymentIntentId
529
+ );
530
+ variableStore.set("payment.status", result.status);
531
+ if (result.eventId) {
532
+ console.log(
533
+ "[Purchase] Tracking purchase.complete (one_time), eventId:",
534
+ result.eventId
535
+ );
536
+ tracker.track("purchase.complete", {
537
+ eventId: result.eventId,
538
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
539
+ currency: product.currencyCode || "USD"
540
+ });
541
+ }
542
+ } else {
543
+ console.log(
544
+ "[Purchase] Subscription \u2014 subscriptionId:",
545
+ result.subscriptionId,
546
+ "status:",
547
+ result.status
548
+ );
549
+ variableStore.set(
550
+ "stripe.subscriptionId",
551
+ result.subscriptionId
552
+ );
553
+ variableStore.set("subscription.status", result.status);
554
+ if (result.trialCharge) {
555
+ console.log(
556
+ "[Purchase] Tracking trial charge purchase.complete"
557
+ );
558
+ tracker.track("purchase.complete", {
559
+ eventId: result.eventIds?.trialCharge,
560
+ amount: result.trialCharge.amount / 100,
561
+ currency: "USD"
562
+ });
563
+ }
564
+ if (result.eventIds?.subscription) {
565
+ console.log(
566
+ "[Purchase] Tracking subscription.created, eventId:",
567
+ result.eventIds.subscription
568
+ );
569
+ tracker.track("subscription.created", {
570
+ eventId: result.eventIds.subscription,
571
+ subscriptionId: result.subscriptionId,
572
+ status: result.status
573
+ });
574
+ }
575
+ if (result.eventIds?.firstPeriod) {
576
+ console.log(
577
+ "[Purchase] Tracking first-period purchase.complete, eventId:",
578
+ result.eventIds.firstPeriod
579
+ );
580
+ tracker.track("purchase.complete", {
581
+ eventId: result.eventIds.firstPeriod,
582
+ amount: product.rawPrice ? product.rawPrice / 100 : 0,
583
+ currency: product.currencyCode || "USD"
584
+ });
585
+ }
586
+ }
587
+ console.log("[Purchase] Calling onSuccess callback");
588
+ options?.onSuccess?.();
589
+ return true;
590
+ } else {
591
+ const msg = result.error || "Purchase failed";
592
+ console.error("[Purchase] Failed:", msg);
593
+ variableStore.set("payment.error", msg);
594
+ options?.onError?.(msg);
595
+ return false;
596
+ }
597
+ } catch (err) {
598
+ const msg = err instanceof Error ? err.message : "Purchase failed";
599
+ console.error("[Purchase] Exception:", err);
600
+ variableStore.set("payment.error", msg);
601
+ options?.onError?.(msg);
602
+ return false;
603
+ } finally {
604
+ console.log("[Purchase] Done \u2014 setting loading to false");
605
+ variableStore.set("payment.loading", false);
606
+ }
607
+ },
608
+ [variableStore, products, campaignId, tracker]
609
+ );
610
+ return useMemo(
611
+ () => ({
381
612
  loading: !!variables["payment.loading"],
382
613
  error: variables["payment.error"] || null,
383
- cardDetails: last4 ? { last4, brand, expMonth, expYear } : null
384
- };
385
- }, [variables]);
614
+ purchase
615
+ }),
616
+ [variables, purchase]
617
+ );
386
618
  }
387
619
  var DEVICE_KEYS = [
388
620
  "os.name",
@@ -575,7 +807,456 @@ function useFunnel() {
575
807
  payment: usePayment()
576
808
  };
577
809
  }
578
- var API_BASE_URL = "https://api.appfunnel.net";
810
+ var FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
811
+ var inputStyle = (focused) => ({
812
+ width: "100%",
813
+ padding: "12px",
814
+ border: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
815
+ borderRadius: "6px",
816
+ fontSize: "14px",
817
+ fontFamily: FONT,
818
+ outline: "none",
819
+ backgroundColor: "#fff",
820
+ boxSizing: "border-box",
821
+ transition: "border-color 0.15s"
822
+ });
823
+ var labelStyle = {
824
+ display: "block",
825
+ fontSize: "14px",
826
+ fontWeight: 500,
827
+ color: "#333",
828
+ marginBottom: "6px",
829
+ fontFamily: FONT
830
+ };
831
+ function CardBrandBadges() {
832
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "4px", alignItems: "center" }, children: [
833
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#1a1f71", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "VISA" }),
834
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#eb001b", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "MC" }),
835
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "10px", fontWeight: 700, color: "#ff6000", background: "#fff", border: "1px solid #e0e0e0", borderRadius: "3px", padding: "2px 4px", fontFamily: FONT }, children: "DISC" })
836
+ ] });
837
+ }
838
+ function CvcIcon() {
839
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "16", viewBox: "0 0 20 16", fill: "none", style: { opacity: 0.4 }, children: [
840
+ /* @__PURE__ */ jsx("rect", { x: "0.5", y: "0.5", width: "19", height: "15", rx: "2", stroke: "#888" }),
841
+ /* @__PURE__ */ jsx("rect", { x: "0", y: "4", width: "20", height: "3", fill: "#888" }),
842
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "10", width: "8", height: "2", rx: "1", fill: "#ccc" }),
843
+ /* @__PURE__ */ jsx("text", { x: "14", y: "12", fontSize: "6", fill: "#888", fontFamily: FONT, children: "123" })
844
+ ] });
845
+ }
846
+ function DemoElementsForm({ onReady }) {
847
+ const [cardNumber, setCardNumber] = useState("");
848
+ const [expiry, setExpiry] = useState("");
849
+ const [cvc, setCvc] = useState("");
850
+ const [country, setCountry] = useState("Denmark");
851
+ const [focusedField, setFocusedField] = useState(null);
852
+ const readyFired = useRef(false);
853
+ useEffect(() => {
854
+ if (!readyFired.current) {
855
+ readyFired.current = true;
856
+ onReady?.();
857
+ }
858
+ }, [onReady]);
859
+ const formatCardNumber = (val) => {
860
+ const digits = val.replace(/\D/g, "").slice(0, 16);
861
+ return digits.replace(/(.{4})/g, "$1 ").trim();
862
+ };
863
+ const formatExpiry = (val) => {
864
+ const digits = val.replace(/\D/g, "").slice(0, 4);
865
+ if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
866
+ return digits;
867
+ };
868
+ return /* @__PURE__ */ jsxs("div", { style: { fontFamily: FONT }, children: [
869
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
870
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Card number" }),
871
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
872
+ /* @__PURE__ */ jsx(
873
+ "input",
874
+ {
875
+ type: "text",
876
+ value: cardNumber,
877
+ onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
878
+ onFocus: () => setFocusedField("card"),
879
+ onBlur: () => setFocusedField(null),
880
+ placeholder: "1234 1234 1234 1234",
881
+ style: inputStyle(focusedField === "card")
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
885
+ ] })
886
+ ] }),
887
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "12px", marginBottom: "14px" }, children: [
888
+ /* @__PURE__ */ jsxs("div", { children: [
889
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Expiry date" }),
890
+ /* @__PURE__ */ jsx(
891
+ "input",
892
+ {
893
+ type: "text",
894
+ value: expiry,
895
+ onChange: (e) => setExpiry(formatExpiry(e.target.value)),
896
+ onFocus: () => setFocusedField("expiry"),
897
+ onBlur: () => setFocusedField(null),
898
+ placeholder: "MM / YY",
899
+ style: inputStyle(focusedField === "expiry")
900
+ }
901
+ )
902
+ ] }),
903
+ /* @__PURE__ */ jsxs("div", { children: [
904
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Security code" }),
905
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
906
+ /* @__PURE__ */ jsx(
907
+ "input",
908
+ {
909
+ type: "text",
910
+ value: cvc,
911
+ onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
912
+ onFocus: () => setFocusedField("cvc"),
913
+ onBlur: () => setFocusedField(null),
914
+ placeholder: "CVC",
915
+ style: inputStyle(focusedField === "cvc")
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
919
+ ] })
920
+ ] })
921
+ ] }),
922
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
923
+ /* @__PURE__ */ jsx("label", { style: labelStyle, children: "Country" }),
924
+ /* @__PURE__ */ jsxs(
925
+ "select",
926
+ {
927
+ value: country,
928
+ onChange: (e) => setCountry(e.target.value),
929
+ onFocus: () => setFocusedField("country"),
930
+ onBlur: () => setFocusedField(null),
931
+ style: {
932
+ ...inputStyle(focusedField === "country"),
933
+ appearance: "none",
934
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
935
+ backgroundRepeat: "no-repeat",
936
+ backgroundPosition: "right 12px center",
937
+ paddingRight: "32px"
938
+ },
939
+ children: [
940
+ /* @__PURE__ */ jsx("option", { children: "Australia" }),
941
+ /* @__PURE__ */ jsx("option", { children: "Austria" }),
942
+ /* @__PURE__ */ jsx("option", { children: "Belgium" }),
943
+ /* @__PURE__ */ jsx("option", { children: "Brazil" }),
944
+ /* @__PURE__ */ jsx("option", { children: "Canada" }),
945
+ /* @__PURE__ */ jsx("option", { children: "Denmark" }),
946
+ /* @__PURE__ */ jsx("option", { children: "Finland" }),
947
+ /* @__PURE__ */ jsx("option", { children: "France" }),
948
+ /* @__PURE__ */ jsx("option", { children: "Germany" }),
949
+ /* @__PURE__ */ jsx("option", { children: "Ireland" }),
950
+ /* @__PURE__ */ jsx("option", { children: "Italy" }),
951
+ /* @__PURE__ */ jsx("option", { children: "Japan" }),
952
+ /* @__PURE__ */ jsx("option", { children: "Netherlands" }),
953
+ /* @__PURE__ */ jsx("option", { children: "New Zealand" }),
954
+ /* @__PURE__ */ jsx("option", { children: "Norway" }),
955
+ /* @__PURE__ */ jsx("option", { children: "Poland" }),
956
+ /* @__PURE__ */ jsx("option", { children: "Portugal" }),
957
+ /* @__PURE__ */ jsx("option", { children: "Spain" }),
958
+ /* @__PURE__ */ jsx("option", { children: "Sweden" }),
959
+ /* @__PURE__ */ jsx("option", { children: "Switzerland" }),
960
+ /* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
961
+ /* @__PURE__ */ jsx("option", { children: "United States" })
962
+ ]
963
+ }
964
+ )
965
+ ] }),
966
+ /* @__PURE__ */ jsx("p", { style: { fontSize: "12px", color: "#6b7280", lineHeight: 1.5, margin: 0, fontFamily: FONT }, children: "By providing your card information, you allow the merchant to charge your card for future payments in accordance with their terms." })
967
+ ] });
968
+ }
969
+ function DemoEmbeddedForm({ product, onReady }) {
970
+ const readyFired = useRef(false);
971
+ const [email, setEmail] = useState("");
972
+ const [cardNumber, setCardNumber] = useState("");
973
+ const [expiry, setExpiry] = useState("");
974
+ const [cvc, setCvc] = useState("");
975
+ const [name, setName] = useState("");
976
+ const [country, setCountry] = useState("Denmark");
977
+ const [focusedField, setFocusedField] = useState(null);
978
+ useEffect(() => {
979
+ if (!readyFired.current) {
980
+ readyFired.current = true;
981
+ onReady?.();
982
+ }
983
+ }, [onReady]);
984
+ const formatCardNumber = (val) => {
985
+ const digits = val.replace(/\D/g, "").slice(0, 16);
986
+ return digits.replace(/(.{4})/g, "$1 ").trim();
987
+ };
988
+ const formatExpiry = (val) => {
989
+ const digits = val.replace(/\D/g, "").slice(0, 4);
990
+ if (digits.length > 2) return digits.slice(0, 2) + " / " + digits.slice(2);
991
+ return digits;
992
+ };
993
+ const embeddedInputStyle = (focused) => ({
994
+ width: "100%",
995
+ padding: "10px 12px",
996
+ border: "none",
997
+ borderBottom: `1px solid ${focused ? "#666" : "#e0e0e0"}`,
998
+ fontSize: "14px",
999
+ fontFamily: FONT,
1000
+ outline: "none",
1001
+ backgroundColor: "#fff",
1002
+ boxSizing: "border-box"
1003
+ });
1004
+ const displayPrice = product?.hasTrial ? product.trialPrice : product?.price;
1005
+ return /* @__PURE__ */ jsxs("div", { style: { fontFamily: FONT, border: "1px solid #e0e0e0", borderRadius: "12px", overflow: "hidden", background: "#fff" }, children: [
1006
+ product && /* @__PURE__ */ jsxs("div", { style: { padding: "24px 20px", borderBottom: "1px solid #e0e0e0" }, children: [
1007
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
1008
+ "Pay ",
1009
+ product.displayName || product.name
1010
+ ] }),
1011
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: "28px", fontWeight: 700, color: "#333", marginBottom: "4px" }, children: [
1012
+ product.currencyCode?.toUpperCase(),
1013
+ displayPrice
1014
+ ] }),
1015
+ product.hasTrial && /* @__PURE__ */ jsxs("div", { style: { fontSize: "13px", color: "#666" }, children: [
1016
+ "Then ",
1017
+ /* @__PURE__ */ jsxs("span", { style: { textDecoration: "underline" }, children: [
1018
+ product.currencyCode?.toUpperCase(),
1019
+ product.price
1020
+ ] }),
1021
+ " every ",
1022
+ product.period
1023
+ ] }),
1024
+ /* @__PURE__ */ jsx(
1025
+ "button",
1026
+ {
1027
+ type: "button",
1028
+ style: {
1029
+ marginTop: "12px",
1030
+ padding: "6px 14px",
1031
+ borderRadius: "6px",
1032
+ border: "1px solid #e0e0e0",
1033
+ backgroundColor: "#fff",
1034
+ color: "#333",
1035
+ fontSize: "13px",
1036
+ cursor: "default",
1037
+ fontFamily: FONT
1038
+ },
1039
+ children: "View details \u25BE"
1040
+ }
1041
+ )
1042
+ ] }),
1043
+ /* @__PURE__ */ jsxs("div", { style: { padding: "20px" }, children: [
1044
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1045
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Email" }),
1046
+ /* @__PURE__ */ jsx(
1047
+ "input",
1048
+ {
1049
+ type: "email",
1050
+ value: email,
1051
+ onChange: (e) => setEmail(e.target.value),
1052
+ onFocus: () => setFocusedField("email"),
1053
+ onBlur: () => setFocusedField(null),
1054
+ placeholder: "you@example.com",
1055
+ style: embeddedInputStyle(focusedField === "email")
1056
+ }
1057
+ )
1058
+ ] }),
1059
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1060
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Card information" }),
1061
+ /* @__PURE__ */ jsxs("div", { style: { border: "1px solid #e0e0e0", borderRadius: "6px", overflow: "hidden" }, children: [
1062
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1063
+ /* @__PURE__ */ jsx(
1064
+ "input",
1065
+ {
1066
+ type: "text",
1067
+ value: cardNumber,
1068
+ onChange: (e) => setCardNumber(formatCardNumber(e.target.value)),
1069
+ onFocus: () => setFocusedField("card"),
1070
+ onBlur: () => setFocusedField(null),
1071
+ placeholder: "1234 1234 1234 1234",
1072
+ style: { ...embeddedInputStyle(focusedField === "card"), padding: "12px" }
1073
+ }
1074
+ ),
1075
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CardBrandBadges, {}) })
1076
+ ] }),
1077
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr" }, children: [
1078
+ /* @__PURE__ */ jsx(
1079
+ "input",
1080
+ {
1081
+ type: "text",
1082
+ value: expiry,
1083
+ onChange: (e) => setExpiry(formatExpiry(e.target.value)),
1084
+ onFocus: () => setFocusedField("expiry"),
1085
+ onBlur: () => setFocusedField(null),
1086
+ placeholder: "MM / YY",
1087
+ style: { ...embeddedInputStyle(focusedField === "expiry"), borderRight: "1px solid #e0e0e0", padding: "12px" }
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
1091
+ /* @__PURE__ */ jsx(
1092
+ "input",
1093
+ {
1094
+ type: "text",
1095
+ value: cvc,
1096
+ onChange: (e) => setCvc(e.target.value.replace(/\D/g, "").slice(0, 4)),
1097
+ onFocus: () => setFocusedField("cvc"),
1098
+ onBlur: () => setFocusedField(null),
1099
+ placeholder: "CVC",
1100
+ style: { ...embeddedInputStyle(focusedField === "cvc"), padding: "12px" }
1101
+ }
1102
+ ),
1103
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", right: "12px", top: "50%", transform: "translateY(-50%)" }, children: /* @__PURE__ */ jsx(CvcIcon, {}) })
1104
+ ] })
1105
+ ] })
1106
+ ] })
1107
+ ] }),
1108
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
1109
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Cardholder name" }),
1110
+ /* @__PURE__ */ jsx(
1111
+ "input",
1112
+ {
1113
+ type: "text",
1114
+ value: name,
1115
+ onChange: (e) => setName(e.target.value),
1116
+ onFocus: () => setFocusedField("name"),
1117
+ onBlur: () => setFocusedField(null),
1118
+ placeholder: "Full name on card",
1119
+ style: inputStyle(focusedField === "name")
1120
+ }
1121
+ )
1122
+ ] }),
1123
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "20px" }, children: [
1124
+ /* @__PURE__ */ jsx("label", { style: { ...labelStyle, fontSize: "13px", color: "#666" }, children: "Country or region" }),
1125
+ /* @__PURE__ */ jsxs(
1126
+ "select",
1127
+ {
1128
+ value: country,
1129
+ onChange: (e) => setCountry(e.target.value),
1130
+ onFocus: () => setFocusedField("country"),
1131
+ onBlur: () => setFocusedField(null),
1132
+ style: {
1133
+ ...inputStyle(focusedField === "country"),
1134
+ appearance: "none",
1135
+ backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23666' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E")`,
1136
+ backgroundRepeat: "no-repeat",
1137
+ backgroundPosition: "right 12px center",
1138
+ paddingRight: "32px"
1139
+ },
1140
+ children: [
1141
+ /* @__PURE__ */ jsx("option", { children: "Australia" }),
1142
+ /* @__PURE__ */ jsx("option", { children: "Austria" }),
1143
+ /* @__PURE__ */ jsx("option", { children: "Belgium" }),
1144
+ /* @__PURE__ */ jsx("option", { children: "Brazil" }),
1145
+ /* @__PURE__ */ jsx("option", { children: "Canada" }),
1146
+ /* @__PURE__ */ jsx("option", { children: "Denmark" }),
1147
+ /* @__PURE__ */ jsx("option", { children: "Finland" }),
1148
+ /* @__PURE__ */ jsx("option", { children: "France" }),
1149
+ /* @__PURE__ */ jsx("option", { children: "Germany" }),
1150
+ /* @__PURE__ */ jsx("option", { children: "Ireland" }),
1151
+ /* @__PURE__ */ jsx("option", { children: "Italy" }),
1152
+ /* @__PURE__ */ jsx("option", { children: "Japan" }),
1153
+ /* @__PURE__ */ jsx("option", { children: "Netherlands" }),
1154
+ /* @__PURE__ */ jsx("option", { children: "New Zealand" }),
1155
+ /* @__PURE__ */ jsx("option", { children: "Norway" }),
1156
+ /* @__PURE__ */ jsx("option", { children: "Poland" }),
1157
+ /* @__PURE__ */ jsx("option", { children: "Portugal" }),
1158
+ /* @__PURE__ */ jsx("option", { children: "Spain" }),
1159
+ /* @__PURE__ */ jsx("option", { children: "Sweden" }),
1160
+ /* @__PURE__ */ jsx("option", { children: "Switzerland" }),
1161
+ /* @__PURE__ */ jsx("option", { children: "United Kingdom" }),
1162
+ /* @__PURE__ */ jsx("option", { children: "United States" })
1163
+ ]
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ /* @__PURE__ */ jsx(
1168
+ "button",
1169
+ {
1170
+ type: "button",
1171
+ disabled: true,
1172
+ style: {
1173
+ width: "100%",
1174
+ padding: "14px",
1175
+ borderRadius: "6px",
1176
+ border: "none",
1177
+ backgroundColor: "#0570de",
1178
+ color: "#fff",
1179
+ fontSize: "16px",
1180
+ fontWeight: 600,
1181
+ cursor: "default",
1182
+ fontFamily: FONT,
1183
+ opacity: 0.7
1184
+ },
1185
+ children: "Subscribe"
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", marginTop: "12px", fontSize: "12px", color: "#aaa" }, children: [
1189
+ "Powered by ",
1190
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 700, letterSpacing: "-0.3px" }, children: "stripe" })
1191
+ ] })
1192
+ ] })
1193
+ ] });
1194
+ }
1195
+ var DemoStripePaymentForm = forwardRef(
1196
+ function DemoStripePaymentForm2({
1197
+ productId,
1198
+ mode = "checkout",
1199
+ variant = "elements",
1200
+ onSuccess,
1201
+ onError,
1202
+ onReady,
1203
+ className
1204
+ }, ref) {
1205
+ const { variableStore, tracker, products } = useFunnelContext();
1206
+ const validateOnly = mode === "validate-only";
1207
+ const product = useMemo(() => {
1208
+ if (productId) return products.find((p) => p.id === productId) || null;
1209
+ const selectedId = variableStore.get("products.selectedProductId");
1210
+ return products.find((p) => p.id === selectedId) || null;
1211
+ }, [productId, products, variableStore]);
1212
+ const hasFiredStart = useRef(false);
1213
+ useEffect(() => {
1214
+ if (hasFiredStart.current) return;
1215
+ hasFiredStart.current = true;
1216
+ variableStore.set("payment.loading", false);
1217
+ tracker.track("checkout.start", {
1218
+ productId: product?.id,
1219
+ priceId: product?.stripePriceId || product?.storePriceId,
1220
+ productName: product?.displayName
1221
+ });
1222
+ }, [product, tracker, variableStore]);
1223
+ const handleSubmit = useCallback(async () => {
1224
+ variableStore.set("payment.loading", true);
1225
+ await new Promise((r) => setTimeout(r, 800));
1226
+ try {
1227
+ tracker.track("checkout.payment_added");
1228
+ if (validateOnly) {
1229
+ variableStore.setMany({
1230
+ "card.last4": "4242",
1231
+ "card.brand": "visa",
1232
+ "card.expMonth": 12,
1233
+ "card.expYear": 2030,
1234
+ "card.funding": "credit",
1235
+ "payment.error": ""
1236
+ });
1237
+ onSuccess?.();
1238
+ return;
1239
+ }
1240
+ variableStore.set("payment.error", "");
1241
+ tracker.track("purchase.complete", {
1242
+ amount: product?.rawPrice ? product.rawPrice / 100 : 0,
1243
+ currency: product?.currencyCode || "USD"
1244
+ });
1245
+ onSuccess?.();
1246
+ } catch (err) {
1247
+ const msg = err instanceof Error ? err.message : "An error occurred";
1248
+ variableStore.set("payment.error", msg);
1249
+ onError?.(msg);
1250
+ } finally {
1251
+ variableStore.set("payment.loading", false);
1252
+ }
1253
+ }, [validateOnly, variableStore, tracker, product, onSuccess, onError]);
1254
+ useImperativeHandle(ref, () => ({ submit: handleSubmit }), [handleSubmit]);
1255
+ return /* @__PURE__ */ jsx("div", { className, children: variant === "embedded" ? /* @__PURE__ */ jsx(DemoEmbeddedForm, { product, onReady }) : /* @__PURE__ */ jsx(DemoElementsForm, { onReady }) });
1256
+ }
1257
+ );
1258
+ var API_BASE_URL2 = "https://api.appfunnel.net";
1259
+ var isDevMode = () => typeof globalThis !== "undefined" && !!globalThis.__APPFUNNEL_DEV__;
579
1260
  var InnerPaymentForm = forwardRef(
580
1261
  function InnerPaymentForm2({ paymentMode, validateOnly, onSuccess, onError, onReady, layout }, ref) {
581
1262
  const stripe = useStripe();
@@ -623,7 +1304,7 @@ var InnerPaymentForm = forwardRef(
623
1304
  return;
624
1305
  }
625
1306
  const response2 = await fetch(
626
- `${API_BASE_URL}/campaign/${campaignId}/stripe/validate-card`,
1307
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/validate-card`,
627
1308
  {
628
1309
  method: "POST",
629
1310
  headers: { "Content-Type": "application/json" },
@@ -664,7 +1345,7 @@ var InnerPaymentForm = forwardRef(
664
1345
  return;
665
1346
  }
666
1347
  const response = await fetch(
667
- `${API_BASE_URL}/campaign/${campaignId}/stripe/purchase`,
1348
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/purchase`,
668
1349
  {
669
1350
  method: "POST",
670
1351
  headers: { "Content-Type": "application/json" },
@@ -709,11 +1390,15 @@ var InnerPaymentForm = forwardRef(
709
1390
  ] });
710
1391
  }
711
1392
  );
712
- var StripePaymentForm = forwardRef(
713
- function StripePaymentForm2({
1393
+ var RealStripePaymentForm = forwardRef(
1394
+ function RealStripePaymentForm2({
714
1395
  productId,
715
1396
  mode = "checkout",
716
1397
  variant = "elements",
1398
+ successPageKey,
1399
+ automaticTax = false,
1400
+ managedPayments = false,
1401
+ allowPromotionCodes = false,
717
1402
  onSuccess,
718
1403
  onError,
719
1404
  onReady,
@@ -721,7 +1406,7 @@ var StripePaymentForm = forwardRef(
721
1406
  appearance,
722
1407
  layout
723
1408
  }, ref) {
724
- const { campaignId, tracker, variableStore, products } = useFunnelContext();
1409
+ const { campaignId, tracker, variableStore, products, router } = useFunnelContext();
725
1410
  const [email] = useVariable("user.email");
726
1411
  const validateOnly = mode === "validate-only";
727
1412
  const product = useMemo(() => {
@@ -748,18 +1433,36 @@ var StripePaymentForm = forwardRef(
748
1433
  const createIntent = async () => {
749
1434
  try {
750
1435
  if (variant === "embedded") {
1436
+ const origin = typeof window !== "undefined" ? window.location.origin : "";
1437
+ let returnUrl = origin + "/";
1438
+ if (successPageKey) {
1439
+ const pageUrl = router.getPageUrl(successPageKey);
1440
+ returnUrl = `${origin}${pageUrl}?checkout=success&session_id={CHECKOUT_SESSION_ID}`;
1441
+ }
1442
+ const apiCheckoutMode = managedPayments ? "embedded" : variant;
1443
+ const body = {
1444
+ campaignId,
1445
+ sessionId: tracker.getSessionId(),
1446
+ customerEmail: email,
1447
+ priceId: product.storePriceId,
1448
+ returnUrl,
1449
+ checkoutMode: apiCheckoutMode,
1450
+ automaticTax,
1451
+ managedPayments,
1452
+ allowPromotionCodes
1453
+ };
1454
+ if (product.hasTrial && product.trialDays > 0) {
1455
+ body.trialPeriodDays = product.trialDays;
1456
+ if (product.paidTrial && product.trialStorePriceId) {
1457
+ body.paidTrialPriceId = product.trialStorePriceId;
1458
+ }
1459
+ }
751
1460
  const response = await fetch(
752
- `${API_BASE_URL}/campaign/${campaignId}/stripe/checkout-session`,
1461
+ `${API_BASE_URL2}/campaign/${campaignId}/stripe/checkout-session`,
753
1462
  {
754
1463
  method: "POST",
755
1464
  headers: { "Content-Type": "application/json" },
756
- body: JSON.stringify({
757
- campaignId,
758
- sessionId: tracker.getSessionId(),
759
- customerEmail: email,
760
- priceId: product.storePriceId,
761
- trialPeriodDays: product.hasTrial ? product.trialDays : void 0
762
- })
1465
+ body: JSON.stringify(body)
763
1466
  }
764
1467
  );
765
1468
  const result = await response.json();
@@ -775,7 +1478,7 @@ var StripePaymentForm = forwardRef(
775
1478
  priceId: product.storePriceId
776
1479
  };
777
1480
  if (validateOnly) body.validateOnly = true;
778
- const response = await fetch(`${API_BASE_URL}${endpoint}`, {
1481
+ const response = await fetch(`${API_BASE_URL2}${endpoint}`, {
779
1482
  method: "POST",
780
1483
  headers: { "Content-Type": "application/json" },
781
1484
  body: JSON.stringify(body)
@@ -801,7 +1504,7 @@ var StripePaymentForm = forwardRef(
801
1504
  }
802
1505
  };
803
1506
  createIntent();
804
- }, [email, campaignId, product, paymentMode, validateOnly, variant, tracker, variableStore]);
1507
+ }, [email, campaignId, product, paymentMode, validateOnly, variant, successPageKey, automaticTax, managedPayments, allowPromotionCodes, router, tracker, variableStore]);
805
1508
  if (isLoading) {
806
1509
  return /* @__PURE__ */ jsx("div", { className, style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "Loading payment form..." });
807
1510
  }
@@ -819,16 +1522,7 @@ var StripePaymentForm = forwardRef(
819
1522
  EmbeddedCheckoutProvider,
820
1523
  {
821
1524
  stripe: stripePromise,
822
- options: {
823
- clientSecret,
824
- onComplete: () => {
825
- tracker.track("purchase.complete", {
826
- amount: product?.rawPrice ? product.rawPrice / 100 : 0,
827
- currency: product?.currencyCode || "USD"
828
- });
829
- onSuccess?.();
830
- }
831
- },
1525
+ options: { clientSecret },
832
1526
  children: /* @__PURE__ */ jsx(EmbeddedCheckout, {})
833
1527
  }
834
1528
  ) });
@@ -851,6 +1545,14 @@ var StripePaymentForm = forwardRef(
851
1545
  ) }) });
852
1546
  }
853
1547
  );
1548
+ var StripePaymentForm = forwardRef(
1549
+ function StripePaymentForm2(props, ref) {
1550
+ if (isDevMode()) {
1551
+ return /* @__PURE__ */ jsx(DemoStripePaymentForm, { ref, ...props });
1552
+ }
1553
+ return /* @__PURE__ */ jsx(RealStripePaymentForm, { ref, ...props });
1554
+ }
1555
+ );
854
1556
  function PaddleCheckout({
855
1557
  productId,
856
1558
  mode = "overlay",