iapiab 0.1.0

iapiab verifies the purchase receipt via AppStore or GooglePlayStore.
Documentation
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),
        };
    }
}

// verify_signature verifies in app billing signature.
// You need to prepare a public key for your Android app's in app billing
// at https://play.google.com/apps/publish/
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())),
    };
}