Added client-server interaction for subscription validation

This commit is contained in:
Roman Kuznetsov 2018-08-23 14:46:20 +03:00 committed by Daria Volvenkova
parent 5ebbef25db
commit 6e572cb3ac
5 changed files with 244 additions and 17 deletions

View file

@ -714,6 +714,14 @@ void CallSetRoutingLoadPointsListener(shared_ptr<jobject> listener, bool success
RoutingManager::LoadRouteHandler g_loadRouteHandler;
void CallSubscriptionValidationListener(shared_ptr<jobject> listener,
Subscription::ValidationCode code)
{
JNIEnv * env = jni::GetEnv();
jmethodID const methodId = jni::GetMethodID(env, *listener, "onValidateSubscription", "(I)V");
env->CallVoidMethod(*listener, methodId, static_cast<jint>(code));
}
/// @name JNI EXPORTS
//@{
JNIEXPORT jstring JNICALL
@ -1730,4 +1738,44 @@ Java_com_mapswithme_maps_Framework_nativeMakeCrash(JNIEnv *env, jclass type)
{
CHECK(false, ("Diagnostic native crash!"));
}
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_Framework_nativeValidateSubscription(JNIEnv * env, jclass,
jstring receiptData)
{
auto const & subscription = frm()->GetSubscription();
if (subscription == nullptr)
return;
subscription->Validate(jni::ToNativeString(env, receiptData), frm()->GetUser().GetAccessToken());
}
JNIEXPORT void JNICALL
Java_com_mapswithme_maps_Framework_nativeSetSubscriptionValidationListener(JNIEnv *, jclass,
jobject listener)
{
auto const & subscription = frm()->GetSubscription();
if (subscription == nullptr)
return;
if (listener != nullptr)
{
subscription->SetValidationCallback(bind(&CallSubscriptionValidationListener,
jni::make_global_ref(listener), _1));
}
else
{
subscription->SetValidationCallback(nullptr);
}
}
JNIEXPORT jboolean JNICALL
Java_com_mapswithme_maps_Framework_nativeHasActiveSubscription(JNIEnv *, jclass)
{
auto const & subscription = frm()->GetSubscription();
if (subscription == nullptr)
return static_cast<jboolean>(false);
return static_cast<jboolean>(subscription->IsActive());
}
} // extern "C"

View file

@ -100,6 +100,17 @@ public class Framework
//TODO(@alexzatsepin): remove TOKEN_MAPSME from this list.
public static final int TOKEN_MAPSME = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ SUBSCRIPTION_ACTIVE, SUBSCRIPTION_NOT_ACTIVE, SUBSCRIPTION_VALIDATION_FAILURE })
public @interface SubscriptionValidationCode {}
// Subscription is active.
public static final int SUBSCRIPTION_ACTIVE = 0;
// Subscription is not active.
public static final int SUBSCRIPTION_NOT_ACTIVE = 1;
// Validation failed, real subscription status is unknown, current one acts.
public static final int SUBSCRIPTION_VALIDATION_FAILURE = 2;
@SuppressWarnings("unused")
public interface MapObjectListener
{
@ -134,6 +145,12 @@ public class Framework
void onRoutePointsLoaded(boolean success);
}
@SuppressWarnings("unused")
public interface SubscriptionValidationListener
{
void onValidateSubscription(@SubscriptionValidationCode int code);
}
public static class Params3dMode
{
public boolean enabled;
@ -455,4 +472,9 @@ public class Framework
public static native String nativeGetMegafonDownloaderBannerUrl();
public static native void nativeMakeCrash();
public static native void nativeValidateSubscription(@NonNull String receiptData);
public static native void nativeSetSubscriptionValidationListener(
@Nullable SubscriptionValidationListener listener);
public static native boolean nativeHasActiveSubscription();
}

View file

@ -503,8 +503,6 @@ Framework::Framework(FrameworkParams const & params)
InitTransliteration();
LOG(LDEBUG, ("Transliterators initialized"));
m_subscription->Validate();
}
Framework::~Framework()

View file

