5 Commits

87 changed files with 2688 additions and 1914 deletions

6
.gitignore vendored
View File

@@ -8,4 +8,10 @@ sdk-intergration/.idea
.worktrees/* .worktrees/*
.docs/* .docs/*
.claude/* .claude/*
sdk-intergration/Assets/Plugins/Android/channel_gradle/
sdk-intergration/Assets/Plugins/Android/channel_gradle.meta
sdk-intergration/Assets/Plugins/Android/channel_manifest_lib/build/
sdk-intergration/Assets/Plugins/Android/channel_manifest_lib/build.meta
ChannelConfigs/Android/*/Plugins/Android/gradle/.gradle/
ChannelConfigs/Android/*/Plugins/Android/gradle/build/

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- Put channel manifest increment here. -->
</application>
</manifest>

View File

@@ -0,0 +1,8 @@
package com.unity3d.player;
public class ChannelAdapter extends ChannelAdapterBase {
private static class Config {
public String SDK_CLIENTID = "ReplaceWithChannelClientId";
}
}

View File

@@ -0,0 +1,12 @@
# Files
可选目录。
如果渠道提供 `google-services.json`,放在这里:
```text
Files/google-services.json
```
没有渠道专用文件时可以保持为空。

View File

@@ -0,0 +1,16 @@
# Plugins/Android
可选目录。
渠道 SDK 的 `.aar` / `.jar` 放在这里。切渠道时,这里的第一层插件文件会同步到:
```text
sdk-intergration/Packages/tysdk/Plugins/Android
```
Gradle 增量放到:
```text
Plugins/Android/gradle/
```

View File

@@ -0,0 +1,26 @@
// Applied to :unityLibrary from mainTemplate.gradle.
// Put channel dependencies and android module config here.
dependencies {
// implementation "group:name:version"
// api "group:name:version"
// coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
}
android {
// compileOptions {
// coreLibraryDesugaringEnabled true
// }
// packagingOptions {
// exclude "**/unused.file"
// pickFirst "**/duplicated.file"
// }
// defaultConfig {
// manifestPlaceholders += [
// key: "value"
// ]
// }
}

View File

@@ -0,0 +1,11 @@
// Applied from settingsTemplate.gradle.
// Put channel Maven repositories or extra module includes here.
dependencyResolutionManagement {
repositories {
// maven {
// url "https://example.maven.repo/"
// }
}
}

View File

@@ -0,0 +1,49 @@
# Android 渠道接入模板
复制 `_template` 为新渠道目录,例如:
```text
ChannelConfigs/Android/mychannel
```
然后按需填写下列文件。
## 目录说明
```text
AndroidManifest.xml
ChannelAdapter.java
Files/
README.md
Plugins/
Android/
README.md
gradle/
main.gradle
settings.gradle
```
## 新增渠道步骤
1. 复制 `_template` 并改名为渠道小写目录名。
2.`ChannelAdapter.java` 改名为真实 Adapter例如 `MyChannelAdapter.java`
3. 修改 Adapter 类名,并按渠道 SDK 文档实现需要的接口。
4. `AndroidManifest.xml` 只写渠道 Manifest 增量,不复制主 Manifest。
5. 渠道有 `google-services.json` 时放到 `Files/`
6. 渠道有 `.aar` / `.jar` 时放到 `Plugins/Android/`
7. 渠道要求添加依赖或 `android {}` 配置时,写到 `Plugins/Android/gradle/main.gradle`
8. 渠道要求添加 Maven 仓库或额外 module 时,写到 `Plugins/Android/gradle/settings.gradle`
## 不要复制这些文件
不要在渠道目录放整份 Unity Gradle 模板:
```text
mainTemplate.gradle
settingsTemplate.gradle
launcherTemplate.gradle
baseProjectTemplate.gradle
gradleTemplate.properties
```
主模板由 `sdk-intergration/Assets/Plugins/Android/` 统一维护,渠道只提供增量。

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="com.sensorsdata.analytics.android.MainProcessName"
android:value="${applicationId}"
tools:replace="android:value" />
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
package com.unity3d.player;
public class EnjoyPayAdapter extends ChannelAdapterBase {
private static class Config {
public String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMasterRussia";
}
}

View File

@@ -0,0 +1,39 @@
{
"project_info": {
"project_number": "550846874481",
"project_id": "fishing-travel-ep",
"storage_bucket": "fishing-travel-ep.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:550846874481:android:800f3c713738b6b51d2cd0",
"android_client_info": {
"package_name": "com.arkgame.ft.ep"
}
},
"oauth_client": [
{
"client_id": "550846874481-8thf5tv2gm5n7b7uui3kts0kajuddhn7.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDw8YISM6R6vtHC4IfJtNBFijrKqgxlfcw"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "550846874481-8thf5tv2gm5n7b7uui3kts0kajuddhn7.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -0,0 +1,9 @@
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="com.sensorsdata.analytics.android.MainProcessName"
android:value="${applicationId}"
tools:replace="android:value" />
<meta-data android:name="com.flexionmobile.fdk.apptoken" android:value="fishingtravel" />
<meta-data android:name="com.flexionmobile.fdk.sdk-type" android:value="unity" />
<provider
android:name="com.facebook.FacebookContentProvider"
android:authorities="com.facebook.app.FacebookContentProvider162066986963808.${applicationId}"
android:exported="true"
tools:replace="android:authorities" />
</application>
</manifest>

View File

@@ -0,0 +1,815 @@
package com.unity3d.player;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import com.flexionmobile.ddpx.listener.ConnectionStateListener;
import com.flexionmobile.ddpx.listener.PurchasesUpdateListener;
import com.flexionmobile.ddpx.model.BillingResult;
import com.flexionmobile.ddpx.model.BillingResults;
import com.flexionmobile.ddpx.model.Purchase;
import com.flexionmobile.ddpx.model.ProductDetails;
import com.flexionmobile.ddpx.model.params.BillingFlowParams;
import com.flexionmobile.ddpx.model.params.ConsumeParams;
import com.flexionmobile.ddpx.model.params.QueryPurchasesParams;
import com.flexionmobile.ddpx.model.params.ProductDetailsParams;
import com.flexionmobile.ddpx.service.BillingService;
import com.flexionmobile.fdk.FLX;
import com.tuyoo.gamesdk.api.SDKAPI;
import com.tuyoo.gamesdk.model.InitParam;
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 FlexionAdapter extends ChannelAdapterBase implements
SDKManager.IPaymentAdapter,
SDKManager.IProductQueryAdapter,
SDKManager.IPendingPurchaseAdapter,
SDKManager.IBillingLifecycleAdapter,
SDKManager.IChannelInfoAdapter,
SDKManager.IReviewAdapter {
private static final String TAG = "FlexionAdapter";
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<String> pendingPurchases = 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 interface BillingReadyOperation {
void run(BillingService service);
}
private static class Config {
public String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.flexion.FishingMaster";
}
private static class PendingBillingOperation {
final String operation;
final BillingReadyOperation onReady;
final Runnable onFail;
PendingBillingOperation(String operation, BillingReadyOperation onReady, Runnable onFail) {
this.operation = operation;
this.onReady = onReady;
this.onFail = onFail;
}
}
private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> {
// 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, null);
} else {
Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode());
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null, null, null);
}
};
@Override
public void init() {
// Log.i(TAG, "[FLX-INIT] adapter init flexionInitialized=" + flexionInitialized
// + " flexionScreensStarted=" + flexionScreensStarted
// + " flexionScreensShown=" + flexionScreensShown);
activity.runOnUiThread(() -> {
SDKAPI.getIns().lightModeEnable();
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);
String developerPayload = "";
try {
byte[] decoded = Base64.getDecoder().decode(appInfo);
JSONObject json = new JSONObject(new String(decoded, StandardCharsets.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", ""));
if (!customData.isEmpty()) {
payload.put("customData", customData);
}
payload.put("prodPrice", productPrice);
payload.put("prodCount", productCount);
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());
}
String finalPayload = developerPayload;
ensureBillingReady(activity, "pay", bs ->
billingExecutor.execute(() -> {
try {
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() != 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, 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, null);
}
}),
() -> sendPayCallback(1, "billing_not_ready", null, null, null, null));
}
@Override
public void queryProducts() {
// Flexion 渠道不通过 thirdExtend 获取商品列表,由 queryProducts(String) 替代
}
@Override
public void queryProducts(String productIds) {
List<String> ids = Arrays.asList(productIds.split(","));
Log.i(TAG, "[FLEXION-SKU-PRICE] queryProducts productIds=" + productIds
+ " count=" + ids.size()
+ " billingConnected=" + billingConnected
+ " billingReady=" + (billingService != null && billingService.isReady()));
ensureBillingReady(null, "queryProducts",
bs -> queryProductDetails(bs, ids),
() -> sendExtensionError("billing_not_ready"));
}
private static void initBillingService(Activity activity) {
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(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,
BillingReadyOperation onReady, Runnable onFail) {
ensureBillingReady(activity, operation, onReady, onFail, false);
}
private static void ensureBillingReady(Activity activity, String operation,
BillingReadyOperation onReady, Runnable onFail,
boolean forceReconnect) {
if (activity != null) {
billingActivity = activity;
}
Activity targetActivity = activity != null ? activity : billingActivity;
if (targetActivity == null) {
Log.e(TAG, "[PAY-FLOW] ensureBillingReady failed: activity is null operation=" + operation);
if (onFail != null) onFail.run();
return;
}
targetActivity.runOnUiThread(() -> {
try {
if (billingService == null) {
// 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);
if (ready && !forceReconnect) {
billingConnected = true;
if (onReady != null) onReady.run(billingService);
return;
}
pendingBillingOperations.add(new PendingBillingOperation(operation, onReady, onFail));
if (billingConnecting) {
// 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);
billingService.startConnection(targetActivity, new ConnectionStateListener() {
@Override
public void onBillingSetupFinished(BillingResult result) {
int code = result.getResponseCode();
// Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code);
billingConnecting = false;
billingConnected = code == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE;
drainBillingOperations(billingConnected);
if (billingConnected) {
queryPendingPurchases(false);
}
}
@Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "[PAY-FLOW] billing service disconnected");
billingConnected = false;
}
});
mainHandler.postDelayed(() -> {
if (!billingConnecting) return;
boolean timeoutReady = billingService != null && billingService.isReady();
Log.w(TAG, "[PAY-FLOW] billing reconnect timeout operation=" + operation
+ " ready=" + timeoutReady);
billingConnecting = false;
billingConnected = timeoutReady;
drainBillingOperations(timeoutReady);
}, 10000);
} catch (Exception e) {
Log.e(TAG, "[PAY-FLOW] ensureBillingReady exception operation=" + operation
+ " msg=" + e.getMessage());
billingConnecting = false;
billingConnected = false;
drainBillingOperations(false);
}
});
}
private static void drainBillingOperations(boolean success) {
List<PendingBillingOperation> operations = new ArrayList<>(pendingBillingOperations);
pendingBillingOperations.clear();
// 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);
} else {
Log.w(TAG, "[PAY-FLOW] billing operation failed operation=" + operation.operation);
if (operation.onFail != null) operation.onFail.run();
}
}
}
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) {
// 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");
}
}
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
// + " sendUnityCallback=" + sendUnityCallback);
ensureBillingReady(null, "queryPendingPurchases",
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());
});
}
@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;
}
}
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());
return result;
} catch (JSONException e) {
Log.e(TAG, "[PAY-FLOW] pendingPurchaseToJson error: " + e.getMessage());
return null;
}
}
private static JSONArray drainCachedPendingPurchases() {
synchronized (pendingPurchases) {
// 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] drainCachedPendingPurchases json error: " + e.getMessage());
}
}
pendingPurchases.clear();
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");
billingConnected = false;
ensureBillingReady(null, "refreshBilling",
bs -> {
// Log.i(TAG, "[PAY-FLOW] refreshBilling ready");
},
() -> Log.w(TAG, "[PAY-FLOW] refreshBilling failed"),
true);
}
private static void queryProductDetails(BillingService service, List<String> productIds) {
final AtomicBoolean completed = new AtomicBoolean(false);
Log.i(TAG, "[FLEXION-SKU-PRICE] queryProductDetails start count=" + productIds.size()
+ " productIds=" + productIds
+ " serviceReady=" + (service != null && service.isReady()));
mainHandler.postDelayed(() -> {
if (!completed.compareAndSet(false, true)) return;
Log.w(TAG, "[PAY-FLOW] queryProductDetails timeout count=" + productIds.size());
sendExtensionError("flexion_query_timeout");
}, 30000);
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;
}
Log.i(TAG, "[FLEXION-SKU-PRICE] queryProductDetails callback code="
+ (billingResult == null ? "null" : billingResult.getResponseCode())
+ " debugMsg="
+ (billingResult == null ? "billing_result_null" : billingResult.getDebugMessage())
+ " count=" + (productDetails == null ? 0 : productDetails.size()));
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());
Log.i(TAG, "[FLEXION-SKU-PRICE] sdk product id=" + detail.getId()
+ " title=" + detail.getTitle()
+ " price=" + detail.getPrice()
+ " priceAmountMicros=" + detail.getPriceAmountMicros()
+ " currency=" + detail.getPriceCurrencyCode()
+ " type=" + detail.getType());
arr.put(item);
}
result.put("code", 0);
result.put("respObj", arr.toString());
Log.i(TAG, "[FLEXION-SKU-PRICE] send Unity SKUListResult 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));
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);
// 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) {
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
ConfigManager.PAY_CALLBACK_FUNCTION_NAME, "{\"code\":1,\"errStr\":\"json_error\"}");
}
}
@Override
public void consume(String token) {
consumePurchase(token);
}
public static void consumePurchase(String token) {
if (token == null || token.isEmpty()) {
Log.w(TAG, "[PAY-FLOW] consumePurchase: token is null");
return;
}
// Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token);
ensureBillingReady(null, "consumePurchase",
bs -> billingExecutor.execute(() -> {
try {
bs.consumeAsync(
new ConsumeParams(token),
r -> {
// Log.i(TAG, "[PAY-FLOW] consume result: " +
// (r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail"));
});
} catch (Exception e) {
Log.e(TAG, "[PAY-FLOW] consumePurchase exception: " + e.getMessage());
}
}),
() -> Log.w(TAG, "[PAY-FLOW] consumePurchase skipped: billing not connected"));
}
public static void cancelPurchase() {
// Log.i(TAG, "[PAY-FLOW] cancelPurchase: TODO - dismiss billing dialog");
}
@Override
public void review(Activity activity) {
String storePageUri = getStorePageUri(activity);
Log.w(TAG, "[ReviewDebug] review uri=" + storePageUri);
if (TextUtils.isEmpty(storePageUri)) {
Log.w(TAG, "review skipped: store page uri is empty");
return;
}
Activity targetActivity = activity != null ? activity : billingActivity;
if (targetActivity == null) targetActivity = UnityPlayer.currentActivity;
if (targetActivity == null) {
Log.w(TAG, "review skipped: activity is null");
return;
}
try {
targetActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(storePageUri)));
} catch (Throwable e) {
Log.w(TAG, "review exception: " + e.getMessage());
}
}
@Override
public String getStorePageUri(Activity activity) {
try {
Activity targetActivity = activity != null ? activity : billingActivity;
if (targetActivity == null) targetActivity = UnityPlayer.currentActivity;
if (targetActivity == null) {
Log.w(TAG, "getStorePageUri skipped: activity is null");
return "";
}
Object storePageUri = FLX.createChannelInfoService(targetActivity)
.getCurrentChannelInfo(targetActivity)
.getStorePageUri();
return storePageUri != null ? storePageUri.toString() : "";
} catch (Throwable e) {
Log.w(TAG, "getStorePageUri exception: " + e.getMessage());
return "";
}
}
private static void sendExtensionError(String errStr) {
try {
JSONObject result = new JSONObject();
result.put("code", 1);
result.put("thirdExtend_errStr", errStr);
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}");
}
}
}

View File

@@ -0,0 +1,10 @@
dependencies {
implementation("com.flexionmobile:sdk:4.0.1")
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}

View File

@@ -0,0 +1,11 @@
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/FlexionSDK/JavaSDK")
credentials {
username = "FlexionSDK"
password = "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"
}
}
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application tools:overrideLibrary="com.tuyoo.gamesdk">
</application>
</manifest>

View File

@@ -0,0 +1,13 @@
dependencies {
implementation(platform("ru.rustore.sdk:bom:2025.11.01"))
implementation("ru.rustore.sdk:pay")
implementation "ru.rustore.sdk:review:10.0.0"
implementation "com.vk.id:vkid:2.5.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}

View File

@@ -0,0 +1,13 @@
dependencyResolutionManagement {
repositories {
maven {
url "https://artifactory-external.vkpartner.ru/artifactory/vkid-sdk-android/"
}
maven {
url "https://artifactory-external.vkpartner.ru/artifactory/maven/"
}
maven {
url "https://artifactory-external.vkpartner.ru/artifactory/vk-id-captcha/android/"
}
}
}

View File

@@ -3,42 +3,44 @@ package com.unity3d.player;
import android.util.Log; import android.util.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
public class ChannelEntry { import java.util.Locale;
public static final String CHANNEL = "Rustore";
public static SDKManager.IChannelSDKAdapter createSDKAdapter() { public class RustoreAdapter extends ChannelAdapterBase {
return null; private static final String TAG = "RustoreAdapter";
}
@Override
public String normalizeSkuList(String msg) {
if (msg == null || msg.isEmpty()) {
return msg;
}
public static String normalizeSkuList(String msg) {
if (msg == null || msg.isEmpty()) return msg;
try { try {
JSONArray products = new JSONArray(msg); JSONArray products = new JSONArray(msg);
for (int i = 0; i < products.length(); i++) { for (int i = 0; i < products.length(); i++) {
JSONObject p = products.getJSONObject(i); JSONObject p = products.getJSONObject(i);
// price is in kopecks, convert to micros: kopecks * 10000
String price = p.optString("price", "0"); String price = p.optString("price", "0");
long micros = Long.parseLong(price) * 10000; long micros = Long.parseLong(price) * 10000;
p.put("price_amount_micros", String.valueOf(micros)); p.put("price_amount_micros", String.valueOf(micros));
// Map currency price_currency_code
String currency = p.optString("currency", ""); String currency = p.optString("currency", "");
if (!currency.isEmpty()) { if (!currency.isEmpty()) {
p.put("price_currency_code", currency); p.put("price_currency_code", currency);
} }
// Convert display price: "199" "₽1.99"
float rubles = Float.parseFloat(price) / 100; float rubles = Float.parseFloat(price) / 100;
p.put("price", String.format("₽%s", rubles)); p.put("price", String.format(Locale.US, "RUB %.2f", rubles));
} }
return products.toString(); return products.toString();
} catch (JSONException e) { } catch (Exception e) {
Log.e("ChannelEntry", "normalizeSkuList error: " + e.getMessage()); Log.e(TAG, "normalizeSkuList error: " + e.getMessage());
return msg; return msg;
} }
} }
private static class Config {
public String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.rustore.FishingTravel";
}
} }

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- TW 渠道,程序内部用来区分 -->
<meta-data android:name="com.arkgame.ft.channel" android:value="TW" />
<!-- AppsFlyer 用来区分 -->
<meta-data android:name="CHANNEL" android:value="TW" />
</application>
</manifest>

View File

@@ -0,0 +1,39 @@
{
"project_info": {
"project_number": "679771497905",
"project_id": "fishing-travel-tw",
"storage_bucket": "fishing-travel-tw.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:679771497905:android:062e2b39ee6613ad210990",
"android_client_info": {
"package_name": "com.arkgame.ft.tw"
}
},
"oauth_client": [
{
"client_id": "679771497905-9d8ht9dnuhbn70p72hhc5ld29lfbqhpr.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCvYkrDG0tMViVdQ28VGQJ1NpgwkROm_9U"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "679771497905-9d8ht9dnuhbn70p72hhc5ld29lfbqhpr.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -0,0 +1,9 @@
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
}
android {
compileOptions {
coreLibraryDesugaringEnabled true
}
}

View File

@@ -0,0 +1,16 @@
# TW Android 渠道配置
当前 TW 渠道先按 GooglePlay 派生包整理,只保留渠道目录和 Manifest 增量入口。
如果 TW 后续有独立配置,按需补充:
```text
Files/google-services.json
Plugins/Android/tuyoosdk_1.0.0.aar
Plugins/Android/gradle/main.gradle
Plugins/Android/gradle/settings.gradle
AddressableAssetsData/Android/addressables_content_state.bin
TWAdapter.java
```
不要在此目录放整份 Unity Gradle 模板文件,只放渠道增量。

View File

@@ -0,0 +1,24 @@
package com.unity3d.player;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Locale;
// Tw 渠道
public class TWAdapter extends ChannelAdapterBase {
private static final String TAG = "TWAdapter";
//- tw包
private static class Config {
//-
public String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.rustore.FishingTravelTw";
//-
public static String SDK_LOGIN_SERVER_URL = "https://128-hwsfsdk-sdk-ts02.sdk-k8s-us-gcp.arksgame.com";
}
}

View File

@@ -1,69 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arkgame.ft.flexion"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:extractNativeLibs="true" android:name="com.unity3d.player.TYApp"
tools:replace="android:usesCleartextTraffic,android:fullBackupContent"
android:fullBackupContent="@xml/flexion_backup_rules"
android:usesCleartextTraffic="true">
<activity android:name="com.unity3d.player.TYUnityActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="fishingtraveliae.onelink.me" android:pathPrefix="/jT1Q"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="mainactivity" android:scheme="zzft" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<!-- 渠道标识ChannelManager 读取此值以识别 Flexion 渠道 -->
<meta-data android:name="CHANNEL" android:value="Flexion" />
<!-- 覆盖 gasdk 与 tuyoosdk 的 MainProcessName 冲突 -->
<meta-data android:name="com.sensorsdata.analytics.android.MainProcessName"
android:value="${applicationId}"
tools:replace="android:value" />
<!-- Flexion SDK 应用标识,从 DropPoint 获取 -->
<meta-data android:name="com.flexionmobile.fdk.apptoken" android:value="fishingtravel" />
<!-- 告知 Flexion SDK 当前为 Unity 项目,必须声明 -->
<meta-data android:name="com.flexionmobile.fdk.sdk-type" android:value="unity" />
<!--
覆盖 tuyoosdk AAR 中硬编码的 Facebook ContentProvider authority。
AAR 原值com.facebook.app.FacebookContentProvider162066986963808
追加 ${applicationId}Flexion 包名)作为后缀后,与 GP 版 authority 不同,
两个渠道包可同时安装在同一设备上,不会触发 INSTALL_FAILED_CONFLICTING_PROVIDER。
参考Flexion 官方文档《Unique package name - follow-on requirements》
-->
<provider
android:name="com.facebook.FacebookContentProvider"
android:authorities="com.facebook.app.FacebookContentProvider162066986963808.${applicationId}"
android:exported="true"
tools:replace="android:authorities" />
</application>
<queries>
<provider android:authorities="com.facebook.katana.provider.PlatformProvider" />
<provider android:authorities="com.facebook.orca.provider.PlatformProvider" />
</queries>
</manifest>

View File

@@ -1,20 +0,0 @@
package com.unity3d.player;
public class ChannelEntry {
public static final String CHANNEL = "Flexion";
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return new FlexionSDKAdapter();
}
// Flexion provides price in micros and currency code, so no normalization needed
// 支付的变化,可能会修改这里
public static String normalizeSkuList(String msg) {
return msg;
}
//public SDKExtBuilder;
}

View File

@@ -1,29 +0,0 @@
package com.unity3d.player;
public class ConfigManager {
// Common SDK parameters
public static int SDK_APPID = 20587;
public static String SDK_GAMEID = "20587";
public static String SDK_PROJECTID = "20587";
public static String SDK_CLOUDID = "128";
public static String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.flexion.FishingMaster";
public static String SDK_LOGIN_SERVER_URL = "https://128-hwsfsdk-sdk-online01.qijihdhk.com";
// FCM (anti-addiction system)
public static String FCM_NAMESPACE = "128";
public static String FCM_IAM_HOST = "tcp://fcmtcp.tuyoo.com:3563";
public static String FCM_ANTISERVERURL = "https://fcmapi.tuyoo.com";
// Unity Facade callback names
public static final String UNITY_FACADE_NAME = "TYSdkFacade";
public static final String LOGIN_CALLBACK_FUNCTION_NAME = "LoginResult";
public static final String LINKACCOUT_CALLBACK_FUNCTION_NAME = "LinkAccoutResult";
public static final String CHANGELINK_CALLBACK_FUNCTION_NAME = "ChangeLinkAccountResult";
public static final String CHECKLINK_CALLBACK_FUNCTION_NAME = "CheckLinkResult";
public static final String PAY_CALLBACK_FUNCTION_NAME = "PayResult";
public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult";
public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback";
public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback";
public static final String INIT_CALLBACK_FUNCTION_NAME = "InitResult";
}

View File

@@ -1,493 +0,0 @@
package com.unity3d.player;
import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.flexionmobile.ddpx.listener.ConnectionStateListener;
import com.flexionmobile.ddpx.listener.PurchasesUpdateListener;
import com.flexionmobile.ddpx.model.BillingResult;
import com.flexionmobile.ddpx.model.BillingResults;
import com.flexionmobile.ddpx.model.Purchase;
import com.flexionmobile.ddpx.model.ProductDetails;
import com.flexionmobile.ddpx.model.params.BillingFlowParams;
import com.flexionmobile.ddpx.model.params.ConsumeParams;
import com.flexionmobile.ddpx.model.params.QueryPurchasesParams;
import com.flexionmobile.ddpx.model.params.ProductDetailsParams;
import com.flexionmobile.ddpx.service.BillingService;
import com.flexionmobile.fdk.FLX;
import com.tuyoo.gamesdk.api.SDKAPI;
import com.tuyoo.gamesdk.model.InitParam;
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 FlexionSDKAdapter implements
SDKManager.IChannelSDKAdapter,
SDKManager.IPaymentAdapter,
SDKManager.IProductQueryAdapter,
SDKManager.IPendingPurchaseAdapter,
SDKManager.IBillingLifecycleAdapter {
private static final String TAG = "FlexionSDKAdapter";
private static BillingService billingService;
private static boolean billingConnected = false;
private static boolean billingConnecting = false;
private static Activity billingActivity;
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
private static final List<String> pendingPurchases = new ArrayList<>();
private static final List<PendingBillingOperation> pendingBillingOperations = new ArrayList<>();
private static final ExecutorService billingExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "FlexionBillingThread"));
private interface BillingReadyOperation {
void run(BillingService service);
}
private static class PendingBillingOperation {
final String operation;
final BillingReadyOperation onReady;
final Runnable onFail;
PendingBillingOperation(String operation, BillingReadyOperation onReady, Runnable onFail) {
this.operation = operation;
this.onReady = onReady;
this.onFail = onFail;
}
}
private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> {
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, null);
} else {
Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode());
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null, null, null);
}
};
@Override
public void init(Activity activity) {
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);
}
});
}
});
}
@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);
String developerPayload = "";
try {
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", ""));
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;
ensureBillingReady(activity, "pay", bs ->
billingExecutor.execute(() -> {
try {
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() != 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, 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, null);
}
}),
() -> sendPayCallback(1, "billing_not_ready", null, null, null, null));
}
@Override
public void queryProducts() {
// Flexion 渠道不通过 thirdExtend 获取商品列表,由 queryProducts(String) 替代
}
@Override
public void queryProducts(String productIds) {
Log.i(TAG, "queryProducts(productIds) called, billingConnected=" + billingConnected);
List<String> ids = Arrays.asList(productIds.split(","));
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");
ensureBillingReady(activity, "initBillingService",
bs -> queryPendingPurchases(),
() -> Log.w(TAG, "[PAY-FLOW] initBillingService failed: billing not ready"));
}
private static void ensureBillingReady(Activity activity, String operation,
BillingReadyOperation onReady, Runnable onFail) {
ensureBillingReady(activity, operation, onReady, onFail, false);
}
private static void ensureBillingReady(Activity activity, String operation,
BillingReadyOperation onReady, Runnable onFail,
boolean forceReconnect) {
if (activity != null) {
billingActivity = activity;
}
Activity targetActivity = activity != null ? activity : billingActivity;
if (targetActivity == null) {
Log.e(TAG, "[PAY-FLOW] ensureBillingReady failed: activity is null operation=" + operation);
if (onFail != null) onFail.run();
return;
}
targetActivity.runOnUiThread(() -> {
try {
if (billingService == null) {
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);
if (ready && !forceReconnect) {
billingConnected = true;
if (onReady != null) onReady.run(billingService);
return;
}
pendingBillingOperations.add(new PendingBillingOperation(operation, onReady, onFail));
if (billingConnecting) {
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);
billingService.startConnection(targetActivity, new ConnectionStateListener() {
@Override
public void onBillingSetupFinished(BillingResult result) {
int code = result.getResponseCode();
Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + code);
billingConnecting = false;
billingConnected = code == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE;
drainBillingOperations(billingConnected);
if (billingConnected) {
queryPendingPurchases();
}
}
@Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "[PAY-FLOW] billing service disconnected");
billingConnected = false;
}
});
mainHandler.postDelayed(() -> {
if (!billingConnecting) return;
boolean timeoutReady = billingService != null && billingService.isReady();
Log.w(TAG, "[PAY-FLOW] billing reconnect timeout operation=" + operation
+ " ready=" + timeoutReady);
billingConnecting = false;
billingConnected = timeoutReady;
drainBillingOperations(timeoutReady);
}, 10000);
} catch (Exception e) {
Log.e(TAG, "[PAY-FLOW] ensureBillingReady exception operation=" + operation
+ " msg=" + e.getMessage());
billingConnecting = false;
billingConnected = false;
drainBillingOperations(false);
}
});
}
private static void drainBillingOperations(boolean success) {
List<PendingBillingOperation> operations = new ArrayList<>(pendingBillingOperations);
pendingBillingOperations.clear();
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);
} else {
Log.w(TAG, "[PAY-FLOW] billing operation failed operation=" + operation.operation);
if (operation.onFail != null) operation.onFail.run();
}
}
}
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) {
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");
}
}
private static void queryPendingPurchases() {
Log.i(TAG, "[PAY-FLOW] queryPendingPurchases start: billingService=" + billingService
+ " billingConnected=" + billingConnected);
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"));
}
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();
}
}
@Override
public void refreshBilling() {
Log.i(TAG, "[PAY-FLOW] refreshBilling requested");
billingConnected = false;
ensureBillingReady(null, "refreshBilling",
bs -> Log.i(TAG, "[PAY-FLOW] refreshBilling ready"),
() -> Log.w(TAG, "[PAY-FLOW] refreshBilling failed"),
true);
}
private static void queryProductDetails(BillingService service, List<String> productIds) {
final boolean[] completed = {false};
mainHandler.postDelayed(() -> {
if (completed[0]) return;
completed[0] = true;
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);
}
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}");
}
});
}
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);
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) {
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
ConfigManager.PAY_CALLBACK_FUNCTION_NAME, "{\"code\":1,\"errStr\":\"json_error\"}");
}
}
@Override
public void consume(String token) {
consumePurchase(token);
}
public static void consumePurchase(String token) {
if (token == null || token.isEmpty()) {
Log.w(TAG, "[PAY-FLOW] consumePurchase: token is null");
return;
}
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"))),
() -> Log.w(TAG, "[PAY-FLOW] consumePurchase skipped: billing not connected"));
}
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();
result.put("code", 1);
result.put("thirdExtend_errStr", errStr);
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}");
}
}
}

