@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.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
|
|
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(
|
|
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
|
|
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.
|
|
2615
|
-
return "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 -
|
|
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 = {
|
|
3285
|
-
|
|
3286
|
-
|
|
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: "
|
|
3317
|
-
...{
|
|
3306
|
+
type: "complete",
|
|
3307
|
+
...options?.variantKey !== void 0 ? { variant_key: options.variantKey } : {}
|
|
3318
3308
|
};
|
|
3319
|
-
return this.emit("onboarding", payload, "
|
|
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
|
|
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
|
-
*
|
|
4149
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5488
|
-
|
|
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"
|
|
6740
|
-
|
|
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.
|
|
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
|
});
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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).
|
|
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
|
|
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();
|