diff --git a/ExtraPluginCfgs/Android/flexion/FlexionSDKAdapter.java b/ExtraPluginCfgs/Android/flexion/FlexionSDKAdapter.java index 690149b..374ab57 100644 --- a/ExtraPluginCfgs/Android/flexion/FlexionSDKAdapter.java +++ b/ExtraPluginCfgs/Android/flexion/FlexionSDKAdapter.java @@ -25,12 +25,14 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; public class FlexionSDKAdapter implements SDKManager.IChannelSDKAdapter, @@ -39,13 +41,23 @@ public class FlexionSDKAdapter implements SDKManager.IPendingPurchaseAdapter, SDKManager.IBillingLifecycleAdapter { private static final String TAG = "FlexionSDKAdapter"; + private static final int FLX_INIT_MAX_ATTEMPTS = 3; + private static final int QUERY_PURCHASES_SUCCESS_CODE = 5000; private static BillingService billingService; private static boolean billingConnected = false; private static boolean billingConnecting = false; + private static boolean tuyooSdkInitialized = false; + private static boolean flexionInitialized = false; + private static boolean flexionScreensStarted = false; + private static boolean flexionScreensShown = false; private static Activity billingActivity; private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static final List pendingPurchases = new ArrayList<>(); private static final List pendingBillingOperations = new ArrayList<>(); + private static final Object pendingQueryLock = new Object(); + private static boolean pendingPurchaseQueryInProgress = false; + private static boolean pendingPurchaseCacheReady = false; + private static boolean pendingPurchaseUnityCallbackWaiting = false; private static final ExecutorService billingExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "FlexionBillingThread")); private interface BillingReadyOperation { @@ -65,12 +77,12 @@ public class FlexionSDKAdapter implements } private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> { - Log.w(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode() - + " hasPurchase=" + (purchase != null)); + // Log.w(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode() + // + " hasPurchase=" + (purchase != null)); if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_SUCCESS_CODE) { if (purchase != null) handlePurchase(purchase); } else if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_USER_CANCELLED_CODE) { - Log.i(TAG, "[PAY-FLOW] user cancelled"); + // Log.i(TAG, "[PAY-FLOW] user cancelled"); sendPayCallback(1, "user_cancelled", null, null, null, null); } else { Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode()); @@ -80,67 +92,160 @@ public class FlexionSDKAdapter implements @Override public void init(Activity activity) { + Log.i(TAG, "[FLX-INIT] adapter init flexionInitialized=" + flexionInitialized + + " flexionScreensStarted=" + flexionScreensStarted + + " flexionScreensShown=" + flexionScreensShown); activity.runOnUiThread(() -> { SDKAPI.getIns().lightModeEnable(); - boolean sdkInitOk = false; - try { - SDKAPI.getIns().init(new InitParam.Builder() - .withActivity(activity) - .withAppId(ConfigManager.SDK_APPID) - .withClientId(ConfigManager.SDK_CLIENTID) - .withServerUrl(ConfigManager.SDK_LOGIN_SERVER_URL) - .build()); - SDKAPI.getIns().onActivityStart(activity); - SDKAPI.getIns().onActivityResume(activity); - sdkInitOk = true; - } catch (Exception e) { - Log.e(TAG, "TuYoo SDK init failed, skip FLX init: " + e); - TYUnityActivity.setSdkInited(); - SDKManager.notifyInitResult(false, "tuyoo_init_failed: " + e.getMessage()); - } - - if (sdkInitOk) { - FLX.init(activity, (initCode, initMsg) -> { - Log.i(TAG, "FLX.init result=" + initCode); - if (initCode == 0) { - FLX.showFlexionScreens(activity, (screenCode, screenMsg) -> { - Log.i(TAG, "FLX.showFlexionScreens result=" + screenCode); - initBillingService(activity); - TYUnityActivity.setSdkInited(); - SDKManager.notifyInitResult(true, "ok"); - }); - } else { - Log.e(TAG, "FLX.init failed: " + initCode); - TYUnityActivity.setSdkInited(); - SDKManager.notifyInitResult(false, "flx_init_failed: " + initCode); - } - }); + if (ensureTuYooSdkInitialized(activity)) { + if (flexionInitialized) { + notifyFlexionInitResult(true, "ok"); + showFlexionScreensThenInitBilling(activity); + } else { + initFlexionWithRetry(activity, 1); + } } }); } + private static boolean ensureTuYooSdkInitialized(Activity activity) { + if (tuyooSdkInitialized) { + Log.i(TAG, "[FLX-INIT] TuYoo SDK already initialized, skip SDKAPI.init"); + return true; + } + + try { + Log.i(TAG, "[FLX-INIT] TuYoo SDK init start"); + SDKAPI.getIns().init(new InitParam.Builder() + .withActivity(activity) + .withAppId(ConfigManager.SDK_APPID) + .withClientId(ConfigManager.SDK_CLIENTID) + .withServerUrl(ConfigManager.SDK_LOGIN_SERVER_URL) + .build()); + SDKAPI.getIns().onActivityStart(activity); + SDKAPI.getIns().onActivityResume(activity); + tuyooSdkInitialized = true; + Log.i(TAG, "[FLX-INIT] TuYoo SDK init success"); + return true; + } catch (Exception e) { + Log.e(TAG, "TuYoo SDK init failed, skip FLX init: " + e); + TYUnityActivity.setSdkInited(); + SDKManager.notifyInitResult(false, "tuyoo_init_failed: " + e.getMessage()); + return false; + } + } + + private static void initFlexionWithRetry(Activity activity, int attempt) { + if (!isActivityAlive(activity)) { + Log.w(TAG, "FLX.init skipped: activity not alive"); + SDKManager.notifyInitResult(false, "flx_init_activity_not_alive"); + return; + } + + Log.i(TAG, "[FLX-INIT] FLX.init start attempt=" + attempt + "/" + FLX_INIT_MAX_ATTEMPTS); + try { + FLX.init(activity, (initCode, initMsg) -> { + Log.i(TAG, "[FLX-INIT] FLX.init result=" + initCode + + " msg=" + initMsg + + " attempt=" + attempt + "/" + FLX_INIT_MAX_ATTEMPTS); + if (initCode == 0) { + flexionInitialized = true; + notifyFlexionInitResult(true, "ok"); + showFlexionScreensThenInitBilling(activity); + return; + } + + onFlexionInitFailed(activity, attempt, "code=" + initCode + " msg=" + initMsg); + }); + } catch (Exception e) { + Log.e(TAG, "FLX.init exception attempt=" + attempt + "/" + FLX_INIT_MAX_ATTEMPTS + + " error=" + e.getMessage()); + onFlexionInitFailed(activity, attempt, "exception=" + e.getMessage()); + } + } + + private static void onFlexionInitFailed(Activity activity, int attempt, String detail) { + if (attempt < FLX_INIT_MAX_ATTEMPTS) { + long delayMs = attempt * 1000L; + Log.w(TAG, "FLX.init failed, retry after " + delayMs + "ms" + + " attempt=" + attempt + "/" + FLX_INIT_MAX_ATTEMPTS + + " detail=" + detail); + mainHandler.postDelayed(() -> initFlexionWithRetry(activity, attempt + 1), delayMs); + return; + } + + Log.e(TAG, "FLX.init failed after retry: " + detail); + TYUnityActivity.setSdkInited(); + SDKManager.notifyInitResult(false, "flx_init_failed_after_retry: " + detail); + } + + private static void showFlexionScreensThenInitBilling(Activity activity) { + if (flexionScreensShown) { + Log.i(TAG, "[FLX-INIT] showFlexionScreens skipped, already shown"); + initBillingServiceAfterScreens(activity); + return; + } + + if (flexionScreensStarted) { + Log.w(TAG, "[FLX-INIT] showFlexionScreens already started without result, skip duplicate show"); + return; + } + + Log.i(TAG, "[FLX-INIT] showFlexionScreens start"); + flexionScreensStarted = true; + FLX.showFlexionScreens(activity, (screenCode, screenMsg) -> { + Log.i(TAG, "[FLX-INIT] showFlexionScreens result=" + screenCode + + " msg=" + screenMsg); + flexionScreensShown = true; + initBillingServiceAfterScreens(activity); + }); + } + + private static void initBillingServiceAfterScreens(Activity activity) { + Log.i(TAG, "[FLX-INIT] initBillingServiceAfterScreens start"); + initBillingService(activity, + () -> { + Log.i(TAG, "[FLX-INIT] initBillingServiceAfterScreens success"); + }, + () -> { + Log.w(TAG, "[FLX-INIT] initBillingServiceAfterScreens failed"); + }); + } + + private static void notifyFlexionInitResult(boolean success, String msg) { + TYUnityActivity.setSdkInited(); + SDKManager.notifyInitResult(success, msg); + } + + private static boolean isActivityAlive(Activity activity) { + return activity != null && !activity.isFinishing() && !activity.isDestroyed(); + } + @Override public void pay(Activity activity, String productId, String productPrice, String productName, String productCount, String prodorderId, String appInfo) { - Log.i(TAG, "[PAY-FLOW] pay called: productId=" + productId + " billingConnected=" + billingConnected); + // Log.i(TAG, "[PAY-FLOW] pay called: productId=" + productId + " billingConnected=" + billingConnected); String developerPayload = ""; try { byte[] decoded = Base64.getDecoder().decode(appInfo); - JSONObject json = new JSONObject(new String(decoded, "UTF-8")); + JSONObject json = new JSONObject(new String(decoded, StandardCharsets.UTF_8)); + Jsonobject json = new JSONObject(); JSONObject payload = new JSONObject(); String customData = json.optString("customData", ""); payload.put("orderId", prodorderId); payload.put("userId", json.optString("userId", "")); payload.put("pfid", json.optString("pfid", "")); - if (!customData.isEmpty()) { - payload.put("customDataB64", Base64.getEncoder().encodeToString(customData.getBytes("UTF-8"))); - } + payload.put("customData", json.optString("customData", "")); + + // if (!customData.isEmpty()) { + // payload.put("customData", parseCustomDataValue(customData)); + // } payload.put("prodPrice", productPrice); payload.put("prodCount", productCount); - developerPayload = payload.toString(); - Log.i(TAG, "[PAY-CUSTOMDATA] customData length=" + customData.length() - + " developerPayload length=" + developerPayload.length()); + developerPayload = Base64.getEncoder().encodeToString(payload.toString().getBytes(StandardCharsets.UTF_8)); + // Log.i(TAG, "[PAY-CUSTOMDATA] customData length=" + customData.length() + // + " developerPayload length=" + developerPayload.length()); } catch (Exception e) { Log.w(TAG, "Failed to extract payload fields from appInfo: " + e.getMessage()); } @@ -153,11 +258,11 @@ public class FlexionSDKAdapter implements sendPayCallback(1, "billing_not_ready", null, null, null, null); return; } - Log.i(TAG, "[PAY-FLOW] launching billing flow for: " + productId + " payload=" + finalPayload); + // Log.i(TAG, "[PAY-FLOW] launching billing flow for: " + productId + " payload=" + finalPayload); BillingResult result = bs.launchBillingFlow(activity, new BillingFlowParams(productId, "inapp", finalPayload)); - Log.i(TAG, "[PAY-FLOW] launchBillingFlow result: code=" + result.getResponseCode() - + " debugMsg=" + result.getDebugMessage()); + // Log.i(TAG, "[PAY-FLOW] launchBillingFlow result: code=" + result.getResponseCode() + // + " debugMsg=" + result.getDebugMessage()); if (result.getResponseCode() != 2000) { Log.e(TAG, "[PAY-FLOW] launchBillingFlow error: code=" + result.getResponseCode() + " debugMsg=" + result.getDebugMessage()); @@ -173,6 +278,21 @@ public class FlexionSDKAdapter implements () -> sendPayCallback(1, "billing_not_ready", null, null, null, null)); } + private static Object parseCustomDataValue(String customData) { + String trimmed = customData == null ? "" : customData.trim(); + try { + if (trimmed.startsWith("{")) { + return new JSONObject(trimmed); + } + if (trimmed.startsWith("[")) { + return new JSONArray(trimmed); + } + } catch (JSONException e) { + Log.w(TAG, "[PAY-CUSTOMDATA] customData json parse failed, keep raw string: " + e.getMessage()); + } + return customData; + } + @Override public void queryProducts() { // Flexion 渠道不通过 thirdExtend 获取商品列表,由 queryProducts(String) 替代 @@ -180,19 +300,29 @@ public class FlexionSDKAdapter implements @Override public void queryProducts(String productIds) { - Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected); + // Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected); List ids = Arrays.asList(productIds.split(",")); - Log.i(TAG, "queryProductDetails with " + ids.size() + " items from config"); + // Log.i(TAG, "queryProductDetails with " + ids.size() + " items from config"); ensureBillingReady(null, "queryProducts", bs -> queryProductDetails(bs, ids), () -> sendExtensionError("billing_not_ready")); } private static void initBillingService(Activity activity) { - Log.i(TAG, "[PAY-FLOW] initBillingService"); + initBillingService(activity, null, null); + } + + private static void initBillingService(Activity activity, Runnable onReady, Runnable onFail) { + // Log.i(TAG, "[PAY-FLOW] initBillingService"); ensureBillingReady(activity, "initBillingService", - bs -> queryPendingPurchases(), - () -> Log.w(TAG, "[PAY-FLOW] initBillingService failed: billing not ready")); + bs -> { + queryPendingPurchases(false); + if (onReady != null) onReady.run(); + }, + () -> { + Log.w(TAG, "[PAY-FLOW] initBillingService failed: billing not ready"); + if (onFail != null) onFail.run(); + }); } private static void ensureBillingReady(Activity activity, String operation, @@ -216,16 +346,16 @@ public class FlexionSDKAdapter implements targetActivity.runOnUiThread(() -> { try { if (billingService == null) { - Log.i(TAG, "[PAY-FLOW] create BillingService operation=" + operation); + // Log.i(TAG, "[PAY-FLOW] create BillingService operation=" + operation); billingService = FLX.createBillingService(targetActivity, purchasesListener); } boolean ready = billingService != null && billingService.isReady(); - Log.i(TAG, "[PAY-FLOW] ensureBillingReady operation=" + operation - + " connected=" + billingConnected - + " ready=" + ready - + " connecting=" + billingConnecting - + " forceReconnect=" + forceReconnect); + // Log.i(TAG, "[PAY-FLOW] ensureBillingReady operation=" + operation + // + " connected=" + billingConnected + // + " ready=" + ready + // + " connecting=" + billingConnecting + // + " forceReconnect=" + forceReconnect); if (ready && !forceReconnect) { billingConnected = true; @@ -235,22 +365,22 @@ public class FlexionSDKAdapter implements pendingBillingOperations.add(new PendingBillingOperation(operation, onReady, onFail)); if (billingConnecting) { - Log.i(TAG, "[PAY-FLOW] billing connection already in progress, queued operation=" + operation); + // Log.i(TAG, "[PAY-FLOW] billing connection already in progress, queued operation=" + operation); return; } billingConnecting = true; - Log.i(TAG, "[PAY-FLOW] billing reconnect start operation=" + operation); + // Log.i(TAG, "[PAY-FLOW] billing reconnect start operation=" + operation); billingService.startConnection(targetActivity, new ConnectionStateListener() { @Override public void onBillingSetupFinished(BillingResult result) { int code = result.getResponseCode(); - Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code); + // Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code); billingConnecting = false; billingConnected = code == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE; drainBillingOperations(billingConnected); if (billingConnected) { - queryPendingPurchases(); + queryPendingPurchases(false); } } @@ -282,8 +412,8 @@ public class FlexionSDKAdapter implements private static void drainBillingOperations(boolean success) { List operations = new ArrayList<>(pendingBillingOperations); pendingBillingOperations.clear(); - Log.i(TAG, "[PAY-FLOW] drainBillingOperations success=" + success - + " count=" + operations.size()); + // Log.i(TAG, "[PAY-FLOW] drainBillingOperations success=" + success + // + " count=" + operations.size()); for (PendingBillingOperation operation : operations) { if (success) { if (operation.onReady != null) operation.onReady.run(billingService); @@ -295,149 +425,283 @@ public class FlexionSDKAdapter implements } private static void handlePurchase(Purchase purchase) { - Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState() - + " orderId=" + purchase.getOrderId() - + " token=" + purchase.getToken()); + // Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState() + // + " orderId=" + purchase.getOrderId() + // + " token=" + purchase.getToken()); if (purchase.getPurchaseState() == 0) { - Log.i(TAG, "[PAY-FLOW] purchase success, sending to Unity for verification"); + // Log.i(TAG, "[PAY-FLOW] purchase success, sending to Unity for verification"); sendPayCallback(0, "success", purchase.getOrderId(), purchase.getPurchaseJson(), purchase.getSignature(), purchase.getToken()); } else if (purchase.getPurchaseState() == 2) { - Log.i(TAG, "[PAY-FLOW] purchase state=2 (pending), skip"); + // Log.i(TAG, "[PAY-FLOW] purchase state=2 (pending), skip"); } } - private static void queryPendingPurchases() { + private static void queryPendingPurchases(boolean sendUnityCallback) { + if (sendUnityCallback) { + JSONArray cached = drainCachedPendingPurchases(); + boolean cacheReady = isPendingPurchaseCacheReady(); + if (cached.length() > 0 || cacheReady) { + Log.i(TAG, "[PAY-FLOW] queryPendingPurchases use cached result count=" + cached.length() + + " cacheReady=" + cacheReady); + markPendingPurchaseCacheConsumed(cached); + sendPendingPurchasesCallback(0, null, cached); + return; + } + } + + synchronized (pendingQueryLock) { + if (sendUnityCallback) { + pendingPurchaseUnityCallbackWaiting = true; + } + if (pendingPurchaseQueryInProgress) { + Log.i(TAG, "[PAY-FLOW] queryPendingPurchases wait running query" + + " sendUnityCallback=" + sendUnityCallback + + " cacheReady=" + pendingPurchaseCacheReady); + return; + } + pendingPurchaseQueryInProgress = true; + pendingPurchaseCacheReady = false; + } + Log.i(TAG, "[PAY-FLOW] queryPendingPurchases start: billingService=" + billingService - + " billingConnected=" + billingConnected); + + " billingConnected=" + billingConnected + + " sendUnityCallback=" + sendUnityCallback); ensureBillingReady(null, "queryPendingPurchases", - bs -> bs.queryPurchasesAsync( - new QueryPurchasesParams("inapp"), - (result, purchases) -> { - Log.i(TAG, "[PAY-FLOW] queryPendingPurchases result: code=" + result.getResponseCode() - + " count=" + (purchases == null ? 0 : purchases.size())); - if (purchases != null) for (Purchase p : purchases) cachePendingPurchase(p); - }), - () -> Log.w(TAG, "[PAY-FLOW] queryPendingPurchases skipped: billing not connected")); + bs -> { + billingExecutor.execute(() -> { + try { + bs.queryPurchasesAsync( + new QueryPurchasesParams("inapp"), + (result, purchases) -> { + int responseCode = result == null ? -1 : result.getResponseCode(); + String debugMessage = result == null ? "billing_result_null" : result.getDebugMessage(); + Log.i(TAG, "[PAY-FLOW] queryPendingPurchases result: code=" + responseCode + + " debugMsg=" + debugMessage + + " count=" + (purchases == null ? 0 : purchases.size())); + if (responseCode != QUERY_PURCHASES_SUCCESS_CODE) { + boolean shouldCallback = finishPendingQueryFailed(); + if (shouldCallback) { + sendPendingPurchasesCallback(1, + "query_pending_failed_" + responseCode + ": " + debugMessage, + new JSONArray()); + } + return; + } + + JSONArray response = new JSONArray(); + if (purchases != null) { + for (Purchase p : purchases) { + JSONObject item = pendingPurchaseToJson(p); + if (item != null) response.put(item); + } + } + + boolean shouldCallback = finishPendingQuerySuccess(response); + if (shouldCallback) { + sendPendingPurchasesCallback(0, null, response); + } + }); + } catch (Exception e) { + Log.e(TAG, "[PAY-FLOW] queryPendingPurchases exception: " + e.getMessage()); + boolean shouldCallback = finishPendingQueryFailed(); + if (shouldCallback) { + sendPendingPurchasesCallback(1, "query_pending_exception: " + e.getMessage(), new JSONArray()); + } + } + }); + }, + () -> { + Log.w(TAG, "[PAY-FLOW] queryPendingPurchases skipped: billing not connected"); + boolean shouldCallback = finishPendingQueryFailed(); + if (shouldCallback) sendPendingPurchasesCallback(1, "billing_not_ready", new JSONArray()); + }); } - private static void cachePendingPurchase(Purchase purchase) { - if (purchase == null) { - Log.w(TAG, "[PAY-FLOW] cachePendingPurchase skipped: purchase is null"); - return; + @Override + public void queryPendingPurchases() { + queryPendingPurchases(true); + } + + private static boolean finishPendingQuerySuccess(JSONArray purchases) { + synchronized (pendingQueryLock) { + boolean shouldCallback = pendingPurchaseUnityCallbackWaiting; + if (shouldCallback) { + clearCachedPendingPurchases(); + } else { + replaceCachedPendingPurchases(purchases); + } + pendingPurchaseQueryInProgress = false; + pendingPurchaseCacheReady = !shouldCallback || purchases == null || purchases.length() == 0; + pendingPurchaseUnityCallbackWaiting = false; + return shouldCallback; } - Log.i(TAG, "[PAY-FLOW] cachePendingPurchase state=" + purchase.getPurchaseState() - + " orderId=" + purchase.getOrderId() + " token=" + purchase.getToken()); - if (purchase.getPurchaseState() != 0) return; + } + + private static boolean finishPendingQueryFailed() { + synchronized (pendingQueryLock) { + pendingPurchaseQueryInProgress = false; + pendingPurchaseCacheReady = false; + boolean shouldCallback = pendingPurchaseUnityCallbackWaiting; + pendingPurchaseUnityCallbackWaiting = false; + return shouldCallback; + } + } + + private static boolean isPendingPurchaseCacheReady() { + synchronized (pendingQueryLock) { + return pendingPurchaseCacheReady; + } + } + + private static void markPendingPurchaseCacheConsumed(JSONArray purchases) { + if (purchases == null || purchases.length() == 0) return; + synchronized (pendingQueryLock) { + pendingPurchaseCacheReady = false; + } + } + + private static JSONObject pendingPurchaseToJson(Purchase purchase) { + if (purchase == null || purchase.getPurchaseState() != 0) return null; try { JSONObject result = new JSONObject(); if (purchase.getOrderId() != null) result.put("orderId", purchase.getOrderId()); if (purchase.getPurchaseJson() != null) result.put("purchaseJson", purchase.getPurchaseJson()); if (purchase.getSignature() != null) result.put("signature", purchase.getSignature()); if (purchase.getToken() != null) result.put("purchaseToken", purchase.getToken()); - - synchronized (pendingPurchases) { - String token = purchase.getToken(); - for (String item : pendingPurchases) { - JSONObject cached = new JSONObject(item); - if (token != null && token.equals(cached.optString("purchaseToken", null))) { - Log.i(TAG, "[PAY-FLOW] pending purchase already cached token=" + token); - return; - } - } - pendingPurchases.add(result.toString()); - } - Log.i(TAG, "[PAY-FLOW] cached pending purchase: orderId=" + purchase.getOrderId() - + " cachedCount=" + pendingPurchases.size()); + return result; } catch (JSONException e) { - Log.e(TAG, "[PAY-FLOW] cachePendingPurchase json error: " + e.getMessage()); + Log.e(TAG, "[PAY-FLOW] pendingPurchaseToJson error: " + e.getMessage()); + return null; } } - @Override - public String getPendingPurchases() { + private static JSONArray drainCachedPendingPurchases() { synchronized (pendingPurchases) { - Log.i(TAG, "[PAY-FLOW] getPendingPurchases start cachedCount=" + pendingPurchases.size()); + Log.i(TAG, "[PAY-FLOW] drainCachedPendingPurchases start cachedCount=" + pendingPurchases.size() + + " queryInProgress=" + pendingPurchaseQueryInProgress + + " cacheReady=" + pendingPurchaseCacheReady); JSONArray result = new JSONArray(); for (String item : pendingPurchases) { try { result.put(new JSONObject(item)); } catch (JSONException e) { - Log.e(TAG, "[PAY-FLOW] getPendingPurchases json error: " + e.getMessage()); + Log.e(TAG, "[PAY-FLOW] drainCachedPendingPurchases json error: " + e.getMessage()); } } pendingPurchases.clear(); - Log.i(TAG, "[PAY-FLOW] getPendingPurchases count=" + result.length()); - return result.toString(); + return result; + } + } + + private static void replaceCachedPendingPurchases(JSONArray purchases) { + synchronized (pendingPurchases) { + pendingPurchases.clear(); + if (purchases == null) return; + for (int i = 0; i < purchases.length(); i++) { + JSONObject item = purchases.optJSONObject(i); + if (item != null) pendingPurchases.add(item.toString()); + } + Log.i(TAG, "[PAY-FLOW] replaceCachedPendingPurchases count=" + pendingPurchases.size()); + } + } + + private static void clearCachedPendingPurchases() { + synchronized (pendingPurchases) { + pendingPurchases.clear(); + } + } + + private static void sendPendingPurchasesCallback(int code, String errStr, JSONArray purchases) { + try { + JSONObject result = new JSONObject(); + result.put("code", code); + if (errStr != null) result.put("errStr", errStr); + result.put("respObj", purchases == null ? "[]" : purchases.toString()); + Log.i(TAG, "[PAY-FLOW] PendingPurchasesResult callback: code=" + code + + " count=" + (purchases == null ? 0 : purchases.length())); + UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, + "PendingPurchasesResult", result.toString()); + } catch (JSONException e) { + Log.e(TAG, "[PAY-FLOW] sendPendingPurchasesCallback json error: " + e.getMessage()); } } @Override public void refreshBilling() { - Log.i(TAG, "[PAY-FLOW] refreshBilling requested"); + // Log.i(TAG, "[PAY-FLOW] refreshBilling requested"); billingConnected = false; ensureBillingReady(null, "refreshBilling", - bs -> Log.i(TAG, "[PAY-FLOW] refreshBilling ready"), + bs -> { + // Log.i(TAG, "[PAY-FLOW] refreshBilling ready"); + }, () -> Log.w(TAG, "[PAY-FLOW] refreshBilling failed"), true); } private static void queryProductDetails(BillingService service, List productIds) { - final boolean[] completed = {false}; + final AtomicBoolean completed = new AtomicBoolean(false); mainHandler.postDelayed(() -> { - if (completed[0]) return; - completed[0] = true; + if (!completed.compareAndSet(false, true)) return; Log.w(TAG, "[PAY-FLOW] queryProductDetails timeout count=" + productIds.size()); sendExtensionError("flexion_query_timeout"); }, 30000); - service.queryProductDetailsAsync( - new ProductDetailsParams("inapp", productIds), - (billingResult, productDetails) -> { - if (completed[0]) { - Log.w(TAG, "[PAY-FLOW] queryProductDetails callback ignored after timeout"); - return; - } - completed[0] = true; - try { - JSONObject result = new JSONObject(); - if (billingResult.getResponseCode() == BillingResults.ResultCode.QUERY_PRODUCT_DETAILS_SUCCESS_CODE - && productDetails != null) { - JSONArray arr = new JSONArray(); - for (ProductDetails detail : productDetails) { - JSONObject item = new JSONObject(); - item.put("productId", detail.getId()); - item.put("ourProductId", detail.getId()); - item.put("title", detail.getTitle()); - item.put("description", detail.getDescription()); - item.put("price", detail.getPrice()); - item.put("price_amount_micros", detail.getPriceAmountMicros()); - item.put("price_currency_code", detail.getPriceCurrencyCode()); - item.put("type", detail.getType()); - arr.put(item); + billingExecutor.execute(() -> { + try { + service.queryProductDetailsAsync( + new ProductDetailsParams("inapp", productIds), + (billingResult, productDetails) -> { + if (!completed.compareAndSet(false, true)) { + Log.w(TAG, "[PAY-FLOW] queryProductDetails callback ignored after timeout"); + return; } - result.put("code", 0); - result.put("respObj", arr.toString()); - } else { - result.put("code", 1); - result.put("thirdExtend_errStr", - "flexion_query_failed: " + billingResult.getDebugMessage()); - } - UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, - ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, result.toString()); - } catch (JSONException e) { - UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, - ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, "{\"code\":1}"); - } - }); + try { + JSONObject result = new JSONObject(); + if (billingResult != null + && billingResult.getResponseCode() == BillingResults.ResultCode.QUERY_PRODUCT_DETAILS_SUCCESS_CODE + && productDetails != null) { + JSONArray arr = new JSONArray(); + for (ProductDetails detail : productDetails) { + JSONObject item = new JSONObject(); + item.put("productId", detail.getId()); + item.put("ourProductId", detail.getId()); + item.put("title", detail.getTitle()); + item.put("description", detail.getDescription()); + item.put("price", detail.getPrice()); + item.put("price_amount_micros", detail.getPriceAmountMicros()); + item.put("price_currency_code", detail.getPriceCurrencyCode()); + item.put("type", detail.getType()); + arr.put(item); + } + result.put("code", 0); + result.put("respObj", arr.toString()); + } else { + result.put("code", 1); + result.put("thirdExtend_errStr", + "flexion_query_failed: " + (billingResult == null ? "billing_result_null" : billingResult.getDebugMessage())); + } + UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, + ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, result.toString()); + } catch (JSONException e) { + UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, + ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, "{\"code\":1}"); + } + }); + } catch (Exception e) { + if (!completed.compareAndSet(false, true)) return; + Log.e(TAG, "[PAY-FLOW] queryProductDetails exception: " + e.getMessage()); + sendExtensionError("flexion_query_exception: " + e.getMessage()); + } + }); } private static void sendPayCallback(int code, String errStr, String storeOrderId, String purchaseJson, String signature, String purchaseToken) { - Log.i(TAG, "[PAY-FLOW] sendPayCallback: code=" + code + " errStr=" + errStr - + " storeOrderId=" + storeOrderId - + " hasPurchaseJson=" + (purchaseJson != null) + " hasSignature=" + (signature != null) - + " hasToken=" + (purchaseToken != null)); + // Log.i(TAG, "[PAY-FLOW] sendPayCallback: code=" + code + " errStr=" + errStr + // + " storeOrderId=" + storeOrderId + // + " hasPurchaseJson=" + (purchaseJson != null) + " hasSignature=" + (signature != null) + // + " hasToken=" + (purchaseToken != null)); try { JSONObject result = new JSONObject(); result.put("code", code); @@ -446,7 +710,7 @@ public class FlexionSDKAdapter implements if (purchaseJson != null) result.put("purchaseJson", purchaseJson); if (signature != null) result.put("signature", signature); if (purchaseToken != null) result.put("purchaseToken", purchaseToken); - Log.i(TAG, "[PAY-FLOW] UnitySendMessage callback: " + result.toString()); + // Log.i(TAG, "[PAY-FLOW] UnitySendMessage callback: " + result.toString()); UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, ConfigManager.PAY_CALLBACK_FUNCTION_NAME, result.toString()); } catch (JSONException e) { @@ -465,17 +729,19 @@ public class FlexionSDKAdapter implements Log.w(TAG, "[PAY-FLOW] consumePurchase: token is null"); return; } - Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token); + // Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token); ensureBillingReady(null, "consumePurchase", bs -> bs.consumeAsync( new ConsumeParams(token), - r -> Log.i(TAG, "[PAY-FLOW] consume result: " + - (r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail"))), + r -> { + // Log.i(TAG, "[PAY-FLOW] consume result: " + + // (r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail")); + }), () -> Log.w(TAG, "[PAY-FLOW] consumePurchase skipped: billing not connected")); } public static void cancelPurchase() { - Log.i(TAG, "[PAY-FLOW] cancelPurchase: TODO - dismiss billing dialog"); + // Log.i(TAG, "[PAY-FLOW] cancelPurchase: TODO - dismiss billing dialog"); } private static void sendExtensionError(String errStr) { diff --git a/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java b/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java index 8a35b5d..443b7fc 100644 --- a/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java +++ b/sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java @@ -16,6 +16,8 @@ import com.barton.log.logapi.IGASDK; import com.facebook.share.model.ShareLinkContent; import com.facebook.share.widget.MessageDialog; import com.facebook.share.widget.ShareDialog; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.tasks.Task; import com.google.android.play.core.review.ReviewException; import com.google.android.play.core.review.ReviewInfo; @@ -73,7 +75,7 @@ public class SDKManager { } public interface IPendingPurchaseAdapter { - String getPendingPurchases(); + void queryPendingPurchases(); } public interface IBillingLifecycleAdapter { @@ -91,21 +93,31 @@ public class SDKManager { } public static void ConsumePurchase(String token) { - Log.i(TAG, "ConsumePurchase: token=" + token); + // Log.i(TAG, "ConsumePurchase: token=" + token); if (sdkAdapter instanceof IPaymentAdapter) { ((IPaymentAdapter) sdkAdapter).consume(token); } } - public static String GetPendingPurchases() { + public static void QueryPendingPurchases() { + Log.i(TAG, "QueryPendingPurchases"); if (sdkAdapter instanceof IPendingPurchaseAdapter) { - return ((IPendingPurchaseAdapter) sdkAdapter).getPendingPurchases(); + ((IPendingPurchaseAdapter) sdkAdapter).queryPendingPurchases(); + return; + } + try { + JSONObject result = new JSONObject(); + result.put("code", 0); + result.put("respObj", "[]"); + UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, + "PendingPurchasesResult", result.toString()); + } catch (JSONException e) { + Log.e(TAG, "QueryPendingPurchases fallback json error: " + e.getMessage()); } - return "[]"; } public static void RefreshBillingService() { - Log.i(TAG, "RefreshBillingService"); + // Log.i(TAG, "RefreshBillingService"); if (sdkAdapter instanceof IBillingLifecycleAdapter) { ((IBillingLifecycleAdapter) sdkAdapter).refreshBilling(); } @@ -116,6 +128,24 @@ public class SDKManager { // FlexionSDKAdapter.cancelPurchase(); } + public static boolean IsGooglePlayServicesAvailable() { + try { + Activity activity = UnityPlayer.currentActivity; + if (activity == null) { + Log.w(TAG, "IsGooglePlayServicesAvailable: activity is null"); + return false; + } + + int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(activity); + boolean available = resultCode == ConnectionResult.SUCCESS; + Log.i(TAG, "IsGooglePlayServicesAvailable: available=" + available + " resultCode=" + resultCode); + return available; + } catch (Exception e) { + Log.w(TAG, "IsGooglePlayServicesAvailable exception: " + e.getMessage()); + return false; + } + } + // ------------------------------------------------------------------------- // Init — with channel helper support // ------------------------------------------------------------------------- @@ -124,9 +154,9 @@ public class SDKManager { channel = ChannelEntry.CHANNEL; sdkAdapter = ChannelEntry.createSDKAdapter(); - Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel - + " fromCSharp=" + ch - + " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getSimpleName() : "default")); + // Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel + // + " fromCSharp=" + ch + // + " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getSimpleName() : "default")); // Sync channel to C# ChannelConfig UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, @@ -515,13 +545,13 @@ public class SDKManager { public static void SetGaUserInfo(String userId) { Log.e(TAG, "SetGaUserInfo : " + userId); - SDKLog.i("SetGaUserInfo : " + userId); + // SDKLog.i("SetGaUserInfo : " + userId); GetGameGa().setUserId(userId); } public static void SetGaCommonInfo(String SetGaCommonInfo) { Log.e(TAG, "SetGaCommonInfo : " + SetGaCommonInfo); - SDKLog.i("SetGaCommonInfo : " + SetGaCommonInfo); + // SDKLog.i("SetGaCommonInfo : " + SetGaCommonInfo); Gson gson = new Gson(); Map map = gson.fromJson(SetGaCommonInfo, new TypeToken>() {}.getType()); diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs index 1468d5b..16952bf 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade.cs @@ -106,6 +106,9 @@ namespace tysdk private static TYSdkFacade _instance; public static bool IsSdkInited { get; private set; } + public static bool IsInitStarted { get; private set; } + public static bool HasInitResult { get; private set; } + public static string LastInitMessage { get; private set; } public static event System.Action OnInitComplete; public static TYSdkFacade Instance @@ -122,7 +125,10 @@ namespace tysdk } public void Init() { - Debug.Log($"[TYSdkFacade] Init()"); + Debug.Log("[FLX-INIT] TYSdkFacade Init"); + IsInitStarted = true; + HasInitResult = false; + LastInitMessage = string.Empty; #if UNITY_ANDROID UnityBridgeFunc.InitSDK(ChannelConfig.Channel); #endif @@ -131,13 +137,13 @@ namespace tysdk public void SetChannelFromNative(string channel) { - Debug.Log($"[TYSdkFacade] SetChannelFromNative: {channel}"); + // Debug.Log($"[TYSdkFacade] SetChannelFromNative: {channel}"); ChannelConfig.SetChannel(channel); } public void InitResult(string json) { - Debug.Log($"[TYSdkFacade] InitResult: {json}"); + Debug.Log($"[FLX-INIT] TYSdkFacade InitResult: {json}"); var success = false; var msg = string.Empty; @@ -154,6 +160,8 @@ namespace tysdk } IsSdkInited = success; + HasInitResult = true; + LastInitMessage = msg; OnInitComplete?.Invoke(success, msg); } @@ -173,7 +181,7 @@ namespace tysdk public void Logout() { - UnityEngine.Debug.Log("Logout"); + // UnityEngine.Debug.Log("Logout"); if (_accountInfo != null) { UnityBridgeFunc.UnityLogOutByChannel(_accountInfo.channel); @@ -253,7 +261,7 @@ namespace tysdk public async Task LoginBySns() { - Debug.Log("[TYSdkFacade] LoginBySns"); + // Debug.Log("[TYSdkFacade] LoginBySns"); var task = TYSDKCallbackManager.Instance.RegisterCallback(TimeSpan.FromSeconds(30)); @@ -286,7 +294,7 @@ namespace tysdk public void LoginResult(string json) { - Debug.Log("[TYSdkFacade] Login callback:"); + // Debug.Log("[TYSdkFacade] Login callback:"); var result = new LoginInfo(); var data = JObject.Parse(json); var code = (int)data["code"]; @@ -553,7 +561,7 @@ namespace tysdk //对sign进行MD5加密 string signMd5 = CalculateMD5Hash(sign); string url = $"{tycs}?{listStr}&sign={signMd5}&isAbroad=1&lan=en"; - Debug.Log($"[TYSdkFacade:Signout] url \n{url}"); + // Debug.Log($"[TYSdkFacade:Signout] url \n{url}"); Application.OpenURL(url); } diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs index c7c7cdc..c1253ef 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkFacade_Pay.cs @@ -36,7 +36,7 @@ namespace tysdk string extraInfo = purchaseInfo.ToString(Newtonsoft.Json.Formatting.None); //to base64 extraInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(extraInfo)); - Debug.Log("[TYSdk] extraInfo: " + extraInfo); + // Debug.Log("[TYSdk] extraInfo: " + extraInfo); var prodId = prod.ProdID; var prodPrice = usdprice.ToString(); var prodName = prod.title; @@ -68,7 +68,6 @@ namespace tysdk result.orderId = orderId; result.productId = prodId; result.price = prodPrice; - result.Check(orderId, prodId); return result; } catch (Exception e) @@ -109,7 +108,7 @@ namespace tysdk //支付的回调 public void PayResult(string json) { - Debug.Log("[TYSdkFacade] pay callback:" + json); + // Debug.Log("[TYSdkFacade] pay callback:" + json); var jresult = JObject.Parse(json); var result = new PaymentInfo(); result.code = jresult["code"].ToString(); @@ -204,20 +203,71 @@ namespace tysdk //商品列表的回调 public void SKUListResult(string json) { - Debug.Log("[TYSdkFacade] GetSKUList callback"); - var jresult = JObject.Parse(json); - int code = int.Parse(jresult["code"].ToString()); + // Debug.Log("[TYSdkFacade] GetSKUList callback"); var result = new ProductListInfo(); - if (code == 0) + try { - string productStr = jresult["respObj"].ToString(); + var jresult = JObject.Parse(json); + int code = jresult["code"]?.Value() ?? 1; result.code = code; + if (code == 0) + { + string productStr = jresult["respObj"]?.ToString(); #if UNITY_ANDROID && !UNITY_EDITOR - var jsonObjList = Newtonsoft.Json.JsonConvert.DeserializeObject>(productStr); - result.products = jsonObjList; + var jsonObjList = Newtonsoft.Json.JsonConvert.DeserializeObject>(productStr); + result.products = jsonObjList; #elif UNITY_IOS && !UNITY_EDITOR - result.ReadSKUFromJson(productStr); + result.ReadSKUFromJson(productStr); #endif + } + } + catch (Exception e) + { + Debug.LogWarning($"[TYSdkFacade] SKUListResult parse failed: {e.Message}"); + result.code = 1; + result.msg = e.Message; + } + + TYSDKCallbackManager.Instance.TryTriggerCallback(result); + } + + public async Task QueryPendingPurchases() + { +#if UNITY_ANDROID && !UNITY_EDITOR + var task = TYSDKCallbackManager.Instance.RegisterCallback(TimeSpan.FromSeconds(30)); + UnityBridgeFunc.QueryPendingPurchases(); + try + { + return await task; + } + catch (TaskCanceledException e) + { + Debug.LogWarning($"[TYSdkFacade] QueryPendingPurchases timeout: {e.Message}"); + return new PendingPurchaseListInfo { code = 1, msg = "pending_purchase_callback_timeout" }; + } +#else + await Task.Yield(); + return new PendingPurchaseListInfo { code = 0, purchasesJson = "[]" }; +#endif + } + + public void PendingPurchasesResult(string json) + { + Debug.Log($"[TYSdkFacade] PendingPurchasesResult: {json}"); + var result = new PendingPurchaseListInfo(); + try + { + var jresult = JObject.Parse(json); + result.code = jresult["code"]?.Value() ?? 1; + result.msg = jresult["errStr"]?.ToString() ?? string.Empty; + result.purchasesJson = jresult["respObj"]?.ToString() ?? "[]"; + } + catch (Exception e) + { + Debug.LogWarning($"[TYSdkFacade] PendingPurchasesResult parse failed: {e.Message}"); + result.code = 1; + result.msg = e.Message; + result.purchasesJson = "[]"; } TYSDKCallbackManager.Instance.TryTriggerCallback(result); diff --git a/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs b/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs index d731427..c7c7bc5 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/TYSdkModel.cs @@ -158,6 +158,13 @@ namespace tysdk } + public class PendingPurchaseListInfo + { + public int code = 1; + public string msg = string.Empty; + public string purchasesJson = "[]"; + } + #if UNITY_IOS public class SKUDetail { diff --git a/sdk-intergration/Packages/tysdk/Runtime/UnityBridgeFunc.cs b/sdk-intergration/Packages/tysdk/Runtime/UnityBridgeFunc.cs index abbc65b..77d8f4f 100644 --- a/sdk-intergration/Packages/tysdk/Runtime/UnityBridgeFunc.cs +++ b/sdk-intergration/Packages/tysdk/Runtime/UnityBridgeFunc.cs @@ -46,10 +46,12 @@ namespace tysdk public static void ConsumePurchase(string token) { } - public static string GetPendingPurchases() => "[]"; + public static void QueryPendingPurchases() { } public static void RefreshBillingService() { } + public static bool IsGooglePlayServicesAvailable() { return true; } + //打点 public static void SetGaUserInfo(string userId){} public static void SetGaCommonInfo(string SetGaCommonInfo){} @@ -218,11 +220,11 @@ namespace tysdk } } - public static string GetPendingPurchases() + public static void QueryPendingPurchases() { using(var sdkManager = new AndroidJavaClass(SDK_CLASS)) { - return sdkManager.CallStatic("GetPendingPurchases"); + sdkManager.CallStatic("QueryPendingPurchases"); } } @@ -244,6 +246,14 @@ namespace tysdk } } + public static bool IsGooglePlayServicesAvailable() + { + using(var sdkManager = new AndroidJavaClass(SDK_CLASS)) + { + return sdkManager.CallStatic("IsGooglePlayServicesAvailable"); + } + } + //打点 public static void SetGaUserInfo(string userId) { @@ -479,10 +489,12 @@ namespace tysdk public static void ConsumePurchase(string token) { } - public static string GetPendingPurchases() => "[]"; + public static void QueryPendingPurchases() { } public static void RefreshBillingService() { } + public static bool IsGooglePlayServicesAvailable() { return true; } + #endif } } diff --git a/sdk-intergration/ProjectSettings/ProjectSettings.asset b/sdk-intergration/ProjectSettings/ProjectSettings.asset index 9b4a344..f4caddc 100644 --- a/sdk-intergration/ProjectSettings/ProjectSettings.asset +++ b/sdk-intergration/ProjectSettings/ProjectSettings.asset @@ -163,7 +163,7 @@ PlayerSettings: androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: - Android: com.arkgame.ft.flexion + Android: com.arkgame.ft Standalone: com.Unity-Technologies.com.unity.template.urp-blank iPhone: com.arkgame.ft buildNumber: