lockbook-server 26.4.13

Lockbook's server, exposed as a library for testing.
Documentation
use crate::billing::billing_model::StripeUserInfo;
use crate::document_service::DocumentService;
use crate::{ClientError, ServerError, ServerState, StripeWebhookError};
use google_androidpublisher3::hyper::body::Bytes;
use google_androidpublisher3::hyper::header::HeaderValue;
use lb_rs::model::api::{
    PaymentMethod, StripeAccountState, StripeAccountTier, UpgradeAccountStripeError,
};
use lb_rs::model::file_metadata::Owner;
use libsecp256k1::PublicKey;

use stripe::{Event, Invoice};
use tracing::*;
use uuid::Uuid;

use super::app_store_client::AppStoreClient;
use super::google_play_client::GooglePlayClient;
use super::stripe_client::StripeClient;

impl<S, A, G, D> ServerState<S, A, G, D>
where
    S: StripeClient,
    A: AppStoreClient,
    G: GooglePlayClient,
    D: DocumentService,
{
    pub async fn create_subscription(
        &self, public_key: &PublicKey, account_tier: &StripeAccountTier,
        maybe_user_info: Option<StripeUserInfo>,
    ) -> Result<StripeUserInfo, ServerError<UpgradeAccountStripeError>> {
        let owner = Owner(*public_key);
        let (payment_method, price_id) = match account_tier {
            StripeAccountTier::Premium(payment_method) => {
                (payment_method, &self.config.billing.stripe.premium_price_id)
            }
        };

        let (customer_id, customer_name, payment_method_id, last_4) = match payment_method {
            PaymentMethod::NewCard { number, exp_year, exp_month, cvc } => {
                info!(?owner, "Creating a new card");
                let payment_method_resp = self
                    .stripe_client
                    .create_payment_method(number, *exp_month, *exp_year, cvc)
                    .await?;

                let last_4 = payment_method_resp
                    .card
                    .as_ref()
                    .ok_or_else::<ServerError<UpgradeAccountStripeError>, _>(|| {
                        internal!(
                            "Cannot retrieve card info from payment method response: {:?}",
                            payment_method_resp
                        )
                    })?
                    .last4
                    .clone();

                info!(?owner, ?last_4, "Created a new payment method");

                let (customer_id, customer_name) = match &maybe_user_info {
                    None => {
                        info!(?owner, "User has no customer_id; creating one with stripe now");

                        let customer_name = Uuid::new_v4();
                        let customer_resp = self
                            .stripe_client
                            .create_customer(
                                customer_name.to_string(),
                                payment_method_resp.id.clone(),
                            )
                            .await?;
                        let customer_id = customer_resp.id.to_string();

                        info!(?owner, ?customer_id, "Created customer_id");

                        self.index_db
                            .lock()
                            .await
                            .stripe_ids
                            .insert(customer_id, Owner(*public_key))?;

                        (customer_resp.id, customer_name)
                    }
                    Some(user_info) => {
                        let customer_id = &user_info.customer_id;

                        info!(?owner, ?customer_id, "User already has customer_id");

                        let customer_id = customer_id.parse()?;

                        info!(?owner, "Disabling old card since a new card has just been added");

                        self.stripe_client
                            .detach_payment_method_from_customer(
                                &user_info.payment_method_id.parse()?,
                            )
                            .await?;

                        (customer_id, user_info.customer_name)
                    }
                };

                info!(
                    ?owner,
                    "Creating a setup intent to confirm a users payment method for their subscription"
                );

                let setup_intent_resp = self
                    .stripe_client
                    .create_setup_intent(customer_id.clone(), payment_method_resp.id.clone())
                    .await?;

                let setup_intent = setup_intent_resp.id.to_string();
                info!(?owner, ?setup_intent, "Created a setup intent");

                (customer_id, customer_name, payment_method_resp.id.to_string(), last_4)
            }
            PaymentMethod::OldCard => {
                info!(?owner, "Using an old card stored on redis");

                let user_info = maybe_user_info
                    .ok_or(ClientError(UpgradeAccountStripeError::OldCardDoesNotExist))?;

                (
                    user_info.customer_id.parse()?,
                    user_info.customer_name,
                    user_info.payment_method_id,
                    user_info.last_4,
                )
            }
        };

        info!(?owner, "Successfully retrieved card");

        let subscription_resp = self
            .stripe_client
            .create_subscription(customer_id.clone(), &payment_method_id, price_id)
            .await?;

        let subscription_id = subscription_resp.id;
        info!(?owner, ?subscription_id, "Successfully created subscription");

        Ok(StripeUserInfo {
            customer_id: customer_id.to_string(),
            customer_name,
            price_id: price_id.to_string(),
            payment_method_id: payment_method_id.to_string(),
            last_4,
            subscription_id: subscription_id.to_string(),
            expiration_time: subscription_resp.current_period_end as u64,
            account_state: StripeAccountState::Ok,
        })
    }

    pub async fn get_public_key_from_invoice(
        &self, invoice: &Invoice,
    ) -> Result<PublicKey, ServerError<StripeWebhookError>> {
        let customer_id = match invoice.customer.as_ref().ok_or_else(|| {
            ClientError(StripeWebhookError::InvalidBody(
                "Cannot retrieve the customer_id".to_string(),
            ))
        })? {
            stripe::Expandable::Id(id) => id.to_string(),
            stripe::Expandable::Object(customer) => customer.id.to_string(),
        };

        let public_key = self
            .index_db
            .lock()
            .await
            .stripe_ids
            .get()
            .get(&customer_id)
            .copied()
            .ok_or_else(|| {
                internal!("There is no public_key related to this customer_id: {:?}", customer_id)
            })?;

        Ok(public_key.0)
    }

    pub fn verify_request_and_get_event(
        &self, request_body: &Bytes, stripe_sig: HeaderValue,
    ) -> Result<Event, ServerError<StripeWebhookError>> {
        let payload = std::str::from_utf8(request_body).map_err(|e| {
            ClientError(StripeWebhookError::InvalidBody(format!("Cannot get body as str: {e:?}")))
        })?;

        let sig = stripe_sig.to_str().map_err(|e| {
            ClientError(StripeWebhookError::InvalidHeader(format!(
                "Cannot get header as str: {e:?}"
            )))
        })?;

        info!("Verifying a stripe webhook request");

        Ok(stripe::Webhook::construct_event(
            payload,
            sig,
            &self.config.billing.stripe.signing_secret,
        )?)
    }
}