View File

@@ -1,11 +0,0 @@
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
unityStreamingAssets=**STREAMING_ASSETS**
# Android Resolver Properties Start
android.useAndroidX=true
android.enableJetifier=true
# Android Resolver Properties End
android.suppressUnsupportedCompileSdk=35
**ADDITIONAL_PROPERTIES**

View File

@@ -1,66 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation project(':unityLibrary')
implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
implementation("com.google.firebase:firebase-analytics")
}
android {
namespace "**NAMESPACE**"
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
applicationId '**APPLICATIONID**'
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**SIGN**
lintOptions {
abortOnError false
}
buildTypes {
debug {
minifyEnabled **MINIFY_DEBUG**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
jniDebuggable true
}
release {
minifyEnabled **MINIFY_RELEASE**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
}
}**PACKAGING_OPTIONS****PLAY_ASSET_PACKS****SPLITS**
**BUILT_APK_LOCATION**
bundle {
language {
enableSplit = false
}
density {
enableSplit = false
}
abi {
enableSplit = true
}
}
}**SPLITS_VERSION_CODE****LAUNCHER_SOURCE_BUILD_SETUP**

View File

@@ -1,131 +0,0 @@
apply plugin: 'com.android.library'
**APPLY_PLUGINS**
// Android Resolver Exclusions Start
android {
packagingOptions {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
namespace "com.unity3d.player"
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api "com.android.support:support-v4:28.0.0"
api "com.android.support:appcompat-v7:28.0.0"
api "com.google.code.gson:gson:2.8.6"
api "com.squareup.okio:okio:1.15.0"
api "com.squareup.okhttp3:okhttp:3.12.13"
api "com.squareup.retrofit2:retrofit:2.3.0"
api "com.squareup.retrofit2:converter-gson:2.2.0"
api "com.squareup.retrofit2:adapter-rxjava:2.1.0"
api "io.reactivex:rxandroid:1.2.1"
api "io.reactivex:rxjava:1.1.9"
// Google Play In-App Review
implementation 'com.google.android.play:review:2.0.1'
// Google Play Billing — tuyoosdk AAR references BillingManager during init
implementation 'com.android.billingclient:billing:7.0.0'
implementation 'net.aihelp:android-aihelp-aar:5.3.+'
implementation 'com.google.android.gms:play-services-auth:20.2.0'
// Flexion SDK (includes wrapped billing)
implementation("com.flexionmobile:sdk:4.0.1")
implementation 'com.google.android.gms:play-services-basement:18.2.0'
implementation 'androidx.fragment:fragment:1.3.6'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.41"
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.41'
implementation 'androidx.core:core:1.8.0'
implementation 'com.miui.referrer:homereferrer:1.0.0.6'
implementation platform('com.google.firebase:firebase-bom:34.6.0')
implementation 'com.google.firebase:firebase-analytics'
implementation('com.facebook.android:facebook-android-sdk:16.2.0')
implementation 'com.google.android.play:asset-delivery:2.2.2'
implementation "androidx.credentials:credentials:1.3.0"
implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
// Android Resolver Dependencies Start
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:7
implementation 'com.applovin.mediation:facebook-adapter:[6.18.0.1]' // Assets/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:fyber-adapter:8.4.2.0' // Assets/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:google-adapter:[23.6.0.1]' // Assets/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:ironsource-adapter:8.6.1.0.0' // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:unityads-adapter:4.12.3.0' // Assets/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4
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
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**}
// Android Resolver Exclusions Start
android {
packagingOptions {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
coreLibraryDesugaringEnabled true
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING_OPTIONS**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**

View File

@@ -1,49 +0,0 @@
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
// Flexion SDK Maven Repository
maven {
url = uri("https://maven.pkg.github.com/FlexionSDK/JavaSDK")
credentials {
username = "FlexionSDK"
// Replace with your actual Flexion PAT from DropPoint
password = "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"
}
}
// 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
}
mavenLocal()
// Android Resolver Repos End
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
maven {
url "http://sdkck.tuyoo.com/artifactory/tuyoo-component/"
allowInsecureProtocol = true
}
}
}

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arkgame.ft"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:extractNativeLibs="true" android:name="com.unity3d.player.TYApp" android:usesCleartextTraffic="true">
<activity android:name="com.unity3d.player.TYUnityActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="fishingtraveliae.onelink.me" android:pathPrefix="/jT1Q"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="mainactivity" android:scheme="zzft" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="true" />
<meta-data android:name="google_analytics_default_allow_ad_storage" android:value="true" />
<meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="true" />
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="true" />
</activity>
<meta-data android:name="CHANNEL" android:value="GooglePlay" />
</application>
<queries>
<provider android:authorities="com.facebook.katana.provider.PlatformProvider" /> <!-- allows app to access Facebook app features -->
<provider android:authorities="com.facebook.orca.provider.PlatformProvider" /> <!-- allows sharing to Messenger app -->
<intent>
<action android:name="android.intent.action.MAIN"/>
</intent>
</queries>
</manifest>

View File

@@ -1,13 +0,0 @@
package com.unity3d.player;
public class ChannelEntry {
public static final String CHANNEL = "GooglePlay";
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return null;
}
public static String normalizeSkuList(String msg) {
return msg;
}
}

View File

@@ -1,29 +0,0 @@
package com.unity3d.player;
public class ConfigManager {
// Common SDK parameters
public static int SDK_APPID = 20587;
public static String SDK_GAMEID = "20587";
public static String SDK_PROJECTID = "20587";
public static String SDK_CLOUDID = "128";
public static String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMaster";
public static String SDK_LOGIN_SERVER_URL = "https://128-hwsfsdk-sdk-online01.qijihdhk.com";
// FCM (anti-addiction system)
public static String FCM_NAMESPACE = "128";
public static String FCM_IAM_HOST = "tcp://fcmtcp.tuyoo.com:3563";
public static String FCM_ANTISERVERURL = "https://fcmapi.tuyoo.com";
// Unity Facade callback names
public static final String UNITY_FACADE_NAME = "TYSdkFacade";
public static final String LOGIN_CALLBACK_FUNCTION_NAME = "LoginResult";
public static final String LINKACCOUT_CALLBACK_FUNCTION_NAME = "LinkAccoutResult";
public static final String CHANGELINK_CALLBACK_FUNCTION_NAME = "ChangeLinkAccountResult";
public static final String CHECKLINK_CALLBACK_FUNCTION_NAME = "CheckLinkResult";
public static final String PAY_CALLBACK_FUNCTION_NAME = "PayResult";
public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult";
public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback";
public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback";
public static final String INIT_CALLBACK_FUNCTION_NAME = "InitResult";
}

View File

@@ -1,47 +0,0 @@
{
"project_info": {
"project_number": "481260393117",
"project_id": "fishing-travel",
"storage_bucket": "fishing-travel.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:481260393117:android:4e8da50d0ee3b5c207fa74",
"android_client_info": {
"package_name": "com.arkgame.ft"
}
},
"oauth_client": [
{
"client_id": "481260393117-jhq50gh6n2p6pat1bjhcgj3th1ke19jg.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.arkgame.ft",
"certificate_hash": "c9cf2f5eaba383cbdd5b968d0342c49afabc32a2"
}
},
{
"client_id": "481260393117-0ah0ekk7lu5r44ko2uqlfoebgak8dmhj.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBU6gAwu0O28jg7-TRd_Vx2YMCH_Baa8Bw"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "481260393117-0ah0ekk7lu5r44ko2uqlfoebgak8dmhj.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@@ -1,11 +0,0 @@
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
unityStreamingAssets=**STREAMING_ASSETS**
# Android Resolver Properties Start
android.useAndroidX=true
android.enableJetifier=true
# Android Resolver Properties End
android.suppressUnsupportedCompileSdk=35
**ADDITIONAL_PROPERTIES**

View File

@@ -1,66 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation project(':unityLibrary')
implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
implementation("com.google.firebase:firebase-analytics")
}
android {
namespace "**NAMESPACE**"
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
applicationId '**APPLICATIONID**'
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**SIGN**
lintOptions {
abortOnError false
}
buildTypes {
debug {
minifyEnabled **MINIFY_DEBUG**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
jniDebuggable true
}
release {
minifyEnabled **MINIFY_RELEASE**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
}
}**PACKAGING_OPTIONS****PLAY_ASSET_PACKS****SPLITS**
**BUILT_APK_LOCATION**
bundle {
language {
enableSplit = false
}
density {
enableSplit = false
}
abi {
enableSplit = true
}
}
}**SPLITS_VERSION_CODE****LAUNCHER_SOURCE_BUILD_SETUP**

View File

@@ -1,110 +0,0 @@
apply plugin: 'com.android.library'
**APPLY_PLUGINS**
// Android Resolver Exclusions Start
android {
packagingOptions {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
namespace "com.unity3d.player"
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api "com.android.support:support-v4:28.0.0"
api "com.android.support:appcompat-v7:28.0.0"
api "com.google.code.gson:gson:2.8.6"
api "com.squareup.okio:okio:1.15.0"
api "com.squareup.okhttp3:okhttp:3.12.13"
api "com.squareup.retrofit2:retrofit:2.3.0"
api "com.squareup.retrofit2:converter-gson:2.2.0"
api "com.squareup.retrofit2:adapter-rxjava:2.1.0"
api "io.reactivex:rxandroid:1.2.1"
api "io.reactivex:rxjava:1.1.9"
// Google Play In-App Review
implementation 'com.google.android.play:review:2.0.1'
implementation 'net.aihelp:android-aihelp-aar:5.3.+'
implementation 'com.google.android.gms:play-services-auth:20.2.0'
implementation('com.android.billingclient:billing:7.0.0')
implementation 'com.google.android.gms:play-services-basement:18.2.0'
implementation 'androidx.fragment:fragment:1.3.6'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.41"
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.41'
implementation 'androidx.core:core:1.8.0'
implementation 'com.miui.referrer:homereferrer:1.0.0.6'
implementation platform('com.google.firebase:firebase-bom:34.6.0')
implementation 'com.google.firebase:firebase-analytics'
implementation('com.facebook.android:facebook-android-sdk:16.2.0')
implementation 'com.google.android.play:asset-delivery:2.2.2'
implementation "androidx.credentials:credentials:1.3.0"
implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
// Android Resolver Dependencies Start
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:7
implementation 'com.applovin.mediation:facebook-adapter:[6.18.0.1]' // Assets/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:fyber-adapter:8.4.2.0' // Assets/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:google-adapter:[23.6.0.1]' // Assets/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:ironsource-adapter:8.6.1.0.0' // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:unityads-adapter:4.12.3.0' // Assets/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4
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
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
**DEPS**}
android {
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING_OPTIONS**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**

View File

@@ -1,35 +0,0 @@
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
// Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://android-sdk.is.com/" // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
}
mavenLocal()
// Android Resolver Repos End
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
maven {
url "http://sdkck.tuyoo.com/artifactory/tuyoo-component/"
allowInsecureProtocol = true
}
}
}

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arkgame.ft"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:extractNativeLibs="true" android:name="com.unity3d.player.TYApp"
tools:replace="android:usesCleartextTraffic"
tools:overrideLibrary="com.tuyoo.gamesdk"
android:usesCleartextTraffic="true">
<activity android:name="com.unity3d.player.TYUnityActivity"
android:theme="@style/UnityThemeSelector">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="fishingtraveliae.onelink.me" android:pathPrefix="/jT1Q"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="mainactivity" android:scheme="zzft" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<meta-data android:name="CHANNEL" android:value="Rustore" />
</application>
<queries>
<provider android:authorities="com.facebook.katana.provider.PlatformProvider" /> <!-- allows app to access Facebook app features -->
<provider android:authorities="com.facebook.orca.provider.PlatformProvider" /> <!-- allows sharing to Messenger app -->
</queries>
</manifest>

View File

@@ -1,29 +0,0 @@
package com.unity3d.player;
public class ConfigManager {
// Common SDK parameters
public static int SDK_APPID = 20587;
public static String SDK_GAMEID = "20587";
public static String SDK_PROJECTID = "20587";
public static String SDK_CLOUDID = "128";
public static String SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.rustore.FishingTravel";
public static String SDK_LOGIN_SERVER_URL = "https://128-hwsfsdk-sdk-online01.qijihdhk.com";
// FCM (anti-addiction system)
public static String FCM_NAMESPACE = "128";
public static String FCM_IAM_HOST = "tcp://fcmtcp.tuyoo.com:3563";
public static String FCM_ANTISERVERURL = "https://fcmapi.tuyoo.com";
// Unity Facade callback names
public static final String UNITY_FACADE_NAME = "TYSdkFacade";
public static final String LOGIN_CALLBACK_FUNCTION_NAME = "LoginResult";
public static final String LINKACCOUT_CALLBACK_FUNCTION_NAME = "LinkAccoutResult";
public static final String CHANGELINK_CALLBACK_FUNCTION_NAME = "ChangeLinkAccountResult";
public static final String CHECKLINK_CALLBACK_FUNCTION_NAME = "CheckLinkResult";
public static final String PAY_CALLBACK_FUNCTION_NAME = "PayResult";
public static final String PAY_TYPE_CALLBACK_FUNCTION_NAME = "SKUListResult";
public static final String FCM_NOTICE_FUNCTION_NAME = "FCMCallback";
public static final String FCM_REALNAME_CALLBACK_FUNCTION_NAME = "RealNameCallback";
public static final String INIT_CALLBACK_FUNCTION_NAME = "InitResult";
}

View File

@@ -1,14 +0,0 @@
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
}

View File

@@ -1,11 +0,0 @@
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
unityStreamingAssets=**STREAMING_ASSETS**
# Android Resolver Properties Start
android.useAndroidX=true
android.enableJetifier=true
# Android Resolver Properties End
android.suppressUnsupportedCompileSdk=35
**ADDITIONAL_PROPERTIES**

View File

@@ -1,74 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
dependencies {
implementation project(':unityLibrary')
implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
implementation("com.google.firebase:firebase-analytics")
}
android {
namespace "**NAMESPACE**"
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
applicationId '**APPLICATIONID**'
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
// ↓↓↓ 这里放你的 VK ID 占位符 ↓↓↓
manifestPlaceholders = [
VKIDRedirectHost : "vk.ru",
VKIDRedirectScheme: "vk54432404",
VKIDClientID : "54432404",
VKIDClientSecret : "WD6BO0CwPXsKMaaFX8Qq"
]
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**SIGN**
lintOptions {
abortOnError false
}
buildTypes {
debug {
minifyEnabled **MINIFY_DEBUG**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
jniDebuggable true
}
release {
minifyEnabled **MINIFY_RELEASE**
proguardFiles getDefaultProguardFile('proguard-android.txt')**SIGNCONFIG**
}
}**PACKAGING_OPTIONS****PLAY_ASSET_PACKS****SPLITS**
**BUILT_APK_LOCATION**
bundle {
language {
enableSplit = false
}
density {
enableSplit = false
}
abi {
enableSplit = true
}
}
}**SPLITS_VERSION_CODE****LAUNCHER_SOURCE_BUILD_SETUP**

View File

