11 Commits

Author SHA1 Message Date
62046f4bdc update:合并代码 2026-02-10 19:30:48 +08:00
a6db6d438c update:更新Rustore的Review 2026-02-02 15:23:24 +08:00
f233be2646 update:更新skuDetail 2026-01-30 20:58:54 +08:00
4636ddc74e update:rustore 支付 2026-01-30 20:37:19 +08:00
39a3505ce9 update:回到正式服 2026-01-30 20:27:34 +08:00
5410f88c6c update:更新Tysdk 2026-01-30 20:13:46 +08:00
b3869d3488 update:当前支付成功 2026-01-29 18:02:40 +08:00
9f1379c465 update:支付调整 2026-01-29 10:22:25 +08:00
333d8fa6f7 update:登陆检测链接更新 2026-01-28 16:40:53 +08:00
29bac5b580 update:成功vk登陆 2026-01-28 16:12:19 +08:00
13b931eb8e update:更新继承aar 和 集成 2026-01-27 20:04:02 +08:00
3423 changed files with 213086 additions and 1260307 deletions

10
.gitignore vendored
View File

@@ -5,13 +5,3 @@ sdk-intergration/.vscode
sdk-intergration/'
sdk-intergration/Assets/.claude/settings.json
sdk-intergration/.idea
.worktrees/*
.docs/*
.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

@@ -1,9 +0,0 @@
<?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

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

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
// 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

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

View File

@@ -1,49 +0,0 @@
# 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

@@ -1,12 +0,0 @@
<?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

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

@@ -1,39 +0,0 @@
{
"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

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

View File

@@ -1,21 +0,0 @@
<?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

@@ -1,100 +0,0 @@
{
"project_info": {
"project_number": "481260393117",
"project_id": "fishing-travel",
"storage_bucket": "fishing-travel.firebasestorage.app"
},
"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-vfn1jueugjqjach8kgc0kfi54g085q6l.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.arkgame.ft",
"certificate_hash": "53fd6ff623712411792a4665163e4cbf4a7a22ec"
}
},
{
"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
},
{
"client_id": "481260393117-88n0v3ht8ashhk0r5ri47hced7o5qj62.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.arkgame.ft",
"app_store_id": "6505145935"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:481260393117:android:eafec4a060b0224907fa74",
"android_client_info": {
"package_name": "com.arkgame.ft.flexion"
}
},
"oauth_client": [
{
"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
},
{
"client_id": "481260393117-88n0v3ht8ashhk0r5ri47hced7o5qj62.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.arkgame.ft",
"app_store_id": "6505145935"
}
}
]
}
}
}
],
"configuration_version": "1"
}

View File

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

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

View File

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

View File

@@ -1,8 +0,0 @@
<?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

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

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

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

View File

@@ -1,12 +0,0 @@
<?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

@@ -1,39 +0,0 @@
{
"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

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

View File

@@ -1,16 +0,0 @@
# 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

@@ -1,24 +0,0 @@
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,7 +0,0 @@
{
"permissions": {
"allow": [
"WebSearch"
]
}
}

View File

@@ -1,144 +0,0 @@
# 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

@@ -1,172 +0,0 @@
# 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

@@ -1,154 +0,0 @@
# 合并记录: dev_rustore 测试内容 → dev_flexion
**日期:** 2026-04-30
**源分支:** dev_rustore (c105818)
**目标分支:** dev_flexion (66aa7a9)
---
## 已合并的文件
### 1. sdk-intergration/Assets/Scripts/SDKTest.cs
新增 VK 登录/绑定/检查 和 Review 测试按钮及事件处理:
**新增字段:**
- `_loginVkBtn` — VK 登录按钮
- `_linkVkBtn` — VK 绑定按钮
- `_checkVkBtn` — VK 检查绑定按钮
- `_reviewBtn` — 应用评价按钮
**新增监听 (Start):**
```csharp
_loginVkBtn.onClick.AddListener(() => OnLogin(EAccoutType.hwVkid));
_linkVkBtn.onClick.AddListener(OnLinkVkId);
_checkVkBtn.onClick.AddListener(OnCheckVKId);
_reviewBtn.onClick.AddListener(OnReview);
```
**新增方法:**
```csharp
private async void OnLinkVkId() // VK 绑定 → TYSdkFacade.Instance.LinkAccount(EAccoutType.hwVkid)
private async void OnCheckVKId() // VK 检查 → TYSdkFacade.Instance.LinkCheck(EAccoutType.hwVkid)
private void OnReview() // 评价 → UnityBridgeFunc.Review()
```
### 2. sdk-intergration/Assets/Scenes/SampleScene.unity
直接从 dev_rustore 检出完整文件,包含新增 UI 按钮 GameObject:
- VK Login 按钮
- VK Link 按钮
- VK Check 按钮
- Review 按钮
### 3. sdk-intergration/ProjectSettings/ProjectSettings.asset
修复构建错误,将 `AndroidMinSdkVersion` 从 25 提升到 26:
- **原因:** `tuyoosdk_1.0.0.aar` 声明 minSdkVersion 26,低于此版本会 manifest 合并失败
- **影响:** 全局生效,所有渠道统一 minSdk 26
---
## 跳过的文件 (dev_flexion 已有更完善的实现)
### TYSdkModel.cs — 跳过
- dev_rustore: 硬编码 `AccountInfo.hwPayType = "rustore.global.app"`, SKUDetail 写死 Rustore 格式
- dev_flexion: 使用 `ChannelConfig.Channel` 动态区分渠道,同时兼容 Google/Rustore/Flexion
- **理由:** dev_flexion 的 `ChannelConfig` 抽象更成熟,无需降级
### TYSdkFacade.cs — 跳过
- dev_rustore: 硬编码 `Channel = "hwVkid"`, 超时 120s
- dev_flexion: 已有 `Channel => ChannelConfig.Channel`, 动态 `LoginTimeoutSeconds`
- **理由:** dev_flexion 已包含 VK 映射 `{"vk.global.app", EAccoutType.hwVkid}`, 且抽象更优
### TYSdkFacade_Pay.cs — 跳过
- dev_rustore: `var pType = tysdk.AccountInfo.hwPayType` (硬编码 rustore)
- dev_flexion: `var pType = ChannelConfig.PayType` (动态配置)
- **理由:** dev_flexion 的方案已覆盖 rustore 支付类型
### UnityBridgeFunc.cs — 跳过
- dev_rustore: 仅代码格式重排,无功能变更
- dev_flexion: 已包含 `Review()` 等方法声明
---
## 构建错误修复
```
Error: uses-sdk:minSdkVersion 25 cannot be smaller than version 26
declared in library [:tuyoosdk_1.0.0:]
```
**修复:** `ProjectSettings.asset``AndroidMinSdkVersion: 25``26`
**替代方案分析:**
- `tools:overrideLibrary="com.tuyoo.gamesdk"` (rustore AndroidManifest 已有) 只能绕过 manifest 合并冲突
- 无法绕过 gradle 层 `processReleaseMainManifest` task 的 minSdkVersion 检查
- 改 ProjectSettings 是唯一可行方案
---
## SKU 列表格式统一
**问题:** Rustore 和 GooglePlay 返回的商品列表 JSON 字段不同C# 端用同一个 `SKUDetail` 类反序列化。
**方案:** 在 Java 端 `ChannelHelpers.normalizeSkuList()` 中统一格式C# 端不做渠道判断。
### 数据流
```
SDK API → defaultThirdExtend() → ChannelHelpers.normalizeSkuList(msg) → Unity SKUListResult → DeserializeObject<List<SKUDetail>>
```
### 变更文件
#### 1. ExtraPluginCfgs/Android/rustore/ChannelHelpers.java — 新增 normalizeSkuList
```java
// 将 Rustore 原始格式转为统一格式:
// price: "199" (戈比) → "₽1.99"
// 新增 price_amount_micros: "199" * 10000 = "1990000"
// 新增 price_currency_code: ← currency 字段
```
#### 2. ExtraPluginCfgs/Android/googleplay/ChannelHelpers.java — 新增 normalizeSkuList
```java
// GooglePlay 格式已是标准,直接原样返回
public static String normalizeSkuList(String msg) { return msg; }
```
#### 3. ExtraPluginCfgs/Android/flexion/ChannelHelpers.java — 新增 normalizeSkuList
```java
// Flexion 有自己的 SDKHelper.queryProducts(),不走 defaultThirdExtend编译需要方法存在
public static String normalizeSkuList(String msg) { return msg; }
```
#### 4. SDKManager.java — defaultThirdExtend 调用 normalizeSkuList
```java
// 回调成功时: result.put("respObj", ChannelHelpers.normalizeSkuList(msg));
```
#### 5. TYSdkModel.cs — SKUDetail 简化
```csharp
// 去掉 Normalize() 方法和 normalized 字段
// 属性直接用统一字段:
// ProdPrice => float.Parse(price_amount_micros) / 1000000
// ProdPriceStr => price
```
#### 6. TYSdkFacade_Pay.cs — SKUListResult 去掉 Normalize 调用
```csharp
// 反序列化后直接赋值,不再调 sku.Normalize()
```
### 扩展新渠道
添加新渠道时只需:
1. 创建 `ExtraPluginCfgs/Android/{channel}/ChannelHelpers.java`
2. 实现 `normalizeSkuList()` 将该渠道 SKU JSON 转为统一格式
3. C# 端无需改动

View File

@@ -19,12 +19,6 @@
# Asset meta data should only be ignored when the corresponding asset is also ignored
!/[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
# Uncomment this line if you wish to ignore the asset store tools plugin
@@ -99,4 +93,3 @@ crashlytics-build.properties
/Assets/StreamingAssets/*.bundle
/Assets/StreamingAssets/*.bundle.meta
.claude/*

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7863556d88b814e09ba9cfc75a91d655
guid: da76ae4f9ba92124087139583d0dd929
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -1,71 +0,0 @@
using System;
using System.Collections.Generic;
namespace AppsFlyerSDK
{
public enum MediationNetwork : ulong
{
GoogleAdMob = 1,
IronSource = 2,
ApplovinMax = 3,
Fyber = 4,
Appodeal = 5,
Admost = 6,
Topon = 7,
Tradplus = 8,
Yandex = 9,
ChartBoost = 10,
Unity = 11,
ToponPte = 12,
Custom = 13,
DirectMonetization = 14
}
public static class AdRevenueScheme
{
/**
* code ISO 3166-1 format
*/
public const string COUNTRY = "country";
/**
* ID of the ad unit for the impression
*/
public const string AD_UNIT = "ad_unit";
/**
* Format of the ad
*/
public const string AD_TYPE = "ad_type";
/**
* ID of the ad placement for the impression
*/
public const string PLACEMENT = "placement";
}
/// <summary>
// Data class representing ad revenue information.
//
// @property monetizationNetwork The name of the network that monetized the ad.
// @property mediationNetwork An instance of MediationNetwork representing the mediation service used.
// @property currencyIso4217Code The ISO 4217 currency code describing the currency of the revenue.
// @property eventRevenue The amount of revenue generated by the ad.
/// </summary>
public class AFAdRevenueData
{
public string monetizationNetwork { get; private set; }
public MediationNetwork mediationNetwork { get; private set; }
public string currencyIso4217Code { get; private set; }
public double eventRevenue { get; private set; }
public AFAdRevenueData(string monetization, MediationNetwork mediation, string currency, double revenue)
{
monetizationNetwork = monetization;
mediationNetwork = mediation;
currencyIso4217Code = currency;
eventRevenue = revenue;
}
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 49e1906ae949e4bfea400bd1da9f7e39
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using System.Collections;
using UnityEngine;
public class AFAdRevenueEvent {
/**
*Pre-defined keys for non-mandatory dictionary
*Code ISO 3166-1 format
**/
public const string COUNTRY = "country";
/**
*ID of the ad unit for the impression
**/
public const string AD_UNIT = "ad_unit";
/**
*Format of the ad
**/
public const string AD_TYPE = "ad_type";
/**
*ID of the ad placement for the impression
**/
public const string PLACEMENT = "placement";
/**
*Provided by Facebook Audience Network only, and will be reported to publishers
*approved by Facebook Audience Network within the closed beta
**/
public const string ECPM_PAYLOAD = "ecpm_payload";
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b73b301ad8b6f4b45809980800a9358a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -28,6 +28,7 @@ public class AFInAppEvents {
public const string LOCATION_CHANGED = "af_location_changed";
public const string LOCATION_COORDINATES = "af_location_coordinates";
public const string ORDER_ID = "af_order_id";
public const string GA = "af_ga_event";
/**
* Event Parameter Name
* **/

0
sdk-intergration/Assets/AppsFlyer/AFMiniJSON.cs Executable file → Normal file
View File

View File

@@ -10,19 +10,24 @@ namespace AppsFlyerSDK
}
/// <summary>
/// Purchase details class matching Android SDK AFPurchaseDetails
//
/// </summary>
public class AFPurchaseDetailsAndroid
{
public AFPurchaseType purchaseType { get; private set; }
public string purchaseToken { get; private set; }
public string productId { get; private set; }
public string price { get; private set; }
public string currency { get; private set; }
public AFPurchaseDetailsAndroid(AFPurchaseType type, String purchaseToken, String productId)
public AFPurchaseDetailsAndroid(AFPurchaseType type, String purchaseToken, String productId, String price, String currency)
{
this.purchaseType = type;
this.purchaseToken = purchaseToken;
this.productId = productId;
this.price = price;
this.currency = currency;
}
}

View File

@@ -4,33 +4,26 @@ using System.Collections.Generic;
namespace AppsFlyerSDK
{
/// <summary>
/// Purchase type enum matching iOS SDK AFSDKPurchaseType
/// </summary>
public enum AFSDKPurchaseType
{
Subscription,
OneTimePurchase
}
/// <summary>
/// Purchase details class matching iOS SDK AFSDKPurchaseDetails
//
/// </summary>
public class AFSDKPurchaseDetailsIOS
{
public string productId { get; private set; }
public string price { get; private set; }
public string currency { get; private set; }
public string transactionId { get; private set; }
public AFSDKPurchaseType purchaseType { get; private set; }
private AFSDKPurchaseDetailsIOS(string productId, string transactionId, AFSDKPurchaseType purchaseType)
private AFSDKPurchaseDetailsIOS(string productId, string price, string currency, string transactionId)
{
this.productId = productId;
this.price = price;
this.currency = currency;
this.transactionId = transactionId;
this.purchaseType = purchaseType;
}
public static AFSDKPurchaseDetailsIOS Init(string productId, string transactionId, AFSDKPurchaseType purchaseType)
public static AFSDKPurchaseDetailsIOS Init(string productId, string price, string currency, string transactionId)
{
return new AFSDKPurchaseDetailsIOS(productId, transactionId, purchaseType);
return new AFSDKPurchaseDetailsIOS(productId, price, currency, transactionId);
}
}

View File

@@ -6,7 +6,8 @@ namespace AppsFlyerSDK
{
public class AppsFlyer : MonoBehaviour
{
public static readonly string kAppsFlyerPluginVersion = "6.17.7";
public static readonly string kAppsFlyerPluginVersion = "6.14.3";
public static string CallBackObjectName = null;
private static EventHandler onRequestResponse;
private static EventHandler onInAppResponse;
@@ -332,19 +333,6 @@ namespace AppsFlyerSDK
}
}
/// <summary>
/// Logs ad revenue data along with additional parameters if provided.
/// </summary>
/// <param name = "adRevenueData" >instance of AFAdRevenueData containing ad revenue information.</param>
/// <param name = "additionalParameters" >An optional map of additional parameters to be logged with ad revenue data. This can be null if there are no additional parameters.</param>
public static void logAdRevenue(AFAdRevenueData adRevenueData, Dictionary<string, string> additionalParameters)
{
if (instance != null)
{
instance.logAdRevenue(adRevenueData, additionalParameters);
}
}
/// <summary>
/// Manually record the location of the user.
/// </summary>
@@ -778,12 +766,12 @@ namespace AppsFlyerSDK
}
// V2
public static void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject)
public static void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> extraEventValues, MonoBehaviour gameObject)
{
if (instance != null && instance is IAppsFlyerIOSBridge)
{
IAppsFlyerIOSBridge appsFlyeriOSInstance = (IAppsFlyerIOSBridge)instance;
appsFlyeriOSInstance.validateAndSendInAppPurchase(details, purchaseAdditionalDetails, gameObject);
appsFlyeriOSInstance.validateAndSendInAppPurchase(details, extraEventValues, gameObject);
}
}
@@ -797,12 +785,12 @@ namespace AppsFlyerSDK
}
// V2
public static void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject)
public static void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> additionalParameters, MonoBehaviour gameObject)
{
if (instance != null && instance is IAppsFlyerAndroidBridge)
{
IAppsFlyerAndroidBridge appsFlyerAndroidInstance = (IAppsFlyerAndroidBridge)instance;
appsFlyerAndroidInstance.validateAndSendInAppPurchase(details, purchaseAdditionalDetails, gameObject);
appsFlyerAndroidInstance.validateAndSendInAppPurchase(details, additionalParameters, gameObject);
}
}

