update:更新接口的修改

This commit is contained in:
2026-06-05 16:57:07 +08:00
parent 4d3eae1c88
commit ea03c8023f
7 changed files with 566 additions and 193 deletions

View File

@@ -25,12 +25,14 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
public class FlexionSDKAdapter implements public class FlexionSDKAdapter implements
SDKManager.IChannelSDKAdapter, SDKManager.IChannelSDKAdapter,
@@ -39,13 +41,23 @@ public class FlexionSDKAdapter implements
SDKManager.IPendingPurchaseAdapter, SDKManager.IPendingPurchaseAdapter,
SDKManager.IBillingLifecycleAdapter { SDKManager.IBillingLifecycleAdapter {
private static final String TAG = "FlexionSDKAdapter"; 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 BillingService billingService;
private static boolean billingConnected = false; private static boolean billingConnected = false;
private static boolean billingConnecting = 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 Activity billingActivity;
private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static final Handler mainHandler = new Handler(Looper.getMainLooper());
private static final List<String> pendingPurchases = new ArrayList<>(); private static final List<String> pendingPurchases = new ArrayList<>();
private static final List<PendingBillingOperation> pendingBillingOperations = new ArrayList<>(); private static final List<PendingBillingOperation> 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 static final ExecutorService billingExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "FlexionBillingThread"));
private interface BillingReadyOperation { private interface BillingReadyOperation {
@@ -65,12 +77,12 @@ public class FlexionSDKAdapter implements
} }
private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> { private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> {
Log.w(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode() // Log.w(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode()
+ " hasPurchase=" + (purchase != null)); // + " hasPurchase=" + (purchase != null));
if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_SUCCESS_CODE) { if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_SUCCESS_CODE) {
if (purchase != null) handlePurchase(purchase); if (purchase != null) handlePurchase(purchase);
} else if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_USER_CANCELLED_CODE) { } 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); sendPayCallback(1, "user_cancelled", null, null, null, null);
} else { } else {
Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode()); Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode());
@@ -80,67 +92,160 @@ public class FlexionSDKAdapter implements
@Override @Override
public void init(Activity activity) { public void init(Activity activity) {
Log.i(TAG, "[FLX-INIT] adapter init flexionInitialized=" + flexionInitialized
+ " flexionScreensStarted=" + flexionScreensStarted
+ " flexionScreensShown=" + flexionScreensShown);
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
SDKAPI.getIns().lightModeEnable(); SDKAPI.getIns().lightModeEnable();
boolean sdkInitOk = false; if (ensureTuYooSdkInitialized(activity)) {
try { if (flexionInitialized) {
SDKAPI.getIns().init(new InitParam.Builder() notifyFlexionInitResult(true, "ok");
.withActivity(activity) showFlexionScreensThenInitBilling(activity);
.withAppId(ConfigManager.SDK_APPID) } else {
.withClientId(ConfigManager.SDK_CLIENTID) initFlexionWithRetry(activity, 1);
.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);
}
});
} }
}); });
} }
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 @Override
public void pay(Activity activity, String productId, String productPrice, public void pay(Activity activity, String productId, String productPrice,
String productName, String productCount, String prodorderId, String productName, String productCount, String prodorderId,
String appInfo) { 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 = ""; String developerPayload = "";
try { try {
byte[] decoded = Base64.getDecoder().decode(appInfo); 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(); JSONObject payload = new JSONObject();
String customData = json.optString("customData", ""); String customData = json.optString("customData", "");
payload.put("orderId", prodorderId); payload.put("orderId", prodorderId);
payload.put("userId", json.optString("userId", "")); payload.put("userId", json.optString("userId", ""));
payload.put("pfid", json.optString("pfid", "")); payload.put("pfid", json.optString("pfid", ""));
if (!customData.isEmpty()) { payload.put("customData", json.optString("customData", ""));
payload.put("customDataB64", Base64.getEncoder().encodeToString(customData.getBytes("UTF-8")));
} // if (!customData.isEmpty()) {
// payload.put("customData", parseCustomDataValue(customData));
// }
payload.put("prodPrice", productPrice); payload.put("prodPrice", productPrice);
payload.put("prodCount", productCount); payload.put("prodCount", productCount);
developerPayload = payload.toString(); developerPayload = Base64.getEncoder().encodeToString(payload.toString().getBytes(StandardCharsets.UTF_8));
Log.i(TAG, "[PAY-CUSTOMDATA] customData length=" + customData.length() // Log.i(TAG, "[PAY-CUSTOMDATA] customData length=" + customData.length()
+ " developerPayload length=" + developerPayload.length()); // + " developerPayload length=" + developerPayload.length());
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Failed to extract payload fields from appInfo: " + e.getMessage()); 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); sendPayCallback(1, "billing_not_ready", null, null, null, null);
return; 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, BillingResult result = bs.launchBillingFlow(activity,
new BillingFlowParams(productId, "inapp", finalPayload)); new BillingFlowParams(productId, "inapp", finalPayload));
Log.i(TAG, "[PAY-FLOW] launchBillingFlow result: code=" + result.getResponseCode() // Log.i(TAG, "[PAY-FLOW] launchBillingFlow result: code=" + result.getResponseCode()
+ " debugMsg=" + result.getDebugMessage()); // + " debugMsg=" + result.getDebugMessage());
if (result.getResponseCode() != 2000) { if (result.getResponseCode() != 2000) {
Log.e(TAG, "[PAY-FLOW] launchBillingFlow error: code=" + result.getResponseCode() Log.e(TAG, "[PAY-FLOW] launchBillingFlow error: code=" + result.getResponseCode()
+ " debugMsg=" + result.getDebugMessage()); + " debugMsg=" + result.getDebugMessage());
@@ -173,6 +278,21 @@ public class FlexionSDKAdapter implements
() -> sendPayCallback(1, "billing_not_ready", null, null, null, null)); () -> 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 @Override
public void queryProducts() { public void queryProducts() {
// Flexion 渠道不通过 thirdExtend 获取商品列表,由 queryProducts(String) 替代 // Flexion 渠道不通过 thirdExtend 获取商品列表,由 queryProducts(String) 替代
@@ -180,19 +300,29 @@ public class FlexionSDKAdapter implements
@Override @Override
public void queryProducts(String productIds) { public void queryProducts(String productIds) {
Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected); // Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected);
List<String> ids = Arrays.asList(productIds.split(",")); List<String> 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", ensureBillingReady(null, "queryProducts",
bs -> queryProductDetails(bs, ids), bs -> queryProductDetails(bs, ids),
() -> sendExtensionError("billing_not_ready")); () -> sendExtensionError("billing_not_ready"));
} }
private static void initBillingService(Activity activity) { 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", ensureBillingReady(activity, "initBillingService",
bs -> queryPendingPurchases(), bs -> {
() -> Log.w(TAG, "[PAY-FLOW] initBillingService failed: billing not ready")); 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, private static void ensureBillingReady(Activity activity, String operation,
@@ -216,16 +346,16 @@ public class FlexionSDKAdapter implements
targetActivity.runOnUiThread(() -> { targetActivity.runOnUiThread(() -> {
try { try {
if (billingService == null) { 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); billingService = FLX.createBillingService(targetActivity, purchasesListener);
} }
boolean ready = billingService != null && billingService.isReady(); boolean ready = billingService != null && billingService.isReady();
Log.i(TAG, "[PAY-FLOW] ensureBillingReady operation=" + operation // Log.i(TAG, "[PAY-FLOW] ensureBillingReady operation=" + operation
+ " connected=" + billingConnected // + " connected=" + billingConnected
+ " ready=" + ready // + " ready=" + ready
+ " connecting=" + billingConnecting // + " connecting=" + billingConnecting
+ " forceReconnect=" + forceReconnect); // + " forceReconnect=" + forceReconnect);
if (ready && !forceReconnect) { if (ready && !forceReconnect) {
billingConnected = true; billingConnected = true;
@@ -235,22 +365,22 @@ public class FlexionSDKAdapter implements
pendingBillingOperations.add(new PendingBillingOperation(operation, onReady, onFail)); pendingBillingOperations.add(new PendingBillingOperation(operation, onReady, onFail));
if (billingConnecting) { 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; return;
} }
billingConnecting = true; 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() { billingService.startConnection(targetActivity, new ConnectionStateListener() {
@Override @Override
public void onBillingSetupFinished(BillingResult result) { public void onBillingSetupFinished(BillingResult result) {
int code = result.getResponseCode(); int code = result.getResponseCode();
Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code); // Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code);
billingConnecting = false; billingConnecting = false;
billingConnected = code == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE; billingConnected = code == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE;
drainBillingOperations(billingConnected); drainBillingOperations(billingConnected);
if (billingConnected) { if (billingConnected) {
queryPendingPurchases(); queryPendingPurchases(false);
} }
} }
@@ -282,8 +412,8 @@ public class FlexionSDKAdapter implements
private static void drainBillingOperations(boolean success) { private static void drainBillingOperations(boolean success) {
List<PendingBillingOperation> operations = new ArrayList<>(pendingBillingOperations); List<PendingBillingOperation> operations = new ArrayList<>(pendingBillingOperations);
pendingBillingOperations.clear(); pendingBillingOperations.clear();
Log.i(TAG, "[PAY-FLOW] drainBillingOperations success=" + success // Log.i(TAG, "[PAY-FLOW] drainBillingOperations success=" + success
+ " count=" + operations.size()); // + " count=" + operations.size());
for (PendingBillingOperation operation : operations) { for (PendingBillingOperation operation : operations) {
if (success) { if (success) {
if (operation.onReady != null) operation.onReady.run(billingService); if (operation.onReady != null) operation.onReady.run(billingService);
@@ -295,149 +425,283 @@ public class FlexionSDKAdapter implements
} }
private static void handlePurchase(Purchase purchase) { private static void handlePurchase(Purchase purchase) {
Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState() // Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState()
+ " orderId=" + purchase.getOrderId() // + " orderId=" + purchase.getOrderId()
+ " token=" + purchase.getToken()); // + " token=" + purchase.getToken());
if (purchase.getPurchaseState() == 0) { 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(), sendPayCallback(0, "success", purchase.getOrderId(),
purchase.getPurchaseJson(), purchase.getSignature(), purchase.getToken()); purchase.getPurchaseJson(), purchase.getSignature(), purchase.getToken());
} else if (purchase.getPurchaseState() == 2) { } 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 Log.i(TAG, "[PAY-FLOW] queryPendingPurchases start: billingService=" + billingService
+ " billingConnected=" + billingConnected); + " billingConnected=" + billingConnected
+ " sendUnityCallback=" + sendUnityCallback);
ensureBillingReady(null, "queryPendingPurchases", ensureBillingReady(null, "queryPendingPurchases",
bs -> bs.queryPurchasesAsync( bs -> {
new QueryPurchasesParams("inapp"), billingExecutor.execute(() -> {
(result, purchases) -> { try {
Log.i(TAG, "[PAY-FLOW] queryPendingPurchases result: code=" + result.getResponseCode() bs.queryPurchasesAsync(
+ " count=" + (purchases == null ? 0 : purchases.size())); new QueryPurchasesParams("inapp"),
if (purchases != null) for (Purchase p : purchases) cachePendingPurchase(p); (result, purchases) -> {
}), int responseCode = result == null ? -1 : result.getResponseCode();
() -> Log.w(TAG, "[PAY-FLOW] queryPendingPurchases skipped: billing not connected")); 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) { @Override
if (purchase == null) { public void queryPendingPurchases() {
Log.w(TAG, "[PAY-FLOW] cachePendingPurchase skipped: purchase is null"); queryPendingPurchases(true);
return; }
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 { try {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
if (purchase.getOrderId() != null) result.put("orderId", purchase.getOrderId()); if (purchase.getOrderId() != null) result.put("orderId", purchase.getOrderId());
if (purchase.getPurchaseJson() != null) result.put("purchaseJson", purchase.getPurchaseJson()); if (purchase.getPurchaseJson() != null) result.put("purchaseJson", purchase.getPurchaseJson());
if (purchase.getSignature() != null) result.put("signature", purchase.getSignature()); if (purchase.getSignature() != null) result.put("signature", purchase.getSignature());
if (purchase.getToken() != null) result.put("purchaseToken", purchase.getToken()); if (purchase.getToken() != null) result.put("purchaseToken", purchase.getToken());
return result;
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());
} catch (JSONException e) { } 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 private static JSONArray drainCachedPendingPurchases() {
public String getPendingPurchases() {
synchronized (pendingPurchases) { 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(); JSONArray result = new JSONArray();
for (String item : pendingPurchases) { for (String item : pendingPurchases) {
try { try {
result.put(new JSONObject(item)); result.put(new JSONObject(item));
} catch (JSONException e) { } 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(); pendingPurchases.clear();
Log.i(TAG, "[PAY-FLOW] getPendingPurchases count=" + result.length()); return result;
return result.toString(); }
}
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 @Override
public void refreshBilling() { public void refreshBilling() {
Log.i(TAG, "[PAY-FLOW] refreshBilling requested"); // Log.i(TAG, "[PAY-FLOW] refreshBilling requested");
billingConnected = false; billingConnected = false;
ensureBillingReady(null, "refreshBilling", 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"), () -> Log.w(TAG, "[PAY-FLOW] refreshBilling failed"),
true); true);
} }
private static void queryProductDetails(BillingService service, List<String> productIds) { private static void queryProductDetails(BillingService service, List<String> productIds) {
final boolean[] completed = {false}; final AtomicBoolean completed = new AtomicBoolean(false);
mainHandler.postDelayed(() -> { mainHandler.postDelayed(() -> {
if (completed[0]) return; if (!completed.compareAndSet(false, true)) return;
completed[0] = true;
Log.w(TAG, "[PAY-FLOW] queryProductDetails timeout count=" + productIds.size()); Log.w(TAG, "[PAY-FLOW] queryProductDetails timeout count=" + productIds.size());
sendExtensionError("flexion_query_timeout"); sendExtensionError("flexion_query_timeout");
}, 30000); }, 30000);
service.queryProductDetailsAsync( billingExecutor.execute(() -> {
new ProductDetailsParams("inapp", productIds), try {
(billingResult, productDetails) -> { service.queryProductDetailsAsync(
if (completed[0]) { new ProductDetailsParams("inapp", productIds),
Log.w(TAG, "[PAY-FLOW] queryProductDetails callback ignored after timeout"); (billingResult, productDetails) -> {
return; if (!completed.compareAndSet(false, true)) {
} Log.w(TAG, "[PAY-FLOW] queryProductDetails callback ignored after timeout");
completed[0] = true; return;
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);
} }
result.put("code", 0); try {
result.put("respObj", arr.toString()); JSONObject result = new JSONObject();
} else { if (billingResult != null
result.put("code", 1); && billingResult.getResponseCode() == BillingResults.ResultCode.QUERY_PRODUCT_DETAILS_SUCCESS_CODE
result.put("thirdExtend_errStr", && productDetails != null) {
"flexion_query_failed: " + billingResult.getDebugMessage()); JSONArray arr = new JSONArray();
} for (ProductDetails detail : productDetails) {
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, JSONObject item = new JSONObject();
ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, result.toString()); item.put("productId", detail.getId());
} catch (JSONException e) { item.put("ourProductId", detail.getId());
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, item.put("title", detail.getTitle());
ConfigManager.PAY_TYPE_CALLBACK_FUNCTION_NAME, "{\"code\":1}"); 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, private static void sendPayCallback(int code, String errStr, String storeOrderId,
String purchaseJson, String signature, String purchaseToken) { String purchaseJson, String signature, String purchaseToken) {
Log.i(TAG, "[PAY-FLOW] sendPayCallback: code=" + code + " errStr=" + errStr // Log.i(TAG, "[PAY-FLOW] sendPayCallback: code=" + code + " errStr=" + errStr
+ " storeOrderId=" + storeOrderId // + " storeOrderId=" + storeOrderId
+ " hasPurchaseJson=" + (purchaseJson != null) + " hasSignature=" + (signature != null) // + " hasPurchaseJson=" + (purchaseJson != null) + " hasSignature=" + (signature != null)
+ " hasToken=" + (purchaseToken != null)); // + " hasToken=" + (purchaseToken != null));
try { try {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("code", code); result.put("code", code);
@@ -446,7 +710,7 @@ public class FlexionSDKAdapter implements
if (purchaseJson != null) result.put("purchaseJson", purchaseJson); if (purchaseJson != null) result.put("purchaseJson", purchaseJson);
if (signature != null) result.put("signature", signature); if (signature != null) result.put("signature", signature);
if (purchaseToken != null) result.put("purchaseToken", purchaseToken); 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, UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
ConfigManager.PAY_CALLBACK_FUNCTION_NAME, result.toString()); ConfigManager.PAY_CALLBACK_FUNCTION_NAME, result.toString());
} catch (JSONException e) { } catch (JSONException e) {
@@ -465,17 +729,19 @@ public class FlexionSDKAdapter implements
Log.w(TAG, "[PAY-FLOW] consumePurchase: token is null"); Log.w(TAG, "[PAY-FLOW] consumePurchase: token is null");
return; return;
} }
Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token); // Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token);
ensureBillingReady(null, "consumePurchase", ensureBillingReady(null, "consumePurchase",
bs -> bs.consumeAsync( bs -> bs.consumeAsync(
new ConsumeParams(token), new ConsumeParams(token),
r -> Log.i(TAG, "[PAY-FLOW] consume result: " + r -> {
(r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail"))), // 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")); () -> Log.w(TAG, "[PAY-FLOW] consumePurchase skipped: billing not connected"));
} }
public static void cancelPurchase() { 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) { private static void sendExtensionError(String errStr) {

View File

@@ -16,6 +16,8 @@ import com.barton.log.logapi.IGASDK;
import com.facebook.share.model.ShareLinkContent; import com.facebook.share.model.ShareLinkContent;
import com.facebook.share.widget.MessageDialog; import com.facebook.share.widget.MessageDialog;
import com.facebook.share.widget.ShareDialog; 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.gms.tasks.Task;
import com.google.android.play.core.review.ReviewException; import com.google.android.play.core.review.ReviewException;
import com.google.android.play.core.review.ReviewInfo; import com.google.android.play.core.review.ReviewInfo;
@@ -73,7 +75,7 @@ public class SDKManager {
} }
public interface IPendingPurchaseAdapter { public interface IPendingPurchaseAdapter {
String getPendingPurchases(); void queryPendingPurchases();
} }
public interface IBillingLifecycleAdapter { public interface IBillingLifecycleAdapter {
@@ -91,21 +93,31 @@ public class SDKManager {
} }
public static void ConsumePurchase(String token) { public static void ConsumePurchase(String token) {
Log.i(TAG, "ConsumePurchase: token=" + token); // Log.i(TAG, "ConsumePurchase: token=" + token);
if (sdkAdapter instanceof IPaymentAdapter) { if (sdkAdapter instanceof IPaymentAdapter) {
((IPaymentAdapter) sdkAdapter).consume(token); ((IPaymentAdapter) sdkAdapter).consume(token);
} }
} }
public static String GetPendingPurchases() { public static void QueryPendingPurchases() {
Log.i(TAG, "QueryPendingPurchases");
if (sdkAdapter instanceof IPendingPurchaseAdapter) { 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() { public static void RefreshBillingService() {
Log.i(TAG, "RefreshBillingService"); // Log.i(TAG, "RefreshBillingService");
if (sdkAdapter instanceof IBillingLifecycleAdapter) { if (sdkAdapter instanceof IBillingLifecycleAdapter) {
((IBillingLifecycleAdapter) sdkAdapter).refreshBilling(); ((IBillingLifecycleAdapter) sdkAdapter).refreshBilling();
} }
@@ -116,6 +128,24 @@ public class SDKManager {
// FlexionSDKAdapter.cancelPurchase(); // 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 // Init — with channel helper support
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -124,9 +154,9 @@ public class SDKManager {
channel = ChannelEntry.CHANNEL; channel = ChannelEntry.CHANNEL;
sdkAdapter = ChannelEntry.createSDKAdapter(); sdkAdapter = ChannelEntry.createSDKAdapter();
Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel // Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel
+ " fromCSharp=" + ch // + " fromCSharp=" + ch
+ " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getSimpleName() : "default")); // + " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getSimpleName() : "default"));
// Sync channel to C# ChannelConfig // Sync channel to C# ChannelConfig
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
@@ -515,13 +545,13 @@ public class SDKManager {
public static void SetGaUserInfo(String userId) { public static void SetGaUserInfo(String userId) {
Log.e(TAG, "SetGaUserInfo : " + userId); Log.e(TAG, "SetGaUserInfo : " + userId);
SDKLog.i("SetGaUserInfo : " + userId); // SDKLog.i("SetGaUserInfo : " + userId);
GetGameGa().setUserId(userId); GetGameGa().setUserId(userId);
} }
public static void SetGaCommonInfo(String SetGaCommonInfo) { public static void SetGaCommonInfo(String SetGaCommonInfo) {
Log.e(TAG, "SetGaCommonInfo : " + SetGaCommonInfo); Log.e(TAG, "SetGaCommonInfo : " + SetGaCommonInfo);
SDKLog.i("SetGaCommonInfo : " + SetGaCommonInfo); // SDKLog.i("SetGaCommonInfo : " + SetGaCommonInfo);
Gson gson = new Gson(); Gson gson = new Gson();
Map<String, Object> map = gson.fromJson(SetGaCommonInfo, Map<String, Object> map = gson.fromJson(SetGaCommonInfo,
new TypeToken<Map<String, Object>>() {}.getType()); new TypeToken<Map<String, Object>>() {}.getType());

View File

@@ -106,6 +106,9 @@ namespace tysdk
private static TYSdkFacade _instance; private static TYSdkFacade _instance;
public static bool IsSdkInited { get; private set; } 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<bool, string> OnInitComplete; public static event System.Action<bool, string> OnInitComplete;
public static TYSdkFacade Instance public static TYSdkFacade Instance
@@ -122,7 +125,10 @@ namespace tysdk
} }
public void Init() public void Init()
{ {
Debug.Log($"[TYSdkFacade] Init()"); Debug.Log("[FLX-INIT] TYSdkFacade Init");
IsInitStarted = true;
HasInitResult = false;
LastInitMessage = string.Empty;
#if UNITY_ANDROID #if UNITY_ANDROID
UnityBridgeFunc.InitSDK(ChannelConfig.Channel); UnityBridgeFunc.InitSDK(ChannelConfig.Channel);
#endif #endif
@@ -131,13 +137,13 @@ namespace tysdk
public void SetChannelFromNative(string channel) public void SetChannelFromNative(string channel)
{ {
Debug.Log($"[TYSdkFacade] SetChannelFromNative: {channel}"); // Debug.Log($"[TYSdkFacade] SetChannelFromNative: {channel}");
ChannelConfig.SetChannel(channel); ChannelConfig.SetChannel(channel);
} }
public void InitResult(string json) public void InitResult(string json)
{ {
Debug.Log($"[TYSdkFacade] InitResult: {json}"); Debug.Log($"[FLX-INIT] TYSdkFacade InitResult: {json}");
var success = false; var success = false;
var msg = string.Empty; var msg = string.Empty;
@@ -154,6 +160,8 @@ namespace tysdk
} }
IsSdkInited = success; IsSdkInited = success;
HasInitResult = true;
LastInitMessage = msg;
OnInitComplete?.Invoke(success, msg); OnInitComplete?.Invoke(success, msg);
} }
@@ -173,7 +181,7 @@ namespace tysdk
public void Logout() public void Logout()
{ {
UnityEngine.Debug.Log("Logout"); // UnityEngine.Debug.Log("Logout");
if (_accountInfo != null) if (_accountInfo != null)
{ {
UnityBridgeFunc.UnityLogOutByChannel(_accountInfo.channel); UnityBridgeFunc.UnityLogOutByChannel(_accountInfo.channel);
@@ -253,7 +261,7 @@ namespace tysdk
public async Task<LoginInfo> LoginBySns() public async Task<LoginInfo> LoginBySns()
{ {
Debug.Log("[TYSdkFacade] LoginBySns"); // Debug.Log("[TYSdkFacade] LoginBySns");
var task = TYSDKCallbackManager.Instance.RegisterCallback<LoginInfo>(TimeSpan.FromSeconds(30)); var task = TYSDKCallbackManager.Instance.RegisterCallback<LoginInfo>(TimeSpan.FromSeconds(30));
@@ -286,7 +294,7 @@ namespace tysdk
public void LoginResult(string json) public void LoginResult(string json)
{ {
Debug.Log("[TYSdkFacade] Login callback:"); // Debug.Log("[TYSdkFacade] Login callback:");
var result = new LoginInfo(); var result = new LoginInfo();
var data = JObject.Parse(json); var data = JObject.Parse(json);
var code = (int)data["code"]; var code = (int)data["code"];
@@ -553,7 +561,7 @@ namespace tysdk
//对sign进行MD5加密 //对sign进行MD5加密
string signMd5 = CalculateMD5Hash(sign); string signMd5 = CalculateMD5Hash(sign);
string url = $"{tycs}?{listStr}&sign={signMd5}&isAbroad=1&lan=en"; 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); Application.OpenURL(url);
} }

View File

@@ -36,7 +36,7 @@ namespace tysdk
string extraInfo = purchaseInfo.ToString(Newtonsoft.Json.Formatting.None); string extraInfo = purchaseInfo.ToString(Newtonsoft.Json.Formatting.None);
//to base64 //to base64
extraInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(extraInfo)); extraInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(extraInfo));
Debug.Log("[TYSdk] extraInfo: " + extraInfo); // Debug.Log("[TYSdk] extraInfo: " + extraInfo);
var prodId = prod.ProdID; var prodId = prod.ProdID;
var prodPrice = usdprice.ToString(); var prodPrice = usdprice.ToString();
var prodName = prod.title; var prodName = prod.title;
@@ -68,7 +68,6 @@ namespace tysdk
result.orderId = orderId; result.orderId = orderId;
result.productId = prodId; result.productId = prodId;
result.price = prodPrice; result.price = prodPrice;
result.Check(orderId, prodId);
return result; return result;
} }
catch (Exception e) catch (Exception e)
@@ -109,7 +108,7 @@ namespace tysdk
//支付的回调 //支付的回调
public void PayResult(string json) public void PayResult(string json)
{ {
Debug.Log("[TYSdkFacade] pay callback:" + json); // Debug.Log("[TYSdkFacade] pay callback:" + json);
var jresult = JObject.Parse(json); var jresult = JObject.Parse(json);
var result = new PaymentInfo(); var result = new PaymentInfo();
result.code = jresult["code"].ToString(); result.code = jresult["code"].ToString();
@@ -204,20 +203,71 @@ namespace tysdk
//商品列表的回调 //商品列表的回调
public void SKUListResult(string json) public void SKUListResult(string json)
{ {
Debug.Log("[TYSdkFacade] GetSKUList callback"); // Debug.Log("[TYSdkFacade] GetSKUList callback");
var jresult = JObject.Parse(json);
int code = int.Parse(jresult["code"].ToString());
var result = new ProductListInfo(); var result = new ProductListInfo();
if (code == 0) try
{ {
string productStr = jresult["respObj"].ToString(); var jresult = JObject.Parse(json);
int code = jresult["code"]?.Value<int>() ?? 1;
result.code = code; result.code = code;
if (code == 0)
{
string productStr = jresult["respObj"]?.ToString();
#if UNITY_ANDROID && !UNITY_EDITOR #if UNITY_ANDROID && !UNITY_EDITOR
var jsonObjList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<SKUDetail>>(productStr); var jsonObjList = Newtonsoft.Json.JsonConvert.DeserializeObject<List<SKUDetail>>(productStr);
result.products = jsonObjList; result.products = jsonObjList;
#elif UNITY_IOS && !UNITY_EDITOR #elif UNITY_IOS && !UNITY_EDITOR
result.ReadSKUFromJson(productStr); result.ReadSKUFromJson(productStr);
#endif #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<PendingPurchaseListInfo> QueryPendingPurchases()
{
#if UNITY_ANDROID && !UNITY_EDITOR
var task = TYSDKCallbackManager.Instance.RegisterCallback<PendingPurchaseListInfo>(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<int>() ?? 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); TYSDKCallbackManager.Instance.TryTriggerCallback(result);

View File

@@ -158,6 +158,13 @@ namespace tysdk
} }
public class PendingPurchaseListInfo
{
public int code = 1;
public string msg = string.Empty;
public string purchasesJson = "[]";
}
#if UNITY_IOS #if UNITY_IOS
public class SKUDetail public class SKUDetail
{ {

View File

@@ -46,10 +46,12 @@ namespace tysdk
public static void ConsumePurchase(string token) { } public static void ConsumePurchase(string token) { }
public static string GetPendingPurchases() => "[]"; public static void QueryPendingPurchases() { }
public static void RefreshBillingService() { } public static void RefreshBillingService() { }
public static bool IsGooglePlayServicesAvailable() { return true; }
//打点 //打点
public static void SetGaUserInfo(string userId){} public static void SetGaUserInfo(string userId){}
public static void SetGaCommonInfo(string SetGaCommonInfo){} 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)) using(var sdkManager = new AndroidJavaClass(SDK_CLASS))
{ {
return sdkManager.CallStatic<string>("GetPendingPurchases"); sdkManager.CallStatic("QueryPendingPurchases");
} }
} }
@@ -244,6 +246,14 @@ namespace tysdk
} }
} }
public static bool IsGooglePlayServicesAvailable()
{
using(var sdkManager = new AndroidJavaClass(SDK_CLASS))
{
return sdkManager.CallStatic<bool>("IsGooglePlayServicesAvailable");
}
}
//打点 //打点
public static void SetGaUserInfo(string userId) public static void SetGaUserInfo(string userId)
{ {
@@ -479,10 +489,12 @@ namespace tysdk
public static void ConsumePurchase(string token) { } public static void ConsumePurchase(string token) { }
public static string GetPendingPurchases() => "[]"; public static void QueryPendingPurchases() { }
public static void RefreshBillingService() { } public static void RefreshBillingService() { }
public static bool IsGooglePlayServicesAvailable() { return true; }
#endif #endif
} }
} }

View File

@@ -163,7 +163,7 @@ PlayerSettings:
androidSupportedAspectRatio: 1 androidSupportedAspectRatio: 1
androidMaxAspectRatio: 2.1 androidMaxAspectRatio: 2.1
applicationIdentifier: applicationIdentifier:
Android: com.arkgame.ft.flexion Android: com.arkgame.ft
Standalone: com.Unity-Technologies.com.unity.template.urp-blank Standalone: com.Unity-Technologies.com.unity.template.urp-blank
iPhone: com.arkgame.ft iPhone: com.arkgame.ft
buildNumber: buildNumber: