@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.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
|
|
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
|
-
|
|
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
|
-
*
|
|
1526
|
+
* Gets the user's inventory
|
|
1527
|
+
* @returns Promise resolving to the user's inventory
|
|
1124
1528
|
*/
|
|
1125
|
-
|
|
1126
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
1155
|
-
return this.
|
|
1537
|
+
async getInventoryItem(itemId) {
|
|
1538
|
+
return this.callApi(`/api/v1/inventory/${itemId}`);
|
|
1156
1539
|
}
|
|
1157
1540
|
/**
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1541
|
+
* Updates the telemetry configuration
|
|
1542
|
+
* @param config New telemetry configuration
|
|
1160
1543
|
*/
|
|
1161
|
-
|
|
1162
|
-
this.
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
-
*
|
|
1558
|
+
* Gets the current user ID
|
|
1559
|
+
* @returns Current user ID or null if not authenticated
|
|
1202
1560
|
*/
|
|
1203
|
-
|
|
1204
|
-
this.
|
|
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
|
-
*
|
|
1565
|
+
* Gets the current session ID
|
|
1566
|
+
* @returns Current session ID
|
|
1292
1567
|
*/
|
|
1293
|
-
|
|
1294
|
-
this.
|
|
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
|
-
*
|
|
1572
|
+
* Gets the current JWT token
|
|
1573
|
+
* @returns Current JWT token or null if not available
|
|
1354
1574
|
*/
|
|
1355
|
-
|
|
1356
|
-
this.
|
|
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
|
-
*
|
|
1579
|
+
* Gets the current game ID
|
|
1580
|
+
* @returns Current game ID or null if not available
|
|
1420
1581
|
*/
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
-
*
|
|
1586
|
+
* Gets the API base URL
|
|
1587
|
+
* @returns API base URL
|
|
1435
1588
|
*/
|
|
1436
|
-
|
|
1437
|
-
|
|
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
|
-
*
|
|
1593
|
+
* Checks if user is authenticated
|
|
1594
|
+
* @returns Boolean indicating authentication status
|
|
1449
1595
|
*/
|
|
1450
|
-
|
|
1451
|
-
return
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
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
|
-
*
|
|
1607
|
+
* Logs out the current user
|
|
1505
1608
|
*/
|
|
1506
|
-
|
|
1507
|
-
this.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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
|
-
*
|
|
1624
|
+
* Configure ads service
|
|
1625
|
+
* @param config Ads configuration
|
|
1567
1626
|
*/
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
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
|
-
*
|
|
1610
|
-
* @param
|
|
1611
|
-
* @
|
|
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
|
|
1614
|
-
this.
|
|
1615
|
-
|
|
1616
|
-
|
|
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.
|
|
1619
|
-
|
|
1676
|
+
if (!mergedConfig.gameId && this.gameId) {
|
|
1677
|
+
mergedConfig.gameId = Number(this.gameId);
|
|
1620
1678
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
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
|
-
*
|
|
1673
|
-
* @
|
|
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
|
-
|
|
1677
|
-
this.
|
|
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
|
-
*
|
|
1702
|
+
* Check if billing is available
|
|
1703
|
+
* @returns Boolean indicating if billing is available
|
|
1700
1704
|
*/
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
-
*
|
|
1709
|
+
* Get available billing products
|
|
1710
|
+
* @returns Promise resolving to array of products
|
|
1725
1711
|
*/
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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.
|
|
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.
|
|
1737
|
+
this.billingCallbacks.onError = callback;
|
|
1738
|
+
this.billingService.onPurchaseError(callback);
|
|
1744
1739
|
}
|
|
1745
1740
|
/**
|
|
1746
|
-
*
|
|
1741
|
+
* Unmount Stripe checkout element
|
|
1747
1742
|
*/
|
|
1748
|
-
|
|
1749
|
-
this.
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
this.
|
|
1757
|
-
this.
|
|
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 {
|