View File

@@ -0,0 +1,178 @@
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;
using System;
namespace AppsFlyerSDK
{
public class AppsFlyerAdRevenue : MonoBehaviour
{
public static readonly string kAppsFlyerAdRevenueVersion = "6.14.3";
#if UNITY_ANDROID && !UNITY_EDITOR
private static AndroidJavaClass appsFlyerAndroid = new AndroidJavaClass("com.appsflyer.unity.afunityadrevenuegenericplugin.AdRevenueUnityWrapper");
#endif
public static void start()
{
#if UNITY_IOS && !UNITY_EDITOR
_start();
#elif UNITY_ANDROID && !UNITY_EDITOR
using(AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
using(AndroidJavaObject cls_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
AndroidJavaObject cls_Application = cls_Activity.Call<AndroidJavaObject>("getApplication");
appsFlyerAndroid.CallStatic("start", cls_Application);
}
}
#else
#endif
}
public static void setIsDebug(bool isDebug)
{
#if UNITY_IOS && !UNITY_EDITOR
_setIsDebugAdrevenue(isDebug);
#elif UNITY_ANDROID && !UNITY_EDITOR
#else
#endif
}
public static void logAdRevenue(string monetizationNetwork,
AppsFlyerAdRevenueMediationNetworkType mediationNetwork,
double eventRevenue,
string revenueCurrency,
Dictionary<string, string> additionalParameters)
{
#if UNITY_IOS && !UNITY_EDITOR
_logAdRevenue(monetizationNetwork, mediationNetwork, eventRevenue, revenueCurrency, AFMiniJSON.Json.Serialize(additionalParameters));
#elif UNITY_ANDROID && !UNITY_EDITOR
int mediationNetworkAndroid = setMediationNetworkTypeAndroid(mediationNetwork);
if (mediationNetworkAndroid == -1)
{
Debug.Log("Please choose a valid mediationNetwork");
} else
{
appsFlyerAndroid.CallStatic("logAdRevenue",
monetizationNetwork,
mediationNetworkAndroid,
revenueCurrency,
eventRevenue,
convertDictionaryToJavaMap(additionalParameters));
}
#else
#endif
}
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void _start();
[DllImport("__Internal")]
private static extern void _setIsDebugAdrevenue(bool isDebug);
[DllImport("__Internal")]
private static extern void _logAdRevenue(string monetizationNetwork,
AppsFlyerAdRevenueMediationNetworkType mediationNetwork,
double eventRevenue,
string revenueCurrency,
string additionalParameters);
#elif UNITY_ANDROID && !UNITY_EDITOR
#else
#endif
private static int setMediationNetworkTypeAndroid(AppsFlyerAdRevenueMediationNetworkType mediationNetwork)
{
switch (mediationNetwork)
{
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeIronSource:
return 0;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeApplovinMax:
return 1;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob:
return 2;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeFyber:
return 3;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeAppodeal:
return 4;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeAdmost:
return 5;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeTopon:
return 6;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeTradplus:
return 7;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeYandex:
return 8;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeChartBoost:
return 9;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeUnity:
return 10;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypeCustomMediation:
return 11;
case AppsFlyerAdRevenueMediationNetworkType.AppsFlyerAdRevenueMediationNetworkTypedirectMonetization:
return 12;
default:
return -1;
}
}
private static AndroidJavaObject convertDictionaryToJavaMap(Dictionary<string, string> dictionary)
{
AndroidJavaObject map = new AndroidJavaObject("java.util.HashMap");
IntPtr putMethod = AndroidJNIHelper.GetMethodID(map.GetRawClass(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jvalue[] val;
if (dictionary != null)
{
foreach (var entry in dictionary)
{
val = AndroidJNIHelper.CreateJNIArgArray(new object[] { entry.Key, entry.Value });
AndroidJNI.CallObjectMethod(map.GetRawObject(), putMethod, val);
AndroidJNI.DeleteLocalRef(val[0].l);
AndroidJNI.DeleteLocalRef(val[1].l);
}
}
return map;
}
}
public enum AppsFlyerAdRevenueMediationNetworkType
{
AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob = 1,
AppsFlyerAdRevenueMediationNetworkTypeIronSource = 2,
AppsFlyerAdRevenueMediationNetworkTypeApplovinMax = 3,
AppsFlyerAdRevenueMediationNetworkTypeFyber = 4,
AppsFlyerAdRevenueMediationNetworkTypeAppodeal = 5,
AppsFlyerAdRevenueMediationNetworkTypeAdmost = 6,
AppsFlyerAdRevenueMediationNetworkTypeTopon = 7,
AppsFlyerAdRevenueMediationNetworkTypeTradplus = 8,
AppsFlyerAdRevenueMediationNetworkTypeYandex = 9,
AppsFlyerAdRevenueMediationNetworkTypeChartBoost = 10,
AppsFlyerAdRevenueMediationNetworkTypeUnity = 11,
AppsFlyerAdRevenueMediationNetworkTypeCustomMediation = 12,
AppsFlyerAdRevenueMediationNetworkTypedirectMonetization = 13
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 644005ac4602e4b78ad3a8d4d29e329d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -398,23 +398,7 @@ namespace AppsFlyerSDK
public void setConsentData(AppsFlyerConsent appsFlyerConsent)
{
#if !UNITY_EDITOR
string isUserSubjectToGDPR = appsFlyerConsent.isUserSubjectToGDPR?.ToString().ToLower() ?? "null";
string hasConsentForDataUsage = appsFlyerConsent.hasConsentForDataUsage?.ToString().ToLower() ?? "null";
string hasConsentForAdsPersonalization = appsFlyerConsent.hasConsentForAdsPersonalization?.ToString().ToLower() ?? "null";
string hasConsentForAdStorage = appsFlyerConsent.hasConsentForAdStorage?.ToString().ToLower() ?? "null";
appsFlyerAndroid.CallStatic("setConsentData", isUserSubjectToGDPR, hasConsentForDataUsage, hasConsentForAdsPersonalization, hasConsentForAdStorage);
#endif
}
/// <summary>
/// Logs ad revenue data along with additional parameters if provided.
/// <param name = "adRevenueData" >instance of AFAdRevenueData containing ad revenue information.</param>
/// <param name = "additionalParameters" >An optional map of additional parameters to be logged with ad revenue data. This can be null if there are no additional parameters.</param>
public void logAdRevenue(AFAdRevenueData adRevenueData, Dictionary<string, string> additionalParameters)
{
#if !UNITY_EDITOR
appsFlyerAndroid.CallStatic("logAdRevenue", adRevenueData.monetizationNetwork, getMediationNetwork(adRevenueData.mediationNetwork), adRevenueData.currencyIso4217Code, adRevenueData.eventRevenue, convertDictionaryToJavaMap(additionalParameters));
appsFlyerAndroid.CallStatic("setConsentData", appsFlyerConsent.isUserSubjectToGDPR, appsFlyerConsent.hasConsentForDataUsage, appsFlyerConsent.hasConsentForAdsPersonalization);
#endif
}
@@ -505,11 +489,11 @@ namespace AppsFlyerSDK
/// An af_purchase event with the relevant values will be automatically sent if the validation is successful.
/// </summary>
/// <param name="details">AFPurchaseDetailsAndroid instance.</param>
/// <param name="purchaseAdditionalDetails">purchaseAdditionalDetails Freehand parameters to be sent with the purchase (if validated).</param>
public void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject)
/// <param name="additionalParameters">additionalParameters Freehand parameters to be sent with the purchase (if validated).</param>
public void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> additionalParameters, MonoBehaviour gameObject)
{
#if !UNITY_EDITOR
appsFlyerAndroid.CallStatic("validateAndTrackInAppPurchaseV2", (int)details.purchaseType, details.purchaseToken, details.productId, convertDictionaryToJavaMap(purchaseAdditionalDetails), gameObject ? gameObject.name : null);
appsFlyerAndroid.CallStatic("validateAndTrackInAppPurchaseV2", (int)details.purchaseType, details.purchaseToken, details.productId, details.price, details.currency, convertDictionaryToJavaMap(additionalParameters), gameObject ? gameObject.name : null);
#endif
}
@@ -765,65 +749,6 @@ namespace AppsFlyerSDK
return emailsCryptType;
}
/// <summary>
/// Internal Helper Method.
/// </summary>
private static AndroidJavaObject getMediationNetwork(MediationNetwork mediationNetwork)
{
AndroidJavaClass mediationNetworkEnumClass = new AndroidJavaClass("com.appsflyer.MediationNetwork");
AndroidJavaObject mediationNetworkObject;
switch (mediationNetwork)
{
case MediationNetwork.IronSource:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("IRONSOURCE");
break;
case MediationNetwork.ApplovinMax:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("APPLOVIN_MAX");
break;
case MediationNetwork.GoogleAdMob:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("GOOGLE_ADMOB");
break;
case MediationNetwork.Fyber:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("FYBER");
break;
case MediationNetwork.Appodeal:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("APPODEAL");
break;
case MediationNetwork.Admost:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("ADMOST");
break;
case MediationNetwork.Topon:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("TOPON");
break;
case MediationNetwork.Tradplus:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("TRADPLUS");
break;
case MediationNetwork.Yandex:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("YANDEX");
break;
case MediationNetwork.ChartBoost:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("CHARTBOOST");
break;
case MediationNetwork.Unity:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("UNITY");
break;
case MediationNetwork.ToponPte:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("TOPON_PTE");
break;
case MediationNetwork.Custom:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("CUSTOM_MEDIATION");
break;
case MediationNetwork.DirectMonetization:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("DIRECT_MONETIZATION_NETWORK");
break;
default:
mediationNetworkObject = mediationNetworkEnumClass.GetStatic<AndroidJavaObject>("NONE");
break;
}
return mediationNetworkObject;
}
/// <summary>
/// Internal Helper Method.
/// </summary>
@@ -846,6 +771,9 @@ namespace AppsFlyerSDK
return map;
}
}
#endif
}

