extern crate base64;
extern crate google_androidpublisher3 as androidpublisher3;
use androidpublisher3::api::{
ProductPurchase, ProductPurchasesAcknowledgeRequest, SubscriptionPurchase,
SubscriptionPurchasesAcknowledgeRequest,
};
use androidpublisher3::{AndroidPublisher, Result};
use async_trait::async_trait;
use rsa::{PaddingScheme, PublicKey};
use sha1::{Digest, Sha1};
use thiserror::Error;
#[async_trait]
pub trait IABProduct {
async fn acknowledge(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
developer_payload: Option<String>,
) -> Result<()>;
async fn verify(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
) -> Result<ProductPurchase>;
}
#[async_trait]
pub trait IABSubscription {
async fn acknowledge_subscription(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
developer_payload: Option<String>,
) -> Result<()>;
async fn verify_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<SubscriptionPurchase>;
async fn cancel_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()>;
async fn refund_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()>;
async fn revoke_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()>;
}
#[derive(Error, Debug, PartialEq)]
pub enum SignatureError {
#[error("base64 decode error. cause = {0}")]
DecodeError(String),
#[error("x509 error. cause = {0}")]
X509Error(String),
#[error("RSA error. cause = {0}")]
RSAError(String),
#[error("Verify error. cause = {0}")]
VerifyError(String),
}
pub struct IABClient {
publisher: AndroidPublisher,
}
impl IABClient {
pub async fn new(
service_account_key: &[u8],
client: hyper::Client<
hyper_rustls::HttpsConnector<hyper::client::connect::HttpConnector>,
hyper::body::Body,
>,
) -> IABClient {
let service_account_key: yup_oauth2::ServiceAccountKey =
serde_json::from_slice(service_account_key).unwrap();
let auth = yup_oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.build()
.await
.unwrap();
IABClient {
publisher: google_androidpublisher3::AndroidPublisher::new(client, auth),
}
}
}
#[async_trait]
impl IABProduct for IABClient {
async fn acknowledge(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
developer_payload: Option<String>,
) -> Result<()> {
let req = ProductPurchasesAcknowledgeRequest { developer_payload };
return match self
.publisher
.purchases()
.products_acknowledge(req, package_name, product_id, purchase_token)
.doit()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
};
}
async fn verify(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
) -> Result<ProductPurchase> {
return match self
.publisher
.purchases()
.products_get(package_name, product_id, purchase_token)
.doit()
.await
{
Ok(ok) => Ok(ok.1),
Err(err) => Err(err),
};
}
}
#[async_trait]
impl IABSubscription for IABClient {
async fn acknowledge_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
developer_payload: Option<String>,
) -> Result<()> {
let req = SubscriptionPurchasesAcknowledgeRequest { developer_payload };
return match self
.publisher
.purchases()
.subscriptions_acknowledge(req, package_name, subscription_id, purchase_token)
.doit()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
};
}
async fn verify_subscription(
&self,
package_name: &str,
product_id: &str,
purchase_token: &str,
) -> Result<SubscriptionPurchase> {
return match self
.publisher
.purchases()
.subscriptions_get(package_name, product_id, purchase_token)
.doit()
.await
{
Ok(ok) => Ok(ok.1),
Err(err) => Err(err),
};
}
async fn cancel_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()> {
return match self
.publisher
.purchases()
.subscriptions_cancel(package_name, subscription_id, purchase_token)
.doit()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
};
}
async fn refund_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()> {
return match self
.publisher
.purchases()
.subscriptions_refund(package_name, subscription_id, purchase_token)
.doit()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
};
}
async fn revoke_subscription(
&self,
package_name: &str,
subscription_id: &str,
purchase_token: &str,
) -> Result<()> {
return match self
.publisher
.purchases()
.subscriptions_revoke(package_name, subscription_id, purchase_token)
.doit()
.await
{
Ok(_) => Ok(()),
Err(err) => Err(err),
};
}
}
pub fn verify_signature(
base64_encoded_public_key: &[u8],
receipt: &[u8],
signature: &[u8],
) -> std::result::Result<(), SignatureError> {
let decoded_public_key = match base64::decode(base64_encoded_public_key) {
Ok(r) => r,
Err(err) => return Err(SignatureError::DecodeError(err.to_string())),
};
let public_key_interface =
match x509_parser::x509::SubjectPublicKeyInfo::from_der(decoded_public_key.as_ref()) {
Ok(r) => r.1.subject_public_key.data,
Err(err) => return Err(SignatureError::X509Error(err.to_string())),
};
let public_key = match rsa::RSAPublicKey::from_pkcs1(public_key_interface) {
Ok(r) => r,
Err(err) => return Err(SignatureError::RSAError(err.to_string())),
};
let decoded_signature = match base64::decode(signature) {
Ok(r) => r,
Err(err) => return Err(SignatureError::DecodeError(err.to_string())),
};
return match public_key.verify(
PaddingScheme::PKCS1v15Sign {
hash: Some(rsa::hash::Hash::SHA1),
},
Sha1::digest(receipt).as_ref(),
decoded_signature.as_ref(),
) {
Ok(_) => Ok(()),
Err(err) => return Err(SignatureError::VerifyError(err.to_string())),
};
}