@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.mjs CHANGED
@@ -648,9 +648,12 @@ var init_IdentityManager = __esm({
648
648
  dateOfBirth: this.dateOfBirth ?? void 0,
649
649
  gender: this.gender ?? void 0
650
650
  });
651
+ if (this.debug) console.log("[Paywallo] identify confirmed", this.anonId.slice(0, 8));
651
652
  } catch (err) {
652
- if (this.debug) console.log("[Paywallo] identify dispatch error", err);
653
+ if (this.debug) console.log("[Paywallo] identify failed:", err);
653
654
  }
655
+ } else {
656
+ if (this.debug) console.log("[Paywallo] identify skipped \u2014 anonId not ready");
654
657
  }
655
658
  }
656
659
  getDistinctId() {
@@ -2285,18 +2288,15 @@ var init_constants = __esm({
2285
2288
  });
2286
2289
 
2287
2290
  // src/domains/notifications/config.ts
2288
- function resolveApiBaseUrl(config) {
2289
- if (config.apiBaseUrl) return config.apiBaseUrl;
2290
- if (config.environment === "sandbox") return SANDBOX_API_BASE_URL;
2291
+ function resolveApiBaseUrl() {
2291
2292
  return DEFAULT_API_BASE_URL;
2292
2293
  }
2293
- var DEFAULT_API_BASE_URL, SANDBOX_API_BASE_URL;
2294
+ var DEFAULT_API_BASE_URL;
2294
2295
  var init_config = __esm({
2295
2296
  "src/domains/notifications/config.ts"() {
2296
2297
  "use strict";
2297
2298
  init_constants();
2298
2299
  DEFAULT_API_BASE_URL = DEFAULT_API_URL;
2299
- SANDBOX_API_BASE_URL = DEFAULT_API_URL;
2300
2300
  }
2301
2301
  });
2302
2302
 
@@ -2611,8 +2611,8 @@ var init_NotificationHandlerSetup = __esm({
2611
2611
  // src/core/version.ts
2612
2612
  function resolveVersion() {
2613
2613
  try {
2614
- if ("2.5.2") {
2615
- return "2.5.2";
2614
+ if ("2.6.0") {
2615
+ return "2.6.0";
2616
2616
  }
2617
2617
  } catch {
2618
2618
  }
@@ -3066,10 +3066,7 @@ var init_NotificationsManager = __esm({
3066
3066
  }
3067
3067
  applyConfig(config) {
3068
3068
  this.appKey = config.appKey;
3069
- this.apiBaseUrl = resolveApiBaseUrl({
3070
- apiBaseUrl: config.apiBaseUrl,
3071
- environment: config.environment
3072
- });
3069
+ this.apiBaseUrl = resolveApiBaseUrl();
3073
3070
  this.debug = config.debug ?? false;
3074
3071
  this.permissionManager.setDebug(this.debug);
3075
3072
  }
@@ -3245,7 +3242,8 @@ var init_OnboardingError = __esm({
3245
3242
  };
3246
3243
  ONBOARDING_ERROR_CODES = {
3247
3244
  NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
3248
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
3245
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME",
3246
+ INVALID_ORDER: "ONBOARDING_INVALID_ORDER"
3249
3247
  };
3250
3248
  }
3251
3249
  });
@@ -3275,16 +3273,20 @@ var init_OnboardingManager = __esm({
3275
3273
  * Saves the step name internally for implicit drop detection on the backend.
3276
3274
  *
3277
3275
  * @param stepName - Unique name for this onboarding step.
3278
- * @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
3276
+ * @param order - Ordem de exibição do step. Obrigatório. Aceita decimais (ex: 2.1 para variantes A/B).
3277
+ * Deve ser um número finito e não-negativo. O servidor faz o floor — não arredondar aqui.
3279
3278
  */
3280
- async step(stepName, order) {
3279
+ async step(stepName, order, options) {
3281
3280
  this.assertConfig();
3282
3281
  this.validateStepName(stepName, "step");
3282
+ this.validateOrder(order);
3283
3283
  this.lastStep = stepName;
3284
- const stepPayload = { step_name: stepName };
3285
- if (order !== void 0 && Number.isFinite(order) && order >= 0) {
3286
- stepPayload.order = order;
3287
- }
3284
+ const stepPayload = {
3285
+ step_name: stepName,
3286
+ order,
3287
+ ...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {},
3288
+ ...options?.timeOnPrevS !== void 0 ? { time_on_prev_s: options.timeOnPrevS } : {}
3289
+ };
3288
3290
  const payload = {
3289
3291
  family: "onboarding",
3290
3292
  type: "step",
@@ -3296,33 +3298,21 @@ var init_OnboardingManager = __esm({
3296
3298
  * Tracks successful completion of the onboarding flow.
3297
3299
  * Marks the flow as finished.
3298
3300
  */
3299
- async complete() {
3301
+ async complete(options) {
3300
3302
  this.assertConfig();
3301
3303
  this.finished = true;
3302
- return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
3303
- }
3304
- /**
3305
- * Tracks an explicit drop (abandonment) at the given step.
3306
- * Marks the flow as finished.
3307
- *
3308
- * @param stepName - The step name where the user dropped off.
3309
- */
3310
- async drop(stepName) {
3311
- this.assertConfig();
3312
- this.validateStepName(stepName, "drop");
3313
- this.finished = true;
3314
3304
  const payload = {
3315
3305
  family: "onboarding",
3316
- type: "drop",
3317
- ...{ step_name: stepName }
3306
+ type: "complete",
3307
+ ...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {}
3318
3308
  };
3319
- return this.emit("onboarding", payload, "drop");
3309
+ return this.emit("onboarding", payload, "complete");
3320
3310
  }
3321
3311
  /** Returns the last step name seen, or null if no step was tracked yet. */
3322
3312
  getLastStep() {
3323
3313
  return this.lastStep;
3324
3314
  }
3325
- /** Returns whether the flow has been marked as finished (complete or drop). */
3315
+ /** Returns whether the flow has been marked as finished (complete). */
3326
3316
  isFinished() {
3327
3317
  return this.finished;
3328
3318
  }
@@ -3354,6 +3344,14 @@ var init_OnboardingManager = __esm({
3354
3344
  );
3355
3345
  }
3356
3346
  }
3347
+ validateOrder(order) {
3348
+ if (order === void 0 || order === null || !Number.isFinite(order) || order < 0) {
3349
+ throw new OnboardingError(
3350
+ ONBOARDING_ERROR_CODES.INVALID_ORDER,
3351
+ "OnboardingManager.step(): order deve ser um n\xFAmero finito e n\xE3o-negativo."
3352
+ );
3353
+ }
3354
+ }
3357
3355
  };
3358
3356
  onboardingManager = new OnboardingManager();
3359
3357
  }
@@ -4106,17 +4104,292 @@ var init_PaywallWebView = __esm({
4106
4104
  }
4107
4105
  });
4108
4106
 
4107
+ // src/domains/iap/PurchaseError.ts
4108
+ function createPurchaseError(code, message) {
4109
+ return new PurchaseError(
4110
+ PURCHASE_ERROR_CODES[code],
4111
+ message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
4112
+ code === "USER_CANCELLED"
4113
+ );
4114
+ }
4115
+ var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
4116
+ var init_PurchaseError = __esm({
4117
+ "src/domains/iap/PurchaseError.ts"() {
4118
+ "use strict";
4119
+ init_PaywalloError();
4120
+ PurchaseError = class extends PaywalloError {
4121
+ constructor(code, message, userCancelled = false, httpStatus) {
4122
+ super("purchase", code, message);
4123
+ this.name = "PurchaseError";
4124
+ this.userCancelled = userCancelled;
4125
+ this.httpStatus = httpStatus;
4126
+ }
4127
+ };
4128
+ PURCHASE_ERROR_CODES = {
4129
+ NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
4130
+ PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
4131
+ PURCHASE_FAILED: "PURCHASE_FAILED",
4132
+ RESTORE_FAILED: "RESTORE_FAILED",
4133
+ VALIDATION_FAILED: "VALIDATION_FAILED",
4134
+ USER_CANCELLED: "USER_CANCELLED",
4135
+ NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
4136
+ STORE_ERROR: "PURCHASE_STORE_ERROR",
4137
+ PENDING_PURCHASE: "PURCHASE_PENDING",
4138
+ DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
4139
+ STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
4140
+ };
4141
+ DEFAULT_MESSAGES = {
4142
+ NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
4143
+ PRODUCT_NOT_FOUND: "Product not found in store.",
4144
+ PURCHASE_FAILED: "Purchase failed. Please try again.",
4145
+ RESTORE_FAILED: "Failed to restore purchases. Please try again.",
4146
+ VALIDATION_FAILED: "Failed to validate purchase with server.",
4147
+ USER_CANCELLED: "Purchase was cancelled.",
4148
+ NETWORK_ERROR: "Network error. Please check your connection.",
4149
+ STORE_ERROR: "Store error. Please try again later.",
4150
+ PENDING_PURCHASE: "Purchase is pending approval.",
4151
+ DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
4152
+ STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
4153
+ };
4154
+ }
4155
+ });
4156
+
4157
+ // src/domains/iap/NativeStoreKit.ts
4158
+ var NativeStoreKit_exports = {};
4159
+ __export(NativeStoreKit_exports, {
4160
+ NativeStoreKit: () => NativeStoreKit,
4161
+ nativeStoreKit: () => nativeStoreKit
4162
+ });
4163
+ import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform11 } from "react-native";
4164
+ function isAvailable3() {
4165
+ return PaywalloStoreKitNative != null;
4166
+ }
4167
+ function mapNativeProduct(native) {
4168
+ const product = {
4169
+ productId: native.productId,
4170
+ title: native.title,
4171
+ description: native.description,
4172
+ price: native.price,
4173
+ priceValue: native.priceValue,
4174
+ currency: native.currency ?? "USD",
4175
+ localizedPrice: native.localizedPrice,
4176
+ type: mapProductType(native.type),
4177
+ subscriptionPeriod: native.subscriptionPeriod,
4178
+ introductoryPrice: native.introductoryPrice,
4179
+ introductoryPriceValue: native.introductoryPriceValue,
4180
+ freeTrialPeriod: native.freeTrialPeriod
4181
+ };
4182
+ if (native.subscriptionPeriod) {
4183
+ const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
4184
+ product.pricePerMonth = prices.perMonth;
4185
+ product.pricePerWeek = prices.perWeek;
4186
+ product.pricePerDay = prices.perDay;
4187
+ }
4188
+ return product;
4189
+ }
4190
+ function mapProductType(type) {
4191
+ switch (type) {
4192
+ case "subscription":
4193
+ case "autoRenewable":
4194
+ return "subscription";
4195
+ case "consumable":
4196
+ return "consumable";
4197
+ default:
4198
+ return "non_consumable";
4199
+ }
4200
+ }
4201
+ function calculatePerPeriodPrices(priceValue, period) {
4202
+ const totalDays = periodToDays(period);
4203
+ if (totalDays <= 0) return {};
4204
+ const perDay = priceValue / totalDays;
4205
+ const perWeek = perDay * 7;
4206
+ const perMonth = perDay * 30;
4207
+ return {
4208
+ perDay: perDay.toFixed(2),
4209
+ perWeek: perWeek.toFixed(2),
4210
+ perMonth: perMonth.toFixed(2)
4211
+ };
4212
+ }
4213
+ function periodToDays(period) {
4214
+ const match = period.match(/^P(\d+)([DWMY])$/);
4215
+ if (!match) return 0;
4216
+ const value = parseInt(match[1], 10);
4217
+ switch (match[2]) {
4218
+ case "D":
4219
+ return value;
4220
+ case "W":
4221
+ return value * 7;
4222
+ case "M":
4223
+ return value * 30;
4224
+ case "Y":
4225
+ return value * 365;
4226
+ default:
4227
+ return 0;
4228
+ }
4229
+ }
4230
+ function mapNativePurchase(native) {
4231
+ return {
4232
+ productId: native.productId,
4233
+ transactionId: native.transactionId,
4234
+ transactionDate: native.transactionDate,
4235
+ receipt: native.receipt,
4236
+ platform: Platform11.OS
4237
+ };
4238
+ }
4239
+ function normalizeTransactionUpdate(raw) {
4240
+ if (!raw || typeof raw !== "object") return null;
4241
+ const r = raw;
4242
+ const type = r.type;
4243
+ if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
4244
+ const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
4245
+ const productId = typeof r.productId === "string" ? r.productId : null;
4246
+ if (!transactionId) return null;
4247
+ const update = {
4248
+ type,
4249
+ transactionId,
4250
+ productId: productId ?? ""
4251
+ };
4252
+ if (typeof r.amount === "number") update.amount = r.amount;
4253
+ if (typeof r.currency === "string") update.currency = r.currency;
4254
+ return update;
4255
+ }
4256
+ var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
4257
+ var init_NativeStoreKit = __esm({
4258
+ "src/domains/iap/NativeStoreKit.ts"() {
4259
+ "use strict";
4260
+ init_PaywalloClient();
4261
+ init_uuid();
4262
+ init_IdentityManager();
4263
+ init_PurchaseError();
4264
+ TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
4265
+ PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
4266
+ nativeStoreKit = {
4267
+ isAvailable: isAvailable3,
4268
+ async getProducts(productIds) {
4269
+ if (!PaywalloStoreKitNative) {
4270
+ return [];
4271
+ }
4272
+ const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
4273
+ return nativeProducts.map(mapNativeProduct);
4274
+ },
4275
+ async purchase(productId) {
4276
+ if (!PaywalloStoreKitNative) {
4277
+ throw new PurchaseError(
4278
+ PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
4279
+ "StoreKit native module not available"
4280
+ );
4281
+ }
4282
+ const distinctId = identityManager.getDistinctId();
4283
+ const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
4284
+ const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
4285
+ if (distinctId && appAccountToken === null) {
4286
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
4287
+ }
4288
+ try {
4289
+ const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
4290
+ if (!result || result.pending) {
4291
+ return null;
4292
+ }
4293
+ return mapNativePurchase(result);
4294
+ } catch (err) {
4295
+ const message = err instanceof Error ? err.message : String(err);
4296
+ if (message.toLowerCase().includes("cancel")) {
4297
+ throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
4298
+ }
4299
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
4300
+ }
4301
+ },
4302
+ async finishTransaction(transactionId) {
4303
+ if (!PaywalloStoreKitNative) return;
4304
+ try {
4305
+ await PaywalloStoreKitNative.finishTransaction(transactionId);
4306
+ } catch (err) {
4307
+ console.error("[Paywallo] finishTransaction native error", err);
4308
+ }
4309
+ },
4310
+ async getActiveTransactions() {
4311
+ if (!PaywalloStoreKitNative) return [];
4312
+ try {
4313
+ const results = await PaywalloStoreKitNative.getActiveTransactions();
4314
+ return results.map(mapNativePurchase);
4315
+ } catch (err) {
4316
+ console.error("[Paywallo] getActiveTransactions failed", err);
4317
+ return [];
4318
+ }
4319
+ },
4320
+ /**
4321
+ * Subscribes to native transaction update events (renewals, refunds,
4322
+ * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
4323
+ * `PurchasesUpdatedListener` + refund diff on Android.
4324
+ *
4325
+ * Returns a subscription with a `remove()` method. The caller is
4326
+ * responsible for cleanup (typically on `Paywallo.reset()`).
4327
+ *
4328
+ * Returns `null` when the native module isn't available (non-mobile env,
4329
+ * native module not linked) — callers should treat null as a no-op.
4330
+ */
4331
+ subscribeToTransactionUpdates(listener) {
4332
+ if (!PaywalloStoreKitNative) return null;
4333
+ try {
4334
+ const EmitterCtor = NativeEventEmitter3;
4335
+ const emitter = new EmitterCtor(PaywalloStoreKitNative);
4336
+ const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
4337
+ const update = normalizeTransactionUpdate(payload);
4338
+ if (update) listener(update);
4339
+ });
4340
+ return { remove: () => sub.remove() };
4341
+ } catch (err) {
4342
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
4343
+ return null;
4344
+ }
4345
+ }
4346
+ };
4347
+ NativeStoreKit = nativeStoreKit;
4348
+ }
4349
+ });
4350
+
4109
4351
  // src/domains/iap/IAPTransactionEmitter.ts
4110
4352
  var IAPTransactionEmitter;
4111
4353
  var init_IAPTransactionEmitter = __esm({
4112
4354
  "src/domains/iap/IAPTransactionEmitter.ts"() {
4113
4355
  "use strict";
4114
4356
  init_PaywalloClient();
4357
+ init_NativeStoreKit();
4115
4358
  IAPTransactionEmitter = class {
4116
4359
  constructor(getProductFromCache, getApiClientFn, getDebug) {
4117
4360
  this.getProductFromCache = getProductFromCache;
4118
4361
  this.getApiClientFn = getApiClientFn;
4119
4362
  this.getDebug = getDebug;
4363
+ this.transactionUpdateSubscription = null;
4364
+ }
4365
+ /**
4366
+ * Emits `transaction {type: "completed"}` after a server-validated purchase.
4367
+ * Fire-and-forget — a tracking failure must never poison the purchase flow.
4368
+ */
4369
+ async emitTransactionCompleted(purchase, productId, options) {
4370
+ try {
4371
+ const apiClient = this.getApiClientFn();
4372
+ if (!apiClient) return;
4373
+ const distinctId = PaywalloClient.getDistinctId();
4374
+ if (!distinctId) return;
4375
+ const product = this.getProductFromCache(productId);
4376
+ const properties = {
4377
+ type: "completed",
4378
+ tx_id: purchase.transactionId,
4379
+ product_id: purchase.productId
4380
+ };
4381
+ if (product?.priceValue !== void 0 && product.priceValue > 0) {
4382
+ properties.amount = product.priceValue;
4383
+ }
4384
+ if (product?.currency) {
4385
+ properties.currency = product.currency;
4386
+ }
4387
+ if (options?.paywallId) properties.paywall_id = options.paywallId;
4388
+ if (options?.variantId) properties.variant_id = options.variantId;
4389
+ await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4390
+ } catch (err) {
4391
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
4392
+ }
4120
4393
  }
4121
4394
  /**
4122
4395
  * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
@@ -4145,15 +4418,65 @@ var init_IAPTransactionEmitter = __esm({
4145
4418
  }
4146
4419
  }
4147
4420
  /**
4148
- * No-op. Native transaction update listener removed — sale events are sourced
4149
- * exclusively from server-side webhooks (Apple/Google/Superwall/RevenueCat).
4421
+ * Forwards a native transaction update to the EventBatcher.
4422
+ * Public so tests can exercise the wiring without a fake NativeEventEmitter.
4423
+ */
4424
+ async emitTransactionUpdate(update) {
4425
+ try {
4426
+ const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
4427
+ if (!apiClient) {
4428
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
4429
+ return;
4430
+ }
4431
+ const distinctId = PaywalloClient.getDistinctId();
4432
+ if (!distinctId) {
4433
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
4434
+ return;
4435
+ }
4436
+ const properties = {
4437
+ type: update.type,
4438
+ tx_id: update.transactionId,
4439
+ product_id: update.productId
4440
+ };
4441
+ if (update.type !== "canceled") {
4442
+ const cached2 = this.getProductFromCache(update.productId);
4443
+ const amount = update.amount ?? cached2?.priceValue;
4444
+ const currency = update.currency ?? cached2?.currency;
4445
+ if (typeof amount === "number" && amount > 0) properties.amount = amount;
4446
+ if (currency) properties.currency = currency;
4447
+ }
4448
+ await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4449
+ if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
4450
+ } catch (err) {
4451
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
4452
+ }
4453
+ }
4454
+ /**
4455
+ * Starts listening to native Transaction.updates. Idempotent.
4150
4456
  */
4151
4457
  startListener() {
4458
+ if (this.transactionUpdateSubscription) return;
4459
+ this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
4460
+ (update) => {
4461
+ void this.emitTransactionUpdate(update);
4462
+ }
4463
+ );
4464
+ if (this.getDebug() && this.transactionUpdateSubscription) {
4465
+ console.log("[Paywallo IAP] transaction update listener started");
4466
+ }
4152
4467
  }
4153
4468
  /**
4154
- * No-op. See startListener.
4469
+ * Removes the native listener. Safe to call multiple times.
4155
4470
  */
4156
4471
  stopListener() {
4472
+ if (!this.transactionUpdateSubscription) return;
4473
+ try {
4474
+ this.transactionUpdateSubscription.remove();
4475
+ } catch (err) {
4476
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
4477
+ }
4478
+ this.transactionUpdateSubscription = null;
4479
+ if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
4157
4480
  }
4158
4481
  };
4159
4482
  }
@@ -4896,7 +5219,7 @@ var init_QueueProcessor = __esm({
4896
5219
  for (const item of batch) {
4897
5220
  retryIds.add(item.id);
4898
5221
  }
4899
- console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
5222
+ 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) });
4900
5223
  this.log("Event batch threw network error - will retry", {
4901
5224
  size: batch.length,
4902
5225
  error: error instanceof Error ? error.message : String(error)
@@ -4909,7 +5232,7 @@ var init_QueueProcessor = __esm({
4909
5232
  processed++;
4910
5233
  }
4911
5234
  this.log("Event batch processed successfully", { size: batch.length });
4912
- } else if (response.status >= 400 && response.status < 500) {
5235
+ } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4913
5236
  for (const item of batch) {
4914
5237
  await offlineQueue.removeItem(item.id);
4915
5238
  failed++;
@@ -4921,7 +5244,7 @@ var init_QueueProcessor = __esm({
4921
5244
  console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
4922
5245
  }
4923
5246
  } else {
4924
- 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) });
5247
+ 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) });
4925
5248
  }
4926
5249
  this.log("Event batch failed permanently (4xx) - removing from queue", {
4927
5250
  status: response.status,
@@ -4932,7 +5255,7 @@ var init_QueueProcessor = __esm({
4932
5255
  for (const item of batch) {
4933
5256
  retryIds.add(item.id);
4934
5257
  }
4935
- console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
5258
+ if (this.config.debug) console.log("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
4936
5259
  this.log("Event batch failed with server error - will retry", {
4937
5260
  status: response.status,
4938
5261
  size: batch.length
@@ -4962,7 +5285,7 @@ var init_QueueProcessor = __esm({
4962
5285
  skipRetry: false
4963
5286
  });
4964
5287
  } catch (error) {
4965
- 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) });
5288
+ 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) });
4966
5289
  this.log("Request threw network error - will retry", {
4967
5290
  id: item.id,
4968
5291
  error: error instanceof Error ? error.message : String(error)
@@ -4973,7 +5296,7 @@ var init_QueueProcessor = __esm({
4973
5296
  this.log("Queue item processed successfully", { id: item.id });
4974
5297
  return { success: true, permanent: false };
4975
5298
  }
4976
- if (response.status >= 400 && response.status < 500) {
5299
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
4977
5300
  this.log("Request failed with permanent client error - removing from queue", {
4978
5301
  id: item.id,
4979
5302
  url: item.url,
@@ -5019,81 +5342,31 @@ var init_QueueProcessor = __esm({
5019
5342
  this.queueUnsubscribe();
5020
5343
  this.queueUnsubscribe = null;
5021
5344
  }
5022
- this.httpClient = null;
5023
- this.initialized = false;
5024
- }
5025
- log(...args) {
5026
- if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
5027
- }
5028
- setDebug(debug) {
5029
- this.config.debug = debug;
5030
- }
5031
- };
5032
- queueProcessor = new QueueProcessorClass();
5033
- }
5034
- });
5035
-
5036
- // src/core/queue/index.ts
5037
- var init_queue = __esm({
5038
- "src/core/queue/index.ts"() {
5039
- "use strict";
5040
- init_OfflineQueue();
5041
- init_QueueProcessor();
5042
- }
5043
- });
5044
-
5045
- // src/domains/iap/PurchaseError.ts
5046
- function createPurchaseError(code, message) {
5047
- return new PurchaseError(
5048
- PURCHASE_ERROR_CODES[code],
5049
- message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
5050
- code === "USER_CANCELLED"
5051
- );
5052
- }
5053
- var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
5054
- var init_PurchaseError = __esm({
5055
- "src/domains/iap/PurchaseError.ts"() {
5056
- "use strict";
5057
- init_PaywalloError();
5058
- PurchaseError = class extends PaywalloError {
5059
- constructor(code, message, userCancelled = false, httpStatus) {
5060
- super("purchase", code, message);
5061
- this.name = "PurchaseError";
5062
- this.userCancelled = userCancelled;
5063
- this.httpStatus = httpStatus;
5345
+ this.httpClient = null;
5346
+ this.initialized = false;
5347
+ }
5348
+ log(...args) {
5349
+ if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
5350
+ }
5351
+ setDebug(debug) {
5352
+ this.config.debug = debug;
5064
5353
  }
5065
5354
  };
5066
- PURCHASE_ERROR_CODES = {
5067
- NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
5068
- PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
5069
- PURCHASE_FAILED: "PURCHASE_FAILED",
5070
- RESTORE_FAILED: "RESTORE_FAILED",
5071
- VALIDATION_FAILED: "VALIDATION_FAILED",
5072
- USER_CANCELLED: "USER_CANCELLED",
5073
- NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
5074
- STORE_ERROR: "PURCHASE_STORE_ERROR",
5075
- PENDING_PURCHASE: "PURCHASE_PENDING",
5076
- DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
5077
- STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
5078
- };
5079
- DEFAULT_MESSAGES = {
5080
- NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
5081
- PRODUCT_NOT_FOUND: "Product not found in store.",
5082
- PURCHASE_FAILED: "Purchase failed. Please try again.",
5083
- RESTORE_FAILED: "Failed to restore purchases. Please try again.",
5084
- VALIDATION_FAILED: "Failed to validate purchase with server.",
5085
- USER_CANCELLED: "Purchase was cancelled.",
5086
- NETWORK_ERROR: "Network error. Please check your connection.",
5087
- STORE_ERROR: "Store error. Please try again later.",
5088
- PENDING_PURCHASE: "Purchase is pending approval.",
5089
- DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
5090
- STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
5091
- };
5355
+ queueProcessor = new QueueProcessorClass();
5356
+ }
5357
+ });
5358
+
5359
+ // src/core/queue/index.ts
5360
+ var init_queue = __esm({
5361
+ "src/core/queue/index.ts"() {
5362
+ "use strict";
5363
+ init_OfflineQueue();
5364
+ init_QueueProcessor();
5092
5365
  }
5093
5366
  });
5094
5367
 
5095
5368
  // src/domains/iap/IAPValidator.ts
5096
- import { Platform as Platform11 } from "react-native";
5369
+ import { Platform as Platform12 } from "react-native";
5097
5370
  var IAPValidator;
5098
5371
  var init_IAPValidator = __esm({
5099
5372
  "src/domains/iap/IAPValidator.ts"() {
@@ -5123,7 +5396,7 @@ var init_IAPValidator = __esm({
5123
5396
  async validateWithServer(purchase, productId, options) {
5124
5397
  const apiClient = this.getApiClientFn();
5125
5398
  const product = this.getProductFromCache(productId);
5126
- const platform = Platform11.OS;
5399
+ const platform = Platform12.OS;
5127
5400
  const distinctId = PaywalloClient.getDistinctId();
5128
5401
  return this.validateWithRetry(
5129
5402
  () => apiClient.validatePurchase(
@@ -5158,7 +5431,7 @@ var init_IAPValidator = __esm({
5158
5431
  try {
5159
5432
  const apiClient = this.getApiClientFn();
5160
5433
  const product = this.getProductFromCache(productId);
5161
- const platform = Platform11.OS;
5434
+ const platform = Platform12.OS;
5162
5435
  const distinctId = PaywalloClient.getDistinctId();
5163
5436
  const appKey = apiClient.getAppKey();
5164
5437
  const body = {
@@ -5190,200 +5463,6 @@ var init_IAPValidator = __esm({
5190
5463
  }
5191
5464
  });
5192
5465
 
5193
- // src/domains/iap/NativeStoreKit.ts
5194
- var NativeStoreKit_exports = {};
5195
- __export(NativeStoreKit_exports, {
5196
- NativeStoreKit: () => NativeStoreKit,
5197
- nativeStoreKit: () => nativeStoreKit
5198
- });
5199
- import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform12 } from "react-native";
5200
- function isAvailable3() {
5201
- return PaywalloStoreKitNative != null;
5202
- }
5203
- function mapNativeProduct(native) {
5204
- const product = {
5205
- productId: native.productId,
5206
- title: native.title,
5207
- description: native.description,
5208
- price: native.price,
5209
- priceValue: native.priceValue,
5210
- currency: native.currency ?? "USD",
5211
- localizedPrice: native.localizedPrice,
5212
- type: mapProductType(native.type),
5213
- subscriptionPeriod: native.subscriptionPeriod,
5214
- introductoryPrice: native.introductoryPrice,
5215
- introductoryPriceValue: native.introductoryPriceValue,
5216
- freeTrialPeriod: native.freeTrialPeriod
5217
- };
5218
- if (native.subscriptionPeriod) {
5219
- const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
5220
- product.pricePerMonth = prices.perMonth;
5221
- product.pricePerWeek = prices.perWeek;
5222
- product.pricePerDay = prices.perDay;
5223
- }
5224
- return product;
5225
- }
5226
- function mapProductType(type) {
5227
- switch (type) {
5228
- case "subscription":
5229
- case "autoRenewable":
5230
- return "subscription";
5231
- case "consumable":
5232
- return "consumable";
5233
- default:
5234
- return "non_consumable";
5235
- }
5236
- }
5237
- function calculatePerPeriodPrices(priceValue, period) {
5238
- const totalDays = periodToDays(period);
5239
- if (totalDays <= 0) return {};
5240
- const perDay = priceValue / totalDays;
5241
- const perWeek = perDay * 7;
5242
- const perMonth = perDay * 30;
5243
- return {
5244
- perDay: perDay.toFixed(2),
5245
- perWeek: perWeek.toFixed(2),
5246
- perMonth: perMonth.toFixed(2)
5247
- };
5248
- }
5249
- function periodToDays(period) {
5250
- const match = period.match(/^P(\d+)([DWMY])$/);
5251
- if (!match) return 0;
5252
- const value = parseInt(match[1], 10);
5253
- switch (match[2]) {
5254
- case "D":
5255
- return value;
5256
- case "W":
5257
- return value * 7;
5258
- case "M":
5259
- return value * 30;
5260
- case "Y":
5261
- return value * 365;
5262
- default:
5263
- return 0;
5264
- }
5265
- }
5266
- function mapNativePurchase(native) {
5267
- return {
5268
- productId: native.productId,
5269
- transactionId: native.transactionId,
5270
- transactionDate: native.transactionDate,
5271
- receipt: native.receipt,
5272
- platform: Platform12.OS
5273
- };
5274
- }
5275
- function normalizeTransactionUpdate(raw) {
5276
- if (!raw || typeof raw !== "object") return null;
5277
- const r = raw;
5278
- const type = r.type;
5279
- if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
5280
- const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
5281
- const productId = typeof r.productId === "string" ? r.productId : null;
5282
- if (!transactionId) return null;
5283
- const update = {
5284
- type,
5285
- transactionId,
5286
- productId: productId ?? ""
5287
- };
5288
- if (typeof r.amount === "number") update.amount = r.amount;
5289
- if (typeof r.currency === "string") update.currency = r.currency;
5290
- return update;
5291
- }
5292
- var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
5293
- var init_NativeStoreKit = __esm({
5294
- "src/domains/iap/NativeStoreKit.ts"() {
5295
- "use strict";
5296
- init_PaywalloClient();
5297
- init_uuid();
5298
- init_IdentityManager();
5299
- init_PurchaseError();
5300
- TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
5301
- PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
5302
- nativeStoreKit = {
5303
- isAvailable: isAvailable3,
5304
- async getProducts(productIds) {
5305
- if (!PaywalloStoreKitNative) {
5306
- return [];
5307
- }
5308
- const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
5309
- return nativeProducts.map(mapNativeProduct);
5310
- },
5311
- async purchase(productId) {
5312
- if (!PaywalloStoreKitNative) {
5313
- throw new PurchaseError(
5314
- PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
5315
- "StoreKit native module not available"
5316
- );
5317
- }
5318
- const distinctId = identityManager.getDistinctId();
5319
- const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
5320
- const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
5321
- if (distinctId && appAccountToken === null) {
5322
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
5323
- }
5324
- try {
5325
- const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
5326
- if (!result || result.pending) {
5327
- return null;
5328
- }
5329
- return mapNativePurchase(result);
5330
- } catch (err) {
5331
- const message = err instanceof Error ? err.message : String(err);
5332
- if (message.toLowerCase().includes("cancel")) {
5333
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
5334
- }
5335
- throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
5336
- }
5337
- },
5338
- async finishTransaction(transactionId) {
5339
- if (!PaywalloStoreKitNative) return;
5340
- try {
5341
- await PaywalloStoreKitNative.finishTransaction(transactionId);
5342
- } catch (err) {
5343
- console.error("[Paywallo] finishTransaction native error", err);
5344
- }
5345
- },
5346
- async getActiveTransactions() {
5347
- if (!PaywalloStoreKitNative) return [];
5348
- try {
5349
- const results = await PaywalloStoreKitNative.getActiveTransactions();
5350
- return results.map(mapNativePurchase);
5351
- } catch (err) {
5352
- console.error("[Paywallo] getActiveTransactions failed", err);
5353
- return [];
5354
- }
5355
- },
5356
- /**
5357
- * Subscribes to native transaction update events (renewals, refunds,
5358
- * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
5359
- * `PurchasesUpdatedListener` + refund diff on Android.
5360
- *
5361
- * Returns a subscription with a `remove()` method. The caller is
5362
- * responsible for cleanup (typically on `Paywallo.reset()`).
5363
- *
5364
- * Returns `null` when the native module isn't available (non-mobile env,
5365
- * native module not linked) — callers should treat null as a no-op.
5366
- */
5367
- subscribeToTransactionUpdates(listener) {
5368
- if (!PaywalloStoreKitNative) return null;
5369
- try {
5370
- const EmitterCtor = NativeEventEmitter3;
5371
- const emitter = new EmitterCtor(PaywalloStoreKitNative);
5372
- const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
5373
- const update = normalizeTransactionUpdate(payload);
5374
- if (update) listener(update);
5375
- });
5376
- return { remove: () => sub.remove() };
5377
- } catch (err) {
5378
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
5379
- return null;
5380
- }
5381
- }
5382
- };
5383
- NativeStoreKit = nativeStoreKit;
5384
- }
5385
- });
5386
-
5387
5466
  // src/domains/iap/IAPService.ts
5388
5467
  import { Platform as Platform13 } from "react-native";
5389
5468
  function getIAPService() {
@@ -5484,8 +5563,8 @@ var init_IAPService = __esm({
5484
5563
  stopTransactionListener() {
5485
5564
  this.emitter.stopListener();
5486
5565
  }
5487
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5488
- async emitTransactionUpdate(_update) {
5566
+ async emitTransactionUpdate(update) {
5567
+ return this.emitter.emitTransactionUpdate(update);
5489
5568
  }
5490
5569
  async purchase(productId, options) {
5491
5570
  if (!nativeStoreKit.isAvailable()) {
@@ -5514,6 +5593,10 @@ var init_IAPService = __esm({
5514
5593
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
5515
5594
  await this.finishTransaction(purchase.transactionId);
5516
5595
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
5596
+ await this.emitter.emitTransactionCompleted(purchase, productId, {
5597
+ paywallId: options?.paywallId,
5598
+ variantId: options?.variantId
5599
+ });
5517
5600
  }).catch(async (err) => {
5518
5601
  const kind = this.validator.classifyValidationError(err);
5519
5602
  if (kind === "server4xx") {
@@ -6736,8 +6819,9 @@ var init_schemas = __esm({
6736
6819
  path: ["transaction_id"]
6737
6820
  });
6738
6821
  onboardingEventSchema = z.object({
6739
- type: z.enum(["step", "complete", "drop"]),
6740
- step_index: z.number().int().nonnegative().optional(),
6822
+ type: z.enum(["step", "complete"]),
6823
+ family: z.literal("onboarding").optional(),
6824
+ order: z.number().nonnegative().optional(),
6741
6825
  step_name: z.string().optional(),
6742
6826
  variant_key: z.string().optional(),
6743
6827
  time_on_prev_s: 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
  });
@@ -8666,7 +8756,7 @@ function reportInitFailure(state, config, error, reason) {
8666
8756
  error: error instanceof Error ? error.message : String(error)
8667
8757
  });
8668
8758
  } catch (err) {
8669
- console.error("[Paywallo] reportInitFailure error", err);
8759
+ if (config.debug) console.log("[Paywallo] reportInitFailure error", err);
8670
8760
  }
8671
8761
  }
8672
8762
  function reportInitSuccess(state, config, attempts) {
@@ -8675,7 +8765,7 @@ function reportInitSuccess(state, config, attempts) {
8675
8765
  void state.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
8676
8766
  }
8677
8767
  } catch (err) {
8678
- console.error("[Paywallo] reportInitSuccess error", err);
8768
+ if (config.debug) console.log("[Paywallo] reportInitSuccess error", err);
8679
8769
  }
8680
8770
  }
8681
8771
  async function resolveSessionFlags(state, keys, apiClient, distinctId) {
@@ -8829,12 +8919,15 @@ async function doInit(state, config) {
8829
8919
  setupNotifications(apiClient, config, environment, eventPipeline);
8830
8920
  if (config.autoStartSession !== false) {
8831
8921
  sessionManager.startSession().catch((err) => {
8832
- console.error("[Paywallo INIT] autoStart session error", err);
8922
+ if (config.debug) console.log("[Paywallo INIT] autoStart session error", err);
8833
8923
  });
8834
8924
  }
8835
8925
  new InstallTracker(config.debug, {
8836
8926
  distinctIdProvider: () => identityManager.getDistinctId()
8837
- }).trackIfNeeded(apiClient, config.requestATT === true).catch(() => {
8927
+ }).trackIfNeeded(apiClient, config.requestATT === true).then(() => {
8928
+ if (config.debug) console.log("[Paywallo INSTALL] $app_installed sent");
8929
+ }).catch(() => {
8930
+ if (config.debug) console.log("[Paywallo INSTALL] $app_installed failed \u2014 will retry on next launch");
8838
8931
  });
8839
8932
  if (config.sessionFlags && config.sessionFlags.length > 0) {
8840
8933
  await resolveSessionFlags(state, config.sessionFlags, apiClient, distinctId);
@@ -9069,6 +9162,7 @@ var init_PaywalloClient = __esm({
9069
9162
  }
9070
9163
  if (this.state.config?.debug) console.log("[Paywallo IDENTIFY]", options ? { hasEmail: "email" in options && !!options.email, hasProperties: "properties" in options } : void 0);
9071
9164
  await identityManager.identify(options);
9165
+ if (this.state.config?.debug) console.log("[Paywallo IDENTIFY] done");
9072
9166
  }
9073
9167
  async track(eventName, options) {
9074
9168
  return trackEvent(this.getApiClientOrThrow(), this.state, eventName, options);
@@ -9550,18 +9644,14 @@ init_onboarding();
9550
9644
  import { useCallback as useCallback9 } from "react";
9551
9645
  function useOnboarding() {
9552
9646
  const step = useCallback9(
9553
- (stepName, order) => onboardingManager.step(stepName, order),
9647
+ (stepName, order, options) => onboardingManager.step(stepName, order, options),
9554
9648
  []
9555
9649
  );
9556
9650
  const complete = useCallback9(
9557
- () => onboardingManager.complete(),
9558
- []
9559
- );
9560
- const drop = useCallback9(
9561
- (stepName) => onboardingManager.drop(stepName),
9651
+ (options) => onboardingManager.complete(options),
9562
9652
  []
9563
9653
  );
9564
- return { step, complete, drop };
9654
+ return { step, complete };
9565
9655
  }
9566
9656
 
9567
9657
  // src/hooks/usePaywallo.ts
@@ -10185,10 +10275,57 @@ function buildCallbacks() {
10185
10275
  const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
10186
10276
  log2("onPurchase", { productId, identifier });
10187
10277
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
10278
+ },
10279
+ // Global Superwall event listener — used specifically for `transactionComplete`
10280
+ // which is the only event that exposes the full product (currency/price) +
10281
+ // transaction (storeTransactionId) shape needed to create a server-side
10282
+ // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
10283
+ // handled above with their typed callbacks).
10284
+ onSuperwallEvent: (info) => {
10285
+ const ev = info?.event;
10286
+ if (!ev || ev.event !== "transactionComplete") return;
10287
+ const ts = Date.now();
10288
+ const product = ev.product;
10289
+ const transaction = ev.transaction;
10290
+ if (!product) {
10291
+ log2("transactionComplete missing product \u2014 skipping transaction track");
10292
+ return;
10293
+ }
10294
+ const productId = product.productIdentifier ?? product.id ?? "unknown";
10295
+ const currency = product.currencyCode ?? "USD";
10296
+ const price = product.price ?? 0;
10297
+ const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
10298
+ const introPrice = product.trialPeriodPrice ?? 0;
10299
+ const isTrial = product.hasFreeTrial === true && introPrice <= 0;
10300
+ const isPaidIntro = introPrice > 0;
10301
+ const paywallIdentifier = ev.paywallInfo?.identifier;
10302
+ log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
10303
+ void (async () => {
10304
+ try {
10305
+ await PaywalloClient.track("transaction", {
10306
+ priority: "critical",
10307
+ properties: {
10308
+ type: isTrial ? "trial_started" : "completed",
10309
+ // Server's IngestTranslator looks for `transaction_id`/`product_id`
10310
+ // /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
10311
+ // amount=0 (no revenue at start); a paid intro offer carries its
10312
+ // intro amount (real revenue); a normal purchase carries `price`.
10313
+ // `full_price` carries the recurring full price so the server can
10314
+ // value the eventual conversion (free trial) or keep the headline
10315
+ // price alongside the discounted intro amount (paid intro).
10316
+ transaction_id: transactionId,
10317
+ product_id: productId,
10318
+ amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
10319
+ currency,
10320
+ ...isTrial || isPaidIntro ? { full_price: price } : {},
10321
+ ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
10322
+ }
10323
+ });
10324
+ } catch (err) {
10325
+ log2("transactionComplete track error", { err: String(err) });
10326
+ }
10327
+ })();
10188
10328
  }
10189
- // onSuperwallEvent (transactionComplete → transaction track) removed —
10190
- // sale/transaction events are sourced exclusively from server-side webhooks
10191
- // (Apple/Google/Superwall/RevenueCat), the single source of truth for revenue.
10192
10329
  };
10193
10330
  }
10194
10331
  function startSuperwallAutoBridge(debug = false) {
@@ -10828,6 +10965,7 @@ function PaywalloProvider({ children, config }) {
10828
10965
  void pushAttributionToSuperwall(configRef.current.debug);
10829
10966
  setDistinctId(PaywalloClient.getDistinctId());
10830
10967
  setIsInitialized(true);
10968
+ if (configRef.current.debug) console.log("[Paywallo] SDK ready", PaywalloClient.getDistinctId()?.slice(0, 8));
10831
10969
  if (!autoPreloadTriggeredRef.current) {
10832
10970
  autoPreloadTriggeredRef.current = true;
10833
10971
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();