View File

@@ -10,45 +10,26 @@ namespace AppsFlyerSDK
// This class should be used to notify and record the user's applicability
// under GDPR, their general consent to data usage, and their consent to personalized
// advertisements based on user data.
// Note that the consent for data usage and ads personalization pair is only applicable when the user is
// subject to GDPR guidelines. Therefore, the following factory methods should be used accordingly:
// - Use [forGDPRUser] when the user is subject to GDPR.
// - Use [forNonGDPRUser] when the user is not subject to GDPR.
/// ## Properties:
/// - `isUserSubjectToGDPR` (optional) - Indicates whether GDPR regulations apply to the user.
/// This may also serve as a general compliance flag for other regional regulations.
/// - `hasConsentForDataUsage` (optional) - Indicates whether the user consents to the processing
/// of their data for advertising purposes.
/// - `hasConsentForAdsPersonalization` (optional) - Indicates whether the user consents to the
/// use of their data for personalized advertising.
/// - `hasConsentForAdStorage` (optional) - Indicates whether the user consents to ad-related storage access.
///
/// **Usage Example:**
/// ```csharp
/// var consent = new AppsFlyerConsent(
/// isUserSubjectToGDPR: true,
/// hasConsentForDataUsage: true,
/// hasConsentForAdsPersonalization: false,
/// hasConsentForAdStorage: true
/// );
/// **Deprecated APIs:**
/// - `ForGDPRUser(...)` and `ForNonGDPRUser(...)` should no longer be used.
/// - Use `new AppsFlyerConsent(...)` instead with relevant consent fields.
///
// @property isUserSubjectToGDPR Indicates whether GDPR regulations apply to the user (true if the user is
// a subject of GDPR). It also serves as a flag for compliance with relevant aspects of DMA regulations.
// @property hasConsentForDataUsage Indicates whether the user has consented to the use of their data for advertising
// purposes under both GDPR and DMA guidelines (true if the user has consented, nullable if not subject to GDPR).
// @property hasConsentForAdsPersonalization Indicates whether the user has consented to the use of their data for
// personalized advertising within the boundaries of GDPR and DMA rules (true if the user has consented to
// personalized ads, nullable if not subject to GDPR).
/// </summary>
public class AppsFlyerConsent
{
public bool? isUserSubjectToGDPR { get; private set; }
public bool? hasConsentForDataUsage { get; private set; }
public bool? hasConsentForAdsPersonalization { get; private set; }
public bool? hasConsentForAdStorage { get; private set; }
public bool isUserSubjectToGDPR { get; private set; }
public bool hasConsentForDataUsage { get; private set; }
public bool hasConsentForAdsPersonalization { get; private set; }
public AppsFlyerConsent( bool? isUserSubjectToGDPR = null, bool? hasConsentForDataUsage = null, bool? hasConsentForAdsPersonalization = null, bool? hasConsentForAdStorage = null)
{
this.isUserSubjectToGDPR = isUserSubjectToGDPR;
this.hasConsentForDataUsage = hasConsentForDataUsage;
this.hasConsentForAdsPersonalization = hasConsentForAdsPersonalization;
this.hasConsentForAdStorage = hasConsentForAdStorage;
}
[Obsolete("Use the new constructor with optional booleans instead.")]
private AppsFlyerConsent(bool isGDPR, bool hasForDataUsage, bool hasForAdsPersonalization)
{
isUserSubjectToGDPR = isGDPR;
@@ -56,16 +37,15 @@ namespace AppsFlyerSDK
hasConsentForAdsPersonalization = hasForAdsPersonalization;
}
[Obsolete("Use new AppsFlyerConsent(...) instead.")]
public static AppsFlyerConsent ForGDPRUser(bool hasConsentForDataUsage, bool hasConsentForAdsPersonalization)
{
return new AppsFlyerConsent(true, hasConsentForDataUsage, hasConsentForAdsPersonalization);
}
[Obsolete("Use new AppsFlyerConsent(...) instead.")]
public static AppsFlyerConsent ForNonGDPRUser()
{
return new AppsFlyerConsent(false);
return new AppsFlyerConsent(false, false, false);
}
}
}

View File

