@virex-tech/paywallo-sdk 2.5.2 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -664,9 +664,12 @@ var init_IdentityManager = __esm({
664
664
  dateOfBirth: this.dateOfBirth ?? void 0,
665
665
  gender: this.gender ?? void 0
666
666
  });
667
+ if (this.debug) console.log("[Paywallo] identify confirmed", this.anonId.slice(0, 8));
667
668
  } catch (err) {
668
- if (this.debug) console.log("[Paywallo] identify dispatch error", err);
669
+ if (this.debug) console.log("[Paywallo] identify failed:", err);
669
670
  }
671
+ } else {
672
+ if (this.debug) console.log("[Paywallo] identify skipped \u2014 anonId not ready");
670
673
  }
671
674
  }
672
675
  getDistinctId() {
@@ -2301,18 +2304,15 @@ var init_constants = __esm({
2301
2304
  });
2302
2305
 
2303
2306
  // src/domains/notifications/config.ts
2304
- function resolveApiBaseUrl(config) {
2305
- if (config.apiBaseUrl) return config.apiBaseUrl;
2306
- if (config.environment === "sandbox") return SANDBOX_API_BASE_URL;
2307
+ function resolveApiBaseUrl() {
2307
2308
  return DEFAULT_API_BASE_URL;
2308
2309
  }
2309
- var DEFAULT_API_BASE_URL, SANDBOX_API_BASE_URL;
2310
+ var DEFAULT_API_BASE_URL;
2310
2311
  var init_config = __esm({
2311
2312
  "src/domains/notifications/config.ts"() {
2312
2313
  "use strict";
2313
2314
  init_constants();
2314
2315
  DEFAULT_API_BASE_URL = DEFAULT_API_URL;
2315
- SANDBOX_API_BASE_URL = DEFAULT_API_URL;
2316
2316
  }
2317
2317
  });
2318
2318
 
@@ -2627,8 +2627,8 @@ var init_NotificationHandlerSetup = __esm({
2627
2627
  // src/core/version.ts
2628
2628
  function resolveVersion() {
2629
2629
  try {
2630
- if ("2.5.2") {
2631
- return "2.5.2";
2630
+ if ("2.6.0") {
2631
+ return "2.6.0";
2632
2632
  }
2633
2633
  } catch {
2634
2634
  }
@@ -3082,10 +3082,7 @@ var init_NotificationsManager = __esm({
3082
3082
  }
3083
3083
  applyConfig(config) {
3084
3084
  this.appKey = config.appKey;
3085
- this.apiBaseUrl = resolveApiBaseUrl({
3086
- apiBaseUrl: config.apiBaseUrl,
3087
- environment: config.environment
3088
- });
3085
+ this.apiBaseUrl = resolveApiBaseUrl();
3089
3086
  this.debug = config.debug ?? false;
3090
3087
  this.permissionManager.setDebug(this.debug);
3091
3088
  }
@@ -3261,7 +3258,8 @@ var init_OnboardingError = __esm({
3261
3258
  };
3262
3259
  ONBOARDING_ERROR_CODES = {
3263
3260
  NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
3264
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
3261
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME",
3262
+ INVALID_ORDER: "ONBOARDING_INVALID_ORDER"
3265
3263
  };
3266
3264
  }
3267
3265
  });
@@ -3291,16 +3289,20 @@ var init_OnboardingManager = __esm({
3291
3289
  * Saves the step name internally for implicit drop detection on the backend.
3292
3290
  *
3293
3291
  * @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).
3292
+ * @param order - Ordem de exibição do step. Obrigatório. Aceita decimais (ex: 2.1 para variantes A/B).
3293
+ * Deve ser um número finito e não-negativo. O servidor faz o floor — não arredondar aqui.
3295
3294
  */
3296
- async step(stepName, order) {
3295
+ async step(stepName, order, options) {
3297
3296
  this.assertConfig();
3298
3297
  this.validateStepName(stepName, "step");
3298
+ this.validateOrder(order);
3299
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
- }
3300
+ const stepPayload = {
3301
+ step_name: stepName,
3302
+ order,
3303
+ ...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {},
3304
+ ...options?.timeOnPrevS !== void 0 ? { time_on_prev_s: options.timeOnPrevS } : {}
3305
+ };
3304
3306
  const payload = {
3305
3307
  family: "onboarding",
3306
3308
  type: "step",
@@ -3312,33 +3314,21 @@ var init_OnboardingManager = __esm({
3312
3314
  * Tracks successful completion of the onboarding flow.
3313
3315
  * Marks the flow as finished.
3314
3316
  */
3315
- async complete() {
3317
+ async complete(options) {
3316
3318
  this.assertConfig();
3317
3319
  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
3320
  const payload = {
3331
3321
  family: "onboarding",
3332
- type: "drop",
3333
- ...{ step_name: stepName }
3322
+ type: "complete",
3323
+ ...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {}
3334
3324
  };
3335
- return this.emit("onboarding", payload, "drop");
3325
+ return this.emit("onboarding", payload, "complete");
3336
3326
  }
3337
3327
  /** Returns the last step name seen, or null if no step was tracked yet. */
3338
3328
  getLastStep() {
3339
3329
  return this.lastStep;
3340
3330
  }
3341
- /** Returns whether the flow has been marked as finished (complete or drop). */
3331
+ /** Returns whether the flow has been marked as finished (complete). */
3342
3332
  isFinished() {
3343
3333
  return this.finished;
3344
3334
  }
@@ -3370,6 +3360,14 @@ var init_OnboardingManager = __esm({
3370
3360
  );
3371
3361
  }
3372
3362
  }
3363
+ validateOrder(order) {
3364
+ if (order === void 0 || order === null || !Number.isFinite(order) || order < 0) {
3365
+ throw new OnboardingError(
3366
+ ONBOARDING_ERROR_CODES.INVALID_ORDER,
3367
+ "OnboardingManager.step(): order deve ser um n\xFAmero finito e n\xE3o-negativo."
3368
+ );
3369
+ }
3370
+ }
3373
3371
  };
3374
3372
  onboardingManager = new OnboardingManager();
3375
3373
  }
@@ -4111,17 +4109,292 @@ var init_PaywallWebView = __esm({
4111
4109
  }
4112
4110
  });
4113
4111
 
4112
+ // src/domains/iap/PurchaseError.ts
4113
+ function createPurchaseError(code, message) {
4114
+ return new PurchaseError(
4115
+ PURCHASE_ERROR_CODES[code],
4116
+ message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
4117
+ code === "USER_CANCELLED"
4118
+ );
4119
+ }
4120
+ var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
4121
+ var init_PurchaseError = __esm({
4122
+ "src/domains/iap/PurchaseError.ts"() {
4123
+ "use strict";
4124
+ init_PaywalloError();
4125
+ PurchaseError = class extends PaywalloError {
4126
+ constructor(code, message, userCancelled = false, httpStatus) {
4127
+ super("purchase", code, message);
4128
+ this.name = "PurchaseError";
4129
+ this.userCancelled = userCancelled;
4130
+ this.httpStatus = httpStatus;
4131
+ }
4132
+ };
4133
+ PURCHASE_ERROR_CODES = {
4134
+ NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
4135
+ PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
4136
+ PURCHASE_FAILED: "PURCHASE_FAILED",
4137
+ RESTORE_FAILED: "RESTORE_FAILED",
4138
+ VALIDATION_FAILED: "VALIDATION_FAILED",
4139
+ USER_CANCELLED: "USER_CANCELLED",
4140
+ NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
4141
+ STORE_ERROR: "PURCHASE_STORE_ERROR",
4142
+ PENDING_PURCHASE: "PURCHASE_PENDING",
4143
+ DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
4144
+ STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
4145
+ };
4146
+ DEFAULT_MESSAGES = {
4147
+ NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
4148
+ PRODUCT_NOT_FOUND: "Product not found in store.",
4149
+ PURCHASE_FAILED: "Purchase failed. Please try again.",
4150
+ RESTORE_FAILED: "Failed to restore purchases. Please try again.",
4151
+ VALIDATION_FAILED: "Failed to validate purchase with server.",
4152
+ USER_CANCELLED: "Purchase was cancelled.",
4153
+ NETWORK_ERROR: "Network error. Please check your connection.",
4154
+ STORE_ERROR: "Store error. Please try again later.",
4155
+ PENDING_PURCHASE: "Purchase is pending approval.",
4156
+ DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
4157
+ STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
4158
+ };
4159
+ }
4160
+ });
4161
+
4162
+ // src/domains/iap/NativeStoreKit.ts
4163
+ var NativeStoreKit_exports = {};
4164
+ __export(NativeStoreKit_exports, {
4165
+ NativeStoreKit: () => NativeStoreKit,
4166
+ nativeStoreKit: () => nativeStoreKit
4167
+ });
4168
+ function isAvailable3() {
4169
+ return PaywalloStoreKitNative != null;
4170
+ }
4171
+ function mapNativeProduct(native) {
4172
+ const product = {
4173
+ productId: native.productId,
4174
+ title: native.title,
4175
+ description: native.description,
4176
+ price: native.price,
4177
+ priceValue: native.priceValue,
4178
+ currency: native.currency ?? "USD",
4179
+ localizedPrice: native.localizedPrice,
4180
+ type: mapProductType(native.type),
4181
+ subscriptionPeriod: native.subscriptionPeriod,
4182
+ introductoryPrice: native.introductoryPrice,
4183
+ introductoryPriceValue: native.introductoryPriceValue,
4184
+ freeTrialPeriod: native.freeTrialPeriod
4185
+ };
4186
+ if (native.subscriptionPeriod) {
4187
+ const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
4188
+ product.pricePerMonth = prices.perMonth;
4189
+ product.pricePerWeek = prices.perWeek;
4190
+ product.pricePerDay = prices.perDay;
4191
+ }
4192
+ return product;
4193
+ }
4194
+ function mapProductType(type) {
4195
+ switch (type) {
4196
+ case "subscription":
4197
+ case "autoRenewable":
4198
+ return "subscription";
4199
+ case "consumable":
4200
+ return "consumable";
4201
+ default:
4202
+ return "non_consumable";
4203
+ }
4204
+ }
4205
+ function calculatePerPeriodPrices(priceValue, period) {
4206
+ const totalDays = periodToDays(period);
4207
+ if (totalDays <= 0) return {};
4208
+ const perDay = priceValue / totalDays;
4209
+ const perWeek = perDay * 7;
4210
+ const perMonth = perDay * 30;
4211
+ return {
4212
+ perDay: perDay.toFixed(2),
4213
+ perWeek: perWeek.toFixed(2),
4214
+ perMonth: perMonth.toFixed(2)
4215
+ };
4216
+ }
4217
+ function periodToDays(period) {
4218
+ const match = period.match(/^P(\d+)([DWMY])$/);
4219
+ if (!match) return 0;
4220
+ const value = parseInt(match[1], 10);
4221
+ switch (match[2]) {
4222
+ case "D":
4223
+ return value;
4224
+ case "W":
4225
+ return value * 7;
4226
+ case "M":
4227
+ return value * 30;
4228
+ case "Y":
4229
+ return value * 365;
4230
+ default:
4231
+ return 0;
4232
+ }
4233
+ }
4234
+ function mapNativePurchase(native) {
4235
+ return {
4236
+ productId: native.productId,
4237
+ transactionId: native.transactionId,
4238
+ transactionDate: native.transactionDate,
4239
+ receipt: native.receipt,
4240
+ platform: import_react_native16.Platform.OS
4241
+ };
4242
+ }
4243
+ function normalizeTransactionUpdate(raw) {
4244
+ if (!raw || typeof raw !== "object") return null;
4245
+ const r = raw;
4246
+ const type = r.type;
4247
+ if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
4248
+ const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
4249
+ const productId = typeof r.productId === "string" ? r.productId : null;
4250
+ if (!transactionId) return null;
4251
+ const update = {
4252
+ type,
4253
+ transactionId,
4254
+ productId: productId ?? ""
4255
+ };
4256
+ if (typeof r.amount === "number") update.amount = r.amount;
4257
+ if (typeof r.currency === "string") update.currency = r.currency;
4258
+ return update;
4259
+ }
4260
+ var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
4261
+ var init_NativeStoreKit = __esm({
4262
+ "src/domains/iap/NativeStoreKit.ts"() {
4263
+ "use strict";
4264
+ import_react_native16 = require("react-native");
4265
+ init_PaywalloClient();
4266
+ init_uuid();
4267
+ init_IdentityManager();
4268
+ init_PurchaseError();
4269
+ TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
4270
+ PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
4271
+ nativeStoreKit = {
4272
+ isAvailable: isAvailable3,
4273
+ async getProducts(productIds) {
4274
+ if (!PaywalloStoreKitNative) {
4275
+ return [];
4276
+ }
4277
+ const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
4278
+ return nativeProducts.map(mapNativeProduct);
4279
+ },
4280
+ async purchase(productId) {
4281
+ if (!PaywalloStoreKitNative) {
4282
+ throw new PurchaseError(
4283
+ PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
4284
+ "StoreKit native module not available"
4285
+ );
4286
+ }
4287
+ const distinctId = identityManager.getDistinctId();
4288
+ const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
4289
+ const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
4290
+ if (distinctId && appAccountToken === null) {
4291
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
4292
+ }
4293
+ try {
4294
+ const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
4295
+ if (!result || result.pending) {
4296
+ return null;
4297
+ }
4298
+ return mapNativePurchase(result);
4299
+ } catch (err) {
4300
+ const message = err instanceof Error ? err.message : String(err);
4301
+ if (message.toLowerCase().includes("cancel")) {
4302
+ throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
4303
+ }
4304
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
4305
+ }
4306
+ },
4307
+ async finishTransaction(transactionId) {
4308
+ if (!PaywalloStoreKitNative) return;
4309
+ try {
4310
+ await PaywalloStoreKitNative.finishTransaction(transactionId);
4311
+ } catch (err) {
4312
+ console.error("[Paywallo] finishTransaction native error", err);
4313
+ }
4314
+ },
4315
+ async getActiveTransactions() {
4316
+ if (!PaywalloStoreKitNative) return [];
4317
+ try {
4318
+ const results = await PaywalloStoreKitNative.getActiveTransactions();
4319
+ return results.map(mapNativePurchase);
4320
+ } catch (err) {
4321
+ console.error("[Paywallo] getActiveTransactions failed", err);
4322
+ return [];
4323
+ }
4324
+ },
4325
+ /**
4326
+ * Subscribes to native transaction update events (renewals, refunds,
4327
+ * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
4328
+ * `PurchasesUpdatedListener` + refund diff on Android.
4329
+ *
4330
+ * Returns a subscription with a `remove()` method. The caller is
4331
+ * responsible for cleanup (typically on `Paywallo.reset()`).
4332
+ *
4333
+ * Returns `null` when the native module isn't available (non-mobile env,
4334
+ * native module not linked) — callers should treat null as a no-op.
4335
+ */
4336
+ subscribeToTransactionUpdates(listener) {
4337
+ if (!PaywalloStoreKitNative) return null;
4338
+ try {
4339
+ const EmitterCtor = import_react_native16.NativeEventEmitter;
4340
+ const emitter = new EmitterCtor(PaywalloStoreKitNative);
4341
+ const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
4342
+ const update = normalizeTransactionUpdate(payload);
4343
+ if (update) listener(update);
4344
+ });
4345
+ return { remove: () => sub.remove() };
4346
+ } catch (err) {
4347
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
4348
+ return null;
4349
+ }
4350
+ }
4351
+ };
4352
+ NativeStoreKit = nativeStoreKit;
4353
+ }
4354
+ });
4355
+
4114
4356
  // src/domains/iap/IAPTransactionEmitter.ts
4115
4357
  var IAPTransactionEmitter;
4116
4358
  var init_IAPTransactionEmitter = __esm({
4117
4359
  "src/domains/iap/IAPTransactionEmitter.ts"() {
4118
4360
  "use strict";
4119
4361
  init_PaywalloClient();
4362
+ init_NativeStoreKit();
4120
4363
  IAPTransactionEmitter = class {
4121
4364
  constructor(getProductFromCache, getApiClientFn, getDebug) {
4122
4365
  this.getProductFromCache = getProductFromCache;
4123
4366
  this.getApiClientFn = getApiClientFn;
4124
4367
  this.getDebug = getDebug;
4368
+ this.transactionUpdateSubscription = null;
4369
+ }
4370
+ /**
4371
+ * Emits `transaction {type: "completed"}` after a server-validated purchase.
4372
+ * Fire-and-forget — a tracking failure must never poison the purchase flow.
4373
+ */
4374
+ async emitTransactionCompleted(purchase, productId, options) {
4375
+ try {
4376
+ const apiClient = this.getApiClientFn();
4377
+ if (!apiClient) return;
4378
+ const distinctId = PaywalloClient.getDistinctId();
4379
+ if (!distinctId) return;
4380
+ const product = this.getProductFromCache(productId);
4381
+ const properties = {
4382
+ type: "completed",
4383
+ tx_id: purchase.transactionId,
4384
+ product_id: purchase.productId
4385
+ };
4386
+ if (product?.priceValue !== void 0 && product.priceValue > 0) {
4387
+ properties.amount = product.priceValue;
4388
+ }
4389
+ if (product?.currency) {
4390
+ properties.currency = product.currency;
4391
+ }
4392
+ if (options?.paywallId) properties.paywall_id = options.paywallId;
4393
+ if (options?.variantId) properties.variant_id = options.variantId;
4394
+ await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4395
+ } catch (err) {
4396
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
4397
+ }
4125
4398
  }
4126
4399
  /**
4127
4400
  * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
@@ -4150,15 +4423,65 @@ var init_IAPTransactionEmitter = __esm({
4150
4423
  }
4151
4424
  }
4152
4425
  /**
4153
- * No-op. Native transaction update listener removed — sale events are sourced
4154
- * exclusively from server-side webhooks (Apple/Google/Superwall/RevenueCat).
4426
+ * Forwards a native transaction update to the EventBatcher.
4427
+ * Public so tests can exercise the wiring without a fake NativeEventEmitter.
4428
+ */
4429
+ async emitTransactionUpdate(update) {
4430
+ try {
4431
+ const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
4432
+ if (!apiClient) {
4433
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
4434
+ return;
4435
+ }
4436
+ const distinctId = PaywalloClient.getDistinctId();
4437
+ if (!distinctId) {
4438
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
4439
+ return;
4440
+ }
4441
+ const properties = {
4442
+ type: update.type,
4443
+ tx_id: update.transactionId,
4444
+ product_id: update.productId
4445
+ };
4446
+ if (update.type !== "canceled") {
4447
+ const cached2 = this.getProductFromCache(update.productId);
4448
+ const amount = update.amount ?? cached2?.priceValue;
4449
+ const currency = update.currency ?? cached2?.currency;
4450
+ if (typeof amount === "number" && amount > 0) properties.amount = amount;
4451
+ if (currency) properties.currency = currency;
4452
+ }
4453
+ await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4454
+ if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
4455
+ } catch (err) {
4456
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
4457
+ }
4458
+ }
4459
+ /**
4460
+ * Starts listening to native Transaction.updates. Idempotent.
4155
4461
  */
4156
4462
  startListener() {
4463
+ if (this.transactionUpdateSubscription) return;
4464
+ this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
4465
+ (update) => {
4466
+ void this.emitTransactionUpdate(update);
4467
+ }
4468
+ );
4469
+ if (this.getDebug() && this.transactionUpdateSubscription) {
4470
+ console.log("[Paywallo IAP] transaction update listener started");
4471
+ }
4157
4472
  }
4158
4473
  /**
4159
- * No-op. See startListener.
4474
+ * Removes the native listener. Safe to call multiple times.
4160
4475
  */
4161
4476
  stopListener() {
4477
+ if (!this.transactionUpdateSubscription) return;
4478
+ try {
4479
+ this.transactionUpdateSubscription.remove();
4480
+ } catch (err) {
4481
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
4482
+ }
4483
+ this.transactionUpdateSubscription = null;
4484
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
4162
4485
  }
4163
4486
  };
4164
4487
  }
@@ -4646,11 +4969,11 @@ var init_types2 = __esm({
4646
4969
  });
4647
4970
 
4648
4971
  // src/core/network/NetworkMonitor.ts
4649
- var import_react_native16, NetworkMonitorClass, networkMonitor;
4972
+ var import_react_native17, NetworkMonitorClass, networkMonitor;
4650
4973
  var init_NetworkMonitor = __esm({
4651
4974
  "src/core/network/NetworkMonitor.ts"() {
4652
4975
  "use strict";
4653
- import_react_native16 = require("react-native");
4976
+ import_react_native17 = require("react-native");
4654
4977
  init_types2();
4655
4978
  NetworkMonitorClass = class {
4656
4979
  constructor() {
@@ -4679,7 +5002,7 @@ var init_NetworkMonitor = __esm({
4679
5002
  }, this.config.checkInterval);
4680
5003
  }
4681
5004
  setupAppStateListener() {
4682
- this.appStateSubscription = import_react_native16.AppState.addEventListener("change", (state) => {
5005
+ this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
4683
5006
  if (state === "active") {
4684
5007
  if (!this.checkIntervalId) {
4685
5008
  this.checkIntervalId = setInterval(() => {
@@ -4901,7 +5224,7 @@ var init_QueueProcessor = __esm({
4901
5224
  for (const item of batch) {
4902
5225
  retryIds.add(item.id);
4903
5226
  }
4904
- console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
5227
+ if (this.config.debug) console.log("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
4905
5228
  this.log("Event batch threw network error - will retry", {
4906
5229
  size: batch.length,
4907
5230
  error: error instanceof Error ? error.message : String(error)
@@ -4914,7 +5237,7 @@ var init_QueueProcessor = __esm({
4914
5237
  processed++;
4915
5238
  }
4916
5239
  this.log("Event batch processed successfully", { size: batch.length });
4917
- } else if (response.status >= 400 && response.status < 500) {
5240
+ } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4918
5241
  for (const item of batch) {
4919
5242
  await offlineQueue.removeItem(item.id);
4920
5243
  failed++;
@@ -4926,7 +5249,7 @@ var init_QueueProcessor = __esm({
4926
5249
  console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
4927
5250
  }
4928
5251
  } else {
4929
- console.error("[Paywallo:QUEUE] event batch failed PERMANENTLY (4xx) \u2014 items dropped", { status: response.status, size: batch.length, responseBody: typeof response.data === "string" ? response.data.slice(0, 1e3) : JSON.stringify(response.data).slice(0, 1e3), firstItemBody: typeof batch[0].body === "string" ? batch[0].body.slice(0, 1e3) : JSON.stringify(batch[0].body).slice(0, 1e3) });
5252
+ if (this.config.debug) console.log("[Paywallo:QUEUE] event batch failed PERMANENTLY (4xx) \u2014 items dropped", { status: response.status, size: batch.length, responseBody: typeof response.data === "string" ? response.data.slice(0, 1e3) : JSON.stringify(response.data).slice(0, 1e3), firstItemBody: typeof batch[0].body === "string" ? batch[0].body.slice(0, 1e3) : JSON.stringify(batch[0].body).slice(0, 1e3) });
4930
5253
  }
4931
5254
  this.log("Event batch failed permanently (4xx) - removing from queue", {
4932
5255
  status: response.status,
@@ -4937,7 +5260,7 @@ var init_QueueProcessor = __esm({
4937
5260
  for (const item of batch) {
4938
5261
  retryIds.add(item.id);
4939
5262
  }
4940
- console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
5263
+ if (this.config.debug) console.log("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
4941
5264
  this.log("Event batch failed with server error - will retry", {
4942
5265
  status: response.status,
4943
5266
  size: batch.length
@@ -4967,7 +5290,7 @@ var init_QueueProcessor = __esm({
4967
5290
  skipRetry: false
4968
5291
  });
4969
5292
  } catch (error) {
4970
- console.error("[Paywallo:QUEUE] individual item network error \u2014 will retry", { id: item.id, url: item.url, error: error instanceof Error ? error.message : String(error) });
5293
+ if (this.config.debug) console.log("[Paywallo:QUEUE] individual item network error \u2014 will retry", { id: item.id, url: item.url, error: error instanceof Error ? error.message : String(error) });
4971
5294
  this.log("Request threw network error - will retry", {
4972
5295
  id: item.id,
4973
5296
  error: error instanceof Error ? error.message : String(error)
@@ -4978,7 +5301,7 @@ var init_QueueProcessor = __esm({
4978
5301
  this.log("Queue item processed successfully", { id: item.id });
4979
5302
  return { success: true, permanent: false };
4980
5303
  }
4981
- if (response.status >= 400 && response.status < 500) {
5304
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4982
5305
  this.log("Request failed with permanent client error - removing from queue", {
4983
5306
  id: item.id,
4984
5307
  url: item.url,
@@ -5024,85 +5347,35 @@ var init_QueueProcessor = __esm({
5024
5347
  this.queueUnsubscribe();
5025
5348
  this.queueUnsubscribe = null;
5026
5349
  }
5027
- this.httpClient = null;
5028
- this.initialized = false;
5029
- }
5030
- log(...args) {
5031
- if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
5032
- }
5033
- setDebug(debug) {
5034
- this.config.debug = debug;
5035
- }
5036
- };
5037
- queueProcessor = new QueueProcessorClass();
5038
- }
5039
- });
5040
-
5041
- // src/core/queue/index.ts
5042
- var init_queue = __esm({
5043
- "src/core/queue/index.ts"() {
5044
- "use strict";
5045
- init_OfflineQueue();
5046
- init_QueueProcessor();
5047
- }
5048
- });
5049
-
5050
- // src/domains/iap/PurchaseError.ts
5051
- function createPurchaseError(code, message) {
5052
- return new PurchaseError(
5053
- PURCHASE_ERROR_CODES[code],
5054
- message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
5055
- code === "USER_CANCELLED"
5056
- );
5057
- }
5058
- var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
5059
- var init_PurchaseError = __esm({
5060
- "src/domains/iap/PurchaseError.ts"() {
5061
- "use strict";
5062
- init_PaywalloError();
5063
- PurchaseError = class extends PaywalloError {
5064
- constructor(code, message, userCancelled = false, httpStatus) {
5065
- super("purchase", code, message);
5066
- this.name = "PurchaseError";
5067
- this.userCancelled = userCancelled;
5068
- this.httpStatus = httpStatus;
5350
+ this.httpClient = null;
5351
+ this.initialized = false;
5352
+ }
5353
+ log(...args) {
5354
+ if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
5355
+ }
5356
+ setDebug(debug) {
5357
+ this.config.debug = debug;
5069
5358
  }
5070
5359
  };
5071
- PURCHASE_ERROR_CODES = {
5072
- NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
5073
- PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
5074
- PURCHASE_FAILED: "PURCHASE_FAILED",
5075
- RESTORE_FAILED: "RESTORE_FAILED",
5076
- VALIDATION_FAILED: "VALIDATION_FAILED",
5077
- USER_CANCELLED: "USER_CANCELLED",
5078
- NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
5079
- STORE_ERROR: "PURCHASE_STORE_ERROR",
5080
- PENDING_PURCHASE: "PURCHASE_PENDING",
5081
- DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
5082
- STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
5083
- };
5084
- DEFAULT_MESSAGES = {
5085
- NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
5086
- PRODUCT_NOT_FOUND: "Product not found in store.",
5087
- PURCHASE_FAILED: "Purchase failed. Please try again.",
5088
- RESTORE_FAILED: "Failed to restore purchases. Please try again.",
5089
- VALIDATION_FAILED: "Failed to validate purchase with server.",
5090
- USER_CANCELLED: "Purchase was cancelled.",
5091
- NETWORK_ERROR: "Network error. Please check your connection.",
5092
- STORE_ERROR: "Store error. Please try again later.",
5093
- PENDING_PURCHASE: "Purchase is pending approval.",
5094
- DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
5095
- STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
5096
- };
5360
+ queueProcessor = new QueueProcessorClass();
5361
+ }
5362
+ });
5363
+
5364
+ // src/core/queue/index.ts
5365
+ var init_queue = __esm({
5366
+ "src/core/queue/index.ts"() {
5367
+ "use strict";
5368
+ init_OfflineQueue();
5369
+ init_QueueProcessor();
5097
5370
  }
5098
5371
  });
5099
5372
 
5100
5373
  // src/domains/iap/IAPValidator.ts
5101
- var import_react_native17, IAPValidator;
5374
+ var import_react_native18, IAPValidator;
5102
5375
  var init_IAPValidator = __esm({
5103
5376
  "src/domains/iap/IAPValidator.ts"() {
5104
5377
  "use strict";
5105
- import_react_native17 = require("react-native");
5378
+ import_react_native18 = require("react-native");
5106
5379
  init_PaywalloClient();
5107
5380
  init_queue();
5108
5381
  init_PurchaseError();
@@ -5128,7 +5401,7 @@ var init_IAPValidator = __esm({
5128
5401
  async validateWithServer(purchase, productId, options) {
5129
5402
  const apiClient = this.getApiClientFn();
5130
5403
  const product = this.getProductFromCache(productId);
5131
- const platform = import_react_native17.Platform.OS;
5404
+ const platform = import_react_native18.Platform.OS;
5132
5405
  const distinctId = PaywalloClient.getDistinctId();
5133
5406
  return this.validateWithRetry(
5134
5407
  () => apiClient.validatePurchase(
@@ -5163,7 +5436,7 @@ var init_IAPValidator = __esm({
5163
5436
  try {
5164
5437
  const apiClient = this.getApiClientFn();
5165
5438
  const product = this.getProductFromCache(productId);
5166
- const platform = import_react_native17.Platform.OS;
5439
+ const platform = import_react_native18.Platform.OS;
5167
5440
  const distinctId = PaywalloClient.getDistinctId();
5168
5441
  const appKey = apiClient.getAppKey();
5169
5442
  const body = {
@@ -5195,200 +5468,6 @@ var init_IAPValidator = __esm({
5195
5468
  }
5196
5469
  });
5197
5470
 
5198
- // src/domains/iap/NativeStoreKit.ts
5199
- var NativeStoreKit_exports = {};
5200
- __export(NativeStoreKit_exports, {
5201
- NativeStoreKit: () => NativeStoreKit,
5202
- nativeStoreKit: () => nativeStoreKit
5203
- });
5204
- function isAvailable3() {
5205
- return PaywalloStoreKitNative != null;
5206
- }
5207
- function mapNativeProduct(native) {
5208
- const product = {
5209
- productId: native.productId,
5210
- title: native.title,
5211
- description: native.description,
5212
- price: native.price,
5213
- priceValue: native.priceValue,
5214
- currency: native.currency ?? "USD",
5215
- localizedPrice: native.localizedPrice,
5216
- type: mapProductType(native.type),
5217
- subscriptionPeriod: native.subscriptionPeriod,
5218
- introductoryPrice: native.introductoryPrice,
5219
- introductoryPriceValue: native.introductoryPriceValue,
5220
- freeTrialPeriod: native.freeTrialPeriod
5221
- };
5222
- if (native.subscriptionPeriod) {
5223
- const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
5224
- product.pricePerMonth = prices.perMonth;
5225
- product.pricePerWeek = prices.perWeek;
5226
- product.pricePerDay = prices.perDay;
5227
- }
5228
- return product;
5229
- }
5230
- function mapProductType(type) {
5231
- switch (type) {
5232
- case "subscription":
5233
- case "autoRenewable":
5234
- return "subscription";
5235
- case "consumable":
5236
- return "consumable";
5237
- default:
5238
- return "non_consumable";
5239
- }
5240
- }
5241
- function calculatePerPeriodPrices(priceValue, period) {
5242
- const totalDays = periodToDays(period);
5243
- if (totalDays <= 0) return {};
5244
- const perDay = priceValue / totalDays;
5245
- const perWeek = perDay * 7;
5246
- const perMonth = perDay * 30;
5247
- return {
5248
- perDay: perDay.toFixed(2),
5249
- perWeek: perWeek.toFixed(2),
5250
- perMonth: perMonth.toFixed(2)
5251
- };
5252
- }
5253
- function periodToDays(period) {
5254
- const match = period.match(/^P(\d+)([DWMY])$/);
5255
- if (!match) return 0;
5256
- const value = parseInt(match[1], 10);
5257
- switch (match[2]) {
5258
- case "D":
5259
- return value;
5260
- case "W":
5261
- return value * 7;
5262
- case "M":
5263
- return value * 30;
5264
- case "Y":
5265
- return value * 365;
5266
- default:
5267
- return 0;
5268
- }
5269
- }
5270
- function mapNativePurchase(native) {
5271
- return {
5272
- productId: native.productId,
5273
- transactionId: native.transactionId,
5274
- transactionDate: native.transactionDate,
5275
- receipt: native.receipt,
5276
- platform: import_react_native18.Platform.OS
5277
- };
5278
- }
5279
- function normalizeTransactionUpdate(raw) {
5280
- if (!raw || typeof raw !== "object") return null;
5281
- const r = raw;
5282
- const type = r.type;
5283
- if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
5284
- const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
5285
- const productId = typeof r.productId === "string" ? r.productId : null;
5286
- if (!transactionId) return null;
5287
- const update = {
5288
- type,
5289
- transactionId,
5290
- productId: productId ?? ""
5291
- };
5292
- if (typeof r.amount === "number") update.amount = r.amount;
5293
- if (typeof r.currency === "string") update.currency = r.currency;
5294
- return update;
5295
- }
5296
- var import_react_native18, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
5297
- var init_NativeStoreKit = __esm({
5298
- "src/domains/iap/NativeStoreKit.ts"() {
5299
- "use strict";
5300
- import_react_native18 = require("react-native");
5301
- init_PaywalloClient();
5302
- init_uuid();
5303
- init_IdentityManager();
5304
- init_PurchaseError();
5305
- TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
5306
- PaywalloStoreKitNative = import_react_native18.NativeModules.PaywalloStoreKit ?? null;
5307
- nativeStoreKit = {
5308
- isAvailable: isAvailable3,
5309
- async getProducts(productIds) {
5310
- if (!PaywalloStoreKitNative) {
5311
- return [];
5312
- }
5313
- const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
5314
- return nativeProducts.map(mapNativeProduct);
5315
- },
5316
- async purchase(productId) {
5317
- if (!PaywalloStoreKitNative) {
5318
- throw new PurchaseError(
5319
- PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
5320
- "StoreKit native module not available"
5321
- );
5322
- }
5323
- const distinctId = identityManager.getDistinctId();
5324
- const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
5325
- const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
5326
- if (distinctId && appAccountToken === null) {
5327
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
5328
- }
5329
- try {
5330
- const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
5331
- if (!result || result.pending) {
5332
- return null;
5333
- }
5334
- return mapNativePurchase(result);
5335
- } catch (err) {
5336
- const message = err instanceof Error ? err.message : String(err);
5337
- if (message.toLowerCase().includes("cancel")) {
5338
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
5339
- }
5340
- throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
5341
- }
5342
- },
5343
- async finishTransaction(transactionId) {
5344
- if (!PaywalloStoreKitNative) return;
5345
- try {
5346
- await PaywalloStoreKitNative.finishTransaction(transactionId);
5347
- } catch (err) {
5348
- console.error("[Paywallo] finishTransaction native error", err);
5349
- }
5350
- },
5351
- async getActiveTransactions() {
5352
- if (!PaywalloStoreKitNative) return [];
5353
- try {
5354
- const results = await PaywalloStoreKitNative.getActiveTransactions();
5355
- return results.map(mapNativePurchase);
5356
- } catch (err) {
5357
- console.error("[Paywallo] getActiveTransactions failed", err);
5358
- return [];
5359
- }
5360
- },
5361
- /**
5362
- * Subscribes to native transaction update events (renewals, refunds,
5363
- * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
5364
- * `PurchasesUpdatedListener` + refund diff on Android.
5365
- *
5366
- * Returns a subscription with a `remove()` method. The caller is
5367
- * responsible for cleanup (typically on `Paywallo.reset()`).
5368
- *
5369
- * Returns `null` when the native module isn't available (non-mobile env,
5370
- * native module not linked) — callers should treat null as a no-op.
5371
- */
5372
- subscribeToTransactionUpdates(listener) {
5373
- if (!PaywalloStoreKitNative) return null;
5374
- try {
5375
- const EmitterCtor = import_react_native18.NativeEventEmitter;
5376
- const emitter = new EmitterCtor(PaywalloStoreKitNative);
5377
- const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
5378
- const update = normalizeTransactionUpdate(payload);
5379
- if (update) listener(update);
5380
- });
5381
- return { remove: () => sub.remove() };
5382
- } catch (err) {
5383
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
5384
- return null;
5385
- }
5386
- }
5387
- };
5388
- NativeStoreKit = nativeStoreKit;
5389
- }
5390
- });
5391
-
5392
5471
  // src/domains/iap/IAPService.ts
5393
5472
  function getIAPService() {
5394
5473
  if (!_instance) {
@@ -5489,8 +5568,8 @@ var init_IAPService = __esm({
5489
5568
  stopTransactionListener() {
5490
5569
  this.emitter.stopListener();
5491
5570
  }
5492
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5493
- async emitTransactionUpdate(_update) {
5571
+ async emitTransactionUpdate(update) {
5572
+ return this.emitter.emitTransactionUpdate(update);
5494
5573
  }
5495
5574
  async purchase(productId, options) {
5496
5575
  if (!nativeStoreKit.isAvailable()) {
@@ -5519,6 +5598,10 @@ var init_IAPService = __esm({
5519
5598
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
5520
5599
  await this.finishTransaction(purchase.transactionId);
5521
5600
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
5601
+ await this.emitter.emitTransactionCompleted(purchase, productId, {
5602
+ paywallId: options?.paywallId,
5603
+ variantId: options?.variantId
5604
+ });
5522
5605
  }).catch(async (err) => {
5523
5606
  const kind = this.validator.classifyValidationError(err);
5524
5607
  if (kind === "server4xx") {
@@ -6736,8 +6819,9 @@ var init_schemas = __esm({
6736
6819
  path: ["transaction_id"]
6737
6820
  });
6738
6821
  onboardingEventSchema = import_zod.z.object({
6739
- type: import_zod.z.enum(["step", "complete", "drop"]),
6740
- step_index: import_zod.z.number().int().nonnegative().optional(),
6822
+ type: import_zod.z.enum(["step", "complete"]),
6823
+ family: import_zod.z.literal("onboarding").optional(),
6824
+ order: import_zod.z.number().nonnegative().optional(),
6741
6825
  step_name: import_zod.z.string().optional(),
6742
6826
  variant_key: import_zod.z.string().optional(),
6743
6827
  time_on_prev_s: import_zod.z.number().nonnegative().optional()
@@ -6834,7 +6918,7 @@ function isValidEventName(name) {
6834
6918
  async function trackEvent(apiClient, state, eventName, options) {
6835
6919
  const distinctId = identityManager.getDistinctId();
6836
6920
  if (!distinctId) {
6837
- console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
6921
+ if (state.config?.debug) console.log("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
6838
6922
  return;
6839
6923
  }
6840
6924
  if (!isValidEventName(eventName)) {
@@ -7569,6 +7653,11 @@ function isServerError(res) {
7569
7653
  const r = res;
7570
7654
  return r.ok === false && typeof r.status === "number" && r.status >= 500;
7571
7655
  }
7656
+ function isRateLimitError(res) {
7657
+ if (typeof res !== "object" || res === null) return false;
7658
+ const r = res;
7659
+ return r.ok === false && r.status === 429;
7660
+ }
7572
7661
  async function postWithQueue(deps, url, payload, label, priority) {
7573
7662
  const enqueue = async () => {
7574
7663
  await offlineQueue.enqueue(
@@ -7590,6 +7679,9 @@ async function postWithQueue(deps, url, payload, label, priority) {
7590
7679
  if (isServerError(res) && deps.isOfflineQueueEnabled()) {
7591
7680
  if (deps.isDebug()) console.log("[Paywallo:ApiClient] Server error, queued for retry:", label, priority ?? "normal");
7592
7681
  await enqueue();
7682
+ } else if (isRateLimitError(res) && deps.isOfflineQueueEnabled()) {
7683
+ if (deps.isDebug()) console.log("[Paywallo:ApiClient] Rate limited (429), queued for retry:", label, priority ?? "normal");
7684
+ await enqueue();
7593
7685
  }
7594
7686
  } catch (error) {
7595
7687
  if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
@@ -7886,7 +7978,7 @@ var init_EventBatcher = __esm({
7886
7978
  try {
7887
7979
  await this.postBatch([event], `event_critical:${event.eventName}`, "critical");
7888
7980
  } catch (err) {
7889
- console.error("[Paywallo:CRITICAL] critical event post failed", { eventName: event.eventName, error: err instanceof Error ? err.message : String(err) });
7981
+ if (this.debug) console.log("[Paywallo:CRITICAL] critical event post failed", { eventName: event.eventName, error: err instanceof Error ? err.message : String(err) });
7890
7982
  }
7891
7983
  }
7892
7984
  }
@@ -7900,7 +7992,7 @@ var init_EventBatcher = __esm({
7900
7992
  try {
7901
7993
  await this.postBatch(batch, `event_batch:${batch.length}`);
7902
7994
  } catch (err) {
7903
- console.error("[Paywallo:NORMAL] batch post failed \u2014 relying on OfflineQueue", { batchSize: batch.length, error: err instanceof Error ? err.message : String(err) });
7995
+ if (this.debug) console.log("[Paywallo:NORMAL] batch post failed \u2014 relying on OfflineQueue", { batchSize: batch.length, error: err instanceof Error ? err.message : String(err) });
7904
7996
  }
7905
7997
  }
7906
7998
  async postBatch(events, label, priority = "normal") {
@@ -7915,7 +8007,7 @@ var init_EventBatcher = __esm({
7915
8007
  const parsed = v2EnvelopeSchema.safeParse(envelope);
7916
8008
  if (!parsed.success) {
7917
8009
  if (this.debug) console.log("[Paywallo EVENTS] V2 envelope schema validation failed:", parsed.error.issues);
7918
- console.error("[Paywallo:CRITICAL] V2 envelope schema validation failed", { issues: parsed.error.issues, eventCount: events.length, firstEventName: events[0]?.eventName, context: providerContext });
8010
+ if (this.debug) console.log("[Paywallo:CRITICAL] V2 envelope schema validation failed", { issues: parsed.error.issues, eventCount: events.length, firstEventName: events[0]?.eventName, context: providerContext });
7919
8011
  throw parsed.error;
7920
8012
  }
7921
8013
  await this.post(V2_BATCH_ENDPOINT, parsed.data, label, priority);
@@ -8533,7 +8625,6 @@ function setupNotifications(apiClient, config, environment, eventPipeline) {
8533
8625
  notificationsManager.setupHandlers({ eventBatcher: eventPipeline });
8534
8626
  notificationsManager.initialize({
8535
8627
  appKey: config.appKey,
8536
- apiBaseUrl: DEFAULT_API_URL,
8537
8628
  debug: config.debug,
8538
8629
  environment: environment === "Sandbox" ? "sandbox" : "production"
8539
8630
  }).catch((err) => {
@@ -8575,7 +8666,6 @@ var init_PaywalloInitHelpers = __esm({
8575
8666
  init_identity();
8576
8667
  init_notifications();
8577
8668
  init_NativePushBridge();
8578
- init_constants();
8579
8669
  init_SecureStorage();
8580
8670
  }
8581
8671
  });
@@ -8665,7 +8755,7 @@ function reportInitFailure(state, config, error, reason) {
8665
8755
  error: error instanceof Error ? error.message : String(error)
8666
8756
  });
8667
8757
  } catch (err) {
8668
- console.error("[Paywallo] reportInitFailure error", err);
8758
+ if (config.debug) console.log("[Paywallo] reportInitFailure error", err);
8669
8759
  }
8670
8760
  }
8671
8761
  function reportInitSuccess(state, config, attempts) {
@@ -8674,7 +8764,7 @@ function reportInitSuccess(state, config, attempts) {
8674
8764
  void state.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
8675
8765
  }
8676
8766
  } catch (err) {
8677
- console.error("[Paywallo] reportInitSuccess error", err);
8767
+ if (config.debug) console.log("[Paywallo] reportInitSuccess error", err);
8678
8768
  }
8679
8769
  }
8680
8770
  async function resolveSessionFlags(state, keys, apiClient, distinctId) {
@@ -8828,12 +8918,15 @@ async function doInit(state, config) {
8828
8918
  setupNotifications(apiClient, config, environment, eventPipeline);
8829
8919
  if (config.autoStartSession !== false) {
8830
8920
  sessionManager.startSession().catch((err) => {
8831
- console.error("[Paywallo INIT] autoStart session error", err);
8921
+ if (config.debug) console.log("[Paywallo INIT] autoStart session error", err);
8832
8922
  });
8833
8923
  }
8834
8924
  new InstallTracker(config.debug, {
8835
8925
  distinctIdProvider: () => identityManager.getDistinctId()
8836
- }).trackIfNeeded(apiClient, config.requestATT === true).catch(() => {
8926
+ }).trackIfNeeded(apiClient, config.requestATT === true).then(() => {
8927
+ if (config.debug) console.log("[Paywallo INSTALL] $app_installed sent");
8928
+ }).catch(() => {
8929
+ if (config.debug) console.log("[Paywallo INSTALL] $app_installed failed \u2014 will retry on next launch");
8837
8930
  });
8838
8931
  if (config.sessionFlags && config.sessionFlags.length > 0) {
8839
8932
  await resolveSessionFlags(state, config.sessionFlags, apiClient, distinctId);
@@ -9070,6 +9163,7 @@ var init_PaywalloClient = __esm({
9070
9163
  }
9071
9164
  if (this.state.config?.debug) console.log("[Paywallo IDENTIFY]", options ? { hasEmail: "email" in options && !!options.email, hasProperties: "properties" in options } : void 0);
9072
9165
  await identityManager.identify(options);
9166
+ if (this.state.config?.debug) console.log("[Paywallo IDENTIFY] done");
9073
9167
  }
9074
9168
  async track(eventName, options) {
9075
9169
  return trackEvent(this.getApiClientOrThrow(), this.state, eventName, options);
@@ -9630,18 +9724,14 @@ var import_react15 = require("react");
9630
9724
  init_onboarding();
9631
9725
  function useOnboarding() {
9632
9726
  const step = (0, import_react15.useCallback)(
9633
- (stepName, order) => onboardingManager.step(stepName, order),
9727
+ (stepName, order, options) => onboardingManager.step(stepName, order, options),
9634
9728
  []
9635
9729
  );
9636
9730
  const complete = (0, import_react15.useCallback)(
9637
- () => onboardingManager.complete(),
9638
- []
9639
- );
9640
- const drop = (0, import_react15.useCallback)(
9641
- (stepName) => onboardingManager.drop(stepName),
9731
+ (options) => onboardingManager.complete(options),
9642
9732
  []
9643
9733
  );
9644
- return { step, complete, drop };
9734
+ return { step, complete };
9645
9735
  }
9646
9736
 
9647
9737
  // src/hooks/usePaywallo.ts
@@ -10265,10 +10355,57 @@ function buildCallbacks() {
10265
10355
  const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
10266
10356
  log2("onPurchase", { productId, identifier });
10267
10357
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
10358
+ },
10359
+ // Global Superwall event listener — used specifically for `transactionComplete`
10360
+ // which is the only event that exposes the full product (currency/price) +
10361
+ // transaction (storeTransactionId) shape needed to create a server-side
10362
+ // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
10363
+ // handled above with their typed callbacks).
10364
+ onSuperwallEvent: (info) => {
10365
+ const ev = info?.event;
10366
+ if (!ev || ev.event !== "transactionComplete") return;
10367
+ const ts = Date.now();
10368
+ const product = ev.product;
10369
+ const transaction = ev.transaction;
10370
+ if (!product) {
10371
+ log2("transactionComplete missing product \u2014 skipping transaction track");
10372
+ return;
10373
+ }
10374
+ const productId = product.productIdentifier ?? product.id ?? "unknown";
10375
+ const currency = product.currencyCode ?? "USD";
10376
+ const price = product.price ?? 0;
10377
+ const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
10378
+ const introPrice = product.trialPeriodPrice ?? 0;
10379
+ const isTrial = product.hasFreeTrial === true && introPrice <= 0;
10380
+ const isPaidIntro = introPrice > 0;
10381
+ const paywallIdentifier = ev.paywallInfo?.identifier;
10382
+ log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
10383
+ void (async () => {
10384
+ try {
10385
+ await PaywalloClient.track("transaction", {
10386
+ priority: "critical",
10387
+ properties: {
10388
+ type: isTrial ? "trial_started" : "completed",
10389
+ // Server's IngestTranslator looks for `transaction_id`/`product_id`
10390
+ // /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
10391
+ // amount=0 (no revenue at start); a paid intro offer carries its
10392
+ // intro amount (real revenue); a normal purchase carries `price`.
10393
+ // `full_price` carries the recurring full price so the server can
10394
+ // value the eventual conversion (free trial) or keep the headline
10395
+ // price alongside the discounted intro amount (paid intro).
10396
+ transaction_id: transactionId,
10397
+ product_id: productId,
10398
+ amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
10399
+ currency,
10400
+ ...isTrial || isPaidIntro ? { full_price: price } : {},
10401
+ ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
10402
+ }
10403
+ });
10404
+ } catch (err) {
10405
+ log2("transactionComplete track error", { err: String(err) });
10406
+ }
10407
+ })();
10268
10408
  }
10269
- // onSuperwallEvent (transactionComplete → transaction track) removed —
10270
- // sale/transaction events are sourced exclusively from server-side webhooks
10271
- // (Apple/Google/Superwall/RevenueCat), the single source of truth for revenue.
10272
10409
  };
10273
10410
  }
10274
10411
  function startSuperwallAutoBridge(debug = false) {
@@ -10908,6 +11045,7 @@ function PaywalloProvider({ children, config }) {
10908
11045
  void pushAttributionToSuperwall(configRef.current.debug);
10909
11046
  setDistinctId(PaywalloClient.getDistinctId());
10910
11047
  setIsInitialized(true);
11048
+ if (configRef.current.debug) console.log("[Paywallo] SDK ready", PaywalloClient.getDistinctId()?.slice(0, 8));
10911
11049
  if (!autoPreloadTriggeredRef.current) {
10912
11050
  autoPreloadTriggeredRef.current = true;
10913
11051
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();