From 55ff761e9eca313327f67c1d271ea1672d12c339 Mon Sep 17 00:00:00 2001 From: Sangamesh Kulkarni <59434228+Sangamesh26@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:44:12 +0530 Subject: [PATCH] feat: Convert QrData into Qr data image source url (#1674) --- crates/common_utils/src/errors.rs | 8 ++++++ crates/router/Cargo.toml | 2 ++ crates/router/src/consts.rs | 4 +++ crates/router/src/utils.rs | 46 +++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index fd662227db..cc29cd2971 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -68,6 +68,14 @@ pub enum CryptoError { SignatureVerificationFailed, } +/// Errors for Qr code handling +#[derive(Debug, thiserror::Error)] +pub enum QrCodeError { + /// Failed to encode data into Qr code + #[error("Failed to create Qr code")] + FailedToCreateQrCode, +} + /// Allows [error_stack::Report] to change between error contexts /// using the dependent [ErrorSwitch] trait to define relations & mappings between traits pub trait ReportSwitchExt { diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 4f0be43b96..2fb63104ae 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -54,6 +54,7 @@ frunk_core = "0.4.1" futures = "0.3.28" hex = "0.4.3" http = "0.2.9" +image = "0.23.14" infer = "0.13.0" josekit = "0.8.3" jsonwebtoken = "8.3.0" @@ -65,6 +66,7 @@ moka = { version = "0.11", features = ["future"] } nanoid = "0.4.0" num_cpus = "1.15.0" once_cell = "1.18.0" +qrcode = "0.12.0" rand = "0.8.5" regex = "1.8.4" reqwest = { version = "0.11.18", features = ["json", "native-tls", "gzip", "multipart"] } diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index aa74aa0a6b..18e0265038 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -32,3 +32,7 @@ pub(crate) const PUB_SUB_CHANNEL: &str = "hyperswitch_invalidate"; // Apple Pay validation url pub(crate) const APPLEPAY_VALIDATION_URL: &str = "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"; + +// Qr Image data source starts with this string +// The base64 image data will be appended to it to image data source +pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 8d909ecc77..fb4e9c26f5 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -5,6 +5,7 @@ pub mod ext_traits; #[cfg(feature = "kv_store")] pub mod storage_partitioning; +use base64::Engine; pub use common_utils::{ crypto, ext_traits::{ByteSliceExt, BytesExt, Encode, StringExt, ValueExt}, @@ -12,7 +13,9 @@ pub use common_utils::{ validation::validate_email, }; use error_stack::{IntoReport, ResultExt}; +use image::Luma; use nanoid::nanoid; +use qrcode; use serde::de::DeserializeOwned; pub use self::ext_traits::{OptionExt, ValidateCall}; @@ -155,3 +158,46 @@ pub fn to_currency_base_unit_asf64( pub fn get_payment_attempt_id(payment_id: impl std::fmt::Display, attempt_count: i16) -> String { format!("{payment_id}_{attempt_count}") } + +#[derive(Debug)] +pub struct QrImage { + pub data: String, +} + +impl QrImage { + pub fn new_from_data( + data: String, + ) -> Result> { + let qr_code = qrcode::QrCode::new(data.as_bytes()) + .into_report() + .change_context(common_utils::errors::QrCodeError::FailedToCreateQrCode)?; + + // Renders the QR code into an image. + let qrcode_image_buffer = qr_code.render::>().build(); + let qrcode_dynamic_image = image::DynamicImage::ImageLuma8(qrcode_image_buffer); + + let mut image_bytes = Vec::new(); + + // Encodes qrcode_dynamic_image and write it to image_bytes + let _ = qrcode_dynamic_image.write_to(&mut image_bytes, image::ImageOutputFormat::Png); + + let image_data_source = format!( + "{},{}", + consts::QR_IMAGE_DATA_SOURCE_STRING, + consts::BASE64_ENGINE.encode(image_bytes) + ); + Ok(Self { + data: image_data_source, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::utils; + #[test] + fn test_image_data_source_url() { + let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); + assert!(qr_image_data_source_url.is_ok()); + } +}