iapiab 0.1.0

iapiab verifies the purchase receipt via AppStore or GooglePlayStore.
Documentation
pub mod appstore;
pub mod playstore;

#[cfg(test)]
mod tests {
    use crate::appstore::model::{AppStoreVerifyError, IAPRequest};
    use crate::appstore::validator::{IAPClient, IAPVerifier};
    use crate::playstore::validator::{
        verify_signature, IABClient, IABProduct, IABSubscription, SignatureError,
    };

    #[tokio::test]
    async fn appstore_verify_success() {
        let v = IAPRequest {
            receipt_data: std::env::var("IAP_TEST_IOS_RECEIPT").unwrap(),
            password: None,
            exclude_old_transactions: false,
        };

        let client = IAPClient::new(reqwest::Client::new());
        let res = client.verify(&v).await;

        assert_eq!(false, res.is_err());
        assert_eq!(true, res.unwrap().error().is_none());
    }

    #[tokio::test]
    async fn appstore_verify_error() {
        let v = IAPRequest {
            receipt_data: String::from("aaa"),
            password: None,
            exclude_old_transactions: false,
        };

        let client = IAPClient::new(reqwest::Client::new());
        let res = client.verify(&v).await;

        assert_eq!(false, res.is_err());
        assert_eq!(
            res.unwrap().error().unwrap(),
            AppStoreVerifyError::InvalidReceiptData
        );
    }

    async fn create_iab_client() -> IABClient {
        let decoded_key =
            base64::decode(std::env::var("IAP_TEST_JSONKEY").unwrap().as_bytes()).unwrap();
        return IABClient::new(
            &decoded_key,
            hyper::Client::builder().build(hyper_rustls::HttpsConnector::with_native_roots()),
        )
        .await;
    }

    fn assert_invalid_value(res: google_androidpublisher3::Result<()>) {
        assert_eq!(true, res.is_err());
        match res.err().unwrap() {
            google_androidpublisher3::client::Error::BadRequest(e) => {
                assert_eq!("Invalid Value", e.error.message);
                assert_eq!(400, e.error.code);
            }
            _ => assert_eq!(false, true),
        };
    }

    fn assert_no_application(res: google_androidpublisher3::Result<()>) {
        assert_eq!(true, res.is_err());
        match res.err().unwrap() {
            google_androidpublisher3::client::Error::BadRequest(e) => {
                assert_eq!(
                    "No application was found for the given package name.",
                    e.error.message
                );
                assert_eq!(404, e.error.code);
            }
            _ => assert_eq!(false, true),
        };
    }

    #[tokio::test]
    async fn playstore_verify_success() {
        let client = create_iab_client().await;
        let res = client
            .verify(
                "com.yoshidan.UnityIAP",
                "item_18",
                std::env::var("IAP_TEST_ANDROID_TOKEN").unwrap().as_str(),
            )
            .await;
        assert_eq!(false, res.is_err());

        let value = res.unwrap();
        assert_eq!(value.acknowledgement_state.unwrap(), 1);
        assert_eq!(
            value.obfuscated_external_account_id.unwrap(),
            "npbunneskhjD3q6TLd/yHFexJM6SQej93ZebDOheqGc="
        );
    }

    #[tokio::test]
    async fn playstore_verify_error() {
        let client = create_iab_client().await;
        let res = client.verify("nopackage", "item_18", "hogehoge").await;
        assert_invalid_value(Err(res.err().unwrap()));
    }

    #[tokio::test]
    async fn playstore_acknowledge_acknowledged() {
        let client = create_iab_client().await;
        let res = client
            .acknowledge(
                "com.yoshidan.UnityIAP",
                "item_1",
                std::env::var("IAP_TEST_ANDROID_TOKEN").unwrap().as_str(),
                None,
            )
            .await;
        assert_eq!(true, res.is_err());
        match res.err().unwrap() {
            google_androidpublisher3::client::Error::BadRequest(e) => {
                assert_eq!(
                    "The purchase is not in a valid state to perform the desired operation.",
                    e.error.message
                );
                assert_eq!(400, e.error.code);
            }
            _ => assert_eq!(false, true),
        };
    }

    #[tokio::test]
    async fn playstore_acknowledge_error() {
        let client = create_iab_client().await;
        let res = client
            .acknowledge("nopackage", "item_18", "hoge", None)
            .await;
        assert_invalid_value(res);
    }

    #[tokio::test]
    async fn playstore_acknowledge_subscription_error() {
        let client = create_iab_client().await;
        let res = client
            .acknowledge_subscription("nopackage", "item_18", "hoge", None)
            .await;
        assert_invalid_value(res);
    }

    #[tokio::test]
    async fn playstore_cancel_subscription_error() {
        let client = create_iab_client().await;
        let res = client
            .cancel_subscription("nopackage", "item_18", "hoge")
            .await;
        assert_invalid_value(res);
    }