@ -1,19 +1,87 @@
#include "map/subscription.hpp"
#include "platform/http_client.hpp"
#include "platform/platform.hpp"
#include "coding/serdes_json.hpp"
#include "coding/sha1.hpp"
#include "coding/writer.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/visitor.hpp"
#include "std/target_os.hpp"
#include <utility>
#define STAGE_PURCHASE_SERVER
#include "private.h"
namespace
{
std::string const kSubscriptionId = "SubscriptionId";
std::string const kServerUrl = PURCHASE_SERVER_URL;
#if defined(OMIM_OS_IPHONE)
std::string const kProductId = "ad.removal.apple";
std::string const kReceiptType = "apple";
#elif defined(OMIM_OS_ANDROID)
std::string const kProductId = "ad.removal.google";
std::string const kReceiptType = "google";
#else
std::string const kProductId {};
std::string const kReceiptType {};
#endif
std::string GetSubscriptionId()
{
return coding::SHA1::CalculateBase64ForString(GetPlatform().UniqueClientId());
}
std::string ValidationUrl()
{
if (kServerUrl.empty())
return {};
return kServerUrl + "purchases/is_purchased";
}
struct ReceiptData
{
std::string m_data;
std::string m_type;
ReceiptData(std::string const & data, std::string const & type)
: m_data(data)
, m_type(type)
{}
DECLARE_VISITOR(visitor(m_data, "data"),
visitor(m_type, "type"))
};
struct ValidationData
{
std::string m_productId;
ReceiptData m_receipt;
ValidationData(std::string const & productId, std::string const & receiptData,
std::string const & receiptType)
: m_productId(productId)
, m_receipt(receiptData, receiptType)
{}
DECLARE_VISITOR(visitor(m_productId, "product_id"),
visitor(m_receipt, "receipt"))
};
struct ValidationResult
{
bool m_isValid = true;
std::string m_error;
DECLARE_VISITOR(visitor(m_isValid, "valid"),
visitor(m_error, "error"))
};
} // namespace
Subscription::Subscription()
@ -32,29 +100,104 @@ void Subscription::Register(SubscriptionListener * listener)
listener->OnSubscriptionChanged(IsActive());
}
void Subscription::SetValidationCallback(ValidationCallback && callback)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
m_validationCallback = std::move(callback);
}
bool Subscription::IsActive() const
{
return m_isActive;
}
void Subscription::Validate()
void Subscription::Validate(std::string const & receiptData, std::string const & accessToken)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
//TODO: check on server.
bool isValid = false;
m_isActive = isValid;
if (isValid)
std::string const url = ValidationUrl();
if (url.empty())
{
m_subscriptionId = GetSubscriptionId();
GetPlatform().GetSecureStorage().Save(kSubscriptionId, m_subscriptionId);
}
else
{
GetPlatform().GetSecureStorage().Remove(kSubscriptionId);
ApplyValidation(ValidationCode::NotActive);
return;
}
for (auto & listener : m_listeners)
listener->OnSubscriptionChanged(isValid);
auto const status = GetPlatform().ConnectionStatus();
if (status == Platform::EConnectionType::CONNECTION_NONE)
{
ApplyValidation(ValidationCode::Failure);
return;
}
GetPlatform().RunTask(Platform::Thread::Network, [this, url, receiptData, accessToken]()
{
platform::HttpClient request(url);
request.SetRawHeader("Accept", "application/json");
request.SetRawHeader("User-Agent", GetPlatform().GetAppUserAgent());
if (!accessToken.empty())
request.SetRawHeader("Authorization", "Bearer " + accessToken);
std::string jsonStr;
{
using Sink = MemWriter<std::string>;
Sink sink(jsonStr);
coding::SerializerJson<Sink> serializer(sink);
serializer(ValidationData(kProductId, receiptData, kReceiptType));
}
request.SetBodyData(std::move(jsonStr), "application/json");
ValidationCode code = ValidationCode::Failure;
if (request.RunHttpRequest())
{
auto const resultCode = request.ErrorCode();
if (resultCode >= 200 && resultCode < 300)
{
ValidationResult result;
{
coding::DeserializerJson des(request.ServerResponse());
des(result);
}
code = result.m_isValid ? ValidationCode::Active : ValidationCode::NotActive;
if (!result.m_error.empty())
LOG(LWARNING, ("Validation URL:", url, "Subscription error:", result.m_error));
}
else
{
LOG(LWARNING, ("Validation URL:", url, "Unexpected server error. Code =", resultCode, request.ServerResponse()));
}
}
else
{
LOG(LWARNING, ("Validation URL:", url, "Request failed."));
}
GetPlatform().RunTask(Platform::Thread::Gui, [this, code]() { ApplyValidation(code); });
});
}
void Subscription::ApplyValidation(ValidationCode code)
{
CHECK_THREAD_CHECKER(m_threadChecker, ());
if (code != ValidationCode::Failure)
{
bool const isActive = (code == ValidationCode::Active);
m_isActive = isActive;
if (isActive)
{
m_subscriptionId = GetSubscriptionId();
GetPlatform().GetSecureStorage().Save(kSubscriptionId, m_subscriptionId);
}
else
{
GetPlatform().GetSecureStorage().Remove(kSubscriptionId);
}
for (auto & listener : m_listeners)
listener->OnSubscriptionChanged(isActive);
}
if (m_validationCallback)
m_validationCallback(code);
}

View file

@ -3,6 +3,7 @@
#include "base/thread_checker.hpp"
#include <atomic>
#include <functional>
#include <string>
#include <vector>
@ -16,15 +17,30 @@ public:
class Subscription
{
public:
enum class ValidationCode
{
Active, // Subscription is active.
NotActive, // Subscription is not active.
Failure, // Validation failed, real subscription status is unknown, current one acts.
};
using ValidationCallback = std::function<void(ValidationCode)>;
Subscription();
void Register(SubscriptionListener * listener);
void SetValidationCallback(ValidationCallback && callback);
bool IsActive() const;
void Validate();
void Validate(std::string const & receiptData, std::string const & accessToken);
private:
void ApplyValidation(ValidationCode code);
std::atomic<bool> m_isActive;
std::string m_subscriptionId;
std::vector<SubscriptionListener *> m_listeners;
ValidationCallback m_validationCallback;
ThreadChecker m_threadChecker;
};