@@ -1,131 +0,0 @@
apply plugin: 'com.android.library'
**APPLY_PLUGINS**
// Android Resolver Exclusions Start
android {
packagingOptions {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
namespace "com.unity3d.player"
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api "com.android.support:support-v4:28.0.0"
api "com.android.support:appcompat-v7:28.0.0"
api "com.google.code.gson:gson:2.8.6"
api "com.squareup.okio:okio:1.15.0"
api "com.squareup.okhttp3:okhttp:3.12.13"
api "com.squareup.retrofit2:retrofit:2.3.0"
api "com.squareup.retrofit2:converter-gson:2.2.0"
api "com.squareup.retrofit2:adapter-rxjava:2.1.0"
api "io.reactivex:rxandroid:1.2.1"
api "io.reactivex:rxjava:1.1.9"
// Google Play In-App Review
implementation 'com.google.android.play:review:2.0.1'
implementation 'net.aihelp:android-aihelp-aar:5.3.+'
implementation 'com.google.android.gms:play-services-auth:20.2.0'
implementation('com.android.billingclient:billing:6.0.1')
implementation 'com.google.android.gms:play-services-basement:18.2.0'
implementation 'androidx.fragment:fragment:1.3.6'
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.41"
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'
implementation 'org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.41'
implementation 'androidx.core:core:1.8.0'
implementation 'com.miui.referrer:homereferrer:1.0.0.6'
implementation platform('com.google.firebase:firebase-bom:32.7.4')
implementation 'com.google.firebase:firebase-analytics'
implementation('com.facebook.android:facebook-android-sdk:16.2.0')
implementation 'com.google.android.play:asset-delivery:2.2.2'
implementation "androidx.credentials:credentials:1.3.0"
implementation "androidx.credentials:credentials-play-services-auth:1.3.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
implementation(platform("ru.rustore.sdk:bom:2025.11.01"))
implementation("ru.rustore.sdk:pay")
implementation "ru.rustore.sdk:review:10.0.0"
implementation "com.vk.id:vkid:2.5.1"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.1.2"
// Android Resolver Dependencies Start
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:7
implementation 'com.applovin.mediation:facebook-adapter:[6.18.0.1]' // Assets/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:fyber-adapter:8.4.2.0' // Assets/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:google-adapter:[23.6.0.1]' // Assets/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:ironsource-adapter:8.6.1.0.0' // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:unityads-adapter:4.12.3.0' // Assets/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4
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
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
**DEPS**}
// Android Resolver Exclusions Start
android {
packagingOptions {
exclude ('/lib/armeabi/*' + '*')
exclude ('/lib/mips/*' + '*')
exclude ('/lib/mips64/*' + '*')
exclude ('/lib/x86/*' + '*')
exclude ('/lib/x86_64/*' + '*')
}
}
// Android Resolver Exclusions End
android {
ndkPath "**NDKPATH**"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
coreLibraryDesugaringEnabled true
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING_OPTIONS**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**

View File

@@ -1,46 +0,0 @@
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
maven{
url "https://artifactory-external.vkpartner.ru/artifactory/vkid-sdk-android/"
}
maven{
url "https://artifactory-external.vkpartner.ru/artifactory/maven/"
}
maven{
url "https://artifactory-external.vkpartner.ru/artifactory/vk-id-captcha/android/"
}
// Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://android-sdk.is.com/" // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
}
mavenLocal()
// Android Resolver Repos End
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
maven {
url "http://sdkck.tuyoo.com/artifactory/tuyoo-component/"
allowInsecureProtocol = true
}
}
}

View File

@@ -1,43 +0,0 @@
#!/bin/bash
# Usage: ./switch-channel.sh <channel>
# channel: googleplay | flexion | rustore
#
# 切换渠道后重新用 Unity 打 Android 包即可打出对应渠道的 APK。
# 脚本会同步AAR、Gradle 配置、AndroidManifest、Java 文件ConfigManager、ChannelHelpers 等)
CHANNEL=${1:-googleplay}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CHANNEL_DIR="$SCRIPT_DIR/$CHANNEL"
PROJ_DIR="$SCRIPT_DIR/../../sdk-intergration"
TYSDK_PLUGINS="$PROJ_DIR/Packages/tysdk/Plugins/Android"
ASSETS_PLUGINS="$PROJ_DIR/Assets/Plugins/Android"
# 校验渠道目录
if [ ! -d "$CHANNEL_DIR" ]; then
echo "ERROR: Unknown channel '$CHANNEL'. Available: googleplay, flexion, rustore"
exit 1
fi
echo "=== Switching to channel: $CHANNEL ==="
# 1. Sync channel AAR to tysdk package (only tuyoosdk, gasdk stays permanently)
echo "[AAR] Syncing tuyoosdk to $TYSDK_PLUGINS"
find "$TYSDK_PLUGINS" -maxdepth 1 -name "tuyoosdk_*.aar" -delete
cp "$CHANNEL_DIR/Plugins/Android/"*.aar "$TYSDK_PLUGINS/" 2>/dev/null
# 2. Sync Gradle + Manifest to Assets/Plugins/Android
echo "[Config] Syncing gradle/manifest to $ASSETS_PLUGINS"
for f in "$CHANNEL_DIR"/*.gradle "$CHANNEL_DIR"/*.properties "$CHANNEL_DIR"/AndroidManifest.xml; do
[ -f "$f" ] && cp "$f" "$ASSETS_PLUGINS/"
done
# 3. Sync Java files to tysdk package
echo "[Java] Syncing ConfigManager + ChannelHelpers to $TYSDK_PLUGINS"
for f in "$CHANNEL_DIR"/*.java; do
[ -f "$f" ] && cp "$f" "$TYSDK_PLUGINS/"
done
echo "=== Done. Channel: $CHANNEL ==="
echo ""
echo "Next: Open Unity and build Android APK."

View File

@@ -0,0 +1,144 @@
# EnjoyPay 本次接入改动记录
## 本次目标
为 Android 测试工程增加 EnjoyPay 渠道切换与打包配置,使 Unity 菜单可以切换到 EnjoyPay 并使用俄罗斯包参数进行打包测试。
## 参数来源
- wiki MCP 文档:
- `https://wiki.bamboogames.top/doc/5oqa5pyv5rkf6yca-dF8FesCMt3`
- `https://wiki.bamboogames.top/doc/sdk-PiMyYd30k5`
- PDF 参数文档:
- `D:/.devup/【128-FishingTravel】【Google俄罗斯】接入支持.pdf`
- Firebase 配置:
- `D:/.devup/google-services (1).json`
- `D:/.devup/google-services (2).json`
两份 `google-services` 文件经 `fc` 对比完全一致,已使用其中一份作为 EnjoyPay 渠道配置。
## 已确认参数
- 渠道名:`EnjoyPay`
- Android 包名:`com.arkgame.ft.ep`
- 客户端支付类型:`enjoypay.global.h5.web`
- clientId`Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMasterRussia`
- Firebase project id`fishing-travel-ep-1ab1e`
- Firebase package name`com.arkgame.ft.ep`
## 代码改动
### 1. 增加 EnjoyPay 切渠道入口
文件:
- `sdk-intergration/Assets/Editor/ChannelBuildTool.cs`
改动:
- `SupportedChannels` 增加 `EnjoyPay`
- 增加菜单 `Tools/Switch Channel/EnjoyPay`
- 增加菜单 `Tools/Build Android (Channel)/EnjoyPay`
- 增加菜单 `Tools/Build Android (Channel)/EnjoyPay Debug`
- `SwitchChannel("EnjoyPay")` 设置 Android bundle id 为 `com.arkgame.ft.ep`
### 2. 增加 EnjoyPay 支付类型映射
文件:
- `sdk-intergration/Packages/tysdk/Runtime/ChannelConfig.cs`
改动:
```csharp
"EnjoyPay" => "enjoypay.global.h5.web",
```
说明:
- 客户端传给 SDK 的 `paytype` 使用 `enjoypay.global.h5.web`
- PDF 中的 `enj` 是后台支付方式配置类型,不作为客户端 `paytype`
### 3. 增加 EnjoyPay 渠道配置目录
目录:
- `ExtraPluginCfgs/Android/enjoypay`
包含:
- `AndroidManifest.xml`
- `ChannelEntry.java`
- `ConfigManager.java`
- `launcherTemplate.gradle`
- `mainTemplate.gradle`
- `settingsTemplate.gradle`
- `gradleTemplate.properties`
- `Files/google-services.json`
- `Plugins/Android/tuyoosdk_1.0.0.aar`
关键配置:
- `AndroidManifest.xml`
- package`com.arkgame.ft.ep`
- `CHANNEL = EnjoyPay`
- `ChannelEntry.java`
- `CHANNEL = "EnjoyPay"`
- 不创建专用 adapter继续走默认 Tuyoo SDK 流程。
- `ConfigManager.java`
- `SDK_CLIENTID = "Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMasterRussia"`
- `Files/google-services.json`
- 来自 `D:/.devup/google-services (1).json`
- package name`com.arkgame.ft.ep`
### 4. Firebase / Gradle 配置核对
Unity Gradle 模板对应关系:
- 项目级 Gradle
- `sdk-intergration/Assets/Plugins/Android/baseProjectTemplate.gradle`
- 当前已有 `com.google.gms.google-services` 插件版本声明。
- 应用模块 Gradle
- `ExtraPluginCfgs/Android/enjoypay/launcherTemplate.gradle`
- 当前已应用 `com.google.gms.google-services` 插件。
- UnityLibrary 模块 Gradle
- `ExtraPluginCfgs/Android/enjoypay/mainTemplate.gradle`
- 当前包含 Firebase Analytics 依赖。
## 未改动 / 延后项
- 当前测试工程不新增 EnjoyPay 商品列表逻辑。
- 正式项目接入时,再从正式项目本地 json table / 商品表构造 SKU。
- 当前不新增 EnjoyPay 专用 Billing adapter。
- 当前不复用 Flexion / RuStore 的 pending、consume、receipt 验签逻辑。
- 后台支付方式无需本次配置。
## 打包前检查
在 Unity 执行 `Tools/Switch Channel/EnjoyPay` 后,检查:
1. `sdk-intergration/Packages/tysdk/Plugins/Android/ChannelEntry.java`
- `CHANNEL = "EnjoyPay"`
2. `sdk-intergration/Packages/tysdk/Plugins/Android/ConfigManager.java`
- `SDK_CLIENTID` 为俄罗斯包 clientId。
3. `sdk-intergration/Assets/Plugins/Android/AndroidManifest.xml`
- package 为 `com.arkgame.ft.ep`
- `CHANNEL = EnjoyPay`
4. `sdk-intergration/Packages/tysdk/Files/google-services.json`
- package name 为 `com.arkgame.ft.ep`
5. `sdk-intergration/Packages/tysdk/Plugins/Android/tuyoosdk_1.0.0.aar`
- 应同步为 EnjoyPay 目录中的新版 AAR。
## 打包步骤
1. 打开 Unity 工程:`sdk-intergration`
2. 执行菜单:`Tools/Switch Channel/EnjoyPay`
3. 执行菜单:`Tools/Build Android (Channel)/EnjoyPay Debug`
4. 安装生成的 APK。
5. 验证初始化、登录、支付拉起和 SDK 回调。
## 已知注意点
- `google-services.json` 的 package name 是 `com.arkgame.ft.ep`,所以 EnjoyPay 包名必须与其一致。
- 已删除历史脚本 `ExtraPluginCfgs/Android/switch-channel.sh`,正式入口是 Unity 菜单中的 `ChannelBuildTool.SwitchChannel`
- 当前仓库内 `SDKTest.cs` 存在行尾状态提示;忽略行尾后无业务差异。

View File

@@ -0,0 +1,172 @@
# EnjoyPay Android 接入开发步骤
## 资料来源
- 主要依据:通过 `wiki` MCP 读取的文档
- `https://wiki.bamboogames.top/doc/5oqa5pyv5rkf6yca-dF8FesCMt3`
- `https://wiki.bamboogames.top/doc/sdk-PiMyYd30k5`
- 辅助参考:`D:/.devup/【Enjoypay三方支付】接入指引.pdf`
- 参数来源:`D:/.devup/【128-FishingTravel】【Google俄罗斯】接入支持.pdf`
## 参数读取规则
- EnjoyPay / Google 俄罗斯包相关参数统一从 `【128-FishingTravel】【Google俄罗斯】接入支持.pdf` 读取。
- 当前代码侧只写入打包和 SDK 初始化必需、且文档中已经明确给出的参数。
- PDF 中覆盖的参数范围包括:
- 基础信息包名、clientId、服务器地址等。
- EnjoyPayEnjoyPay 应用 AppId、商户号、AppSecret、计费点配置、发货通知地址、退款通知地址等。
- AppsFlyer`af_appid``devkey`
- Firebase`google-services.json`、Gradle / Firebase 依赖说明。
- AIHelpApp key、domain、Android platform 配置。
- 设备指纹businessId、secretId、secretKey。
- 广告变现:按平台申请对应参数。
## 已确认结论
- 渠道名:`EnjoyPay`
- Android 包名:`com.arkgame.ft.ep`
- 俄罗斯包 clientId`Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMasterRussia`
- 客户端支付类型:`enjoypay.global.h5.web`
- 支付入口:复用现有 Tuyoo SDK `SDKAPI.payNew` 支付链路。
- SDK 依赖:需要替换为支持 EnjoyPay 的新版 Tuyoo AAR。
- 商品列表:当前测试工程不处理;接入正式项目时再从正式项目本地 json table / 表格数据构造 SKU。
- 服务端发货:复用现有 OrderFunc / `WaitForSvrOrder` / `ConfirmOrder` 流程。
- 不新增独立 EnjoyPay Billing adapter。
- 不复用 Flexion / RuStore 的 pending、consume、receipt 验签逻辑。
- 后台支付方式无需本次配置。
## 开发步骤
### 1. 在 ChannelBuildTool 中增加 EnjoyPay 渠道
文件:
- `sdk-intergration/Assets/Editor/ChannelBuildTool.cs`
改动:
- `SupportedChannels` 增加 `EnjoyPay`
- 增加菜单 `Tools/Switch Channel/EnjoyPay`
- 增加菜单 `Tools/Build Android (Channel)/EnjoyPay`
- 增加菜单 `Tools/Build Android (Channel)/EnjoyPay Debug`
- `SwitchChannel` 中为 `EnjoyPay` 设置 bundle id`com.arkgame.ft.ep`
- 已删除历史脚本 `ExtraPluginCfgs/Android/switch-channel.sh`;真实入口是 `ChannelBuildTool.SwitchChannel`
### 2. 增加 EnjoyPay 渠道配置目录
目录:
- `ExtraPluginCfgs/Android/enjoypay`
需要包含:
- `ChannelEntry.java`
- `ConfigManager.java`
- `AndroidManifest.xml`
- `mainTemplate.gradle`
- `launcherTemplate.gradle`
- `settingsTemplate.gradle`
- `gradleTemplate.properties`
- `Plugins/Android/<新版 Tuyoo AAR>`
- 可选:`Files/google-services.json`,仅在 EnjoyPay / Appsflyer / Firebase 需要独立配置时添加。
关键配置:
- `ChannelEntry.CHANNEL = "EnjoyPay"`
- Manifest package`com.arkgame.ft.ep`
- Manifest 渠道标识:`CHANNEL = EnjoyPay`
- `ConfigManager.SDK_CLIENTID` 使用参数文档中的俄罗斯包 clientId`Android_5.00_tyGuest,facebook.googleplay.0-hall20587.googleplay.FishingMasterRussia`
### 3. 配置 Unity 侧支付类型
文件:
- `sdk-intergration/Packages/tysdk/Runtime/ChannelConfig.cs`
增加映射:
```csharp
"EnjoyPay" => "enjoypay.global.h5.web",
```
说明:
- `enj` 是 SDK 后台“支付方式配置”使用的类型。
- `enj` 不是客户端传给 `SDKAPI.payNew``paytype`
### 4. Java 支付链路保持默认实现
文件:
- `sdk-intergration/Packages/tysdk/Plugins/Android/SDKManager.java`
预期行为:
- EnjoyPay 不新增专用 `IPaymentAdapter`
- `UnityKnowNew(...)` 走现有默认支付链路:
```java
SDKAPI.getIns().payNew(payData, payType, mPay);
```
其中 `payType.paytype` 由 Unity 传入,值为 `enjoypay.global.h5.web`
### 5. 商品列表接入延后到正式项目
当前测试工程:
- 不新增 EnjoyPay 商品列表逻辑。
- 不改 `SDKTest` 的商品列表测试流程。
正式项目接入时:
- EnjoyPay 不调用渠道商品列表查询接口。
- 客户端从正式项目本地 table / json 构造 SKU 列表。
- `productId``ourProductId` 使用同一个商品具体 ID。
- 如果 `TbIAPItemList.ProductId1` 就是 EnjoyPay 支付所需的商品具体 ID则直接使用 `ProductId1` 构造 SKU并复用现有 `SKUDetail.ProdID -> TYSdkFacade.Pay -> SDKAPI.payNew` 链路。
### 6. 替换新版 Tuyoo AAR
预期行为:
- 将支持 EnjoyPay 的新版 Tuyoo AAR 放入:
```text
ExtraPluginCfgs/Android/enjoypay/Plugins/Android/
```
- `ChannelBuildTool.SwitchChannel("EnjoyPay")` 会同步到:
```text
sdk-intergration/Packages/tysdk/Plugins/Android/
```
### 7. 后台与配置系统工作
这些工作在代码外完成:
- 配置 EnjoyPay AppId、商户号、AppSecret。
- 配置 EnjoyPay 计费点。
- 配置途游商品 ID 与 EnjoyPay 计费点 ID 映射。
- 确认 Appsflyer 是否需要与 RuStore 分开统计,以及是否需要独立 Appsflyer 配置。
## 验证清单
1. 在 Unity 执行 `Tools/Switch Channel/EnjoyPay`
2. 确认 `sdk-intergration/Packages/tysdk/Plugins/Android/ChannelEntry.java``CHANNEL = "EnjoyPay"`
3. 确认 Android bundle id 为 `com.arkgame.ft.ep`
4. 确认 `ChannelConfig.PayType` 在 EnjoyPay 渠道下返回 `enjoypay.global.h5.web`
5. 确认 `SDKManager.UnityKnowNew` 使用默认 `SDKAPI.payNew` 链路。
6. 确认 EnjoyPay 未启用 Flexion / RuStore 的 pending、consume、receipt 逻辑。
7. 构建 `Tools/Build Android (Channel)/EnjoyPay Debug`
8. 测试支付拉起与 SDK 回调。
9. 确认 OrderFunc 匹配和发货流程复用现有逻辑。
## 待确认项
- 新版 Tuyoo AAR 的具体文件名和版本。
- EnjoyPay / Google 俄罗斯包是否需要独立 `google-services.json`,以参数 PDF 中最新文件为准。
- 外链支付回跳是否需要新增 Android `scheme / host / path / intent-filter`
- 正式项目接入时的本地商品表字段映射。
- 是否必须展示 RUB 价格;如果需要,确认 RUB 金额来自哪张表、哪个字段。
- 参数 PDF 与 `google-services.json` 中的包名均为 `com.arkgame.ft.ep`;当前代码按 `com.arkgame.ft.ep` 执行。

View File

@@ -19,6 +19,12 @@
# Asset meta data should only be ignored when the corresponding asset is also ignored # Asset meta data should only be ignored when the corresponding asset is also ignored
!/[Aa]ssets/**/*.meta !/[Aa]ssets/**/*.meta
# Channel build generated files
/[Aa]ssets/Plugins/Android/channel_gradle/
/[Aa]ssets/Plugins/Android/channel_gradle.meta
/[Aa]ssets/Plugins/Android/channel_manifest_lib/build/
/[Aa]ssets/Plugins/Android/channel_manifest_lib/build.meta
/AssetsBammbooooooGameShare.PNG /AssetsBammbooooooGameShare.PNG
# Uncomment this line if you wish to ignore the asset store tools plugin # Uncomment this line if you wish to ignore the asset store tools plugin

View File

@@ -58,13 +58,21 @@ public class BuildTool
[MenuItem("Tools/Build Android")] [MenuItem("Tools/Build Android")]
public static void BuildAndroid() public static void BuildAndroid()
{ {
ChannelBuildTool.Build_GooglePlay(); ChannelBuildTool.SwitchChannel("GooglePlay");
BuildAndroid(new BuildParams
{
outPath = "../Bin/tysdkTest_GooglePlay.apk"
});
} }
[MenuItem("Tools/Build Android With Debug")] [MenuItem("Tools/Build Android With Debug")]
public static void BuildAndroidWithDebug() public static void BuildAndroidWithDebug()
{ {
ChannelBuildTool.Build_GooglePlay_Debug(); ChannelBuildTool.SwitchChannel("GooglePlay");
BuildAndroid(new BuildParams
{
outPath = "../Bin/tysdkTest_GooglePlay_debug.apk"
}, BuildOptions.Development | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection);
} }
public static void CIBuildAndroid() public static void CIBuildAndroid()

View File

@@ -1,137 +1,103 @@
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using UnityEditor; using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEngine; using UnityEngine;
public class ChannelBuildTool public class ChannelBuildTool
{ {
public static readonly string[] SupportedChannels = { "GooglePlay", "Rustore", "Flexion" }; private const string ChannelConfigRoot = "../ChannelConfigs/Android";
private const string DefaultChannel = "GooglePlay";
private const string DefaultAndroidBundleIdentifier = "com.arkgame.ft";
private const string ExtraCfgRoot = "../ExtraPluginCfgs/Android";
private const string TysdkPlugins = "Packages/tysdk/Plugins/Android"; private const string TysdkPlugins = "Packages/tysdk/Plugins/Android";
private const string TysdkFiles = "Packages/tysdk/Files"; private const string TysdkFiles = "Packages/tysdk/Files";
private const string AssetsPlugins = "Assets/Plugins/Android"; private const string AssetsPlugins = "Assets/Plugins/Android";
private static readonly string[] GradleFiles = private const string AddressablesContentState = "AddressableAssetsData/Android/addressables_content_state.bin";
{ private const string AndroidAddressablesContentState = "Assets/AddressableAssetsData/Android/addressables_content_state.bin";
"mainTemplate.gradle", private const string DefaultProductName = "Fishing Travel";
"launcherTemplate.gradle", private const string EnjoyPayProductName = "Fishing Travel: Hook & Explore";
"settingsTemplate.gradle", private const string ChannelManifestLib = "channel_manifest_lib";
"gradleTemplate.properties", private const string ChannelGradle = "channel_gradle";
"AndroidManifest.xml" private const string ChannelMetaName = "com.arkgame.ft.channel";
}; private const string EmptyManifest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" />\n";
private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android";
private static readonly XNamespace ToolsNamespace = "http://schemas.android.com/tools";
// ==================== Channel Switching ==================== // ==================== Channel Switching ====================
private static void SwitchChannel(string channel) public static void SwitchChannel(string channel)
{ {
var channelDir = channel.ToLower(); channel = NormalizeChannelName(channel);
var cfgDir = Path.Combine(ExtraCfgRoot, channelDir); var cfgDir = Path.Combine(ChannelConfigRoot, channel.ToLowerInvariant());
if (!Directory.Exists(cfgDir))
{
Debug.LogError($"[ChannelSwitch] Channel config not found: {cfgDir}");
return;
}
Debug.Log($"[ChannelSwitch] Switching to {channel}..."); Debug.Log($"[ChannelSwitch] Switching to {channel}...");
currentChannel = channel; CleanChannelManifestLibBuildArtifacts();
// 1. Sync AAR // 1. Sync root Android config files
var srcAarDir = Path.Combine(cfgDir, "Plugins/Android"); SyncRootAndroidConfigFiles(channel, cfgDir);
if (Directory.Exists(srcAarDir))
{
foreach (var oldAar in Directory.GetFiles(TysdkPlugins, "tuyoosdk_*.aar"))
File.Delete(oldAar);
foreach (var oldJar in Directory.GetFiles(TysdkPlugins, "tuyoosdk_*.jar"))
File.Delete(oldJar);
foreach (var aar in Directory.GetFiles(srcAarDir, "*.aar"))
{
var dest = Path.Combine(TysdkPlugins, Path.GetFileName(aar));
File.Copy(aar, dest, true);
Debug.Log($"[ChannelSwitch] AAR: {aar} → {dest}");
}
}
// 2. Clean up previous channel Java files // 2. Sync channel SDK adapter Java files to tysdk package
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "ChannelHelpers.java")) SyncChannelAdapterFiles(cfgDir);
File.Delete(oldJava);
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "ChannelEntry.java"))
File.Delete(oldJava);
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "*SDKHelper.java"))
File.Delete(oldJava);
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "*SDKService.java"))
File.Delete(oldJava);
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "*SDKAdapter.java"))
File.Delete(oldJava);
var oldConfig = Path.Combine(TysdkPlugins, "ConfigManager.java");
if (File.Exists(oldConfig)) File.Delete(oldConfig);
// 3. Sync Gradle + Manifest // 3. Sync google-services.json
foreach (var fileName in GradleFiles) SyncGoogleServices(cfgDir);
{
var src = Path.Combine(cfgDir, fileName);
if (File.Exists(src))
{
var dest = Path.Combine(AssetsPlugins, fileName);
File.Copy(src, dest, true);
Debug.Log($"[ChannelSwitch] Config: {src} → {dest}");
}
}
// 4. Sync channel Java files to tysdk package // 4. Sync channel plugin files (.aar, .jar, etc.) to tysdk package
foreach (var javaFile in Directory.GetFiles(cfgDir, "*.java")) SyncChannelPluginFiles(cfgDir);
{
var fileName = Path.GetFileName(javaFile);
var dest = Path.Combine(TysdkPlugins, fileName);
File.Copy(javaFile, dest, true);
Debug.Log($"[ChannelSwitch] Java: {javaFile} → {dest}");
}
// 5. Sync google-services.json (fallback to GooglePlay default) // 5. Set Bundle Identifier per channel
var srcGoogleServices = Path.Combine(cfgDir, "Files/google-services.json"); var bundleId = GetBundleId(channel);
if (!File.Exists(srcGoogleServices))
srcGoogleServices = Path.Combine(ExtraCfgRoot, "googleplay/Files/google-services.json");
var destGoogleServices = Path.Combine(TysdkFiles, "google-services.json");
File.Copy(srcGoogleServices, destGoogleServices, true);
Debug.Log($"[ChannelSwitch] google-services.json: {srcGoogleServices} → {destGoogleServices}");
// 6. Set Bundle Identifier per channel
var bundleId = channel switch
{
"Flexion" => "com.arkgame.ft.flexion",
_ => "com.arkgame.ft"
};
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, bundleId); PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, bundleId);
Debug.Log($"[CHANNEL-VERIFY] Bundle Identifier {bundleId}"); Debug.Log($"[ChannelSwitch] Bundle Identifier -> {bundleId}");
// 7. Set runtime channel var productName = channel == "EnjoyPay" ? EnjoyPayProductName : DefaultProductName;
PlayerSettings.productName = productName;
Debug.Log($"[ChannelSwitch] Product Name -> {productName}");
// 6. Set runtime channel
tysdk.ChannelConfig.SetChannel(channel); tysdk.ChannelConfig.SetChannel(channel);
// 7. Set Addressables profile and optional channel content state
SetAddressablesProfile(channel);
SyncAddressablesContentState(channel, cfgDir);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
Debug.Log($"[ChannelSwitch] Done. Channel: {channel}"); Debug.Log($"[ChannelSwitch] Done. Channel: {channel}");
} }
// ==================== Build ==================== // ==================== Menu ====================
private static void RestoreDefaultChannel() [MenuItem("Tools/Channel/Switch Channel/GooglePlay", false, 10000)]
public static void Switch_GooglePlay() => SwitchChannelTo("GooglePlay");
[MenuItem("Tools/Channel/Switch Channel/Flexion", false, 10001)]
public static void Switch_Flexion() => SwitchChannelTo("Flexion");
[MenuItem("Tools/Channel/Switch Channel/Rustore", false, 10002)]
public static void Switch_Rustore() => SwitchChannelTo("Rustore");
[MenuItem("Tools/Channel/Switch Channel/EnjoyPay", false, 10003)]
public static void Switch_EnjoyPay() => SwitchChannelTo("EnjoyPay");
private static void SwitchChannelTo(string channel)
{ {
Debug.Log($"[ChannelBuild] Restore default channel -> {DefaultChannel}"); RestoreChannelWorkspace(skipOnCi: true);
SwitchChannel(DefaultChannel); SwitchChannel(channel);
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, DefaultAndroidBundleIdentifier);
Debug.Log($"[ChannelBuild] Restore bundle identifier -> {DefaultAndroidBundleIdentifier}");
AssetDatabase.SaveAssets();
} }
// ==================== Build ====================
private static void BuildAndroidForChannel(string channel, bool debug) private static void BuildAndroidForChannel(string channel, bool debug)
{ {
try try
{ {
RestoreChannelWorkspace(skipOnCi: true);
CleanChannelManifestLibBuildArtifacts();
SwitchChannel(channel); SwitchChannel(channel);
string fileName = debug ? $"tysdkTest_{channel}_debug.apk" : $"tysdkTest_{channel}.apk"; var fileName = debug ? $"tysdkTest_{channel}_debug.apk" : $"tysdkTest_{channel}.apk";
string outPath = $"../Bin/{fileName}"; var outPath = $"../Bin/{fileName}";
var buildOptions = debug var buildOptions = debug
? BuildOptions.Development | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection ? BuildOptions.Development | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection
: BuildOptions.None; : BuildOptions.None;
@@ -144,7 +110,7 @@ public class ChannelBuildTool
options = buildOptions, options = buildOptions,
}; };
Debug.Log($"[ChannelBuild] Building {channel} {outPath}"); Debug.Log($"[ChannelBuild] Building {channel} -> {outPath}");
var buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions); var buildReport = BuildPipeline.BuildPlayer(buildPlayerOptions);
if (buildReport?.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded) if (buildReport?.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
@@ -159,40 +125,447 @@ public class ChannelBuildTool
} }
finally finally
{ {
RestoreDefaultChannel(); RestoreChannelWorkspace(skipOnCi: true);
AssetDatabase.SaveAssets();
} }
} }
private static string currentChannel = ""; [MenuItem("Tools/Channel/Build Android (Channel)/GooglePlay", false, 10100)]
// ==================== Menu: Switch Channel ====================
[MenuItem("Tools/Switch Channel/GooglePlay")]
public static void Switch_GooglePlay() => SwitchChannel("GooglePlay");
[MenuItem("Tools/Switch Channel/Rustore")]
public static void Switch_Rustore() => SwitchChannel("Rustore");
[MenuItem("Tools/Switch Channel/Flexion")]
public static void Switch_Flexion() => SwitchChannel("Flexion");
// ==================== Menu: Build ====================
[MenuItem("Tools/Build Android (Channel)/GooglePlay")]
public static void Build_GooglePlay() => BuildAndroidForChannel("GooglePlay", false); public static void Build_GooglePlay() => BuildAndroidForChannel("GooglePlay", false);
[MenuItem("Tools/Build Android (Channel)/GooglePlay Debug")] [MenuItem("Tools/Channel/Build Android (Channel)/GooglePlay Debug", false, 10101)]
public static void Build_GooglePlay_Debug() => BuildAndroidForChannel("GooglePlay", true); public static void Build_GooglePlay_Debug() => BuildAndroidForChannel("GooglePlay", true);
[MenuItem("Tools/Build Android (Channel)/Rustore")] [MenuItem("Tools/Channel/Build Android (Channel)/Rustore", false, 10102)]
public static void Build_Rustore() => BuildAndroidForChannel("Rustore", false); public static void Build_Rustore() => BuildAndroidForChannel("Rustore", false);
[MenuItem("Tools/Build Android (Channel)/Rustore Debug")] [MenuItem("Tools/Channel/Build Android (Channel)/Rustore Debug", false, 10103)]
public static void Build_Rustore_Debug() => BuildAndroidForChannel("Rustore", true); public static void Build_Rustore_Debug() => BuildAndroidForChannel("Rustore", true);
[MenuItem("Tools/Build Android (Channel)/Flexion")] [MenuItem("Tools/Channel/Build Android (Channel)/Flexion", false, 10104)]
public static void Build_Flexion() => BuildAndroidForChannel("Flexion", false); public static void Build_Flexion() => BuildAndroidForChannel("Flexion", false);
[MenuItem("Tools/Build Android (Channel)/Flexion Debug")] [MenuItem("Tools/Channel/Build Android (Channel)/Flexion Debug", false, 10105)]
public static void Build_Flexion_Debug() => BuildAndroidForChannel("Flexion", true); public static void Build_Flexion_Debug() => BuildAndroidForChannel("Flexion", true);
[MenuItem("Tools/Channel/Build Android (Channel)/EnjoyPay", false, 10106)]
public static void Build_EnjoyPay() => BuildAndroidForChannel("EnjoyPay", false);
[MenuItem("Tools/Channel/Build Android (Channel)/EnjoyPay Debug", false, 10107)]
public static void Build_EnjoyPay_Debug() => BuildAndroidForChannel("EnjoyPay", true);
[MenuItem("Tools/Channel/Restore Workspace (Git Checkout)", false, 10200)]
public static void RestoreChannelWorkspaceWithGitCheckoutMenu()
{
RestoreChannelWorkspace(skipOnCi: false);
}
private static string NormalizeChannelName(string channel)
{
if (string.IsNullOrEmpty(channel))
return channel;
switch (channel.Trim().ToLowerInvariant())
{
case "googleplay":
return "GooglePlay";
case "flexion":
return "Flexion";
case "enjoypay":
return "EnjoyPay";
case "rustore":
return "Rustore";
default:
return channel.Trim();
}
}
private static string GetBundleId(string channel)
{
return channel switch
{
"Flexion" => "com.arkgame.ft.flexion",
"EnjoyPay" => "com.arkgame.ft.ep",
_ => "com.arkgame.ft"
};
}
private static void RestoreChannelWorkspace(bool skipOnCi)
{
if (skipOnCi && IsCiBuild())
{
Debug.Log("[ChannelSwitch] Skip git checkout restore on CI.");
return;
}
RunCommand("git", "checkout -- .");
RunCommand("git", "clean -fd -- .");
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
Debug.Log("[ChannelSwitch] Workspace restored from git index.");
}
private static bool IsCiBuild()
{
return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("JENKINS_URL")) ||
!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("JENKINS_HOME")) ||
!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("BUILD_NUMBER"));
}
private static void RunCommand(string fileName, string arguments)
{
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (var process = System.Diagnostics.Process.Start(startInfo))
{
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (process.ExitCode != 0)
{
throw new System.InvalidOperationException($"{fileName} {arguments} failed.\n{output}\n{error}");
}
}
}
private static void SyncRootAndroidConfigFiles(string channel, string cfgDir)
{
SyncChannelGradleFiles(channel, cfgDir);
SetMainManifestChannel(channel);
SyncChannelManifestLib(channel, cfgDir);
}
private static void SyncChannelAdapterFiles(string cfgDir)
{
// ChannelAdapterManager, ChannelAdapterBase, ConfigManager and SDKManager
// are fixed platform files and are not copied per channel.
foreach (var oldJava in Directory.GetFiles(TysdkPlugins, "*Adapter.java"))
{
File.Delete(oldJava);
}
if (!Directory.Exists(cfgDir))
{
return;
}
foreach (var javaFile in Directory.GetFiles(cfgDir, "*Adapter.java"))
{
var fileName = Path.GetFileName(javaFile);
var dest = Path.Combine(TysdkPlugins, fileName);
File.Copy(javaFile, dest, true);
Debug.Log($"[ChannelSwitch] Adapter: {javaFile} -> {dest}");
}
}
private static void SyncGoogleServices(string cfgDir)
{
var dest = Path.Combine(TysdkFiles, "google-services.json");
var src = Path.Combine(cfgDir, "Files", "google-services.json");
if (!File.Exists(src))
{
Debug.Log($"[ChannelSwitch] google-services.json not copied. Checked={src}");
return;
}
var destDir = Path.GetDirectoryName(dest);
if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
File.Copy(src, dest, true);
Debug.Log($"[ChannelSwitch] google-services.json -> {dest}");
}
private static void SyncChannelPluginFiles(string cfgDir)
{
var pluginsSrcDir = Path.Combine(cfgDir, "Plugins", "Android");
if (!Directory.Exists(pluginsSrcDir))
{
Debug.Log($"[ChannelSwitch] Plugin directory not found. Checked={pluginsSrcDir}");
return;
}
foreach (var oldPlugin in Directory.GetFiles(TysdkPlugins, "tuyoosdk_*.aar"))
{
File.Delete(oldPlugin);
}
foreach (var pluginFile in Directory.GetFiles(pluginsSrcDir))
{
if (pluginFile.EndsWith(".meta")) continue;
var fileName = Path.GetFileName(pluginFile);
var dest = Path.Combine(TysdkPlugins, fileName);
File.Copy(pluginFile, dest, true);
Debug.Log($"[ChannelSwitch] Plugin: {pluginFile} -> {dest}");
}
}
private static void SyncChannelManifestLib(string channel, string cfgDir)
{
var dest = Path.Combine(AssetsPlugins, ChannelManifestLib, "src");
if (channel == "GooglePlay" || !Directory.Exists(cfgDir))
{
WriteEmptyChannelManifestLib(dest);
Debug.Log($"[ChannelSwitch] Manifest lib reset to empty default. Channel={channel}");
return;
}
var srcManifest = Path.Combine(cfgDir, "AndroidManifest.xml");
if (!File.Exists(srcManifest))
{
throw new FileNotFoundException($"[ChannelSwitch] Channel manifest not found: {srcManifest}");
}
WriteChannelManifestLib(srcManifest, Path.Combine(cfgDir, "res"), dest);
Debug.Log($"[ChannelSwitch] Manifest: {srcManifest} -> {Path.Combine(dest, "main", "AndroidManifest.xml")}");
}
private static void SyncChannelGradleFiles(string channel, string cfgDir)
{
var src = Path.Combine(cfgDir, "Plugins", "Android", "gradle");
var dest = Path.Combine(AssetsPlugins, ChannelGradle);
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
var destMeta = dest + ".meta";
if (File.Exists(destMeta))
{
File.Delete(destMeta);
}
if (!Directory.Exists(src))
{
Debug.Log($"[ChannelSwitch] Gradle increment reset to empty default. Channel={channel}");
return;
}
CopyDirectoryClean(src, dest);
Debug.Log($"[ChannelSwitch] Gradle increment: {src} -> {dest}");
}
private static void SetMainManifestChannel(string channel)
{
var manifestPath = Path.Combine(AssetsPlugins, "AndroidManifest.xml");
if (!File.Exists(manifestPath))
{
throw new FileNotFoundException($"[ChannelSwitch] Main AndroidManifest not found: {manifestPath}");
}
var doc = XDocument.Load(manifestPath);
var root = doc.Root;
var application = root?.Elements().FirstOrDefault(x => x.Name.LocalName == "application");
if (application == null)
{
throw new InvalidDataException($"[ChannelSwitch] Main AndroidManifest application node not found: {manifestPath}");
}
root.SetAttributeValue("package", GetBundleId(channel));
SetMainManifestChannelMeta(application, channel);
SetMainManifestBackupRules(application, channel);
doc.Save(manifestPath);
AssetDatabase.ImportAsset(manifestPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate);
Debug.Log($"[ChannelSwitch] Main manifest channel -> {channel}");
}
private static void SetMainManifestChannelMeta(XElement application, string channel)
{
var channelMeta = application.Elements()
.FirstOrDefault(x => x.Name.LocalName == "meta-data" &&
(string)x.Attribute(AndroidNamespace + "name") == ChannelMetaName);
if (channelMeta == null)
{
channelMeta = new XElement("meta-data",
new XAttribute(AndroidNamespace + "name", ChannelMetaName));
application.Add(channelMeta);
}
channelMeta.SetAttributeValue(AndroidNamespace + "name", ChannelMetaName);
channelMeta.SetAttributeValue(AndroidNamespace + "value", channel);
}
private static void SetMainManifestBackupRules(XElement application, string channel)
{
const string fullBackupContent = "android:fullBackupContent";
var replaceAttr = application.Attribute(ToolsNamespace + "replace");
var replaceItems = replaceAttr?.Value
.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrEmpty(x))
.ToList() ?? new List<string>();
if (channel == "Flexion")
{
application.SetAttributeValue(AndroidNamespace + "fullBackupContent", "@xml/flexion_backup_rules");
if (!replaceItems.Contains(fullBackupContent))
{
replaceItems.Add(fullBackupContent);
}
}
else
{
application.SetAttributeValue(AndroidNamespace + "fullBackupContent", null);
replaceItems.RemoveAll(x => x == fullBackupContent);
}
application.SetAttributeValue(ToolsNamespace + "replace",
replaceItems.Count > 0 ? string.Join(",", replaceItems) : null);
}
private static void WriteEmptyChannelManifestLib(string dest)
{
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
var manifestDir = Path.Combine(dest, "main");
Directory.CreateDirectory(manifestDir);
File.WriteAllText(Path.Combine(manifestDir, "AndroidManifest.xml"), EmptyManifest);
}
private static void CleanChannelManifestLibBuildArtifacts()
{
var buildDir = Path.Combine(AssetsPlugins, ChannelManifestLib, "build");
if (Directory.Exists(buildDir))
{
Directory.Delete(buildDir, true);
}
var buildMeta = buildDir + ".meta";
if (File.Exists(buildMeta))
{
File.Delete(buildMeta);
}
}
private static void WriteChannelManifestLib(string srcManifest, string srcResDir, string dest)
{
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
var manifestDir = Path.Combine(dest, "main");
Directory.CreateDirectory(manifestDir);
File.Copy(srcManifest, Path.Combine(manifestDir, "AndroidManifest.xml"), true);
if (Directory.Exists(srcResDir))
{
CopyDirectoryClean(srcResDir, Path.Combine(manifestDir, "res"));
}
}
private static void CopyDirectoryClean(string src, string dest)
{
if (Directory.Exists(dest))
{
Directory.Delete(dest, true);
}
Directory.CreateDirectory(dest);
foreach (var srcFile in Directory.GetFiles(src, "*", SearchOption.AllDirectories))
{
if (srcFile.EndsWith(".meta")) continue;
var relativePath = srcFile.Substring(src.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var destFile = Path.Combine(dest, relativePath);
var destDir = Path.GetDirectoryName(destFile);
if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
File.Copy(srcFile, destFile, true);
}
}
private static void SetAddressablesProfile(string channel)
{
var settings = AddressableAssetSettingsDefaultObject.Settings;
if (settings == null)
{
Debug.LogWarning("[ChannelSwitch] Addressables settings not found.");
return;
}
var profileName = UsesRustoreAddressablesProfile(channel) ? "arksgameru" : "arksgamecos";
var profileId = settings.profileSettings.GetProfileId(profileName);
if (string.IsNullOrEmpty(profileId))
{
Debug.LogError($"[ChannelSwitch] Addressables profile not found: {profileName}");
return;
}
settings.activeProfileId = profileId;
var remoteLoadPath = settings.RemoteCatalogLoadPath.GetValue(settings);
var remoteBuildPath = settings.RemoteCatalogBuildPath.GetValue(settings);
Debug.Log($"[ChannelSwitch] Addressables profile -> {profileName}, profileId={profileId}, Remote.LoadPath={remoteLoadPath}, Remote.BuildPath={remoteBuildPath}");
}
private static void SyncAddressablesContentState(string channel, string cfgDir)
{
var src = Path.Combine(cfgDir, AddressablesContentState);
if (!File.Exists(src) && UsesRustoreAddressablesContentState(channel))
{
Debug.Log($"[ChannelSwitch] Channel content state not found, try legacy Rustore content state path. Channel={channel}, checked={src}");
src = Path.Combine("../ChannelConfigs", AddressablesContentState);
}
if (!File.Exists(src))
{
ClearAddressablesContentState(channel);
Debug.Log($"[ChannelSwitch] Addressables content state not copied. Channel={channel}, checked={src}");
return;
}
var destDir = Path.GetDirectoryName(AndroidAddressablesContentState);
if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
File.Copy(src, AndroidAddressablesContentState, true);
Debug.Log($"[ChannelSwitch] Addressables content state: {src} -> {AndroidAddressablesContentState}");
}
private static void ClearAddressablesContentState(string channel)
{
if (File.Exists(AndroidAddressablesContentState))
{
File.Delete(AndroidAddressablesContentState);
}
var meta = AndroidAddressablesContentState + ".meta";
if (File.Exists(meta))
{
File.Delete(meta);
}
var dir = Path.GetDirectoryName(AndroidAddressablesContentState);
if (Directory.Exists(dir) && !Directory.EnumerateFileSystemEntries(dir).Any())
{
Directory.Delete(dir);
}
var dirMeta = dir + ".meta";
if (File.Exists(dirMeta) && !Directory.Exists(dir))
{
File.Delete(dirMeta);
}
Debug.Log($"[ChannelSwitch] Addressables content state cleared. Channel={channel}");
}
private static bool UsesRustoreAddressablesContentState(string channel)
{
return channel == "Rustore" || channel == "EnjoyPay";
}
private static bool UsesRustoreAddressablesProfile(string channel)
{
return channel == "Rustore" || channel == "EnjoyPay";
}
} }

View File

@@ -1,48 +1,42 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.arkgame.ft" xmlns:tools="http://schemas.android.com/tools">
xmlns:android="http://schemas.android.com/apk/res/android" <uses-permission android:name="android.permission.VIBRATE" />
package="com.arkgame.ft" <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" /> <application android:extractNativeLibs="true" android:name="com.unity3d.player.TYApp" android:usesCleartextTraffic="true">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <activity android:name="com.unity3d.player.TYUnityActivity" android:theme="@style/UnityThemeSelector">
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <intent-filter>
<application android:extractNativeLibs="true" android:name="com.unity3d.player.TYApp" android:usesCleartextTraffic="true"> <action android:name="android.intent.action.MAIN" />
<activity android:name="com.unity3d.player.TYUnityActivity" <category android:name="android.intent.category.LAUNCHER" />
android:theme="@style/UnityThemeSelector"> </intent-filter>
<intent-filter> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="fishingtraveliae.onelink.me" android:pathPrefix="/jT1Q" />
<intent-filter android:autoVerify="true"> </intent-filter>
<action android:name="android.intent.action.VIEW" /> <intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" android:host="fishingtraveliae.onelink.me" android:pathPrefix="/jT1Q"/> <category android:name="android.intent.category.BROWSABLE" />
</intent-filter> <data android:host="mainactivity" android:scheme="zzft" />
</intent-filter>
<intent-filter> <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
<action android:name="android.intent.action.VIEW" /> <meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="true" />
<category android:name="android.intent.category.DEFAULT" /> <meta-data android:name="google_analytics_default_allow_ad_storage" android:value="true" />
<category android:name="android.intent.category.BROWSABLE" /> <meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="true" />
<data android:host="mainactivity" android:scheme="zzft" /> <meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="true" />
</intent-filter> </activity>
<meta-data android:name="com.arkgame.ft.channel" android:value="tw" />
<meta-data android:name="unityplayer.UnityActivity" android:value="true" /> <meta-data android:name="CHANNEL" android:value="tw" />
</application>
<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="true" /> <queries>
<meta-data android:name="google_analytics_default_allow_ad_storage" android:value="true" /> <provider android:authorities="com.facebook.katana.provider.PlatformProvider" />
<meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="true" /> <!-- allows app to access Facebook app features -->
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="true" /> <provider android:authorities="com.facebook.orca.provider.PlatformProvider" />
<!-- allows sharing to Messenger app -->
</activity> <intent>
<meta-data android:name="CHANNEL" android:value="GooglePlay" /> <action android:name="android.intent.action.MAIN" />
</application> </intent>
<queries> </queries>
<provider android:authorities="com.facebook.katana.provider.PlatformProvider" /> <!-- allows app to access Facebook app features --> </manifest>
<provider android:authorities="com.facebook.orca.provider.PlatformProvider" /> <!-- allows sharing to Messenger app -->
<intent>
<action android:name="android.intent.action.MAIN"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ad9923efa7716e24a9eacfdeaba25735
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
apply plugin: 'com.android.library'
evaluationDependsOn(':unityLibrary')
def unityAndroid = project(':unityLibrary').extensions.getByName('android')
android {
namespace 'com.arkgame.ft.channelmanifest'
compileSdkVersion unityAndroid.compileSdkVersion
buildToolsVersion unityAndroid.buildToolsVersion
defaultConfig {
minSdkVersion unityAndroid.defaultConfig.minSdkVersion.apiLevel
}
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6b1864b7c14c2874290c96b313f016da
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0c6d5688cdd2a434fae148ccc076e1ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4835437bf5f97d548abb765917c4873c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
</application>
</manifest>

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f8101f5c11c019f45843819fd0a0aec8
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,8 +3,8 @@ apply plugin: 'com.google.gms.google-services'
dependencies { dependencies {
implementation project(':unityLibrary') implementation project(':unityLibrary')
implementation(platform("com.google.firebase:firebase-bom:33.6.0")) implementation platform('com.google.firebase:firebase-bom:34.6.0')
implementation("com.google.firebase:firebase-analytics") implementation 'com.google.firebase:firebase-analytics'
} }
android { android {
@@ -17,7 +17,6 @@ android {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_11 sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11
} }
defaultConfig { defaultConfig {

View File

@@ -18,6 +18,7 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':channel_manifest_lib')
api "com.android.support:support-v4:28.0.0" api "com.android.support:support-v4:28.0.0"
api "com.android.support:appcompat-v7:28.0.0" api "com.android.support:appcompat-v7:28.0.0"
@@ -60,18 +61,24 @@ dependencies {
implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1" implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
// Android Resolver Dependencies Start // Android Resolver Dependencies Start
implementation 'androidx.recyclerview:recyclerview:1.2.1' // Assets/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:9
implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:7 implementation 'com.android.installreferrer:installreferrer:2.1' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:7
implementation 'com.applovin.mediation:bytedance-adapter:8.1.0.3.0' // Assets/MaxSdk/Mediation/ByteDance/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:facebook-adapter:[6.18.0.1]' // Assets/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8 implementation 'com.applovin.mediation:facebook-adapter:[6.18.0.1]' // Assets/MaxSdk/Mediation/Facebook/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:fyber-adapter:8.4.2.0' // Assets/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4 implementation 'com.applovin.mediation:fyber-adapter:8.4.2.0' // Assets/MaxSdk/Mediation/Fyber/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:google-adapter:[23.6.0.1]' // Assets/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5 implementation 'com.applovin.mediation:google-adapter:[23.6.0.1]' // Assets/MaxSdk/Mediation/Google/Editor/Dependencies.xml:5
implementation 'com.applovin.mediation:ironsource-adapter:8.6.1.0.0' // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8 implementation 'com.applovin.mediation:ironsource-adapter:8.6.1.0.0' // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:mintegral-adapter:17.1.61.0' // Assets/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:8
implementation 'com.applovin.mediation:moloco-adapter:4.9.0.0' // Assets/MaxSdk/Mediation/Moloco/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:unityads-adapter:4.12.3.0' // Assets/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4 implementation 'com.applovin.mediation:unityads-adapter:4.12.3.0' // Assets/MaxSdk/Mediation/UnityAds/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:vungle-adapter:7.4.2.2' // Assets/MaxSdk/Mediation/Vungle/Editor/Dependencies.xml:4 implementation 'com.applovin.mediation:vungle-adapter:7.4.2.2' // Assets/MaxSdk/Mediation/Vungle/Editor/Dependencies.xml:4
implementation 'com.applovin.mediation:yandex-adapter:8.1.0.0' // Assets/MaxSdk/Mediation/Yandex/Editor/Dependencies.xml:4
implementation 'com.applovin:applovin-sdk:13.5.1' // Assets/MaxSdk/AppLovin/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 implementation 'com.appsflyer:af-android-sdk:6.17.3' // Assets/AppsFlyer/Editor/AppsFlyerDependencies.xml:5
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.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.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 // Android Resolver Dependencies End
**DEPS**} **DEPS**}
@@ -108,3 +115,9 @@ android {
**IL_CPP_BUILD_SETUP** **IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP** **SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES** **EXTERNAL_SOURCES**
def channelUnityProjectPath = $/**DIR_UNITYPROJECT**/$.replace("\\", "/")
def channelGradleMain = new File(channelUnityProjectPath, 'Assets/Plugins/Android/channel_gradle/main.gradle')
if (channelGradleMain.exists()) {
apply from: channelGradleMain
}

View File

@@ -10,6 +10,15 @@ pluginManagement {
include ':launcher', ':unityLibrary' include ':launcher', ':unityLibrary'
**INCLUDES** **INCLUDES**
def channelUnityProjectPath = $/**DIR_UNITYPROJECT**/$.replace("\\", "/")
include ':channel_manifest_lib'
project(':channel_manifest_lib').projectDir = new File(channelUnityProjectPath, 'Assets/Plugins/Android/channel_manifest_lib')
def channelGradleSettings = new File(channelUnityProjectPath, 'Assets/Plugins/Android/channel_gradle/settings.gradle')
if (channelGradleSettings.exists()) {
apply from: channelGradleSettings
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories { repositories {
@@ -18,9 +27,18 @@ dependencyResolutionManagement {
mavenCentral() mavenCentral()
// Android Resolver Repos Start // Android Resolver Repos Start
def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/")
maven {
url "https://sdk.tapjoy.com/" // Packages/com.unity.package-offerwall/Editor/TJPluginDependencies.xml:9
}
maven {
url "https://artifact.bytedance.com/repository/pangle" // Assets/MaxSdk/Mediation/ByteDance/Editor/Dependencies.xml:8
}
maven { maven {
url "https://android-sdk.is.com/" // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8 url "https://android-sdk.is.com/" // Assets/MaxSdk/Mediation/IronSource/Editor/Dependencies.xml:8
} }
maven {
url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" // Assets/MaxSdk/Mediation/Mintegral/Editor/Dependencies.xml:8
}
mavenLocal() mavenLocal()
// Android Resolver Repos End // Android Resolver Repos End
flatDir { flatDir {

View File

@@ -73,8 +73,8 @@ public class SDKTest : MonoBehaviour
IConfig config = new Config(serializer); IConfig config = new Config(serializer);
GContext.container.RegisterInstance<IConfig>(config); GContext.container.RegisterInstance<IConfig>(config);
Debug.Log("SDKTest Start"); Debug.Log("SDKTest Start");
UpdateChannelTitle(); ClearChannelTitle();
//=============================================================================== //===============================================================================
//====================== Test Server =========================== //====================== Test Server ===========================
// UnityBridgeFunc.UnityResetServerUrl("https://128-hwsfsdk-sdk-test01.qijihdhk.com"); // UnityBridgeFunc.UnityResetServerUrl("https://128-hwsfsdk-sdk-test01.qijihdhk.com");
@@ -86,8 +86,9 @@ public class SDKTest : MonoBehaviour
TYSdkFacade.OnInitComplete += (success, msg) => TYSdkFacade.OnInitComplete += (success, msg) =>
{ {
_messageTxt.text = success ? $"Init OK: {msg}" : $"Init Failed: {msg}"; _messageTxt.text = success ? $"Init OK: {msg}" : $"Init Failed: {msg}";
}; UpdateChannelTitle();
_loginGuestBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwGuest)); };
_loginGuestBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwGuest));
_loginGoogleBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwGoogle)); _loginGoogleBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwGoogle));
_loginFbBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwFacebook)); _loginFbBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwFacebook));
_loginVkBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwVkid)); _loginVkBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwVkid));
@@ -111,11 +112,11 @@ public class SDKTest : MonoBehaviour
_signoutBtn.onClick.AddListener(OnSignoutBtn); _signoutBtn.onClick.AddListener(OnSignoutBtn);
_fbShareLinkBtn.onClick.AddListener(OnFBShareLinkBtn); _fbShareLinkBtn.onClick.AddListener(OnFBShareLinkBtn);
_mfbShareLinkBtn.onClick.AddListener(OnMessengerShareLinkBtn); _mfbShareLinkBtn.onClick.AddListener(OnMessengerShareLinkBtn);
_reviewBtn.onClick.AddListener(OnReview); _reviewBtn.onClick.AddListener(OnReview);
Debug.Log("SDKTest Started"); Debug.Log("SDKTest Started");
} }
void Init() void Init()
{ {
_messageTxt.text = "Initializing..."; _messageTxt.text = "Initializing...";
TYSdkFacade.Instance.Init(); TYSdkFacade.Instance.Init();
@@ -140,19 +141,21 @@ public class SDKTest : MonoBehaviour
GContext.container.Resolve<IConfig>().Set("PAY_VERIFY_URL", "https://192.168.9.91:7036/api/FlexionCallback"); GContext.container.Resolve<IConfig>().Set("PAY_VERIFY_URL", "https://192.168.9.91:7036/api/FlexionCallback");
} }
private void UpdateChannelTitle() private void UpdateChannelTitle()
{ {
#if UNITY_ANDROID #if UNITY_ANDROID
using (var cls = new AndroidJavaClass("com.unity3d.player.ChannelEntry")) string channel = ChannelConfig.Channel;
{ Debug.Log($"[SDKTest] Channel from ChannelConfig: {channel}");
string channel = cls.GetStatic<string>("CHANNEL"); _titleText.text = $" {channel}";
Debug.Log($"[SDKTest] Channel from Java: {channel}"); #endif
_titleText.text = $" {channel}"; }
}
#endif private void ClearChannelTitle()
} {
_titleText.text = string.Empty;
int retryAttempt; }
int retryAttempt;
public void InitializeRewardedAds() public void InitializeRewardedAds()
{ {
@@ -211,10 +214,11 @@ public class SDKTest : MonoBehaviour
{ {
_messageTxt.text = ""; _messageTxt.text = "";
if (ChannelConfig.Channel.Equals("Flexion")) var channel = ChannelConfig.Channel;
{ if (string.Equals(channel, "Flexion"))
sku = new SKUDetail {
{ sku = new SKUDetail
{
productId = "com.arkgame.ft.pack0199", productId = "com.arkgame.ft.pack0199",
ourProductId = "com.arkgame.ft.pack0199", ourProductId = "com.arkgame.ft.pack0199",
price = "1.99", price = "1.99",
@@ -223,13 +227,31 @@ public class SDKTest : MonoBehaviour
type = "inapp" type = "inapp"
}; };
} }
if (sku == null){ else if (string.Equals(channel, "EnjoyPay"))
_messageTxt.text = "no product"; {
return; sku = new SKUDetail
{
productId = "comarkgameftpack0199",
ourProductId = "comarkgameftpack0199",
price = "1.99",
price_amount_micros = "1990000",
title = "EnjoyPay Test Pack",
type = "inapp"
};
} }
if (sku == null){
#if !UNITY_EDITOR && UNITY_IOS _messageTxt.text = "no product";
sku.productId = "comarkgameftpack0199"; return;
}
if (!TYSdkFacade.IsLoggedIn)
{
_messageTxt.text = "not logged in";
return;
}
#if !UNITY_EDITOR && UNITY_IOS
sku.productId = "comarkgameftpack0199";
sku.title = "商店-体力直充1.99" ; sku.title = "商店-体力直充1.99" ;
sku.price = "1.99"; sku.price = "1.99";
#endif #endif
@@ -240,18 +262,18 @@ public class SDKTest : MonoBehaviour
// purchaseInfo["customData"] ="{\"BuyType\":{\"type\":1,\"ID\":100502,\"key\":null,\"endDay\":0,\"extraPurcheData\":null,\"IsHide\":false},\"IAPID\":302,\"ItemData\":[{\"id\":1005,\"curCount\":22728,\"count\":260}],\"ShowType\":8,\"CONTENT_ID\":\"com.arkgame.ft.pack0499\",\"PRICE\":4.99,\"CURRENCY\":\"USD\",\"ProdPrice\":4.99,\"extraPurcheData\":{\"purchase_count\":9,\"trade_name\":\"商店-钻石直充4.99\",\"trade_type\":\"商店-钻石直充\",\"usd_price\":4.99,\"iap_id\":302,\"drop_id\":1005,\"trade_group\":\"商店\",\"item_1005\":260},\"orderId\":null,\"time\":\"5/20/2026 3:59:07 AM\"}"; // purchaseInfo["customData"] ="{\"BuyType\":{\"type\":1,\"ID\":100502,\"key\":null,\"endDay\":0,\"extraPurcheData\":null,\"IsHide\":false},\"IAPID\":302,\"ItemData\":[{\"id\":1005,\"curCount\":22728,\"count\":260}],\"ShowType\":8,\"CONTENT_ID\":\"com.arkgame.ft.pack0499\",\"PRICE\":4.99,\"CURRENCY\":\"USD\",\"ProdPrice\":4.99,\"extraPurcheData\":{\"purchase_count\":9,\"trade_name\":\"商店-钻石直充4.99\",\"trade_type\":\"商店-钻石直充\",\"usd_price\":4.99,\"iap_id\":302,\"drop_id\":1005,\"trade_group\":\"商店\",\"item_1005\":260},\"orderId\":null,\"time\":\"5/20/2026 3:59:07 AM\"}";
purchaseInfo["userId"] = TYSdkFacade.TYAccountInfo.strUserId; purchaseInfo["userId"] = TYSdkFacade.TYAccountInfo.strUserId;
PaymentInfo payResult = await TYSdkFacade.Instance.Pay(sku, 1, sku.ProdPrice, purchaseInfo); PaymentInfo payResult = await TYSdkFacade.Instance.Pay(sku, 1, sku.ProdPrice, purchaseInfo);
_messageTxt.text = "\n" + $"Success : {payResult.isSuccess}"; _messageTxt.text = "\n" + $"Success : {payResult.isSuccess}";
_messageTxt.text += "\n" + $"message : {payResult.msg}"; _messageTxt.text += "\n" + $"message : {payResult.msg}";
_messageTxt.text += "\n" + $"orderId : {payResult.orderId}"; _messageTxt.text += "\n" + $"orderId : {payResult.orderId}";
_messageTxt.text += "\n" + $"storeOrderId : {payResult.storeOrderId}"; _messageTxt.text += "\n" + $"storeOrderId : {payResult.storeOrderId}";
var consumeToken = payResult.Receipt?.PurchaseToken ?? payResult.purchaseToken; var consumeToken = payResult.Receipt?.PurchaseToken ?? payResult.purchaseToken;
_messageTxt.text += "\n" + $"hasToken : {!string.IsNullOrEmpty(consumeToken)}"; _messageTxt.text += "\n" + $"hasToken : {!string.IsNullOrEmpty(consumeToken)}";
if (!string.IsNullOrEmpty(consumeToken)) if (!string.IsNullOrEmpty(consumeToken))
{ {
Debug.Log($"[SDKTest] Consume purchase token after pay result token={consumeToken}"); Debug.Log($"[SDKTest] Consume purchase token after pay result token={consumeToken}");
UnityBridgeFunc.ConsumePurchase(consumeToken); UnityBridgeFunc.ConsumePurchase(consumeToken);
_messageTxt.text += "\nconsume requested"; _messageTxt.text += "\nconsume requested";
} }
} }

View File

@@ -0,0 +1,83 @@
package com.unity3d.player;
import android.app.Activity;
import android.util.Log;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ChannelAdapterBase implements SDKManager.IChannelSDKAdapter {
private static final String TAG = "ChannelAdapterBase";
protected Activity activity;
protected String channel = "GooglePlay";
final ChannelAdapterBase setup(Activity activity, String channel) {
this.activity = activity;
this.channel = channel;
return this;
}
@Override
public final SDKManager.IChannelSDKAdapter loadConfig() {
applyConfig();
return this;
}
protected void applyConfig() {
applyConfigObject(createConfig());
}
@Override
public String getChannel() {
return channel;
}
@Override
public void init() {
SDKManager.initDefault(activity);
}
protected void applyConfigObject(Object source) {
if (source == null) {
return;
}
for (Field sourceField : source.getClass().getDeclaredFields()) {
sourceField.setAccessible(true);
try {
Object value = sourceField.get(source);
if (value == null) {
continue;
}
Field targetField = ConfigManager.class.getDeclaredField(sourceField.getName());
targetField.setAccessible(true);
targetField.set(null, value);
Log.i(TAG, "apply config: " + sourceField.getName() + "=" + value);
} catch (NoSuchFieldException ignored) {
try {
Object value = sourceField.get(source);
Log.w(TAG, "ignore extra config: " + sourceField.getName() + "=" + value);
} catch (Exception e) {
Log.w(TAG, "ignore extra config: " + sourceField.getName());
}
} catch (Exception e) {
Log.e(TAG, "apply config failed: " + sourceField.getName(), e);
}
}
}
private Object createConfig() {
try {
Class<?> configClass = Class.forName(this.getClass().getName() + "$Config");
Constructor<?> constructor = configClass.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (ClassNotFoundException ignored) {
return null;
} catch (Exception e) {
Log.e(TAG, "create config failed: " + this.getClass().getName(), e);
return null;
}
}
}

View File

@@ -0,0 +1,32 @@
fileFormatVersion: 2
guid: 045e7597d96519641ae97bf542955d42
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Android: Android
second:
enabled: 1
settings: {}
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
package com.unity3d.player;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.Constructor;
public class ChannelAdapterManager {
private static final String TAG = "ChannelAdapterManager";
private static final String DEFAULT_CHANNEL = "GooglePlay";
private static final String META_CHANNEL = "com.arkgame.ft.channel";
private static SDKManager.IChannelSDKAdapter adapter;
public static SDKManager.IChannelSDKAdapter createSdkAdapter(Activity activity) {
String channel = readChannel(activity);
adapter = createAdapter(channel);
if (adapter instanceof ChannelAdapterBase) {
((ChannelAdapterBase) adapter).setup(activity, channel);
}
return adapter;
}
public static String normalizeSkuList(String msg) {
if (adapter == null) {
return msg;
}
return adapter.normalizeSkuList(msg);
}
private static SDKManager.IChannelSDKAdapter createAdapter(String channel) {
if (TextUtils.isEmpty(channel) || DEFAULT_CHANNEL.equals(channel)) {
return new ChannelAdapterBase();
}
SDKManager.IChannelSDKAdapter adapter = tryCreateAdapter(
"com.unity3d.player." + channel + "Adapter");
if (adapter != null) {
return adapter;
}
return new ChannelAdapterBase();
}
private static SDKManager.IChannelSDKAdapter tryCreateAdapter(String adapterClassName) {
try {
Class<?> adapterClass = Class.forName(adapterClassName);
Constructor<?> constructor = adapterClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object instance = constructor.newInstance();
if (instance instanceof SDKManager.IChannelSDKAdapter) {
return (SDKManager.IChannelSDKAdapter) instance;
}
Log.e(TAG, "adapter does not implement IChannelSDKAdapter: " + adapterClassName);
} catch (Exception e) {
Log.e(TAG, "create adapter failed: " + adapterClassName, e);
}
return null;
}
private static String readChannel(Activity activity) {
if (activity == null) {
return DEFAULT_CHANNEL;
}
try {
ApplicationInfo appInfo = activity.getPackageManager().getApplicationInfo(
activity.getPackageName(),
PackageManager.GET_META_DATA);
Bundle metaData = appInfo.metaData;
if (metaData == null) {
return DEFAULT_CHANNEL;
}
String value = metaData.getString(META_CHANNEL);
return TextUtils.isEmpty(value) ? DEFAULT_CHANNEL : value;
} catch (Exception e) {
Log.e(TAG, "read channel failed", e);
return DEFAULT_CHANNEL;
}
}
}

View File

@@ -1,13 +0,0 @@
package com.unity3d.player;
public class ChannelEntry {
public static final String CHANNEL = "GooglePlay";
public static SDKManager.IChannelSDKAdapter createSDKAdapter() {
return null;
}
public static String normalizeSkuList(String msg) {
return msg;
}
}

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;
@@ -57,7 +59,17 @@ public class SDKManager {
// Channel SDK adapter // Channel SDK adapter
public interface IChannelSDKAdapter { public interface IChannelSDKAdapter {
void init(Activity activity); default IChannelSDKAdapter loadConfig() {
return this;
}
String getChannel();
void init();
default String normalizeSkuList(String msg) {
return msg;
}
} }
public interface IPaymentAdapter { public interface IPaymentAdapter {
@@ -73,16 +85,26 @@ public class SDKManager {
} }
public interface IPendingPurchaseAdapter { public interface IPendingPurchaseAdapter {
String getPendingPurchases(); void queryPendingPurchases();
} }
public interface IBillingLifecycleAdapter { public interface IBillingLifecycleAdapter {
void refreshBilling(); void refreshBilling();
} }
public interface IChannelInfoAdapter {
String getStorePageUri(Activity activity);
}
public interface IReviewAdapter {
void review(Activity activity);
}
private static IChannelSDKAdapter sdkAdapter; private static IChannelSDKAdapter sdkAdapter;
public static void queryProductsByIDs(String productIds) { public static void queryProductsByIDs(String productIds) {
Log.i(TAG, "[FLEXION-SKU-PRICE] queryProductsByIDs productIds=" + productIds
+ " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getName() : "null"));
if (sdkAdapter instanceof IProductQueryAdapter) { if (sdkAdapter instanceof IProductQueryAdapter) {
((IProductQueryAdapter) sdkAdapter).queryProducts(productIds); ((IProductQueryAdapter) sdkAdapter).queryProducts(productIds);
return; return;
@@ -91,21 +113,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();
} }
@@ -113,33 +145,58 @@ public class SDKManager {
public static void CancelBillingFlow() { public static void CancelBillingFlow() {
Log.w(TAG, "CancelBillingFlow"); Log.w(TAG, "CancelBillingFlow");
// FlexionSDKAdapter.cancelPurchase();
} }
// ------------------------------------------------------------------------- public static String GetStorePageUri() {
// Init — with channel helper support if (sdkAdapter instanceof IChannelInfoAdapter) {
// ------------------------------------------------------------------------- try {
return ((IChannelInfoAdapter) sdkAdapter).getStorePageUri(UnityPlayer.currentActivity);
} catch (Throwable e) {
Log.w(TAG, "GetStorePageUri exception: " + e.getMessage());
}
}
return "";
}
public static void InitSDK(Activity activity, String ch) { public static boolean IsGooglePlayServicesAvailable() {
channel = ChannelEntry.CHANNEL; try {
sdkAdapter = ChannelEntry.createSDKAdapter(); Activity activity = UnityPlayer.currentActivity;
if (activity == null) {
Log.w(TAG, "IsGooglePlayServicesAvailable: activity is null");
return false;
}
Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel int resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(activity);
+ " fromCSharp=" + ch boolean available = resultCode == ConnectionResult.SUCCESS;
+ " adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getSimpleName() : "default")); Log.i(TAG, "IsGooglePlayServicesAvailable: available=" + available + " resultCode=" + resultCode);
return available;
// Sync channel to C# ChannelConfig } catch (Exception e) {
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME, Log.w(TAG, "IsGooglePlayServicesAvailable exception: " + e.getMessage());
"SetChannelFromNative", channel); return false;
if (sdkAdapter != null) {
sdkAdapter.init(activity);
} else {
initDefault(activity);
} }
} }
private static void initDefault(Activity activity) { // -------------------------------------------------------------------------
// Init with channel helper support
// -------------------------------------------------------------------------
public static void InitSDK(Activity activity, String ch) {
sdkAdapter = ChannelAdapterManager.createSdkAdapter(activity);
sdkAdapter.loadConfig();
channel = sdkAdapter.getChannel();
Log.i(TAG, "[CHANNEL-VERIFY] Java InitSDK channel=" + channel
+ " fromCSharp=" + ch
+ " adapter=" + sdkAdapter.getClass().getName());
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
"SetChannelFromNative", channel);
sdkAdapter.init();
}
static void initDefault(Activity activity) {
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
SDKAPI.getIns().lightModeEnable(); SDKAPI.getIns().lightModeEnable();
try { try {
@@ -228,6 +285,7 @@ public class SDKManager {
public static void UnityResetServerUrl(String url) { public static void UnityResetServerUrl(String url) {
SDKLog.i("UnityResetServerUrl : " + url); SDKLog.i("UnityResetServerUrl : " + url);
SetDNSIPsEnable(false);
SDKAPI.getIns().updateServer(url); SDKAPI.getIns().updateServer(url);
} }
@@ -416,7 +474,7 @@ public class SDKManager {
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Payment channel dispatch // Payment channel dispatch
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
public static void UnityKnow(String productId, String productName, String productCount, public static void UnityKnow(String productId, String productName, String productCount,
@@ -431,6 +489,15 @@ public class SDKManager {
public static void UnityKnowNew(String productId, String productPrice, String productName, public static void UnityKnowNew(String productId, String productPrice, String productName,
String productCount, String prodorderId, String appInfo, String productCount, String prodorderId, String appInfo,
String ptype) { String ptype) {
String currentChannel = getCurrentChannelForLog();
SDKLog.i("[SDKManager.UnityKnowNew] channel=" + currentChannel
+ ", adapter=" + (sdkAdapter != null ? sdkAdapter.getClass().getName() : "null")
+ ", ptype=" + ptype
+ ", productId=" + productId
+ ", productPrice=" + productPrice
+ ", productName=" + productName
+ ", productCount=" + productCount
+ ", orderId=" + prodorderId);
if (sdkAdapter instanceof IPaymentAdapter) { if (sdkAdapter instanceof IPaymentAdapter) {
((IPaymentAdapter) sdkAdapter).pay(UnityPlayer.currentActivity, productId, productPrice, productName, productCount, prodorderId, appInfo); ((IPaymentAdapter) sdkAdapter).pay(UnityPlayer.currentActivity, productId, productPrice, productName, productCount, prodorderId, appInfo);
return; return;
@@ -448,11 +515,23 @@ public class SDKManager {
payData.payReq = mPayReq; payData.payReq = mPayReq;
PayType payType = new PayType(); PayType payType = new PayType();
payType.paytype = ptype; payType.paytype = ptype;
SDKLog.i("[SDKManager.UnityKnowNew] payNew payType.paytype=" + payType.paytype);
SDKAPI.getIns().payNew(payData, payType, mPay); SDKAPI.getIns().payNew(payData, payType, mPay);
} }
private static String getCurrentChannelForLog() {
try {
if (sdkAdapter != null && !TextUtils.isEmpty(sdkAdapter.getChannel())) {
return sdkAdapter.getChannel();
}
} catch (Throwable ignored) {
}
return TextUtils.isEmpty(channel) ? "GooglePlay" : channel;
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// thirdExtend with channel helper support // thirdExtend with channel helper support
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
public static void thirdExtend() { public static void thirdExtend() {
@@ -480,7 +559,7 @@ public class SDKManager {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("code", code); result.put("code", code);
if (code == SDKCallBack.Extend.EXTEND_CODE_SUCCESS) { if (code == SDKCallBack.Extend.EXTEND_CODE_SUCCESS) {
result.put("respObj", ChannelEntry.normalizeSkuList(msg)); result.put("respObj", ChannelAdapterManager.normalizeSkuList(msg));
} else { } else {
Log.e(TAG, "thirdExtend failed: " + msg); Log.e(TAG, "thirdExtend failed: " + msg);
result.put("thirdExtend_errStr", msg); result.put("thirdExtend_errStr", msg);
@@ -515,13 +594,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());
@@ -556,12 +635,19 @@ public class SDKManager {
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Review channel dispatch // Review channel dispatch
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
public static void Review() { public static void Review() {
Log.w(TAG, "[ReviewDebug] Review called channel=" + channel);
if ("Rustore".equals(channel)) { if ("Rustore".equals(channel)) {
RuStoreReview(); RuStoreReview();
} else if ("Flexion".equals(channel)) {
if (sdkAdapter instanceof IReviewAdapter) {
((IReviewAdapter) sdkAdapter).review(UnityPlayer.currentActivity);
} else {
Log.w(TAG, "Review skipped: no Flexion review adapter");
}
} else { } else {
GooglePlayReview(); GooglePlayReview();
} }
@@ -655,4 +741,34 @@ public class SDKManager {
Log.e(TAG, "Messenger ShareDialog can not show"); Log.e(TAG, "Messenger ShareDialog can not show");
} }
} }
public static void SetDNSIPsEnable(boolean enable) {
if ("Flexion".equals(channel)) {
return;
}
if(enable) {
SetDNSIPs(true, new String[]{
"34.111.1.231",
"2600:1901:0:c31b::"
});
}
else{
SetDNSIPs(false, new String[0]);
}
}
private static void SetDNSIPs(boolean enable, String[] ips) {
InvokeSDKAPIStaticMethod("enableDoh", new Class[]{boolean.class}, new Object[]{enable});
InvokeSDKAPIStaticMethod("setHttpDnsIps", new Class[]{String[].class}, new Object[]{ips});
}
private static void InvokeSDKAPIStaticMethod(String methodName, Class<?>[] parameterTypes, Object[] args) {
try {
SDKAPI.class.getMethod(methodName, parameterTypes).invoke(null, args);
} catch (NoSuchMethodException ignored) {
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@@ -12,6 +12,7 @@ public class TYApp extends Application {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
SDKAPI.getIns().onApplicationCreate(this); SDKAPI.getIns().onApplicationCreate(this);
SDKManager.SetDNSIPsEnable(true);
} }
@Override @Override
@@ -19,4 +20,5 @@ public class TYApp extends Application {
super.attachBaseContext(base); super.attachBaseContext(base);
SDKAPI.getIns().onAttachBaseContext(base, this); SDKAPI.getIns().onAttachBaseContext(base, this);
} }
} }

View File

@@ -8,6 +8,7 @@ namespace tysdk
{ {
"Rustore" => "rustore.global.app", "Rustore" => "rustore.global.app",
"Flexion" => "flexion.global.app", "Flexion" => "flexion.global.app",
"EnjoyPay" => "enjoypay.global.h5.web",
_ => "googleiab.global.app" _ => "googleiab.global.app"
}; };

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;
@@ -59,6 +59,7 @@ namespace tysdk
#elif UNITY_ANDROID #elif UNITY_ANDROID
var pType = ChannelConfig.PayType; var pType = ChannelConfig.PayType;
Debug.Log($"[TYSdkFacade::Pay] Channel={ChannelConfig.Channel}, PayType={pType}, prodId={prodId}, price={prodPrice}, orderId={orderId}");
var task = TYSDKCallbackManager.Instance.RegisterCallback<PaymentInfo>(TimeSpan.FromSeconds(90)); var task = TYSDKCallbackManager.Instance.RegisterCallback<PaymentInfo>(TimeSpan.FromSeconds(90));
try try
{ {
@@ -68,7 +69,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 +109,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 +204,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: