Skip to content

Commit

Permalink
Merge pull request #241 from qonversion/release/8.0.0
Browse files Browse the repository at this point in the history
Release/8.0.0
  • Loading branch information
suriksarkisyan authored Jul 16, 2024
2 parents 0887ff8 + 756df00 commit e647100
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 47 deletions.
4 changes: 2 additions & 2 deletions Editor/QonversionDependencies.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<dependencies>
<androidPackages>
<androidPackage spec="io.qonversion.sandwich:sandwich:4.5.0" />
<androidPackage spec="io.qonversion.sandwich:sandwich:5.0.3" />
<androidPackage spec="com.fasterxml.jackson.core:jackson-databind:2.11.1" />
<androidPackage spec="org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.61" />
</androidPackages>
<iosPods>
<iosPod name="QonversionSandwich" version="4.5.0" />
<iosPod name="QonversionSandwich" version="5.0.3" />
</iosPods>
</dependencies>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.qonversion.sandwich.PurchaseResultListener;
import io.qonversion.sandwich.QonversionSandwich;
import io.qonversion.sandwich.ResultListener;
import io.qonversion.sandwich.SandwichError;
Expand Down Expand Up @@ -117,7 +116,7 @@ public static synchronized void checkEntitlements(String unityCallbackName) {
}

public static synchronized void purchase(String productId, @Nullable String offerId, boolean applyOffer, String unityCallbackName) {
qonversionSandwich.purchase(productId, offerId, applyOffer, getPurchaseResultListener(unityCallbackName));
qonversionSandwich.purchase(productId, offerId, applyOffer, getResultListener(unityCallbackName));
}