@@ -1,426 +0,0 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using System;
namespace AppsFlyerSDK
{
public interface IAppsFlyerPurchaseRevenueDataSource
{
Dictionary<string, object> PurchaseRevenueAdditionalParametersForProducts(HashSet<object> products, HashSet<object> transactions);
}
public interface IAppsFlyerPurchaseRevenueDataSourceStoreKit2
{
Dictionary<string, object> PurchaseRevenueAdditionalParametersStoreKit2ForProducts(HashSet<object> products, HashSet<object> transactions);
}
public class AppsFlyerPurchaseRevenueBridge : MonoBehaviour
{
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void RegisterUnityPurchaseRevenueParamsCallback(Func<string, string, string> callback);
[DllImport("__Internal")]
private static extern void RegisterUnityPurchaseRevenueParamsCallbackSK2(Func<string, string, string> callback);
#endif
private static IAppsFlyerPurchaseRevenueDataSource _dataSource;
private static IAppsFlyerPurchaseRevenueDataSourceStoreKit2 _dataSourceSK2;
public static void RegisterDataSource(IAppsFlyerPurchaseRevenueDataSource dataSource)
{
_dataSource = dataSource;
#if UNITY_IOS && !UNITY_EDITOR
RegisterUnityPurchaseRevenueParamsCallback(GetAdditionalParameters);
#elif UNITY_ANDROID && !UNITY_EDITOR
using (AndroidJavaClass jc = new AndroidJavaClass("com.appsflyer.unity.PurchaseRevenueBridge"))
{
jc.CallStatic("setUnityBridge", new UnityPurchaseRevenueBridgeProxy());
}
#endif
}
public static void RegisterDataSourceStoreKit2(IAppsFlyerPurchaseRevenueDataSourceStoreKit2 dataSource)
{
#if UNITY_IOS && !UNITY_EDITOR
_dataSourceSK2 = dataSource;
RegisterUnityPurchaseRevenueParamsCallbackSK2(GetAdditionalParametersSK2);
#endif
}
public static Dictionary<string, object> GetAdditionalParametersForAndroid(HashSet<object> products, HashSet<object> transactions)
{
return _dataSource?.PurchaseRevenueAdditionalParametersForProducts(products, transactions)
?? new Dictionary<string, object>();
}
#if UNITY_IOS && !UNITY_EDITOR
[AOT.MonoPInvokeCallback(typeof(Func<string, string, string>))]
public static string GetAdditionalParameters(string productsJson, string transactionsJson)
{
try
{
HashSet<object> products = new HashSet<object>();
HashSet<object> transactions = new HashSet<object>();
if (!string.IsNullOrEmpty(productsJson))
{
var dict = AFMiniJSON.Json.Deserialize(productsJson) as Dictionary<string, object>;
if (dict != null)
{
if (dict.TryGetValue("products", out var productsObj) && productsObj is List<object> productList)
products = new HashSet<object>(productList);
if (dict.TryGetValue("transactions", out var transactionsObj) && transactionsObj is List<object> transactionList)
transactions = new HashSet<object>(transactionList);
}
}
var parameters = _dataSource?.PurchaseRevenueAdditionalParametersForProducts(products, transactions)
?? new Dictionary<string, object>();
return AFMiniJSON.Json.Serialize(parameters);
}
catch (Exception e)
{
Debug.LogError($"[AppsFlyer] Exception in GetAdditionalParameters: {e}");
return "{}";
}
}
#endif
#if UNITY_IOS && !UNITY_EDITOR
[AOT.MonoPInvokeCallback(typeof(Func<string, string, string>))]
public static string GetAdditionalParametersSK2(string productsJson, string transactionsJson)
{
try
{
HashSet<object> products = new HashSet<object>();
HashSet<object> transactions = new HashSet<object>();
if (!string.IsNullOrEmpty(productsJson))
{
var dict = AFMiniJSON.Json.Deserialize(productsJson) as Dictionary<string, object>;
if (dict != null && dict.TryGetValue("products", out var productsObj) && productsObj is List<object> productList)
products = new HashSet<object>(productList);
}
if (!string.IsNullOrEmpty(transactionsJson))
{
var dict = AFMiniJSON.Json.Deserialize(transactionsJson) as Dictionary<string, object>;
if (dict != null && dict.TryGetValue("transactions", out var transactionsObj) && transactionsObj is List<object> transactionList)
transactions = new HashSet<object>(transactionList);
}
var parameters = _dataSourceSK2?.PurchaseRevenueAdditionalParametersStoreKit2ForProducts(products, transactions)
?? new Dictionary<string, object>();
return AFMiniJSON.Json.Serialize(parameters);
}
catch (Exception e)
{
Debug.LogError($"[AppsFlyer] Exception in GetAdditionalParametersSK2: {e}");
return "{}";
}
}
#endif
}
public class UnityPurchaseRevenueBridgeProxy : AndroidJavaProxy
{
public UnityPurchaseRevenueBridgeProxy() : base("com.appsflyer.unity.PurchaseRevenueBridge$UnityPurchaseRevenueBridge") { }
public string getAdditionalParameters(string productsJson, string transactionsJson)
{
try
{
// Create empty sets if JSON is null or empty
HashSet<object> products = new HashSet<object>();
HashSet<object> transactions = new HashSet<object>();
// Only try to parse if we have valid JSON
if (!string.IsNullOrEmpty(productsJson))
{
try
{
// First try to parse as a simple array
var parsedProducts = AFMiniJSON.Json.Deserialize(productsJson);
if (parsedProducts is List<object> productList)
{
products = new HashSet<object>(productList);
}
else if (parsedProducts is Dictionary<string, object> dict)
{
if (dict.ContainsKey("events") && dict["events"] is List<object> eventsList)
{
products = new HashSet<object>(eventsList);
}
else
{
// If it's a dictionary but doesn't have events, add the whole dict
products.Add(dict);
}
}
}
catch (Exception e)
{
Debug.LogError($"Error parsing products JSON: {e.Message}\nJSON: {productsJson}");
}
}
if (!string.IsNullOrEmpty(transactionsJson))
{
try
{
// First try to parse as a simple array
var parsedTransactions = AFMiniJSON.Json.Deserialize(transactionsJson);
if (parsedTransactions is List<object> transactionList)
{
transactions = new HashSet<object>(transactionList);
}
else if (parsedTransactions is Dictionary<string, object> dict)
{
if (dict.ContainsKey("events") && dict["events"] is List<object> eventsList)
{
transactions = new HashSet<object>(eventsList);
}
else
{
// If it's a dictionary but doesn't have events, add the whole dict
transactions.Add(dict);
}
}
}
catch (Exception e)
{
Debug.LogError($"Error parsing transactions JSON: {e.Message}\nJSON: {transactionsJson}");
}
}
var parameters = AppsFlyerPurchaseRevenueBridge.GetAdditionalParametersForAndroid(products, transactions);
return AFMiniJSON.Json.Serialize(parameters);
}
catch (Exception e)
{
Debug.LogError($"Error in getAdditionalParameters: {e.Message}\nProducts JSON: {productsJson}\nTransactions JSON: {transactionsJson}");
return "{}";
}
}
}
public class AppsFlyerPurchaseConnector : MonoBehaviour {
private static AppsFlyerPurchaseConnector instance;
private Dictionary<string, object> pendingParameters;
private Action<Dictionary<string, object>> pendingCallback;
public static AppsFlyerPurchaseConnector Instance
{
get
{
if (instance == null)
{
GameObject go = new GameObject("AppsFlyerPurchaseConnector");
instance = go.AddComponent<AppsFlyerPurchaseConnector>();
DontDestroyOnLoad(go);
}
return instance;
}
}
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
#if UNITY_ANDROID && !UNITY_EDITOR
private static AndroidJavaClass appsFlyerAndroidConnector = new AndroidJavaClass("com.appsflyer.unity.AppsFlyerAndroidWrapper");
#endif
public static void init(MonoBehaviour unityObject, Store s) {
#if UNITY_IOS && !UNITY_EDITOR
_initPurchaseConnector(unityObject.name);
#elif UNITY_ANDROID && !UNITY_EDITOR
int store = mapStoreToInt(s);
appsFlyerAndroidConnector.CallStatic("initPurchaseConnector", unityObject ? unityObject.name : null, store);
#endif
}
public static void build() {
#if UNITY_IOS && !UNITY_EDITOR
//not for iOS
#elif UNITY_ANDROID && !UNITY_EDITOR
appsFlyerAndroidConnector.CallStatic("build");
#else
#endif
}
public static void startObservingTransactions() {
#if UNITY_IOS && !UNITY_EDITOR
_startObservingTransactions();
#elif UNITY_ANDROID && !UNITY_EDITOR
appsFlyerAndroidConnector.CallStatic("startObservingTransactions");
#else
#endif
}
public static void stopObservingTransactions() {
#if UNITY_IOS && !UNITY_EDITOR
_stopObservingTransactions();
#elif UNITY_ANDROID && !UNITY_EDITOR
appsFlyerAndroidConnector.CallStatic("stopObservingTransactions");
#else
#endif
}
public static void setIsSandbox(bool isSandbox) {
#if UNITY_IOS && !UNITY_EDITOR
_setIsSandbox(isSandbox);
#elif UNITY_ANDROID && !UNITY_EDITOR
appsFlyerAndroidConnector.CallStatic("setIsSandbox", isSandbox);
#else
#endif
}
public static void setPurchaseRevenueValidationListeners(bool enableCallbacks) {
#if UNITY_IOS && !UNITY_EDITOR
_setPurchaseRevenueDelegate();
#elif UNITY_ANDROID && !UNITY_EDITOR
appsFlyerAndroidConnector.CallStatic("setPurchaseRevenueValidationListeners", enableCallbacks);
#else
#endif
}
public static void setAutoLogPurchaseRevenue(params AppsFlyerAutoLogPurchaseRevenueOptions[] autoLogPurchaseRevenueOptions) {
#if UNITY_IOS && !UNITY_EDITOR
int option = 0;
foreach (AppsFlyerAutoLogPurchaseRevenueOptions op in autoLogPurchaseRevenueOptions) {
option = option | (int)op;
}
_setAutoLogPurchaseRevenue(option);
#elif UNITY_ANDROID && !UNITY_EDITOR
if (autoLogPurchaseRevenueOptions.Length == 0) {
return;
}
foreach (AppsFlyerAutoLogPurchaseRevenueOptions op in autoLogPurchaseRevenueOptions) {
switch(op) {
case AppsFlyerAutoLogPurchaseRevenueOptions.AppsFlyerAutoLogPurchaseRevenueOptionsDisabled:
break;
case AppsFlyerAutoLogPurchaseRevenueOptions.AppsFlyerAutoLogPurchaseRevenueOptionsAutoRenewableSubscriptions:
appsFlyerAndroidConnector.CallStatic("setAutoLogSubscriptions", true);
break;
case AppsFlyerAutoLogPurchaseRevenueOptions.AppsFlyerAutoLogPurchaseRevenueOptionsInAppPurchases:
appsFlyerAndroidConnector.CallStatic("setAutoLogInApps", true);
break;
default:
break;
}
}
#else
#endif
}
public static void setPurchaseRevenueDataSource(IAppsFlyerPurchaseRevenueDataSource dataSource)
{
#if UNITY_IOS && !UNITY_EDITOR
if (dataSource != null)
{
_setPurchaseRevenueDataSource(dataSource.GetType().Name);
AppsFlyerPurchaseRevenueBridge.RegisterDataSource(dataSource);
}
#elif UNITY_ANDROID && !UNITY_EDITOR
if (dataSource != null)
{
AppsFlyerPurchaseRevenueBridge.RegisterDataSource(dataSource);
}
#endif
}
public static void setPurchaseRevenueDataSourceStoreKit2(IAppsFlyerPurchaseRevenueDataSourceStoreKit2 dataSourceSK2)
{
#if UNITY_IOS && !UNITY_EDITOR
if (dataSourceSK2 != null)
{
AppsFlyerPurchaseRevenueBridge.RegisterDataSourceStoreKit2(dataSourceSK2);
_setPurchaseRevenueDataSource("AppsFlyerObjectScript_StoreKit2");
}
#endif
}
private static int mapStoreToInt(Store s) {
switch(s) {
case(Store.GOOGLE):
return 0;
default:
return -1;
}
}
public static void setStoreKitVersion(StoreKitVersion storeKitVersion) {
#if UNITY_IOS && !UNITY_EDITOR
_setStoreKitVersion((int)storeKitVersion);
#elif UNITY_ANDROID && !UNITY_EDITOR
// Android doesn't use StoreKit
#else
#endif
}
public static void logConsumableTransaction(string transactionJson) {
#if UNITY_IOS && !UNITY_EDITOR
_logConsumableTransaction(transactionJson);
#elif UNITY_ANDROID && !UNITY_EDITOR
// Android doesn't use StoreKit
#else
#endif
}
#if UNITY_IOS && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void _startObservingTransactions();
[DllImport("__Internal")]
private static extern void _stopObservingTransactions();
[DllImport("__Internal")]
private static extern void _setIsSandbox(bool isSandbox);
[DllImport("__Internal")]
private static extern void _setPurchaseRevenueDelegate();
[DllImport("__Internal")]
private static extern void _setPurchaseRevenueDataSource(string dataSourceName);
[DllImport("__Internal")]
private static extern void _setAutoLogPurchaseRevenue(int option);
[DllImport("__Internal")]
private static extern void _initPurchaseConnector(string objectName);
[DllImport("__Internal")]
private static extern void _setStoreKitVersion(int storeKitVersion);
[DllImport("__Internal")]
private static extern void _logConsumableTransaction(string transactionJson);
#endif
}
public enum Store {
GOOGLE = 0
}
public enum AppsFlyerAutoLogPurchaseRevenueOptions
{
AppsFlyerAutoLogPurchaseRevenueOptionsDisabled = 0,
AppsFlyerAutoLogPurchaseRevenueOptionsAutoRenewableSubscriptions = 1 << 0,
AppsFlyerAutoLogPurchaseRevenueOptionsInAppPurchases = 1 << 1
}
public enum StoreKitVersion {
SK1 = 0,
SK2 = 1
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 0636ea07d370d437183f3762280c08ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -215,24 +215,7 @@ public void startSDK(bool shouldCallback, string CallBackObjectName)
public void setConsentData(AppsFlyerConsent appsFlyerConsent)
{
#if !UNITY_EDITOR
string isUserSubjectToGDPR = appsFlyerConsent.isUserSubjectToGDPR?.ToString().ToLower() ?? "null";
string hasConsentForDataUsage = appsFlyerConsent.hasConsentForDataUsage?.ToString().ToLower() ?? "null";
string hasConsentForAdsPersonalization = appsFlyerConsent.hasConsentForAdsPersonalization?.ToString().ToLower() ?? "null";
string hasConsentForAdStorage = appsFlyerConsent.hasConsentForAdStorage?.ToString().ToLower() ?? "null";
_setConsentData(isUserSubjectToGDPR, hasConsentForDataUsage, hasConsentForAdsPersonalization, hasConsentForAdStorage);
#endif
}
/// <summary>
/// Logs ad revenue data along with additional parameters if provided.
/// </summary>
/// <param name = "adRevenueData" >instance of AFAdRevenueData containing ad revenue information.</param>
/// <param name = "additionalParameters" >An optional map of additional parameters to be logged with ad revenue data. This can be null if there are no additional parameters.</param>
public void logAdRevenue(AFAdRevenueData adRevenueData, Dictionary<string, string> additionalParameters)
{
#if !UNITY_EDITOR
_logAdRevenue(adRevenueData.monetizationNetwork, adRevenueData.mediationNetwork, adRevenueData.currencyIso4217Code, adRevenueData.eventRevenue, AFMiniJSON.Json.Serialize(additionalParameters));
_setConsentData(appsFlyerConsent.isUserSubjectToGDPR, appsFlyerConsent.hasConsentForDataUsage, appsFlyerConsent.hasConsentForAdsPersonalization);
#endif
}
@@ -354,11 +337,11 @@ public void startSDK(bool shouldCallback, string CallBackObjectName)
/// To send and validate in app purchases you can call this method from the processPurchase method.
/// </summary>
/// <param name="details">The AFSDKPurchaseDetailsIOS instance.</param>
/// <param name="purchaseAdditionalDetails">The additional params, which you want to receive it in the raw reports.</param>
public void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject)
/// <param name="extraEventValues">The extra params, which you want to receive it in the raw reports.</param>
public void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> extraEventValues, MonoBehaviour gameObject)
{
#if !UNITY_EDITOR
_validateAndSendInAppPurchaseV2(details.productId, details.transactionId, (int)details.purchaseType, AFMiniJSON.Json.Serialize(purchaseAdditionalDetails), gameObject ? gameObject.name : null);
_validateAndSendInAppPurchaseV2(details.productId, details.price, details.currency, details.transactionId, AFMiniJSON.Json.Serialize(extraEventValues), gameObject ? gameObject.name : null);
#endif
}
@@ -765,14 +748,7 @@ public void startSDK(bool shouldCallback, string CallBackObjectName)
#elif UNITY_STANDALONE_OSX
[DllImport("AppsFlyerBundle")]
#endif
private static extern void _setConsentData(string isUserSubjectToGDPR, string hasConsentForDataUsage, string hasConsentForAdsPersonalization, string hasConsentForAdStorage);
#if UNITY_IOS
[DllImport("__Internal")]
#elif UNITY_STANDALONE_OSX
[DllImport("AppsFlyerBundle")]
#endif
private static extern void _logAdRevenue(string monetizationNetwork, MediationNetwork mediationNetwork, string currencyIso4217Code, double eventRevenue, string additionalParameters);
private static extern void _setConsentData(bool isUserSubjectToGDPR, bool hasConsentForDataUsage, bool hasConsentForAdsPersonalization);
#if UNITY_IOS
[DllImport("__Internal")]
@@ -843,7 +819,7 @@ public void startSDK(bool shouldCallback, string CallBackObjectName)
#elif UNITY_STANDALONE_OSX
[DllImport("AppsFlyerBundle")]
#endif
private static extern void _validateAndSendInAppPurchaseV2(string product, string transactionId, int purchaseType, string purchaseAdditionalDetails, string objectName);
private static extern void _validateAndSendInAppPurchaseV2(string product, string price, string currency, string transactionId, string extraEventValues, string objectName);
#if UNITY_IOS
[DllImport("__Internal")]

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<dependencies>
<androidPackages>
<androidPackage spec="com.appsflyer:adrevenue:6.9.1"></androidPackage>
<androidPackage spec="com.appsflyer:unity-adrevenue-generic-wrapper:6.9.1"></androidPackage>
</androidPackages>
<iosPods>
<iosPod name="AppsFlyer-AdRevenue" version="6.14.3" minTargetSdk="12.0">
</iosPod>
</iosPods>
</dependencies>

