summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-api/src/lib.rs59
-rw-r--r--mullvad-daemon/src/device/api.rs55
-rw-r--r--mullvad-daemon/src/device/mod.rs123
-rw-r--r--mullvad-daemon/src/device/service.rs41
-rw-r--r--mullvad-daemon/src/lib.rs52
-rw-r--r--mullvad-jni/src/daemon_interface.rs22
-rw-r--r--mullvad-jni/src/lib.rs114
-rw-r--r--mullvad-types/src/account.rs17
8 files changed, 475 insertions, 8 deletions
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index 37faf5c40c..647b5cf071 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -6,7 +6,7 @@ use futures::channel::mpsc;
use futures::Stream;
use hyper::Method;
use mullvad_types::{
- account::{AccountToken, VoucherSubmission},
+ account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
version::AppVersion,
};
use proxy::ApiConnectionMode;
@@ -63,6 +63,7 @@ pub const API_IP_CACHE_FILENAME: &str = "api-ip-address.txt";
const ACCOUNTS_URL_PREFIX: &str = "accounts/v1";
const APP_URL_PREFIX: &str = "app/v1";
+const GOOGLE_PAYMENTS_URL_PREFIX: &str = "payments/google-play/v1";
pub static API: LazyManual<ApiEndpoint> = LazyManual::new(ApiEndpoint::from_env_vars);
@@ -457,6 +458,62 @@ impl AccountsProxy {
}
}
+ pub fn init_play_purchase(
+ &mut self,
+ account_token: AccountToken,
+ ) -> impl Future<Output = Result<PlayPurchasePaymentToken, rest::Error>> {
+ #[derive(serde::Deserialize)]
+ struct PlayPurchaseInitResponse {
+ obfuscated_id: String,
+ }
+
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+ let access_proxy = self.handle.token_store.clone();
+
+ async move {
+ let response = rest::send_json_request(
+ &factory,
+ service,
+ &format!("{GOOGLE_PAYMENTS_URL_PREFIX}/init"),
+ Method::POST,
+ &(),
+ Some((access_proxy, account_token)),
+ &[StatusCode::OK],
+ )
+ .await;
+
+ let PlayPurchaseInitResponse { obfuscated_id } =
+ rest::deserialize_body(response?).await?;
+
+ Ok(obfuscated_id)
+ }
+ }
+
+ pub fn verify_play_purchase(
+ &mut self,
+ account_token: AccountToken,
+ play_purchase: PlayPurchase,
+ ) -> impl Future<Output = Result<(), rest::Error>> {
+ let service = self.handle.service.clone();
+ let factory = self.handle.factory.clone();
+ let access_proxy = self.handle.token_store.clone();
+
+ async move {
+ rest::send_json_request(
+ &factory,
+ service,
+ &format!("{GOOGLE_PAYMENTS_URL_PREFIX}/acknowledge"),
+ Method::POST,
+ &play_purchase,
+ Some((access_proxy, account_token)),
+ &[StatusCode::ACCEPTED],
+ )
+ .await?;
+ Ok(())
+ }
+ }
+
pub fn get_www_auth_token(
&self,
account: AccountToken,
diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs
index 93dae78261..07e7b35311 100644
--- a/mullvad-daemon/src/device/api.rs
+++ b/mullvad-daemon/src/device/api.rs
@@ -2,7 +2,11 @@ use std::pin::Pin;
use chrono::{DateTime, Utc};
use futures::{future::FusedFuture, Future};
-use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData};
+use mullvad_types::{
+ account::{PlayPurchasePaymentToken, VoucherSubmission},
+ device::Device,
+ wireguard::WireguardData,
+};
use super::{Error, PrivateAccountAndDevice, ResponseTx};
@@ -47,6 +51,25 @@ impl CurrentApiCall {
self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx)));
}
+ pub fn set_init_play_purchase(
+ &mut self,
+ init_play_purchase_call: ApiCall<PlayPurchasePaymentToken>,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ ) {
+ self.current_call = Some(Call::InitPlayPurchase(init_play_purchase_call, Some(tx)));
+ }
+
+ pub fn set_verify_play_purchase(
+ &mut self,
+ verify_play_purchase_call: ApiCall<()>,
+ tx: ResponseTx<()>,
+ ) {
+ self.current_call = Some(Call::VerifyPlayPurchase(
+ verify_play_purchase_call,
+ Some(tx),
+ ));
+ }
+
pub fn is_validating(&self) -> bool {
matches!(
&self.current_call,
@@ -109,6 +132,11 @@ enum Call {
ApiCall<VoucherSubmission>,
Option<ResponseTx<VoucherSubmission>>,
),
+ InitPlayPurchase(
+ ApiCall<PlayPurchasePaymentToken>,
+ Option<ResponseTx<PlayPurchasePaymentToken>>,
+ ),
+ VerifyPlayPurchase(ApiCall<()>, Option<ResponseTx<()>>),
ExpiryCheck(ApiCall<DateTime<Utc>>),
}
@@ -142,6 +170,26 @@ impl futures::Future for Call {
std::task::Poll::Pending
}
}
+ InitPlayPurchase(call, tx) => {
+ if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
+ std::task::Poll::Ready(ApiResult::InitPlayPurchase(
+ response,
+ tx.take().unwrap(),
+ ))
+ } else {
+ std::task::Poll::Pending
+ }
+ }
+ VerifyPlayPurchase(call, tx) => {
+ if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
+ std::task::Poll::Ready(ApiResult::VerifyPlayPurchase(
+ response,
+ tx.take().unwrap(),
+ ))
+ } else {
+ std::task::Poll::Pending
+ }
+ }
ExpiryCheck(call) => Pin::new(call).poll(cx).map(ApiResult::ExpiryCheck),
}
}
@@ -155,5 +203,10 @@ pub(crate) enum ApiResult {
Result<VoucherSubmission, Error>,
ResponseTx<VoucherSubmission>,
),
+ InitPlayPurchase(
+ Result<PlayPurchasePaymentToken, Error>,
+ ResponseTx<PlayPurchasePaymentToken>,
+ ),
+ VerifyPlayPurchase(Result<(), Error>, ResponseTx<()>),
ExpiryCheck(Result<DateTime<Utc>, Error>),
}
diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs
index b54638f140..528c25ce9a 100644
--- a/mullvad-daemon/src/device/mod.rs
+++ b/mullvad-daemon/src/device/mod.rs
@@ -6,7 +6,7 @@ use futures::{
use mullvad_api::rest;
use mullvad_types::{
- account::{AccountToken, VoucherSubmission},
+ account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
device::{
AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DeviceState,
},
@@ -305,6 +305,8 @@ enum AccountManagerCommand {
SetRotationInterval(RotationInterval, ResponseTx<()>),
ValidateDevice(ResponseTx<()>),
SubmitVoucher(String, ResponseTx<VoucherSubmission>),
+ InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken>),
+ VerifyPlayPurchase(ResponseTx<()>, PlayPurchase),
CheckExpiry(ResponseTx<DateTime<Utc>>),
Shutdown(oneshot::Sender<()>),
}
@@ -359,6 +361,16 @@ impl AccountManagerHandle {
.await
}
+ pub async fn init_play_purchase(&self) -> Result<PlayPurchasePaymentToken, Error> {
+ self.send_command(AccountManagerCommand::InitPlayPurchase)
+ .await
+ }
+
+ pub async fn verify_play_purchase(&self, play_purchase: PlayPurchase) -> Result<(), Error> {
+ self.send_command(move |tx| AccountManagerCommand::VerifyPlayPurchase(tx, play_purchase))
+ .await
+ }
+
pub async fn check_expiry(&self) -> Result<DateTime<Utc>, Error> {
self.send_command(AccountManagerCommand::CheckExpiry).await
}
@@ -514,6 +526,12 @@ impl AccountManager {
Some(AccountManagerCommand::SubmitVoucher(voucher, tx)) => {
self.handle_voucher_submission(tx, voucher, &mut current_api_call);
},
+ Some(AccountManagerCommand::InitPlayPurchase(tx)) => {
+ self.handle_init_play_purchase(tx, &mut current_api_call);
+ },
+ Some(AccountManagerCommand::VerifyPlayPurchase(tx, play_purchase)) => {
+ self.handle_verify_play_purchase(tx, play_purchase, &mut current_api_call);
+ },
Some(AccountManagerCommand::CheckExpiry(tx)) => {
self.handle_expiry_request(tx, &mut current_api_call);
},
@@ -589,6 +607,65 @@ impl AccountManager {
}
}
+ fn handle_init_play_purchase(
+ &mut self,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+
+ let init_play_purchase_api_call = move || {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move { account_service.init_play_purchase(account_token).await })
+ };
+
+ match init_play_purchase_api_call() {
+ Ok(call) => {
+ current_api_call.set_init_play_purchase(Box::pin(call), tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
+ fn handle_verify_play_purchase(
+ &mut self,
+ tx: ResponseTx<()>,
+ play_purchase: PlayPurchase,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+
+ let play_purchase_verify_api_call = move || {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move {
+ account_service
+ .verify_play_purchase(account_token, play_purchase)
+ .await
+ })
+ };
+
+ match play_purchase_verify_api_call() {
+ Ok(call) => {
+ current_api_call.set_verify_play_purchase(Box::pin(call), tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
fn handle_expiry_request(
&mut self,
tx: ResponseTx<DateTime<Utc>>,
@@ -627,6 +704,14 @@ impl AccountManager {
VoucherSubmission(data_response, tx) => {
self.consume_voucher_result(data_response, tx).await
}
+ InitPlayPurchase(data_response, tx) => {
+ self.consume_init_play_purchase_result(data_response, tx)
+ .await
+ }
+ VerifyPlayPurchase(data_response, tx) => {
+ self.consume_verify_play_purchase_result(data_response, tx)
+ .await
+ }
ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await,
}
}
@@ -665,6 +750,42 @@ impl AccountManager {
let _ = tx.send(response);
}
+ async fn consume_init_play_purchase_result(
+ &mut self,
+ response: Result<PlayPurchasePaymentToken, Error>,
+ tx: ResponseTx<PlayPurchasePaymentToken>,
+ ) {
+ match &response {
+ Ok(_) => (),
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => log::error!("Failed to initialize play purchase: {}", err),
+ }
+ let _ = tx.send(response);
+ }
+
+ async fn consume_verify_play_purchase_result(
+ &mut self,
+ response: Result<(), Error>,
+ tx: ResponseTx<()>,
+ ) {
+ match &response {
+ Ok(_) => (),
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => log::error!("Failed to verify play purchase: {}", err),
+ }
+ let _ = tx.send(response);
+ }
+
async fn consume_expiry_result(&mut self, response: Result<DateTime<Utc>, Error>) {
match response {
Ok(expiry) => {
diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs
index 9c967e4413..85d9d56d84 100644
--- a/mullvad-daemon/src/device/service.rs
+++ b/mullvad-daemon/src/device/service.rs
@@ -3,7 +3,7 @@ use std::{future::Future, time::Duration};
use chrono::{DateTime, Utc};
use futures::future::{abortable, AbortHandle};
use mullvad_types::{
- account::{AccountToken, VoucherSubmission},
+ account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
device::{Device, DeviceId},
wireguard::WireguardData,
};
@@ -320,6 +320,45 @@ impl AccountService {
}
result.map_err(map_rest_error)
}
+
+ pub async fn init_play_purchase(
+ &self,
+ account_token: AccountToken,
+ ) -> Result<PlayPurchasePaymentToken, Error> {
+ let mut proxy = self.proxy.clone();
+ let api_handle = self.api_availability.clone();
+ let result = retry_future(
+ move || proxy.init_play_purchase(account_token.clone()),
+ move |result| should_retry(result, &api_handle),
+ RETRY_ACTION_STRATEGY,
+ )
+ .await;
+ if result.is_ok() {
+ self.initial_check_abort_handle.abort();
+ self.api_availability.resume_background();
+ }
+ result.map_err(map_rest_error)
+ }
+
+ pub async fn verify_play_purchase(
+ &self,
+ account_token: AccountToken,
+ play_purchase: PlayPurchase,
+ ) -> Result<(), Error> {
+ let mut proxy = self.proxy.clone();
+ let api_handle = self.api_availability.clone();
+ let result = retry_future(
+ move || proxy.verify_play_purchase(account_token.clone(), play_purchase.clone()),
+ move |result| should_retry(result, &api_handle),
+ RETRY_ACTION_STRATEGY,
+ )
+ .await;
+ if result.is_ok() {
+ self.initial_check_abort_handle.abort();
+ self.api_availability.resume_background();
+ }
+ result.map_err(map_rest_error)
+ }
}
pub fn spawn_account_service(
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index f7343acc87..ceb50ca2b2 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -40,7 +40,7 @@ use mullvad_relay_selector::{
};
use mullvad_types::{
access_method::{AccessMethod, AccessMethodSetting},
- account::{AccountData, AccountToken, VoucherSubmission},
+ account::{AccountData, AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
@@ -178,6 +178,16 @@ pub enum Error {
#[cfg(target_os = "macos")]
#[error(display = "Failed to set exclusion group")]
GroupIdError(#[error(source)] io::Error),
+
+ // TODO
+ //#[cfg(target_os = "android")]
+ #[error(display = "Failed to initialize play purchase")]
+ InitPlayPurchase(#[error(source)] device::Error),
+
+ // TODO
+ //#[cfg(target_os = "android")]
+ #[error(display = "Failed to verify play purchase")]
+ VerifyPlayPurchase(#[error(source)] device::Error),
}
/// Enum representing commands that can be sent to the daemon.
@@ -327,6 +337,14 @@ pub enum DaemonCommand {
/// to bypass the tunnel in blocking states.
#[cfg(target_os = "android")]
BypassSocket(RawFd, oneshot::Sender<()>),
+ /// Initialize a google play purchase through the API.
+ ///TODO
+ //#[cfg(target_os = "android")]
+ InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken, Error>),
+ /// Verify that a google play payment was successful through the API.
+ ///TODO
+ //#[cfg(target_os = "android")]
+ VerifyPlayPurchase(ResponseTx<(), Error>, PlayPurchase),
}
/// All events that can happen in the daemon. Sent from various threads and exposed interfaces.
@@ -1027,6 +1045,10 @@ where
GetAccountData(tx, account_token) => self.on_get_account_data(tx, account_token),
GetWwwAuthToken(tx) => self.on_get_www_auth_token(tx).await,
SubmitVoucher(tx, voucher) => self.on_submit_voucher(tx, voucher),
+ InitPlayPurchase(tx) => self.on_init_play_purchase(tx),
+ VerifyPlayPurchase(tx, play_purchase) => {
+ self.on_verify_play_purchase(tx, play_purchase)
+ }
GetRelayLocations(tx) => self.on_get_relay_locations(tx),
UpdateRelayLocations => self.on_update_relay_locations().await,
LoginAccount(tx, account_token) => self.on_login_account(tx, account_token),
@@ -1404,6 +1426,34 @@ where
});
}
+ fn on_init_play_purchase(&mut self, tx: ResponseTx<PlayPurchasePaymentToken, Error>) {
+ let manager = self.account_manager.clone();
+ tokio::spawn(async move {
+ Self::oneshot_send(
+ tx,
+ manager
+ .init_play_purchase()
+ .await
+ .map_err(Error::InitPlayPurchase),
+ "init_play_purchase response",
+ );
+ });
+ }
+
+ fn on_verify_play_purchase(&mut self, tx: ResponseTx<(), Error>, play_purchase: PlayPurchase) {
+ let manager = self.account_manager.clone();
+ tokio::spawn(async move {
+ Self::oneshot_send(
+ tx,
+ manager
+ .verify_play_purchase(play_purchase)
+ .await
+ .map_err(Error::VerifyPlayPurchase),
+ "verify_play_purchase response",
+ );
+ });
+ }
+
fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) {
Self::oneshot_send(tx, self.relay_selector.get_locations(), "relay locations");
}
diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs
index 771c432a7a..c64c94041e 100644
--- a/mullvad-jni/src/daemon_interface.rs
+++ b/mullvad-jni/src/daemon_interface.rs
@@ -1,7 +1,7 @@
use futures::{channel::oneshot, executor::block_on};
use mullvad_daemon::{device, DaemonCommand, DaemonCommandSender};
use mullvad_types::{
- account::{AccountData, AccountToken, VoucherSubmission},
+ account::{AccountData, AccountToken, PlayPurchase, VoucherSubmission},
device::{Device, DeviceState},
location::GeoIpLocation,
relay_constraints::{ObfuscationSettings, RelaySettingsUpdate},
@@ -307,6 +307,26 @@ impl DaemonInterface {
.map_err(Error::from)
}
+ pub fn init_play_purchase(&self) -> Result<String> {
+ let (tx, rx) = oneshot::channel();
+
+ self.send_command(DaemonCommand::InitPlayPurchase(tx))?;
+
+ block_on(rx)
+ .map_err(|_| Error::NoResponse)?
+ .map_err(Error::from)
+ }
+
+ pub fn verify_play_purchase(&self, play_purchase: PlayPurchase) -> Result<()> {
+ let (tx, rx) = oneshot::channel();
+
+ self.send_command(DaemonCommand::VerifyPlayPurchase(tx, play_purchase))?;
+
+ block_on(rx)
+ .map_err(|_| Error::NoResponse)?
+ .map_err(Error::from)
+ }
+
pub fn update_relay_settings(&self, update: RelaySettingsUpdate) -> Result<()> {
let (tx, rx) = oneshot::channel();
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index 6f33938e99..ff7a978799 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -24,7 +24,7 @@ use mullvad_daemon::{
DaemonCommandChannel,
};
use mullvad_types::{
- account::{AccountData, VoucherSubmission},
+ account::{AccountData, PlayPurchase, VoucherSubmission},
settings::DnsOptions,
};
use std::{
@@ -191,6 +191,62 @@ impl From<daemon_interface::Error> for VoucherSubmissionError {
}
}
+#[derive(IntoJava)]
+#[jnix(package = "net.mullvad.mullvadvpn.model")]
+pub enum PlayPurchaseInitResult {
+ Ok(String),
+ Error(PlayPurchaseInitError),
+}
+
+#[derive(IntoJava)]
+#[jnix(package = "net.mullvad.mullvadvpn.model")]
+pub enum PlayPurchaseInitError {
+ OtherError,
+}
+
+impl From<Result<String, daemon_interface::Error>> for PlayPurchaseInitResult {
+ fn from(result: Result<String, daemon_interface::Error>) -> Self {
+ match result {
+ Ok(obfuscated_id) => PlayPurchaseInitResult::Ok(obfuscated_id),
+ Err(error) => PlayPurchaseInitResult::Error(error.into()),
+ }
+ }
+}
+
+impl From<daemon_interface::Error> for PlayPurchaseInitError {
+ fn from(error: daemon_interface::Error) -> Self {
+ PlayPurchaseInitError::OtherError
+ }
+}
+
+#[derive(IntoJava)]
+#[jnix(package = "net.mullvad.mullvadvpn.model")]
+pub enum PlayPurchaseVerifyResult {
+ Ok,
+ Error(PlayPurchaseVerifyError),
+}
+
+#[derive(IntoJava)]
+#[jnix(package = "net.mullvad.mullvadvpn.model")]
+pub enum PlayPurchaseVerifyError {
+ OtherError,
+}
+
+impl From<Result<(), daemon_interface::Error>> for PlayPurchaseVerifyResult {
+ fn from(result: Result<(), daemon_interface::Error>) -> Self {
+ match result {
+ Ok(()) => PlayPurchaseVerifyResult::Ok,
+ Err(error) => PlayPurchaseVerifyResult::Error(error.into()),
+ }
+ }
+}
+
+impl From<daemon_interface::Error> for PlayPurchaseVerifyError {
+ fn from(error: daemon_interface::Error) -> Self {
+ PlayPurchaseVerifyError::OtherError
+ }
+}
+
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_initialize(
@@ -1194,6 +1250,62 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_submitV
#[no_mangle]
#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_initPlayPurchase<'env>(
+ env: JNIEnv<'env>,
+ _: JObject<'_>,
+ daemon_interface_address: jlong,
+) -> JObject<'env> {
+ let env = JnixEnv::from(env);
+
+ let result =
+ // SAFETY: The address points to an instance valid for the duration of this function call
+ if let Some(daemon_interface) = unsafe { get_daemon_interface(daemon_interface_address) } {
+ let raw_result = daemon_interface.init_play_purchase();
+
+ if let Err(ref error) = &raw_result {
+ log_request_error("init google play purchase", error);
+ }
+
+ PlayPurchaseInitResult::from(raw_result)
+ } else {
+ PlayPurchaseInitResult::Error(PlayPurchaseInitError::OtherError)
+ };
+
+ result.into_java(&env).forget()
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
+pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_verifyPlayPurchase<
+ 'env,
+>(
+ env: JNIEnv<'env>,
+ _: JObject<'_>,
+ daemon_interface_address: jlong,
+ play_purchase: JObject<'_>,
+) -> JObject<'env> {
+ let env = JnixEnv::from(env);
+
+ let result =
+ // SAFETY: The address points to an instance valid for the duration of this function call
+ if let Some(daemon_interface) = unsafe { get_daemon_interface(daemon_interface_address) } {
+ let play_purchase = PlayPurchase::from_java(&env, play_purchase);
+ let raw_result = daemon_interface.verify_play_purchase(play_purchase);
+
+ if let Err(ref error) = &raw_result {
+ log_request_error("verify google play purchase", error);
+ }
+
+ PlayPurchaseVerifyResult::from(raw_result)
+ } else {
+ PlayPurchaseVerifyResult::Error(PlayPurchaseVerifyError::OtherError)
+ };
+
+ result.into_java(&env).forget()
+}
+
+#[no_mangle]
+#[allow(non_snake_case)]
pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_updateRelaySettings(
env: JNIEnv<'_>,
_: JObject<'_>,
diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs
index 16f6a963f2..2d12a80a2a 100644
--- a/mullvad-types/src/account.rs
+++ b/mullvad-types/src/account.rs
@@ -1,6 +1,6 @@
use chrono::{offset::Utc, DateTime};
#[cfg(target_os = "android")]
-use jnix::IntoJava;
+use jnix::{FromJava, IntoJava};
use serde::{Deserialize, Serialize};
/// Identifier used to identify a Mullvad account.
@@ -9,6 +9,11 @@ pub type AccountToken = String;
/// Identifier used to authenticate a Mullvad account.
pub type AccessToken = String;
+// TODO: Should be only android
+/// The payment token returned by initiating a google play purchase.
+/// In the API this is called the `obfuscated_id`.
+pub type PlayPurchasePaymentToken = String;
+
/// Account expiration info returned by the API via `/v1/me`.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[cfg_attr(target_os = "android", derive(IntoJava))]
@@ -39,6 +44,16 @@ pub struct VoucherSubmission {
pub new_expiry: DateTime<Utc>,
}
+/// TODO Should be only android
+/// `PlayPurchase` is provided to google in order to verify that a google play purchase was acknowledged.
+#[derive(Deserialize, Serialize, Debug, Clone)]
+#[cfg_attr(target_os = "android", derive(FromJava))]
+#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))]
+pub struct PlayPurchase {
+ pub product_id: String,
+ pub purchase_token: String,
+}
+
/// Token used for authentication in the API.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct AccessTokenData {