update:逻辑更新

This commit is contained in:
2026-05-21 00:37:19 +08:00
parent eecf54c6a4
commit ed6fe668ad
29 changed files with 677 additions and 747 deletions

View File

@@ -1,11 +1,11 @@
package com.unity3d.player;
public class ChannelHelpers {
public class ChannelEntry {
public static final String CHANNEL = "Flexion";
static void register() {
SDKManager.registerHelper(new FlexionSDKHelper());
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return new FlexionSDKService();
}
@@ -14,7 +14,7 @@ public class ChannelHelpers {
public static String normalizeSkuList(String msg) {
return msg;
}
//public SDKExtBuilder;
}

View File

@@ -17,35 +17,41 @@ import com.flexionmobile.ddpx.service.BillingService;
import com.flexionmobile.fdk.FLX;
import com.tuyoo.gamesdk.api.SDKAPI;
import com.tuyoo.gamesdk.api.SDKCallBack;
import com.tuyoo.gamesdk.model.InitParam;
import com.tuyoo.gamesdk.util.SDKLog;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
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;
public class FlexionSDKHelper implements SDKManager.ISDKHelper {
private static final String TAG = "FlexionSDKHelper";
public class FlexionSDKService implements
SDKManager.IChannelSDKAdapter,
SDKManager.IPaymentAdapter,
SDKManager.IProductQueryAdapter,
SDKManager.IPendingPurchaseAdapter {
private static final String TAG = "FlexionSDKService";
private static BillingService billingService;
private static boolean billingConnected = false;
private static final List<String> pendingPurchases = new ArrayList<>();
private static final ExecutorService billingExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "FlexionBillingThread"));
private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> {
Log.i(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode()
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");
sendPayCallback(1, "user_cancelled", null, null, null);
sendPayCallback(1, "user_cancelled", null, null, null, null);
} else {
Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode());
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null, null);
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null, null, null);
}
};
@@ -95,9 +101,9 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
String productName, String productCount, String prodorderId,
String appInfo) {
Log.i(TAG, "[PAY-FLOW] pay called: productId=" + productId + " billingReady=" + billingConnected);
if (billingService == null || !billingConnected) {
if (billingService == null || !billingConnected || !billingService.isReady()) {
Log.e(TAG, "[PAY-FLOW] billing not ready");
sendPayCallback(1, "billing_not_ready", null, null, null);
sendPayCallback(1, "billing_not_ready", null, null, null, null);
return;
}
String developerPayload = "";
@@ -105,97 +111,66 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
byte[] decoded = Base64.getDecoder().decode(appInfo);
JSONObject json = new JSONObject(new String(decoded, "UTF-8"));
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", ""));
payload.put("customData", json.optString("customData", ""));
if (!customData.isEmpty()) {
payload.put("customDataB64", Base64.getEncoder().encodeToString(customData.getBytes("UTF-8")));
}
payload.put("prodPrice", productPrice);
payload.put("prodCount", productCount);
developerPayload = payload.toString();
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());
}
String finalPayload = developerPayload;
activity.runOnUiThread(() -> {
Log.i(TAG, "[PAY-FLOW] launching billing flow for: " + productId + " payload=" + finalPayload);
Log.i(TAG, "[PAY-FLOW] billingService=" + billingService + " billingConnected=" + billingConnected);
billingExecutor.execute(() -> {
try {
BillingResult result = billingService.launchBillingFlow(activity,
BillingService bs = billingService;
if (bs == null || !bs.isReady()) {
Log.e(TAG, "[PAY-FLOW] billingService became null or not ready");
sendPayCallback(1, "billing_not_ready", null, null, null, null);
return;
}
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());
if (result.getResponseCode() == 2001) {
Log.i(TAG, "[PAY-FLOW] service not ready, retry after 3s...");
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
try {
BillingResult retryResult = billingService.launchBillingFlow(activity,
new BillingFlowParams(productId, "inapp", finalPayload));
Log.i(TAG, "[PAY-FLOW] launchBillingFlow retry result: code=" + retryResult.getResponseCode()
+ " debugMsg=" + retryResult.getDebugMessage());
if (retryResult.getResponseCode() != 2000) {
sendPayCallback(1, "billing_flow_error_" + retryResult.getResponseCode()
+ "_" + retryResult.getDebugMessage(), null, null, null);
}
} catch (Exception ex) {
sendPayCallback(1, "launch_billing_failed: " + ex.getMessage(), null, null, null);
}
}, 3000);
return;
}
if (result.getResponseCode() != 2000) {
Log.e(TAG, "[PAY-FLOW] launchBillingFlow error: code=" + result.getResponseCode()
+ " debugMsg=" + result.getDebugMessage());
sendPayCallback(1, "billing_flow_error_" + result.getResponseCode()
+ "_" + result.getDebugMessage(), null, null, null);
+ "_" + result.getDebugMessage(), null, null, null, null);
}
} catch (Exception e) {
Log.e(TAG, "[PAY-FLOW] launchBillingFlow exception: " + e.getClass().getName()
+ " msg=" + e.getMessage());
sendPayCallback(1, "launch_billing_failed: " + e.getMessage(), null, null, null);
sendPayCallback(1, "launch_billing_failed: " + e.getMessage(), null, null, null, null);
}
});
}
@Override
public void queryProducts() {
Log.i(TAG, "queryProducts called, billingConnected=" + billingConnected);
// Flexion 渠道不通过 thirdExtend 获取商品列表 queryProducts(String) 替代
}
@Override
public void queryProducts(String productIds) {
Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected);
if (billingService == null || !billingConnected) {
Log.w(TAG, "queryProducts: billing not ready");
sendExtensionError("billing_not_ready");
return;
}
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("action", "ACTION_GET_SKU_DETAILS_LIST");
} catch (JSONException e) {
throw new RuntimeException(e);
}
SDKAPI.getIns().thirdExtend(jsonObject.toString(), new SDKCallBack.Extend() {
@Override
public void callback(int code, String msg) {
Log.i(TAG, "TuYoo thirdExtend callback: code=" + code + " msg=" + msg);
if (code == SDKCallBack.Extend.EXTEND_CODE_SUCCESS && msg != null) {
try {
JSONArray products = new JSONArray(msg);
List<String> productIds = new java.util.ArrayList<>();
for (int i = 0; i < products.length(); i++) {
JSONObject p = products.getJSONObject(i);
String pid = p.optString("productId", "");
if (!pid.isEmpty()) productIds.add(pid);
}
Log.i(TAG, "queryProductDetails with " + productIds.size() + " items: " + productIds);
queryProductDetails(productIds);
} catch (JSONException e) {
sendExtensionError("parse_error: " + e.getMessage());
}
} else {
sendExtensionError("tuyoo_query_failed: " + msg);
}
}
});
List<String> ids = Arrays.asList(productIds.split(","));
Log.i(TAG, "queryProductDetails with " + ids.size() + " items from config");
queryProductDetails(ids);
}
// ---- Billing Infrastructure ----
private static void initBillingService(Activity activity) {
Log.i(TAG, "[PAY-FLOW] initBillingService");
billingService = FLX.createBillingService(activity, purchasesListener);
@@ -208,6 +183,7 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
queryPendingPurchases();
}
}
@Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "[PAY-FLOW] billing service disconnected");
@@ -218,29 +194,84 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
private static void handlePurchase(Purchase purchase) {
Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState()
+ " orderId=" + purchase.getOrderId()
+ " token=" + purchase.getToken());
if (purchase.getPurchaseState() == 0) {
// 支付成功 回传数据给 Unity 做服务端验签验签通过后由 C# ConsumePurchase
Log.i(TAG, "[PAY-FLOW] purchase success, sending to Unity for verification");
sendPayCallback(0, "success",
sendPayCallback(0, "success", purchase.getOrderId(),
purchase.getPurchaseJson(), purchase.getSignature(), purchase.getToken());
// [旧逻辑] 直接消费跳过验签
// Log.i(TAG, "[PAY-FLOW] auto-consuming pending purchase: " + purchase.getToken());
// consumePurchase(purchase.getToken());
} else if (purchase.getPurchaseState() == 2) {
Log.i(TAG, "[PAY-FLOW] purchase state=2 (pending), skip");
}
}
private static void queryPendingPurchases() {
if (billingService == null || !billingConnected) return;
Log.i(TAG, "[PAY-FLOW] queryPendingPurchases start: billingService=" + billingService
+ " billingConnected=" + billingConnected);
if (billingService == null || !billingConnected) {
Log.w(TAG, "[PAY-FLOW] queryPendingPurchases skipped: billing not connected");
return;
}
billingService.queryPurchasesAsync(
new QueryPurchasesParams("inapp"),
(result, purchases) -> {
if (purchases != null) for (Purchase p : purchases) handlePurchase(p);
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);
});
}
private static void cachePendingPurchase(Purchase purchase) {
if (purchase == null) {
Log.w(TAG, "[PAY-FLOW] cachePendingPurchase skipped: purchase is null");
return;
}
Log.i(TAG, "[PAY-FLOW] cachePendingPurchase state=" + purchase.getPurchaseState()
+ " orderId=" + purchase.getOrderId() + " token=" + purchase.getToken());
if (purchase.getPurchaseState() != 0) return;
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());
} catch (JSONException e) {
Log.e(TAG, "[PAY-FLOW] cachePendingPurchase json error: " + e.getMessage());
}
}
@Override
public String getPendingPurchases() {
synchronized (pendingPurchases) {
Log.i(TAG, "[PAY-FLOW] getPendingPurchases start cachedCount=" + pendingPurchases.size());
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());
}
}
pendingPurchases.clear();
Log.i(TAG, "[PAY-FLOW] getPendingPurchases count=" + result.length());
return result.toString();
}
}
private static void queryProductDetails(List<String> productIds) {
billingService.queryProductDetailsAsync(
new ProductDetailsParams("inapp", productIds),
@@ -278,17 +309,17 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
});
}
// ---- Callbacks to Unity ----
private static void sendPayCallback(int code, String errStr,
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));
try {
JSONObject result = new JSONObject();
result.put("code", code);
result.put("errStr", errStr != null ? errStr : "");
if (storeOrderId != null) result.put("orderId", storeOrderId);
if (purchaseJson != null) result.put("purchaseJson", purchaseJson);
if (signature != null) result.put("signature", signature);
if (purchaseToken != null) result.put("purchaseToken", purchaseToken);
@@ -318,6 +349,10 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
(r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail")));
}
public static void cancelPurchase() {
Log.i(TAG, "[PAY-FLOW] cancelPurchase: TODO - dismiss billing dialog");
}
private static void sendExtensionError(String errStr) {
try {
JSONObject result = new JSONObject();

View File

@@ -16,13 +16,6 @@ android {
namespace "com.unity3d.player"
}
// Flexion 渠道专用:排除不需要的 AppsFlyer 模块
// - purchase-connector: 基于 Google Play BillingFlexion 不走 GP 计费,没用还增大包
// 即使 Unity Android Resolver 下次又把 implementation 行写回来,这里的 exclude 也兜底排除
configurations.all {
exclude group: 'com.appsflyer', module: 'purchase-connector'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
@@ -82,9 +75,7 @@ dependencies {
implementation 'com.applovin.mediation:vungle-adapter:7.4.2.2' // Assets/MaxSdk/Mediation/Vungle/Editor/Dependencies.xml:4
implementation 'com.applovin:applovin-sdk:13.5.1' // Assets/MaxSdk/AppLovin/Editor/Dependencies.xml:4
implementation 'com.appsflyer:af-android-sdk:6.17.3' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:5
// Flexion: purchase-connector removed — Flexion does not use Google Play Billing.
// Also covered by top-level `configurations.all { exclude ... }` as a safety net.
// implementation 'com.appsflyer:purchase-connector:2.2.0' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.appsflyer:purchase-connector:2.2.0' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.appsflyer:unity-wrapper:6.17.7' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:6
implementation 'com.google.android.ump:user-messaging-platform:2.1.0' // Assets/MaxSdk/AppLovin/Editor/Dependencies.xml:5
// Android Resolver Dependencies End

View File

@@ -1,10 +1,10 @@
package com.unity3d.player;
public class ChannelHelpers {
public class ChannelEntry {
public static final String CHANNEL = "GooglePlay";
static void register() {
// GooglePlay uses default behavior, no additional SDKHelper
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return null;
}
public static String normalizeSkuList(String msg) {

View File

@@ -72,7 +72,6 @@ dependencies {
implementation 'com.appsflyer:purchase-connector:2.2.0' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:8
implementation 'com.appsflyer:unity-wrapper:6.17.7' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:6
implementation 'com.google.android.ump:user-messaging-platform:2.1.0' // Assets/MaxSdk/AppLovin/Editor/Dependencies.xml:5
implementation 'com.tapjoy:tapjoy-android-unitybridge:14.5.0' // Packages/com.unity.package-offerwall/Editor/TJPluginDependencies.xml:10
// Android Resolver Dependencies End
**DEPS**}

View File

@@ -18,9 +18,6 @@ dependencyResolutionManagement {
mavenCentral()
// Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://sdk.tapjoy.com/" // Packages/com.unity.package-offerwall/Editor/TJPluginDependencies.xml:9
}
maven {
url "https://android-sdk.is.com/" // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
}

View File

@@ -6,11 +6,11 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class ChannelHelpers {
public class ChannelEntry {
public static final String CHANNEL = "Rustore";
static void register() {
// Rustore uses default behavior, no additional SDKHelper
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return null;
}
public static String normalizeSkuList(String msg) {
@@ -37,7 +37,7 @@ public class ChannelHelpers {
}
return products.toString();
} catch (JSONException e) {
Log.e("ChannelHelpers", "normalizeSkuList error: " + e.getMessage());
Log.e("ChannelEntry", "normalizeSkuList error: " + e.getMessage());
return msg;
}
}

View File

@@ -0,0 +1,14 @@
plugins {
// If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity
// See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html
// See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'com.google.gms.google-services' version '4.3.3' apply false
**BUILD_SCRIPT_DEPS**
}
task clean(type: Delete) {
delete rootProject.buildDir
}