View File

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

View File

@@ -2,15 +2,17 @@
<dependencies>
<androidPackages>
<androidPackage spec="com.appsflyer:af-android-sdk:6.17.3"></androidPackage>
<androidPackage spec="com.appsflyer:unity-wrapper:6.17.7"></androidPackage>
<androidPackage spec="com.android.installreferrer:installreferrer:2.1"></androidPackage>
<androidPackage spec="com.appsflyer:purchase-connector:2.2.0"></androidPackage>
<androidPackage spec="com.appsflyer:af-android-sdk:6.14.0">
</androidPackage>
<androidPackage spec="com.appsflyer:unity-wrapper:6.14.3">
</androidPackage>
<androidPackage spec="com.android.installreferrer:installreferrer:2.1">
</androidPackage>
</androidPackages>
<iosPods>
<iosPod name="AppsFlyerFramework" version="6.17.7" minTargetSdk="12.0"></iosPod>
<iosPod name="PurchaseConnector" version="6.17.7" minTargetSdk="12.0"></iosPod>
<iosPod name="AppsFlyerFramework" version="6.14.3" minTargetSdk="12.0">
</iosPod>
</iosPods>
</dependencies>

View File

@@ -22,7 +22,7 @@ namespace AppsFlyerSDK
string getAttributionId();
void handlePushNotifications();
void validateAndSendInAppPurchase(string publicKey, string signature, string purchaseData, string price, string currency, Dictionary<string, string> additionalParameters, MonoBehaviour gameObject);
void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject);
void validateAndSendInAppPurchase(AFPurchaseDetailsAndroid details, Dictionary<string, string> additionalParameters, MonoBehaviour gameObject);
void setCollectOaid(bool isCollect);
void setDisableAdvertisingIdentifiers(bool disable);
void setDisableNetworkData(bool disable);

View File

@@ -14,7 +14,7 @@ namespace AppsFlyerSDK
void setUseReceiptValidationSandbox(bool useReceiptValidationSandbox);
void setUseUninstallSandbox(bool useUninstallSandbox);
void validateAndSendInAppPurchase(string productIdentifier, string price, string currency, string transactionId, Dictionary<string, string> additionalParameters, MonoBehaviour gameObject);
void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> purchaseAdditionalDetails, MonoBehaviour gameObject);
void validateAndSendInAppPurchase(AFSDKPurchaseDetailsIOS details, Dictionary<string, string> extraEventValues, MonoBehaviour gameObject);
void registerUninstall(byte[] deviceToken);
void handleOpenUrl(string url, string sourceApplication, string annotation);
void waitForATTUserAuthorizationWithTimeoutInterval(int timeoutInterval);

View File

@@ -43,8 +43,6 @@ namespace AppsFlyerSDK
void setConsentData(AppsFlyerConsent appsFlyerConsent);
void logAdRevenue(AFAdRevenueData adRevenueData, Dictionary<string, string> additionalParameters);
void setMinTimeBetweenSessions(int seconds);
void setHost(string hostPrefixName, string hostName);

View File

@@ -1,8 +0,0 @@
namespace AppsFlyerSDK
{
public interface IAppsFlyerPurchaseValidation
{
void didReceivePurchaseRevenueValidationInfo(string validationInfo);
void didReceivePurchaseRevenueError(string error);
}
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 7c60f499ae0d048b1be8ffd6878a184c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,79 +0,0 @@
import Foundation
import StoreKit
#if canImport(PurchaseConnector)
import PurchaseConnector
@available(iOS 15.0, *)
@objc
public class AFUnityStoreKit2Bridge: NSObject {
@objc
public static func fetchAFSDKTransactionSK2(withTransactionId transactionId: String, completion: @escaping (AFSDKTransactionSK2?) -> Void) {
guard let transactionIdUInt64 = UInt64(transactionId) else {
print("Invalid transaction ID format.")
completion(nil)
return
}
Task {
for await result in StoreKit.Transaction.all {
if case .verified(let transaction) = result, transaction.id == transactionIdUInt64 {
let afTransaction = AFSDKTransactionSK2(transaction: transaction)
DispatchQueue.main.async {
completion(afTransaction)
}
return
}
}
DispatchQueue.main.async {
completion(nil)
}
}
}
@objc
public static func extractSK2ProductInfo(_ products: [AFSDKProductSK2]) -> NSArray {
var result: [[String: Any]] = []
for product in products {
if let swiftProduct = Mirror(reflecting: product).children.first(where: { $0.label == "product" })?.value {
let productId = (swiftProduct as? NSObject)?.value(forKey: "id") as? String ?? ""
let title = (swiftProduct as? NSObject)?.value(forKey: "displayName") as? String ?? ""
let desc = (swiftProduct as? NSObject)?.value(forKey: "description") as? String ?? ""
let price = (swiftProduct as? NSObject)?.value(forKey: "price") as? NSDecimalNumber ?? 0
result.append([
"productIdentifier": productId,
"localizedTitle": title,
"localizedDescription": desc,
"price": price
])
}
}
return result as NSArray
}
@objc
public static func extractSK2TransactionInfo(_ transactions: [AFSDKTransactionSK2]) -> NSArray {
var result: [[String: Any]] = []
for txn in transactions {
guard let mirrorChild = Mirror(reflecting: txn).children.first(where: { $0.label == "transaction" }),
let swiftTxn = mirrorChild.value as? StoreKit.Transaction else {
continue
}
let transactionId = "\(swiftTxn.id)"
let date = NSNumber(value: swiftTxn.purchaseDate.timeIntervalSince1970)
result.append([
"transactionIdentifier": transactionId,
"transactionState": "verified", // or skip this line
"transactionDate": date
])
}
return result as NSArray
}
}
#endif

View File

@@ -1,42 +0,0 @@
fileFormatVersion: 2
guid: 5652805602a6b4273a6e527b00aea272
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
VisionOS: VisionOS
second:
enabled: 1
settings: {}
- first:
iPhone: iOS
second:
enabled: 1
settings: {}
- first:
tvOS: tvOS
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -17,8 +17,6 @@ static NSArray<NSString*> *NSArrayFromCArray(int length, const char **arr);
static char* getCString(const char* string);
static AppsFlyerLinkGenerator* generatorFromDictionary(NSDictionary* dictionary, AppsFlyerLinkGenerator* generator);
static EmailCryptType emailCryptTypeFromInt(int emailCryptTypeInt);
static AppsFlyerAdRevenueMediationNetworkType mediationNetworkTypeFromInt(int mediationNetwork);
static NSNumber *intFromNullableBool(const char *cStr);
static NSString* stringFromDeepLinkResultStatus(AFSDKDeepLinkResultStatus deepLinkResult);
static NSString* stringFromDeepLinkResultError(AppsFlyerDeepLinkResult *result);

View File

@@ -108,72 +108,6 @@ static EmailCryptType emailCryptTypeFromInt(int emailCryptTypeInt){
return emailCryptType;
}
static NSNumber *intFromNullableBool(const char *cStr) {
if (!cStr) return nil;
NSString *str = [NSString stringWithUTF8String:cStr];
if ([str caseInsensitiveCompare:@"true"] == NSOrderedSame) {
return @YES;
} else if ([str caseInsensitiveCompare:@"false"] == NSOrderedSame) {
return @NO;
}
return nil;
}
static AppsFlyerAdRevenueMediationNetworkType mediationNetworkTypeFromInt(int mediationNetworkInt){
AppsFlyerAdRevenueMediationNetworkType mediationNetworkType;
switch (mediationNetworkInt){
case 1:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeGoogleAdMob;
break;
case 2:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeIronSource;
break;
case 3:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeApplovinMax;
break;
case 4:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeFyber;
break;
case 5:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeAppodeal;
break;
case 6:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeAdmost;
break;
case 7:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeTopon;
break;
case 8:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeTradplus;
break;
case 9:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeYandex;
break;
case 10:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeChartBoost;
break;
case 11:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeUnity;
break;
case 12:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeToponPte;
break;
case 13:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeCustom;
break;
case 14:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeDirectMonetization;
break;
default:
mediationNetworkType = AppsFlyerAdRevenueMediationNetworkTypeCustom;
break;
}
return mediationNetworkType;
}
static NSString* stringFromDeepLinkResultStatus(AFSDKDeepLinkResultStatus deepLinkResult){
NSString* result;
switch (deepLinkResult){

View File

@@ -29,34 +29,6 @@ static IMP __original_openUrl_Imp __unused;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if !AFSDK_SHOULD_SWIZZLE
id swizzleFlag = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppsFlyerShouldSwizzle"];
BOOL shouldSwizzle = swizzleFlag ? [swizzleFlag boolValue] : NO;
if(shouldSwizzle){
Method method1 = class_getInstanceMethod([self class], @selector(applicationDidBecomeActive:));
__original_applicationDidBecomeActive_Imp = method_setImplementation(method1, (IMP)__swizzled_applicationDidBecomeActive);
Method method2 = class_getInstanceMethod([self class], @selector(applicationDidEnterBackground:));
__original_applicationDidEnterBackground_Imp = method_setImplementation(method2, (IMP)__swizzled_applicationDidEnterBackground);
Method method3 = class_getInstanceMethod([self class], @selector(didReceiveRemoteNotification:));
__original_didReceiveRemoteNotification_Imp = method_setImplementation(method3, (IMP)__swizzled_didReceiveRemoteNotification);
Method method4 = class_getInstanceMethod([self class], @selector(application:openURL:options:));
__original_openUrl_Imp = method_setImplementation(method4, (IMP)__swizzled_openURL);
if (_AppsFlyerdelegate == nil) {
_AppsFlyerdelegate = [[AppsFlyeriOSWarpper alloc] init];
}
[self swizzleContinueUserActivity:[self class]];
}
#elif AFSDK_SHOULD_SWIZZLE
Method method1 = class_getInstanceMethod([self class], @selector(applicationDidBecomeActive:));
__original_applicationDidBecomeActive_Imp = method_setImplementation(method1, (IMP)__swizzled_applicationDidBecomeActive);
@@ -76,10 +48,6 @@ static IMP __original_openUrl_Imp __unused;
}
[self swizzleContinueUserActivity:[self class]];
#endif
});
}

View File

@@ -0,0 +1,16 @@
//
// AppsFlyerAdRevenueWrapper.h
// Unity-iPhone
//
// Created by Jonathan Wesfield on 01/12/2019.
//
#import <Foundation/Foundation.h>
#if __has_include(<AppsFlyerAdRevenue/AppsFlyerAdRevenue.h>)
#import <AppsFlyerAdRevenue/AppsFlyerAdRevenue.h>
#endif
@interface AppsFlyerAdRevenueWrapper : NSObject
@end

View File

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

View File

@@ -0,0 +1,51 @@
//
// AppsFlyerAdRevenueWrapper.mm
// Unity-iPhone
//
// Created by Jonathan Wesfield on 25/11/2019.
//
#import "AppsFlyerAdRevenueWrapper.h"
@implementation AppsFlyerAdRevenueWrapper
extern "C" {
NSString* stringFromChar(const char *str) {
return str ? [NSString stringWithUTF8String:str] : nil;
}
NSDictionary* dictionaryFromJson(const char *jsonString) {
if(jsonString){
NSData *jsonData = [[NSData alloc] initWithBytes:jsonString length:strlen(jsonString)];
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:nil];
return dictionary;
}
return nil;
}
const void _start(int length, int* adRevenueTypes){
[AppsFlyerAdRevenue start];
}
const void _setIsDebugAdrevenue(bool isDebug){
[[AppsFlyerAdRevenue shared] setIsDebug:isDebug];
}
const void _logAdRevenue(const char* monetizationNetwork,
int mediationNetwork,
double eventRevenue,
const char* revenueCurrency,
const char* additionalParameters){
[[AppsFlyerAdRevenue shared] logAdRevenueWithMonetizationNetwork:stringFromChar(monetizationNetwork)
mediationNetwork:(AppsFlyerAdRevenueMediationNetworkType) mediationNetwork
eventRevenue:[NSNumber numberWithDouble:eventRevenue]
revenueCurrency:stringFromChar(revenueCurrency)
additionalParameters:dictionaryFromJson(additionalParameters)];
}
}
@end

View File

