@virex-tech/paywallo-sdk 2.5.1 → 2.5.3
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/README.md +23 -2
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +14 -2
- package/dist/index.d.mts +41 -30
- package/dist/index.d.ts +41 -30
- package/dist/index.js +1091 -806
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +914 -629
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloAPNSProxy.m +138 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -348,13 +348,13 @@ function getCachedDeviceInfo() {
|
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
-
var import_react_native2,
|
|
351
|
+
var import_react_native2, Application, Device, Localization, _debug2, cachedDeviceInfo, nativeDeviceInfo;
|
|
352
352
|
var init_NativeDeviceInfo = __esm({
|
|
353
353
|
"src/utils/NativeDeviceInfo.ts"() {
|
|
354
354
|
"use strict";
|
|
355
355
|
import_react_native2 = require("react-native");
|
|
356
|
-
Device = __toESM(require("expo-device"));
|
|
357
356
|
Application = __toESM(require("expo-application"));
|
|
357
|
+
Device = __toESM(require("expo-device"));
|
|
358
358
|
Localization = __toESM(require("expo-localization"));
|
|
359
359
|
init_ClientError();
|
|
360
360
|
_debug2 = false;
|
|
@@ -968,7 +968,9 @@ var init_AdvertisingIdManager = __esm({
|
|
|
968
968
|
return this.cached;
|
|
969
969
|
}
|
|
970
970
|
async collect(requestATT = false) {
|
|
971
|
-
if (this.cached
|
|
971
|
+
if (this.cached && !(requestATT && this.cached.attStatus === "undetermined")) {
|
|
972
|
+
return this.cached;
|
|
973
|
+
}
|
|
972
974
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
973
975
|
try {
|
|
974
976
|
const tracking = await import("expo-tracking-transparency");
|
|
@@ -1391,6 +1393,7 @@ var init_AttributionTracker = __esm({
|
|
|
1391
1393
|
constructor(debug = false) {
|
|
1392
1394
|
this.cache = null;
|
|
1393
1395
|
this.captureInProgress = false;
|
|
1396
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1394
1397
|
this.storage = new SecureStorage(debug);
|
|
1395
1398
|
}
|
|
1396
1399
|
/**
|
|
@@ -1430,10 +1433,31 @@ var init_AttributionTracker = __esm({
|
|
|
1430
1433
|
};
|
|
1431
1434
|
await this.storage.set(ATTRIBUTION_STORAGE_KEY, JSON.stringify(capture));
|
|
1432
1435
|
this.cache = capture;
|
|
1436
|
+
this.notifyListeners(capture);
|
|
1433
1437
|
} finally {
|
|
1434
1438
|
this.captureInProgress = false;
|
|
1435
1439
|
}
|
|
1436
1440
|
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Subscribes to the first real attribution write (first-write-wins — no-op
|
|
1443
|
+
* captures never fire). Useful for late enrichment consumers (e.g. re-push
|
|
1444
|
+
* of Superwall attributes when a warm deep link / deferred match lands after
|
|
1445
|
+
* init). Returns an unsubscribe function.
|
|
1446
|
+
*/
|
|
1447
|
+
onCapture(listener) {
|
|
1448
|
+
this.listeners.add(listener);
|
|
1449
|
+
return () => {
|
|
1450
|
+
this.listeners.delete(listener);
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
notifyListeners(capture) {
|
|
1454
|
+
for (const listener of this.listeners) {
|
|
1455
|
+
try {
|
|
1456
|
+
listener(capture);
|
|
1457
|
+
} catch {
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1437
1461
|
/**
|
|
1438
1462
|
* Returns the persisted attribution capture, or `null` if none exists.
|
|
1439
1463
|
* Synchronous — reads from in-memory cache (populated via `loadFromStorage()`).
|
|
@@ -1527,6 +1551,68 @@ var init_InstallIdempotency = __esm({
|
|
|
1527
1551
|
}
|
|
1528
1552
|
});
|
|
1529
1553
|
|
|
1554
|
+
// src/domains/identity/deferredAppLink.ts
|
|
1555
|
+
function parseQuery(s) {
|
|
1556
|
+
const result = {};
|
|
1557
|
+
for (const pair of s.split("&")) {
|
|
1558
|
+
const eqIndex = pair.indexOf("=");
|
|
1559
|
+
if (eqIndex === -1) continue;
|
|
1560
|
+
try {
|
|
1561
|
+
const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
|
|
1562
|
+
const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
|
|
1563
|
+
if (key) result[key] = value;
|
|
1564
|
+
} catch {
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return result;
|
|
1568
|
+
}
|
|
1569
|
+
function parseDeferredAppLink(url) {
|
|
1570
|
+
if (!url || url.trim() === "") return null;
|
|
1571
|
+
const raw = url.slice(0, 2048);
|
|
1572
|
+
const cleanRaw = raw.split("#")[0];
|
|
1573
|
+
const qIndex = cleanRaw.indexOf("?");
|
|
1574
|
+
if (qIndex === -1) return null;
|
|
1575
|
+
const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
|
|
1576
|
+
let fbclid = nz(topParams["fbclid"]);
|
|
1577
|
+
let utmSource = nz(topParams["utm_source"]);
|
|
1578
|
+
let utmMedium = nz(topParams["utm_medium"]);
|
|
1579
|
+
let utmCampaign = nz(topParams["utm_campaign"]);
|
|
1580
|
+
let ttclid = nz(topParams["ttclid"]);
|
|
1581
|
+
let trackingId = nz(topParams["tracking_id"]);
|
|
1582
|
+
let targetUrl = null;
|
|
1583
|
+
if (topParams["target_url"]) {
|
|
1584
|
+
try {
|
|
1585
|
+
targetUrl = decodeURIComponent(topParams["target_url"]);
|
|
1586
|
+
} catch {
|
|
1587
|
+
targetUrl = topParams["target_url"];
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (!fbclid && targetUrl) {
|
|
1591
|
+
const cleanTargetUrl = targetUrl.split("#")[0];
|
|
1592
|
+
const innerQ = cleanTargetUrl.indexOf("?");
|
|
1593
|
+
if (innerQ !== -1) {
|
|
1594
|
+
const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
|
|
1595
|
+
fbclid = fbclid ?? nz(inner["fbclid"]);
|
|
1596
|
+
utmSource = utmSource ?? nz(inner["utm_source"]);
|
|
1597
|
+
utmMedium = utmMedium ?? nz(inner["utm_medium"]);
|
|
1598
|
+
utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
|
|
1599
|
+
ttclid = ttclid ?? nz(inner["ttclid"]);
|
|
1600
|
+
trackingId = trackingId ?? nz(inner["tracking_id"]);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
|
|
1607
|
+
}
|
|
1608
|
+
var nz;
|
|
1609
|
+
var init_deferredAppLink = __esm({
|
|
1610
|
+
"src/domains/identity/deferredAppLink.ts"() {
|
|
1611
|
+
"use strict";
|
|
1612
|
+
nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1530
1616
|
// src/domains/identity/MetaBridge.ts
|
|
1531
1617
|
var import_react_native5, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
|
|
1532
1618
|
var init_MetaBridge = __esm({
|
|
@@ -1625,68 +1711,6 @@ var init_MetaBridge = __esm({
|
|
|
1625
1711
|
}
|
|
1626
1712
|
});
|
|
1627
1713
|
|
|
1628
|
-
// src/domains/identity/deferredAppLink.ts
|
|
1629
|
-
function parseQuery(s) {
|
|
1630
|
-
const result = {};
|
|
1631
|
-
for (const pair of s.split("&")) {
|
|
1632
|
-
const eqIndex = pair.indexOf("=");
|
|
1633
|
-
if (eqIndex === -1) continue;
|
|
1634
|
-
try {
|
|
1635
|
-
const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
|
|
1636
|
-
const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
|
|
1637
|
-
if (key) result[key] = value;
|
|
1638
|
-
} catch {
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
return result;
|
|
1642
|
-
}
|
|
1643
|
-
function parseDeferredAppLink(url) {
|
|
1644
|
-
if (!url || url.trim() === "") return null;
|
|
1645
|
-
const raw = url.slice(0, 2048);
|
|
1646
|
-
const cleanRaw = raw.split("#")[0];
|
|
1647
|
-
const qIndex = cleanRaw.indexOf("?");
|
|
1648
|
-
if (qIndex === -1) return null;
|
|
1649
|
-
const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
|
|
1650
|
-
let fbclid = nz(topParams["fbclid"]);
|
|
1651
|
-
let utmSource = nz(topParams["utm_source"]);
|
|
1652
|
-
let utmMedium = nz(topParams["utm_medium"]);
|
|
1653
|
-
let utmCampaign = nz(topParams["utm_campaign"]);
|
|
1654
|
-
let ttclid = nz(topParams["ttclid"]);
|
|
1655
|
-
let trackingId = nz(topParams["tracking_id"]);
|
|
1656
|
-
let targetUrl = null;
|
|
1657
|
-
if (topParams["target_url"]) {
|
|
1658
|
-
try {
|
|
1659
|
-
targetUrl = decodeURIComponent(topParams["target_url"]);
|
|
1660
|
-
} catch {
|
|
1661
|
-
targetUrl = topParams["target_url"];
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
if (!fbclid && targetUrl) {
|
|
1665
|
-
const cleanTargetUrl = targetUrl.split("#")[0];
|
|
1666
|
-
const innerQ = cleanTargetUrl.indexOf("?");
|
|
1667
|
-
if (innerQ !== -1) {
|
|
1668
|
-
const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
|
|
1669
|
-
fbclid = fbclid ?? nz(inner["fbclid"]);
|
|
1670
|
-
utmSource = utmSource ?? nz(inner["utm_source"]);
|
|
1671
|
-
utmMedium = utmMedium ?? nz(inner["utm_medium"]);
|
|
1672
|
-
utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
|
|
1673
|
-
ttclid = ttclid ?? nz(inner["ttclid"]);
|
|
1674
|
-
trackingId = trackingId ?? nz(inner["tracking_id"]);
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
|
|
1678
|
-
return null;
|
|
1679
|
-
}
|
|
1680
|
-
return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
|
|
1681
|
-
}
|
|
1682
|
-
var nz;
|
|
1683
|
-
var init_deferredAppLink = __esm({
|
|
1684
|
-
"src/domains/identity/deferredAppLink.ts"() {
|
|
1685
|
-
"use strict";
|
|
1686
|
-
nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
|
|
1687
|
-
}
|
|
1688
|
-
});
|
|
1689
|
-
|
|
1690
1714
|
// src/domains/identity/InstallReferrerManager.ts
|
|
1691
1715
|
var import_react_native6, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
|
|
1692
1716
|
var init_InstallReferrerManager = __esm({
|
|
@@ -1694,8 +1718,8 @@ var init_InstallReferrerManager = __esm({
|
|
|
1694
1718
|
"use strict";
|
|
1695
1719
|
import_react_native6 = require("react-native");
|
|
1696
1720
|
init_NativeDeviceInfo();
|
|
1697
|
-
init_MetaBridge();
|
|
1698
1721
|
init_deferredAppLink();
|
|
1722
|
+
init_MetaBridge();
|
|
1699
1723
|
REFERRER_TIMEOUT_MS = 5e3;
|
|
1700
1724
|
InstallReferrerManager = class {
|
|
1701
1725
|
async getReferrer() {
|
|
@@ -1795,7 +1819,17 @@ var init_InstallReferrerManager = __esm({
|
|
|
1795
1819
|
});
|
|
1796
1820
|
|
|
1797
1821
|
// src/domains/identity/InstallTracker.ts
|
|
1798
|
-
|
|
1822
|
+
function extractDeferredAttribution(raw) {
|
|
1823
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
1824
|
+
const source = raw;
|
|
1825
|
+
const out = {};
|
|
1826
|
+
for (const field of DEFERRED_ATTRIBUTION_FIELDS) {
|
|
1827
|
+
const value = source[field];
|
|
1828
|
+
if (typeof value === "string" && value !== "") out[field] = value;
|
|
1829
|
+
}
|
|
1830
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1831
|
+
}
|
|
1832
|
+
var import_react_native7, InstallTracker, DEFERRED_ATTRIBUTION_FIELDS;
|
|
1799
1833
|
var init_InstallTracker = __esm({
|
|
1800
1834
|
"src/domains/identity/InstallTracker.ts"() {
|
|
1801
1835
|
"use strict";
|
|
@@ -1961,7 +1995,7 @@ var init_InstallTracker = __esm({
|
|
|
1961
1995
|
const baseUrl = apiClient.getBaseUrl();
|
|
1962
1996
|
const appKey = apiClient.getAppKey();
|
|
1963
1997
|
const referrer = await installReferrerManager.getReferrer();
|
|
1964
|
-
await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
|
|
1998
|
+
const response = await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
|
|
1965
1999
|
method: "POST",
|
|
1966
2000
|
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
1967
2001
|
body: JSON.stringify({
|
|
@@ -1983,12 +2017,42 @@ var init_InstallTracker = __esm({
|
|
|
1983
2017
|
});
|
|
1984
2018
|
await storage.set(INSTALL_KEYS.DEFERRED_MATCH_DONE, "1").catch(() => {
|
|
1985
2019
|
});
|
|
2020
|
+
await this.applyDeferredMatchResponse(response);
|
|
1986
2021
|
} catch {
|
|
1987
2022
|
} finally {
|
|
1988
2023
|
clearTimeout(timer);
|
|
1989
2024
|
}
|
|
1990
2025
|
}
|
|
2026
|
+
async applyDeferredMatchResponse(response) {
|
|
2027
|
+
try {
|
|
2028
|
+
const body = await response.json();
|
|
2029
|
+
if (body === null || typeof body !== "object") return;
|
|
2030
|
+
const outer = body;
|
|
2031
|
+
const source = typeof outer.data === "object" && outer.data !== null ? outer.data : body;
|
|
2032
|
+
if (source.matched !== true) return;
|
|
2033
|
+
const attribution = extractDeferredAttribution(source.attribution);
|
|
2034
|
+
if (!attribution) return;
|
|
2035
|
+
await attributionTracker.capture({
|
|
2036
|
+
...attribution,
|
|
2037
|
+
installReferrerSource: "deferred_match"
|
|
2038
|
+
});
|
|
2039
|
+
} catch {
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
1991
2042
|
};
|
|
2043
|
+
DEFERRED_ATTRIBUTION_FIELDS = [
|
|
2044
|
+
"fbclid",
|
|
2045
|
+
"gclid",
|
|
2046
|
+
"ttclid",
|
|
2047
|
+
"utmSource",
|
|
2048
|
+
"utmMedium",
|
|
2049
|
+
"utmCampaign",
|
|
2050
|
+
"utmContent",
|
|
2051
|
+
"utmTerm",
|
|
2052
|
+
"tiktokCampaignId",
|
|
2053
|
+
"tiktokAdgroupId",
|
|
2054
|
+
"tiktokAdId"
|
|
2055
|
+
];
|
|
1992
2056
|
}
|
|
1993
2057
|
});
|
|
1994
2058
|
|
|
@@ -2563,8 +2627,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2563
2627
|
// src/core/version.ts
|
|
2564
2628
|
function resolveVersion() {
|
|
2565
2629
|
try {
|
|
2566
|
-
if ("2.5.
|
|
2567
|
-
return "2.5.
|
|
2630
|
+
if ("2.5.3") {
|
|
2631
|
+
return "2.5.3";
|
|
2568
2632
|
}
|
|
2569
2633
|
} catch {
|
|
2570
2634
|
}
|
|
@@ -3183,99 +3247,236 @@ var init_notifications = __esm({
|
|
|
3183
3247
|
}
|
|
3184
3248
|
});
|
|
3185
3249
|
|
|
3186
|
-
// src/domains/
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
}
|
|
3191
|
-
var CLOSE_REASON_MAP;
|
|
3192
|
-
var init_paywallCloseReason = __esm({
|
|
3193
|
-
"src/domains/paywall/paywallCloseReason.ts"() {
|
|
3250
|
+
// src/domains/onboarding/OnboardingError.ts
|
|
3251
|
+
var OnboardingError, ONBOARDING_ERROR_CODES;
|
|
3252
|
+
var init_OnboardingError = __esm({
|
|
3253
|
+
"src/domains/onboarding/OnboardingError.ts"() {
|
|
3194
3254
|
"use strict";
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3255
|
+
init_PaywalloError();
|
|
3256
|
+
OnboardingError = class extends PaywalloError {
|
|
3257
|
+
constructor(code, message) {
|
|
3258
|
+
super("onboarding", code, message);
|
|
3259
|
+
this.name = "OnboardingError";
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
ONBOARDING_ERROR_CODES = {
|
|
3263
|
+
NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
|
|
3264
|
+
INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
|
|
3200
3265
|
};
|
|
3201
3266
|
}
|
|
3202
3267
|
});
|
|
3203
3268
|
|
|
3204
|
-
// src/domains/
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
if (!api) return;
|
|
3209
|
-
const sessionId = PaywalloClient.getSessionId();
|
|
3210
|
-
const closedAt = /* @__PURE__ */ new Date();
|
|
3211
|
-
const durationS = Math.max(0, Math.round((closedAt.getTime() - opts.presentedAt) / 1e3));
|
|
3212
|
-
await api.trackEvent("paywall", PaywalloClient.getDistinctId(), {
|
|
3213
|
-
type: "closed",
|
|
3214
|
-
paywall_id: opts.paywallId,
|
|
3215
|
-
placement: opts.placement,
|
|
3216
|
-
closed_at: closedAt.toISOString(),
|
|
3217
|
-
duration_s: durationS,
|
|
3218
|
-
close_reason: toCanonicalReason(opts.reason),
|
|
3219
|
-
...opts.variantId && { variant_id: opts.variantId },
|
|
3220
|
-
...opts.variantKey && { variant_key: opts.variantKey },
|
|
3221
|
-
...opts.campaignId && { campaign_id: opts.campaignId },
|
|
3222
|
-
...typeof opts.scrollDepth === "number" && { scroll_depth: opts.scrollDepth },
|
|
3223
|
-
...sessionId && { sessionId }
|
|
3224
|
-
});
|
|
3225
|
-
} catch (err) {
|
|
3226
|
-
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] emitPaywallClosed failed", err);
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
var init_emitPaywallClosed = __esm({
|
|
3230
|
-
"src/domains/paywall/emitPaywallClosed.ts"() {
|
|
3231
|
-
"use strict";
|
|
3232
|
-
init_PaywalloClient();
|
|
3233
|
-
init_paywallCloseReason();
|
|
3234
|
-
}
|
|
3235
|
-
});
|
|
3236
|
-
|
|
3237
|
-
// src/domains/paywall/extractFirstPageBackground.ts
|
|
3238
|
-
function extractFirstPageBackground(craftData) {
|
|
3239
|
-
if (!craftData) return DEFAULT_BACKGROUND;
|
|
3240
|
-
try {
|
|
3241
|
-
const data = JSON.parse(craftData);
|
|
3242
|
-
const nodes = { ...data.nodes, ...data.sharedNodes };
|
|
3243
|
-
const rootId = data.rootId;
|
|
3244
|
-
if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
|
|
3245
|
-
const firstPageId = nodes[rootId].children?.[0];
|
|
3246
|
-
if (!firstPageId) return DEFAULT_BACKGROUND;
|
|
3247
|
-
const bg = nodes[firstPageId]?.props?.backgroundColor;
|
|
3248
|
-
return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
|
|
3249
|
-
} catch {
|
|
3250
|
-
return DEFAULT_BACKGROUND;
|
|
3251
|
-
}
|
|
3252
|
-
}
|
|
3253
|
-
function isColorDark(color) {
|
|
3254
|
-
const hex = color.replace("#", "").trim();
|
|
3255
|
-
const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
3256
|
-
if (normalized.length !== 6) return false;
|
|
3257
|
-
const r = parseInt(normalized.slice(0, 2), 16);
|
|
3258
|
-
const g = parseInt(normalized.slice(2, 4), 16);
|
|
3259
|
-
const b = parseInt(normalized.slice(4, 6), 16);
|
|
3260
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
|
|
3261
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
3262
|
-
return luminance < 0.5;
|
|
3263
|
-
}
|
|
3264
|
-
var DEFAULT_BACKGROUND;
|
|
3265
|
-
var init_extractFirstPageBackground = __esm({
|
|
3266
|
-
"src/domains/paywall/extractFirstPageBackground.ts"() {
|
|
3269
|
+
// src/domains/onboarding/OnboardingManager.ts
|
|
3270
|
+
var OnboardingManager, onboardingManager;
|
|
3271
|
+
var init_OnboardingManager = __esm({
|
|
3272
|
+
"src/domains/onboarding/OnboardingManager.ts"() {
|
|
3267
3273
|
"use strict";
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3274
|
+
init_OnboardingError();
|
|
3275
|
+
OnboardingManager = class {
|
|
3276
|
+
constructor() {
|
|
3277
|
+
this.pipeline = null;
|
|
3278
|
+
this.debug = false;
|
|
3279
|
+
this.lastStep = null;
|
|
3280
|
+
this.finished = false;
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Injects runtime dependencies. Called by PaywalloClient during init.
|
|
3284
|
+
*/
|
|
3285
|
+
injectDeps(config) {
|
|
3286
|
+
this.pipeline = config.pipeline;
|
|
3287
|
+
this.debug = config.debug ?? false;
|
|
3288
|
+
}
|
|
3289
|
+
/**
|
|
3290
|
+
* Tracks an onboarding step view.
|
|
3291
|
+
* Saves the step name internally for implicit drop detection on the backend.
|
|
3292
|
+
*
|
|
3293
|
+
* @param stepName - Unique name for this onboarding step.
|
|
3294
|
+
* @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
|
|
3295
|
+
*/
|
|
3296
|
+
async step(stepName, order) {
|
|
3297
|
+
this.assertConfig();
|
|
3298
|
+
this.validateStepName(stepName, "step");
|
|
3299
|
+
this.lastStep = stepName;
|
|
3300
|
+
const stepPayload = { step_name: stepName };
|
|
3301
|
+
if (order !== void 0 && Number.isFinite(order) && order >= 0) {
|
|
3302
|
+
stepPayload.order = order;
|
|
3303
|
+
}
|
|
3304
|
+
const payload = {
|
|
3305
|
+
family: "onboarding",
|
|
3306
|
+
type: "step",
|
|
3307
|
+
...stepPayload
|
|
3308
|
+
};
|
|
3309
|
+
return this.emit("onboarding", payload, "step");
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Tracks successful completion of the onboarding flow.
|
|
3313
|
+
* Marks the flow as finished.
|
|
3314
|
+
*/
|
|
3315
|
+
async complete() {
|
|
3316
|
+
this.assertConfig();
|
|
3317
|
+
this.finished = true;
|
|
3318
|
+
return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Tracks an explicit drop (abandonment) at the given step.
|
|
3322
|
+
* Marks the flow as finished.
|
|
3323
|
+
*
|
|
3324
|
+
* @param stepName - The step name where the user dropped off.
|
|
3325
|
+
*/
|
|
3326
|
+
async drop(stepName) {
|
|
3327
|
+
this.assertConfig();
|
|
3328
|
+
this.validateStepName(stepName, "drop");
|
|
3329
|
+
this.finished = true;
|
|
3330
|
+
const payload = {
|
|
3331
|
+
family: "onboarding",
|
|
3332
|
+
type: "drop",
|
|
3333
|
+
...{ step_name: stepName }
|
|
3334
|
+
};
|
|
3335
|
+
return this.emit("onboarding", payload, "drop");
|
|
3336
|
+
}
|
|
3337
|
+
/** Returns the last step name seen, or null if no step was tracked yet. */
|
|
3338
|
+
getLastStep() {
|
|
3339
|
+
return this.lastStep;
|
|
3340
|
+
}
|
|
3341
|
+
/** Returns whether the flow has been marked as finished (complete or drop). */
|
|
3342
|
+
isFinished() {
|
|
3343
|
+
return this.finished;
|
|
3344
|
+
}
|
|
3345
|
+
async emit(eventName, properties, logLabel) {
|
|
3346
|
+
const pipeline = this.pipeline;
|
|
3347
|
+
const distinctId = pipeline.getDistinctId();
|
|
3348
|
+
if (!distinctId) {
|
|
3349
|
+
if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
await pipeline.trackEvent(eventName, distinctId, properties);
|
|
3353
|
+
if (this.debug) {
|
|
3354
|
+
console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
assertConfig() {
|
|
3358
|
+
if (!this.pipeline) {
|
|
3359
|
+
throw new OnboardingError(
|
|
3360
|
+
ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
|
|
3361
|
+
"OnboardingManager not initialized. Call Paywallo.init() first."
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
validateStepName(stepName, method) {
|
|
3366
|
+
if (typeof stepName !== "string" || stepName.trim().length === 0) {
|
|
3367
|
+
throw new OnboardingError(
|
|
3368
|
+
ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
|
|
3369
|
+
`OnboardingManager.${method}(): stepName must be a non-empty string.`
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
};
|
|
3374
|
+
onboardingManager = new OnboardingManager();
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
|
|
3378
|
+
// src/domains/onboarding/index.ts
|
|
3379
|
+
var init_onboarding = __esm({
|
|
3380
|
+
"src/domains/onboarding/index.ts"() {
|
|
3381
|
+
"use strict";
|
|
3382
|
+
init_OnboardingError();
|
|
3383
|
+
init_OnboardingManager();
|
|
3384
|
+
}
|
|
3385
|
+
});
|
|
3386
|
+
|
|
3387
|
+
// src/domains/paywall/paywallCloseReason.ts
|
|
3388
|
+
function toCanonicalReason(reason) {
|
|
3389
|
+
if (reason in CLOSE_REASON_MAP) return CLOSE_REASON_MAP[reason];
|
|
3390
|
+
return reason;
|
|
3391
|
+
}
|
|
3392
|
+
var CLOSE_REASON_MAP;
|
|
3393
|
+
var init_paywallCloseReason = __esm({
|
|
3394
|
+
"src/domains/paywall/paywallCloseReason.ts"() {
|
|
3395
|
+
"use strict";
|
|
3396
|
+
CLOSE_REASON_MAP = {
|
|
3397
|
+
dismissed: "dismiss",
|
|
3398
|
+
purchased: "purchase",
|
|
3399
|
+
backgrounded: "dismiss",
|
|
3400
|
+
timeout: "timeout"
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
});
|
|
3404
|
+
|
|
3405
|
+
// src/domains/paywall/emitPaywallClosed.ts
|
|
3406
|
+
async function emitPaywallClosed(opts) {
|
|
3407
|
+
try {
|
|
3408
|
+
const api = PaywalloClient.getApiClient();
|
|
3409
|
+
if (!api) return;
|
|
3410
|
+
const sessionId = PaywalloClient.getSessionId();
|
|
3411
|
+
const closedAt = /* @__PURE__ */ new Date();
|
|
3412
|
+
const durationS = Math.max(0, Math.round((closedAt.getTime() - opts.presentedAt) / 1e3));
|
|
3413
|
+
await api.trackEvent("paywall", PaywalloClient.getDistinctId(), {
|
|
3414
|
+
type: "closed",
|
|
3415
|
+
paywall_id: opts.paywallId,
|
|
3416
|
+
placement: opts.placement,
|
|
3417
|
+
closed_at: closedAt.toISOString(),
|
|
3418
|
+
duration_s: durationS,
|
|
3419
|
+
close_reason: toCanonicalReason(opts.reason),
|
|
3420
|
+
...opts.variantId && { variant_id: opts.variantId },
|
|
3421
|
+
...opts.variantKey && { variant_key: opts.variantKey },
|
|
3422
|
+
...opts.campaignId && { campaign_id: opts.campaignId },
|
|
3423
|
+
...typeof opts.scrollDepth === "number" && { scroll_depth: opts.scrollDepth },
|
|
3424
|
+
...sessionId && { sessionId }
|
|
3425
|
+
});
|
|
3426
|
+
} catch (err) {
|
|
3427
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] emitPaywallClosed failed", err);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
var init_emitPaywallClosed = __esm({
|
|
3431
|
+
"src/domains/paywall/emitPaywallClosed.ts"() {
|
|
3432
|
+
"use strict";
|
|
3433
|
+
init_PaywalloClient();
|
|
3434
|
+
init_paywallCloseReason();
|
|
3435
|
+
}
|
|
3436
|
+
});
|
|
3437
|
+
|
|
3438
|
+
// src/domains/paywall/extractFirstPageBackground.ts
|
|
3439
|
+
function extractFirstPageBackground(craftData) {
|
|
3440
|
+
if (!craftData) return DEFAULT_BACKGROUND;
|
|
3441
|
+
try {
|
|
3442
|
+
const data = JSON.parse(craftData);
|
|
3443
|
+
const nodes = { ...data.nodes, ...data.sharedNodes };
|
|
3444
|
+
const rootId = data.rootId;
|
|
3445
|
+
if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
|
|
3446
|
+
const firstPageId = nodes[rootId].children?.[0];
|
|
3447
|
+
if (!firstPageId) return DEFAULT_BACKGROUND;
|
|
3448
|
+
const bg = nodes[firstPageId]?.props?.backgroundColor;
|
|
3449
|
+
return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
|
|
3450
|
+
} catch {
|
|
3451
|
+
return DEFAULT_BACKGROUND;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
function isColorDark(color) {
|
|
3455
|
+
const hex = color.replace("#", "").trim();
|
|
3456
|
+
const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
3457
|
+
if (normalized.length !== 6) return false;
|
|
3458
|
+
const r = parseInt(normalized.slice(0, 2), 16);
|
|
3459
|
+
const g = parseInt(normalized.slice(2, 4), 16);
|
|
3460
|
+
const b = parseInt(normalized.slice(4, 6), 16);
|
|
3461
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
|
|
3462
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
3463
|
+
return luminance < 0.5;
|
|
3464
|
+
}
|
|
3465
|
+
var DEFAULT_BACKGROUND;
|
|
3466
|
+
var init_extractFirstPageBackground = __esm({
|
|
3467
|
+
"src/domains/paywall/extractFirstPageBackground.ts"() {
|
|
3468
|
+
"use strict";
|
|
3469
|
+
DEFAULT_BACKGROUND = "#FFFFFF";
|
|
3470
|
+
}
|
|
3471
|
+
});
|
|
3472
|
+
|
|
3473
|
+
// src/domains/paywall/parseWebViewMessage.ts
|
|
3474
|
+
function deriveMessageId(message) {
|
|
3475
|
+
if (message.id) return message.id;
|
|
3476
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
3477
|
+
return `${message.type}:${payloadKey}`;
|
|
3478
|
+
}
|
|
3479
|
+
function parseWebViewMessage(raw) {
|
|
3279
3480
|
try {
|
|
3280
3481
|
const parsed = JSON.parse(raw);
|
|
3281
3482
|
if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
|
|
@@ -3910,54 +4111,379 @@ var init_PaywallWebView = __esm({
|
|
|
3910
4111
|
}
|
|
3911
4112
|
});
|
|
3912
4113
|
|
|
3913
|
-
// src/domains/iap/
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
4114
|
+
// src/domains/iap/PurchaseError.ts
|
|
4115
|
+
function createPurchaseError(code, message) {
|
|
4116
|
+
return new PurchaseError(
|
|
4117
|
+
PURCHASE_ERROR_CODES[code],
|
|
4118
|
+
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4119
|
+
code === "USER_CANCELLED"
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4123
|
+
var init_PurchaseError = __esm({
|
|
4124
|
+
"src/domains/iap/PurchaseError.ts"() {
|
|
3917
4125
|
"use strict";
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
constructor(
|
|
3921
|
-
|
|
3922
|
-
this.
|
|
3923
|
-
this.
|
|
4126
|
+
init_PaywalloError();
|
|
4127
|
+
PurchaseError = class extends PaywalloError {
|
|
4128
|
+
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4129
|
+
super("purchase", code, message);
|
|
4130
|
+
this.name = "PurchaseError";
|
|
4131
|
+
this.userCancelled = userCancelled;
|
|
4132
|
+
this.httpStatus = httpStatus;
|
|
3924
4133
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
4134
|
+
};
|
|
4135
|
+
PURCHASE_ERROR_CODES = {
|
|
4136
|
+
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4137
|
+
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4138
|
+
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4139
|
+
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4140
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4141
|
+
USER_CANCELLED: "USER_CANCELLED",
|
|
4142
|
+
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4143
|
+
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4144
|
+
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4145
|
+
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4146
|
+
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4147
|
+
};
|
|
4148
|
+
DEFAULT_MESSAGES = {
|
|
4149
|
+
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4150
|
+
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4151
|
+
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4152
|
+
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4153
|
+
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4154
|
+
USER_CANCELLED: "Purchase was cancelled.",
|
|
4155
|
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4156
|
+
STORE_ERROR: "Store error. Please try again later.",
|
|
4157
|
+
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4158
|
+
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4159
|
+
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
// src/domains/iap/NativeStoreKit.ts
|
|
4165
|
+
var NativeStoreKit_exports = {};
|
|
4166
|
+
__export(NativeStoreKit_exports, {
|
|
4167
|
+
NativeStoreKit: () => NativeStoreKit,
|
|
4168
|
+
nativeStoreKit: () => nativeStoreKit
|
|
4169
|
+
});
|
|
4170
|
+
function isAvailable3() {
|
|
4171
|
+
return PaywalloStoreKitNative != null;
|
|
4172
|
+
}
|
|
4173
|
+
function mapNativeProduct(native) {
|
|
4174
|
+
const product = {
|
|
4175
|
+
productId: native.productId,
|
|
4176
|
+
title: native.title,
|
|
4177
|
+
description: native.description,
|
|
4178
|
+
price: native.price,
|
|
4179
|
+
priceValue: native.priceValue,
|
|
4180
|
+
currency: native.currency ?? "USD",
|
|
4181
|
+
localizedPrice: native.localizedPrice,
|
|
4182
|
+
type: mapProductType(native.type),
|
|
4183
|
+
subscriptionPeriod: native.subscriptionPeriod,
|
|
4184
|
+
introductoryPrice: native.introductoryPrice,
|
|
4185
|
+
introductoryPriceValue: native.introductoryPriceValue,
|
|
4186
|
+
freeTrialPeriod: native.freeTrialPeriod
|
|
4187
|
+
};
|
|
4188
|
+
if (native.subscriptionPeriod) {
|
|
4189
|
+
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
4190
|
+
product.pricePerMonth = prices.perMonth;
|
|
4191
|
+
product.pricePerWeek = prices.perWeek;
|
|
4192
|
+
product.pricePerDay = prices.perDay;
|
|
4193
|
+
}
|
|
4194
|
+
return product;
|
|
4195
|
+
}
|
|
4196
|
+
function mapProductType(type) {
|
|
4197
|
+
switch (type) {
|
|
4198
|
+
case "subscription":
|
|
4199
|
+
case "autoRenewable":
|
|
4200
|
+
return "subscription";
|
|
4201
|
+
case "consumable":
|
|
4202
|
+
return "consumable";
|
|
4203
|
+
default:
|
|
4204
|
+
return "non_consumable";
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function calculatePerPeriodPrices(priceValue, period) {
|
|
4208
|
+
const totalDays = periodToDays(period);
|
|
4209
|
+
if (totalDays <= 0) return {};
|
|
4210
|
+
const perDay = priceValue / totalDays;
|
|
4211
|
+
const perWeek = perDay * 7;
|
|
4212
|
+
const perMonth = perDay * 30;
|
|
4213
|
+
return {
|
|
4214
|
+
perDay: perDay.toFixed(2),
|
|
4215
|
+
perWeek: perWeek.toFixed(2),
|
|
4216
|
+
perMonth: perMonth.toFixed(2)
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
function periodToDays(period) {
|
|
4220
|
+
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
4221
|
+
if (!match) return 0;
|
|
4222
|
+
const value = parseInt(match[1], 10);
|
|
4223
|
+
switch (match[2]) {
|
|
4224
|
+
case "D":
|
|
4225
|
+
return value;
|
|
4226
|
+
case "W":
|
|
4227
|
+
return value * 7;
|
|
4228
|
+
case "M":
|
|
4229
|
+
return value * 30;
|
|
4230
|
+
case "Y":
|
|
4231
|
+
return value * 365;
|
|
4232
|
+
default:
|
|
4233
|
+
return 0;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
function mapNativePurchase(native) {
|
|
4237
|
+
return {
|
|
4238
|
+
productId: native.productId,
|
|
4239
|
+
transactionId: native.transactionId,
|
|
4240
|
+
transactionDate: native.transactionDate,
|
|
4241
|
+
receipt: native.receipt,
|
|
4242
|
+
platform: import_react_native16.Platform.OS
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
function normalizeTransactionUpdate(raw) {
|
|
4246
|
+
if (!raw || typeof raw !== "object") return null;
|
|
4247
|
+
const r = raw;
|
|
4248
|
+
const type = r.type;
|
|
4249
|
+
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
4250
|
+
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
4251
|
+
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
4252
|
+
if (!transactionId) return null;
|
|
4253
|
+
const update = {
|
|
4254
|
+
type,
|
|
4255
|
+
transactionId,
|
|
4256
|
+
productId: productId ?? ""
|
|
4257
|
+
};
|
|
4258
|
+
if (typeof r.amount === "number") update.amount = r.amount;
|
|
4259
|
+
if (typeof r.currency === "string") update.currency = r.currency;
|
|
4260
|
+
return update;
|
|
4261
|
+
}
|
|
4262
|
+
var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
4263
|
+
var init_NativeStoreKit = __esm({
|
|
4264
|
+
"src/domains/iap/NativeStoreKit.ts"() {
|
|
4265
|
+
"use strict";
|
|
4266
|
+
import_react_native16 = require("react-native");
|
|
4267
|
+
init_PaywalloClient();
|
|
4268
|
+
init_uuid();
|
|
4269
|
+
init_IdentityManager();
|
|
4270
|
+
init_PurchaseError();
|
|
4271
|
+
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
4272
|
+
PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
|
|
4273
|
+
nativeStoreKit = {
|
|
4274
|
+
isAvailable: isAvailable3,
|
|
4275
|
+
async getProducts(productIds) {
|
|
4276
|
+
if (!PaywalloStoreKitNative) {
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4279
|
+
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
4280
|
+
return nativeProducts.map(mapNativeProduct);
|
|
4281
|
+
},
|
|
4282
|
+
async purchase(productId) {
|
|
4283
|
+
if (!PaywalloStoreKitNative) {
|
|
4284
|
+
throw new PurchaseError(
|
|
4285
|
+
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
4286
|
+
"StoreKit native module not available"
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
const distinctId = identityManager.getDistinctId();
|
|
4290
|
+
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
4291
|
+
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
4292
|
+
if (distinctId && appAccountToken === null) {
|
|
4293
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
4294
|
+
}
|
|
4295
|
+
try {
|
|
4296
|
+
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
4297
|
+
if (!result || result.pending) {
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4300
|
+
return mapNativePurchase(result);
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4303
|
+
if (message.toLowerCase().includes("cancel")) {
|
|
4304
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
4305
|
+
}
|
|
4306
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
4307
|
+
}
|
|
4308
|
+
},
|
|
4309
|
+
async finishTransaction(transactionId) {
|
|
4310
|
+
if (!PaywalloStoreKitNative) return;
|
|
4311
|
+
try {
|
|
4312
|
+
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
4313
|
+
} catch (err) {
|
|
4314
|
+
console.error("[Paywallo] finishTransaction native error", err);
|
|
4315
|
+
}
|
|
4316
|
+
},
|
|
4317
|
+
async getActiveTransactions() {
|
|
4318
|
+
if (!PaywalloStoreKitNative) return [];
|
|
4319
|
+
try {
|
|
4320
|
+
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
4321
|
+
return results.map(mapNativePurchase);
|
|
4322
|
+
} catch (err) {
|
|
4323
|
+
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
4324
|
+
return [];
|
|
4325
|
+
}
|
|
4326
|
+
},
|
|
4327
|
+
/**
|
|
4328
|
+
* Subscribes to native transaction update events (renewals, refunds,
|
|
4329
|
+
* cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
|
|
4330
|
+
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
4331
|
+
*
|
|
4332
|
+
* Returns a subscription with a `remove()` method. The caller is
|
|
4333
|
+
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
4334
|
+
*
|
|
4335
|
+
* Returns `null` when the native module isn't available (non-mobile env,
|
|
4336
|
+
* native module not linked) — callers should treat null as a no-op.
|
|
4337
|
+
*/
|
|
4338
|
+
subscribeToTransactionUpdates(listener) {
|
|
4339
|
+
if (!PaywalloStoreKitNative) return null;
|
|
4340
|
+
try {
|
|
4341
|
+
const EmitterCtor = import_react_native16.NativeEventEmitter;
|
|
4342
|
+
const emitter = new EmitterCtor(PaywalloStoreKitNative);
|
|
4343
|
+
const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
|
|
4344
|
+
const update = normalizeTransactionUpdate(payload);
|
|
4345
|
+
if (update) listener(update);
|
|
4346
|
+
});
|
|
4347
|
+
return { remove: () => sub.remove() };
|
|
4348
|
+
} catch (err) {
|
|
4349
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
|
|
4350
|
+
return null;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
NativeStoreKit = nativeStoreKit;
|
|
4355
|
+
}
|
|
4356
|
+
});
|
|
4357
|
+
|
|
4358
|
+
// src/domains/iap/IAPTransactionEmitter.ts
|
|
4359
|
+
var IAPTransactionEmitter;
|
|
4360
|
+
var init_IAPTransactionEmitter = __esm({
|
|
4361
|
+
"src/domains/iap/IAPTransactionEmitter.ts"() {
|
|
4362
|
+
"use strict";
|
|
4363
|
+
init_PaywalloClient();
|
|
4364
|
+
init_NativeStoreKit();
|
|
4365
|
+
IAPTransactionEmitter = class {
|
|
4366
|
+
constructor(getProductFromCache, getApiClientFn, getDebug) {
|
|
4367
|
+
this.getProductFromCache = getProductFromCache;
|
|
4368
|
+
this.getApiClientFn = getApiClientFn;
|
|
4369
|
+
this.getDebug = getDebug;
|
|
4370
|
+
this.transactionUpdateSubscription = null;
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* Emits `transaction {type: "completed"}` after a server-validated purchase.
|
|
4374
|
+
* Fire-and-forget — a tracking failure must never poison the purchase flow.
|
|
4375
|
+
*/
|
|
4376
|
+
async emitTransactionCompleted(purchase, productId, options) {
|
|
4377
|
+
try {
|
|
4378
|
+
const apiClient = this.getApiClientFn();
|
|
4379
|
+
if (!apiClient) return;
|
|
4380
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4381
|
+
if (!distinctId) return;
|
|
4382
|
+
const product = this.getProductFromCache(productId);
|
|
4383
|
+
const properties = {
|
|
4384
|
+
type: "completed",
|
|
4385
|
+
tx_id: purchase.transactionId,
|
|
4386
|
+
product_id: purchase.productId
|
|
4387
|
+
};
|
|
4388
|
+
if (product?.priceValue !== void 0 && product.priceValue > 0) {
|
|
4389
|
+
properties.amount = product.priceValue;
|
|
4390
|
+
}
|
|
4391
|
+
if (product?.currency) {
|
|
4392
|
+
properties.currency = product.currency;
|
|
4393
|
+
}
|
|
4394
|
+
if (options?.paywallId) properties.paywall_id = options.paywallId;
|
|
4395
|
+
if (options?.variantId) properties.variant_id = options.variantId;
|
|
4396
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4397
|
+
} catch (err) {
|
|
4398
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
4403
|
+
* antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
|
|
4404
|
+
* Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
|
|
4405
|
+
* nunca pode travar a compra.
|
|
4406
|
+
*/
|
|
4407
|
+
async emitCheckoutStarted(productId) {
|
|
4408
|
+
try {
|
|
4409
|
+
const apiClient = this.getApiClientFn();
|
|
4410
|
+
if (!apiClient) return;
|
|
4411
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4412
|
+
if (!distinctId) return;
|
|
4413
|
+
const product = this.getProductFromCache(productId);
|
|
4414
|
+
const properties = {
|
|
4415
|
+
product_id: productId,
|
|
4416
|
+
amount: product?.priceValue ?? 0,
|
|
4417
|
+
currency: product?.currency ?? "USD"
|
|
4418
|
+
};
|
|
4419
|
+
await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
|
|
4420
|
+
priority: "critical"
|
|
4421
|
+
});
|
|
4422
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
|
|
3947
4423
|
} catch (err) {
|
|
3948
4424
|
if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
|
|
3949
4425
|
}
|
|
3950
4426
|
}
|
|
3951
4427
|
/**
|
|
3952
|
-
*
|
|
3953
|
-
*
|
|
4428
|
+
* Forwards a native transaction update to the EventBatcher.
|
|
4429
|
+
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
4430
|
+
*/
|
|
4431
|
+
async emitTransactionUpdate(update) {
|
|
4432
|
+
try {
|
|
4433
|
+
const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
|
|
4434
|
+
if (!apiClient) {
|
|
4435
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4439
|
+
if (!distinctId) {
|
|
4440
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
const properties = {
|
|
4444
|
+
type: update.type,
|
|
4445
|
+
tx_id: update.transactionId,
|
|
4446
|
+
product_id: update.productId
|
|
4447
|
+
};
|
|
4448
|
+
if (update.type !== "canceled") {
|
|
4449
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
4450
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
4451
|
+
const currency = update.currency ?? cached2?.currency;
|
|
4452
|
+
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
4453
|
+
if (currency) properties.currency = currency;
|
|
4454
|
+
}
|
|
4455
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4456
|
+
if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
|
|
4457
|
+
} catch (err) {
|
|
4458
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Starts listening to native Transaction.updates. Idempotent.
|
|
3954
4463
|
*/
|
|
3955
4464
|
startListener() {
|
|
4465
|
+
if (this.transactionUpdateSubscription) return;
|
|
4466
|
+
this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
|
|
4467
|
+
(update) => {
|
|
4468
|
+
void this.emitTransactionUpdate(update);
|
|
4469
|
+
}
|
|
4470
|
+
);
|
|
4471
|
+
if (this.getDebug() && this.transactionUpdateSubscription) {
|
|
4472
|
+
console.log("[Paywallo IAP] transaction update listener started");
|
|
4473
|
+
}
|
|
3956
4474
|
}
|
|
3957
4475
|
/**
|
|
3958
|
-
*
|
|
4476
|
+
* Removes the native listener. Safe to call multiple times.
|
|
3959
4477
|
*/
|
|
3960
4478
|
stopListener() {
|
|
4479
|
+
if (!this.transactionUpdateSubscription) return;
|
|
4480
|
+
try {
|
|
4481
|
+
this.transactionUpdateSubscription.remove();
|
|
4482
|
+
} catch (err) {
|
|
4483
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
|
|
4484
|
+
}
|
|
4485
|
+
this.transactionUpdateSubscription = null;
|
|
4486
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
|
|
3961
4487
|
}
|
|
3962
4488
|
};
|
|
3963
4489
|
}
|
|
@@ -4445,11 +4971,11 @@ var init_types2 = __esm({
|
|
|
4445
4971
|
});
|
|
4446
4972
|
|
|
4447
4973
|
// src/core/network/NetworkMonitor.ts
|
|
4448
|
-
var
|
|
4974
|
+
var import_react_native17, NetworkMonitorClass, networkMonitor;
|
|
4449
4975
|
var init_NetworkMonitor = __esm({
|
|
4450
4976
|
"src/core/network/NetworkMonitor.ts"() {
|
|
4451
4977
|
"use strict";
|
|
4452
|
-
|
|
4978
|
+
import_react_native17 = require("react-native");
|
|
4453
4979
|
init_types2();
|
|
4454
4980
|
NetworkMonitorClass = class {
|
|
4455
4981
|
constructor() {
|
|
@@ -4478,7 +5004,7 @@ var init_NetworkMonitor = __esm({
|
|
|
4478
5004
|
}, this.config.checkInterval);
|
|
4479
5005
|
}
|
|
4480
5006
|
setupAppStateListener() {
|
|
4481
|
-
this.appStateSubscription =
|
|
5007
|
+
this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
|
|
4482
5008
|
if (state === "active") {
|
|
4483
5009
|
if (!this.checkIntervalId) {
|
|
4484
5010
|
this.checkIntervalId = setInterval(() => {
|
|
@@ -4846,62 +5372,12 @@ var init_queue = __esm({
|
|
|
4846
5372
|
}
|
|
4847
5373
|
});
|
|
4848
5374
|
|
|
4849
|
-
// src/domains/iap/PurchaseError.ts
|
|
4850
|
-
function createPurchaseError(code, message) {
|
|
4851
|
-
return new PurchaseError(
|
|
4852
|
-
PURCHASE_ERROR_CODES[code],
|
|
4853
|
-
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4854
|
-
code === "USER_CANCELLED"
|
|
4855
|
-
);
|
|
4856
|
-
}
|
|
4857
|
-
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4858
|
-
var init_PurchaseError = __esm({
|
|
4859
|
-
"src/domains/iap/PurchaseError.ts"() {
|
|
4860
|
-
"use strict";
|
|
4861
|
-
init_PaywalloError();
|
|
4862
|
-
PurchaseError = class extends PaywalloError {
|
|
4863
|
-
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4864
|
-
super("purchase", code, message);
|
|
4865
|
-
this.name = "PurchaseError";
|
|
4866
|
-
this.userCancelled = userCancelled;
|
|
4867
|
-
this.httpStatus = httpStatus;
|
|
4868
|
-
}
|
|
4869
|
-
};
|
|
4870
|
-
PURCHASE_ERROR_CODES = {
|
|
4871
|
-
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4872
|
-
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4873
|
-
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4874
|
-
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4875
|
-
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4876
|
-
USER_CANCELLED: "USER_CANCELLED",
|
|
4877
|
-
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4878
|
-
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4879
|
-
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4880
|
-
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4881
|
-
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4882
|
-
};
|
|
4883
|
-
DEFAULT_MESSAGES = {
|
|
4884
|
-
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4885
|
-
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4886
|
-
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4887
|
-
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4888
|
-
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4889
|
-
USER_CANCELLED: "Purchase was cancelled.",
|
|
4890
|
-
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4891
|
-
STORE_ERROR: "Store error. Please try again later.",
|
|
4892
|
-
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4893
|
-
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4894
|
-
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
});
|
|
4898
|
-
|
|
4899
5375
|
// src/domains/iap/IAPValidator.ts
|
|
4900
|
-
var
|
|
5376
|
+
var import_react_native18, IAPValidator;
|
|
4901
5377
|
var init_IAPValidator = __esm({
|
|
4902
5378
|
"src/domains/iap/IAPValidator.ts"() {
|
|
4903
5379
|
"use strict";
|
|
4904
|
-
|
|
5380
|
+
import_react_native18 = require("react-native");
|
|
4905
5381
|
init_PaywalloClient();
|
|
4906
5382
|
init_queue();
|
|
4907
5383
|
init_PurchaseError();
|
|
@@ -4927,7 +5403,7 @@ var init_IAPValidator = __esm({
|
|
|
4927
5403
|
async validateWithServer(purchase, productId, options) {
|
|
4928
5404
|
const apiClient = this.getApiClientFn();
|
|
4929
5405
|
const product = this.getProductFromCache(productId);
|
|
4930
|
-
const platform =
|
|
5406
|
+
const platform = import_react_native18.Platform.OS;
|
|
4931
5407
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4932
5408
|
return this.validateWithRetry(
|
|
4933
5409
|
() => apiClient.validatePurchase(
|
|
@@ -4948,243 +5424,49 @@ var init_IAPValidator = __esm({
|
|
|
4948
5424
|
* Bucketizes a validation failure into `server4xx` (permanent) vs `network` (transient).
|
|
4949
5425
|
*/
|
|
4950
5426
|
classifyValidationError(error) {
|
|
4951
|
-
if (error instanceof PurchaseError && typeof error.httpStatus === "number") {
|
|
4952
|
-
const status = error.httpStatus;
|
|
4953
|
-
if (status >= 400 && status < 500) return "server4xx";
|
|
4954
|
-
}
|
|
4955
|
-
return "network";
|
|
4956
|
-
}
|
|
4957
|
-
/**
|
|
4958
|
-
* Pushes the validation payload into the offline queue for durable retry.
|
|
4959
|
-
* The StoreKit transaction is intentionally left un-finished.
|
|
4960
|
-
*/
|
|
4961
|
-
async enqueueValidationRetry(purchase, productId, options) {
|
|
4962
|
-
try {
|
|
4963
|
-
const apiClient = this.getApiClientFn();
|
|
4964
|
-
const product = this.getProductFromCache(productId);
|
|
4965
|
-
const platform = import_react_native17.Platform.OS;
|
|
4966
|
-
const distinctId = PaywalloClient.getDistinctId();
|
|
4967
|
-
const appKey = apiClient.getAppKey();
|
|
4968
|
-
const body = {
|
|
4969
|
-
platform,
|
|
4970
|
-
receipt_data: purchase.receipt,
|
|
4971
|
-
product_id: purchase.productId,
|
|
4972
|
-
transaction_id: purchase.transactionId,
|
|
4973
|
-
price_local: product?.priceValue ?? 0,
|
|
4974
|
-
currency: product?.currency ?? "USD",
|
|
4975
|
-
country: this.getDeviceCountry(),
|
|
4976
|
-
distinct_id: distinctId
|
|
4977
|
-
};
|
|
4978
|
-
if (options?.paywallPlacement !== void 0) body.paywall_placement = options.paywallPlacement;
|
|
4979
|
-
if (options?.variantKey !== void 0) body.variant_key = options.variantKey;
|
|
4980
|
-
await offlineQueue.enqueue(
|
|
4981
|
-
"POST",
|
|
4982
|
-
"/sdk/purchases/validate",
|
|
4983
|
-
body,
|
|
4984
|
-
{ "X-App-Key": appKey },
|
|
4985
|
-
`validate:${purchase.transactionId}`,
|
|
4986
|
-
"critical"
|
|
4987
|
-
);
|
|
4988
|
-
if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
|
|
4989
|
-
} catch (err) {
|
|
4990
|
-
console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
|
|
4991
|
-
}
|
|
4992
|
-
}
|
|
4993
|
-
};
|
|
4994
|
-
}
|
|
4995
|
-
});
|
|
4996
|
-
|
|
4997
|
-
// src/domains/iap/NativeStoreKit.ts
|
|
4998
|
-
var NativeStoreKit_exports = {};
|
|
4999
|
-
__export(NativeStoreKit_exports, {
|
|
5000
|
-
NativeStoreKit: () => NativeStoreKit,
|
|
5001
|
-
nativeStoreKit: () => nativeStoreKit
|
|
5002
|
-
});
|
|
5003
|
-
function isAvailable3() {
|
|
5004
|
-
return PaywalloStoreKitNative != null;
|
|
5005
|
-
}
|
|
5006
|
-
function mapNativeProduct(native) {
|
|
5007
|
-
const product = {
|
|
5008
|
-
productId: native.productId,
|
|
5009
|
-
title: native.title,
|
|
5010
|
-
description: native.description,
|
|
5011
|
-
price: native.price,
|
|
5012
|
-
priceValue: native.priceValue,
|
|
5013
|
-
currency: native.currency ?? "USD",
|
|
5014
|
-
localizedPrice: native.localizedPrice,
|
|
5015
|
-
type: mapProductType(native.type),
|
|
5016
|
-
subscriptionPeriod: native.subscriptionPeriod,
|
|
5017
|
-
introductoryPrice: native.introductoryPrice,
|
|
5018
|
-
introductoryPriceValue: native.introductoryPriceValue,
|
|
5019
|
-
freeTrialPeriod: native.freeTrialPeriod
|
|
5020
|
-
};
|
|
5021
|
-
if (native.subscriptionPeriod) {
|
|
5022
|
-
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
5023
|
-
product.pricePerMonth = prices.perMonth;
|
|
5024
|
-
product.pricePerWeek = prices.perWeek;
|
|
5025
|
-
product.pricePerDay = prices.perDay;
|
|
5026
|
-
}
|
|
5027
|
-
return product;
|
|
5028
|
-
}
|
|
5029
|
-
function mapProductType(type) {
|
|
5030
|
-
switch (type) {
|
|
5031
|
-
case "subscription":
|
|
5032
|
-
case "autoRenewable":
|
|
5033
|
-
return "subscription";
|
|
5034
|
-
case "consumable":
|
|
5035
|
-
return "consumable";
|
|
5036
|
-
default:
|
|
5037
|
-
return "non_consumable";
|
|
5038
|
-
}
|
|
5039
|
-
}
|
|
5040
|
-
function calculatePerPeriodPrices(priceValue, period) {
|
|
5041
|
-
const totalDays = periodToDays(period);
|
|
5042
|
-
if (totalDays <= 0) return {};
|
|
5043
|
-
const perDay = priceValue / totalDays;
|
|
5044
|
-
const perWeek = perDay * 7;
|
|
5045
|
-
const perMonth = perDay * 30;
|
|
5046
|
-
return {
|
|
5047
|
-
perDay: perDay.toFixed(2),
|
|
5048
|
-
perWeek: perWeek.toFixed(2),
|
|
5049
|
-
perMonth: perMonth.toFixed(2)
|
|
5050
|
-
};
|
|
5051
|
-
}
|
|
5052
|
-
function periodToDays(period) {
|
|
5053
|
-
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
5054
|
-
if (!match) return 0;
|
|
5055
|
-
const value = parseInt(match[1], 10);
|
|
5056
|
-
switch (match[2]) {
|
|
5057
|
-
case "D":
|
|
5058
|
-
return value;
|
|
5059
|
-
case "W":
|
|
5060
|
-
return value * 7;
|
|
5061
|
-
case "M":
|
|
5062
|
-
return value * 30;
|
|
5063
|
-
case "Y":
|
|
5064
|
-
return value * 365;
|
|
5065
|
-
default:
|
|
5066
|
-
return 0;
|
|
5067
|
-
}
|
|
5068
|
-
}
|
|
5069
|
-
function mapNativePurchase(native) {
|
|
5070
|
-
return {
|
|
5071
|
-
productId: native.productId,
|
|
5072
|
-
transactionId: native.transactionId,
|
|
5073
|
-
transactionDate: native.transactionDate,
|
|
5074
|
-
receipt: native.receipt,
|
|
5075
|
-
platform: import_react_native18.Platform.OS
|
|
5076
|
-
};
|
|
5077
|
-
}
|
|
5078
|
-
function normalizeTransactionUpdate(raw) {
|
|
5079
|
-
if (!raw || typeof raw !== "object") return null;
|
|
5080
|
-
const r = raw;
|
|
5081
|
-
const type = r.type;
|
|
5082
|
-
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
5083
|
-
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
5084
|
-
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
5085
|
-
if (!transactionId) return null;
|
|
5086
|
-
const update = {
|
|
5087
|
-
type,
|
|
5088
|
-
transactionId,
|
|
5089
|
-
productId: productId ?? ""
|
|
5090
|
-
};
|
|
5091
|
-
if (typeof r.amount === "number") update.amount = r.amount;
|
|
5092
|
-
if (typeof r.currency === "string") update.currency = r.currency;
|
|
5093
|
-
return update;
|
|
5094
|
-
}
|
|
5095
|
-
var import_react_native18, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
5096
|
-
var init_NativeStoreKit = __esm({
|
|
5097
|
-
"src/domains/iap/NativeStoreKit.ts"() {
|
|
5098
|
-
"use strict";
|
|
5099
|
-
import_react_native18 = require("react-native");
|
|
5100
|
-
init_PaywalloClient();
|
|
5101
|
-
init_uuid();
|
|
5102
|
-
init_IdentityManager();
|
|
5103
|
-
init_PurchaseError();
|
|
5104
|
-
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
5105
|
-
PaywalloStoreKitNative = import_react_native18.NativeModules.PaywalloStoreKit ?? null;
|
|
5106
|
-
nativeStoreKit = {
|
|
5107
|
-
isAvailable: isAvailable3,
|
|
5108
|
-
async getProducts(productIds) {
|
|
5109
|
-
if (!PaywalloStoreKitNative) {
|
|
5110
|
-
return [];
|
|
5111
|
-
}
|
|
5112
|
-
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
5113
|
-
return nativeProducts.map(mapNativeProduct);
|
|
5114
|
-
},
|
|
5115
|
-
async purchase(productId) {
|
|
5116
|
-
if (!PaywalloStoreKitNative) {
|
|
5117
|
-
throw new PurchaseError(
|
|
5118
|
-
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
5119
|
-
"StoreKit native module not available"
|
|
5120
|
-
);
|
|
5121
|
-
}
|
|
5122
|
-
const distinctId = identityManager.getDistinctId();
|
|
5123
|
-
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
5124
|
-
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
5125
|
-
if (distinctId && appAccountToken === null) {
|
|
5126
|
-
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
5127
|
-
}
|
|
5128
|
-
try {
|
|
5129
|
-
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
5130
|
-
if (!result || result.pending) {
|
|
5131
|
-
return null;
|
|
5132
|
-
}
|
|
5133
|
-
return mapNativePurchase(result);
|
|
5134
|
-
} catch (err) {
|
|
5135
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5136
|
-
if (message.toLowerCase().includes("cancel")) {
|
|
5137
|
-
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
5138
|
-
}
|
|
5139
|
-
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
5140
|
-
}
|
|
5141
|
-
},
|
|
5142
|
-
async finishTransaction(transactionId) {
|
|
5143
|
-
if (!PaywalloStoreKitNative) return;
|
|
5144
|
-
try {
|
|
5145
|
-
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
5146
|
-
} catch (err) {
|
|
5147
|
-
console.error("[Paywallo] finishTransaction native error", err);
|
|
5148
|
-
}
|
|
5149
|
-
},
|
|
5150
|
-
async getActiveTransactions() {
|
|
5151
|
-
if (!PaywalloStoreKitNative) return [];
|
|
5152
|
-
try {
|
|
5153
|
-
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
5154
|
-
return results.map(mapNativePurchase);
|
|
5155
|
-
} catch (err) {
|
|
5156
|
-
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
5157
|
-
return [];
|
|
5427
|
+
if (error instanceof PurchaseError && typeof error.httpStatus === "number") {
|
|
5428
|
+
const status = error.httpStatus;
|
|
5429
|
+
if (status >= 400 && status < 500) return "server4xx";
|
|
5158
5430
|
}
|
|
5159
|
-
|
|
5431
|
+
return "network";
|
|
5432
|
+
}
|
|
5160
5433
|
/**
|
|
5161
|
-
*
|
|
5162
|
-
*
|
|
5163
|
-
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
5164
|
-
*
|
|
5165
|
-
* Returns a subscription with a `remove()` method. The caller is
|
|
5166
|
-
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
5167
|
-
*
|
|
5168
|
-
* Returns `null` when the native module isn't available (non-mobile env,
|
|
5169
|
-
* native module not linked) — callers should treat null as a no-op.
|
|
5434
|
+
* Pushes the validation payload into the offline queue for durable retry.
|
|
5435
|
+
* The StoreKit transaction is intentionally left un-finished.
|
|
5170
5436
|
*/
|
|
5171
|
-
|
|
5172
|
-
if (!PaywalloStoreKitNative) return null;
|
|
5437
|
+
async enqueueValidationRetry(purchase, productId, options) {
|
|
5173
5438
|
try {
|
|
5174
|
-
const
|
|
5175
|
-
const
|
|
5176
|
-
const
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5439
|
+
const apiClient = this.getApiClientFn();
|
|
5440
|
+
const product = this.getProductFromCache(productId);
|
|
5441
|
+
const platform = import_react_native18.Platform.OS;
|
|
5442
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
5443
|
+
const appKey = apiClient.getAppKey();
|
|
5444
|
+
const body = {
|
|
5445
|
+
platform,
|
|
5446
|
+
receipt_data: purchase.receipt,
|
|
5447
|
+
product_id: purchase.productId,
|
|
5448
|
+
transaction_id: purchase.transactionId,
|
|
5449
|
+
price_local: product?.priceValue ?? 0,
|
|
5450
|
+
currency: product?.currency ?? "USD",
|
|
5451
|
+
country: this.getDeviceCountry(),
|
|
5452
|
+
distinct_id: distinctId
|
|
5453
|
+
};
|
|
5454
|
+
if (options?.paywallPlacement !== void 0) body.paywall_placement = options.paywallPlacement;
|
|
5455
|
+
if (options?.variantKey !== void 0) body.variant_key = options.variantKey;
|
|
5456
|
+
await offlineQueue.enqueue(
|
|
5457
|
+
"POST",
|
|
5458
|
+
"/sdk/purchases/validate",
|
|
5459
|
+
body,
|
|
5460
|
+
{ "X-App-Key": appKey },
|
|
5461
|
+
`validate:${purchase.transactionId}`,
|
|
5462
|
+
"critical"
|
|
5463
|
+
);
|
|
5464
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
|
|
5181
5465
|
} catch (err) {
|
|
5182
|
-
|
|
5183
|
-
return null;
|
|
5466
|
+
console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
|
|
5184
5467
|
}
|
|
5185
5468
|
}
|
|
5186
5469
|
};
|
|
5187
|
-
NativeStoreKit = nativeStoreKit;
|
|
5188
5470
|
}
|
|
5189
5471
|
});
|
|
5190
5472
|
|
|
@@ -5288,8 +5570,8 @@ var init_IAPService = __esm({
|
|
|
5288
5570
|
stopTransactionListener() {
|
|
5289
5571
|
this.emitter.stopListener();
|
|
5290
5572
|
}
|
|
5291
|
-
|
|
5292
|
-
|
|
5573
|
+
async emitTransactionUpdate(update) {
|
|
5574
|
+
return this.emitter.emitTransactionUpdate(update);
|
|
5293
5575
|
}
|
|
5294
5576
|
async purchase(productId, options) {
|
|
5295
5577
|
if (!nativeStoreKit.isAvailable()) {
|
|
@@ -5318,6 +5600,10 @@ var init_IAPService = __esm({
|
|
|
5318
5600
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
5319
5601
|
await this.finishTransaction(purchase.transactionId);
|
|
5320
5602
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
5603
|
+
await this.emitter.emitTransactionCompleted(purchase, productId, {
|
|
5604
|
+
paywallId: options?.paywallId,
|
|
5605
|
+
variantId: options?.variantId
|
|
5606
|
+
});
|
|
5321
5607
|
}).catch(async (err) => {
|
|
5322
5608
|
const kind = this.validator.classifyValidationError(err);
|
|
5323
5609
|
if (kind === "server4xx") {
|
|
@@ -6299,254 +6585,117 @@ var init_PlanService = __esm({
|
|
|
6299
6585
|
(p) => p.identifier === cached2.data.currentIdentifier
|
|
6300
6586
|
);
|
|
6301
6587
|
return current ?? null;
|
|
6302
|
-
}
|
|
6303
|
-
}
|
|
6304
|
-
if (!this.cache.get()) {
|
|
6305
|
-
try {
|
|
6306
|
-
const stored = await secureStorage.get(ALL_PLANS_CACHE_KEY);
|
|
6307
|
-
if (stored) {
|
|
6308
|
-
const parsed = JSON.parse(stored);
|
|
6309
|
-
this.cache.set(parsed);
|
|
6310
|
-
}
|
|
6311
|
-
} catch {
|
|
6312
|
-
}
|
|
6313
|
-
}
|
|
6314
|
-
const memCached = this.cache.get();
|
|
6315
|
-
if (memCached) {
|
|
6316
|
-
void this.fetchAllPlans();
|
|
6317
|
-
const current = memCached.data.plans.find(
|
|
6318
|
-
(p) => p.identifier === memCached.data.currentIdentifier
|
|
6319
|
-
);
|
|
6320
|
-
return current ?? null;
|
|
6321
|
-
}
|
|
6322
|
-
return this.fetchCurrentPlan();
|
|
6323
|
-
}
|
|
6324
|
-
async getAllPlans(forceRefresh = false) {
|
|
6325
|
-
if (!forceRefresh) {
|
|
6326
|
-
const cached2 = this.cache.get();
|
|
6327
|
-
if (cached2 && !cached2.isStale) return cached2.data;
|
|
6328
|
-
}
|
|
6329
|
-
if (!this.cache.get()) {
|
|
6330
|
-
try {
|
|
6331
|
-
const stored = await secureStorage.get(ALL_PLANS_CACHE_KEY);
|
|
6332
|
-
if (stored) {
|
|
6333
|
-
const parsed = JSON.parse(stored);
|
|
6334
|
-
this.cache.set(parsed);
|
|
6335
|
-
}
|
|
6336
|
-
} catch {
|
|
6337
|
-
}
|
|
6338
|
-
}
|
|
6339
|
-
const memCached = this.cache.get();
|
|
6340
|
-
if (memCached) {
|
|
6341
|
-
void this.fetchAllPlans();
|
|
6342
|
-
return memCached.data;
|
|
6343
|
-
}
|
|
6344
|
-
return this.fetchAllPlans();
|
|
6345
|
-
}
|
|
6346
|
-
invalidateCache() {
|
|
6347
|
-
this.cache.invalidate();
|
|
6348
|
-
secureStorage.remove(PLAN_CACHE_KEY).catch(() => void 0);
|
|
6349
|
-
secureStorage.remove(ALL_PLANS_CACHE_KEY).catch(() => void 0);
|
|
6350
|
-
}
|
|
6351
|
-
getApiClient() {
|
|
6352
|
-
if (!this.config) {
|
|
6353
|
-
throw new PlanError(PLAN_ERROR_CODES.NOT_INITIALIZED, "PlanService not initialized. Call Paywallo.init() first");
|
|
6354
|
-
}
|
|
6355
|
-
return this.config.apiClient;
|
|
6356
|
-
}
|
|
6357
|
-
async fetchCurrentPlan() {
|
|
6358
|
-
try {
|
|
6359
|
-
const res = await this.getApiClient().get("/sdk/plans");
|
|
6360
|
-
if (!res.ok) {
|
|
6361
|
-
this.log("fetchCurrentPlan failed", res.status);
|
|
6362
|
-
return null;
|
|
6363
|
-
}
|
|
6364
|
-
const raw = res.data.data.plan;
|
|
6365
|
-
return raw ? mapPlan(raw) : null;
|
|
6366
|
-
} catch (err) {
|
|
6367
|
-
this.log("fetchCurrentPlan error", err);
|
|
6368
|
-
return null;
|
|
6369
|
-
}
|
|
6370
|
-
}
|
|
6371
|
-
async fetchAllPlans() {
|
|
6372
|
-
try {
|
|
6373
|
-
const res = await this.getApiClient().get("/sdk/plans/all");
|
|
6374
|
-
if (!res.ok) {
|
|
6375
|
-
this.log("fetchAllPlans failed", res.status);
|
|
6376
|
-
return { plans: [], currentIdentifier: null };
|
|
6377
|
-
}
|
|
6378
|
-
const result = {
|
|
6379
|
-
plans: res.data.data.plans.map(mapPlan),
|
|
6380
|
-
currentIdentifier: res.data.data.current_identifier
|
|
6381
|
-
};
|
|
6382
|
-
this.cache.set(result);
|
|
6383
|
-
secureStorage.set(ALL_PLANS_CACHE_KEY, JSON.stringify(result)).catch(() => void 0);
|
|
6384
|
-
return result;
|
|
6385
|
-
} catch (err) {
|
|
6386
|
-
this.log("fetchAllPlans error", err);
|
|
6387
|
-
return { plans: [], currentIdentifier: null };
|
|
6388
|
-
}
|
|
6389
|
-
}
|
|
6390
|
-
log(...args) {
|
|
6391
|
-
if (this.config?.debug) console.log("[Paywallo Plans]", ...args);
|
|
6392
|
-
}
|
|
6393
|
-
};
|
|
6394
|
-
planService = new PlanService();
|
|
6395
|
-
}
|
|
6396
|
-
});
|
|
6397
|
-
|
|
6398
|
-
// src/domains/plan/index.ts
|
|
6399
|
-
var init_plan = __esm({
|
|
6400
|
-
"src/domains/plan/index.ts"() {
|
|
6401
|
-
"use strict";
|
|
6402
|
-
init_PlanCache();
|
|
6403
|
-
init_PlanService();
|
|
6404
|
-
}
|
|
6405
|
-
});
|
|
6406
|
-
|
|
6407
|
-
// src/domains/session/index.ts
|
|
6408
|
-
var init_session = __esm({
|
|
6409
|
-
"src/domains/session/index.ts"() {
|
|
6410
|
-
"use strict";
|
|
6411
|
-
init_SessionError();
|
|
6412
|
-
init_SessionManager();
|
|
6413
|
-
}
|
|
6414
|
-
});
|
|
6415
|
-
|
|
6416
|
-
// src/domains/onboarding/OnboardingError.ts
|
|
6417
|
-
var OnboardingError, ONBOARDING_ERROR_CODES;
|
|
6418
|
-
var init_OnboardingError = __esm({
|
|
6419
|
-
"src/domains/onboarding/OnboardingError.ts"() {
|
|
6420
|
-
"use strict";
|
|
6421
|
-
init_PaywalloError();
|
|
6422
|
-
OnboardingError = class extends PaywalloError {
|
|
6423
|
-
constructor(code, message) {
|
|
6424
|
-
super("onboarding", code, message);
|
|
6425
|
-
this.name = "OnboardingError";
|
|
6426
|
-
}
|
|
6427
|
-
};
|
|
6428
|
-
ONBOARDING_ERROR_CODES = {
|
|
6429
|
-
NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
|
|
6430
|
-
INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
|
|
6431
|
-
};
|
|
6432
|
-
}
|
|
6433
|
-
});
|
|
6434
|
-
|
|
6435
|
-
// src/domains/onboarding/OnboardingManager.ts
|
|
6436
|
-
var OnboardingManager, onboardingManager;
|
|
6437
|
-
var init_OnboardingManager = __esm({
|
|
6438
|
-
"src/domains/onboarding/OnboardingManager.ts"() {
|
|
6439
|
-
"use strict";
|
|
6440
|
-
init_OnboardingError();
|
|
6441
|
-
OnboardingManager = class {
|
|
6442
|
-
constructor() {
|
|
6443
|
-
this.pipeline = null;
|
|
6444
|
-
this.debug = false;
|
|
6445
|
-
this.lastStep = null;
|
|
6446
|
-
this.finished = false;
|
|
6447
|
-
}
|
|
6448
|
-
/**
|
|
6449
|
-
* Injects runtime dependencies. Called by PaywalloClient during init.
|
|
6450
|
-
*/
|
|
6451
|
-
injectDeps(config) {
|
|
6452
|
-
this.pipeline = config.pipeline;
|
|
6453
|
-
this.debug = config.debug ?? false;
|
|
6454
|
-
}
|
|
6455
|
-
/**
|
|
6456
|
-
* Tracks an onboarding step view.
|
|
6457
|
-
* Saves the step name internally for implicit drop detection on the backend.
|
|
6458
|
-
*
|
|
6459
|
-
* @param stepName - Unique name for this onboarding step.
|
|
6460
|
-
* @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
|
|
6461
|
-
*/
|
|
6462
|
-
async step(stepName, order) {
|
|
6463
|
-
this.assertConfig();
|
|
6464
|
-
this.validateStepName(stepName, "step");
|
|
6465
|
-
this.lastStep = stepName;
|
|
6466
|
-
const stepPayload = { step_name: stepName };
|
|
6467
|
-
if (order !== void 0 && Number.isFinite(order) && order >= 0) {
|
|
6468
|
-
stepPayload.order = order;
|
|
6469
|
-
}
|
|
6470
|
-
const payload = {
|
|
6471
|
-
family: "onboarding",
|
|
6472
|
-
type: "step",
|
|
6473
|
-
...stepPayload
|
|
6474
|
-
};
|
|
6475
|
-
return this.emit("onboarding", payload, "step");
|
|
6476
|
-
}
|
|
6477
|
-
/**
|
|
6478
|
-
* Tracks successful completion of the onboarding flow.
|
|
6479
|
-
* Marks the flow as finished.
|
|
6480
|
-
*/
|
|
6481
|
-
async complete() {
|
|
6482
|
-
this.assertConfig();
|
|
6483
|
-
this.finished = true;
|
|
6484
|
-
return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
|
|
6485
|
-
}
|
|
6486
|
-
/**
|
|
6487
|
-
* Tracks an explicit drop (abandonment) at the given step.
|
|
6488
|
-
* Marks the flow as finished.
|
|
6489
|
-
*
|
|
6490
|
-
* @param stepName - The step name where the user dropped off.
|
|
6491
|
-
*/
|
|
6492
|
-
async drop(stepName) {
|
|
6493
|
-
this.assertConfig();
|
|
6494
|
-
this.validateStepName(stepName, "drop");
|
|
6495
|
-
this.finished = true;
|
|
6496
|
-
const payload = {
|
|
6497
|
-
family: "onboarding",
|
|
6498
|
-
type: "drop",
|
|
6499
|
-
...{ step_name: stepName }
|
|
6500
|
-
};
|
|
6501
|
-
return this.emit("onboarding", payload, "drop");
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
if (!this.cache.get()) {
|
|
6591
|
+
try {
|
|
6592
|
+
const stored = await secureStorage.get(ALL_PLANS_CACHE_KEY);
|
|
6593
|
+
if (stored) {
|
|
6594
|
+
const parsed = JSON.parse(stored);
|
|
6595
|
+
this.cache.set(parsed);
|
|
6596
|
+
}
|
|
6597
|
+
} catch {
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
const memCached = this.cache.get();
|
|
6601
|
+
if (memCached) {
|
|
6602
|
+
void this.fetchAllPlans();
|
|
6603
|
+
const current = memCached.data.plans.find(
|
|
6604
|
+
(p) => p.identifier === memCached.data.currentIdentifier
|
|
6605
|
+
);
|
|
6606
|
+
return current ?? null;
|
|
6607
|
+
}
|
|
6608
|
+
return this.fetchCurrentPlan();
|
|
6502
6609
|
}
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6610
|
+
async getAllPlans(forceRefresh = false) {
|
|
6611
|
+
if (!forceRefresh) {
|
|
6612
|
+
const cached2 = this.cache.get();
|
|
6613
|
+
if (cached2 && !cached2.isStale) return cached2.data;
|
|
6614
|
+
}
|
|
6615
|
+
if (!this.cache.get()) {
|
|
6616
|
+
try {
|
|
6617
|
+
const stored = await secureStorage.get(ALL_PLANS_CACHE_KEY);
|
|
6618
|
+
if (stored) {
|
|
6619
|
+
const parsed = JSON.parse(stored);
|
|
6620
|
+
this.cache.set(parsed);
|
|
6621
|
+
}
|
|
6622
|
+
} catch {
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
const memCached = this.cache.get();
|
|
6626
|
+
if (memCached) {
|
|
6627
|
+
void this.fetchAllPlans();
|
|
6628
|
+
return memCached.data;
|
|
6629
|
+
}
|
|
6630
|
+
return this.fetchAllPlans();
|
|
6506
6631
|
}
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6632
|
+
invalidateCache() {
|
|
6633
|
+
this.cache.invalidate();
|
|
6634
|
+
secureStorage.remove(PLAN_CACHE_KEY).catch(() => void 0);
|
|
6635
|
+
secureStorage.remove(ALL_PLANS_CACHE_KEY).catch(() => void 0);
|
|
6510
6636
|
}
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
if (!distinctId) {
|
|
6515
|
-
if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
|
|
6516
|
-
return;
|
|
6517
|
-
}
|
|
6518
|
-
await pipeline.trackEvent(eventName, distinctId, properties);
|
|
6519
|
-
if (this.debug) {
|
|
6520
|
-
console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
|
|
6637
|
+
getApiClient() {
|
|
6638
|
+
if (!this.config) {
|
|
6639
|
+
throw new PlanError(PLAN_ERROR_CODES.NOT_INITIALIZED, "PlanService not initialized. Call Paywallo.init() first");
|
|
6521
6640
|
}
|
|
6641
|
+
return this.config.apiClient;
|
|
6522
6642
|
}
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
"
|
|
6528
|
-
|
|
6643
|
+
async fetchCurrentPlan() {
|
|
6644
|
+
try {
|
|
6645
|
+
const res = await this.getApiClient().get("/sdk/plans");
|
|
6646
|
+
if (!res.ok) {
|
|
6647
|
+
this.log("fetchCurrentPlan failed", res.status);
|
|
6648
|
+
return null;
|
|
6649
|
+
}
|
|
6650
|
+
const raw = res.data.data.plan;
|
|
6651
|
+
return raw ? mapPlan(raw) : null;
|
|
6652
|
+
} catch (err) {
|
|
6653
|
+
this.log("fetchCurrentPlan error", err);
|
|
6654
|
+
return null;
|
|
6529
6655
|
}
|
|
6530
6656
|
}
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6657
|
+
async fetchAllPlans() {
|
|
6658
|
+
try {
|
|
6659
|
+
const res = await this.getApiClient().get("/sdk/plans/all");
|
|
6660
|
+
if (!res.ok) {
|
|
6661
|
+
this.log("fetchAllPlans failed", res.status);
|
|
6662
|
+
return { plans: [], currentIdentifier: null };
|
|
6663
|
+
}
|
|
6664
|
+
const result = {
|
|
6665
|
+
plans: res.data.data.plans.map(mapPlan),
|
|
6666
|
+
currentIdentifier: res.data.data.current_identifier
|
|
6667
|
+
};
|
|
6668
|
+
this.cache.set(result);
|
|
6669
|
+
secureStorage.set(ALL_PLANS_CACHE_KEY, JSON.stringify(result)).catch(() => void 0);
|
|
6670
|
+
return result;
|
|
6671
|
+
} catch (err) {
|
|
6672
|
+
this.log("fetchAllPlans error", err);
|
|
6673
|
+
return { plans: [], currentIdentifier: null };
|
|
6537
6674
|
}
|
|
6538
6675
|
}
|
|
6676
|
+
log(...args) {
|
|
6677
|
+
if (this.config?.debug) console.log("[Paywallo Plans]", ...args);
|
|
6678
|
+
}
|
|
6539
6679
|
};
|
|
6540
|
-
|
|
6680
|
+
planService = new PlanService();
|
|
6541
6681
|
}
|
|
6542
6682
|
});
|
|
6543
6683
|
|
|
6544
|
-
// src/domains/
|
|
6545
|
-
var
|
|
6546
|
-
"src/domains/
|
|
6684
|
+
// src/domains/plan/index.ts
|
|
6685
|
+
var init_plan = __esm({
|
|
6686
|
+
"src/domains/plan/index.ts"() {
|
|
6547
6687
|
"use strict";
|
|
6548
|
-
|
|
6549
|
-
|
|
6688
|
+
init_PlanCache();
|
|
6689
|
+
init_PlanService();
|
|
6690
|
+
}
|
|
6691
|
+
});
|
|
6692
|
+
|
|
6693
|
+
// src/domains/session/index.ts
|
|
6694
|
+
var init_session = __esm({
|
|
6695
|
+
"src/domains/session/index.ts"() {
|
|
6696
|
+
"use strict";
|
|
6697
|
+
init_SessionError();
|
|
6698
|
+
init_SessionManager();
|
|
6550
6699
|
}
|
|
6551
6700
|
});
|
|
6552
6701
|
|
|
@@ -6622,7 +6771,21 @@ var init_schemas = __esm({
|
|
|
6622
6771
|
// `closed`-specific
|
|
6623
6772
|
closed_at: import_zod.z.string().optional(),
|
|
6624
6773
|
duration_s: import_zod.z.number().nonnegative().optional(),
|
|
6625
|
-
|
|
6774
|
+
// Two emitter dialects coexist: the canonical emitters
|
|
6775
|
+
// (`emitPaywallClosed`/heartbeat) send "dismiss"/"cta"/"purchase"/"error"/
|
|
6776
|
+
// "timeout"; the SuperwallAutoBridge forwards the server-enum values
|
|
6777
|
+
// "purchased"/"dismissed"/"restored" straight through. The server accepts
|
|
6778
|
+
// both — the schema must too, or every bridge close fails debug validation.
|
|
6779
|
+
close_reason: import_zod.z.enum([
|
|
6780
|
+
"dismiss",
|
|
6781
|
+
"cta",
|
|
6782
|
+
"purchase",
|
|
6783
|
+
"error",
|
|
6784
|
+
"timeout",
|
|
6785
|
+
"purchased",
|
|
6786
|
+
"dismissed",
|
|
6787
|
+
"restored"
|
|
6788
|
+
]).optional(),
|
|
6626
6789
|
scroll_depth: import_zod.z.number().min(0).max(1).optional(),
|
|
6627
6790
|
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6628
6791
|
product_id: import_zod.z.string().optional()
|
|
@@ -6630,21 +6793,33 @@ var init_schemas = __esm({
|
|
|
6630
6793
|
transactionEventSchema = import_zod.z.object({
|
|
6631
6794
|
type: import_zod.z.enum([
|
|
6632
6795
|
"completed",
|
|
6796
|
+
"trial_started",
|
|
6633
6797
|
"failed",
|
|
6634
6798
|
"refunded",
|
|
6635
6799
|
"renewed",
|
|
6636
6800
|
"canceled",
|
|
6637
6801
|
"expired"
|
|
6638
6802
|
]),
|
|
6639
|
-
|
|
6803
|
+
// Server contract key is `transaction_id` (what IngestTranslator reads and
|
|
6804
|
+
// the SuperwallAutoBridge emits); `tx_id` is the legacy key still emitted
|
|
6805
|
+
// by IAPTransactionEmitter — the server accepts both. At least one is
|
|
6806
|
+
// required (enforced by the refine below).
|
|
6807
|
+
transaction_id: import_zod.z.string().min(1).optional(),
|
|
6808
|
+
tx_id: import_zod.z.string().min(1).optional(),
|
|
6640
6809
|
amount: import_zod.z.number().optional(),
|
|
6810
|
+
// Full (post-trial) price carried on trial_started for analytics, since
|
|
6811
|
+
// `amount` is 0 for a free trial.
|
|
6812
|
+
full_price: import_zod.z.number().nonnegative().optional(),
|
|
6641
6813
|
currency: import_zod.z.string().length(3).optional(),
|
|
6642
6814
|
product_id: import_zod.z.string().optional(),
|
|
6643
6815
|
subscription_id: import_zod.z.string().optional(),
|
|
6644
6816
|
paywall_id: import_zod.z.string().optional(),
|
|
6645
6817
|
variant_id: import_zod.z.string().optional(),
|
|
6646
6818
|
reason: import_zod.z.string().optional()
|
|
6647
|
-
}).strict()
|
|
6819
|
+
}).strict().refine((v) => v.transaction_id !== void 0 || v.tx_id !== void 0, {
|
|
6820
|
+
message: "transaction requires transaction_id (or legacy tx_id)",
|
|
6821
|
+
path: ["transaction_id"]
|
|
6822
|
+
});
|
|
6648
6823
|
onboardingEventSchema = import_zod.z.object({
|
|
6649
6824
|
type: import_zod.z.enum(["step", "complete", "drop"]),
|
|
6650
6825
|
step_index: import_zod.z.number().int().nonnegative().optional(),
|
|
@@ -7474,8 +7649,13 @@ var init_ApiClientFlags = __esm({
|
|
|
7474
7649
|
});
|
|
7475
7650
|
|
|
7476
7651
|
// src/core/ApiClientQueue.ts
|
|
7652
|
+
function isServerError(res) {
|
|
7653
|
+
if (typeof res !== "object" || res === null) return false;
|
|
7654
|
+
const r = res;
|
|
7655
|
+
return r.ok === false && typeof r.status === "number" && r.status >= 500;
|
|
7656
|
+
}
|
|
7477
7657
|
async function postWithQueue(deps, url, payload, label, priority) {
|
|
7478
|
-
|
|
7658
|
+
const enqueue = async () => {
|
|
7479
7659
|
await offlineQueue.enqueue(
|
|
7480
7660
|
"POST",
|
|
7481
7661
|
url,
|
|
@@ -7484,22 +7664,22 @@ async function postWithQueue(deps, url, payload, label, priority) {
|
|
|
7484
7664
|
void 0,
|
|
7485
7665
|
priority
|
|
7486
7666
|
);
|
|
7667
|
+
};
|
|
7668
|
+
if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
|
|
7669
|
+
await enqueue();
|
|
7487
7670
|
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Queued for offline:", label, priority ?? "normal");
|
|
7488
7671
|
return;
|
|
7489
7672
|
}
|
|
7490
7673
|
try {
|
|
7491
|
-
await deps.post(url, payload);
|
|
7674
|
+
const res = await deps.post(url, payload);
|
|
7675
|
+
if (isServerError(res) && deps.isOfflineQueueEnabled()) {
|
|
7676
|
+
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Server error, queued for retry:", label, priority ?? "normal");
|
|
7677
|
+
await enqueue();
|
|
7678
|
+
}
|
|
7492
7679
|
} catch (error) {
|
|
7493
7680
|
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
|
|
7494
7681
|
if (deps.isOfflineQueueEnabled()) {
|
|
7495
|
-
await
|
|
7496
|
-
"POST",
|
|
7497
|
-
url,
|
|
7498
|
-
payload,
|
|
7499
|
-
{ "X-App-Key": deps.getAppKey() },
|
|
7500
|
-
void 0,
|
|
7501
|
-
priority
|
|
7502
|
-
);
|
|
7682
|
+
await enqueue();
|
|
7503
7683
|
return;
|
|
7504
7684
|
}
|
|
7505
7685
|
throw error;
|
|
@@ -8905,11 +9085,11 @@ var init_PaywalloClient = __esm({
|
|
|
8905
9085
|
init_campaign();
|
|
8906
9086
|
init_identity();
|
|
8907
9087
|
init_notifications();
|
|
9088
|
+
init_onboarding();
|
|
8908
9089
|
init_paywall();
|
|
8909
9090
|
init_plan();
|
|
8910
9091
|
init_session();
|
|
8911
9092
|
init_ClientError();
|
|
8912
|
-
init_onboarding();
|
|
8913
9093
|
init_network();
|
|
8914
9094
|
init_PaywalloAnalytics();
|
|
8915
9095
|
init_PaywalloFlags();
|
|
@@ -9616,8 +9796,41 @@ function usePlanPurchase() {
|
|
|
9616
9796
|
};
|
|
9617
9797
|
}
|
|
9618
9798
|
|
|
9619
|
-
// src/hooks/
|
|
9799
|
+
// src/hooks/usePaywalloScreenTracking.ts
|
|
9620
9800
|
var import_react18 = require("react");
|
|
9801
|
+
init_PaywalloClient();
|
|
9802
|
+
function isNavState(value) {
|
|
9803
|
+
return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
|
|
9804
|
+
}
|
|
9805
|
+
function getActiveRouteName(state) {
|
|
9806
|
+
try {
|
|
9807
|
+
if (!isNavState(state)) return null;
|
|
9808
|
+
const route = state.routes[state.index];
|
|
9809
|
+
if (!route) return null;
|
|
9810
|
+
if (isNavState(route.state)) {
|
|
9811
|
+
return getActiveRouteName(route.state);
|
|
9812
|
+
}
|
|
9813
|
+
return typeof route.name === "string" ? route.name : null;
|
|
9814
|
+
} catch {
|
|
9815
|
+
return null;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
function usePaywalloScreenTracking() {
|
|
9819
|
+
const lastScreenRef = (0, import_react18.useRef)(null);
|
|
9820
|
+
return (0, import_react18.useCallback)((state) => {
|
|
9821
|
+
if (!PaywalloClient.isReady()) return;
|
|
9822
|
+
const screenName = getActiveRouteName(state);
|
|
9823
|
+
if (!screenName || screenName === lastScreenRef.current) return;
|
|
9824
|
+
lastScreenRef.current = screenName;
|
|
9825
|
+
void PaywalloClient.track("screen_view", {
|
|
9826
|
+
properties: { screen_name: screenName }
|
|
9827
|
+
}).catch(() => {
|
|
9828
|
+
});
|
|
9829
|
+
}, []);
|
|
9830
|
+
}
|
|
9831
|
+
|
|
9832
|
+
// src/hooks/usePlans.ts
|
|
9833
|
+
var import_react19 = require("react");
|
|
9621
9834
|
async function getPlanService() {
|
|
9622
9835
|
try {
|
|
9623
9836
|
const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
|
|
@@ -9627,11 +9840,11 @@ async function getPlanService() {
|
|
|
9627
9840
|
}
|
|
9628
9841
|
}
|
|
9629
9842
|
function usePlans() {
|
|
9630
|
-
const [allPlans, setAllPlans] = (0,
|
|
9631
|
-
const [currentPlan, setCurrentPlan] = (0,
|
|
9632
|
-
const [isLoading, setIsLoading] = (0,
|
|
9633
|
-
const [error, setError] = (0,
|
|
9634
|
-
const fetchPlans = (0,
|
|
9843
|
+
const [allPlans, setAllPlans] = (0, import_react19.useState)([]);
|
|
9844
|
+
const [currentPlan, setCurrentPlan] = (0, import_react19.useState)(null);
|
|
9845
|
+
const [isLoading, setIsLoading] = (0, import_react19.useState)(true);
|
|
9846
|
+
const [error, setError] = (0, import_react19.useState)(null);
|
|
9847
|
+
const fetchPlans = (0, import_react19.useCallback)(async (forceRefresh = false) => {
|
|
9635
9848
|
setIsLoading(true);
|
|
9636
9849
|
setError(null);
|
|
9637
9850
|
try {
|
|
@@ -9652,17 +9865,17 @@ function usePlans() {
|
|
|
9652
9865
|
setIsLoading(false);
|
|
9653
9866
|
}
|
|
9654
9867
|
}, []);
|
|
9655
|
-
const refresh = (0,
|
|
9868
|
+
const refresh = (0, import_react19.useCallback)(() => {
|
|
9656
9869
|
return fetchPlans(true);
|
|
9657
9870
|
}, [fetchPlans]);
|
|
9658
|
-
(0,
|
|
9871
|
+
(0, import_react19.useEffect)(() => {
|
|
9659
9872
|
void fetchPlans(false);
|
|
9660
9873
|
}, [fetchPlans]);
|
|
9661
9874
|
return { currentPlan, allPlans, isLoading, error, refresh };
|
|
9662
9875
|
}
|
|
9663
9876
|
|
|
9664
9877
|
// src/hooks/useProducts.ts
|
|
9665
|
-
var
|
|
9878
|
+
var import_react20 = require("react");
|
|
9666
9879
|
|
|
9667
9880
|
// src/utils/productMappers.ts
|
|
9668
9881
|
function mapToIAPProduct(product) {
|
|
@@ -9701,11 +9914,11 @@ function mapToFormattedProduct(product) {
|
|
|
9701
9914
|
|
|
9702
9915
|
// src/hooks/useProducts.ts
|
|
9703
9916
|
function useProducts(initialProductIds) {
|
|
9704
|
-
const [products, setProducts] = (0,
|
|
9705
|
-
const [formattedProducts, setFormattedProducts] = (0,
|
|
9706
|
-
const [isLoading, setIsLoading] = (0,
|
|
9707
|
-
const [error, setError] = (0,
|
|
9708
|
-
const loadProducts = (0,
|
|
9917
|
+
const [products, setProducts] = (0, import_react20.useState)([]);
|
|
9918
|
+
const [formattedProducts, setFormattedProducts] = (0, import_react20.useState)([]);
|
|
9919
|
+
const [isLoading, setIsLoading] = (0, import_react20.useState)(false);
|
|
9920
|
+
const [error, setError] = (0, import_react20.useState)(null);
|
|
9921
|
+
const loadProducts = (0, import_react20.useCallback)(async (productIds) => {
|
|
9709
9922
|
setIsLoading(true);
|
|
9710
9923
|
setError(null);
|
|
9711
9924
|
try {
|
|
@@ -9719,24 +9932,24 @@ function useProducts(initialProductIds) {
|
|
|
9719
9932
|
setIsLoading(false);
|
|
9720
9933
|
}
|
|
9721
9934
|
}, []);
|
|
9722
|
-
(0,
|
|
9935
|
+
(0, import_react20.useEffect)(() => {
|
|
9723
9936
|
if (initialProductIds && initialProductIds.length > 0) {
|
|
9724
9937
|
void loadProducts(initialProductIds);
|
|
9725
9938
|
}
|
|
9726
9939
|
}, []);
|
|
9727
|
-
const getProduct = (0,
|
|
9940
|
+
const getProduct = (0, import_react20.useCallback)(
|
|
9728
9941
|
(productId) => {
|
|
9729
9942
|
return products.find((p) => p.productId === productId);
|
|
9730
9943
|
},
|
|
9731
9944
|
[products]
|
|
9732
9945
|
);
|
|
9733
|
-
const getFormattedProduct = (0,
|
|
9946
|
+
const getFormattedProduct = (0, import_react20.useCallback)(
|
|
9734
9947
|
(productId) => {
|
|
9735
9948
|
return formattedProducts.find((p) => p.productId === productId);
|
|
9736
9949
|
},
|
|
9737
9950
|
[formattedProducts]
|
|
9738
9951
|
);
|
|
9739
|
-
const getPriceInfo = (0,
|
|
9952
|
+
const getPriceInfo = (0, import_react20.useCallback)(
|
|
9740
9953
|
(productId) => {
|
|
9741
9954
|
const p = formattedProducts.find((fp) => fp.productId === productId);
|
|
9742
9955
|
if (!p) return void 0;
|
|
@@ -9765,14 +9978,14 @@ function useProducts(initialProductIds) {
|
|
|
9765
9978
|
}
|
|
9766
9979
|
|
|
9767
9980
|
// src/hooks/usePurchase.ts
|
|
9768
|
-
var
|
|
9981
|
+
var import_react21 = require("react");
|
|
9769
9982
|
function toIAPPurchase(p) {
|
|
9770
9983
|
return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
|
|
9771
9984
|
}
|
|
9772
9985
|
function usePurchase() {
|
|
9773
|
-
const [state, setState] = (0,
|
|
9774
|
-
const [error, setError] = (0,
|
|
9775
|
-
const purchase = (0,
|
|
9986
|
+
const [state, setState] = (0, import_react21.useState)("idle");
|
|
9987
|
+
const [error, setError] = (0, import_react21.useState)(null);
|
|
9988
|
+
const purchase = (0, import_react21.useCallback)(async (productId) => {
|
|
9776
9989
|
setState("purchasing");
|
|
9777
9990
|
setError(null);
|
|
9778
9991
|
try {
|
|
@@ -9800,7 +10013,7 @@ function usePurchase() {
|
|
|
9800
10013
|
return { status: "failed", error: purchaseError };
|
|
9801
10014
|
}
|
|
9802
10015
|
}, []);
|
|
9803
|
-
const restore = (0,
|
|
10016
|
+
const restore = (0, import_react21.useCallback)(async () => {
|
|
9804
10017
|
setState("restoring");
|
|
9805
10018
|
setError(null);
|
|
9806
10019
|
try {
|
|
@@ -9819,14 +10032,14 @@ function usePurchase() {
|
|
|
9819
10032
|
}
|
|
9820
10033
|
|
|
9821
10034
|
// src/hooks/useSubscription.ts
|
|
9822
|
-
var
|
|
10035
|
+
var import_react22 = require("react");
|
|
9823
10036
|
init_SubscriptionManager();
|
|
9824
10037
|
var EMPTY_ENTITLEMENTS = [];
|
|
9825
10038
|
function useSubscription() {
|
|
9826
|
-
const [status, setStatus] = (0,
|
|
9827
|
-
const [isLoading, setIsLoading] = (0,
|
|
9828
|
-
const [error, setError] = (0,
|
|
9829
|
-
const refresh = (0,
|
|
10039
|
+
const [status, setStatus] = (0, import_react22.useState)(null);
|
|
10040
|
+
const [isLoading, setIsLoading] = (0, import_react22.useState)(true);
|
|
10041
|
+
const [error, setError] = (0, import_react22.useState)(null);
|
|
10042
|
+
const refresh = (0, import_react22.useCallback)(async () => {
|
|
9830
10043
|
setIsLoading(true);
|
|
9831
10044
|
setError(null);
|
|
9832
10045
|
try {
|
|
@@ -9839,7 +10052,7 @@ function useSubscription() {
|
|
|
9839
10052
|
setIsLoading(false);
|
|
9840
10053
|
}
|
|
9841
10054
|
}, []);
|
|
9842
|
-
const restore = (0,
|
|
10055
|
+
const restore = (0, import_react22.useCallback)(async () => {
|
|
9843
10056
|
setIsLoading(true);
|
|
9844
10057
|
setError(null);
|
|
9845
10058
|
try {
|
|
@@ -9852,7 +10065,7 @@ function useSubscription() {
|
|
|
9852
10065
|
setIsLoading(false);
|
|
9853
10066
|
}
|
|
9854
10067
|
}, []);
|
|
9855
|
-
(0,
|
|
10068
|
+
(0, import_react22.useEffect)(() => {
|
|
9856
10069
|
void subscriptionManager.getSubscriptionStatus().then((s) => {
|
|
9857
10070
|
setStatus(s);
|
|
9858
10071
|
setIsLoading(false);
|
|
@@ -9879,39 +10092,6 @@ function useSubscription() {
|
|
|
9879
10092
|
};
|
|
9880
10093
|
}
|
|
9881
10094
|
|
|
9882
|
-
// src/hooks/usePaywalloScreenTracking.ts
|
|
9883
|
-
var import_react22 = require("react");
|
|
9884
|
-
init_PaywalloClient();
|
|
9885
|
-
function isNavState(value) {
|
|
9886
|
-
return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
|
|
9887
|
-
}
|
|
9888
|
-
function getActiveRouteName(state) {
|
|
9889
|
-
try {
|
|
9890
|
-
if (!isNavState(state)) return null;
|
|
9891
|
-
const route = state.routes[state.index];
|
|
9892
|
-
if (!route) return null;
|
|
9893
|
-
if (isNavState(route.state)) {
|
|
9894
|
-
return getActiveRouteName(route.state);
|
|
9895
|
-
}
|
|
9896
|
-
return typeof route.name === "string" ? route.name : null;
|
|
9897
|
-
} catch {
|
|
9898
|
-
return null;
|
|
9899
|
-
}
|
|
9900
|
-
}
|
|
9901
|
-
function usePaywalloScreenTracking() {
|
|
9902
|
-
const lastScreenRef = (0, import_react22.useRef)(null);
|
|
9903
|
-
return (0, import_react22.useCallback)((state) => {
|
|
9904
|
-
if (!PaywalloClient.isReady()) return;
|
|
9905
|
-
const screenName = getActiveRouteName(state);
|
|
9906
|
-
if (!screenName || screenName === lastScreenRef.current) return;
|
|
9907
|
-
lastScreenRef.current = screenName;
|
|
9908
|
-
void PaywalloClient.track("screen_view", {
|
|
9909
|
-
properties: { screen_name: screenName }
|
|
9910
|
-
}).catch(() => {
|
|
9911
|
-
});
|
|
9912
|
-
}, []);
|
|
9913
|
-
}
|
|
9914
|
-
|
|
9915
10095
|
// src/PaywalloProvider.tsx
|
|
9916
10096
|
var import_react28 = require("react");
|
|
9917
10097
|
var import_react_native29 = require("react-native");
|
|
@@ -9919,6 +10099,7 @@ init_PaywalloClient();
|
|
|
9919
10099
|
init_NativeStorage();
|
|
9920
10100
|
init_SecureStorage();
|
|
9921
10101
|
init_campaign();
|
|
10102
|
+
init_identity();
|
|
9922
10103
|
init_paywall();
|
|
9923
10104
|
init_paywall();
|
|
9924
10105
|
init_paywall();
|
|
@@ -9930,7 +10111,9 @@ init_paywall();
|
|
|
9930
10111
|
init_identity();
|
|
9931
10112
|
var CONFIG_POLL_INTERVAL_MS = 250;
|
|
9932
10113
|
var CONFIG_POLL_MAX_ATTEMPTS = 40;
|
|
10114
|
+
var CONFIG_RETRY_DELAYS_MS = [3e4, 6e4, 12e4];
|
|
9933
10115
|
var lastSignature = null;
|
|
10116
|
+
var retryTimer = null;
|
|
9934
10117
|
function log(debug, msg, data) {
|
|
9935
10118
|
if (!debug) return;
|
|
9936
10119
|
if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
|
|
@@ -9997,28 +10180,37 @@ async function waitForConfigured(mod, debug) {
|
|
|
9997
10180
|
status = String(await mod.getConfigurationStatus());
|
|
9998
10181
|
} catch (err) {
|
|
9999
10182
|
log(debug, "getConfigurationStatus error", { err: String(err) });
|
|
10000
|
-
return
|
|
10183
|
+
return "failed";
|
|
10001
10184
|
}
|
|
10002
|
-
if (status === "CONFIGURED") return
|
|
10185
|
+
if (status === "CONFIGURED") return "configured";
|
|
10003
10186
|
if (status === "FAILED") {
|
|
10004
10187
|
log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
|
|
10005
|
-
return
|
|
10188
|
+
return "failed";
|
|
10006
10189
|
}
|
|
10007
10190
|
await delay(CONFIG_POLL_INTERVAL_MS);
|
|
10008
10191
|
}
|
|
10009
10192
|
log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
|
|
10010
|
-
return
|
|
10193
|
+
return "timeout";
|
|
10011
10194
|
}
|
|
10012
10195
|
async function pushAttributionToSuperwall(debug = false) {
|
|
10196
|
+
clearRetryTimer();
|
|
10197
|
+
await attemptPush(debug, 0);
|
|
10198
|
+
}
|
|
10199
|
+
async function attemptPush(debug, retryIndex) {
|
|
10200
|
+
const native = await resolveSuperwallNativeModule(debug);
|
|
10201
|
+
if (!native) return;
|
|
10202
|
+
const waited = await waitForConfigured(native, debug);
|
|
10203
|
+
if (waited === "timeout") {
|
|
10204
|
+
scheduleRetry(debug, retryIndex);
|
|
10205
|
+
return;
|
|
10206
|
+
}
|
|
10207
|
+
if (waited !== "configured") return;
|
|
10013
10208
|
const attrs = buildSuperwallAttributes(attributionTracker.get());
|
|
10014
10209
|
const signature = JSON.stringify(attrs);
|
|
10015
10210
|
if (signature === lastSignature) {
|
|
10016
10211
|
log(debug, "attributes unchanged \u2014 skipping push");
|
|
10017
10212
|
return;
|
|
10018
10213
|
}
|
|
10019
|
-
const native = await resolveSuperwallNativeModule(debug);
|
|
10020
|
-
if (!native) return;
|
|
10021
|
-
if (!await waitForConfigured(native, debug)) return;
|
|
10022
10214
|
try {
|
|
10023
10215
|
await native.setUserAttributes(attrs);
|
|
10024
10216
|
lastSignature = signature;
|
|
@@ -10027,6 +10219,28 @@ async function pushAttributionToSuperwall(debug = false) {
|
|
|
10027
10219
|
log(debug, "setUserAttributes error", { err: String(err) });
|
|
10028
10220
|
}
|
|
10029
10221
|
}
|
|
10222
|
+
function scheduleRetry(debug, retryIndex) {
|
|
10223
|
+
const delayMs = CONFIG_RETRY_DELAYS_MS[retryIndex];
|
|
10224
|
+
if (delayMs === void 0) {
|
|
10225
|
+
log(debug, "Superwall never configured \u2014 giving up attribute push");
|
|
10226
|
+
return;
|
|
10227
|
+
}
|
|
10228
|
+
clearRetryTimer();
|
|
10229
|
+
log(debug, "scheduling attribute push retry", { retryIndex, delayMs });
|
|
10230
|
+
retryTimer = setTimeout(() => {
|
|
10231
|
+
retryTimer = null;
|
|
10232
|
+
void attemptPush(debug, retryIndex + 1);
|
|
10233
|
+
}, delayMs);
|
|
10234
|
+
}
|
|
10235
|
+
function clearRetryTimer() {
|
|
10236
|
+
if (retryTimer !== null) {
|
|
10237
|
+
clearTimeout(retryTimer);
|
|
10238
|
+
retryTimer = null;
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
function cancelSuperwallAttributeRetry() {
|
|
10242
|
+
clearRetryTimer();
|
|
10243
|
+
}
|
|
10030
10244
|
|
|
10031
10245
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
10032
10246
|
init_PaywalloClient();
|
|
@@ -10059,6 +10273,7 @@ function buildDeterministicEventId(parts) {
|
|
|
10059
10273
|
|
|
10060
10274
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
10061
10275
|
var bridgeStarted = false;
|
|
10276
|
+
var startInProgress = false;
|
|
10062
10277
|
var unsubscribe = null;
|
|
10063
10278
|
var debugEnabled = false;
|
|
10064
10279
|
function log2(msg, data) {
|
|
@@ -10099,8 +10314,7 @@ function mapDismissToCloseReason(resultType) {
|
|
|
10099
10314
|
case "purchased":
|
|
10100
10315
|
return "purchased";
|
|
10101
10316
|
case "restored":
|
|
10102
|
-
return "
|
|
10103
|
-
// restore counts as a purchase outcome for funnel
|
|
10317
|
+
return "restored";
|
|
10104
10318
|
case "declined":
|
|
10105
10319
|
return "dismissed";
|
|
10106
10320
|
default:
|
|
@@ -10136,17 +10350,66 @@ function buildCallbacks() {
|
|
|
10136
10350
|
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
10137
10351
|
log2("onPurchase", { productId, identifier });
|
|
10138
10352
|
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10353
|
+
},
|
|
10354
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
10355
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
10356
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
10357
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
10358
|
+
// handled above with their typed callbacks).
|
|
10359
|
+
onSuperwallEvent: (info) => {
|
|
10360
|
+
const ev = info?.event;
|
|
10361
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
10362
|
+
const ts = Date.now();
|
|
10363
|
+
const product = ev.product;
|
|
10364
|
+
const transaction = ev.transaction;
|
|
10365
|
+
if (!product) {
|
|
10366
|
+
log2("transactionComplete missing product \u2014 skipping transaction track");
|
|
10367
|
+
return;
|
|
10368
|
+
}
|
|
10369
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
10370
|
+
const currency = product.currencyCode ?? "USD";
|
|
10371
|
+
const price = product.price ?? 0;
|
|
10372
|
+
const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
10373
|
+
const introPrice = product.trialPeriodPrice ?? 0;
|
|
10374
|
+
const isTrial = product.hasFreeTrial === true && introPrice <= 0;
|
|
10375
|
+
const isPaidIntro = introPrice > 0;
|
|
10376
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
10377
|
+
log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
|
|
10378
|
+
void (async () => {
|
|
10379
|
+
try {
|
|
10380
|
+
await PaywalloClient.track("transaction", {
|
|
10381
|
+
priority: "critical",
|
|
10382
|
+
properties: {
|
|
10383
|
+
type: isTrial ? "trial_started" : "completed",
|
|
10384
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
10385
|
+
// /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
|
|
10386
|
+
// amount=0 (no revenue at start); a paid intro offer carries its
|
|
10387
|
+
// intro amount (real revenue); a normal purchase carries `price`.
|
|
10388
|
+
// `full_price` carries the recurring full price so the server can
|
|
10389
|
+
// value the eventual conversion (free trial) or keep the headline
|
|
10390
|
+
// price alongside the discounted intro amount (paid intro).
|
|
10391
|
+
transaction_id: transactionId,
|
|
10392
|
+
product_id: productId,
|
|
10393
|
+
amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
|
|
10394
|
+
currency,
|
|
10395
|
+
...isTrial || isPaidIntro ? { full_price: price } : {},
|
|
10396
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10397
|
+
}
|
|
10398
|
+
});
|
|
10399
|
+
} catch (err) {
|
|
10400
|
+
log2("transactionComplete track error", { err: String(err) });
|
|
10401
|
+
}
|
|
10402
|
+
})();
|
|
10139
10403
|
}
|
|
10140
|
-
// onSuperwallEvent removed — transaction sale events are sourced exclusively
|
|
10141
|
-
// from server-side webhooks (Apple/Google/Superwall/RevenueCat).
|
|
10142
10404
|
};
|
|
10143
10405
|
}
|
|
10144
10406
|
function startSuperwallAutoBridge(debug = false) {
|
|
10145
10407
|
debugEnabled = debug;
|
|
10146
|
-
if (bridgeStarted) {
|
|
10147
|
-
log2("already started, skipping");
|
|
10408
|
+
if (bridgeStarted || startInProgress) {
|
|
10409
|
+
log2("already started or starting, skipping");
|
|
10148
10410
|
return;
|
|
10149
10411
|
}
|
|
10412
|
+
startInProgress = true;
|
|
10150
10413
|
void detectAndStart();
|
|
10151
10414
|
}
|
|
10152
10415
|
async function detectAndStart() {
|
|
@@ -10157,6 +10420,7 @@ async function detectAndStart() {
|
|
|
10157
10420
|
const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
|
|
10158
10421
|
if (typeof internal.subscribeToSuperwallEvents !== "function") {
|
|
10159
10422
|
log2("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
|
|
10423
|
+
startInProgress = false;
|
|
10160
10424
|
return;
|
|
10161
10425
|
}
|
|
10162
10426
|
subscribeFn = internal.subscribeToSuperwallEvents;
|
|
@@ -10165,6 +10429,11 @@ async function detectAndStart() {
|
|
|
10165
10429
|
}
|
|
10166
10430
|
} catch {
|
|
10167
10431
|
log2("expo-superwall not installed \u2014 bridge inactive");
|
|
10432
|
+
startInProgress = false;
|
|
10433
|
+
return;
|
|
10434
|
+
}
|
|
10435
|
+
if (!startInProgress) {
|
|
10436
|
+
log2("start cancelled by stop \u2014 bridge inactive");
|
|
10168
10437
|
return;
|
|
10169
10438
|
}
|
|
10170
10439
|
try {
|
|
@@ -10174,7 +10443,18 @@ async function detectAndStart() {
|
|
|
10174
10443
|
log2("bridge started");
|
|
10175
10444
|
} catch (err) {
|
|
10176
10445
|
log2("subscribe failed", { err: String(err) });
|
|
10446
|
+
} finally {
|
|
10447
|
+
startInProgress = false;
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10450
|
+
function stopSuperwallAutoBridge() {
|
|
10451
|
+
if (unsubscribe) {
|
|
10452
|
+
unsubscribe();
|
|
10453
|
+
unsubscribe = null;
|
|
10177
10454
|
}
|
|
10455
|
+
bridgeStarted = false;
|
|
10456
|
+
startInProgress = false;
|
|
10457
|
+
log2("bridge stopped");
|
|
10178
10458
|
}
|
|
10179
10459
|
|
|
10180
10460
|
// src/PaywalloProvider.tsx
|
|
@@ -10741,6 +11021,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10741
11021
|
const configRef = (0, import_react28.useRef)(config);
|
|
10742
11022
|
(0, import_react28.useEffect)(() => {
|
|
10743
11023
|
let mounted = true;
|
|
11024
|
+
const unsubscribeAttribution = attributionTracker.onCapture(() => {
|
|
11025
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
11026
|
+
});
|
|
10744
11027
|
const bootstrap = async () => {
|
|
10745
11028
|
if (!PaywalloClient.isReady()) {
|
|
10746
11029
|
initLocalization({ detectDevice: true });
|
|
@@ -10751,11 +11034,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
10751
11034
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
10752
11035
|
return;
|
|
10753
11036
|
}
|
|
10754
|
-
if (!mounted) return;
|
|
10755
|
-
startSuperwallAutoBridge(configRef.current.debug);
|
|
10756
|
-
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10757
11037
|
}
|
|
10758
11038
|
if (!mounted) return;
|
|
11039
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
11040
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10759
11041
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10760
11042
|
setIsInitialized(true);
|
|
10761
11043
|
if (!autoPreloadTriggeredRef.current) {
|
|
@@ -10773,6 +11055,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10773
11055
|
void bootstrap();
|
|
10774
11056
|
return () => {
|
|
10775
11057
|
mounted = false;
|
|
11058
|
+
unsubscribeAttribution();
|
|
11059
|
+
stopSuperwallAutoBridge();
|
|
11060
|
+
cancelSuperwallAttributeRetry();
|
|
10776
11061
|
};
|
|
10777
11062
|
}, [preloadCampaign]);
|
|
10778
11063
|
const getPaywallConfig = (0, import_react28.useCallback)(async (p) => {
|