@hyve-sdk/js 1.3.2 → 1.3.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.mjs CHANGED
@@ -757,7 +757,568 @@ var AdsService = class {
757
757
  }
758
758
  };
759
759
 
760
+ // src/services/billing.ts
761
+ var BillingPlatform = /* @__PURE__ */ ((BillingPlatform3) => {
762
+ BillingPlatform3["WEB"] = "web";
763
+ BillingPlatform3["NATIVE"] = "native";
764
+ BillingPlatform3["UNKNOWN"] = "unknown";
765
+ return BillingPlatform3;
766
+ })(BillingPlatform || {});
767
+ var BillingService = class {
768
+ config;
769
+ platform = "unknown" /* UNKNOWN */;
770
+ isInitialized = false;
771
+ nativeAvailable = false;
772
+ products = [];
773
+ // Stripe instance for web payments
774
+ stripe = null;
775
+ checkoutElement = null;
776
+ // Callbacks for purchase events
777
+ onPurchaseCompleteCallback;
778
+ onPurchaseErrorCallback;
779
+ // Callback for logging (so external UI can capture logs)
780
+ onLogCallback;
781
+ constructor(config) {
782
+ this.config = config;
783
+ this.detectPlatform();
784
+ }
785
+ /**
786
+ * Register a callback to receive all internal logs
787
+ * Useful for displaying logs in a UI
788
+ */
789
+ onLog(callback) {
790
+ this.onLogCallback = callback;
791
+ }
792
+ /**
793
+ * Internal logging method that calls both logger and custom callback
794
+ */
795
+ log(level, message, data) {
796
+ const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
797
+ if (data !== void 0) {
798
+ consoleMethod(message, data);
799
+ } else {
800
+ consoleMethod(message);
801
+ }
802
+ if (this.onLogCallback) {
803
+ this.onLogCallback(level, message, data);
804
+ }
805
+ }
806
+ /**
807
+ * Detects if running on web or native platform
808
+ */
809
+ detectPlatform() {
810
+ const isNative = NativeBridge.isNativeContext();
811
+ const hasWindow = typeof window !== "undefined";
812
+ if (isNative) {
813
+ this.platform = "native" /* NATIVE */;
814
+ this.log("info", "[BillingService] Platform: NATIVE");
815
+ } else if (hasWindow) {
816
+ this.platform = "web" /* WEB */;
817
+ this.log("info", "[BillingService] Platform: WEB");
818
+ } else {
819
+ this.platform = "unknown" /* UNKNOWN */;
820
+ this.log("warn", "[BillingService] Platform: UNKNOWN");
821
+ }
822
+ }
823
+ /**
824
+ * Get the current platform
825
+ */
826
+ getPlatform() {
827
+ return this.platform;
828
+ }
829
+ /**
830
+ * Initialize the billing service
831
+ * Must be called before using any other methods
832
+ */
833
+ async initialize() {
834
+ if (this.isInitialized) {
835
+ return true;
836
+ }
837
+ this.log("info", `[BillingService] Initializing for ${this.platform} platform...`);
838
+ try {
839
+ if (this.platform === "native" /* NATIVE */) {
840
+ return await this.initializeNative();
841
+ } else if (this.platform === "web" /* WEB */) {
842
+ return await this.initializeWeb();
843
+ }
844
+ this.log("error", "[BillingService] Cannot initialize: unknown platform");
845
+ return false;
846
+ } catch (error) {
847
+ this.log("error", "[BillingService] Initialization failed:", error?.message);
848
+ return false;
849
+ }
850
+ }
851
+ /**
852
+ * Initialize native billing
853
+ */
854
+ async initializeNative() {
855
+ return new Promise((resolve) => {
856
+ try {
857
+ NativeBridge.initialize();
858
+ NativeBridge.on(
859
+ "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
860
+ (payload) => {
861
+ this.log("info", "[BillingService] Purchase complete:", payload.productId);
862
+ const result = {
863
+ success: true,
864
+ productId: payload.productId,
865
+ transactionId: payload.transactionId,
866
+ transactionDate: payload.transactionDate
867
+ };
868
+ if (this.onPurchaseCompleteCallback) {
869
+ this.onPurchaseCompleteCallback(result);
870
+ }
871
+ }
872
+ );
873
+ NativeBridge.on(
874
+ "PURCHASE_ERROR" /* PURCHASE_ERROR */,
875
+ (payload) => {
876
+ this.log("error", "[BillingService] Purchase error:", payload.message);
877
+ const result = {
878
+ success: false,
879
+ productId: payload.productId || "unknown",
880
+ error: {
881
+ code: payload.code,
882
+ message: payload.message
883
+ }
884
+ };
885
+ if (this.onPurchaseErrorCallback) {
886
+ this.onPurchaseErrorCallback(result);
887
+ }
888
+ }
889
+ );
890
+ NativeBridge.on(
891
+ "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
892
+ (payload) => {
893
+ this.nativeAvailable = payload.available;
894
+ this.isInitialized = true;
895
+ this.log("info", `[BillingService] Native billing ${payload.available ? "available" : "unavailable"}`);
896
+ resolve(payload.available);
897
+ }
898
+ );
899
+ setTimeout(() => {
900
+ NativeBridge.checkIAPAvailability();
901
+ }, 100);
902
+ setTimeout(() => {
903
+ if (!this.isInitialized) {
904
+ this.log("warn", "[BillingService] Native initialization timeout");
905
+ this.isInitialized = true;
906
+ resolve(false);
907
+ }
908
+ }, 5e3);
909
+ } catch (error) {
910
+ this.log("error", "[BillingService] Native initialization failed:", error?.message);
911
+ this.isInitialized = true;
912
+ resolve(false);
913
+ }
914
+ });
915
+ }
916
+ /**
917
+ * Initialize web billing (Stripe)
918
+ */
919
+ async initializeWeb() {
920
+ if (!this.config.stripePublishableKey) {
921
+ this.log("error", "[BillingService] Stripe publishable key not provided");
922
+ return false;
923
+ }
924
+ try {
925
+ if (typeof window === "undefined") {
926
+ this.log("error", "[BillingService] Window is undefined (not in browser)");
927
+ return false;
928
+ }
929
+ if (!window.Stripe) {
930
+ await this.loadStripeScript();
931
+ }
932
+ if (window.Stripe) {
933
+ this.stripe = window.Stripe(this.config.stripePublishableKey);
934
+ this.isInitialized = true;
935
+ this.log("info", "[BillingService] Web billing initialized");
936
+ return true;
937
+ } else {
938
+ this.log("error", "[BillingService] Stripe not available after loading");
939
+ return false;
940
+ }
941
+ } catch (error) {
942
+ this.log("error", "[BillingService] Stripe initialization failed:", error?.message);
943
+ return false;
944
+ }
945
+ }
946
+ /**
947
+ * Load Stripe.js script dynamically
948
+ */
949
+ loadStripeScript() {
950
+ return new Promise((resolve, reject) => {
951
+ if (typeof window === "undefined") {
952
+ reject(new Error("Window is not defined"));
953
+ return;
954
+ }
955
+ if (window.Stripe) {
956
+ resolve();
957
+ return;
958
+ }
959
+ if (!document || !document.head) {
960
+ reject(new Error("document is undefined"));
961
+ return;
962
+ }
963
+ const script = document.createElement("script");
964
+ script.src = "https://js.stripe.com/v3/";
965
+ script.async = true;
966
+ script.onload = () => {
967
+ this.log("info", "[BillingService] Stripe.js loaded");
968
+ resolve();
969
+ };
970
+ script.onerror = () => {
971
+ this.log("error", "[BillingService] Failed to load Stripe.js");
972
+ reject(new Error("Failed to load Stripe.js"));
973
+ };
974
+ try {
975
+ document.head.appendChild(script);
976
+ } catch (appendError) {
977
+ reject(appendError);
978
+ }
979
+ });
980
+ }
981
+ /**
982
+ * Check if billing is available
983
+ */
984
+ isAvailable() {
985
+ if (!this.isInitialized) {
986
+ return false;
987
+ }
988
+ if (this.platform === "native" /* NATIVE */) {
989
+ return this.nativeAvailable;
990
+ } else if (this.platform === "web" /* WEB */) {
991
+ return !!this.config.stripePublishableKey;
992
+ }
993
+ return false;
994
+ }
995
+ /**
996
+ * Get available products
997
+ */
998
+ async getProducts() {
999
+ if (!this.isInitialized) {
1000
+ throw new Error("BillingService not initialized. Call initialize() first.");
1001
+ }
1002
+ if (this.platform === "native" /* NATIVE */) {
1003
+ return await this.getProductsNative();
1004
+ } else if (this.platform === "web" /* WEB */) {
1005
+ return await this.getProductsWeb();
1006
+ }
1007
+ throw new Error("Cannot get products: unknown platform");
1008
+ }
1009
+ /**
1010
+ * Get products from native IAP
1011
+ */
1012
+ async getProductsNative() {
1013
+ return new Promise((resolve, reject) => {
1014
+ if (!this.config.gameId || !this.config.checkoutUrl) {
1015
+ const error = new Error("gameId and checkoutUrl required for native purchases");
1016
+ this.log("error", "[BillingService]", error.message);
1017
+ reject(error);
1018
+ return;
1019
+ }
1020
+ const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1021
+ fetch(url).then((response) => {
1022
+ if (!response.ok) {
1023
+ throw new Error(`Failed to fetch native products: ${response.status}`);
1024
+ }
1025
+ return response.json();
1026
+ }).then((data) => {
1027
+ if (!data.packages || !Array.isArray(data.packages)) {
1028
+ throw new Error("Invalid response format: missing packages array");
1029
+ }
1030
+ this.products = data.packages.map((pkg) => ({
1031
+ productId: pkg.productId,
1032
+ title: pkg.package_name,
1033
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1034
+ price: pkg.price_cents / 100,
1035
+ localizedPrice: pkg.price_display,
1036
+ currency: "USD"
1037
+ }));
1038
+ this.log("info", `[BillingService] Fetched ${this.products.length} native products`);
1039
+ resolve(this.products);
1040
+ }).catch((error) => {
1041
+ this.log("error", "[BillingService] Failed to fetch native products:", error?.message);
1042
+ reject(error);
1043
+ });
1044
+ });
1045
+ }
1046
+ /**
1047
+ * Get products from web API (Stripe)
1048
+ */
1049
+ async getProductsWeb() {
1050
+ if (!this.config.checkoutUrl || !this.config.gameId) {
1051
+ const error = new Error("checkoutUrl and gameId required for web purchases");
1052
+ this.log("error", "[BillingService]", error.message);
1053
+ throw error;
1054
+ }
1055
+ try {
1056
+ const url = `${this.config.checkoutUrl}/get-packages?game_id=${this.config.gameId}`;
1057
+ const response = await fetch(url);
1058
+ if (!response.ok) {
1059
+ throw new Error(`Failed to fetch web products: ${response.status}`);
1060
+ }
1061
+ const data = await response.json();
1062
+ if (!data.packages || !Array.isArray(data.packages)) {
1063
+ throw new Error("Invalid response format: missing packages array");
1064
+ }
1065
+ this.products = data.packages.map((pkg) => ({
1066
+ productId: pkg.priceId || pkg.productId,
1067
+ // Prefer priceId for Stripe
1068
+ title: pkg.package_name,
1069
+ description: `${pkg.game_name} - ${pkg.package_name}`,
1070
+ price: pkg.price_cents / 100,
1071
+ localizedPrice: pkg.price_display,
1072
+ currency: "USD"
1073
+ }));
1074
+ this.log("info", `[BillingService] Fetched ${this.products.length} web products`);
1075
+ return this.products;
1076
+ } catch (error) {
1077
+ this.log("error", "[BillingService] Failed to fetch web products:", error?.message);
1078
+ throw error;
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Purchase a product
1083
+ * @param productId - The product ID (priceId for web/Stripe, productId for native)
1084
+ * @param options - Optional purchase options
1085
+ * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1086
+ */
1087
+ async purchase(productId, options) {
1088
+ if (!this.isInitialized) {
1089
+ throw new Error("BillingService not initialized. Call initialize() first.");
1090
+ }
1091
+ if (!this.isAvailable()) {
1092
+ throw new Error("Billing is not available on this platform");
1093
+ }
1094
+ if (this.platform === "native" /* NATIVE */) {
1095
+ return await this.purchaseNative(productId);
1096
+ } else if (this.platform === "web" /* WEB */) {
1097
+ return await this.purchaseWeb(productId, options?.elementId);
1098
+ }
1099
+ throw new Error("Cannot purchase: unknown platform");
1100
+ }
1101
+ /**
1102
+ * Purchase via native IAP
1103
+ */
1104
+ async purchaseNative(productId) {
1105
+ return new Promise((resolve, reject) => {
1106
+ if (!this.config.userId) {
1107
+ reject(new Error("userId is required for native purchases"));
1108
+ return;
1109
+ }
1110
+ this.log("info", `[BillingService] Purchasing: ${productId}`);
1111
+ const previousCompleteCallback = this.onPurchaseCompleteCallback;
1112
+ const previousErrorCallback = this.onPurchaseErrorCallback;
1113
+ const cleanup = () => {
1114
+ this.onPurchaseCompleteCallback = previousCompleteCallback;
1115
+ this.onPurchaseErrorCallback = previousErrorCallback;
1116
+ };
1117
+ const completeHandler = (result) => {
1118
+ if (result.productId === productId) {
1119
+ cleanup();
1120
+ resolve(result);
1121
+ if (previousCompleteCallback) {
1122
+ previousCompleteCallback(result);
1123
+ }
1124
+ }
1125
+ };
1126
+ const errorHandler = (result) => {
1127
+ if (result.productId === productId) {
1128
+ cleanup();
1129
+ reject(new Error(result.error?.message || "Purchase failed"));
1130
+ if (previousErrorCallback) {
1131
+ previousErrorCallback(result);
1132
+ }
1133
+ }
1134
+ };
1135
+ this.onPurchaseCompleteCallback = completeHandler;
1136
+ this.onPurchaseErrorCallback = errorHandler;
1137
+ NativeBridge.purchase(productId, this.config.userId);
1138
+ setTimeout(() => {
1139
+ cleanup();
1140
+ reject(new Error("Purchase timeout"));
1141
+ }, 6e4);
1142
+ });
1143
+ }
1144
+ /**
1145
+ * Purchase via web (Stripe)
1146
+ * @param productId - The priceId (Stripe price ID) for web purchases
1147
+ * @param elementId - Optional DOM element ID to mount the checkout form (default: 'stripe-checkout-element')
1148
+ */
1149
+ async purchaseWeb(productId, elementId) {
1150
+ if (!this.config.userId || !this.config.checkoutUrl) {
1151
+ throw new Error("userId and checkoutUrl are required for web purchases");
1152
+ }
1153
+ if (!this.stripe) {
1154
+ throw new Error("Stripe not initialized. Call initialize() first.");
1155
+ }
1156
+ try {
1157
+ const returnUrl = typeof window !== "undefined" ? `${window.location.href}${window.location.href.includes("?") ? "&" : "?"}payment=complete` : void 0;
1158
+ const requestBody = {
1159
+ priceId: productId,
1160
+ // API expects priceId for Stripe
1161
+ userId: this.config.userId,
1162
+ gameId: this.config.gameId,
1163
+ embedded: true,
1164
+ // Enable embedded checkout
1165
+ return_url: returnUrl
1166
+ };
1167
+ const response = await fetch(`${this.config.checkoutUrl}/create-checkout-session`, {
1168
+ method: "POST",
1169
+ headers: {
1170
+ "Content-Type": "application/json"
1171
+ },
1172
+ body: JSON.stringify(requestBody)
1173
+ });
1174
+ if (!response.ok) {
1175
+ const errorText = await response.text();
1176
+ throw new Error(`Failed to create checkout session: ${response.status} - ${errorText}`);
1177
+ }
1178
+ const responseData = await response.json();
1179
+ const { client_secret, id } = responseData;
1180
+ if (!client_secret) {
1181
+ throw new Error("No client_secret returned from checkout session");
1182
+ }
1183
+ await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1184
+ this.log("info", `[BillingService] Checkout session created: ${id}`);
1185
+ return {
1186
+ success: true,
1187
+ productId,
1188
+ transactionId: id
1189
+ };
1190
+ } catch (error) {
1191
+ this.log("error", "[BillingService] Web purchase failed:", error?.message);
1192
+ return {
1193
+ success: false,
1194
+ productId,
1195
+ error: {
1196
+ code: "WEB_PURCHASE_FAILED",
1197
+ message: error.message || "Purchase failed"
1198
+ }
1199
+ };
1200
+ }
1201
+ }
1202
+ /**
1203
+ * Mount Stripe embedded checkout element to the DOM
1204
+ * @param clientSecret - The client secret from the checkout session
1205
+ * @param elementId - The ID of the DOM element to mount to
1206
+ */
1207
+ async mountCheckoutElement(clientSecret, elementId) {
1208
+ if (!this.stripe) {
1209
+ throw new Error("Stripe not initialized");
1210
+ }
1211
+ try {
1212
+ const container = document.getElementById(elementId);
1213
+ if (!container) {
1214
+ throw new Error(`Element with id "${elementId}" not found in the DOM`);
1215
+ }
1216
+ container.innerHTML = "";
1217
+ this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1218
+ clientSecret
1219
+ });
1220
+ this.checkoutElement.mount(`#${elementId}`);
1221
+ this.setupCheckoutEventListeners();
1222
+ } catch (error) {
1223
+ this.log("error", "[BillingService] Failed to mount checkout:", error?.message);
1224
+ throw error;
1225
+ }
1226
+ }
1227
+ /**
1228
+ * Set up event listeners for checkout completion
1229
+ */
1230
+ setupCheckoutEventListeners() {
1231
+ if (typeof window !== "undefined") {
1232
+ const urlParams = new URLSearchParams(window.location.search);
1233
+ const paymentStatus = urlParams.get("payment");
1234
+ if (paymentStatus === "complete") {
1235
+ const sessionId = urlParams.get("session_id");
1236
+ if (this.onPurchaseCompleteCallback) {
1237
+ this.onPurchaseCompleteCallback({
1238
+ success: true,
1239
+ productId: "",
1240
+ // Would need to be tracked separately
1241
+ transactionId: sessionId || void 0
1242
+ });
1243
+ }
1244
+ this.log("info", "[BillingService] Payment completed");
1245
+ urlParams.delete("payment");
1246
+ urlParams.delete("session_id");
1247
+ const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
1248
+ window.history.replaceState({}, "", newUrl);
1249
+ }
1250
+ }
1251
+ }
1252
+ /**
1253
+ * Unmount and destroy the checkout element
1254
+ */
1255
+ unmountCheckoutElement() {
1256
+ if (this.checkoutElement) {
1257
+ this.checkoutElement.unmount();
1258
+ this.checkoutElement = null;
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Set callback for successful purchases
1263
+ */
1264
+ onPurchaseComplete(callback) {
1265
+ this.onPurchaseCompleteCallback = callback;
1266
+ }
1267
+ /**
1268
+ * Set callback for failed purchases
1269
+ */
1270
+ onPurchaseError(callback) {
1271
+ this.onPurchaseErrorCallback = callback;
1272
+ }
1273
+ /**
1274
+ * Clean up resources
1275
+ */
1276
+ dispose() {
1277
+ if (this.platform === "native" /* NATIVE */) {
1278
+ NativeBridge.off("IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */);
1279
+ NativeBridge.off("PRODUCTS_RESULT" /* PRODUCTS_RESULT */);
1280
+ NativeBridge.off("PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */);
1281
+ NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1282
+ }
1283
+ this.unmountCheckoutElement();
1284
+ this.isInitialized = false;
1285
+ this.nativeAvailable = false;
1286
+ this.products = [];
1287
+ this.stripe = null;
1288
+ this.onPurchaseCompleteCallback = void 0;
1289
+ this.onPurchaseErrorCallback = void 0;
1290
+ }
1291
+ };
1292
+
760
1293
  // src/core/client.ts
1294
+ function determineEnvironmentFromParentUrl() {
1295
+ try {
1296
+ let parentUrl = "";
1297
+ if (window !== window.top) {
1298
+ try {
1299
+ parentUrl = window.parent.location.href;
1300
+ } catch (e) {
1301
+ parentUrl = document.referrer;
1302
+ }
1303
+ } else {
1304
+ parentUrl = window.location.href;
1305
+ }
1306
+ logger.debug("Detected parent URL:", parentUrl);
1307
+ if (parentUrl.includes("dev.hyve.gg")) {
1308
+ logger.info("Environment detected: dev (from parent URL)");
1309
+ return true;
1310
+ }
1311
+ if (parentUrl.includes("hyve.gg")) {
1312
+ logger.info("Environment detected: prod (from parent URL)");
1313
+ return false;
1314
+ }
1315
+ logger.info("Environment unknown, defaulting to dev");
1316
+ return true;
1317
+ } catch (error) {
1318
+ logger.warn("Failed to determine environment from parent URL:", error);
1319
+ return true;
1320
+ }
1321
+ }
761
1322
  var HyveClient = class {
762
1323
  telemetryConfig;
763
1324
  apiBaseUrl;
@@ -766,14 +1327,18 @@ var HyveClient = class {
766
1327
  jwtToken = null;
767
1328
  gameId = null;
768
1329
  adsService;
1330
+ billingService;
1331
+ billingConfig;
1332
+ // Store callbacks to preserve them when recreating BillingService
1333
+ billingCallbacks = {};
769
1334
  /**
770
1335
  * Creates a new HyveClient instance
771
1336
  * @param config Optional configuration including telemetry and ads
772
1337
  */
773
1338
  constructor(config) {
1339
+ const isDev = config?.isDev !== void 0 ? config.isDev : determineEnvironmentFromParentUrl();
774
1340
  this.telemetryConfig = {
775
- isDev: true,
776
- // Default to dev environment
1341
+ isDev,
777
1342
  ...config
778
1343
  };
779
1344
  if (this.telemetryConfig.apiBaseUrl) {
@@ -786,10 +1351,24 @@ var HyveClient = class {
786
1351
  if (config?.ads) {
787
1352
  this.adsService.configure(config.ads);
788
1353
  }
789
- logger.info("Client initialized with sessionId:", this.sessionId);
1354
+ this.billingConfig = config?.billing || {};
1355
+ this.billingService = new BillingService(this.billingConfig);
1356
+ const envSource = config?.isDev !== void 0 ? "explicit config" : "auto-detected from parent URL";
1357
+ logger.info("==========================================");
1358
+ logger.info("HyveClient Initialized");
1359
+ logger.info("==========================================");
1360
+ logger.info("Session ID:", this.sessionId);
1361
+ logger.info("Environment:", this.telemetryConfig.isDev ? "DEVELOPMENT" : "PRODUCTION", `(${envSource})`);
790
1362
  logger.info("API Base URL:", this.apiBaseUrl);
791
- logger.info("Environment:", this.telemetryConfig.isDev ? "dev" : "prod");
792
1363
  logger.info("Ads enabled:", this.adsService.isEnabled());
1364
+ logger.info("Billing configured:", Object.keys(this.billingConfig).length > 0);
1365
+ logger.debug("Config:", {
1366
+ isDev: this.telemetryConfig.isDev,
1367
+ hasCustomApiUrl: !!config?.apiBaseUrl,
1368
+ adsEnabled: config?.ads?.enabled || false,
1369
+ billingConfigured: Object.keys(this.billingConfig).length > 0
1370
+ });
1371
+ logger.info("==========================================");
793
1372
  }
794
1373
  /**
795
1374
  * Authenticates a user from URL parameters
@@ -940,826 +1519,237 @@ var HyveClient = class {
940
1519
  return await response.json();
941
1520
  } catch (error) {
942
1521
  logger.error("API call failed:", error);
943
- throw error;
944
- }
945
- }
946
- /**
947
- * Gets the user's inventory
948
- * @returns Promise resolving to the user's inventory
949
- */
950
- async getInventory() {
951
- return this.callApi("/api/v1/inventory");
952
- }
953
- /**
954
- * Gets a specific inventory item by ID
955
- * @param itemId The inventory item ID
956
- * @returns Promise resolving to the inventory item details
957
- */
958
- async getInventoryItem(itemId) {
959
- return this.callApi(`/api/v1/inventory/${itemId}`);
960
- }
961
- /**
962
- * Updates the telemetry configuration
963
- * @param config New telemetry configuration
964
- */
965
- updateTelemetryConfig(config) {
966
- this.telemetryConfig = {
967
- ...this.telemetryConfig,
968
- ...config
969
- };
970
- if (config.apiBaseUrl !== void 0) {
971
- this.apiBaseUrl = config.apiBaseUrl;
972
- } else if (config.isDev !== void 0) {
973
- this.apiBaseUrl = config.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
974
- }
975
- logger.info("Config updated");
976
- logger.info("API Base URL:", this.apiBaseUrl);
977
- }
978
- /**
979
- * Gets the current user ID
980
- * @returns Current user ID or null if not authenticated
981
- */
982
- getUserId() {
983
- return this.userId;
984
- }
985
- /**
986
- * Gets the current session ID
987
- * @returns Current session ID
988
- */
989
- getSessionId() {
990
- return this.sessionId;
991
- }
992
- /**
993
- * Gets the current JWT token
994
- * @returns Current JWT token or null if not available
995
- */
996
- getJwtToken() {
997
- return this.jwtToken;
998
- }
999
- /**
1000
- * Gets the current game ID
1001
- * @returns Current game ID or null if not available
1002
- */
1003
- getGameId() {
1004
- return this.gameId;
1005
- }
1006
- /**
1007
- * Gets the API base URL
1008
- * @returns API base URL
1009
- */
1010
- getApiBaseUrl() {
1011
- return this.apiBaseUrl;
1012
- }
1013
- /**
1014
- * Checks if user is authenticated
1015
- * @returns Boolean indicating authentication status
1016
- */
1017
- isUserAuthenticated() {
1018
- return this.userId !== null;
1019
- }
1020
- /**
1021
- * Checks if JWT token is available
1022
- * @returns Boolean indicating if JWT token is present
1023
- */
1024
- hasJwtToken() {
1025
- return this.jwtToken !== null;
1026
- }
1027
- /**
1028
- * Logs out the current user
1029
- */
1030
- logout() {
1031
- this.userId = null;
1032
- this.jwtToken = null;
1033
- this.gameId = null;
1034
- logger.info("User logged out");
1035
- }
1036
- /**
1037
- * Resets the client state
1038
- */
1039
- reset() {
1040
- this.logout();
1041
- this.sessionId = generateUUID();
1042
- logger.info("Client reset with new sessionId:", this.sessionId);
1043
- }
1044
- /**
1045
- * Configure ads service
1046
- * @param config Ads configuration
1047
- */
1048
- configureAds(config) {
1049
- this.adsService.configure(config);
1050
- logger.info("Ads configuration updated");
1051
- }
1052
- /**
1053
- * Show an ad
1054
- * @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
1055
- * @returns Promise resolving to ad result
1056
- */
1057
- async showAd(type) {
1058
- return this.adsService.show(type);
1059
- }
1060
- /**
1061
- * Check if ads are enabled
1062
- * @returns Boolean indicating if ads are enabled
1063
- */
1064
- areAdsEnabled() {
1065
- return this.adsService.isEnabled();
1066
- }
1067
- /**
1068
- * Check if ads are ready to show
1069
- * @returns Boolean indicating if ads are ready
1070
- */
1071
- areAdsReady() {
1072
- return this.adsService.isReady();
1073
- }
1074
- };
1075
-
1076
- // src/services/billing.ts
1077
- var BillingPlatform = /* @__PURE__ */ ((BillingPlatform2) => {
1078
- BillingPlatform2["WEB"] = "web";
1079
- BillingPlatform2["NATIVE"] = "native";
1080
- BillingPlatform2["UNKNOWN"] = "unknown";
1081
- return BillingPlatform2;
1082
- })(BillingPlatform || {});
1083
- var BillingService = class {
1084
- config;
1085
- platform = "unknown" /* UNKNOWN */;
1086
- isInitialized = false;
1087
- nativeAvailable = false;
1088
- products = [];
1089
- // Stripe instance for web payments
1090
- stripe = null;
1091
- checkoutElement = null;
1092
- // Callbacks for purchase events
1093
- onPurchaseCompleteCallback;
1094
- onPurchaseErrorCallback;
1095
- // Callback for logging (so external UI can capture logs)
1096
- onLogCallback;
1097
- constructor(config) {
1098
- this.config = config;
1099
- this.detectPlatform();
1100
- }
1101
- /**
1102
- * Register a callback to receive all internal logs
1103
- * Useful for displaying logs in a UI
1104
- */
1105
- onLog(callback) {
1106
- this.onLogCallback = callback;
1107
- }
1108
- /**
1109
- * Internal logging method that calls both logger and custom callback
1110
- */
1111
- log(level, message, data) {
1112
- const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
1113
- if (data !== void 0) {
1114
- consoleMethod(message, data);
1115
- } else {
1116
- consoleMethod(message);
1117
- }
1118
- if (this.onLogCallback) {
1119
- this.onLogCallback(level, message, data);
1522
+ throw error;
1120
1523
  }
1121
1524
  }
1122
1525
  /**
1123
- * Detects if running on web or native platform
1526
+ * Gets the user's inventory
1527
+ * @returns Promise resolving to the user's inventory
1124
1528
  */
1125
- detectPlatform() {
1126
- this.log("info", "[BillingService] ========== PLATFORM DETECTION ==========");
1127
- this.log("info", "[BillingService] Checking environment...");
1128
- this.log("info", "[BillingService] Checking NativeBridge.isNativeContext()...");
1129
- const isNative = NativeBridge.isNativeContext();
1130
- this.log("info", `[BillingService] NativeBridge.isNativeContext() = ${isNative}`);
1131
- this.log("info", "[BillingService] Checking typeof window...");
1132
- const hasWindow = typeof window !== "undefined";
1133
- this.log("info", `[BillingService] typeof window !== "undefined" = ${hasWindow}`);
1134
- if (hasWindow) {
1135
- this.log("info", "[BillingService] Checking window.ReactNativeWebView...");
1136
- const hasWebView = !!window.ReactNativeWebView;
1137
- this.log("info", `[BillingService] window.ReactNativeWebView exists = ${hasWebView}`);
1138
- }
1139
- if (isNative) {
1140
- this.platform = "native" /* NATIVE */;
1141
- this.log("info", "[BillingService] \u2713 Platform: NATIVE (React Native WebView)");
1142
- } else if (hasWindow) {
1143
- this.platform = "web" /* WEB */;
1144
- this.log("info", "[BillingService] \u2713 Platform: WEB (Browser)");
1145
- } else {
1146
- this.platform = "unknown" /* UNKNOWN */;
1147
- this.log("warn", "[BillingService] \u26A0 Platform: UNKNOWN");
1148
- }
1149
- this.log("info", "[BillingService] ========================================");
1529
+ async getInventory() {
1530
+ return this.callApi("/api/v1/inventory");
1150
1531
  }
1151
1532
  /**
1152
- * Get the current platform
1533
+ * Gets a specific inventory item by ID
1534
+ * @param itemId The inventory item ID
1535
+ * @returns Promise resolving to the inventory item details
1153
1536
  */
1154
- getPlatform() {
1155
- return this.platform;
1537
+ async getInventoryItem(itemId) {
1538
+ return this.callApi(`/api/v1/inventory/${itemId}`);
1156
1539
  }
1157
1540
  /**
1158
- * Initialize the billing service
1159
- * Must be called before using any other methods
1541
+ * Updates the telemetry configuration
1542
+ * @param config New telemetry configuration
1160
1543
  */
1161
- async initialize() {
1162
- this.log("info", "[BillingService] ========== INITIALIZATION START ==========");
1163
- this.log("info", "[BillingService] Current config:", {
1164
- hasStripeKey: !!this.config.stripePublishableKey,
1165
- stripeKeyLength: this.config.stripePublishableKey?.length,
1166
- gameId: this.config.gameId,
1167
- userId: this.config.userId,
1168
- hasCheckoutUrl: !!this.config.checkoutUrl,
1169
- checkoutUrl: this.config.checkoutUrl
1170
- });
1171
- if (this.isInitialized) {
1172
- this.log("info", "[BillingService] Already initialized");
1173
- return true;
1174
- }
1175
- this.log("info", `[BillingService] Detected platform: ${this.platform}`);
1176
- try {
1177
- if (this.platform === "native" /* NATIVE */) {
1178
- this.log("info", "[BillingService] Attempting native initialization...");
1179
- const result = await this.initializeNative();
1180
- this.log("info", `[BillingService] Native initialization result: ${result}`);
1181
- return result;
1182
- } else if (this.platform === "web" /* WEB */) {
1183
- this.log("info", "[BillingService] Attempting web initialization...");
1184
- const result = await this.initializeWeb();
1185
- this.log("info", `[BillingService] Web initialization result: ${result}`);
1186
- return result;
1187
- }
1188
- this.log("error", "[BillingService] Cannot initialize: unknown platform");
1189
- this.log("error", "[BillingService] Platform detection may have failed");
1190
- return false;
1191
- } catch (error) {
1192
- this.log("error", "[BillingService] Initialization failed with exception:", error);
1193
- this.log("error", "[BillingService] Error message:", error?.message);
1194
- this.log("error", "[BillingService] Error stack:", error?.stack);
1195
- return false;
1196
- } finally {
1197
- this.log("info", "[BillingService] ========== INITIALIZATION END ==========");
1544
+ updateTelemetryConfig(config) {
1545
+ this.telemetryConfig = {
1546
+ ...this.telemetryConfig,
1547
+ ...config
1548
+ };
1549
+ if (config.apiBaseUrl !== void 0) {
1550
+ this.apiBaseUrl = config.apiBaseUrl;
1551
+ } else if (config.isDev !== void 0) {
1552
+ this.apiBaseUrl = config.isDev ? "https://product-api.dev.hyve.gg" : "https://product-api.prod.hyve.gg";
1198
1553
  }
1554
+ logger.info("Config updated");
1555
+ logger.info("API Base URL:", this.apiBaseUrl);
1199
1556
  }
1200
1557
  /**
1201
- * Initialize native billing
1558
+ * Gets the current user ID
1559
+ * @returns Current user ID or null if not authenticated
1202
1560
  */
1203
- async initializeNative() {
1204
- this.log("info", "[BillingService] --- Native Initialization Start ---");
1205
- return new Promise((resolve) => {
1206
- this.log("info", "[BillingService] Checking NativeBridge...");
1207
- try {
1208
- this.log("info", "[BillingService] Calling NativeBridge.initialize()...");
1209
- NativeBridge.initialize();
1210
- this.log("info", "[BillingService] \u2713 NativeBridge.initialize() called");
1211
- this.log("info", "[BillingService] Setting up PURCHASE_COMPLETE listener...");
1212
- NativeBridge.on(
1213
- "PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */,
1214
- (payload) => {
1215
- this.log("info", "[BillingService] \u2713 Native purchase complete:", payload);
1216
- const result = {
1217
- success: true,
1218
- productId: payload.productId,
1219
- transactionId: payload.transactionId,
1220
- transactionDate: payload.transactionDate
1221
- };
1222
- if (this.onPurchaseCompleteCallback) {
1223
- this.onPurchaseCompleteCallback(result);
1224
- }
1225
- }
1226
- );
1227
- this.log("info", "[BillingService] \u2713 PURCHASE_COMPLETE listener registered");
1228
- this.log("info", "[BillingService] Setting up PURCHASE_ERROR listener...");
1229
- NativeBridge.on(
1230
- "PURCHASE_ERROR" /* PURCHASE_ERROR */,
1231
- (payload) => {
1232
- this.log("error", "[BillingService] \u274C Native purchase error:", payload);
1233
- const result = {
1234
- success: false,
1235
- productId: payload.productId || "unknown",
1236
- error: {
1237
- code: payload.code,
1238
- message: payload.message
1239
- }
1240
- };
1241
- if (this.onPurchaseErrorCallback) {
1242
- this.onPurchaseErrorCallback(result);
1243
- }
1244
- }
1245
- );
1246
- this.log("info", "[BillingService] \u2713 PURCHASE_ERROR listener registered");
1247
- this.log("info", "[BillingService] Setting up IAP_AVAILABILITY_RESULT listener...");
1248
- NativeBridge.on(
1249
- "IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */,
1250
- (payload) => {
1251
- this.log("info", "[BillingService] \u2713 IAP availability result received:", payload.available);
1252
- this.nativeAvailable = payload.available;
1253
- this.isInitialized = true;
1254
- if (payload.available) {
1255
- this.log("info", "[BillingService] \u2713\u2713\u2713 Native billing available and initialized \u2713\u2713\u2713");
1256
- } else {
1257
- this.log("warn", "[BillingService] \u26A0 Native billing initialized but IAP not available");
1258
- }
1259
- resolve(payload.available);
1260
- }
1261
- );
1262
- this.log("info", "[BillingService] \u2713 IAP_AVAILABILITY_RESULT listener registered");
1263
- this.log("info", "[BillingService] Waiting 100ms for event listeners to be ready...");
1264
- setTimeout(() => {
1265
- this.log("info", "[BillingService] Sending checkIAPAvailability message to native...");
1266
- NativeBridge.checkIAPAvailability();
1267
- this.log("info", "[BillingService] \u2713 checkIAPAvailability message sent");
1268
- this.log("info", "[BillingService] Waiting for IAP availability response (5s timeout)...");
1269
- }, 100);
1270
- setTimeout(() => {
1271
- if (!this.isInitialized) {
1272
- this.log("warn", "[BillingService] \u274C Native billing initialization TIMEOUT after 5 seconds");
1273
- this.log("warn", "[BillingService] No response from native app");
1274
- this.log("warn", "[BillingService] Marking as initialized but unavailable");
1275
- this.isInitialized = true;
1276
- resolve(false);
1277
- }
1278
- }, 5e3);
1279
- } catch (error) {
1280
- this.log("error", "[BillingService] \u274C Exception during native initialization:", error);
1281
- this.log("error", "[BillingService] Error message:", error?.message);
1282
- this.log("error", "[BillingService] Error stack:", error?.stack);
1283
- this.isInitialized = true;
1284
- resolve(false);
1285
- } finally {
1286
- this.log("info", "[BillingService] --- Native Initialization End ---");
1287
- }
1288
- });
1561
+ getUserId() {
1562
+ return this.userId;
1289
1563
  }
1290
1564
  /**
1291
- * Initialize web billing (Stripe)
1565
+ * Gets the current session ID
1566
+ * @returns Current session ID
1292
1567
  */
1293
- async initializeWeb() {
1294
- this.log("info", "[BillingService] --- Web Initialization Start ---");
1295
- this.log("info", "[BillingService] Checking for Stripe publishable key...");
1296
- if (!this.config.stripePublishableKey) {
1297
- this.log(
1298
- "error",
1299
- "[BillingService] \u274C FAILED: stripePublishableKey not provided in config"
1300
- );
1301
- this.log("error", "[BillingService] Config keys present:", Object.keys(this.config));
1302
- return false;
1303
- }
1304
- this.log(
1305
- "info",
1306
- `[BillingService] \u2713 Stripe key found (length: ${this.config.stripePublishableKey.length})`
1307
- );
1308
- this.log(
1309
- "info",
1310
- `[BillingService] Key starts with: ${this.config.stripePublishableKey.substring(0, 12)}...`
1311
- );
1312
- try {
1313
- this.log("info", "[BillingService] Checking window object...");
1314
- if (typeof window === "undefined") {
1315
- this.log("error", "[BillingService] \u274C FAILED: window is undefined (not in browser)");
1316
- return false;
1317
- }
1318
- this.log("info", "[BillingService] \u2713 Window object exists");
1319
- this.log("info", "[BillingService] Checking if Stripe.js is already loaded...");
1320
- if (!window.Stripe) {
1321
- this.log("info", "[BillingService] Stripe.js not loaded, loading now...");
1322
- await this.loadStripeScript();
1323
- this.log("info", "[BillingService] Stripe.js load complete");
1324
- } else {
1325
- this.log("info", "[BillingService] \u2713 Stripe.js already loaded");
1326
- }
1327
- this.log("info", "[BillingService] Attempting to initialize Stripe instance...");
1328
- if (window.Stripe) {
1329
- this.log("info", "[BillingService] Creating Stripe instance with publishable key...");
1330
- this.stripe = window.Stripe(this.config.stripePublishableKey);
1331
- this.log("info", "[BillingService] \u2713 Stripe instance created");
1332
- this.log("info", "[BillingService] Stripe object type:", typeof this.stripe);
1333
- this.log("info", "[BillingService] Stripe object keys:", Object.keys(this.stripe || {}));
1334
- this.isInitialized = true;
1335
- this.log("info", "[BillingService] \u2713\u2713\u2713 Web billing initialized successfully \u2713\u2713\u2713");
1336
- return true;
1337
- } else {
1338
- this.log("error", "[BillingService] \u274C FAILED: window.Stripe is undefined after loading");
1339
- this.log("error", "[BillingService] window keys:", Object.keys(window));
1340
- return false;
1341
- }
1342
- } catch (error) {
1343
- this.log("error", "[BillingService] \u274C EXCEPTION during Stripe initialization:");
1344
- this.log("error", "[BillingService] Error type:", error?.constructor?.name);
1345
- this.log("error", "[BillingService] Error message:", error?.message);
1346
- this.log("error", "[BillingService] Error stack:", error?.stack);
1347
- return false;
1348
- } finally {
1349
- this.log("info", "[BillingService] --- Web Initialization End ---");
1350
- }
1568
+ getSessionId() {
1569
+ return this.sessionId;
1351
1570
  }
1352
1571
  /**
1353
- * Load Stripe.js script dynamically
1572
+ * Gets the current JWT token
1573
+ * @returns Current JWT token or null if not available
1354
1574
  */
1355
- loadStripeScript() {
1356
- this.log("info", "[BillingService] --- loadStripeScript() Start ---");
1357
- return new Promise((resolve, reject) => {
1358
- this.log("info", "[BillingService] Checking window object in loadStripeScript...");
1359
- if (typeof window === "undefined") {
1360
- this.log("error", "[BillingService] \u274C FAILED: Window is not defined");
1361
- reject(new Error("Window is not defined"));
1362
- return;
1363
- }
1364
- this.log("info", "[BillingService] \u2713 Window exists");
1365
- this.log("info", "[BillingService] Checking if Stripe is already on window...");
1366
- if (window.Stripe) {
1367
- this.log("info", "[BillingService] \u2713 Stripe already exists, no need to load");
1368
- resolve();
1369
- return;
1370
- }
1371
- this.log("info", "[BillingService] Stripe not found, need to load script");
1372
- this.log("info", "[BillingService] Checking document object...");
1373
- if (!document) {
1374
- this.log("error", "[BillingService] \u274C FAILED: document is undefined");
1375
- reject(new Error("document is undefined"));
1376
- return;
1377
- }
1378
- this.log("info", "[BillingService] \u2713 Document exists");
1379
- this.log("info", "[BillingService] Checking document.head...");
1380
- if (!document.head) {
1381
- this.log("error", "[BillingService] \u274C FAILED: document.head is undefined");
1382
- reject(new Error("document.head is undefined"));
1383
- return;
1384
- }
1385
- this.log("info", "[BillingService] \u2713 document.head exists");
1386
- this.log("info", "[BillingService] Creating script element...");
1387
- const script = document.createElement("script");
1388
- script.src = "https://js.stripe.com/v3/";
1389
- script.async = true;
1390
- this.log("info", "[BillingService] \u2713 Script element created");
1391
- this.log("info", "[BillingService] Script src:", script.src);
1392
- script.onload = () => {
1393
- this.log("info", "[BillingService] \u2713\u2713\u2713 Stripe.js script loaded successfully \u2713\u2713\u2713");
1394
- this.log("info", "[BillingService] Checking if window.Stripe now exists...");
1395
- if (window.Stripe) {
1396
- this.log("info", "[BillingService] \u2713 window.Stripe confirmed to exist");
1397
- } else {
1398
- this.log("warn", "[BillingService] \u26A0 Warning: Script loaded but window.Stripe is still undefined");
1399
- }
1400
- resolve();
1401
- };
1402
- script.onerror = (error) => {
1403
- this.log("error", "[BillingService] \u274C\u274C\u274C Failed to load Stripe.js script");
1404
- this.log("error", "[BillingService] Script error event:", error);
1405
- this.log("error", "[BillingService] Script src attempted:", script.src);
1406
- reject(new Error("Failed to load Stripe.js"));
1407
- };
1408
- this.log("info", "[BillingService] Appending script to document.head...");
1409
- try {
1410
- document.head.appendChild(script);
1411
- this.log("info", "[BillingService] \u2713 Script appended to head, waiting for load...");
1412
- } catch (appendError) {
1413
- this.log("error", "[BillingService] \u274C Exception while appending script:", appendError);
1414
- reject(appendError);
1415
- }
1416
- });
1575
+ getJwtToken() {
1576
+ return this.jwtToken;
1417
1577
  }
1418
1578
  /**
1419
- * Check if billing is available
1579
+ * Gets the current game ID
1580
+ * @returns Current game ID or null if not available
1420
1581
  */
1421
- isAvailable() {
1422
- if (!this.isInitialized) {
1423
- this.log("warn", "[BillingService] Not initialized. Call initialize() first.");
1424
- return false;
1425
- }
1426
- if (this.platform === "native" /* NATIVE */) {
1427
- return this.nativeAvailable;
1428
- } else if (this.platform === "web" /* WEB */) {
1429
- return !!this.config.stripePublishableKey;
1430
- }
1431
- return false;
1582
+ getGameId() {
1583
+ return this.gameId;
1432
1584
  }
1433
1585
  /**
1434
- * Get available products
1586
+ * Gets the API base URL
1587
+ * @returns API base URL
1435
1588
  */
1436
- async getProducts() {
1437
- if (!this.isInitialized) {
1438
- throw new Error("BillingService not initialized. Call initialize() first.");
1439
- }
1440
- if (this.platform === "native" /* NATIVE */) {
1441
- return await this.getProductsNative();
1442
- } else if (this.platform === "web" /* WEB */) {
1443
- return await this.getProductsWeb();
1444
- }
1445
- throw new Error("Cannot get products: unknown platform");
1589
+ getApiBaseUrl() {
1590
+ return this.apiBaseUrl;
1446
1591
  }
1447
1592
  /**
1448
- * Get products from native IAP
1593
+ * Checks if user is authenticated
1594
+ * @returns Boolean indicating authentication status
1449
1595
  */
1450
- async getProductsNative() {
1451
- return new Promise((resolve, reject) => {
1452
- if (!this.config.gameId) {
1453
- const error = new Error("gameId is required for native purchases");
1454
- this.log("error", "[BillingService] getProductsNative failed:", error);
1455
- reject(error);
1456
- return;
1457
- }
1458
- if (!this.config.checkoutUrl) {
1459
- const error = new Error("checkoutUrl is required for native purchases");
1460
- this.log("error", "[BillingService] getProductsNative failed:", error);
1461
- reject(error);
1462
- return;
1463
- }
1464
- this.log(
1465
- "info",
1466
- `[BillingService] Fetching native products for gameId: ${this.config.gameId}`
1467
- );
1468
- const url = `${this.config.checkoutUrl}/get-native-packages?game_id=${this.config.gameId}`;
1469
- this.log("info", `[BillingService] Native products URL: ${url}`);
1470
- fetch(url).then((response) => {
1471
- this.log(
1472
- "info",
1473
- `[BillingService] Native products response status: ${response.status}`
1474
- );
1475
- if (!response.ok) {
1476
- throw new Error(`Failed to fetch native products: ${response.status}`);
1477
- }
1478
- return response.json();
1479
- }).then((data) => {
1480
- this.log("info", `[BillingService] Native products data:`, data);
1481
- if (!data.packages || !Array.isArray(data.packages)) {
1482
- throw new Error("Invalid response format: missing packages array");
1483
- }
1484
- this.products = data.packages.map((pkg) => ({
1485
- productId: pkg.productId,
1486
- title: pkg.package_name,
1487
- description: `${pkg.game_name} - ${pkg.package_name}`,
1488
- price: pkg.price_cents / 100,
1489
- localizedPrice: pkg.price_display,
1490
- currency: "USD"
1491
- }));
1492
- this.log(
1493
- "info",
1494
- `[BillingService] Successfully fetched ${this.products.length} native products`
1495
- );
1496
- resolve(this.products);
1497
- }).catch((error) => {
1498
- this.log("error", "[BillingService] Failed to fetch native products:", error);
1499
- reject(error);
1500
- });
1501
- });
1596
+ isUserAuthenticated() {
1597
+ return this.userId !== null;
1598
+ }
1599
+ /**
1600
+ * Checks if JWT token is available
1601
+ * @returns Boolean indicating if JWT token is present
1602
+ */
1603
+ hasJwtToken() {
1604
+ return this.jwtToken !== null;
1502
1605
  }
1503
1606
  /**
1504
- * Get products from web API (Stripe)
1607
+ * Logs out the current user
1505
1608
  */
1506
- async getProductsWeb() {
1507
- this.log("info", "[BillingService] Fetching web products (Stripe)...");
1508
- if (!this.config.checkoutUrl || !this.config.gameId) {
1509
- const error = new Error("checkoutUrl and gameId are required for web purchases");
1510
- this.log("error", "[BillingService] getProductsWeb failed:", error);
1511
- throw error;
1512
- }
1513
- try {
1514
- const url = `${this.config.checkoutUrl}/get-packages?game_id=${this.config.gameId}`;
1515
- this.log("info", `[BillingService] Web products URL: ${url}`);
1516
- const response = await fetch(url);
1517
- this.log("info", `[BillingService] Web products response status: ${response.status}`);
1518
- if (!response.ok) {
1519
- throw new Error(`Failed to fetch web products: ${response.status}`);
1520
- }
1521
- const data = await response.json();
1522
- this.log("info", `[BillingService] Web products data:`, data);
1523
- if (!data.packages || !Array.isArray(data.packages)) {
1524
- throw new Error("Invalid response format: missing packages array");
1525
- }
1526
- this.products = data.packages.map((pkg) => ({
1527
- productId: pkg.priceId || pkg.productId,
1528
- // Prefer priceId for Stripe
1529
- title: pkg.package_name,
1530
- description: `${pkg.game_name} - ${pkg.package_name}`,
1531
- price: pkg.price_cents / 100,
1532
- localizedPrice: pkg.price_display,
1533
- currency: "USD"
1534
- }));
1535
- this.log(
1536
- "info",
1537
- `[BillingService] Successfully fetched ${this.products.length} web products`
1538
- );
1539
- return this.products;
1540
- } catch (error) {
1541
- this.log("error", "[BillingService] Failed to fetch web products:", error);
1542
- throw error;
1543
- }
1609
+ logout() {
1610
+ this.userId = null;
1611
+ this.jwtToken = null;
1612
+ this.gameId = null;
1613
+ logger.info("User logged out");
1544
1614
  }
1545
1615
  /**
1546
- * Purchase a product
1547
- * @param productId - The product ID (priceId for web/Stripe, productId for native)
1548
- * @param options - Optional purchase options
1549
- * @param options.elementId - For web: DOM element ID to mount Stripe checkout (default: 'stripe-checkout-element')
1616
+ * Resets the client state
1550
1617
  */
1551
- async purchase(productId, options) {
1552
- if (!this.isInitialized) {
1553
- throw new Error("BillingService not initialized. Call initialize() first.");
1554
- }
1555
- if (!this.isAvailable()) {
1556
- throw new Error("Billing is not available on this platform");
1557
- }
1558
- if (this.platform === "native" /* NATIVE */) {
1559
- return await this.purchaseNative(productId);
1560
- } else if (this.platform === "web" /* WEB */) {
1561
- return await this.purchaseWeb(productId, options?.elementId);
1562
- }
1563
- throw new Error("Cannot purchase: unknown platform");
1618
+ reset() {
1619
+ this.logout();
1620
+ this.sessionId = generateUUID();
1621
+ logger.info("Client reset with new sessionId:", this.sessionId);
1564
1622
  }
1565
1623
  /**
1566
- * Purchase via native IAP
1624
+ * Configure ads service
1625
+ * @param config Ads configuration
1567
1626
  */
1568
- async purchaseNative(productId) {
1569
- return new Promise((resolve, reject) => {
1570
- if (!this.config.userId) {
1571
- reject(new Error("userId is required for native purchases"));
1572
- return;
1573
- }
1574
- this.log("info", `[BillingService] Initiating native purchase for: ${productId}`);
1575
- const previousCompleteCallback = this.onPurchaseCompleteCallback;
1576
- const previousErrorCallback = this.onPurchaseErrorCallback;
1577
- const cleanup = () => {
1578
- this.onPurchaseCompleteCallback = previousCompleteCallback;
1579
- this.onPurchaseErrorCallback = previousErrorCallback;
1580
- };
1581
- const completeHandler = (result) => {
1582
- if (result.productId === productId) {
1583
- cleanup();
1584
- resolve(result);
1585
- if (previousCompleteCallback) {
1586
- previousCompleteCallback(result);
1587
- }
1588
- }
1589
- };
1590
- const errorHandler = (result) => {
1591
- if (result.productId === productId) {
1592
- cleanup();
1593
- reject(new Error(result.error?.message || "Purchase failed"));
1594
- if (previousErrorCallback) {
1595
- previousErrorCallback(result);
1596
- }
1597
- }
1598
- };
1599
- this.onPurchaseCompleteCallback = completeHandler;
1600
- this.onPurchaseErrorCallback = errorHandler;
1601
- NativeBridge.purchase(productId, this.config.userId);
1602
- setTimeout(() => {
1603
- cleanup();
1604
- reject(new Error("Purchase timeout"));
1605
- }, 6e4);
1606
- });
1627
+ configureAds(config) {
1628
+ this.adsService.configure(config);
1629
+ logger.info("Ads configuration updated");
1607
1630
  }
1608
1631
  /**
1609
- * Purchase via web (Stripe)
1610
- * @param productId - The priceId (Stripe price ID) for web purchases
1611
- * @param elementId - Optional DOM element ID to mount the checkout form (default: 'stripe-checkout-element')
1632
+ * Show an ad
1633
+ * @param type Type of ad to show ('rewarded', 'interstitial', or 'preroll')
1634
+ * @returns Promise resolving to ad result
1612
1635
  */
1613
- async purchaseWeb(productId, elementId) {
1614
- this.log("info", `[BillingService] Initiating web purchase for priceId: ${productId}`);
1615
- if (!this.config.userId || !this.config.checkoutUrl) {
1616
- throw new Error("userId and checkoutUrl are required for web purchases");
1636
+ async showAd(type) {
1637
+ return this.adsService.show(type);
1638
+ }
1639
+ /**
1640
+ * Check if ads are enabled
1641
+ * @returns Boolean indicating if ads are enabled
1642
+ */
1643
+ areAdsEnabled() {
1644
+ return this.adsService.isEnabled();
1645
+ }
1646
+ /**
1647
+ * Check if ads are ready to show
1648
+ * @returns Boolean indicating if ads are ready
1649
+ */
1650
+ areAdsReady() {
1651
+ return this.adsService.isReady();
1652
+ }
1653
+ /**
1654
+ * Configure billing service
1655
+ * @param config Billing configuration
1656
+ */
1657
+ configureBilling(config) {
1658
+ this.billingConfig = {
1659
+ ...this.billingConfig,
1660
+ ...config
1661
+ };
1662
+ logger.info("Billing configuration updated");
1663
+ }
1664
+ /**
1665
+ * Initialize billing service
1666
+ * Must be called before using billing features
1667
+ * @returns Promise resolving to boolean indicating success
1668
+ */
1669
+ async initializeBilling() {
1670
+ const mergedConfig = {
1671
+ ...this.billingConfig
1672
+ };
1673
+ if (!mergedConfig.userId && this.userId) {
1674
+ mergedConfig.userId = this.userId;
1617
1675
  }
1618
- if (!this.stripe) {
1619
- throw new Error("Stripe not initialized. Call initialize() first.");
1676
+ if (!mergedConfig.gameId && this.gameId) {
1677
+ mergedConfig.gameId = Number(this.gameId);
1620
1678
  }
1621
- try {
1622
- const returnUrl = typeof window !== "undefined" ? `${window.location.href}${window.location.href.includes("?") ? "&" : "?"}payment=complete` : void 0;
1623
- this.log("info", `[BillingService] Creating checkout session with return URL: ${returnUrl}`);
1624
- const requestBody = {
1625
- priceId: productId,
1626
- // API expects priceId for Stripe
1627
- userId: this.config.userId,
1628
- gameId: this.config.gameId,
1629
- embedded: true,
1630
- // Enable embedded checkout
1631
- return_url: returnUrl
1632
- };
1633
- this.log("info", `[BillingService] Checkout session request:`, requestBody);
1634
- const response = await fetch(`${this.config.checkoutUrl}/create-checkout-session`, {
1635
- method: "POST",
1636
- headers: {
1637
- "Content-Type": "application/json"
1638
- },
1639
- body: JSON.stringify(requestBody)
1640
- });
1641
- this.log("info", `[BillingService] Checkout session response status: ${response.status}`);
1642
- if (!response.ok) {
1643
- const errorText = await response.text();
1644
- this.log("error", `[BillingService] Checkout session error: ${errorText}`);
1645
- throw new Error(`Failed to create checkout session: ${response.status} - ${errorText}`);
1646
- }
1647
- const responseData = await response.json();
1648
- this.log("info", `[BillingService] Checkout session created:`, responseData);
1649
- const { client_secret, id } = responseData;
1650
- if (!client_secret) {
1651
- throw new Error("No client_secret returned from checkout session");
1652
- }
1653
- await this.mountCheckoutElement(client_secret, elementId || "stripe-checkout-element");
1654
- return {
1655
- success: true,
1656
- productId,
1657
- transactionId: id
1658
- };
1659
- } catch (error) {
1660
- this.log("error", "[BillingService] Web purchase failed:", error);
1661
- return {
1662
- success: false,
1663
- productId,
1664
- error: {
1665
- code: "WEB_PURCHASE_FAILED",
1666
- message: error.message || "Purchase failed"
1667
- }
1668
- };
1679
+ if (!mergedConfig.checkoutUrl) {
1680
+ mergedConfig.checkoutUrl = this.apiBaseUrl;
1669
1681
  }
1682
+ this.billingService = new BillingService(mergedConfig);
1683
+ if (this.billingCallbacks.onComplete) {
1684
+ this.billingService.onPurchaseComplete(this.billingCallbacks.onComplete);
1685
+ }
1686
+ if (this.billingCallbacks.onError) {
1687
+ this.billingService.onPurchaseError(this.billingCallbacks.onError);
1688
+ }
1689
+ if (this.billingCallbacks.onLog) {
1690
+ this.billingService.onLog(this.billingCallbacks.onLog);
1691
+ }
1692
+ return await this.billingService.initialize();
1670
1693
  }
1671
1694
  /**
1672
- * Mount Stripe embedded checkout element to the DOM
1673
- * @param clientSecret - The client secret from the checkout session
1674
- * @param elementId - The ID of the DOM element to mount to
1695
+ * Get the billing platform
1696
+ * @returns Current billing platform
1675
1697
  */
1676
- async mountCheckoutElement(clientSecret, elementId) {
1677
- this.log("info", `[BillingService] Mounting checkout element to #${elementId}`);
1678
- if (!this.stripe) {
1679
- throw new Error("Stripe not initialized");
1680
- }
1681
- try {
1682
- const container = document.getElementById(elementId);
1683
- if (!container) {
1684
- throw new Error(`Element with id "${elementId}" not found in the DOM`);
1685
- }
1686
- container.innerHTML = "";
1687
- this.checkoutElement = await this.stripe.initEmbeddedCheckout({
1688
- clientSecret
1689
- });
1690
- this.checkoutElement.mount(`#${elementId}`);
1691
- this.log("info", "[BillingService] Checkout element mounted successfully");
1692
- this.setupCheckoutEventListeners();
1693
- } catch (error) {
1694
- this.log("error", "[BillingService] Failed to mount checkout element:", error);
1695
- throw error;
1696
- }
1698
+ getBillingPlatform() {
1699
+ return this.billingService.getPlatform();
1697
1700
  }
1698
1701
  /**
1699
- * Set up event listeners for checkout completion
1702
+ * Check if billing is available
1703
+ * @returns Boolean indicating if billing is available
1700
1704
  */
1701
- setupCheckoutEventListeners() {
1702
- if (typeof window !== "undefined") {
1703
- const urlParams = new URLSearchParams(window.location.search);
1704
- const paymentStatus = urlParams.get("payment");
1705
- if (paymentStatus === "complete") {
1706
- this.log("info", "[BillingService] Payment completed, processing result...");
1707
- const sessionId = urlParams.get("session_id");
1708
- if (this.onPurchaseCompleteCallback) {
1709
- this.onPurchaseCompleteCallback({
1710
- success: true,
1711
- productId: "",
1712
- // Would need to be tracked separately
1713
- transactionId: sessionId || void 0
1714
- });
1715
- }
1716
- urlParams.delete("payment");
1717
- urlParams.delete("session_id");
1718
- const newUrl = `${window.location.pathname}${urlParams.toString() ? "?" + urlParams.toString() : ""}`;
1719
- window.history.replaceState({}, "", newUrl);
1720
- }
1721
- }
1705
+ isBillingAvailable() {
1706
+ return this.billingService.isAvailable();
1722
1707
  }
1723
1708
  /**
1724
- * Unmount and destroy the checkout element
1709
+ * Get available billing products
1710
+ * @returns Promise resolving to array of products
1725
1711
  */
1726
- unmountCheckoutElement() {
1727
- if (this.checkoutElement) {
1728
- this.log("info", "[BillingService] Unmounting checkout element");
1729
- this.checkoutElement.unmount();
1730
- this.checkoutElement = null;
1731
- }
1712
+ async getBillingProducts() {
1713
+ return await this.billingService.getProducts();
1714
+ }
1715
+ /**
1716
+ * Purchase a product
1717
+ * @param productId Product ID to purchase
1718
+ * @param options Optional purchase options (e.g., elementId for web)
1719
+ * @returns Promise resolving to purchase result
1720
+ */
1721
+ async purchaseProduct(productId, options) {
1722
+ return await this.billingService.purchase(productId, options);
1732
1723
  }
1733
1724
  /**
1734
1725
  * Set callback for successful purchases
1726
+ * @param callback Function to call on purchase completion
1735
1727
  */
1736
1728
  onPurchaseComplete(callback) {
1737
- this.onPurchaseCompleteCallback = callback;
1729
+ this.billingCallbacks.onComplete = callback;
1730
+ this.billingService.onPurchaseComplete(callback);
1738
1731
  }
1739
1732
  /**
1740
1733
  * Set callback for failed purchases
1734
+ * @param callback Function to call on purchase error
1741
1735
  */
1742
1736
  onPurchaseError(callback) {
1743
- this.onPurchaseErrorCallback = callback;
1737
+ this.billingCallbacks.onError = callback;
1738
+ this.billingService.onPurchaseError(callback);
1744
1739
  }
1745
1740
  /**
1746
- * Clean up resources
1741
+ * Unmount Stripe checkout element
1747
1742
  */
1748
- dispose() {
1749
- this.log("info", "[BillingService] Disposing...");
1750
- if (this.platform === "native" /* NATIVE */) {
1751
- NativeBridge.off("IAP_AVAILABILITY_RESULT" /* IAP_AVAILABILITY_RESULT */);
1752
- NativeBridge.off("PRODUCTS_RESULT" /* PRODUCTS_RESULT */);
1753
- NativeBridge.off("PURCHASE_COMPLETE" /* PURCHASE_COMPLETE */);
1754
- NativeBridge.off("PURCHASE_ERROR" /* PURCHASE_ERROR */);
1755
- }
1756
- this.unmountCheckoutElement();
1757
- this.isInitialized = false;
1758
- this.nativeAvailable = false;
1759
- this.products = [];
1760
- this.stripe = null;
1761
- this.onPurchaseCompleteCallback = void 0;
1762
- this.onPurchaseErrorCallback = void 0;
1743
+ unmountBillingCheckout() {
1744
+ this.billingService.unmountCheckoutElement();
1745
+ }
1746
+ /**
1747
+ * Register a callback to receive billing logs
1748
+ * @param callback Function to call with log messages
1749
+ */
1750
+ onBillingLog(callback) {
1751
+ this.billingCallbacks.onLog = callback;
1752
+ this.billingService.onLog(callback);
1763
1753
  }
1764
1754
  };
1765
1755
  export {