원스토어 결제

This commit is contained in:
Ino 2025-09-07 15:59:30 +09:00
parent 97c5381fea
commit e07d40d684
121 changed files with 1916 additions and 586 deletions

View File

@ -51,7 +51,7 @@ public static class AutoBuild
[MenuItem("AutoBuild/EmptySymbol")]
static void EmptySymbol()
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "");
PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Android, "");
}
[MenuItem("AutoBuild/Set_AndroidKeyStore")]
static void Set_AndroidKeyStore()
@ -73,13 +73,24 @@ public static class AutoBuild
BuildStart("AndroidData/GoStop_Test_" + Application.version + "_(" + PlayerSettings.Android.bundleVersionCode + ").apk", BuildTarget.Android);
PlayerSettings.bundleVersion = curVer;
}
[MenuItem("AutoBuild/Build OneStore APK")]
static void Build_APK_OneStore()
{
Common(BuildTargetGroup.Android, BuildTarget.Android);
//PlayerSettings.Android.minSdkVersion = AndroidSdkVersions.AndroidApiLevel23;
PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Android, "OneStore");
EditorUserBuildSettings.buildAppBundle = false;
BuildStart("AndroidData/GoStop_Test_" + Application.version + "_(" + PlayerSettings.Android.bundleVersionCode + ").apk", BuildTarget.Android);
EmptySymbol();
}
[MenuItem("AutoBuild/Build Live AAB")]
static void Build_AAB()
{
++PlayerSettings.Android.bundleVersionCode;
Common(BuildTargetGroup.Android, BuildTarget.Android);
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "FGB_LIVE");
PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Android, "");
EditorUserBuildSettings.buildAppBundle = true;
BuildStart("AndroidData/GoStop_Live_" + Application.version + "_(" + PlayerSettings.Android.bundleVersionCode + ").aab", BuildTarget.Android);
EmptySymbol();

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 7ab68dd3fa82e4b4eb8e4795d6fbb273
guid: f73007bb22bf455458373bb61c6817cb
labels:
- gvh
- gvh_version-9.5.0

View File

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

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: e87bd305d2c974f279321dd6dbe03ec6
folderAsset: yes
timeCreated: 1458317573
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,24 +0,0 @@
using UnityEngine;
using UnityEditor;
using System.IO;
#if UNITY_EDITOR
[InitializeOnLoad]
public class AndroidKeystoreLoader
{
static AndroidKeystoreLoader()
{
string keystorePass, keyaliasName, keyaliasPass;
keystorePass = KeystoreHelper.ReadPrefs(KeystoreHelper.KEYSTOREPASS);
keyaliasName = KeystoreHelper.ReadPrefs(KeystoreHelper.KEYALIASNAME);
keyaliasPass = KeystoreHelper.ReadPrefs(KeystoreHelper.KEYALIASPASS);
PlayerSettings.Android.keystorePass = keystorePass;
PlayerSettings.Android.keyaliasName = keyaliasName;
PlayerSettings.Android.keyaliasPass = keyaliasPass;
}
}
#endif

View File

@ -1,91 +0,0 @@
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
using System.Text;
using System.Reflection;
class KeystoreHelper : EditorWindow {
public const string KEYSTOREPASS = "AKS_keystorePass_";
public const string KEYALIASNAME = "AKS_keyaliasName_";
public const string KEYALIASPASS = "AKS_keyaliasPass_";
private string keystorePass = "";
private string keyaliasName = "";
private string keyaliasPass = "";
[MenuItem ("Window/Keystore Helper")]
public static void ShowWindow () {
EditorWindow.GetWindow(typeof(KeystoreHelper));
}
void OnEnable() {
keystorePass = ReadPrefs(KEYSTOREPASS);
keyaliasName = ReadPrefs(KEYALIASNAME);
keyaliasPass = ReadPrefs(KEYALIASPASS);
PlayerSettings.Android.keystorePass = keystorePass;
PlayerSettings.Android.keyaliasName = keyaliasName;
PlayerSettings.Android.keyaliasPass = keyaliasPass;
}
void OnDisable() {
WritePrefs(KEYSTOREPASS, keystorePass);
WritePrefs(KEYALIASNAME, keyaliasName);
WritePrefs(KEYALIASPASS, keyaliasPass);
PlayerSettings.Android.keystorePass = keystorePass;
PlayerSettings.Android.keyaliasName = keyaliasName;
PlayerSettings.Android.keyaliasPass = keyaliasPass;
}
public static string ReadPrefs(string _key) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
string key = _key+Md5Sum(codeBase);
return Decode(EditorPrefs.GetString(key, ""));
}
public static void WritePrefs(string _key, string value) {
string codeBase = Assembly.GetExecutingAssembly().CodeBase;
string key = _key + Md5Sum(codeBase);
EditorPrefs.SetString(key, Encode(value));
}
void OnGUI () {
GUILayout.Label ("Android Keystore", EditorStyles.boldLabel);
keystorePass = EditorGUILayout.PasswordField ("Keystore Password", keystorePass);
keyaliasName = EditorGUILayout.TextField ("Key Alias Name", keyaliasName);
keyaliasPass = EditorGUILayout.PasswordField ("Key Alias Password", keyaliasPass);
}
private static string Md5Sum(string strToEncrypt)
{
System.Text.UTF8Encoding ue = new System.Text.UTF8Encoding();
byte[] bytes = ue.GetBytes(strToEncrypt);
// encrypt bytes
System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] hashBytes = md5.ComputeHash(bytes);
// Convert the encrypted bytes back to a string (base 16)
string hashString = "";
for (int i = 0; i < hashBytes.Length; i++)
{
hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
}
return hashString.PadLeft(32, '0');
}
private static string Encode(string inputText) {
byte[] bytesToEncode = Encoding.UTF8.GetBytes (inputText);
return Convert.ToBase64String (bytesToEncode);
}
private static string Decode(string encodedText) {
byte[] decodedBytes = Convert.FromBase64String (encodedText);
return Encoding.UTF8.GetString (decodedBytes);
}
}

Binary file not shown.

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 4a989aa6f253d4fba9884871e22997d1
timeCreated: 1460876014
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -28,6 +28,5 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
데이터 저장, 불러오기 (구글 플레이센터로 변경) - 확인 필요
원스토어 검수 빌드 후 원스토어 결제 추가
원스토어 검수 빌드 후 원스토어 결제 추가
한달에 70만원 초과 구매 체크

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a38b4c520db724670b30b04da0be7c01
guid: 204651c98036c4d3daa51358c1f71e07
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9b15d8887bf22409089afedfc3215b46
guid: c912baa65aed04a0695d7deeb9e26f9f
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5e737082478de4f8cb850a38f00c2488
guid: 45866b059e1ac4748ac1f5997e7563ac
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,8 +1,8 @@
<dependencies>
<androidPackages>
<repositories>
<repository>https://repo.onestore.co.kr/repository/onestore-sdk-public</repository>
<repository>https://repo.onestore.net/repository/onestore-sdk-public</repository>
</repositories>
<androidPackage spec="com.onestorecorp.sdk:sdk-licensing:2.1.0" />
<androidPackage spec="com.onestorecorp.sdk:sdk-licensing:2.2.1" />
</androidPackages>
</dependencies>

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 22835d64d81424a4a973b971bdf001f7
guid: 3a871155c97ce4cb0befa3037f342edc
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5368639f6f8b94a3fa108ae0442ac776
guid: e8430a97b91bb4c4986ed7a70c40bfd3
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d25a6068257e1484a80f45db0abfa7a2
guid: 5ca8f8998e0534e088a4771ae1fc1909
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b0be8215d7b2841d2ba7a2c0920485b1
guid: 0c113be81d6ec415a800631bc1b1c9e2
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 43b9607f255614a8d8bc960e200a6eaf
guid: 586e06c7044794af6b82b88f0dcf0b6b
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c0c7575908d62447bbbb66c6f846f505
guid: 2812dfc9335dd4ca680f70b95551cf18
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2272b91e6c0354de4af84ac36237d115
guid: 51ff5605005df46c3aeaed3994057a54
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -4,7 +4,7 @@ namespace OneStore.Alc.Internal
{
internal static class Constants
{
public const string Version = "2.1.0";
public const string Version = "2.2.1";
public static readonly TimeSpan AsyncTimeout = TimeSpan.FromMilliseconds(30000);
public const string AppLicenseChecker = "com.onestore.extern.licensing.AppLicenseCheckerImpl";

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ad6e43bd6f5be4ade91f9331f2143270
guid: 9b59d64b9c466404faf6ad9e76a2bd69
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c1dd2cc05dab6464e8438052ec61fb93
guid: 6a637d767706346c2b6ccc07ce3dc9ca
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -4,31 +4,38 @@ using System;
using OneStore.Common;
using OneStore.Alc.Internal;
using UnityEngine;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Alc
{
public class OneStoreAppLicenseCheckerImpl
{
private AndroidJavaObject _appLicenseChecker;
private readonly OneStoreLogger _logger;
private readonly string _licenseKey;
private LicenseCheckerListener _listener;
private ILicenseCheckCallback _callback;
public OneStoreAppLicenseCheckerImpl(string licenseKey) {
if (Application.platform != RuntimePlatform.Android) {
/// <summary>
/// Initializes the ONE store App License Checker with the provided license key.
/// Ensures that the platform is Android before proceeding.
/// </summary>
/// <param name="licenseKey">The license key required for validation.</param>
public OneStoreAppLicenseCheckerImpl(string licenseKey)
{
if (Application.platform != RuntimePlatform.Android)
{
throw new PlatformNotSupportedException("Operation is not supported on this platform.");
}
_logger = new OneStoreLogger();
_licenseKey = licenseKey;
}
// ALC 연결 초기화
public void Initialize(ILicenseCheckCallback callback) {
/// <summary>
/// Initializes the App License Checker (ALC) and establishes a connection.
/// </summary>
/// <param name="callback">Callback interface for handling license check results.</param>
public void Initialize(ILicenseCheckCallback callback)
{
_callback = callback;
_appLicenseChecker = new AndroidJavaObject(Constants.AppLicenseChecker, _licenseKey);
@ -38,6 +45,7 @@ namespace OneStore.Alc
_listener.Denied += OnDenied;
_listener.Error += OnError;
// Sets up the license checker with the Unity activity context and the listener.
_appLicenseChecker.Call(
Constants.AppLicenseCheckerSetupMethod,
context,
@ -45,37 +53,63 @@ namespace OneStore.Alc
);
}
// Cached API 호출
// 캐시된 라이센스를 이용할 경우 사용한다.
public void QueryLicense() {
_logger.Log("do queryLicense");
/// <summary>
/// Calls the Cached API to query the license.
/// Uses cached license information when available.
/// </summary>
public void QueryLicense()
{
Logger.Log("do queryLicense");
_appLicenseChecker.Call(Constants.AppLicenseCheckerQueryLicenseMethod);
}
// Non-Cached API 호출
// 캐시된 라이센스를 이용하지 않고 사용할 경우 사용한다.
public void StrictQueryLicense() {
_logger.Log("do strictQueryLicense");
/// <summary>
/// Calls the Non-Cached API to query the license.
/// Does not use cached license information and forces a fresh validation.
/// </summary>
public void StrictQueryLicense()
{
Logger.Log("do strictQueryLicense");
_appLicenseChecker.Call(Constants.AppLicenseCheckerStrickQueryLicenseMethod);
}
// ALC 연결 해제
public void Destroy() {
_logger.Log("do destroy");
/// <summary>
/// Disconnects and releases resources related to the App License Checker (ALC).
/// </summary>
public void Destroy()
{
Logger.Log("do destroy");
_appLicenseChecker.Call(Constants.AppLicenseCheckerDestroy);
}
private void OnGranted(string license, string signature) {
/// <summary>
/// Callback triggered when the license is successfully granted.
/// </summary>
/// <param name="license">The granted license key.</param>
/// <param name="signature">The license signature for verification.</param>
private void OnGranted(string license, string signature)
{
_callback.OnGranted(license, signature);
}
private void OnDenied() {
/// <summary>
/// Callback triggered when the license validation is denied.
/// </summary>
private void OnDenied()
{
_callback.OnDenied();
}
private void OnError(int code, string message) {
/// <summary>
/// Callback triggered when an error occurs during license validation.
/// </summary>
/// <param name="code">The error code returned.</param>
/// <param name="message">A message describing the error.</param>
private void OnError(int code, string message)
{
_callback.OnError(code, message);
}
}
}
#endif
#endif

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b3b4c2e72d54a47089f9924b864c1073
guid: 67da1af35e6ff4d7696b6e38fac9e567
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 23c72fe8a92df4875bbe0b34fd3baf78
guid: 7c7dbd20a57f34f549514b36c28c2464
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 80a4911cfab194fc0bfd87f745cba5de
guid: 865f0763c3a514f71952dafdcdd70025
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 88896edd6f04a49948dc1bce81c814ac
guid: 5fddbfe50d40c4e42b3ef7bc7b0a4007
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 37cb852ebcdd54283a52d99a5cc86f96
guid: 5ea618f5f42c04bb4b0fbe8a4e06bc28
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 59f7259c06ccb4c589db49c97df7a643
guid: 4591019618cbc44098c4780faff6edab
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 67a438630cf644db58c0badde6a4d803
guid: d281333515ded4eb491b21fc3cd2ca4f
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,23 +1,16 @@
using System;
using UnityEngine;
using OneStore.Common;
using OneStore.Auth;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Auth.Internal
{
public class AuthHelper
public static class AuthHelper
{
private readonly OneStoreLogger _logger;
public AuthHelper(OneStoreLogger logger)
{
_logger = logger;
}
/// <summary>
/// Parses the SignIn results returned by Gaa Auth Client.
/// </summary>
/// <returns> The SignInResult that indicates the outcome of the Java SignInResult. </returns>
public SignInResult ParseJavaSignInpResult(AndroidJavaObject javaSignInResult)
public static SignInResult ParseJavaSignInpResult(AndroidJavaObject javaSignInResult)
{
var code = javaSignInResult.Call<int>("getCode");
var message = javaSignInResult.Call<string>("getMessage");
@ -29,7 +22,7 @@ namespace OneStore.Auth.Internal
/// Parses the SignInResults returned by Gaa SignIn Client.
/// </summary>
/// <returns>Returns the code value of the SignInResult in ResponseCode.</returns>
public ResponseCode GetResponseCodeFromSignInResult(SignInResult signInResult)
public static ResponseCode GetResponseCodeFromSignInResult(SignInResult signInResult)
{
var resultResponseCode = ResponseCode.RESULT_ERROR;
try
@ -38,11 +31,11 @@ namespace OneStore.Auth.Internal
}
catch (ArgumentNullException)
{
_logger.Error("Missing response code, return ResponseCode.RESULT_ERROR.");
Logger.Error("Missing response code, return ResponseCode.RESULT_ERROR.");
}
catch (ArgumentException)
{
_logger.Error("Unknown response code {0}, return ResponseCode.RESULT_ERROR.", signInResult.Code);
Logger.Error("Unknown response code {0}, return ResponseCode.RESULT_ERROR.", signInResult.Code);
}
return resultResponseCode;
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3f5d217fdb8804701ba32cd03e453cee
guid: a1f867c7b662d4b22933c64ffc9ce2e0
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -4,7 +4,7 @@ namespace OneStore.Auth.Internal
{
internal static class Constants
{
public const string Version = "1.1.2";
public const string Version = "1.2.1";
public static readonly TimeSpan AsyncTimeout = TimeSpan.FromMilliseconds(30000);

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 027b0062071924b73be50dbe31e00d63
guid: 58c615962d2e144dea4a9517a4ad1d80
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b51bc302fe47f44ffba8d75820917107
guid: 22253330bda434f55a0401ca5944c271
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -5,35 +5,54 @@ using System;
using OneStore.Common;
using OneStore.Auth.Internal;
using UnityEngine;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Auth
{
public class OneStoreAuthClientImpl
{
private AndroidJavaObject _signInClient;
private readonly OneStoreLogger _logger;
private readonly AuthHelper _authHelper;
/// <summary>
/// Initializes the ONE store authentication client.
/// Ensures that the platform is Android before proceeding.
/// </summary>
public OneStoreAuthClientImpl()
{
if (Application.platform != RuntimePlatform.Android)
{
throw new PlatformNotSupportedException("Operation is not supported on this platform.");
}
_logger = new OneStoreLogger();
_authHelper = new AuthHelper(_logger);
}
/// <summary>
/// Attempts to sign in silently using a stored login token.
/// This method can only be called in the background.
/// </summary>
/// <param name="callback">Callback function to handle the sign-in result.</param>
public void SilentSignIn(Action<SignInResult> callback)
{
SignInInternal(true, callback);
}
/// <summary>
/// Attempts to sign in using the stored login token first.
/// If silent login fails, a login screen is displayed to prompt the user to log in.
/// This method must be called in the foreground.
/// </summary>
/// <param name="callback">Callback function to handle the sign-in result.</param>
public void LaunchSignInFlow(Action<SignInResult> callback)
{
SignInInternal(false, callback);
}
/// <summary>
/// Handles the sign-in process.
/// If `isSilent` is true, a silent login is attempted.
/// If `isSilent` is false or the silent login fails, a login screen is displayed.
/// </summary>
/// <param name="isSilent">Determines whether the sign-in should be silent.</param>
/// <param name="callback">Callback function to return the sign-in result.</param>
private void SignInInternal(bool isSilent, Action<SignInResult> callback)
{
var context = JniHelper.GetApplicationContext();
@ -42,11 +61,11 @@ namespace OneStore.Auth
var authListener = new OnAuthListener();
authListener.OnAuthResponse += (javaSignInResult) => {
var signInResult = _authHelper.ParseJavaSignInpResult(javaSignInResult);
var responseCode = _authHelper.GetResponseCodeFromSignInResult(signInResult);
var signInResult = AuthHelper.ParseJavaSignInpResult(javaSignInResult);
var responseCode = AuthHelper.GetResponseCodeFromSignInResult(signInResult);
if (responseCode != ResponseCode.RESULT_OK)
{
_logger.Error("Failed to signIn with error code {0} and message: {1}", signInResult.Code, signInResult.Message);
Logger.Error("Failed to signIn with error code {0} and message: {1}", signInResult.Code, signInResult.Message);
}
RunOnMainThread(() => callback?.Invoke(signInResult));
@ -95,6 +114,10 @@ namespace OneStore.Auth
// );
// }
/// <summary>
/// Runs the provided action on the main thread.
/// </summary>
/// <param name="action">The action to execute on the main thread.</param>
private void RunOnMainThread(Action action)
{
OneStoreDispatcher.RunOnMainThread(() => action());

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 280e837976a7c4756b3e0192e35e2f47
guid: a9230c0d3e676444fbaf4c6c5a3307e5
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b25dbdcc8597f45a8a4b91aabb30699b
guid: 2dce71f6a2a1f4749b65c5fd1f63d10d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: dc2db6a2e4d914f69887793389752e1a
guid: 4915e45c611874c7ebd04bbfe73351cd
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: dbbed83b03870418fadb93987e8444e3
guid: 1a1f1e2fd32df4b41aebf90b5a83074b
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9ecc0d0eb034b4728b627f98121eaf98
guid: 84eca2a2ddc984d44a706d98801f3906
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -22,3 +22,7 @@
-keep class com.gaa.sdk.base.ResultListener { *; }
-keep class com.gaa.sdk.base.Utils { *; }
-keep class com.gaa.sdk.base.StoreEnvironment (
public *;
)

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5bd27940027ea4339a48c16d7397de38
guid: 086e6716fd1c140ab8c1900df67960d6
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 20d274d91b62b40aa87642481e9e31b3
guid: 0e7f3f62a1d934afa8c5a50871b4e961
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 51b187192c98746c38f9f3fef6d5f77d
guid: c890dfc1dbfab44d9b5708963c3ac833
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ccbf429e4c2a44d0a8b32088c56ac6b3
guid: 5a270edd7fdb445bdac56df23638c238
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -0,0 +1,15 @@
using System;
namespace OneStore.Common.Internal
{
internal static class Constants
{
public const string Version = "1.2.1";
public const string SdkLogger = "com.gaa.sdk.base.Logger";
public const string SdkLoggerSetLogLevelMethod = "setLogLevel";
public const string ResultListener = "com.gaa.sdk.base.ResultListener";
public const string StoreEnvironment = "com.gaa.sdk.base.StoreEnvironment";
public const string StoreEnvironmentGetStoreTypeMethod = "getStoreType";
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 9d86995d9257a440c907fae32aa2fa18
timeCreated: 1458309246
licenseType: Store
guid: 26e8301dedd9c4d8cb5c5becbeb6089d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@ -7,7 +7,7 @@ namespace OneStore.Common.Internal
{
public event Action<int, string> OnResponse = delegate { };
public ResultListener() : base("com.gaa.sdk.base.ResultListener") { }
public ResultListener() : base(Constants.ResultListener) { }
void onResponse(int code, string message)
{

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 12d01ff0c86d5497a9748ae538253a5f
guid: 2cf2f0ceed6a040e68e5620fcb8f7b42
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -2,7 +2,7 @@ using UnityEngine;
#if UNITY_ANDROID || !UNITY_EDITOR
namespace OneStore.Common
{
public class JniHelper
public static class JniHelper
{
/// <summary>
/// Returns the Android activity context of the Unity app.

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4e1f0f7aabbc646afa163a77e8d8910f
guid: 97c7b848926a1402e8b15c9348a47853
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3492da76c2c644025b0f367f039ed0cd
guid: 1d74f11dbf39a4acc97dc280c1e23d1b
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,25 +1,45 @@
using UnityEngine;
using OneStore.Common.Internal;
using System;
namespace OneStore.Common
{
public class OneStoreLogger
public static class OneStoreLogger
{
private const string TAG = "ONE Store: ";
private readonly ILogger _logger = Debug.unityLogger;
private static readonly ILogger _logger = Debug.unityLogger;
private static int _logLevel = 4; // default: 4 (android.util.Log.INFO)
/// <summary>
/// Logs a formatted message with ILogger.
/// Logs a verbose-level message (for detailed debugging).
/// This message will only be logged if log level is 2 or higher.
/// </summary>
public void Log(string format, params object[] args)
public static void Verbose(string format, params object[] args)
{
_logger.LogFormat(LogType.Log, TAG + format, args);
if (_logLevel >= 2)
{
_logger.LogFormat(LogType.Log, TAG + format, args);
}
}
/// <summary>
/// Logs a standard log message (informational).
/// This message will only be logged if log level is 4 or higher.
/// </summary>
public static void Log(string format, params object[] args)
{
if (_logLevel >= 4)
{
_logger.LogFormat(LogType.Log, TAG + format, args);
}
}
/// <summary>
/// Logs a formatted warning message with ILogger.
/// </summary>
public void Warning(string format, params object[] args)
public static void Warning(string format, params object[] args)
{
_logger.LogFormat(LogType.Warning, TAG + format, args);
}
@ -27,11 +47,19 @@ namespace OneStore.Common
/// <summary>
/// Logs a formatted error message with ILogger.
/// </summary>
public void Error(string format, params object[] args)
public static void Error(string format, params object[] args)
{
_logger.LogFormat(LogType.Error, TAG + format, args);
}
/// <summary>
/// Logs an exception with full stack trace.
/// </summary>
public static void Exception(Exception exception)
{
_logger.LogException(exception);
}
/// <summary>
/// Sets the log level of the library For the convenience of development.<br/>
/// Caution! When deploying an app, you must set the logging level to its default value.<br/>
@ -40,8 +68,18 @@ namespace OneStore.Common
/// <param name="level">default: 4 (android.util.Log.INFO)</param>
public static void SetLogLevel(int level)
{
var sdkLogger = new AndroidJavaObject("com.gaa.sdk.base.Logger");
sdkLogger.CallStatic("setLogLevel", level);
_logLevel = level;
var sdkLogger = new AndroidJavaObject(Constants.SdkLogger);
sdkLogger.CallStatic(Constants.SdkLoggerSetLogLevelMethod, level);
}
/// <summary>
/// Enables or disables detailed debug logging for development purposes.
/// When enabled, sets log level to VERBOSE (2); otherwise, sets it to INFO (4).
/// </summary>
public static void EnableDebugLog(bool enable)
{
SetLogLevel(enable ? 2 : 4);
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5cc3b668dd9c3455fa9172b1636ccfef
guid: 793d3dd6ce8fa47229c0da7e96c453d2
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 995455e74dfb14e2b9d0c04ffee2da4b
guid: 31f0bcacb83d74c12a02c326642286f7
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,36 @@
using UnityEngine;
using OneStore.Common.Internal;
using System;
namespace OneStore.Common
{
/// <summary>
/// The StoreEnvironment class is responsible for determining the type of store where the app is installed.
/// </summary>
public class StoreEnvironment
{
/// <summary>
/// Represents the StoreEnvironment Java class.
/// </summary>
private static readonly AndroidJavaClass storeEnvironmentClass = new AndroidJavaClass(Constants.StoreEnvironment);
/// <summary>
/// Determines the store type where the app was installed.<br/>
/// <br/>
/// @return One of the following values: <br/>
/// - <see cref="StoreType.ONESTORE"/>: Installed from ONE Store or a trusted store. <br/>
/// - <see cref="StoreType.VENDING"/>: Installed from Google Play Store. <br/>
/// - <see cref="StoreType.ETC"/>: Installed from other stores. <br/>
/// - <see cref="StoreType.UNKNOWN"/>: Store information is unknown. <br/>
/// </summary>
/// <returns>A <see cref="StoreType"/> value representing the app's installation source.</returns>
public static StoreType GetStoreType()
{
var storeTypeValue = storeEnvironmentClass.CallStatic<int>(
Constants.StoreEnvironmentGetStoreTypeMethod,
JniHelper.GetApplicationContext()
);
return Enum.IsDefined(typeof(StoreType), storeTypeValue) ? (StoreType)storeTypeValue : StoreType.UNKNOWN;
}
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: ae0d8bec6856f45db890916bc274a00e
timeCreated: 1458310695
licenseType: Store
guid: 21c8b93fdb49b465fa4a680ebe84c984
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@ -0,0 +1,28 @@
namespace OneStore.Common
{
/// <summary>
/// It is a constant that represents the type of store where the app is installed.
/// </summary>
public enum StoreType
{
/// <summary>
/// Unable to determine the store (APK sideloaded, unknown source)
/// </summary>
UNKNOWN = 0,
/// <summary>
/// Installed from ONE Store (or a trusted store defined in Developer Options)
/// </summary>
ONESTORE = 1,
/// <summary>
/// Installed from Google Play Store
/// </summary>
VENDING = 2,
/// <summary>
/// Installed from other stores (Samsung Galaxy Store, Amazon Appstore, etc.)
/// </summary>
ETC = 3,
}
}

View File

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 83431c64bc5d94a78aacc7b562db1795
guid: 864ad717c4e614bc0a9db4fa2c3aae0e
DefaultImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 541cf0e43641147f394d8a32b3089905
guid: 635a0593099484eeb86843341155efba
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 590b8eb656428488cbe0514881515afa
guid: c3fabc56fd842463bb99c8e57c758680
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,8 +1,8 @@
<dependencies>
<androidPackages>
<repositories>
<repository>https://repo.onestore.co.kr/repository/onestore-sdk-public</repository>
<repository>https://repo.onestore.net/repository/onestore-sdk-public</repository>
</repositories>
<androidPackage spec="com.onestorecorp.sdk:sdk-iap:21.01.00" />
<androidPackage spec="com.onestorecorp.sdk:sdk-iap:21.02.01" />
</androidPackages>
</dependencies>

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8abdc96158a994b00aa31af2ac0db879
guid: ab974bc6d99964828895de1da9e0c67f
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8580b732c1a9046189d398e4d902d356
guid: a8be89816d0a4449cafdf9261fa82e9a
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d78bc3a00355f497383963c097da7fb1
guid: 2045a161414604bdfbb2c575c52b481b
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 453ae0db519424f768b7e47dd4d770de
guid: 24f8ee9b2400c4066b0213141214b350
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6579c52c9a84e40b19c6950f8940812e
guid: 4a46668187d28471caa65849b3f5d7d0
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 44f6c5580605e496790f90e64628d10f
guid: 24484c467aa7d4ce181da6d1e4725c3a
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 087121c8ffc51413ea4b6cfbcd4da6f1
guid: 7059e457b5e6b4063940b27d33e320ce
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 45a88cb38305847f993f7a92dc762864
guid: 9747ed7e096c34dcc8c17ecb12d0319a
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 0930120a3615c46759a4ba4487fbf6ea
guid: a85708d2f7eb44faf95f4134caeeab20
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d92232be28e1b42c39e526e00611a8de
guid: 8646eff21cc32420c8226a4da4c4ae39
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 75bcc9e21ba7e4b2aa4a48ff1f7f8776
guid: a74d8f7ec29824f25b20deb0e8e1c9b2
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -4,7 +4,7 @@ namespace OneStore.Purchasing.Internal
{
internal static class Constants
{
public const string Version = "21.01.00";
public const string Version = "21.02.01";
public static readonly TimeSpan AsyncTimeout = TimeSpan.FromMilliseconds(30000);

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 8e64f61104c86498aa934fa89bd417f6
guid: 1461d8108bf7e4400996432a42335571
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c06b2c2e4a7364c3696ba84e0be1e9a9
guid: d46db22ec1a8144ed9dad5701acca54e
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -3,25 +3,21 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using OneStore.Common;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Purchasing.Internal
{
/// <summary>
/// A collection of utils methods to process AndroidJavaObject returned by Gaa Purchasing Library.
/// </summary>
public class IapHelper
public static class IapHelper
{
private readonly OneStoreLogger _logger;
public IapHelper(OneStoreLogger logger)
{
_logger = logger;
}
/// <summary>
/// Parses the Iap results returned by Gaa Purchasing Client.
/// </summary>
/// <returns> The IapResult that indicates the outcome of the Java IapResult. </returns>
public IapResult ParseJavaIapResult(AndroidJavaObject javaIapResult)
public static IapResult ParseJavaIapResult(AndroidJavaObject javaIapResult)
{
var code = javaIapResult.Call<int>("getResponseCode");
var message = javaIapResult.Call<string>("getMessage");
@ -33,7 +29,7 @@ namespace OneStore.Purchasing.Internal
/// Parses the IapResults returned by Gaa Purchasing Client.
/// </summary>
/// <returns>Returns the code value of the IapResult in ResponseCode.</returns>
public ResponseCode GetResponseCodeFromIapResult(IapResult iapResult)
public static ResponseCode GetResponseCodeFromIapResult(IapResult iapResult)
{
var resultResponseCode = ResponseCode.RESULT_ERROR;
try
@ -42,11 +38,11 @@ namespace OneStore.Purchasing.Internal
}
catch (ArgumentNullException)
{
_logger.Error("Missing response code, return ResponseCode.RESULT_ERROR.");
Logger.Error("Missing response code, return ResponseCode.RESULT_ERROR.");
}
catch (ArgumentException)
{
_logger.Error("Unknown response code {0}, return ResponseCode.RESULT_ERROR.", iapResult.Code);
Logger.Error("Unknown response code {0}, return ResponseCode.RESULT_ERROR.", iapResult.Code);
}
return resultResponseCode;
}
@ -55,12 +51,12 @@ namespace OneStore.Purchasing.Internal
/// Parses the ProductDetail list results returned by the Gaa Purchasing Library.
/// </summary>
/// <returns>An IEnumerable of <cref="SkuDetails"/>. The IEnumerable could be empty.</returns>
public IEnumerable<ProductDetail> ParseProductDetailsResult(AndroidJavaObject javaIapResult, AndroidJavaObject productDetailsList)
public static IEnumerable<ProductDetail> ParseProductDetailsResult(AndroidJavaObject javaIapResult, AndroidJavaObject productDetailsList)
{
var iapResult = ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Warning("Failed to retrieve products information! Error code {0}, message: {1}.",
Logger.Warning("Failed to retrieve products information! Error code {0}, message: {1}.",
iapResult.Code, iapResult.Message);
return Enumerable.Empty<ProductDetail>();
}
@ -71,6 +67,8 @@ namespace OneStore.Purchasing.Internal
{
var javaProductDetail = productDetailsList.Call<AndroidJavaObject>("get", i);
var originalJson = javaProductDetail.Call<string>(Constants.ProductDetailGetOriginalJson);
Logger.Verbose("ParseProductDetailsResult: originalJson: {0}", originalJson);
ProductDetail productDetail;
if (ProductDetail.FromJson(originalJson, out productDetail))
{
@ -78,7 +76,7 @@ namespace OneStore.Purchasing.Internal
}
else
{
_logger.Warning("Failed to parse productDetails {0} ", originalJson);
Logger.Warning("Failed to parse productDetails {0} ", originalJson);
}
}
@ -89,7 +87,7 @@ namespace OneStore.Purchasing.Internal
/// Parses the Java list of purchaseData list returned by the Gaa Purchasing Library.
/// </summary>
/// <returns>An IEnumerable of <cref="PurchaseData"/>. The IEnumerable could be empty.</returns>
public IEnumerable<PurchaseData> ParseJavaPurchasesList(AndroidJavaObject javaPurchasesList)
public static IEnumerable<PurchaseData> ParseJavaPurchasesList(AndroidJavaObject javaPurchasesList)
{
var parsedPurchasesList = new List<PurchaseData>();
var size = javaPurchasesList.Call<int>("size");
@ -98,6 +96,8 @@ namespace OneStore.Purchasing.Internal
var javaPurchase = javaPurchasesList.Call<AndroidJavaObject>("get", i);
var originalJson = javaPurchase.Call<string>(Constants.PurchaseDataGetOriginalJsonMethod);
var signature = javaPurchase.Call<string>(Constants.PurchaseDataGetSignatureMethod);
Logger.Verbose("ParseJavaPurchasesList: originalJson: {0}, signature: {1}", originalJson, signature);
PurchaseData purchaseData;
if (PurchaseData.FromJson(originalJson, signature, out purchaseData))
{
@ -105,23 +105,22 @@ namespace OneStore.Purchasing.Internal
}
else
{
_logger.Warning("Failed to parse purchase {0} ", originalJson);
Logger.Warning("Failed to parse purchase {0} ", originalJson);
}
}
return parsedPurchasesList;
}
public PurchaseData ParseJavaPurchaseData(AndroidJavaObject javaPurchaseData)
public static PurchaseData ParseJavaPurchaseData(AndroidJavaObject javaPurchaseData)
{
var originalJson = javaPurchaseData.Call<string>("getOriginalJson");
var signature = javaPurchaseData.Call<string>("getSignature");
PurchaseData purchaseData;
if (PurchaseData.FromJson(originalJson, signature, out purchaseData))
var originalJson = javaPurchaseData.Call<string>(Constants.PurchaseDataGetOriginalJsonMethod);
var signature = javaPurchaseData.Call<string>(Constants.PurchaseDataGetSignatureMethod);
if (PurchaseData.FromJson(originalJson, signature, out PurchaseData purchaseData))
{
return purchaseData;
}
return null;
}
@ -146,7 +145,7 @@ namespace OneStore.Purchasing.Internal
// var responseCode = GetResponseCodeFromIapResult(iapResult);
// if (responseCode != ResponseCode.RESULT_OK)
// {
// _logger.Error("Failed to retrieve purchases information! Error code {0}, message: {1}.",
// Logger.Error("Failed to retrieve purchases information! Error code {0}, message: {1}.",
// iapResult.Code, iapResult.Message);
// return Enumerable.Empty<PurchaseData>();
// }

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9ff3024883f804d08918d7b14baefb97
guid: 826c4b35303374ef896b7eb23d2a3c16
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ea27d8035db8f4f2c9351525e474d806
guid: 7545e6e6d3d4d447788110bef866985c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c4bf13942066d4db495cb8ac556789ae
guid: c5efe3bf23765426bbaf92f82f5cbd20
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 161f62becd9884402b9930107ce68982
guid: 709f0596b6b8c4b6b9b64b5a836c6b79
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fb1ca6ef17e26421e80445ad6a286245
guid: ef9e60b8602a8459885a544273a4f4da
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 099220b88e8f14b1e8c20a94af76e7a3
guid: a46dd76b0654b4ace8018eed3ad0602c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 32cccf6915cf74937be26ca1674b1e4b
guid: 107a553b495544bfca51807e3ce88f93
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3075a4c0c070a495cb198e9747e9c981
guid: fe478ec6c9d4e4a0db2e2cce3ae4d4cf
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5eabc8ed8e0e340b79d53d6c47f553e1
guid: c214bbaa82deb465c8dc0d33eb013bd1
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 74e6007e0cb824d73803296c1ac942a3
guid: 7f238cae7b9d94d84adda85b667ecdad
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,6 +1,7 @@
using System;
using UnityEngine;
using OneStore.Purchasing.Internal;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Purchasing
{
@ -41,8 +42,10 @@ namespace OneStore.Purchasing
productDetail.JsonProductDetail = jsonProductDetail;
return true;
}
catch (Exception)
catch (Exception ex)
{
Logger.Error("[ProductDetail]: Failed to parse purchase data: {0}", jsonProductDetail);
Logger.Exception(ex);
productDetail = null;
return false;
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4891df32dba4642319f9e2d48e6923a5
guid: cc5c07c0c0c584c418d11f3ce55ef7d8
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 82e8e1ef36f844207ac2a641db1663b2
guid: b25bfa4e2b7454b6cbd096e9abf46540
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -7,10 +7,15 @@ using System.Collections.ObjectModel;
using System.Linq;
using OneStore.Purchasing.Internal;
using OneStore.Common;
using Logger = OneStore.Common.OneStoreLogger;
using UnityEngine;
namespace OneStore.Purchasing
{
/// <summary>
/// Implementation of the One Store IAP client for Unity.
/// This class handles purchase initialization, product queries, purchase flows, and subscription management.
/// </summary>
public class PurchaseClientImpl : IPurchaseExtensions
{
private IPurchaseCallback _callback;
@ -19,8 +24,6 @@ namespace OneStore.Purchasing
private PurchasesUpdatedListener _purchaseUpdatedListener;
private readonly OneStorePurchasingInventory _inventory;
private readonly OneStoreLogger _logger;
private readonly IapHelper _iapHelper;
private volatile string _productInPurchaseFlow;
@ -32,10 +35,16 @@ namespace OneStore.Purchasing
private volatile ConnectionStatus _connectionStatus = ConnectionStatus.DISCONNECTED;
/// <summary>
/// Enum representing the connection status of the purchase client.
/// </summary>
private enum ConnectionStatus {
DISCONNECTED, CONNECTING, CONNECTED,
}
/// <summary>
/// Dictionary to track query purchase status for different product types.
/// </summary>
private volatile Dictionary<ProductType, AsyncRequestStatus> _queryPurchasesCallStatus =
new Dictionary<ProductType, AsyncRequestStatus>
{
@ -44,6 +53,9 @@ namespace OneStore.Purchasing
{ProductType.AUTO, AsyncRequestStatus.Succeed},
};
/// <summary>
/// Dictionary to track query product details status for different product types.
/// </summary>
private volatile Dictionary<ProductType, AsyncRequestStatus> _queryProductDetailsCallStatus =
new Dictionary<ProductType, AsyncRequestStatus>
{
@ -53,11 +65,23 @@ namespace OneStore.Purchasing
{ProductType.ALL, AsyncRequestStatus.Succeed},
};
/// <summary>
/// Enum representing the asynchronous request status.
/// </summary>
private enum AsyncRequestStatus
{
Pending, Failed, Succeed,
}
/// <summary>
/// Initializes a new instance of the `PurchaseClientImpl` class with the specified license key.
/// </summary>
/// <param name="licenseKey">The license key required for authentication with the ONE store IAP SDK.</param>
/// <remarks>
/// - Checks if the application is running on an Android platform; otherwise, throws a `PlatformNotSupportedException`.
/// - Initializes `_inventory` as an instance of `OneStorePurchasingInventory` to manage purchase data.
/// - Stores the provided `licenseKey` in `_licenseKey` for later use.
/// </remarks>
public PurchaseClientImpl(string licenseKey)
{
if (Application.platform != RuntimePlatform.Android)
@ -66,11 +90,21 @@ namespace OneStore.Purchasing
}
_inventory = new OneStorePurchasingInventory();
_logger = new OneStoreLogger();
_iapHelper = new IapHelper(_logger);
_licenseKey = licenseKey;
}
/// <summary>
/// Initializes the ONE store IAP client and establishes a connection to the service.
/// </summary>
/// <param name="callback">The callback interface to handle purchase-related events.</param>
/// <remarks>
/// - Assigns the provided `callback` to `_callback`.
/// - Creates an instance of `_purchaseClient` using the ONE store IAP SDK.
/// - Retrieves the application context using `JniHelper.GetApplicationContext()`.
/// - Initializes `_purchaseUpdatedListener` to handle purchase updates and assigns `ProcessPurchaseUpdatedResult` as the callback.
/// - Calls `_purchaseClient.Call()` to set up the purchase client with the application context, license key, and purchase listener.
/// - Initiates the service connection using `StartConnection()`, and upon successful connection, retrieves the `storeCode`.
/// </remarks>
public void Initialize(IPurchaseCallback callback)
{
_callback = callback;
@ -88,16 +122,30 @@ namespace OneStore.Purchasing
);
StartConnection(()=> {
_logger.Log("Initialize: Successfully connected to the service.");
Logger.Log("Initialize: Successfully connected to the service.");
GetStoreCode();
});
}
/// <summary>
/// Initiates a connection to the ONE store IAP service and processes pending requests.
/// </summary>
/// <param name="action">The action to execute once the connection is successfully established.</param>
/// <remarks>
/// - If the client is already connecting (`ConnectionStatus.CONNECTING`), the action is added to `_requestQueue` and logged.
/// - Updates `_connectionStatus` to `CONNECTING` before starting the connection process.
/// - Creates a `PurchaseClientStateListener` to handle connection events:
/// - `OnServiceDisconnected`: Logs a warning, resets `_productInPurchaseFlow`, and sets `_connectionStatus` to `DISCONNECTED`.
/// - `OnSetupFinished`:
/// - If successful, updates `_connectionStatus` to `CONNECTED`, executes the provided action, and processes queued requests.
/// - If failed, logs an error, clears `_requestQueue`, and triggers `OnSetupFailed` callback with the error result.
/// - Calls `_purchaseClient.Call()` to start the connection process.
/// </remarks>
private void StartConnection(Action action)
{
if (_connectionStatus == ConnectionStatus.CONNECTING) {
_requestQueue.Enqueue(action);
_logger.Log("Client is already in the process of connecting to service.");
Logger.Log("Client is already in the process of connecting to service.");
return;
}
@ -106,14 +154,14 @@ namespace OneStore.Purchasing
var purchaseClientStateListener = new PurchaseClientStateListener();
purchaseClientStateListener.OnServiceDisconnected += () =>
{
_logger.Warning("Client is disconnected from the service.");
Logger.Warning("Client is disconnected from the service.");
_productInPurchaseFlow = null;
_connectionStatus = ConnectionStatus.DISCONNECTED;
};
purchaseClientStateListener.OnSetupFinished += (javaIapResult) =>
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (iapResult.IsSuccessful())
{
_connectionStatus = ConnectionStatus.CONNECTED;
@ -125,7 +173,7 @@ namespace OneStore.Purchasing
}
else
{
_logger.Error("Failed to connect to service with error code '{0}' and message: '{1}'.",
Logger.Error("Failed to connect to service with error code '{0}' and message: '{1}'.",
iapResult.Code, iapResult.Message);
_requestQueue.Clear();
@ -141,6 +189,14 @@ namespace OneStore.Purchasing
);
}
/// <summary>
/// Executes the given action if the IAP service is connected; otherwise, attempts to establish a connection first.
/// </summary>
/// <param name="action">The action to execute once the connection is established.</param>
/// <remarks>
/// - If `_connectionStatus` is `CONNECTED`, the provided action is immediately executed.
/// - If not connected, `StartConnection(action)` is called to establish a connection before executing the action.
/// </remarks>
private void ExecuteService(Action action)
{
if (_connectionStatus == ConnectionStatus.CONNECTED)
@ -153,6 +209,10 @@ namespace OneStore.Purchasing
}
}
/// <summary>
/// Ends the connection to the One Store purchase service.
/// This should be called when the application exits or the purchasing service is no longer needed.
/// </summary>
public void EndConnection()
{
_productInPurchaseFlow = null;
@ -161,6 +221,18 @@ namespace OneStore.Purchasing
_connectionStatus = ConnectionStatus.DISCONNECTED;
}
/// <summary>
/// Retrieves detailed product information for the given product IDs using the ONE store IAP SDK.
/// </summary>
/// <param name="productIds">A collection of product IDs to query.</param>
/// <param name="type">The type of product to query.</param>
/// <remarks>
/// - If there is an ongoing request for the given product type, the function returns without executing a new request.
/// - If product details are already available in the local inventory and match the requested product IDs,
/// the details are returned immediately without making a new request.
/// - Otherwise, a request is constructed using `ProductDetailsParamBuilder` and sent through `_purchaseClient.Call()`.
/// - The result is handled by `ProductDetailsListener`, which triggers the appropriate callback upon response.
/// </remarks>
public void QueryProductDetails(ReadOnlyCollection<string> productIds, ProductType type)
{
if (_queryProductDetailsCallStatus[type] == AsyncRequestStatus.Pending)
@ -203,12 +275,22 @@ namespace OneStore.Purchasing
return IsPurchasingServiceAvailable() ? _inventory.GetAllProductDetails() : null;
}
/// <summary>
/// Queries the non-consumed purchase history for managed products using the ONE store IAP SDK.
/// </summary>
/// <param name="type">The type of product to query. Must not be `ProductType.ALL` or `null`.</param>
/// <remarks>
/// - If `ProductType.ALL` is passed, an error is logged, and the request is rejected, as querying all product types is not supported.
/// - If `type` is `null`, an error is returned indicating an illegal argument.
/// - If there is an ongoing query for the given product type, the function returns without executing another request.
/// - Otherwise, the function sets the request status to `Pending`, then initiates the query using `_purchaseClient.Call()`.
/// </remarks>
public void QueryPurchases(ProductType type)
{
if (ProductType.ALL == type)
{
var message = "ProductType.ALL is not supported. This is supported only by the QueryProductDetails.";
_logger.Error(message);
Logger.Error(message);
RunOnMainThread(() => _callback.OnPurchaseFailed(new IapResult((int) ResponseCode.ERROR_ILLEGAL_ARGUMENT, message)));
return;
}
@ -237,12 +319,22 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Initiates a purchase request for a product using the ONE store IAP SDK.
/// </summary>
/// <param name="purchaseFlowParams">The parameters required to process the purchase, including product ID, type, quantity, and additional metadata.</param>
/// <remarks>
/// - If a purchase is already in progress for another product, the request is rejected, and an error is logged.
/// - The function constructs a `PurchaseFlowParams` object using the provided parameters.
/// - The purchase flow is initiated using the `LaunchPurchaseFlow` method.
/// - The request includes additional metadata such as `ProductName`, `GameUserId`, and whether the product is eligible for promotions.
/// </remarks>
public void Purchase(PurchaseFlowParams purchaseFlowParams)
{
if (_productInPurchaseFlow != null)
{
var message = string.Format("A purchase for {0} is already in progress.", _productInPurchaseFlow);
_logger.Error(message);
Logger.Error(message);
RunOnMainThread(() => _callback.OnPurchaseFailed(new IapResult((int) ResponseCode.RESULT_ERROR, message)));
return;
}
@ -274,12 +366,22 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Updates an existing subscription product using the ONE store IAP SDK.
/// </summary>
/// <param name="purchaseFlowParams">The parameters required to process the subscription update, including product ID, type, developer payload, and proration mode.</param>
/// <remarks>
/// - If a subscription update is already in progress, the request is rejected, and an error is logged.
/// - The function constructs a `PurchaseFlowParams` object using the provided parameters.
/// - A `SubscriptionUpdateParams` object is created to include the proration mode and the old purchase token for the update.
/// - The update request is initiated using the `LaunchPurchaseFlow` method.
/// </remarks>
public void UpdateSubscription(PurchaseFlowParams purchaseFlowParams)
{
if (_productInPurchaseFlow != null)
{
var message = string.Format("The update subscription for {0} is already in progress.", _productInPurchaseFlow);
_logger.Error(message);
Logger.Error(message);
RunOnMainThread(() => _callback.OnPurchaseFailed(new IapResult((int) ResponseCode.RESULT_ERROR, message)));
return;
}
@ -325,6 +427,16 @@ namespace OneStore.Purchasing
purchaseFlowParamsBuilder.Call<AndroidJavaObject>(Constants.BuildMethod));
}
/// <summary>
/// Consumes a purchased managed product using the ONE store IAP SDK.
/// </summary>
/// <param name="purchaseData">The purchase data of the managed product to be consumed.</param>
/// <remarks>
/// - If `purchaseData` is null, the request is rejected, and an error is logged.
/// - The function constructs a `ConsumeParams` object using the provided `purchaseData`.
/// - The consumption request is sent using `_purchaseClient.Call()`.
/// - The result is handled by `ConsumeListener`, which triggers the appropriate callback upon response.
/// </remarks>
public void ConsumePurchase(PurchaseData purchaseData)
{
if (purchaseData == null)
@ -353,6 +465,19 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Acknowledges a purchased non-consumable or subscription product using the ONE store IAP SDK.
/// </summary>
/// <param name="purchaseData">The purchase data of the product to be acknowledged.</param>
/// <param name="type">The type of product being acknowledged (must not be `ProductType.ALL` or `null`).</param>
/// <remarks>
/// - If `purchaseData` is null, the request is rejected, and an error is logged.
/// - If `type` is null, the request is rejected, and an error is logged.
/// - If `type` is `ProductType.ALL`, the request is rejected, as acknowledging all product types is not supported.
/// - The function constructs an `AcknowledgeParams` object using the provided `purchaseData`.
/// - The acknowledgment request is sent using `_purchaseClient.Call()`.
/// - The result is handled by `AcknowledgeListener`, which triggers the appropriate callback upon response.
/// </remarks>
public void AcknowledgePurchase(PurchaseData purchaseData, ProductType type)
{
if (purchaseData == null)
@ -372,7 +497,7 @@ namespace OneStore.Purchasing
else if (ProductType.ALL == type)
{
var message = "ProductType.ALL is not supported. This is supported only by the QueryProductDetails.";
_logger.Error(message);
Logger.Error(message);
RunOnMainThread(() => _callback.OnAcknowledgeFailed(new IapResult((int) ResponseCode.ERROR_ILLEGAL_ARGUMENT, message)));
return;
}
@ -395,6 +520,19 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Manages an auto product (auto-renewable subscription) using the ONE store IAP SDK.
/// This method is obsolete.
/// </summary>
/// <param name="purchaseData">The purchase data of the auto product to be managed.</param>
/// <param name="action">The recurring action to be performed (e.g., cancel, pause, resume).</param>
/// <remarks>
/// - If `purchaseData` is null, the request is rejected, and an error is logged.
/// - If the product type is not `AUTO`, the request is rejected, as only auto products support this feature.
/// - The function constructs a `RecurringProductParams` object using the provided `purchaseData` and `action`.
/// - The request is sent using `_purchaseClient.Call()`.
/// - The result is handled by `RecurringProductListener`, which triggers the appropriate callback upon response.
/// </remarks>
[Obsolete]
public void ManageRecurringProduct(PurchaseData purchaseData, RecurringAction action)
{
@ -433,12 +571,26 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Launches the update or installation flow for the ONE store IAP SDK.
/// This ensures that the latest version of the ONE store service is installed or updated if necessary.
/// </summary>
/// <param name="callback">A callback function that receives the result of the update or installation process.</param>
/// <remarks>
/// - In-app payments cannot be used if the ONE store service is outdated or not installed.
/// - The first API call attempts to connect to the ONE store service.
/// - If `RESULT_NEED_UPDATE` occurs, you must call this method to update or install the service.
/// - The function creates an `IapResultListener` to handle the response.
/// - The result from the Java IAP service is parsed using `IapHelper.ParseJavaIapResult()`.
/// - If the update or installation is successful, the provided callback is invoked with the `IapResult`.
/// - The process is executed via `_purchaseClient.Call()`, which triggers the update or installation flow.
/// </remarks>
public void LaunchUpdateOrInstallFlow(Action<IapResult> callback)
{
var iapResultListener = new IapResultListener();
iapResultListener.OnResponse += (javaIapResult) =>
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
HandleErrorCode(iapResult, () => {
RunOnMainThread(() => callback?.Invoke(iapResult));
});
@ -451,6 +603,20 @@ namespace OneStore.Purchasing
);
}
/// <summary>
/// Launches the subscription management screen using the ONE store IAP SDK.
/// </summary>
/// <param name="purchaseData">
/// The purchase data of the subscription product to manage.
/// If `purchaseData` is provided, the management screen for that specific subscription product is displayed.
/// If `purchaseData` is `null`, the user's subscription list screen is launched.
/// </param>
/// <remarks>
/// - The function creates a `SubscriptionParamsBuilder` to set the purchase data if available.
/// - If `purchaseData` is provided, the management screen for that specific subscription product is shown.
/// - If `purchaseData` is `null`, the general subscription list screen is displayed.
/// - The function executes `_purchaseClient.Call()` to open the subscription management screen.
/// </remarks>
public void LaunchManageSubscription(PurchaseData purchaseData)
{
using (var subscriptionParamsBuilder = new AndroidJavaObject(Constants.SubscriptionParamsBuilder))
@ -468,6 +634,15 @@ namespace OneStore.Purchasing
}
}
/// <summary>
/// Retrieves the market distinction code (storeCode) using the ONE store IAP SDK.
/// This code is required for using the S2S API from SDK v19 onward.
/// </summary>
/// <remarks>
/// - When the `PurchaseClientImpl` object is initialized, the SDK attempts to connect to the payment module.
/// - Upon successful connection, the `storeCode` is automatically obtained and assigned to `PurchaseClientImpl.storeCode`.
/// - This function requests the `storeCode` using `_purchaseClient.Call()` and assigns it upon response.
/// </remarks>
private void GetStoreCode()
{
var storeCodeListener = new StoreInfoListener();
@ -479,6 +654,14 @@ namespace OneStore.Purchasing
);
}
/// <summary>
/// Checks whether the purchasing service is available by verifying the connection status.
/// </summary>
/// <returns>Returns `true` if the service is connected; otherwise, logs a warning and returns `false`.</returns>
/// <remarks>
/// - If `_connectionStatus` is `CONNECTED`, the function returns `true`, indicating that the purchasing service is available.
/// - If not connected, a warning message is logged, and the function returns `false`.
/// </remarks>
private bool IsPurchasingServiceAvailable()
{
if (_connectionStatus == ConnectionStatus.CONNECTED)
@ -486,16 +669,29 @@ namespace OneStore.Purchasing
return true;
}
var message = string.Format("Purchasing service unavailable. ConnectionStatus: {0}", _connectionStatus.ToString());
_logger.Warning(message);
Logger.Warning(message);
return false;
}
/// <summary>
/// Processes the result of a product details query and updates the inventory accordingly.
/// </summary>
/// <param name="type">The type of product being queried.</param>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="javaProductDetailList">The list of product details retrieved from the query.</param>
/// <remarks>
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the query fails, logs a warning with the error details and updates the query status as `Failed`.
/// - Handles error cases by invoking `HandleErrorCode(iapResult)`, triggering the `OnProductDetailsFailed` callback.
/// - If successful, parses the product details list and updates the inventory using `_inventory.UpdateProductDetailInventory()`.
/// - Marks the query status as `Succeed` and invokes the `OnProductDetailsSucceeded` callback with the retrieved product details.
/// </remarks>
private void ProcessProductDetailsResult(ProductType type, AndroidJavaObject javaIapResult, AndroidJavaObject javaProductDetailList)
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Warning("Retrieve product failed with error code '{0}' and message: '{1}'",
Logger.Warning("Retrieve product failed with error code '{0}' and message: '{1}'",
iapResult.Code, iapResult.Message);
_queryProductDetailsCallStatus[type] = AsyncRequestStatus.Failed;
@ -506,19 +702,32 @@ namespace OneStore.Purchasing
return;
}
var productDetailList = _iapHelper.ParseProductDetailsResult(javaIapResult, javaProductDetailList);
var productDetailList = IapHelper.ParseProductDetailsResult(javaIapResult, javaProductDetailList);
_inventory.UpdateProductDetailInventory(productDetailList);
_queryProductDetailsCallStatus[type] = AsyncRequestStatus.Succeed;
RunOnMainThread(() => _callback.OnProductDetailsSucceeded(productDetailList.ToList()));
}
/// <summary>
/// Processes the result of a purchase update and updates the inventory accordingly.
/// </summary>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="javaPurchasesList">The list of purchases retrieved from the query.</param>
/// <remarks>
/// - Resets `_productInPurchaseFlow` to `null` after the purchase process completes.
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the purchase fails, logs a warning with the error details and invokes `OnPurchaseFailed` callback.
/// - If the purchase is successful, retrieves the purchase list using `IapHelper.ParseJavaPurchasesList(javaPurchasesList)`.
/// - If any purchases exist, updates the inventory using `_inventory.UpdatePurchaseInventory()`.
/// - Invokes the `OnPurchaseSucceeded` callback with the retrieved purchase list.
/// </remarks>
private void ProcessPurchaseUpdatedResult(AndroidJavaObject javaIapResult, AndroidJavaObject javaPurchasesList)
{
_productInPurchaseFlow = null;
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Warning("Purchase failed with error code '{0}' and message: '{1}'",
Logger.Warning("Purchase failed with error code '{0}' and message: '{1}'",
iapResult.Code, iapResult.Message);
HandleErrorCode(iapResult, () => {
@ -527,7 +736,7 @@ namespace OneStore.Purchasing
return;
}
var purchasesList = _iapHelper.ParseJavaPurchasesList(javaPurchasesList);
var purchasesList = IapHelper.ParseJavaPurchasesList(javaPurchasesList);
if (purchasesList.Any())
{
_inventory.UpdatePurchaseInventory(purchasesList);
@ -535,13 +744,28 @@ namespace OneStore.Purchasing
}
}
/// <summary>
/// Processes the result of a purchase query and updates the inventory accordingly.
/// </summary>
/// <param name="type">The type of product being queried.</param>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="javaPurchasesList">The list of purchases retrieved from the query.</param>
/// <remarks>
/// - Resets `_productInPurchaseFlow` to `null` after processing the query.
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the query fails, logs a warning, sets the query status to `Failed`, and triggers the `OnPurchaseFailed` callback.
/// - If the query is successful, updates `_queryPurchasesCallStatus[type]` to `Succeed`.
/// - Retrieves the purchase list using `IapHelper.ParseJavaPurchasesList(javaPurchasesList)`.
/// - Updates the inventory with the retrieved purchases using `_inventory.UpdatePurchaseInventory()`.
/// - Invokes the `OnPurchaseSucceeded` callback with the purchase list.
/// </remarks>
private void ProcessQueryPurchasesResult(ProductType type, AndroidJavaObject javaIapResult, AndroidJavaObject javaPurchasesList)
{
_productInPurchaseFlow = null;
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Warning("Purchase failed with error code '{0}' and message: '{1}'",
Logger.Warning("Purchase failed with error code '{0}' and message: '{1}'",
iapResult.Code, iapResult.Message);
_queryPurchasesCallStatus[type] = AsyncRequestStatus.Failed;
@ -553,17 +777,29 @@ namespace OneStore.Purchasing
}
_queryPurchasesCallStatus[type] = AsyncRequestStatus.Succeed;
var purchasesList = _iapHelper.ParseJavaPurchasesList(javaPurchasesList);
var purchasesList = IapHelper.ParseJavaPurchasesList(javaPurchasesList);
_inventory.UpdatePurchaseInventory(purchasesList);
RunOnMainThread(() => _callback.OnPurchaseSucceeded(purchasesList.ToList()));
}
/// <summary>
/// Processes the result of a consume purchase request and updates the inventory accordingly.
/// </summary>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="javaPurchaseData">The purchase data of the consumed product.</param>
/// <remarks>
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the consumption request fails, logs an error, handles the error code, and triggers the `OnConsumeFailed` callback.
/// - If successful, parses the consumed purchase data using `IapHelper.ParseJavaPurchaseData(javaPurchaseData)`.
/// - Removes the consumed product from the inventory using `_inventory.RemovePurchase(purchaseData.ProductId)`.
/// - Invokes the `OnConsumeSucceeded` callback with the consumed purchase data.
/// </remarks>
private void ProcessConsumePurchaseResult(AndroidJavaObject javaIapResult, AndroidJavaObject javaPurchaseData)
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Error("Failed to finish the consume purchase with error code {0} and message: {1}",
Logger.Error("Failed to finish the consume purchase with error code {0} and message: {1}",
iapResult.Code, iapResult.Message);
HandleErrorCode(iapResult, () => {
@ -572,17 +808,30 @@ namespace OneStore.Purchasing
return;
}
var purchaseData = _iapHelper.ParseJavaPurchaseData(javaPurchaseData);
var purchaseData = IapHelper.ParseJavaPurchaseData(javaPurchaseData);
_inventory.RemovePurchase(purchaseData.ProductId);
RunOnMainThread(() => _callback.OnConsumeSucceeded(purchaseData));
}
/// <summary>
/// Processes the result of an acknowledge purchase request and verifies the updated purchase status.
/// </summary>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="productId">The product ID of the acknowledged purchase.</param>
/// <param name="type">The type of product being acknowledged.</param>
/// <remarks>
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the acknowledgment request fails, logs an error, handles the error code, and triggers the `OnAcknowledgeFailed` callback.
/// - If successful, calls `QueryPurchasesInternal()` to verify the purchase status.
/// - Runs the result on the main thread and invokes `OnAcknowledgeSucceeded` if the query is successful.
/// - If the query fails, invokes the `OnAcknowledgeFailed` callback.
/// </remarks>
private void ProcessAcknowledgePurchaseResult(AndroidJavaObject javaIapResult, string productId, ProductType type)
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Error("Failed to finish the acknowledge purchase with error code {0} and message: {1}",
Logger.Error("Failed to finish the acknowledge purchase with error code {0} and message: {1}",
iapResult.Code, iapResult.Message);
HandleErrorCode(iapResult, () => {
@ -605,12 +854,24 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Processes the result of a recurring product management request and verifies the updated purchase status.
/// </summary>
/// <param name="javaIapResult">The result of the IAP request from the ONE store SDK.</param>
/// <param name="productId">The product ID of the recurring product being managed.</param>
/// <param name="recurringAction">The recurring action performed (e.g., cancel, pause, resume).</param>
/// <remarks>
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the request fails, logs an error, handles the error code, and triggers the `OnManageRecurringProduct` callback with a failure response.
/// - If successful, calls `QueryPurchasesInternal()` to verify the updated purchase status.
/// - Runs the result on the main thread and invokes `OnManageRecurringProduct` with the retrieved purchase data.
/// </remarks>
private void ProcessRecurringProductResult(AndroidJavaObject javaIapResult, string productId, string recurringAction)
{
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Error("Failed to finish the manage recurring with error code {0} and message: {1}",
Logger.Error("Failed to finish the manage recurring with error code {0} and message: {1}",
iapResult.Code, iapResult.Message);
HandleErrorCode(iapResult, () => {
@ -624,14 +885,33 @@ namespace OneStore.Purchasing
});
}
/// <summary>
/// Internally queries the latest purchase status after an `AcknowledgePurchase()` or `ManageRecurringProduct()` API call,
/// ensuring that the product's state is updated accordingly by refreshing the inventory.
/// </summary>
/// <param name="productId">The ID of the product to query.</param>
/// <param name="type">The type of product being queried.</param>
/// <param name="callback">The callback function to return the query result and purchase data.</param>
/// <remarks>
/// - This function is called internally after an `AcknowledgePurchase()` or `ManageRecurringProduct()` API call
/// to refresh the product's state based on the latest purchase information.
/// - It executes `queryPurchasesAsync` internally to update the inventory with the latest state values.
/// - Creates a `QueryPurchasesListener` instance to listen for purchase query responses.
/// - Parses the IAP result using `IapHelper.ParseJavaIapResult(javaIapResult)`.
/// - If the query fails, logs a warning, handles the error code, and invokes the callback with a failure response.
/// - If the query succeeds, retrieves and parses the purchase list using `IapHelper.ParseJavaPurchasesList(javaPurchasesList)`.
/// - Updates `_inventory` with the retrieved purchases to maintain the latest product state.
/// - If a purchase matching `productId` exists in `_inventory`, invokes the callback with the corresponding `PurchaseData`.
/// - Calls `_purchaseClient.Call()` to execute the purchase query asynchronously for the specified product type.
/// </remarks>
private void QueryPurchasesInternal(string productId, ProductType type, Action<IapResult, PurchaseData> callback)
{
var queryPurchasesListener = new QueryPurchasesListener(type);
queryPurchasesListener.OnPurchasesResponse += (_, javaIapResult, javaPurchasesList) => {
var iapResult = _iapHelper.ParseJavaIapResult(javaIapResult);
var iapResult = IapHelper.ParseJavaIapResult(javaIapResult);
if (!iapResult.IsSuccessful())
{
_logger.Warning("QueryPurchasesInternal failed with error code '{0}' and message: '{1}'",
Logger.Warning("QueryPurchasesInternal failed with error code '{0}' and message: '{1}'",
iapResult.Code, iapResult.Message);
HandleErrorCode(iapResult, () => {
@ -640,7 +920,7 @@ namespace OneStore.Purchasing
return;
}
var purchasesList = _iapHelper.ParseJavaPurchasesList(javaPurchasesList);
var purchasesList = IapHelper.ParseJavaPurchasesList(javaPurchasesList);
if (purchasesList.Any())
{
_inventory.UpdatePurchaseInventory(purchasesList);
@ -659,9 +939,21 @@ namespace OneStore.Purchasing
);
}
/// <summary>
/// Handles specific IAP error codes and executes the appropriate response.
/// </summary>
/// <param name="iapResult">The IAP result containing the error code.</param>
/// <param name="action">An optional action to execute if the error code does not require a special response.</param>
/// <remarks>
/// - Retrieves the response code from the IAP result using `IapHelper.GetResponseCodeFromIapResult(iapResult)`.
/// - If the response code is `RESULT_NEED_UPDATE`, triggers the `OnNeedUpdate` callback to prompt the user to update the ONE store service.
/// - If the response code is `RESULT_NEED_LOGIN`, triggers the `OnNeedLogin` callback to prompt the user to log in.
/// - If the response code is `ERROR_SERVICE_DISCONNECTED`, resets `_productInPurchaseFlow` and updates `_connectionStatus` to `DISCONNECTED`.
/// - If none of the above cases match, the provided `action` (if any) is executed.
/// </remarks>
private void HandleErrorCode(IapResult iapResult, Action action = null)
{
var responseCode = _iapHelper.GetResponseCodeFromIapResult(iapResult);
var responseCode = IapHelper.GetResponseCodeFromIapResult(iapResult);
switch (responseCode)
{
case ResponseCode.RESULT_NEED_UPDATE:
@ -680,6 +972,14 @@ namespace OneStore.Purchasing
}
}
/// <summary>
/// Executes the specified action on the main thread using the ONE store dispatcher.
/// </summary>
/// <param name="action">The action to execute on the main thread.</param>
/// <remarks>
/// - Calls `OneStoreDispatcher.RunOnMainThread()` to ensure that the provided action runs on the main UI thread.
/// - This is useful for updating UI elements or triggering callbacks that require execution on the main thread.
/// </remarks>
private void RunOnMainThread(Action action)
{
OneStoreDispatcher.RunOnMainThread(() => action());

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 83bea0b014e634756af66f3a340f9ed8
guid: a428fd88042f746e8a7b125eea2b255c
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,6 +1,7 @@
using System;
using UnityEngine;
using OneStore.Purchasing.Internal;
using Logger = OneStore.Common.OneStoreLogger;
namespace OneStore.Purchasing
{
@ -29,6 +30,7 @@ namespace OneStore.Purchasing
public long PurchaseTime { get { return _purchaseMeta.purchaseTime; } }
[Obsolete]
public string PurchaseId { get { return _purchaseMeta.purchaseId; } }
public string PurchaseToken { get { return _purchaseMeta.purchaseToken; } }
@ -61,9 +63,11 @@ namespace OneStore.Purchasing
purchaseData = new PurchaseData(purchaseMeta, jsonPurchaseData, signature);
return true;
}
catch (Exception)
catch (Exception ex)
{
// Error is logged at the caller side.
Logger.Error("[PurchaseData]: Failed to parse purchase data: {0}", jsonPurchaseData);
Logger.Exception(ex);
purchaseData = null;
return false;
}
@ -71,7 +75,12 @@ namespace OneStore.Purchasing
public AndroidJavaObject ToJava()
{
return new AndroidJavaObject(Constants.PurchaseDataClass, _purchaseReceipt.json);
return new AndroidJavaObject(
Constants.PurchaseDataClass,
_purchaseReceipt.json,
_purchaseReceipt.signature,
null
);
}
[Serializable]
@ -96,12 +105,12 @@ namespace OneStore.Purchasing
private class PurchaseReceipt
{
public string json;
public string sigature;
public string signature;
public PurchaseReceipt(string jsonPurchaseData, string signature)
{
json = jsonPurchaseData;
this.sigature = signature;
this.signature = signature;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 32d816303781743ebbef5fd546e63d01
guid: c0c9deb7e41a54ff9a7af2666ad1f463
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a28e0eadc8ecc49d1a29b4b0ae1e942a
guid: 92dd96d95eedf4f149d2f39a1b4b0748
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fb3ca9121fecd400388c947a7a42b506
guid: 8d580cce6b60743eabfc1ecb92344eeb
MonoImporter:
externalObjects: {}
serializedVersion: 2

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