@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.d.mts +27 -29
- package/dist/index.d.ts +27 -29
- package/dist/index.js +476 -338
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +472 -334
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
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
|
|
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.
|
|
2631
|
-
return "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 -
|
|
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 = {
|
|
3301
|
-
|
|
3302
|
-
|
|
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: "
|
|
3333
|
-
...{
|
|
3322
|
+
type: "complete",
|
|
3323
|
+
...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {}
|
|
3334
3324
|
};
|
|
3335
|
-
return this.emit("onboarding", payload, "
|
|
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
|
|
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
|
-
*
|
|
4154
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
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
|
|
5374
|
+
var import_react_native18, IAPValidator;
|
|
5102
5375
|
var init_IAPValidator = __esm({
|
|
5103
5376
|
"src/domains/iap/IAPValidator.ts"() {
|
|
5104
5377
|
"use strict";
|
|
5105
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5493
|
-
|
|
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"
|
|
6740
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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).
|
|
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
|
|
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();
|