@virex-tech/paywallo-sdk 2.5.2 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +393 -261
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +389 -257
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2627,8 +2627,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2627
2627
|
// src/core/version.ts
|
|
2628
2628
|
function resolveVersion() {
|
|
2629
2629
|
try {
|
|
2630
|
-
if ("2.5.
|
|
2631
|
-
return "2.5.
|
|
2630
|
+
if ("2.5.3") {
|
|
2631
|
+
return "2.5.3";
|
|
2632
2632
|
}
|
|
2633
2633
|
} catch {
|
|
2634
2634
|
}
|
|
@@ -4111,17 +4111,292 @@ var init_PaywallWebView = __esm({
|
|
|
4111
4111
|
}
|
|
4112
4112
|
});
|
|
4113
4113
|
|
|
4114
|
+
// src/domains/iap/PurchaseError.ts
|
|
4115
|
+
function createPurchaseError(code, message) {
|
|
4116
|
+
return new PurchaseError(
|
|
4117
|
+
PURCHASE_ERROR_CODES[code],
|
|
4118
|
+
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4119
|
+
code === "USER_CANCELLED"
|
|
4120
|
+
);
|
|
4121
|
+
}
|
|
4122
|
+
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4123
|
+
var init_PurchaseError = __esm({
|
|
4124
|
+
"src/domains/iap/PurchaseError.ts"() {
|
|
4125
|
+
"use strict";
|
|
4126
|
+
init_PaywalloError();
|
|
4127
|
+
PurchaseError = class extends PaywalloError {
|
|
4128
|
+
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4129
|
+
super("purchase", code, message);
|
|
4130
|
+
this.name = "PurchaseError";
|
|
4131
|
+
this.userCancelled = userCancelled;
|
|
4132
|
+
this.httpStatus = httpStatus;
|
|
4133
|
+
}
|
|
4134
|
+
};
|
|
4135
|
+
PURCHASE_ERROR_CODES = {
|
|
4136
|
+
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4137
|
+
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4138
|
+
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4139
|
+
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4140
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4141
|
+
USER_CANCELLED: "USER_CANCELLED",
|
|
4142
|
+
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4143
|
+
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4144
|
+
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4145
|
+
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4146
|
+
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4147
|
+
};
|
|
4148
|
+
DEFAULT_MESSAGES = {
|
|
4149
|
+
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4150
|
+
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4151
|
+
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4152
|
+
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4153
|
+
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4154
|
+
USER_CANCELLED: "Purchase was cancelled.",
|
|
4155
|
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4156
|
+
STORE_ERROR: "Store error. Please try again later.",
|
|
4157
|
+
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4158
|
+
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4159
|
+
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
});
|
|
4163
|
+
|
|
4164
|
+
// src/domains/iap/NativeStoreKit.ts
|
|
4165
|
+
var NativeStoreKit_exports = {};
|
|
4166
|
+
__export(NativeStoreKit_exports, {
|
|
4167
|
+
NativeStoreKit: () => NativeStoreKit,
|
|
4168
|
+
nativeStoreKit: () => nativeStoreKit
|
|
4169
|
+
});
|
|
4170
|
+
function isAvailable3() {
|
|
4171
|
+
return PaywalloStoreKitNative != null;
|
|
4172
|
+
}
|
|
4173
|
+
function mapNativeProduct(native) {
|
|
4174
|
+
const product = {
|
|
4175
|
+
productId: native.productId,
|
|
4176
|
+
title: native.title,
|
|
4177
|
+
description: native.description,
|
|
4178
|
+
price: native.price,
|
|
4179
|
+
priceValue: native.priceValue,
|
|
4180
|
+
currency: native.currency ?? "USD",
|
|
4181
|
+
localizedPrice: native.localizedPrice,
|
|
4182
|
+
type: mapProductType(native.type),
|
|
4183
|
+
subscriptionPeriod: native.subscriptionPeriod,
|
|
4184
|
+
introductoryPrice: native.introductoryPrice,
|
|
4185
|
+
introductoryPriceValue: native.introductoryPriceValue,
|
|
4186
|
+
freeTrialPeriod: native.freeTrialPeriod
|
|
4187
|
+
};
|
|
4188
|
+
if (native.subscriptionPeriod) {
|
|
4189
|
+
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
4190
|
+
product.pricePerMonth = prices.perMonth;
|
|
4191
|
+
product.pricePerWeek = prices.perWeek;
|
|
4192
|
+
product.pricePerDay = prices.perDay;
|
|
4193
|
+
}
|
|
4194
|
+
return product;
|
|
4195
|
+
}
|
|
4196
|
+
function mapProductType(type) {
|
|
4197
|
+
switch (type) {
|
|
4198
|
+
case "subscription":
|
|
4199
|
+
case "autoRenewable":
|
|
4200
|
+
return "subscription";
|
|
4201
|
+
case "consumable":
|
|
4202
|
+
return "consumable";
|
|
4203
|
+
default:
|
|
4204
|
+
return "non_consumable";
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
function calculatePerPeriodPrices(priceValue, period) {
|
|
4208
|
+
const totalDays = periodToDays(period);
|
|
4209
|
+
if (totalDays <= 0) return {};
|
|
4210
|
+
const perDay = priceValue / totalDays;
|
|
4211
|
+
const perWeek = perDay * 7;
|
|
4212
|
+
const perMonth = perDay * 30;
|
|
4213
|
+
return {
|
|
4214
|
+
perDay: perDay.toFixed(2),
|
|
4215
|
+
perWeek: perWeek.toFixed(2),
|
|
4216
|
+
perMonth: perMonth.toFixed(2)
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
function periodToDays(period) {
|
|
4220
|
+
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
4221
|
+
if (!match) return 0;
|
|
4222
|
+
const value = parseInt(match[1], 10);
|
|
4223
|
+
switch (match[2]) {
|
|
4224
|
+
case "D":
|
|
4225
|
+
return value;
|
|
4226
|
+
case "W":
|
|
4227
|
+
return value * 7;
|
|
4228
|
+
case "M":
|
|
4229
|
+
return value * 30;
|
|
4230
|
+
case "Y":
|
|
4231
|
+
return value * 365;
|
|
4232
|
+
default:
|
|
4233
|
+
return 0;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
function mapNativePurchase(native) {
|
|
4237
|
+
return {
|
|
4238
|
+
productId: native.productId,
|
|
4239
|
+
transactionId: native.transactionId,
|
|
4240
|
+
transactionDate: native.transactionDate,
|
|
4241
|
+
receipt: native.receipt,
|
|
4242
|
+
platform: import_react_native16.Platform.OS
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
function normalizeTransactionUpdate(raw) {
|
|
4246
|
+
if (!raw || typeof raw !== "object") return null;
|
|
4247
|
+
const r = raw;
|
|
4248
|
+
const type = r.type;
|
|
4249
|
+
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
4250
|
+
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
4251
|
+
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
4252
|
+
if (!transactionId) return null;
|
|
4253
|
+
const update = {
|
|
4254
|
+
type,
|
|
4255
|
+
transactionId,
|
|
4256
|
+
productId: productId ?? ""
|
|
4257
|
+
};
|
|
4258
|
+
if (typeof r.amount === "number") update.amount = r.amount;
|
|
4259
|
+
if (typeof r.currency === "string") update.currency = r.currency;
|
|
4260
|
+
return update;
|
|
4261
|
+
}
|
|
4262
|
+
var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
4263
|
+
var init_NativeStoreKit = __esm({
|
|
4264
|
+
"src/domains/iap/NativeStoreKit.ts"() {
|
|
4265
|
+
"use strict";
|
|
4266
|
+
import_react_native16 = require("react-native");
|
|
4267
|
+
init_PaywalloClient();
|
|
4268
|
+
init_uuid();
|
|
4269
|
+
init_IdentityManager();
|
|
4270
|
+
init_PurchaseError();
|
|
4271
|
+
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
4272
|
+
PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
|
|
4273
|
+
nativeStoreKit = {
|
|
4274
|
+
isAvailable: isAvailable3,
|
|
4275
|
+
async getProducts(productIds) {
|
|
4276
|
+
if (!PaywalloStoreKitNative) {
|
|
4277
|
+
return [];
|
|
4278
|
+
}
|
|
4279
|
+
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
4280
|
+
return nativeProducts.map(mapNativeProduct);
|
|
4281
|
+
},
|
|
4282
|
+
async purchase(productId) {
|
|
4283
|
+
if (!PaywalloStoreKitNative) {
|
|
4284
|
+
throw new PurchaseError(
|
|
4285
|
+
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
4286
|
+
"StoreKit native module not available"
|
|
4287
|
+
);
|
|
4288
|
+
}
|
|
4289
|
+
const distinctId = identityManager.getDistinctId();
|
|
4290
|
+
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
4291
|
+
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
4292
|
+
if (distinctId && appAccountToken === null) {
|
|
4293
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
4294
|
+
}
|
|
4295
|
+
try {
|
|
4296
|
+
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
4297
|
+
if (!result || result.pending) {
|
|
4298
|
+
return null;
|
|
4299
|
+
}
|
|
4300
|
+
return mapNativePurchase(result);
|
|
4301
|
+
} catch (err) {
|
|
4302
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4303
|
+
if (message.toLowerCase().includes("cancel")) {
|
|
4304
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
4305
|
+
}
|
|
4306
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
4307
|
+
}
|
|
4308
|
+
},
|
|
4309
|
+
async finishTransaction(transactionId) {
|
|
4310
|
+
if (!PaywalloStoreKitNative) return;
|
|
4311
|
+
try {
|
|
4312
|
+
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
4313
|
+
} catch (err) {
|
|
4314
|
+
console.error("[Paywallo] finishTransaction native error", err);
|
|
4315
|
+
}
|
|
4316
|
+
},
|
|
4317
|
+
async getActiveTransactions() {
|
|
4318
|
+
if (!PaywalloStoreKitNative) return [];
|
|
4319
|
+
try {
|
|
4320
|
+
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
4321
|
+
return results.map(mapNativePurchase);
|
|
4322
|
+
} catch (err) {
|
|
4323
|
+
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
4324
|
+
return [];
|
|
4325
|
+
}
|
|
4326
|
+
},
|
|
4327
|
+
/**
|
|
4328
|
+
* Subscribes to native transaction update events (renewals, refunds,
|
|
4329
|
+
* cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
|
|
4330
|
+
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
4331
|
+
*
|
|
4332
|
+
* Returns a subscription with a `remove()` method. The caller is
|
|
4333
|
+
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
4334
|
+
*
|
|
4335
|
+
* Returns `null` when the native module isn't available (non-mobile env,
|
|
4336
|
+
* native module not linked) — callers should treat null as a no-op.
|
|
4337
|
+
*/
|
|
4338
|
+
subscribeToTransactionUpdates(listener) {
|
|
4339
|
+
if (!PaywalloStoreKitNative) return null;
|
|
4340
|
+
try {
|
|
4341
|
+
const EmitterCtor = import_react_native16.NativeEventEmitter;
|
|
4342
|
+
const emitter = new EmitterCtor(PaywalloStoreKitNative);
|
|
4343
|
+
const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
|
|
4344
|
+
const update = normalizeTransactionUpdate(payload);
|
|
4345
|
+
if (update) listener(update);
|
|
4346
|
+
});
|
|
4347
|
+
return { remove: () => sub.remove() };
|
|
4348
|
+
} catch (err) {
|
|
4349
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
|
|
4350
|
+
return null;
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
NativeStoreKit = nativeStoreKit;
|
|
4355
|
+
}
|
|
4356
|
+
});
|
|
4357
|
+
|
|
4114
4358
|
// src/domains/iap/IAPTransactionEmitter.ts
|
|
4115
4359
|
var IAPTransactionEmitter;
|
|
4116
4360
|
var init_IAPTransactionEmitter = __esm({
|
|
4117
4361
|
"src/domains/iap/IAPTransactionEmitter.ts"() {
|
|
4118
4362
|
"use strict";
|
|
4119
4363
|
init_PaywalloClient();
|
|
4364
|
+
init_NativeStoreKit();
|
|
4120
4365
|
IAPTransactionEmitter = class {
|
|
4121
4366
|
constructor(getProductFromCache, getApiClientFn, getDebug) {
|
|
4122
4367
|
this.getProductFromCache = getProductFromCache;
|
|
4123
4368
|
this.getApiClientFn = getApiClientFn;
|
|
4124
4369
|
this.getDebug = getDebug;
|
|
4370
|
+
this.transactionUpdateSubscription = null;
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* Emits `transaction {type: "completed"}` after a server-validated purchase.
|
|
4374
|
+
* Fire-and-forget — a tracking failure must never poison the purchase flow.
|
|
4375
|
+
*/
|
|
4376
|
+
async emitTransactionCompleted(purchase, productId, options) {
|
|
4377
|
+
try {
|
|
4378
|
+
const apiClient = this.getApiClientFn();
|
|
4379
|
+
if (!apiClient) return;
|
|
4380
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4381
|
+
if (!distinctId) return;
|
|
4382
|
+
const product = this.getProductFromCache(productId);
|
|
4383
|
+
const properties = {
|
|
4384
|
+
type: "completed",
|
|
4385
|
+
tx_id: purchase.transactionId,
|
|
4386
|
+
product_id: purchase.productId
|
|
4387
|
+
};
|
|
4388
|
+
if (product?.priceValue !== void 0 && product.priceValue > 0) {
|
|
4389
|
+
properties.amount = product.priceValue;
|
|
4390
|
+
}
|
|
4391
|
+
if (product?.currency) {
|
|
4392
|
+
properties.currency = product.currency;
|
|
4393
|
+
}
|
|
4394
|
+
if (options?.paywallId) properties.paywall_id = options.paywallId;
|
|
4395
|
+
if (options?.variantId) properties.variant_id = options.variantId;
|
|
4396
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4397
|
+
} catch (err) {
|
|
4398
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
4399
|
+
}
|
|
4125
4400
|
}
|
|
4126
4401
|
/**
|
|
4127
4402
|
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
@@ -4150,15 +4425,65 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
4150
4425
|
}
|
|
4151
4426
|
}
|
|
4152
4427
|
/**
|
|
4153
|
-
*
|
|
4154
|
-
*
|
|
4428
|
+
* Forwards a native transaction update to the EventBatcher.
|
|
4429
|
+
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
4430
|
+
*/
|
|
4431
|
+
async emitTransactionUpdate(update) {
|
|
4432
|
+
try {
|
|
4433
|
+
const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
|
|
4434
|
+
if (!apiClient) {
|
|
4435
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4439
|
+
if (!distinctId) {
|
|
4440
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
const properties = {
|
|
4444
|
+
type: update.type,
|
|
4445
|
+
tx_id: update.transactionId,
|
|
4446
|
+
product_id: update.productId
|
|
4447
|
+
};
|
|
4448
|
+
if (update.type !== "canceled") {
|
|
4449
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
4450
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
4451
|
+
const currency = update.currency ?? cached2?.currency;
|
|
4452
|
+
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
4453
|
+
if (currency) properties.currency = currency;
|
|
4454
|
+
}
|
|
4455
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4456
|
+
if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
|
|
4457
|
+
} catch (err) {
|
|
4458
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
/**
|
|
4462
|
+
* Starts listening to native Transaction.updates. Idempotent.
|
|
4155
4463
|
*/
|
|
4156
4464
|
startListener() {
|
|
4465
|
+
if (this.transactionUpdateSubscription) return;
|
|
4466
|
+
this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
|
|
4467
|
+
(update) => {
|
|
4468
|
+
void this.emitTransactionUpdate(update);
|
|
4469
|
+
}
|
|
4470
|
+
);
|
|
4471
|
+
if (this.getDebug() && this.transactionUpdateSubscription) {
|
|
4472
|
+
console.log("[Paywallo IAP] transaction update listener started");
|
|
4473
|
+
}
|
|
4157
4474
|
}
|
|
4158
4475
|
/**
|
|
4159
|
-
*
|
|
4476
|
+
* Removes the native listener. Safe to call multiple times.
|
|
4160
4477
|
*/
|
|
4161
4478
|
stopListener() {
|
|
4479
|
+
if (!this.transactionUpdateSubscription) return;
|
|
4480
|
+
try {
|
|
4481
|
+
this.transactionUpdateSubscription.remove();
|
|
4482
|
+
} catch (err) {
|
|
4483
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
|
|
4484
|
+
}
|
|
4485
|
+
this.transactionUpdateSubscription = null;
|
|
4486
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
|
|
4162
4487
|
}
|
|
4163
4488
|
};
|
|
4164
4489
|
}
|
|
@@ -4646,11 +4971,11 @@ var init_types2 = __esm({
|
|
|
4646
4971
|
});
|
|
4647
4972
|
|
|
4648
4973
|
// src/core/network/NetworkMonitor.ts
|
|
4649
|
-
var
|
|
4974
|
+
var import_react_native17, NetworkMonitorClass, networkMonitor;
|
|
4650
4975
|
var init_NetworkMonitor = __esm({
|
|
4651
4976
|
"src/core/network/NetworkMonitor.ts"() {
|
|
4652
4977
|
"use strict";
|
|
4653
|
-
|
|
4978
|
+
import_react_native17 = require("react-native");
|
|
4654
4979
|
init_types2();
|
|
4655
4980
|
NetworkMonitorClass = class {
|
|
4656
4981
|
constructor() {
|
|
@@ -4679,7 +5004,7 @@ var init_NetworkMonitor = __esm({
|
|
|
4679
5004
|
}, this.config.checkInterval);
|
|
4680
5005
|
}
|
|
4681
5006
|
setupAppStateListener() {
|
|
4682
|
-
this.appStateSubscription =
|
|
5007
|
+
this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
|
|
4683
5008
|
if (state === "active") {
|
|
4684
5009
|
if (!this.checkIntervalId) {
|
|
4685
5010
|
this.checkIntervalId = setInterval(() => {
|
|
@@ -5047,62 +5372,12 @@ var init_queue = __esm({
|
|
|
5047
5372
|
}
|
|
5048
5373
|
});
|
|
5049
5374
|
|
|
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;
|
|
5069
|
-
}
|
|
5070
|
-
};
|
|
5071
|
-
PURCHASE_ERROR_CODES = {
|
|
5072
|
-
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
5073
|
-
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
5074
|
-
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
5075
|
-
RESTORE_FAILED: "RESTORE_FAILED",
|
|
5076
|
-
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
5077
|
-
USER_CANCELLED: "USER_CANCELLED",
|
|
5078
|
-
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
5079
|
-
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
5080
|
-
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
5081
|
-
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
5082
|
-
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
5083
|
-
};
|
|
5084
|
-
DEFAULT_MESSAGES = {
|
|
5085
|
-
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
5086
|
-
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
5087
|
-
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
5088
|
-
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
5089
|
-
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
5090
|
-
USER_CANCELLED: "Purchase was cancelled.",
|
|
5091
|
-
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
5092
|
-
STORE_ERROR: "Store error. Please try again later.",
|
|
5093
|
-
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
5094
|
-
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
5095
|
-
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
5096
|
-
};
|
|
5097
|
-
}
|
|
5098
|
-
});
|
|
5099
|
-
|
|
5100
5375
|
// src/domains/iap/IAPValidator.ts
|
|
5101
|
-
var
|
|
5376
|
+
var import_react_native18, IAPValidator;
|
|
5102
5377
|
var init_IAPValidator = __esm({
|
|
5103
5378
|
"src/domains/iap/IAPValidator.ts"() {
|
|
5104
5379
|
"use strict";
|
|
5105
|
-
|
|
5380
|
+
import_react_native18 = require("react-native");
|
|
5106
5381
|
init_PaywalloClient();
|
|
5107
5382
|
init_queue();
|
|
5108
5383
|
init_PurchaseError();
|
|
@@ -5128,7 +5403,7 @@ var init_IAPValidator = __esm({
|
|
|
5128
5403
|
async validateWithServer(purchase, productId, options) {
|
|
5129
5404
|
const apiClient = this.getApiClientFn();
|
|
5130
5405
|
const product = this.getProductFromCache(productId);
|
|
5131
|
-
const platform =
|
|
5406
|
+
const platform = import_react_native18.Platform.OS;
|
|
5132
5407
|
const distinctId = PaywalloClient.getDistinctId();
|
|
5133
5408
|
return this.validateWithRetry(
|
|
5134
5409
|
() => apiClient.validatePurchase(
|
|
@@ -5163,7 +5438,7 @@ var init_IAPValidator = __esm({
|
|
|
5163
5438
|
try {
|
|
5164
5439
|
const apiClient = this.getApiClientFn();
|
|
5165
5440
|
const product = this.getProductFromCache(productId);
|
|
5166
|
-
const platform =
|
|
5441
|
+
const platform = import_react_native18.Platform.OS;
|
|
5167
5442
|
const distinctId = PaywalloClient.getDistinctId();
|
|
5168
5443
|
const appKey = apiClient.getAppKey();
|
|
5169
5444
|
const body = {
|
|
@@ -5195,200 +5470,6 @@ var init_IAPValidator = __esm({
|
|
|
5195
5470
|
}
|
|
5196
5471
|
});
|
|
5197
5472
|
|
|
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
5473
|
// src/domains/iap/IAPService.ts
|
|
5393
5474
|
function getIAPService() {
|
|
5394
5475
|
if (!_instance) {
|
|
@@ -5489,8 +5570,8 @@ var init_IAPService = __esm({
|
|
|
5489
5570
|
stopTransactionListener() {
|
|
5490
5571
|
this.emitter.stopListener();
|
|
5491
5572
|
}
|
|
5492
|
-
|
|
5493
|
-
|
|
5573
|
+
async emitTransactionUpdate(update) {
|
|
5574
|
+
return this.emitter.emitTransactionUpdate(update);
|
|
5494
5575
|
}
|
|
5495
5576
|
async purchase(productId, options) {
|
|
5496
5577
|
if (!nativeStoreKit.isAvailable()) {
|
|
@@ -5519,6 +5600,10 @@ var init_IAPService = __esm({
|
|
|
5519
5600
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
5520
5601
|
await this.finishTransaction(purchase.transactionId);
|
|
5521
5602
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
5603
|
+
await this.emitter.emitTransactionCompleted(purchase, productId, {
|
|
5604
|
+
paywallId: options?.paywallId,
|
|
5605
|
+
variantId: options?.variantId
|
|
5606
|
+
});
|
|
5522
5607
|
}).catch(async (err) => {
|
|
5523
5608
|
const kind = this.validator.classifyValidationError(err);
|
|
5524
5609
|
if (kind === "server4xx") {
|
|
@@ -10265,10 +10350,57 @@ function buildCallbacks() {
|
|
|
10265
10350
|
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
10266
10351
|
log2("onPurchase", { productId, identifier });
|
|
10267
10352
|
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10353
|
+
},
|
|
10354
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
10355
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
10356
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
10357
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
10358
|
+
// handled above with their typed callbacks).
|
|
10359
|
+
onSuperwallEvent: (info) => {
|
|
10360
|
+
const ev = info?.event;
|
|
10361
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
10362
|
+
const ts = Date.now();
|
|
10363
|
+
const product = ev.product;
|
|
10364
|
+
const transaction = ev.transaction;
|
|
10365
|
+
if (!product) {
|
|
10366
|
+
log2("transactionComplete missing product \u2014 skipping transaction track");
|
|
10367
|
+
return;
|
|
10368
|
+
}
|
|
10369
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
10370
|
+
const currency = product.currencyCode ?? "USD";
|
|
10371
|
+
const price = product.price ?? 0;
|
|
10372
|
+
const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
10373
|
+
const introPrice = product.trialPeriodPrice ?? 0;
|
|
10374
|
+
const isTrial = product.hasFreeTrial === true && introPrice <= 0;
|
|
10375
|
+
const isPaidIntro = introPrice > 0;
|
|
10376
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
10377
|
+
log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
|
|
10378
|
+
void (async () => {
|
|
10379
|
+
try {
|
|
10380
|
+
await PaywalloClient.track("transaction", {
|
|
10381
|
+
priority: "critical",
|
|
10382
|
+
properties: {
|
|
10383
|
+
type: isTrial ? "trial_started" : "completed",
|
|
10384
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
10385
|
+
// /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
|
|
10386
|
+
// amount=0 (no revenue at start); a paid intro offer carries its
|
|
10387
|
+
// intro amount (real revenue); a normal purchase carries `price`.
|
|
10388
|
+
// `full_price` carries the recurring full price so the server can
|
|
10389
|
+
// value the eventual conversion (free trial) or keep the headline
|
|
10390
|
+
// price alongside the discounted intro amount (paid intro).
|
|
10391
|
+
transaction_id: transactionId,
|
|
10392
|
+
product_id: productId,
|
|
10393
|
+
amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
|
|
10394
|
+
currency,
|
|
10395
|
+
...isTrial || isPaidIntro ? { full_price: price } : {},
|
|
10396
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10397
|
+
}
|
|
10398
|
+
});
|
|
10399
|
+
} catch (err) {
|
|
10400
|
+
log2("transactionComplete track error", { err: String(err) });
|
|
10401
|
+
}
|
|
10402
|
+
})();
|
|
10268
10403
|
}
|
|
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
10404
|
};
|
|
10273
10405
|
}
|
|
10274
10406
|
function startSuperwallAutoBridge(debug = false) {
|