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