summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-api/src/lib.rs7
-rw-r--r--mullvad-api/src/rest.rs32
-rw-r--r--mullvad-daemon/src/device/api.rs16
-rw-r--r--mullvad-daemon/src/device/mod.rs155
-rw-r--r--mullvad-daemon/src/device/service.rs7
-rw-r--r--mullvad-daemon/src/lib.rs72
-rw-r--r--mullvad-jni/src/lib.rs4
-rw-r--r--mullvad-types/src/account.rs6
8 files changed, 181 insertions, 118 deletions
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index 647b5cf071..63f5c2ad5b 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -5,8 +5,10 @@ use chrono::{offset::Utc, DateTime};
use futures::channel::mpsc;
use futures::Stream;
use hyper::Method;
+#[cfg(target_os = "android")]
+use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
use mullvad_types::{
- account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
+ account::{AccountToken, VoucherSubmission},
version::AppVersion,
};
use proxy::ApiConnectionMode;
@@ -63,6 +65,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";
+#[cfg(target_os = "android")]
const GOOGLE_PAYMENTS_URL_PREFIX: &str = "payments/google-play/v1";
pub static API: LazyManual<ApiEndpoint> = LazyManual::new(ApiEndpoint::from_env_vars);
@@ -458,6 +461,7 @@ impl AccountsProxy {
}
}
+ #[cfg(target_os = "android")]
pub fn init_play_purchase(
&mut self,
account_token: AccountToken,
@@ -490,6 +494,7 @@ impl AccountsProxy {
}
}
+ #[cfg(target_os = "android")]
pub fn verify_play_purchase(
&mut self,
account_token: AccountToken,
diff --git a/mullvad-api/src/rest.rs b/mullvad-api/src/rest.rs
index 674bcf8c4e..c3687a1eee 100644
--- a/mullvad-api/src/rest.rs
+++ b/mullvad-api/src/rest.rs
@@ -394,10 +394,17 @@ impl From<Request> for RestRequest {
}
#[derive(serde::Deserialize)]
-pub struct ErrorResponse {
+struct OldErrorResponse {
pub code: String,
}
+/// If `NewErrorResponse::type` is not defined it should default to "about:blank"
+const DEFAULT_ERROR_TYPE: &str = "about:blank";
+#[derive(serde::Deserialize)]
+struct NewErrorResponse {
+ pub r#type: Option<String>,
+}
+
#[derive(Clone)]
pub struct RequestFactory {
hostname: String,
@@ -600,8 +607,27 @@ pub async fn handle_error_response<T>(response: Response) -> Result<T> {
status => match get_body_length(&response) {
0 => status.canonical_reason().unwrap_or("Unexpected error"),
body_length => {
- let err: ErrorResponse = deserialize_body_inner(response, body_length).await?;
- return Err(Error::ApiError(status, err.code));
+ return match response.headers().get("content-type") {
+ Some(content_type) if content_type == "application/problem+json" => {
+ // TODO: We should make sure we unify the new error format and the old
+ // error format so that they both produce the same Errors for the same
+ // problems after being processed.
+ let err: NewErrorResponse =
+ deserialize_body_inner(response, body_length).await?;
+ // The new error type replaces the `code` field with the `type` field.
+ // This is what is used to programmatically check the error.
+ Err(Error::ApiError(
+ status,
+ err.r#type
+ .unwrap_or_else(|| String::from(DEFAULT_ERROR_TYPE)),
+ ))
+ }
+ _ => {
+ let err: OldErrorResponse =
+ deserialize_body_inner(response, body_length).await?;
+ Err(Error::ApiError(status, err.code))
+ }
+ };
}
},
};
diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs
index 07e7b35311..d7b73d6e7b 100644
--- a/mullvad-daemon/src/device/api.rs
+++ b/mullvad-daemon/src/device/api.rs
@@ -2,11 +2,9 @@ use std::pin::Pin;
use chrono::{DateTime, Utc};
use futures::{future::FusedFuture, Future};
-use mullvad_types::{
- account::{PlayPurchasePaymentToken, VoucherSubmission},
- device::Device,
- wireguard::WireguardData,
-};
+#[cfg(target_os = "android")]
+use mullvad_types::account::PlayPurchasePaymentToken;
+use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData};
use super::{Error, PrivateAccountAndDevice, ResponseTx};
@@ -51,6 +49,7 @@ impl CurrentApiCall {
self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx)));
}
+ #[cfg(target_os = "android")]
pub fn set_init_play_purchase(
&mut self,
init_play_purchase_call: ApiCall<PlayPurchasePaymentToken>,
@@ -59,6 +58,7 @@ impl CurrentApiCall {
self.current_call = Some(Call::InitPlayPurchase(init_play_purchase_call, Some(tx)));
}
+ #[cfg(target_os = "android")]
pub fn set_verify_play_purchase(
&mut self,
verify_play_purchase_call: ApiCall<()>,
@@ -132,10 +132,12 @@ enum Call {
ApiCall<VoucherSubmission>,
Option<ResponseTx<VoucherSubmission>>,
),
+ #[cfg(target_os = "android")]
InitPlayPurchase(
ApiCall<PlayPurchasePaymentToken>,
Option<ResponseTx<PlayPurchasePaymentToken>>,
),
+ #[cfg(target_os = "android")]
VerifyPlayPurchase(ApiCall<()>, Option<ResponseTx<()>>),
ExpiryCheck(ApiCall<DateTime<Utc>>),
}
@@ -170,6 +172,7 @@ impl futures::Future for Call {
std::task::Poll::Pending
}
}
+ #[cfg(target_os = "android")]
InitPlayPurchase(call, tx) => {
if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
std::task::Poll::Ready(ApiResult::InitPlayPurchase(
@@ -180,6 +183,7 @@ impl futures::Future for Call {
std::task::Poll::Pending
}
}
+ #[cfg(target_os = "android")]
VerifyPlayPurchase(call, tx) => {
if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
std::task::Poll::Ready(ApiResult::VerifyPlayPurchase(
@@ -203,10 +207,12 @@ pub(crate) enum ApiResult {
Result<VoucherSubmission, Error>,
ResponseTx<VoucherSubmission>,
),
+ #[cfg(target_os = "android")]
InitPlayPurchase(
Result<PlayPurchasePaymentToken, Error>,
ResponseTx<PlayPurchasePaymentToken>,
),
+ #[cfg(target_os = "android")]
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 528c25ce9a..df5606a143 100644
--- a/mullvad-daemon/src/device/mod.rs
+++ b/mullvad-daemon/src/device/mod.rs
@@ -5,13 +5,16 @@ use futures::{
};
use mullvad_api::rest;
+#[cfg(target_os = "android")]
+use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
use mullvad_types::{
- account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
+ account::{AccountToken, VoucherSubmission},
device::{
AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DeviceState,
},
wireguard::{self, RotationInterval, WireguardData},
};
+
use std::{
future::Future,
path::Path,
@@ -305,7 +308,9 @@ enum AccountManagerCommand {
SetRotationInterval(RotationInterval, ResponseTx<()>),
ValidateDevice(ResponseTx<()>),
SubmitVoucher(String, ResponseTx<VoucherSubmission>),
+ #[cfg(target_os = "android")]
InitPlayPurchase(ResponseTx<PlayPurchasePaymentToken>),
+ #[cfg(target_os = "android")]
VerifyPlayPurchase(ResponseTx<()>, PlayPurchase),
CheckExpiry(ResponseTx<DateTime<Utc>>),
Shutdown(oneshot::Sender<()>),
@@ -361,20 +366,22 @@ impl AccountManagerHandle {
.await
}
+ pub async fn check_expiry(&self) -> Result<DateTime<Utc>, Error> {
+ self.send_command(AccountManagerCommand::CheckExpiry).await
+ }
+
+ #[cfg(target_os = "android")]
pub async fn init_play_purchase(&self) -> Result<PlayPurchasePaymentToken, Error> {
self.send_command(AccountManagerCommand::InitPlayPurchase)
.await
}
+ #[cfg(target_os = "android")]
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
- }
-
pub async fn shutdown(self) {
let (tx, rx) = oneshot::channel();
let _ = self
@@ -526,15 +533,17 @@ impl AccountManager {
Some(AccountManagerCommand::SubmitVoucher(voucher, tx)) => {
self.handle_voucher_submission(tx, voucher, &mut current_api_call);
},
+ Some(AccountManagerCommand::CheckExpiry(tx)) => {
+ self.handle_expiry_request(tx, &mut current_api_call);
+ },
+ #[cfg(target_os = "android")]
Some(AccountManagerCommand::InitPlayPurchase(tx)) => {
self.handle_init_play_purchase(tx, &mut current_api_call);
},
+ #[cfg(target_os = "android")]
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);
- },
None => {
break;
@@ -607,6 +616,7 @@ impl AccountManager {
}
}
+ #[cfg(target_os = "android")]
fn handle_init_play_purchase(
&mut self,
tx: ResponseTx<PlayPurchasePaymentToken>,
@@ -634,31 +644,24 @@ impl AccountManager {
}
}
- fn handle_verify_play_purchase(
+ fn handle_expiry_request(
&mut self,
- tx: ResponseTx<()>,
- play_purchase: PlayPurchase,
+ tx: ResponseTx<DateTime<Utc>>,
current_api_call: &mut api::CurrentApiCall,
) {
if current_api_call.is_logging_in() {
let _ = tx.send(Err(Error::AccountChange));
return;
}
+ if current_api_call.is_checking_expiry() {
+ self.expiry_requests.push(tx);
+ 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() {
+ match self.expiry_call() {
Ok(call) => {
- current_api_call.set_verify_play_purchase(Box::pin(call), tx);
+ current_api_call.set_expiry_check(Box::pin(call));
+ self.expiry_requests.push(tx);
}
Err(err) => {
let _ = tx.send(Err(err));
@@ -666,24 +669,32 @@ impl AccountManager {
}
}
- fn handle_expiry_request(
+ #[cfg(target_os = "android")]
+ fn handle_verify_play_purchase(
&mut self,
- tx: ResponseTx<DateTime<Utc>>,
+ 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;
}
- if current_api_call.is_checking_expiry() {
- self.expiry_requests.push(tx);
- return;
- }
- match self.expiry_call() {
+ 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_expiry_check(Box::pin(call));
- self.expiry_requests.push(tx);
+ current_api_call.set_verify_play_purchase(Box::pin(call), tx);
}
Err(err) => {
let _ = tx.send(Err(err));
@@ -704,15 +715,17 @@ impl AccountManager {
VoucherSubmission(data_response, tx) => {
self.consume_voucher_result(data_response, tx).await
}
+ ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await,
+ #[cfg(target_os = "android")]
InitPlayPurchase(data_response, tx) => {
self.consume_init_play_purchase_result(data_response, tx)
.await
}
+ #[cfg(target_os = "android")]
VerifyPlayPurchase(data_response, tx) => {
self.consume_verify_play_purchase_result(data_response, tx)
.await
}
- ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await,
}
}
@@ -750,42 +763,6 @@ 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) => {
@@ -920,6 +897,44 @@ impl AccountManager {
}
}
+ #[cfg(target_os = "android")]
+ 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);
+ }
+
+ #[cfg(target_os = "android")]
+ 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);
+ }
+
fn drain_device_requests_with_err(&mut self, err: Error) {
let cloneable_err = Arc::new(err);
Self::drain_requests(&mut self.rotation_requests, || {
diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs
index 85d9d56d84..fdda61297f 100644
--- a/mullvad-daemon/src/device/service.rs
+++ b/mullvad-daemon/src/device/service.rs
@@ -2,8 +2,10 @@ use std::{future::Future, time::Duration};
use chrono::{DateTime, Utc};
use futures::future::{abortable, AbortHandle};
+#[cfg(target_os = "android")]
+use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
use mullvad_types::{
- account::{AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
+ account::{AccountToken, VoucherSubmission},
device::{Device, DeviceId},
wireguard::WireguardData,
};
@@ -321,6 +323,7 @@ impl AccountService {
result.map_err(map_rest_error)
}
+ #[cfg(target_os = "android")]
pub async fn init_play_purchase(
&self,
account_token: AccountToken,
@@ -340,6 +343,7 @@ impl AccountService {
result.map_err(map_rest_error)
}
+ #[cfg(target_os = "android")]
pub async fn verify_play_purchase(
&self,
account_token: AccountToken,
@@ -448,6 +452,7 @@ fn should_retry_backoff<T>(result: &Result<T, RestError>) -> bool {
fn map_rest_error(error: rest::Error) -> Error {
match error {
RestError::ApiError(_status, ref code) => match code.as_str() {
+ // TODO: Implement invalid payment
mullvad_api::DEVICE_NOT_FOUND => Error::InvalidDevice,
mullvad_api::INVALID_ACCOUNT => Error::InvalidAccount,
mullvad_api::MAX_DEVICES_REACHED => Error::MaxDevicesReached,
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 8f661d981a..1077185ca3 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -38,9 +38,11 @@ use mullvad_relay_selector::{
updater::{RelayListUpdater, RelayListUpdaterHandle},
RelaySelector, SelectorConfig,
};
+#[cfg(target_os = "android")]
+use mullvad_types::account::{PlayPurchase, PlayPurchasePaymentToken};
use mullvad_types::{
access_method::{AccessMethod, AccessMethodSetting},
- account::{AccountData, AccountToken, PlayPurchase, PlayPurchasePaymentToken, VoucherSubmission},
+ account::{AccountData, AccountToken, VoucherSubmission},
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
@@ -1041,10 +1043,6 @@ 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),
@@ -1127,6 +1125,12 @@ where
PrepareRestart => self.on_prepare_restart(),
#[cfg(target_os = "android")]
BypassSocket(fd, tx) => self.on_bypass_socket(fd, tx),
+ #[cfg(target_os = "android")]
+ InitPlayPurchase(tx) => self.on_init_play_purchase(tx),
+ #[cfg(target_os = "android")]
+ VerifyPlayPurchase(tx, play_purchase) => {
+ self.on_verify_play_purchase(tx, play_purchase)
+ }
}
}
@@ -1422,34 +1426,6 @@ 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");
}
@@ -2416,6 +2392,36 @@ where
}
}
+ #[cfg(target_os = "android")]
+ 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",
+ );
+ });
+ }
+
+ #[cfg(target_os = "android")]
+ 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",
+ );
+ });
+ }
+
/// Set the target state of the client. If it changed trigger the operations needed to
/// progress towards that state.
/// Returns a bool representing whether or not a state change was initiated.
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index ff7a978799..a40bf73fc1 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -214,7 +214,7 @@ impl From<Result<String, daemon_interface::Error>> for PlayPurchaseInitResult {
}
impl From<daemon_interface::Error> for PlayPurchaseInitError {
- fn from(error: daemon_interface::Error) -> Self {
+ fn from(_error: daemon_interface::Error) -> Self {
PlayPurchaseInitError::OtherError
}
}
@@ -242,7 +242,7 @@ impl From<Result<(), daemon_interface::Error>> for PlayPurchaseVerifyResult {
}
impl From<daemon_interface::Error> for PlayPurchaseVerifyError {
- fn from(error: daemon_interface::Error) -> Self {
+ fn from(_error: daemon_interface::Error) -> Self {
PlayPurchaseVerifyError::OtherError
}
}
diff --git a/mullvad-types/src/account.rs b/mullvad-types/src/account.rs
index 2d12a80a2a..7adb7fbffa 100644
--- a/mullvad-types/src/account.rs
+++ b/mullvad-types/src/account.rs
@@ -9,9 +9,9 @@ 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`.
+#[cfg(target_os = "android")]
pub type PlayPurchasePaymentToken = String;
/// Account expiration info returned by the API via `/v1/me`.
@@ -44,14 +44,14 @@ 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"))]
+#[cfg(target_os = "android")]
pub struct PlayPurchase {
pub product_id: String,
- pub purchase_token: String,
+ pub purchase_token: PlayPurchasePaymentToken,
}
/// Token used for authentication in the API.