public static synchronized void updatePurchase(
Expand All @@ -128,7 +127,7 @@ public static synchronized void updatePurchase(
@Nullable String updatePolicyKey,
String unityCallbackName
) {
qonversionSandwich.updatePurchase(productId, offerId, applyOffer, oldProductId, updatePolicyKey, getPurchaseResultListener(unityCallbackName));
qonversionSandwich.updatePurchase(productId, offerId, applyOffer, oldProductId, updatePolicyKey, getResultListener(unityCallbackName));
}

public static synchronized void restore(String unityCallbackName) {
Expand Down Expand Up @@ -180,6 +179,10 @@ public static synchronized void detachUserFromRemoteConfiguration(String remoteC
qonversionSandwich.detachUserFromRemoteConfiguration(remoteConfigurationId, getResultListener(unityCallbackName));
}

public static synchronized void isFallbackFileAccessible(String unityCallbackName) {
qonversionSandwich.isFallbackFileAccessible(getResultListener(unityCallbackName));
}

public static synchronized void checkTrialIntroEligibility(String productIds, String unityCallbackName) {
try {
ObjectMapper mapper = new ObjectMapper();
Expand All @@ -206,24 +209,6 @@ public void onError(@NonNull SandwichError error) {
};
}

private static PurchaseResultListener getPurchaseResultListener(@NotNull String methodName) {
return new PurchaseResultListener() {
@Override
public void onSuccess(@NonNull Map<String, ?> data) {
sendMessageToUnity(data, methodName);
}

@Override
public void onError(@NonNull SandwichError error, boolean isCancelled) {
final ObjectMapper mapper = new ObjectMapper();
final ObjectNode rootNode = Utils.createErrorNode(error);
final JsonNode isCancelledNode = mapper.convertValue(isCancelled, JsonNode.class);
rootNode.set("isCancelled", isCancelledNode);
sendMessageToUnity(rootNode, methodName);
}
};
}

private static ResultListener getRemoteConfigResultListener(@Nullable String contextKey, @NotNull String methodName) {
return new ResultListener() {
@Override
Expand Down
5 changes: 5 additions & 0 deletions Runtime/Android/QonversionWrapperAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ public void DetachUserFromRemoteConfiguration(string remoteConfigurationId, stri
CallQonversion("detachUserFromRemoteConfiguration", remoteConfigurationId, callbackName);
}

public void IsFallbackFileAccessible(string callbackName)
{
CallQonversion("isFallbackFileAccessible", callbackName);
}

public void CheckTrialIntroEligibility(string productIdsJson, string callbackName)
{
CallQonversion("checkTrialIntroEligibility", productIdsJson, callbackName);
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Scripts/Dto/Eligibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ private EligibilityStatus FormatEligibilityStatus(object status)
switch (value)
{
case "non_intro_or_trial_product":
result = EligibilityStatus.NonIntroProduct;
result = EligibilityStatus.NonIntroOrTrialProduct;
break;
case "intro_or_trial_ineligible":
result = EligibilityStatus.Ineligible;
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Scripts/Dto/EligibilityStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public enum EligibilityStatus
{
Unknown,
NonIntroProduct,
NonIntroOrTrialProduct,
Ineligible,
Eligible
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;

namespace QonversionUnity
{
/// <summary>
/// This class represents the details about the installment plan for a subscription product.
/// </summary>
public class ProductInstallmentPlanDetails
{
/// Committed payments count after a user signs up for this subscription plan.
public readonly int CommitmentPaymentsCount;

/// Subsequent committed payments count after this subscription plan renews.
///
/// Returns 0 if the installment plan doesn't have any subsequent commitment,
/// which means this subscription plan will fall back to a normal
/// non-installment monthly plan when the plan renews.
public readonly int SubsequentCommitmentPaymentsCount;

public ProductInstallmentPlanDetails(Dictionary<string, object> dict)
{
if (dict.TryGetValue("commitmentPaymentsCount", out object value)) CommitmentPaymentsCount = (int)(long)value;
if (dict.TryGetValue("subsequentCommitmentPaymentsCount", out value)) SubsequentCommitmentPaymentsCount = (int)(long)value;
}

public override string ToString()
{
return $"{nameof(CommitmentPaymentsCount)}: {CommitmentPaymentsCount}, " +
$"{nameof(SubsequentCommitmentPaymentsCount)}: {SubsequentCommitmentPaymentsCount}";
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class ProductOfferDetails
/// A base plan phase details.
[CanBeNull] public readonly ProductPricingPhase BasePlan;

/// Additional details of an installment plan, if exists.
[CanBeNull] public readonly ProductInstallmentPlanDetails InstallmentPlanDetails;

/// A trial phase details, if exists.
[CanBeNull] public readonly ProductPricingPhase IntroPhase;

Expand Down Expand Up @@ -81,6 +84,11 @@ public ProductOfferDetails(Dictionary<string, object> dict)
{
BasePlan = new ProductPricingPhase(basePlan);
}

if (dict.TryGetValue("installmentPlanDetails", out value) && value is Dictionary<string, object> installmentPlan)
{
InstallmentPlanDetails = new ProductInstallmentPlanDetails(installmentPlan);
}

if (dict.TryGetValue("introPhase", out value) && value is Dictionary<string, object> introPhase)
{
Expand Down Expand Up @@ -117,6 +125,7 @@ public override string ToString()
$"{nameof(Tags)}: {tags}, " +
$"{nameof(PricingPhases)}: {pricingPhases}, " +
$"{nameof(BasePlan)}: {BasePlan}, " +
$"{nameof(InstallmentPlanDetails)}: {InstallmentPlanDetails}, " +
$"{nameof(IntroPhase)}: {IntroPhase}, " +
$"{nameof(TrialPhase)}: {TrialPhase}, " +
$"{nameof(HasTrial)}: {HasTrial}, " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public class ProductStoreDetails
/// they will need to make a new payment to extend their plan.
public readonly bool IsPrepaid;

/// True, if the subscription product is installment, which means that users commit
/// to pay for a specified amount of periods every month.
public readonly bool IsInstallment;

public ProductStoreDetails(Dictionary<string, object> dict)
{
if (dict.TryGetValue("productId", out object value)) ProductId = value as string;
Expand Down Expand Up @@ -119,6 +123,7 @@ public ProductStoreDetails(Dictionary<string, object> dict)
if (dict.TryGetValue("isInApp", out value)) IsInApp = (bool)value;
if (dict.TryGetValue("isSubscription", out value)) IsSubscription = (bool)value;
if (dict.TryGetValue("isPrepaid", out value)) IsPrepaid = (bool)value;
if (dict.TryGetValue("isInstallment", out value)) IsInstallment = (bool)value;
}

public override string ToString()
Expand All @@ -144,7 +149,8 @@ public override string ToString()
$"{nameof(ProductType)}: {ProductType}, " +
$"{nameof(IsInApp)}: {IsInApp}, " +
$"{nameof(IsSubscription)}: {IsSubscription}, " +
$"{nameof(IsPrepaid)}: {IsPrepaid}";
$"{nameof(IsPrepaid)}: {IsPrepaid}" +
$"{nameof(IsInstallment)}: {IsInstallment}";
}
}
}
56 changes: 53 additions & 3 deletions Runtime/Scripts/Dto/QonversionError.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using System;
using System.Collections.Generic;

namespace QonversionUnity
{
public class QonversionError
{
public string Code;
public QErrorCode Code;
public string Message;

public QonversionError(Dictionary<string, object> dict)
{
if (dict.TryGetValue("code", out object value)) Code = value as string;
if (dict.TryGetValue("code", out object value)) Code = FormatErrorCode(value);
string message = "";
if (dict.TryGetValue("description", out object description))
{
Expand All @@ -27,7 +28,7 @@ public QonversionError(Dictionary<string, object> dict)
Message = message;
}

internal QonversionError(string code, string message)
internal QonversionError(QErrorCode code, string message)
{
Code = code;
Message = message;
Expand All @@ -38,5 +39,54 @@ public override string ToString()
return $"{nameof(Code)}: {Code}, " +
$"{nameof(Message)}: {Message}";
}

private QErrorCode FormatErrorCode(object code)
{
return Enum.TryParse(code.ToString(), out QErrorCode parsedSource)
? parsedSource
: QErrorCode.Unknown;
}
}

public enum QErrorCode
{
Unknown, // Unknown error
ApiRateLimitExceeded, // API requests rate limit exceeded
AppleStoreError, // Apple Store error received
BackendError, // There was a backend error
BillingUnavailable, // The Billing service is unavailable on the device
ClientInvalid, // Client is not allowed to issue the request, etc
CloudServiceNetworkConnectionFailed, // The device could not connect to the network
CloudServicePermissionDenied, // User is not allowed to access cloud service information
CloudServiceRevoked, // User has revoked permission to use this cloud service
FailedToReceiveData, // Could not receive data
FeatureNotSupported, // The requested feature is not supported
FraudPurchase, // Fraud purchase was detected
IncorrectRequest, // Request failed
InternalError, // Internal backend error
InvalidClientUid, // Client Uid is invalid or not set
InvalidCredentials, // Access token is invalid or not set
InvalidStoreCredentials, // This account does not have access to the requested application
LaunchError, // There was an error while launching Qonversion SDK
NetworkConnectionFailed, // There was a network issue. Make sure that the Internet connection is available on the device
OfferingsNotFound, // No offerings found
PaymentInvalid, // Purchase identifier was invalid, etc.
PaymentNotAllowed, // This device is not allowed to make the payment
PlayStoreError, // There was an issue with the Play Store service
PrivacyAcknowledgementRequired, // User needs to acknowledge Apple's privacy policy
ProductAlreadyOwned, // Failed to purchase since item is already owned
ProductNotFound, // Failed to purchase since the Qonversion product was not found
ProductNotOwned, // Failed to consume purchase since item is not owned
ProjectConfigError, // The project is not configured or configured incorrectly in the Qonversion Dashboard
PurchaseCanceled, // User pressed back or canceled a dialog for purchase
PurchaseInvalid, // Failure of purchase
PurchasePending, // Purchase is pending
PurchaseUnspecified, // Unspecified state of the purchase
ReceiptValidationError, // Receipt validation error
RemoteConfigurationNotAvailable, // Remote configuration is not available for the current user or for the provided context key
ResponseParsingFailed, // A problem occurred while serializing or deserializing data
StoreProductNotAvailable, // Requested product is not available for purchase or its product id was not found
UnauthorizedRequestData, // App is attempting to use SKPayment's requestData property, but does not have the appropriate entitlement
UnknownClientPlatform, // The current platform is not supported
}
}
6 changes: 6 additions & 0 deletions Runtime/Scripts/IQonversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ public interface IQonversion
/// <param name="callback">Callback that will be called when response is received.</param>
public void DetachUserFromRemoteConfiguration(string remoteConfigurationId, Qonversion.OnAttachUserResponseReceived callback);

/// <summary>
/// Call this function to check if the fallback file is accessible.
/// </summary>
/// <param name="callback">Callback that will be called when response is received.</param>
public void IsFallbackFileAccessible(Qonversion.OnFallbackFileAccessibilityResponseReceived callback);

/// <summary>
/// You can check if a user is eligible for an introductory offer, including a free trial.
/// You can show only a regular price for users who are not eligible for an introductory offer.
Expand Down
24 changes: 13 additions & 11 deletions Runtime/Scripts/Internal/Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ namespace QonversionUnity
{
internal class Mapper
{
internal static bool GetIsCancelledFromJson(string jsonStr)
{
if (!(Json.Deserialize(jsonStr) is Dictionary<string, object> result))
{
Debug.LogError("Could not parse purchase result");
return false;
}

return result.TryGetValue("isCancelled", out var isCancelled) && Convert.ToBoolean(isCancelled);
}

internal static Dictionary<string, Entitlement> EntitlementsFromJson(string jsonStr)
{
var result = new Dictionary<string, Entitlement>();
Expand Down Expand Up @@ -117,6 +106,19 @@ internal static string ScreenIdFromJson(string jsonStr)
return screenResult.GetString("screenId", "");
}

internal static bool IsFallbackFileAccessibleFromJson(string jsonStr)
{
var isAccessible = false;
if (!(Json.Deserialize(jsonStr) is Dictionary<string, object> rawResult))
{
Debug.LogError("Could not parse fallback file accessibility");
return false;
}
if (rawResult.TryGetValue("success", out object value)) isAccessible = (bool)value;

return isAccessible;
}

internal static Dictionary<string, Eligibility> EligibilitiesFromJson(string jsonStr)
{
var result = new Dictionary<string, Eligibility>();
Expand Down
22 changes: 20 additions & 2 deletions Runtime/Scripts/Internal/QonversionInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ internal class QonversionInternal : MonoBehaviour, IQonversion
private const string OnUserPropertiesMethodName = "OnUserProperties";
private const string OnAttachUserMethodName = "OnAttachUser";
private const string OnDetachUserMethodName = "OnDetachUser";
private const string OnIsFallbackFileAccessibleMethodName = "OnIsFallbackFileAccessible";

private const string SdkVersion = "7.5.0";
private const string SdkVersion = "8.0.0";
private const string SdkSource = "unity";

private const string DefaultRemoteConfigContextKey = "";
Expand All @@ -53,6 +54,7 @@ internal class QonversionInternal : MonoBehaviour, IQonversion
private Qonversion.OnUserPropertiesReceived UserPropertiesCallback { get; set; }
private Qonversion.OnAttachUserResponseReceived AttachUserCallback { get; set; }
private Qonversion.OnAttachUserResponseReceived DetachUserCallback { get; set; }
private Qonversion.OnFallbackFileAccessibilityResponseReceived FallbackFileCallback { get; set; }

public event Qonversion.OnPromoPurchasesReceived PromoPurchasesReceived
{
Expand Down Expand Up @@ -205,6 +207,13 @@ public void DetachUserFromRemoteConfiguration(string remoteConfigurationId, Qonv
instance.DetachUserFromRemoteConfiguration(remoteConfigurationId, OnDetachUserMethodName);
}

public void IsFallbackFileAccessible(Qonversion.OnFallbackFileAccessibilityResponseReceived callback)
{
FallbackFileCallback = callback;
IQonversionWrapper instance = GetNativeWrapper();
instance.IsFallbackFileAccessible(OnIsFallbackFileAccessibleMethodName);
}

public void CheckTrialIntroEligibility(IList<string> productIds, Qonversion.OnEligibilitiesReceived callback)
{
var productIdsJson = Json.Serialize(productIds);
Expand Down Expand Up @@ -494,6 +503,15 @@ private void OnDetachUser(string jsonString)
}
}

private void OnIsFallbackFileAccessible(string jsonString)
{
if (FallbackFileCallback == null) return;

var isAccessible = Mapper.IsFallbackFileAccessibleFromJson(jsonString);

FallbackFileCallback(isAccessible);
}

// Called from the native SDK - Called when eligibilities received from the checkTrialIntroEligibilityForProductIds() method
private void OnEligibilities(string jsonString)
{
Expand Down Expand Up @@ -623,7 +641,7 @@ private void HandlePurchaseResult(Qonversion.OnPurchaseResultReceived callback,
var error = Mapper.ErrorFromJson(jsonString);
if (error != null)
{
var isCancelled = Mapper.GetIsCancelledFromJson(jsonString);
var isCancelled = error.Code == QErrorCode.PurchaseCanceled;
callback(null, error, isCancelled);
}
else
Expand Down
Loading

0 comments on commit e647100

Please sign in to comment.