@@ -0,0 +1,37 @@
fileFormatVersion: 2
guid: 84d99cf0f930e4c80b4bc1858e042b70
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings: {}
- first:
tvOS: tvOS
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -101,11 +101,6 @@
@end
#if !(AFSDK_SHOULD_SWIZZLE)
IMPL_APP_CONTROLLER_SUBCLASS(AppsFlyerAppController)
#endif
/**
Note if you would not like to use IMPL_APP_CONTROLLER_SUBCLASS you can replace it with the code below.
<code>

View File

@@ -13,27 +13,10 @@
#else
#import "AppsFlyerLib.h"
#endif
#if __has_include(<PurchaseConnector/PurchaseConnector.h>)
#import <PurchaseConnector/PurchaseConnector.h>
#else
#import "PurchaseConnector.h"
#endif
#import <PurchaseConnector/PurchaseConnector-Swift.h>
// Add StoreKit 2 support
#if __has_include(<StoreKit/StoreKit.h>)
#import <StoreKit/StoreKit.h>
#endif
@interface AppsFlyeriOSWarpper : NSObject <AppsFlyerLibDelegate, AppsFlyerDeepLinkDelegate, AppsFlyerPurchaseRevenueDelegate, AppsFlyerPurchaseRevenueDataSource, AppsFlyerPurchaseRevenueDataSourceStoreKit2>
@interface AppsFlyeriOSWarpper : NSObject <AppsFlyerLibDelegate, AppsFlyerDeepLinkDelegate>
+ (BOOL) didCallStart;
+ (void) setDidCallStart:(BOOL)val;
// Add StoreKit 2 methods
- (void)setStoreKitVersion:(int)storeKitVersion;
- (void)logConsumableTransaction:(id)transaction;
@end
@@ -65,7 +48,3 @@ static NSString* startRequestObjectName = @"";
static NSString* inAppRequestObjectName = @"";
static NSString* onDeeplinkingObjectName = @"";
static const char* PURCHASE_REVENUE_VALIDATION_CALLBACK = "didReceivePurchaseRevenueValidationInfo";
static const char* PURCHASE_REVENUE_ERROR_CALLBACK = "didReceivePurchaseRevenueError";
static NSString* onPurchaseValidationObjectName = @"";

View File

@@ -6,22 +6,7 @@
//
#import "AppsFlyeriOSWrapper.h"
#import <objc/runtime.h>
#import <StoreKit/StoreKit.h>
#import "UnityFramework/UnityFramework-Swift.h"
#if __has_include(<PurchaseConnector/PurchaseConnector-Swift.h>)
#import <PurchaseConnector/PurchaseConnector-Swift.h>
#elif __has_include("PurchaseConnector-Swift.h")
#import "PurchaseConnector-Swift.h"
#endif
#if __has_include(<UnityFramework/UnityFramework-Swift.h>)
#import <UnityFramework/UnityFramework-Swift.h>
#elif __has_include("UnityFramework-Swift.h")
#import "UnityFramework-Swift.h"
#endif
static void unityCallBack(NSString* objectName, const char* method, const char* msg) {
if(objectName){
@@ -33,7 +18,7 @@ extern "C" {
const void _startSDK(bool shouldCallback, const char* objectName) {
[[AppsFlyerLib shared] setPluginInfoWith: AFSDKPluginUnity
pluginVersion:@"6.17.7"
pluginVersion:@"6.14.3"
additionalParams:nil];
startRequestObjectName = stringFromChar(objectName);
AppsFlyeriOSWarpper.didCallStart = YES;
@@ -102,26 +87,14 @@ extern "C" {
[[AppsFlyerLib shared] enableTCFDataCollection:shouldCollectTcfData];
}
const void _setConsentData(const char* isUserSubjectToGDPR, const char* hasConsentForDataUsage, const char* hasConsentForAdsPersonalization, const char* hasConsentForAdStorage) {
NSNumber *gdpr = intFromNullableBool(isUserSubjectToGDPR);
NSNumber *dataUsage = intFromNullableBool(hasConsentForDataUsage);
NSNumber *adsPersonalization = intFromNullableBool(hasConsentForAdsPersonalization);
NSNumber *adStorage = intFromNullableBool(hasConsentForAdStorage);
AppsFlyerConsent *consentData = [[AppsFlyerConsent alloc] initWithIsUserSubjectToGDPR:gdpr
hasConsentForDataUsage:dataUsage
hasConsentForAdsPersonalization:adsPersonalization
hasConsentForAdStorage:adStorage];
[[AppsFlyerLib shared] setConsentData:consentData];
}
const void _logAdRevenue(const char* monetizationNetwork, int mediationNetworkInt, const char* currencyIso4217Code, double eventRevenue, const char* additionalParameters) {
AppsFlyerAdRevenueMediationNetworkType mediationNetwork = mediationNetworkTypeFromInt(mediationNetworkInt);
NSNumber *number = [NSNumber numberWithDouble:eventRevenue];
AFAdRevenueData *adRevenue = [[AFAdRevenueData alloc] initWithMonetizationNetwork:stringFromChar(monetizationNetwork) mediationNetwork:mediationNetwork currencyIso4217Code:stringFromChar(currencyIso4217Code) eventRevenue:number];
[[AppsFlyerLib shared] logAdRevenue: adRevenue additionalParameters:dictionaryFromJson(additionalParameters)];
const void _setConsentData(bool isUserSubjectToGDPR, bool hasConsentForDataUsage, bool hasConsentForAdsPersonalization) {
AppsFlyerConsent *consentData = nil;
if (isUserSubjectToGDPR) {
consentData = [[AppsFlyerConsent alloc] initForGDPRUserWithHasConsentForDataUsage:hasConsentForDataUsage hasConsentForAdsPersonalization:hasConsentForAdsPersonalization];
} else {
consentData = [[AppsFlyerConsent alloc] initNonGDPRUser];
}
[[AppsFlyerLib shared] setConsentData:consentData];
}
const void _setDisableCollectIAd (bool disableCollectASA) {
@@ -288,38 +261,21 @@ extern "C" {
}];
}
const void _validateAndSendInAppPurchaseV2 (const char* product, const char* transactionId, int purchaseType, const char* purchaseAdditionalDetails, const char* objectName) {
const void _validateAndSendInAppPurchaseV2 (const char* product, const char* price, const char* currency, const char* transactionId, const char* extraEventValues, const char* objectName) {
validateAndLogObjectName = stringFromChar(objectName);
AFSDKPurchaseDetails *details = [[AFSDKPurchaseDetails alloc] initWithProductId:stringFromChar(product) transactionId:stringFromChar(transactionId) purchaseType:(AFSDKPurchaseType)purchaseType];
AFSDKPurchaseDetails *details = [[AFSDKPurchaseDetails alloc] initWithProductId:stringFromChar(product) price:stringFromChar(price) currency:stringFromChar(currency) transactionId:stringFromChar(transactionId)];
[[AppsFlyerLib shared]
validateAndLogInAppPurchase:details
purchaseAdditionalDetails:dictionaryFromJson(purchaseAdditionalDetails)
completion:^(NSDictionary * _Nullable response, NSError * _Nullable error) {
if (error) {
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_ERROR_CALLBACK, stringFromdictionary(dictionaryFromNSError(error)));
extraEventValues:dictionaryFromJson(extraEventValues)
completionHandler:^(AFSDKValidateAndLogResult * _Nullable result) {
if (result.status == AFSDKValidateAndLogStatusSuccess) {
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_CALLBACK, stringFromdictionary(result.result));
} else if (result.status == AFSDKValidateAndLogStatusFailure) {
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_CALLBACK, stringFromdictionary(result.errorData));
} else {
// Check if the response indicates validation failure
BOOL isValidationFailure = NO;
if (response && [response isKindOfClass:[NSDictionary class]]) {
NSDictionary *resultDict = response[@"result"];
if (resultDict && [resultDict isKindOfClass:[NSDictionary class]]) {
NSDictionary *responseForProduct = resultDict.allValues.firstObject;
if (responseForProduct && [responseForProduct isKindOfClass:[NSDictionary class]]) {
BOOL validationResult = [responseForProduct[@"result"] boolValue];
if (!validationResult) {
isValidationFailure = YES;
}
}
}
}
if (isValidationFailure) {
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_ERROR_CALLBACK, stringFromdictionary(response));
} else {
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_CALLBACK, stringFromdictionary(response));
}
unityCallBack(validateAndLogObjectName, VALIDATE_AND_LOG_V2_ERROR_CALLBACK, stringFromdictionary(dictionaryFromNSError(result.error)));
}
}];
@@ -369,97 +325,6 @@ extern "C" {
[AppsFlyerLib shared].disableIDFVCollection = isDisabled;
}
// Purchase connector
const void _startObservingTransactions() {
[[PurchaseConnector shared] startObservingTransactions];
}
const void _stopObservingTransactions() {
[[PurchaseConnector shared] stopObservingTransactions];
}
const void _setIsSandbox(bool isSandBox) {
[[PurchaseConnector shared] setIsSandbox:isSandBox];
}
const void _setPurchaseRevenueDelegate() {
if (_AppsFlyerdelegate== nil) {
_AppsFlyerdelegate = [[AppsFlyeriOSWarpper alloc] init];
}
[[PurchaseConnector shared] setPurchaseRevenueDelegate:_AppsFlyerdelegate];
}
const void _setAutoLogPurchaseRevenue(int option) {
[[PurchaseConnector shared] setAutoLogPurchaseRevenue:option];
}
const void _initPurchaseConnector(const char* objectName) {
if (_AppsFlyerdelegate == nil) {
_AppsFlyerdelegate = [[AppsFlyeriOSWarpper alloc] init];
}
onPurchaseValidationObjectName = stringFromChar(objectName);
}
const void _setPurchaseRevenueDataSource(const char* objectName) {
if (_AppsFlyerdelegate == nil) {
_AppsFlyerdelegate = [[AppsFlyeriOSWarpper alloc] init];
}
if (strstr(objectName, "StoreKit2") != NULL) {
// Force protocol conformance
Protocol *sk2Protocol = @protocol(AppsFlyerPurchaseRevenueDataSourceStoreKit2);
class_addProtocol([_AppsFlyerdelegate class], sk2Protocol);
if (![_AppsFlyerdelegate conformsToProtocol:@protocol(AppsFlyerPurchaseRevenueDataSourceStoreKit2)]) {
NSLog(@"[AppsFlyer] Warning: SK2 protocol not conformed!");
}
}
[PurchaseConnector shared].purchaseRevenueDataSource = _AppsFlyerdelegate;
}
const void _setStoreKitVersion(int storeKitVersion) {
[[PurchaseConnector shared] setStoreKitVersion:(AFSDKStoreKitVersion)storeKitVersion];
}
const void _logConsumableTransaction(const char* transactionId) {
if (@available(iOS 15.0, *)) {
NSString *transactionIdStr = [NSString stringWithUTF8String:transactionId];
[AFUnityStoreKit2Bridge fetchAFSDKTransactionSK2WithTransactionId:transactionIdStr completion:^(AFSDKTransactionSK2 *afTransaction) {
if (afTransaction) {
[[PurchaseConnector shared] logConsumableTransaction:afTransaction];
} else {
NSLog(@"No AFSDKTransactionSK2 found for id %@", transactionIdStr);
}
}];
}
}
#ifdef __cplusplus
extern "C" {
#endif
typedef const char *(*UnityPurchaseCallback)(const char *, const char *);
UnityPurchaseCallback UnityPurchasesGetAdditionalParamsCallback = NULL;
UnityPurchaseCallback UnityPurchasesGetAdditionalParamsCallbackSK2 = NULL;
__attribute__((visibility("default")))
void RegisterUnityPurchaseRevenueParamsCallback(UnityPurchaseCallback callback) {
UnityPurchasesGetAdditionalParamsCallback = callback;
}
__attribute__((visibility("default")))
void RegisterUnityPurchaseRevenueParamsCallbackSK2(UnityPurchaseCallback callback) {
UnityPurchasesGetAdditionalParamsCallbackSK2 = callback;
}
#ifdef __cplusplus
}
#endif
}
@implementation AppsFlyeriOSWarpper
@@ -501,120 +366,5 @@ static BOOL didCallStart;
unityCallBack(onDeeplinkingObjectName, ON_DEEPLINKING, stringFromdictionary(dict));
}
// Purchase Connector
- (void)didReceivePurchaseRevenueValidationInfo:(NSDictionary *)validationInfo error:(NSError *)error {
if (error != nil) {
unityCallBack(onPurchaseValidationObjectName, PURCHASE_REVENUE_ERROR_CALLBACK, [[error localizedDescription] UTF8String]);
} else {
unityCallBack(onPurchaseValidationObjectName, PURCHASE_REVENUE_VALIDATION_CALLBACK, stringFromdictionary(validationInfo));
}
}
- (NSDictionary *)purchaseRevenueAdditionalParametersForProducts:(NSSet<SKProduct *> *)products
transactions:(NSSet<SKPaymentTransaction *> *)transactions {
NSMutableArray *productsArray = [NSMutableArray array];
for (SKProduct *product in products) {
[productsArray addObject:@{
@"productIdentifier": product.productIdentifier ?: @"",
@"localizedTitle": product.localizedTitle ?: @"",
@"localizedDescription": product.localizedDescription ?: @"",
@"price": [product.price stringValue] ?: @""
}];
}
NSMutableArray *transactionsArray = [NSMutableArray array];
for (SKPaymentTransaction *txn in transactions) {
[transactionsArray addObject:@{
@"transactionIdentifier": txn.transactionIdentifier ?: @"",
@"transactionState": @(txn.transactionState),
@"transactionDate": txn.transactionDate ? [@(txn.transactionDate.timeIntervalSince1970) stringValue] : @""
}];
}
NSDictionary *input = @{
@"products": productsArray,
@"transactions": transactionsArray
};
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:input options:0 error:&error];
if (error || !jsonData) {
NSLog(@"[AppsFlyer] Failed to serialize Unity purchase data: %@", error);
return @{};
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
if (!jsonString || !UnityPurchasesGetAdditionalParamsCallback) {
NSLog(@"[AppsFlyer] Unity callback not registered");
return @{};
}
const char *resultCStr = UnityPurchasesGetAdditionalParamsCallback([jsonString UTF8String], "");
if (!resultCStr) {
NSLog(@"[AppsFlyer] Unity callback returned null");
return @{};
}
NSString *resultJson = [NSString stringWithUTF8String:resultCStr];
NSData *resultData = [resultJson dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *parsedResult = [NSJSONSerialization JSONObjectWithData:resultData options:0 error:&error];
if (error || ![parsedResult isKindOfClass:[NSDictionary class]]) {
NSLog(@"[AppsFlyer] Failed to parse Unity response: %@", error);
return @{};
}
return parsedResult;
}
#pragma mark - AppsFlyerPurchaseRevenueDataSourceStoreKit2
- (NSDictionary *)purchaseRevenueAdditionalParametersStoreKit2ForProducts:(NSSet<AFSDKProductSK2 *> *)products transactions:(NSSet<AFSDKTransactionSK2 *> *)transactions {
if (@available(iOS 15.0, *)) {
NSArray *productInfoArray = [AFUnityStoreKit2Bridge extractSK2ProductInfo:[products allObjects]];
NSArray *transactionInfoArray = [AFUnityStoreKit2Bridge extractSK2TransactionInfo:[transactions allObjects]];
NSDictionary *input = @{
@"products": productInfoArray,
@"transactions": transactionInfoArray
};
if (UnityPurchasesGetAdditionalParamsCallbackSK2) {
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:input options:0 error:&error];
if (error || !jsonData) {
NSLog(@"[AppsFlyer] Failed to serialize Unity purchase data: %@", error);
return @{};
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
const char *resultCStr = UnityPurchasesGetAdditionalParamsCallbackSK2([jsonString UTF8String], "");
if (!resultCStr) {
NSLog(@"[AppsFlyer] Unity callback returned null");
return @{};
}
NSString *resultJson = [NSString stringWithUTF8String:resultCStr];
NSData *resultData = [resultJson dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *parsedResult = [NSJSONSerialization JSONObjectWithData:resultData options:0 error:&error];
if (error || ![parsedResult isKindOfClass:[NSDictionary class]]) {
NSLog(@"[AppsFlyer] Failed to parse Unity response: %@", error);
return @{};
}
return parsedResult;
} else {
NSLog(@"[AppsFlyer] SK2 - Unity callback is NOT registered");
}
} else {
NSLog(@"[AppsFlyer] SK2 - iOS version not supported");
}
return @{};
}
@end

View File

@@ -0,0 +1 @@
import Foundation

View File

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

View File

@@ -1,184 +0,0 @@
#nullable enable
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
[System.Serializable]
public class InAppPurchaseValidationResult : EventArgs
{
public bool success;
public ProductPurchase? productPurchase;
public ValidationFailureData? failureData;
public string? token;
}
[System.Serializable]
public class ProductPurchase
{
public string? kind;
public string? purchaseTimeMillis;
public int purchaseState;
public int consumptionState;
public string? developerPayload;
public string? orderId;
public int purchaseType;
public int acknowledgementState;
public string? purchaseToken;
public string? productId;
public int quantity;
public string? obfuscatedExternalAccountId;
public string? obfuscatedExternalProfil;
public string? regionCode;
}
[System.Serializable]
public class ValidationFailureData
{
public int status;
public string? description;
}
[System.Serializable]
public class SubscriptionValidationResult
{
public bool success;
public SubscriptionPurchase? subscriptionPurchase;
public ValidationFailureData? failureData;
public string? token;
}
[System.Serializable]
public class SubscriptionPurchase
{
public string? acknowledgementState;
public CanceledStateContext? canceledStateContext;
public ExternalAccountIdentifiers? externalAccountIdentifiers;
public string? kind;
public string? latestOrderId;
public List<SubscriptionPurchaseLineItem>? lineItems;
public string? linkedPurchaseToken;
public PausedStateContext? pausedStateContext;
public string? regionCode;
public string? startTime;
public SubscribeWithGoogleInfo? subscribeWithGoogleInfo;
public string? subscriptionState;
public TestPurchase? testPurchase;
}
[System.Serializable]
public class CanceledStateContext
{
public DeveloperInitiatedCancellation? developerInitiatedCancellation;
public ReplacementCancellation? replacementCancellation;
public SystemInitiatedCancellation? systemInitiatedCancellation;
public UserInitiatedCancellation? userInitiatedCancellation;
}
[System.Serializable]
public class ExternalAccountIdentifiers
{
public string? externalAccountId;
public string? obfuscatedExternalAccountId;
public string? obfuscatedExternalProfileId;
}
[System.Serializable]
public class SubscriptionPurchaseLineItem
{
public AutoRenewingPlan? autoRenewingPlan;
public DeferredItemReplacement? deferredItemReplacement;
public string? expiryTime;
public OfferDetails? offerDetails;
public PrepaidPlan? prepaidPlan;
public string? productId;
}
[System.Serializable]
public class PausedStateContext
{
public string? autoResumeTime;
}
[System.Serializable]
public class SubscribeWithGoogleInfo
{
public string? emailAddress;
public string? familyName;
public string? givenName;
public string? profileId;
public string? profileName;
}
[System.Serializable]
public class TestPurchase{}
[System.Serializable]
public class DeveloperInitiatedCancellation{}
[System.Serializable]
public class ReplacementCancellation{}
[System.Serializable]
public class SystemInitiatedCancellation{}
[System.Serializable]
public class UserInitiatedCancellation
{
public CancelSurveyResult? cancelSurveyResult;
public string? cancelTime;
}
[System.Serializable]
public class AutoRenewingPlan
{
public string? autoRenewEnabled;
public SubscriptionItemPriceChangeDetails? priceChangeDetails;
}
[System.Serializable]
public class DeferredItemReplacement
{
public string? productId;
}
[System.Serializable]
public class OfferDetails
{
public List<string>? offerTags;
public string? basePlanId;
public string? offerId;
}
[System.Serializable]
public class PrepaidPlan
{
public string? allowExtendAfterTime;
}
[System.Serializable]
public class CancelSurveyResult
{
public string? reason;
public string? reasonUserInput;
}
[System.Serializable]
public class SubscriptionItemPriceChangeDetails
{
public string? expectedNewPriceChargeTime;
public Money? newPrice;
public string? priceChangeMode;
public string? priceChangeState;
}
[System.Serializable]
public class Money
{
public string? currencyCode;
public long nanos;
public long units;
}

View File

@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9a1435104a69d4c8ebcc6f237cc29a54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 7b5b4579db85b4cfd8395bfb19aa309e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 642cf65ed2573419bab7e7d44fc73181
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3a5ccbd864ba94a9a9ba47895ff14922
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: ee45ae2608f3c44d08fc6e21a94d133f
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@
{
"name": "Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"AppsFlyer"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll",
"NSubstitute.dll",
"Castle.Core.dll",
"System.Threading.Tasks.Extensions.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@@ -0,0 +1,810 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using NSubstitute;
namespace AppsFlyerSDK.Tests
{
public class NewTestScript
{
[Test]
public void test_startSDK_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.startSDK();
AppsFlyerMOCKInterface.Received().startSDK(Arg.Any<bool>(), Arg.Any<string>());
}
[Test]
public void test_sendEvent_withValues()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
var eventParams = new Dictionary<string, string>();
eventParams.Add("key", "value");
AppsFlyer.sendEvent("testevent", eventParams);
AppsFlyerMOCKInterface.Received().sendEvent("testevent", eventParams, false, null);
}
[Test]
public void test_sendEvent_withNullParams()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.sendEvent("testevent", null);
AppsFlyerMOCKInterface.Received().sendEvent("testevent", null,false, null);
}
[Test]
public void test_isSDKStopped_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.stopSDK(true);
AppsFlyerMOCKInterface.Received().stopSDK(true);
}
[Test]
public void test_isSDKStopped_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.stopSDK(false);
AppsFlyerMOCKInterface.Received().stopSDK(false);
}
[Test]
public void test_isSDKStopped_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
var isSDKStopped = AppsFlyer.isSDKStopped();
AppsFlyerMOCKInterface.Received().isSDKStopped();
}
[Test]
public void test_isSDKStopped_receveivedFalse()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
var isSDKStopped = AppsFlyer.isSDKStopped();
Assert.AreEqual(AppsFlyerMOCKInterface.Received().isSDKStopped(), false);
}
[Test]
public void test_getSdkVersion_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.getSdkVersion();
AppsFlyerMOCKInterface.Received().getSdkVersion();
}
[Test]
public void test_setCustomerUserId_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCustomerUserId("test");
AppsFlyerMOCKInterface.Received().setCustomerUserId("test");
}
[Test]
public void Test_setAppInviteOneLinkID_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setAppInviteOneLinkID("2f36");
AppsFlyerMOCKInterface.Received().setAppInviteOneLinkID("2f36");
}
[Test]
public void Test_setAdditionalData_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
var customData = new Dictionary<string, string>();
customData.Add("test", "test");
AppsFlyer.setAdditionalData(customData);
AppsFlyerMOCKInterface.Received().setAdditionalData(customData);
}
[Test]
public void Test_setResolveDeepLinkURLs_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setResolveDeepLinkURLs("url1", "url2");
AppsFlyerMOCKInterface.Received().setResolveDeepLinkURLs("url1", "url2");
}
[Test]
public void Test_setOneLinkCustomDomain_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setOneLinkCustomDomain("url1", "url2");
AppsFlyerMOCKInterface.Received().setOneLinkCustomDomain("url1", "url2");
}
[Test]
public void Test_setCurrencyCode_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCurrencyCode("usd");
AppsFlyerMOCKInterface.Received().setCurrencyCode("usd");
}
[Test]
public void Test_recordLocation_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.recordLocation(0.3, 5.2);
AppsFlyerMOCKInterface.Received().recordLocation(0.3, 5.2);
}
[Test]
public void Test_anonymizeUser_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.anonymizeUser(true);
AppsFlyerMOCKInterface.Received().anonymizeUser(true);
}
[Test]
public void Test_anonymizeUser_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.anonymizeUser(false);
AppsFlyerMOCKInterface.Received().anonymizeUser(false);
}
[Test]
public void Test_getAppsFlyerId_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.getAppsFlyerId();
AppsFlyerMOCKInterface.Received().getAppsFlyerId();
}
[Test]
public void Test_setMinTimeBetweenSessions_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setMinTimeBetweenSessions(3);
AppsFlyerMOCKInterface.Received().setMinTimeBetweenSessions(3);
}
[Test]
public void Test_setHost_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setHost("prefix", "name");
AppsFlyerMOCKInterface.Received().setHost("prefix", "name");
}
[Test]
public void Test_setPhoneNumber_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setPhoneNumber("002");
AppsFlyerMOCKInterface.Received().setPhoneNumber("002");
}
[Test]
[System.Obsolete]
public void Test_setSharingFilterForAllPartners_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setSharingFilterForAllPartners();
AppsFlyerMOCKInterface.Received().setSharingFilterForAllPartners();
}
[Test]
[System.Obsolete]
public void Test_setSharingFilter_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setSharingFilter("filter1", "filter2");
AppsFlyerMOCKInterface.Received().setSharingFilter("filter1", "filter2");
}
[Test]
public void Test_getConversionData_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.getConversionData("ObjectName");
AppsFlyerMOCKInterface.Received().getConversionData("ObjectName");
}
[Test]
public void Test_attributeAndOpenStore_called_withParams()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("af_sub1", "val");
parameters.Add("custom_param", "val2");
AppsFlyer.attributeAndOpenStore("appid", "campaign", parameters, new MonoBehaviour());
AppsFlyerMOCKInterface.Received().attributeAndOpenStore("appid", "campaign", parameters, Arg.Any<MonoBehaviour>());
}
[Test]
public void Test_attributeAndOpenStore_called_nullParams()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.attributeAndOpenStore("appid", "campaign", null, new MonoBehaviour());
AppsFlyerMOCKInterface.Received().attributeAndOpenStore("appid", "campaign", null, Arg.Any<MonoBehaviour>());
}
[Test]
public void Test_recordCrossPromoteImpression_calledWithParameters()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("af_sub1", "val");
parameters.Add("custom_param", "val2");
AppsFlyer.recordCrossPromoteImpression("appid", "campaign", parameters);
AppsFlyerMOCKInterface.Received().recordCrossPromoteImpression("appid", "campaign", parameters);
}
[Test]
public void Test_recordCrossPromoteImpression_calledWithoutParameters()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.recordCrossPromoteImpression("appid", "campaign", null);
AppsFlyerMOCKInterface.Received().recordCrossPromoteImpression("appid", "campaign", null);
}
[Test]
public void Test_generateUserInviteLink_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.generateUserInviteLink(new Dictionary<string, string>(), new MonoBehaviour());
AppsFlyerMOCKInterface.Received().generateUserInviteLink(Arg.Any<Dictionary<string, string>>(), Arg.Any<MonoBehaviour>());
}
[Test]
public void Test_addPushNotificationDeepLinkPath_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerNativeBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.addPushNotificationDeepLinkPath("path1", "path2");
AppsFlyerMOCKInterface.Received().addPushNotificationDeepLinkPath("path1", "path2");
}
#if UNITY_ANDROID
[Test]
public void updateServerUninstallToken_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.updateServerUninstallToken("tokenTest");
AppsFlyerMOCKInterface.Received().updateServerUninstallToken("tokenTest");
}
[Test]
public void setImeiData_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setImeiData("imei");
AppsFlyerMOCKInterface.Received().setImeiData("imei");
}
[Test]
public void setAndroidIdData_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setAndroidIdData("androidId");
AppsFlyerMOCKInterface.Received().setAndroidIdData("androidId");
}
[Test]
public void waitForCustomerUserId_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.waitForCustomerUserId(true);
AppsFlyerMOCKInterface.Received().waitForCustomerUserId(true);
}
[Test]
public void setCustomerIdAndStartSDK_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCustomerIdAndStartSDK("01234");
AppsFlyerMOCKInterface.Received().setCustomerIdAndStartSDK("01234");
}
[Test]
public void getOutOfStore_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.getOutOfStore();
AppsFlyerMOCKInterface.Received().getOutOfStore();
}
[Test]
public void setOutOfStore_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setOutOfStore("test");
AppsFlyerMOCKInterface.Received().setOutOfStore("test");
}
[Test]
public void setCollectAndroidID_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCollectAndroidID(true);
AppsFlyerMOCKInterface.Received().setCollectAndroidID(true);
}
[Test]
public void setCollectIMEI_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCollectIMEI(true);
AppsFlyerMOCKInterface.Received().setCollectIMEI(true);
}
[Test]
public void setIsUpdate_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setIsUpdate(true);
AppsFlyerMOCKInterface.Received().setIsUpdate(true);
}
[Test]
public void setPreinstallAttribution_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setPreinstallAttribution("mediaSourceTestt", "campaign", "sideId");
AppsFlyerMOCKInterface.Received().setPreinstallAttribution("mediaSourceTestt", "campaign", "sideId");
}
[Test]
public void isPreInstalledApp_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.isPreInstalledApp();
AppsFlyerMOCKInterface.Received().isPreInstalledApp();
}
[Test]
public void getAttributionId_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.getAttributionId();
AppsFlyerMOCKInterface.Received().getAttributionId();
}
[Test]
public void handlePushNotifications_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.handlePushNotifications();
AppsFlyerMOCKInterface.Received().handlePushNotifications();
}
[Test]
public void validateAndSendInAppPurchase_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.validateAndSendInAppPurchase("ewjkekwjekw","hewjehwj", "purchaseData", "3.0", "USD", null, null);
AppsFlyerMOCKInterface.Received().validateAndSendInAppPurchase("ewjkekwjekw", "hewjehwj", "purchaseData", "3.0", "USD", null, null);
}
[Test]
public void setCollectOaid_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCollectOaid(true);
AppsFlyerMOCKInterface.Received().setCollectOaid(true);
}
[Test]
public void setDisableAdvertisingIdentifiers_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableAdvertisingIdentifiers(true);
AppsFlyerMOCKInterface.Received().setDisableAdvertisingIdentifiers(true);
}
[Test]
public void setDisableNetworkData_called() {
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerAndroidBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableNetworkData(true);
AppsFlyerMOCKInterface.Received().setDisableNetworkData(true);
}
#elif UNITY_IOS
[Test]
public void setDisableCollectAppleAdSupport_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableCollectAppleAdSupport(true);
AppsFlyerMOCKInterface.Received().setDisableCollectAppleAdSupport(true);
}
[Test]
public void setDisableCollectAppleAdSupport_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableCollectAppleAdSupport(false);
AppsFlyerMOCKInterface.Received().setDisableCollectAppleAdSupport(false);
}
[Test]
[System.Obsolete]
public void setShouldCollectDeviceName_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setShouldCollectDeviceName(true);
AppsFlyerMOCKInterface.Received().setShouldCollectDeviceName(true);
}
[Test]
[System.Obsolete]
public void setShouldCollectDeviceName_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setShouldCollectDeviceName(false);
AppsFlyerMOCKInterface.Received().setShouldCollectDeviceName(false);
}
[Test]
public void setDisableCollectIAd_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableCollectIAd(true);
AppsFlyerMOCKInterface.Received().setDisableCollectIAd(true);
}
[Test]
public void setDisableCollectIAd_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setDisableCollectIAd(false);
AppsFlyerMOCKInterface.Received().setDisableCollectIAd(false);
}
[Test]
public void setUseReceiptValidationSandbox_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setUseReceiptValidationSandbox(true);
AppsFlyerMOCKInterface.Received().setUseReceiptValidationSandbox(true);
}
[Test]
public void setUseReceiptValidationSandbox_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setUseReceiptValidationSandbox(false);
AppsFlyerMOCKInterface.Received().setUseReceiptValidationSandbox(false);
}
[Test]
public void ssetUseUninstallSandbox_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setUseUninstallSandbox(true);
AppsFlyerMOCKInterface.Received().setUseUninstallSandbox(true);
}
[Test]
public void setUseUninstallSandbox_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setUseUninstallSandbox(false);
AppsFlyerMOCKInterface.Received().setUseUninstallSandbox(false);
}
[Test]
public void validateAndSendInAppPurchase_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.validateAndSendInAppPurchase("3d2", "5.0","USD", "45", null, null);
AppsFlyerMOCKInterface.Received().validateAndSendInAppPurchase("3d2", "5.0", "USD", "45", null, null);
}
[Test]
public void registerUninstall_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
byte[] token = System.Text.Encoding.UTF8.GetBytes("740f4707 bebcf74f 9b7c25d4 8e335894 5f6aa01d a5ddb387 462c7eaf 61bb78ad");
AppsFlyer.registerUninstall(token);
AppsFlyerMOCKInterface.Received().registerUninstall(token);
}
[Test]
public void handleOpenUrl_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.handleOpenUrl("www.test.com", "appTest", "test");
AppsFlyerMOCKInterface.Received().handleOpenUrl("www.test.com", "appTest", "test");
}
[Test]
public void waitForATTUserAuthorizationWithTimeoutInterval_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.waitForATTUserAuthorizationWithTimeoutInterval(30);
AppsFlyerMOCKInterface.Received().waitForATTUserAuthorizationWithTimeoutInterval(30);
}
[Test]
public void setCurrentDeviceLanguage_called()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.setCurrentDeviceLanguage("en");
AppsFlyerMOCKInterface.Received().setCurrentDeviceLanguage("en");
}
[Test]
public void disableSKAdNetwork_called_true()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.disableSKAdNetwork(true);
AppsFlyerMOCKInterface.Received().disableSKAdNetwork(true);
}
[Test]
public void disableSKAdNetwork_called_false()
{
var AppsFlyerMOCKInterface = Substitute.For<IAppsFlyerIOSBridge>();
AppsFlyer.instance = AppsFlyerMOCKInterface;
AppsFlyer.disableSKAdNetwork(false);
AppsFlyerMOCKInterface.Received().disableSKAdNetwork(false);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b1a24aa01166451d804d7c03c14a3db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,7 +2,7 @@
"name": "appsflyer-unity-plugin",
"displayName": "AppsFlyer",
"description": "AppsFlyer Unity plugin",
"version": "6.17.5",
"version": "6.14.0",
"unity": "2019.4",
"license": "MIT"
}

Some files were not shown because too many files have changed in this diff Show More