    #[tokio::test]
    async fn playstore_refund_subscription_error() {
        let client = create_iab_client().await;
        let res = client
            .refund_subscription("nopackage", "item_18", "hoge")
            .await;
        assert_no_application(res);
    }

    #[tokio::test]
    async fn playstore_revoke_subscription_error() {
        let client = create_iab_client().await;
        let res = client
            .revoke_subscription("nopackage", "item_18", "hoge")
            .await;
        assert_no_application(res);
    }

    #[tokio::test]
    async fn playstore_rerify_subscription_error() {
        let client = create_iab_client().await;
        let res = client
            .verify_subscription("nopackage", "item_18", "hoge")
            .await;
        assert_invalid_value(Err(res.err().unwrap()));
    }

    #[tokio::test]
    async fn playstore_verify_signature_success() {
        // https://github.com/awa/go-iap/blob/master/playstore/validator_test.go#L198
        let receipt = r#"{"orderId":"GPA.xxxx-xxxx-xxxx-xxxxx","packageName":"my.package","productId":"myproduct","purchaseTime":1437564796303,"purchaseState":0,"developerPayload":"user001","purchaseToken":"some-token"}"#;
        let pub_key = b"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGvModvVUrqJ9C5fy8J77ZQ7JDC6+tf5iK8C74/3mjmcvwo4nmprCgzR/BQIEuZWJi8KX+jiJUXKXF90JPsXHkKAPq6A1SCga7kWvs/M8srMpjNS9zJdwZF+eDOR0+lJEihO04zlpAV9ybPJ3Q621y1HUeVpwdxDNLQpJTuIflnwIDAQAB";
        let signature = b"gj0N8LANKXOw4OhWkS1UZmDVUxM1UIP28F6bDzEp7BCqcVAe0DuDxmAY5wXdEgMRx/VM1Nl2crjogeV60OqCsbIaWqS/ZJwdP127aKR0jk8sbX36ssyYZ0DdZdBdCr1tBZ/eSW1GlGuD/CgVaxns0JaWecXakgoV7j+RF2AFbS4=";
        assert_eq!(
            false,
            verify_signature(pub_key, receipt.as_bytes(), signature).is_err()
        )
    }

    #[tokio::test]
    async fn playstore_verify_signature_error() {
        // https://github.com/awa/go-iap/blob/master/playstore/validator_test.go#L198
        let receipt = r#"{"orderId":"GPA.xxxx-xxxx-xxxx-xxxx","packageName":"my.package","productId":"myproduct","purchaseTime":1437564796303,"purchaseState":0,"developerPayload":"user001","purchaseToken":"some-token"}"#;
        let pub_key = b"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGvModvVUrqJ9C5fy8J77ZQ7JDC6+tf5iK8C74/3mjmcvwo4nmprCgzR/BQIEuZWJi8KX+jiJUXKXF90JPsXHkKAPq6A1SCga7kWvs/M8srMpjNS9zJdwZF+eDOR0+lJEihO04zlpAV9ybPJ3Q621y1HUeVpwdxDNLQpJTuIflnwIDAQAB";
        let signature = b"gj0N8LANKXOw4OhWkS1UZmDVUxM1UIP28F6bDzEp7BCqcVAe0DuDxmAY5wXdEgMRx/VM1Nl2crjogeV60OqCsbIaWqS/ZJwdP127aKR0jk8sbX36ssyYZ0DdZdBdCr1tBZ/eSW1GlGuD/CgVaxns0JaWecXakgoV7j+RF2AFbS4=";
        let err = verify_signature(pub_key, receipt.as_bytes(), signature)
            .err()
            .unwrap();
        assert_eq!(
            SignatureError::VerifyError("verification error".to_string()),
            err
        );
    }

    #[tokio::test]
    async fn playstore_verify_signature_pubkey_error() {
        // https://github.com/awa/go-iap/blob/master/playstore/validator_test.go#L198
        let receipt = r#"{"orderId":"GPA.xxxx-xxxx-xxxx-xxxx","packageName":"my.package","productId":"myproduct","purchaseTime":1437564796303,"purchaseState":0,"developerPayload":"user001","purchaseToken":"some-token"}"#;
        let pub_key = b"KIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGvModvVUrqJ9C5fy8J77ZQ7JDC6+tf5iK8C74/3mjmcvwo4nmprCgzR/BQIEuZWJi8KX+jiJUXKXF90JPsXHkKAPq6A1SCga7kWvs/M8srMpjNS9zJdwZF+eDOR0+lJEihO04zlpAV9ybPJ3Q621y1HUeVpwdxDNLQpJTuIflnwIDAQAB";
        let signature = b"";
        match verify_signature(pub_key, receipt.as_bytes(), signature)
            .err()
            .unwrap()
        {
            SignatureError::X509Error(s) => {
                assert_eq!("Parsing Error: Der(InvalidTag)".to_string(), s);
            }
            _ => assert_eq!(false, true),
        }
    }
}