@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.mjs
CHANGED
|
@@ -2611,8 +2611,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2611
2611
|
// src/core/version.ts
|
|
2612
2612
|
function resolveVersion() {
|
|
2613
2613
|
try {
|
|
2614
|
-
if ("2.5.
|
|
2615
|
-
return "2.5.
|
|
2614
|
+
if ("2.5.3") {
|
|
2615
|
+
return "2.5.3";
|
|
2616
2616
|
}
|
|
2617
2617
|
} catch {
|
|
2618
2618
|
}
|
|
@@ -4106,17 +4106,292 @@ var init_PaywallWebView = __esm({
|
|
|
4106
4106
|
}
|
|
4107
4107
|
});
|
|
4108
4108
|
|
|
4109
|
+
// src/domains/iap/PurchaseError.ts
|
|
4110
|
+
function createPurchaseError(code, message) {
|
|
4111
|
+
return new PurchaseError(
|
|
4112
|
+
PURCHASE_ERROR_CODES[code],
|
|
4113
|
+
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4114
|
+
code === "USER_CANCELLED"
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4118
|
+
var init_PurchaseError = __esm({
|
|
4119
|
+
"src/domains/iap/PurchaseError.ts"() {
|
|
4120
|
+
"use strict";
|
|
4121
|
+
init_PaywalloError();
|
|
4122
|
+
PurchaseError = class extends PaywalloError {
|
|
4123
|
+
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4124
|
+
super("purchase", code, message);
|
|
4125
|
+
this.name = "PurchaseError";
|
|
4126
|
+
this.userCancelled = userCancelled;
|
|
4127
|
+
this.httpStatus = httpStatus;
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
PURCHASE_ERROR_CODES = {
|
|
4131
|
+
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4132
|
+
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4133
|
+
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4134
|
+
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4135
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4136
|
+
USER_CANCELLED: "USER_CANCELLED",
|
|
4137
|
+
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4138
|
+
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4139
|
+
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4140
|
+
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4141
|
+
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4142
|
+
};
|
|
4143
|
+
DEFAULT_MESSAGES = {
|
|
4144
|
+
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4145
|
+
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4146
|
+
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4147
|
+
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4148
|
+
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4149
|
+
USER_CANCELLED: "Purchase was cancelled.",
|
|
4150
|
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4151
|
+
STORE_ERROR: "Store error. Please try again later.",
|
|
4152
|
+
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4153
|
+
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4154
|
+
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
});
|
|
4158
|
+
|
|
4159
|
+
// src/domains/iap/NativeStoreKit.ts
|
|
4160
|
+
var NativeStoreKit_exports = {};
|
|
4161
|
+
__export(NativeStoreKit_exports, {
|
|
4162
|
+
NativeStoreKit: () => NativeStoreKit,
|
|
4163
|
+
nativeStoreKit: () => nativeStoreKit
|
|
4164
|
+
});
|
|
4165
|
+
import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform11 } from "react-native";
|
|
4166
|
+
function isAvailable3() {
|
|
4167
|
+
return PaywalloStoreKitNative != null;
|
|
4168
|
+
}
|
|
4169
|
+
function mapNativeProduct(native) {
|
|
4170
|
+
const product = {
|
|
4171
|
+
productId: native.productId,
|
|
4172
|
+
title: native.title,
|
|
4173
|
+
description: native.description,
|
|
4174
|
+
price: native.price,
|
|
4175
|
+
priceValue: native.priceValue,
|
|
4176
|
+
currency: native.currency ?? "USD",
|
|
4177
|
+
localizedPrice: native.localizedPrice,
|
|
4178
|
+
type: mapProductType(native.type),
|
|
4179
|
+
subscriptionPeriod: native.subscriptionPeriod,
|
|
4180
|
+
introductoryPrice: native.introductoryPrice,
|
|
4181
|
+
introductoryPriceValue: native.introductoryPriceValue,
|
|
4182
|
+
freeTrialPeriod: native.freeTrialPeriod
|
|
4183
|
+
};
|
|
4184
|
+
if (native.subscriptionPeriod) {
|
|
4185
|
+
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
4186
|
+
product.pricePerMonth = prices.perMonth;
|
|
4187
|
+
product.pricePerWeek = prices.perWeek;
|
|
4188
|
+
product.pricePerDay = prices.perDay;
|
|
4189
|
+
}
|
|
4190
|
+
return product;
|
|
4191
|
+
}
|
|
4192
|
+
function mapProductType(type) {
|
|
4193
|
+
switch (type) {
|
|
4194
|
+
case "subscription":
|
|
4195
|
+
case "autoRenewable":
|
|
4196
|
+
return "subscription";
|
|
4197
|
+
case "consumable":
|
|
4198
|
+
return "consumable";
|
|
4199
|
+
default:
|
|
4200
|
+
return "non_consumable";
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function calculatePerPeriodPrices(priceValue, period) {
|
|
4204
|
+
const totalDays = periodToDays(period);
|
|
4205
|
+
if (totalDays <= 0) return {};
|
|
4206
|
+
const perDay = priceValue / totalDays;
|
|
4207
|
+
const perWeek = perDay * 7;
|
|
4208
|
+
const perMonth = perDay * 30;
|
|
4209
|
+
return {
|
|
4210
|
+
perDay: perDay.toFixed(2),
|
|
4211
|
+
perWeek: perWeek.toFixed(2),
|
|
4212
|
+
perMonth: perMonth.toFixed(2)
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
function periodToDays(period) {
|
|
4216
|
+
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
4217
|
+
if (!match) return 0;
|
|
4218
|
+
const value = parseInt(match[1], 10);
|
|
4219
|
+
switch (match[2]) {
|
|
4220
|
+
case "D":
|
|
4221
|
+
return value;
|
|
4222
|
+
case "W":
|
|
4223
|
+
return value * 7;
|
|
4224
|
+
case "M":
|
|
4225
|
+
return value * 30;
|
|
4226
|
+
case "Y":
|
|
4227
|
+
return value * 365;
|
|
4228
|
+
default:
|
|
4229
|
+
return 0;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
function mapNativePurchase(native) {
|
|
4233
|
+
return {
|
|
4234
|
+
productId: native.productId,
|
|
4235
|
+
transactionId: native.transactionId,
|
|
4236
|
+
transactionDate: native.transactionDate,
|
|
4237
|
+
receipt: native.receipt,
|
|
4238
|
+
platform: Platform11.OS
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
function normalizeTransactionUpdate(raw) {
|
|
4242
|
+
if (!raw || typeof raw !== "object") return null;
|
|
4243
|
+
const r = raw;
|
|
4244
|
+
const type = r.type;
|
|
4245
|
+
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
4246
|
+
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
4247
|
+
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
4248
|
+
if (!transactionId) return null;
|
|
4249
|
+
const update = {
|
|
4250
|
+
type,
|
|
4251
|
+
transactionId,
|
|
4252
|
+
productId: productId ?? ""
|
|
4253
|
+
};
|
|
4254
|
+
if (typeof r.amount === "number") update.amount = r.amount;
|
|
4255
|
+
if (typeof r.currency === "string") update.currency = r.currency;
|
|
4256
|
+
return update;
|
|
4257
|
+
}
|
|
4258
|
+
var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
4259
|
+
var init_NativeStoreKit = __esm({
|
|
4260
|
+
"src/domains/iap/NativeStoreKit.ts"() {
|
|
4261
|
+
"use strict";
|
|
4262
|
+
init_PaywalloClient();
|
|
4263
|
+
init_uuid();
|
|
4264
|
+
init_IdentityManager();
|
|
4265
|
+
init_PurchaseError();
|
|
4266
|
+
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
4267
|
+
PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
|
|
4268
|
+
nativeStoreKit = {
|
|
4269
|
+
isAvailable: isAvailable3,
|
|
4270
|
+
async getProducts(productIds) {
|
|
4271
|
+
if (!PaywalloStoreKitNative) {
|
|
4272
|
+
return [];
|
|
4273
|
+
}
|
|
4274
|
+
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
4275
|
+
return nativeProducts.map(mapNativeProduct);
|
|
4276
|
+
},
|
|
4277
|
+
async purchase(productId) {
|
|
4278
|
+
if (!PaywalloStoreKitNative) {
|
|
4279
|
+
throw new PurchaseError(
|
|
4280
|
+
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
4281
|
+
"StoreKit native module not available"
|
|
4282
|
+
);
|
|
4283
|
+
}
|
|
4284
|
+
const distinctId = identityManager.getDistinctId();
|
|
4285
|
+
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
4286
|
+
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
4287
|
+
if (distinctId && appAccountToken === null) {
|
|
4288
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
4289
|
+
}
|
|
4290
|
+
try {
|
|
4291
|
+
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
4292
|
+
if (!result || result.pending) {
|
|
4293
|
+
return null;
|
|
4294
|
+
}
|
|
4295
|
+
return mapNativePurchase(result);
|
|
4296
|
+
} catch (err) {
|
|
4297
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4298
|
+
if (message.toLowerCase().includes("cancel")) {
|
|
4299
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
4300
|
+
}
|
|
4301
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
4302
|
+
}
|
|
4303
|
+
},
|
|
4304
|
+
async finishTransaction(transactionId) {
|
|
4305
|
+
if (!PaywalloStoreKitNative) return;
|
|
4306
|
+
try {
|
|
4307
|
+
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
4308
|
+
} catch (err) {
|
|
4309
|
+
console.error("[Paywallo] finishTransaction native error", err);
|
|
4310
|
+
}
|
|
4311
|
+
},
|
|
4312
|
+
async getActiveTransactions() {
|
|
4313
|
+
if (!PaywalloStoreKitNative) return [];
|
|
4314
|
+
try {
|
|
4315
|
+
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
4316
|
+
return results.map(mapNativePurchase);
|
|
4317
|
+
} catch (err) {
|
|
4318
|
+
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
4319
|
+
return [];
|
|
4320
|
+
}
|
|
4321
|
+
},
|
|
4322
|
+
/**
|
|
4323
|
+
* Subscribes to native transaction update events (renewals, refunds,
|
|
4324
|
+
* cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
|
|
4325
|
+
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
4326
|
+
*
|
|
4327
|
+
* Returns a subscription with a `remove()` method. The caller is
|
|
4328
|
+
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
4329
|
+
*
|
|
4330
|
+
* Returns `null` when the native module isn't available (non-mobile env,
|
|
4331
|
+
* native module not linked) — callers should treat null as a no-op.
|
|
4332
|
+
*/
|
|
4333
|
+
subscribeToTransactionUpdates(listener) {
|
|
4334
|
+
if (!PaywalloStoreKitNative) return null;
|
|
4335
|
+
try {
|
|
4336
|
+
const EmitterCtor = NativeEventEmitter3;
|
|
4337
|
+
const emitter = new EmitterCtor(PaywalloStoreKitNative);
|
|
4338
|
+
const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
|
|
4339
|
+
const update = normalizeTransactionUpdate(payload);
|
|
4340
|
+
if (update) listener(update);
|
|
4341
|
+
});
|
|
4342
|
+
return { remove: () => sub.remove() };
|
|
4343
|
+
} catch (err) {
|
|
4344
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
|
|
4345
|
+
return null;
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
};
|
|
4349
|
+
NativeStoreKit = nativeStoreKit;
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
4352
|
+
|
|
4109
4353
|
// src/domains/iap/IAPTransactionEmitter.ts
|
|
4110
4354
|
var IAPTransactionEmitter;
|
|
4111
4355
|
var init_IAPTransactionEmitter = __esm({
|
|
4112
4356
|
"src/domains/iap/IAPTransactionEmitter.ts"() {
|
|
4113
4357
|
"use strict";
|
|
4114
4358
|
init_PaywalloClient();
|
|
4359
|
+
init_NativeStoreKit();
|
|
4115
4360
|
IAPTransactionEmitter = class {
|
|
4116
4361
|
constructor(getProductFromCache, getApiClientFn, getDebug) {
|
|
4117
4362
|
this.getProductFromCache = getProductFromCache;
|
|
4118
4363
|
this.getApiClientFn = getApiClientFn;
|
|
4119
4364
|
this.getDebug = getDebug;
|
|
4365
|
+
this.transactionUpdateSubscription = null;
|
|
4366
|
+
}
|
|
4367
|
+
/**
|
|
4368
|
+
* Emits `transaction {type: "completed"}` after a server-validated purchase.
|
|
4369
|
+
* Fire-and-forget — a tracking failure must never poison the purchase flow.
|
|
4370
|
+
*/
|
|
4371
|
+
async emitTransactionCompleted(purchase, productId, options) {
|
|
4372
|
+
try {
|
|
4373
|
+
const apiClient = this.getApiClientFn();
|
|
4374
|
+
if (!apiClient) return;
|
|
4375
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4376
|
+
if (!distinctId) return;
|
|
4377
|
+
const product = this.getProductFromCache(productId);
|
|
4378
|
+
const properties = {
|
|
4379
|
+
type: "completed",
|
|
4380
|
+
tx_id: purchase.transactionId,
|
|
4381
|
+
product_id: purchase.productId
|
|
4382
|
+
};
|
|
4383
|
+
if (product?.priceValue !== void 0 && product.priceValue > 0) {
|
|
4384
|
+
properties.amount = product.priceValue;
|
|
4385
|
+
}
|
|
4386
|
+
if (product?.currency) {
|
|
4387
|
+
properties.currency = product.currency;
|
|
4388
|
+
}
|
|
4389
|
+
if (options?.paywallId) properties.paywall_id = options.paywallId;
|
|
4390
|
+
if (options?.variantId) properties.variant_id = options.variantId;
|
|
4391
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4392
|
+
} catch (err) {
|
|
4393
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
4394
|
+
}
|
|
4120
4395
|
}
|
|
4121
4396
|
/**
|
|
4122
4397
|
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
@@ -4145,15 +4420,65 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
4145
4420
|
}
|
|
4146
4421
|
}
|
|
4147
4422
|
/**
|
|
4148
|
-
*
|
|
4149
|
-
*
|
|
4423
|
+
* Forwards a native transaction update to the EventBatcher.
|
|
4424
|
+
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
4425
|
+
*/
|
|
4426
|
+
async emitTransactionUpdate(update) {
|
|
4427
|
+
try {
|
|
4428
|
+
const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
|
|
4429
|
+
if (!apiClient) {
|
|
4430
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4434
|
+
if (!distinctId) {
|
|
4435
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
const properties = {
|
|
4439
|
+
type: update.type,
|
|
4440
|
+
tx_id: update.transactionId,
|
|
4441
|
+
product_id: update.productId
|
|
4442
|
+
};
|
|
4443
|
+
if (update.type !== "canceled") {
|
|
4444
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
4445
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
4446
|
+
const currency = update.currency ?? cached2?.currency;
|
|
4447
|
+
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
4448
|
+
if (currency) properties.currency = currency;
|
|
4449
|
+
}
|
|
4450
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4451
|
+
if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
|
|
4452
|
+
} catch (err) {
|
|
4453
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
/**
|
|
4457
|
+
* Starts listening to native Transaction.updates. Idempotent.
|
|
4150
4458
|
*/
|
|
4151
4459
|
startListener() {
|
|
4460
|
+
if (this.transactionUpdateSubscription) return;
|
|
4461
|
+
this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
|
|
4462
|
+
(update) => {
|
|
4463
|
+
void this.emitTransactionUpdate(update);
|
|
4464
|
+
}
|
|
4465
|
+
);
|
|
4466
|
+
if (this.getDebug() && this.transactionUpdateSubscription) {
|
|
4467
|
+
console.log("[Paywallo IAP] transaction update listener started");
|
|
4468
|
+
}
|
|
4152
4469
|
}
|
|
4153
4470
|
/**
|
|
4154
|
-
*
|
|
4471
|
+
* Removes the native listener. Safe to call multiple times.
|
|
4155
4472
|
*/
|
|
4156
4473
|
stopListener() {
|
|
4474
|
+
if (!this.transactionUpdateSubscription) return;
|
|
4475
|
+
try {
|
|
4476
|
+
this.transactionUpdateSubscription.remove();
|
|
4477
|
+
} catch (err) {
|
|
4478
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
|
|
4479
|
+
}
|
|
4480
|
+
this.transactionUpdateSubscription = null;
|
|
4481
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
|
|
4157
4482
|
}
|
|
4158
4483
|
};
|
|
4159
4484
|
}
|
|
@@ -5042,58 +5367,8 @@ var init_queue = __esm({
|
|
|
5042
5367
|
}
|
|
5043
5368
|
});
|
|
5044
5369
|
|
|
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;
|
|
5064
|
-
}
|
|
5065
|
-
};
|
|
5066
|
-
PURCHASE_ERROR_CODES = {
|
|
5067
|
-
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
5068
|
-
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
5069
|
-
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
5070
|
-
RESTORE_FAILED: "RESTORE_FAILED",
|
|
5071
|
-
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
5072
|
-
USER_CANCELLED: "USER_CANCELLED",
|
|
5073
|
-
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
5074
|
-
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
5075
|
-
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
5076
|
-
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
5077
|
-
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
5078
|
-
};
|
|
5079
|
-
DEFAULT_MESSAGES = {
|
|
5080
|
-
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
5081
|
-
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
5082
|
-
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
5083
|
-
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
5084
|
-
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
5085
|
-
USER_CANCELLED: "Purchase was cancelled.",
|
|
5086
|
-
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
5087
|
-
STORE_ERROR: "Store error. Please try again later.",
|
|
5088
|
-
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
5089
|
-
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
5090
|
-
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
5091
|
-
};
|
|
5092
|
-
}
|
|
5093
|
-
});
|
|
5094
|
-
|
|
5095
5370
|
// src/domains/iap/IAPValidator.ts
|
|
5096
|
-
import { Platform as
|
|
5371
|
+
import { Platform as Platform12 } from "react-native";
|
|
5097
5372
|
var IAPValidator;
|
|
5098
5373
|
var init_IAPValidator = __esm({
|
|
5099
5374
|
"src/domains/iap/IAPValidator.ts"() {
|
|
@@ -5123,7 +5398,7 @@ var init_IAPValidator = __esm({
|
|
|
5123
5398
|
async validateWithServer(purchase, productId, options) {
|
|
5124
5399
|
const apiClient = this.getApiClientFn();
|
|
5125
5400
|
const product = this.getProductFromCache(productId);
|
|
5126
|
-
const platform =
|
|
5401
|
+
const platform = Platform12.OS;
|
|
5127
5402
|
const distinctId = PaywalloClient.getDistinctId();
|
|
5128
5403
|
return this.validateWithRetry(
|
|
5129
5404
|
() => apiClient.validatePurchase(
|
|
@@ -5158,7 +5433,7 @@ var init_IAPValidator = __esm({
|
|
|
5158
5433
|
try {
|
|
5159
5434
|
const apiClient = this.getApiClientFn();
|
|
5160
5435
|
const product = this.getProductFromCache(productId);
|
|
5161
|
-
const platform =
|
|
5436
|
+
const platform = Platform12.OS;
|
|
5162
5437
|
const distinctId = PaywalloClient.getDistinctId();
|
|
5163
5438
|
const appKey = apiClient.getAppKey();
|
|
5164
5439
|
const body = {
|
|
@@ -5190,200 +5465,6 @@ var init_IAPValidator = __esm({
|
|
|
5190
5465
|
}
|
|
5191
5466
|
});
|
|
5192
5467
|
|
|
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
5468
|
// src/domains/iap/IAPService.ts
|
|
5388
5469
|
import { Platform as Platform13 } from "react-native";
|
|
5389
5470
|
function getIAPService() {
|
|
@@ -5484,8 +5565,8 @@ var init_IAPService = __esm({
|
|
|
5484
5565
|
stopTransactionListener() {
|
|
5485
5566
|
this.emitter.stopListener();
|
|
5486
5567
|
}
|
|
5487
|
-
|
|
5488
|
-
|
|
5568
|
+
async emitTransactionUpdate(update) {
|
|
5569
|
+
return this.emitter.emitTransactionUpdate(update);
|
|
5489
5570
|
}
|
|
5490
5571
|
async purchase(productId, options) {
|
|
5491
5572
|
if (!nativeStoreKit.isAvailable()) {
|
|
@@ -5514,6 +5595,10 @@ var init_IAPService = __esm({
|
|
|
5514
5595
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
5515
5596
|
await this.finishTransaction(purchase.transactionId);
|
|
5516
5597
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
5598
|
+
await this.emitter.emitTransactionCompleted(purchase, productId, {
|
|
5599
|
+
paywallId: options?.paywallId,
|
|
5600
|
+
variantId: options?.variantId
|
|
5601
|
+
});
|
|
5517
5602
|
}).catch(async (err) => {
|
|
5518
5603
|
const kind = this.validator.classifyValidationError(err);
|
|
5519
5604
|
if (kind === "server4xx") {
|
|
@@ -10185,10 +10270,57 @@ function buildCallbacks() {
|
|
|
10185
10270
|
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
10186
10271
|
log2("onPurchase", { productId, identifier });
|
|
10187
10272
|
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10273
|
+
},
|
|
10274
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
10275
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
10276
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
10277
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
10278
|
+
// handled above with their typed callbacks).
|
|
10279
|
+
onSuperwallEvent: (info) => {
|
|
10280
|
+
const ev = info?.event;
|
|
10281
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
10282
|
+
const ts = Date.now();
|
|
10283
|
+
const product = ev.product;
|
|
10284
|
+
const transaction = ev.transaction;
|
|
10285
|
+
if (!product) {
|
|
10286
|
+
log2("transactionComplete missing product \u2014 skipping transaction track");
|
|
10287
|
+
return;
|
|
10288
|
+
}
|
|
10289
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
10290
|
+
const currency = product.currencyCode ?? "USD";
|
|
10291
|
+
const price = product.price ?? 0;
|
|
10292
|
+
const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
10293
|
+
const introPrice = product.trialPeriodPrice ?? 0;
|
|
10294
|
+
const isTrial = product.hasFreeTrial === true && introPrice <= 0;
|
|
10295
|
+
const isPaidIntro = introPrice > 0;
|
|
10296
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
10297
|
+
log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
|
|
10298
|
+
void (async () => {
|
|
10299
|
+
try {
|
|
10300
|
+
await PaywalloClient.track("transaction", {
|
|
10301
|
+
priority: "critical",
|
|
10302
|
+
properties: {
|
|
10303
|
+
type: isTrial ? "trial_started" : "completed",
|
|
10304
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
10305
|
+
// /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
|
|
10306
|
+
// amount=0 (no revenue at start); a paid intro offer carries its
|
|
10307
|
+
// intro amount (real revenue); a normal purchase carries `price`.
|
|
10308
|
+
// `full_price` carries the recurring full price so the server can
|
|
10309
|
+
// value the eventual conversion (free trial) or keep the headline
|
|
10310
|
+
// price alongside the discounted intro amount (paid intro).
|
|
10311
|
+
transaction_id: transactionId,
|
|
10312
|
+
product_id: productId,
|
|
10313
|
+
amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
|
|
10314
|
+
currency,
|
|
10315
|
+
...isTrial || isPaidIntro ? { full_price: price } : {},
|
|
10316
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10317
|
+
}
|
|
10318
|
+
});
|
|
10319
|
+
} catch (err) {
|
|
10320
|
+
log2("transactionComplete track error", { err: String(err) });
|
|
10321
|
+
}
|
|
10322
|
+
})();
|
|
10188
10323
|
}
|
|
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
10324
|
};
|
|
10193
10325
|
}
|
|
10194
10326
|
function startSuperwallAutoBridge(debug = false) {
|