1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use std::sync::Arc;

use lightning::log_error;
use lightning::util::logger::*;
use reqwest::{Method, StatusCode, Url};
use serde::{Deserialize, Serialize};

use crate::{auth::MutinyAuthClient, error::MutinyError, logging::MutinyLogger, nodemanager::Plan};

pub struct MutinySubscriptionClient {
    auth_client: Arc<MutinyAuthClient>,
    url: String,
    logger: Arc<MutinyLogger>,
}

impl MutinySubscriptionClient {
    pub fn new(auth_client: Arc<MutinyAuthClient>, url: String, logger: Arc<MutinyLogger>) -> Self {
        Self {
            auth_client,
            url,
            logger,
        }
    }

    /// Checks whether or not the user is subscribed to Mutiny+.
    /// Submits a NWC string to keep the subscription active if not expired.
    ///
    /// Returns None if there's no subscription at all.
    /// Returns Some(u64) for their unix expiration timestamp, which may be in the
    /// past or in the future, depending on whether or not it is currently active.
    pub async fn check_subscribed(&self) -> Result<Option<u64>, MutinyError> {
        let url = Url::parse(&format!("{}/v1/check-subscribed", self.url)).map_err(|e| {
            log_error!(self.logger, "Error parsing check subscribed url: {e}");
            MutinyError::ConnectionFailed
        })?;
        let res = self
            .auth_client
            .request(Method::GET, url, None)
            .await?
            .json::<CheckSubscribedResponse>()
            .await
            .map_err(|e| {
                log_error!(self.logger, "Error parsing subscribe response: {e}");
                MutinyError::ConnectionFailed
            })?;
        if let Some(expired) = res.expired_date {
            Ok(Some(expired))
        } else {
            Ok(None)
        }
    }

    pub async fn get_plans(&self) -> Result<Vec<Plan>, MutinyError> {
        let url = Url::parse(&format!("{}/v1/plans", self.url)).map_err(|e| {
            log_error!(self.logger, "Error parsing plan url: {e}");
            MutinyError::ConnectionFailed
        })?;
        let res = self
            .auth_client
            .request(Method::GET, url, None)
            .await?
            .json::<Vec<Plan>>()
            .await
            .map_err(|e| {
                log_error!(self.logger, "Error parsing plans: {e}");
                MutinyError::ConnectionFailed
            })?;

        Ok(res)
    }

    pub async fn subscribe_to_plan(&self, id: u8) -> Result<String, MutinyError> {
        let url = Url::parse(&format!("{}/v1/plans/{}/subscribe", self.url, id)).map_err(|e| {
            log_error!(self.logger, "Error parsing subscribe url: {e}");
            MutinyError::ConnectionFailed
        })?;
        let res = self
            .auth_client
            .request(Method::POST, url, None)
            .await?
            .json::<UserInvoiceResponse>()
            .await
            .map_err(|e| {
                log_error!(self.logger, "Error parsing subscription invoice: {e}");
                MutinyError::ConnectionFailed
            })?;

        Ok(res.inv)
    }

    pub async fn submit_nwc(&self, wallet_connect_string: String) -> Result<(), MutinyError> {
        let url = Url::parse(&format!("{}/v1/wallet-connect", self.url)).map_err(|e| {
            log_error!(self.logger, "Error parsing wallet connect url: {e}");
            MutinyError::ConnectionFailed
        })?;
        let body = serde_json::to_value(WalletConnectRequest {
            wallet_connect_string,
        })?;

        let res = self
            .auth_client
            .request(Method::POST, url, Some(body))
            .await?;

        match res.status() {
            StatusCode::OK => Ok(()), // If status is 200 OK, return Ok(()).
            status => {
                log_error!(self.logger, "Unexpected status code: {status}");
                Err(MutinyError::ConnectionFailed)
            }
        }
    }
}

#[derive(Serialize, Deserialize)]
pub struct CheckSubscribedResponse {
    pub expired_date: Option<u64>,
}

#[derive(Serialize, Deserialize)]
pub struct UserInvoiceResponse {
    inv: String,
}

#[derive(Serialize, Deserialize)]
pub struct WalletConnectRequest {
    wallet_connect_string: String,
}