diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index cac94a07326a..c5a941515636 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1892,8 +1892,12 @@ pub enum NextActionData { /// Contains url for Qr code image, this qr code has to be shown in sdk QrCodeInformation { #[schema(value_type = String)] - image_data_url: Url, + /// Hyperswitch generated image data source url + image_data_url: Option, display_to_timestamp: Option, + #[schema(value_type = String)] + /// The url for Qr code given by the connector + qr_code_url: Option, }, /// Contains the download url and the reference number for transaction DisplayVoucherInformation { @@ -1907,6 +1911,26 @@ pub enum NextActionData { }, } +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(untagged)] +// the enum order shouldn't be changed as this is being used during serialization and deserialization +pub enum QrCodeInformation { + QrCodeUrl { + image_data_url: Url, + qr_code_url: Url, + display_to_timestamp: Option, + }, + QrDataUrl { + image_data_url: Url, + display_to_timestamp: Option, + }, + QrCodeImageUrl { + qr_code_url: Url, + display_to_timestamp: Option, + }, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] pub struct BankTransferNextStepsData { /// The instructions for performing a bank transfer @@ -1932,6 +1956,7 @@ pub struct VoucherNextStepData { pub struct QrCodeNextStepsInstruction { pub image_data_url: Url, pub display_to_timestamp: Option, + pub qr_code_url: Option, } #[derive(Clone, Debug, serde::Deserialize)] diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 38007a3110d6..810e0ed1d284 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -794,8 +794,9 @@ pub enum StripeNextAction { session_token: Option, }, QrCodeInformation { - image_data_url: url::Url, + image_data_url: Option, display_to_timestamp: Option, + qr_code_url: Option, }, DisplayVoucherInformation { voucher_details: payments::VoucherNextStepData, @@ -830,9 +831,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::QrCodeInformation { image_data_url, display_to_timestamp, + qr_code_url, } => StripeNextAction::QrCodeInformation { image_data_url, display_to_timestamp, + qr_code_url, }, payments::NextActionData::DisplayVoucherInformation { voucher_details } => { StripeNextAction::DisplayVoucherInformation { voucher_details } diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 4c99d0cb00b4..5a2c7a02897e 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -384,8 +384,9 @@ pub enum StripeNextAction { session_token: Option, }, QrCodeInformation { - image_data_url: url::Url, + image_data_url: Option, display_to_timestamp: Option, + qr_code_url: Option, }, DisplayVoucherInformation { voucher_details: payments::VoucherNextStepData, @@ -420,9 +421,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::QrCodeInformation { image_data_url, display_to_timestamp, + qr_code_url, } => StripeNextAction::QrCodeInformation { image_data_url, display_to_timestamp, + qr_code_url, }, payments::NextActionData::DisplayVoucherInformation { voucher_details } => { StripeNextAction::DisplayVoucherInformation { voucher_details } diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index e00b829f2834..5b41cd5b2186 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -345,6 +345,7 @@ pub struct QrCodeResponseResponse { action: AdyenQrCodeAction, refusal_reason: Option, refusal_reason_code: Option, + additional_data: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -354,10 +355,17 @@ pub struct AdyenQrCodeAction { #[serde(rename = "type")] type_of_response: ActionType, #[serde(rename = "url")] - mobile_redirection_url: Option, + qr_code_url: Option, qr_code_data: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct QrCodeAdditionalData { + #[serde(rename = "pix.expirationDate")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pix_expiration_date: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenPtsAction { @@ -404,20 +412,20 @@ pub struct Amount { #[derive(Debug, Clone, Serialize)] #[serde(tag = "type")] pub enum AdyenPaymentMethod<'a> { - AdyenAffirm(Box), + AdyenAffirm(Box), AdyenCard(Box), - AdyenKlarna(Box), - AdyenPaypal(Box), + AdyenKlarna(Box), + AdyenPaypal(Box), #[serde(rename = "afterpaytouch")] - AfterPay(Box), - AlmaPayLater(Box), - AliPay(Box), - AliPayHk(Box), + AfterPay(Box), + AlmaPayLater(Box), + AliPay(Box), + AliPayHk(Box), ApplePay(Box), #[serde(rename = "atome")] Atome, BancontactCard(Box), - Bizum(Box), + Bizum(Box), Blik(Box), #[serde(rename = "boletobancario")] BoletoBancario, @@ -428,7 +436,7 @@ pub enum AdyenPaymentMethod<'a> { Eps(Box>), #[serde(rename = "gcash")] Gcash(Box), - Giropay(Box), + Giropay(Box), Gpay(Box), #[serde(rename = "gopay_wallet")] GoPay(Box), @@ -437,7 +445,7 @@ pub enum AdyenPaymentMethod<'a> { Kakaopay(Box), Mandate(Box), Mbway(Box), - MobilePay(Box), + MobilePay(Box), #[serde(rename = "momo_wallet")] Momo(Box), #[serde(rename = "momo_atm")] @@ -445,7 +453,7 @@ pub enum AdyenPaymentMethod<'a> { #[serde(rename = "touchngo")] TouchNGo(Box), OnlineBankingCzechRepublic(Box), - OnlineBankingFinland(Box), + OnlineBankingFinland(Box), OnlineBankingPoland(Box), OnlineBankingSlovakia(Box), #[serde(rename = "molpay_ebanking_fpx_MY")] @@ -515,6 +523,13 @@ pub enum AdyenPaymentMethod<'a> { Seicomart(Box), #[serde(rename = "econtext_stores")] PayEasy(Box), + Pix(Box), +} + +#[derive(Debug, Clone, Serialize)] +pub struct PmdForPaymentType { + #[serde(rename = "type")] + payment_type: PaymentType, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] @@ -594,11 +609,6 @@ pub struct BancontactCardData { holder_name: Secret, } -#[derive(Debug, Clone, Serialize)] -pub struct MobilePayData { - #[serde(rename = "type")] - payment_type: PaymentType, -} #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct MbwayData { @@ -627,11 +637,6 @@ pub struct PayBrightData { payment_type: PaymentType, } -#[derive(Debug, Clone, Serialize)] -pub struct OnlineBankingFinlandData { - #[serde(rename = "type")] - payment_type: PaymentType, -} #[derive(Debug, Clone, Serialize)] pub struct OnlineBankingCzechRepublicData { #[serde(rename = "type")] @@ -1014,13 +1019,6 @@ pub struct BlikRedirectionData { blik_code: String, } -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BankRedirectionPMData { - #[serde(rename = "type")] - payment_type: PaymentType, -} - #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct BankRedirectionWithIssuer<'a> { @@ -1079,23 +1077,6 @@ pub enum CancelStatus { #[default] Processing, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AdyenPaypal { - #[serde(rename = "type")] - payment_type: PaymentType, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AliPayData { - #[serde(rename = "type")] - payment_type: PaymentType, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AliPayHkData { - #[serde(rename = "type")] - payment_type: PaymentType, -} #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoPayData {} @@ -1127,12 +1108,6 @@ pub struct AdyenApplePay { apple_pay_token: Secret, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AdyenPayLaterData { - #[serde(rename = "type")] - payment_type: PaymentType, -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DokuBankData { @@ -1272,6 +1247,7 @@ pub enum PaymentType { Seicomart, #[serde(rename = "econtext_stores")] PayEasy, + Pix, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -1964,19 +1940,19 @@ impl<'a> TryFrom<&api::WalletData> for AdyenPaymentMethod<'a> { Ok(AdyenPaymentMethod::ApplePay(Box::new(apple_pay_data))) } api_models::payments::WalletData::PaypalRedirect(_) => { - let wallet = AdyenPaypal { + let wallet = PmdForPaymentType { payment_type: PaymentType::Paypal, }; Ok(AdyenPaymentMethod::AdyenPaypal(Box::new(wallet))) } api_models::payments::WalletData::AliPayRedirect(_) => { - let alipay_data = AliPayData { + let alipay_data = PmdForPaymentType { payment_type: PaymentType::Alipay, }; Ok(AdyenPaymentMethod::AliPay(Box::new(alipay_data))) } api_models::payments::WalletData::AliPayHkRedirect(_) => { - let alipay_hk_data = AliPayHkData { + let alipay_hk_data = PmdForPaymentType { payment_type: PaymentType::AlipayHk, }; Ok(AdyenPaymentMethod::AliPayHk(Box::new(alipay_hk_data))) @@ -2009,7 +1985,7 @@ impl<'a> TryFrom<&api::WalletData> for AdyenPaymentMethod<'a> { Ok(AdyenPaymentMethod::Mbway(Box::new(mbway_data))) } api_models::payments::WalletData::MobilePayRedirect(_) => { - let data = MobilePayData { + let data = PmdForPaymentType { payment_type: PaymentType::MobilePay, }; Ok(AdyenPaymentMethod::MobilePay(Box::new(data))) @@ -2054,13 +2030,13 @@ impl<'a> TryFrom<(&api::PayLaterData, Option)> let (pay_later_data, country_code) = value; match pay_later_data { api_models::payments::PayLaterData::KlarnaRedirect { .. } => { - let klarna = AdyenPayLaterData { + let klarna = PmdForPaymentType { payment_type: PaymentType::Klarna, }; Ok(AdyenPaymentMethod::AdyenKlarna(Box::new(klarna))) } api_models::payments::PayLaterData::AffirmRedirect { .. } => Ok( - AdyenPaymentMethod::AdyenAffirm(Box::new(AdyenPayLaterData { + AdyenPaymentMethod::AdyenAffirm(Box::new(PmdForPaymentType { payment_type: PaymentType::Affirm, })), ), @@ -2071,7 +2047,7 @@ impl<'a> TryFrom<(&api::PayLaterData, Option)> | api_enums::CountryAlpha2::FR | api_enums::CountryAlpha2::ES | api_enums::CountryAlpha2::GB => Ok(AdyenPaymentMethod::ClearPay), - _ => Ok(AdyenPaymentMethod::AfterPay(Box::new(AdyenPayLaterData { + _ => Ok(AdyenPaymentMethod::AfterPay(Box::new(PmdForPaymentType { payment_type: PaymentType::Afterpaytouch, }))), } @@ -2088,7 +2064,7 @@ impl<'a> TryFrom<(&api::PayLaterData, Option)> Ok(AdyenPaymentMethod::Walley) } api_models::payments::PayLaterData::AlmaRedirect { .. } => Ok( - AdyenPaymentMethod::AlmaPayLater(Box::new(AdyenPayLaterData { + AdyenPaymentMethod::AlmaPayLater(Box::new(PmdForPaymentType { payment_type: PaymentType::Alma, })), ), @@ -2147,7 +2123,7 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod }, ))), api_models::payments::BankRedirectData::Bizum { .. } => { - Ok(AdyenPaymentMethod::Bizum(Box::new(BankRedirectionPMData { + Ok(AdyenPaymentMethod::Bizum(Box::new(PmdForPaymentType { payment_type: PaymentType::Bizum, }))) } @@ -2174,11 +2150,11 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod ), })), ), - api_models::payments::BankRedirectData::Giropay { .. } => Ok( - AdyenPaymentMethod::Giropay(Box::new(BankRedirectionPMData { + api_models::payments::BankRedirectData::Giropay { .. } => { + Ok(AdyenPaymentMethod::Giropay(Box::new(PmdForPaymentType { payment_type: PaymentType::Giropay, - })), - ), + }))) + } api_models::payments::BankRedirectData::Ideal { bank_name, .. } => Ok( AdyenPaymentMethod::Ideal(Box::new(BankRedirectionWithIssuer { payment_type: PaymentType::Ideal, @@ -2201,7 +2177,7 @@ impl<'a> TryFrom<&api_models::payments::BankRedirectData> for AdyenPaymentMethod ))) } api_models::payments::BankRedirectData::OnlineBankingFinland { .. } => Ok( - AdyenPaymentMethod::OnlineBankingFinland(Box::new(OnlineBankingFinlandData { + AdyenPaymentMethod::OnlineBankingFinland(Box::new(PmdForPaymentType { payment_type: PaymentType::OnlineBankingFinland, })), ), @@ -2312,8 +2288,12 @@ impl<'a> TryFrom<&api_models::payments::BankTransferData> for AdyenPaymentMethod last_name: billing_details.last_name.clone(), shopper_email: billing_details.email.clone(), }))), - api_models::payments::BankTransferData::Pix {} - | api_models::payments::BankTransferData::AchBankTransfer { .. } + api_models::payments::BankTransferData::Pix {} => { + Ok(AdyenPaymentMethod::Pix(Box::new(PmdForPaymentType { + payment_type: PaymentType::Pix, + }))) + } + api_models::payments::BankTransferData::AchBankTransfer { .. } | api_models::payments::BankTransferData::SepaBankTransfer { .. } | api_models::payments::BankTransferData::BacsBankTransfer { .. } | api_models::payments::BankTransferData::MultibancoBankTransfer { .. } @@ -3330,20 +3310,50 @@ pub fn get_qr_metadata( let image_data = crate_utils::QrImage::new_from_data(response.action.qr_code_data.to_owned()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; - let image_data_url = Url::parse(image_data.data.as_str()) - .ok() - .ok_or(errors::ConnectorError::ResponseHandlingFailed)?; + let image_data_url = Url::parse(image_data.data.clone().as_str()).ok(); + let qr_code_url = response.action.qr_code_url.clone(); + let display_to_timestamp = response + .additional_data + .clone() + .and_then(|additional_data| additional_data.pix_expiration_date) + .map(|time| utils::get_timestamp_in_milliseconds(&time)); - let qr_code_instructions = payments::QrCodeNextStepsInstruction { - image_data_url, - display_to_timestamp: None, - }; + if let (Some(image_data_url), Some(qr_code_url)) = (image_data_url.clone(), qr_code_url.clone()) + { + let qr_code_info = payments::QrCodeInformation::QrCodeUrl { + image_data_url, + qr_code_url, + display_to_timestamp, + }; + Some(common_utils::ext_traits::Encode::< + payments::QrCodeInformation, + >::encode_to_value(&qr_code_info)) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } else if let (None, Some(qr_code_url)) = (image_data_url.clone(), qr_code_url.clone()) { + let qr_code_info = payments::QrCodeInformation::QrCodeImageUrl { + qr_code_url, + display_to_timestamp, + }; + Some(common_utils::ext_traits::Encode::< + payments::QrCodeInformation, + >::encode_to_value(&qr_code_info)) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } else if let (Some(image_data_url), None) = (image_data_url, qr_code_url) { + let qr_code_info = payments::QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp, + }; - Some(common_utils::ext_traits::Encode::< - payments::QrCodeNextStepsInstruction, - >::encode_to_value(&qr_code_instructions)) - .transpose() - .change_context(errors::ConnectorError::ResponseHandlingFailed) + Some(common_utils::ext_traits::Encode::< + payments::QrCodeInformation, + >::encode_to_value(&qr_code_info)) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } else { + Ok(None) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -3434,7 +3444,8 @@ pub fn get_wait_screen_metadata( | PaymentType::MiniStop | PaymentType::FamilyMart | PaymentType::Seicomart - | PaymentType::PayEasy => Ok(None), + | PaymentType::PayEasy + | PaymentType::Pix => Ok(None), } } @@ -3537,7 +3548,8 @@ pub fn get_present_to_shopper_metadata( | PaymentType::Vipps | PaymentType::Swish | PaymentType::PaySafeCard - | PaymentType::SevenEleven => Ok(None), + | PaymentType::SevenEleven + | PaymentType::Pix => Ok(None), } } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 8f028e37a9e5..7226cb77342f 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -17,6 +17,7 @@ use masking::{ExposeInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; use serde::Serializer; +use time::PrimitiveDateTime; #[cfg(feature = "frm")] use crate::types::{fraud_check, storage::enums as storage_enums}; @@ -1607,6 +1608,11 @@ pub fn validate_currency( Ok(()) } +pub fn get_timestamp_in_milliseconds(datetime: &PrimitiveDateTime) -> i64 { + let utc_datetime = datetime.assume_utc(); + utc_datetime.unix_timestamp() * 1000 +} + #[cfg(feature = "frm")] pub trait FraudCheckSaleRequest { fn get_order_details(&self) -> Result, Error>; diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index dffcff23595b..30566e6647e3 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -521,10 +521,7 @@ where } })) .or(next_action_containing_qr_code_url.map(|qr_code_data| { - api_models::payments::NextActionData::QrCodeInformation { - image_data_url: qr_code_data.image_data_url, - display_to_timestamp: qr_code_data.display_to_timestamp, - } + api_models::payments::NextActionData::foreign_from(qr_code_data) })) .or(next_action_containing_wait_screen.map(|wait_screen_data| { api_models::payments::NextActionData::WaitScreenInformation { @@ -827,11 +824,10 @@ where pub fn qr_code_next_steps_check( payment_attempt: storage::PaymentAttempt, -) -> RouterResult> { - let qr_code_steps: Option> = - payment_attempt - .connector_metadata - .map(|metadata| metadata.parse_value("QrCodeNextStepsInstruction")); +) -> RouterResult> { + let qr_code_steps: Option> = payment_attempt + .connector_metadata + .map(|metadata| metadata.parse_value("QrCodeInformation")); let qr_code_instructions = qr_code_steps.transpose().ok().flatten(); Ok(qr_code_instructions) @@ -901,17 +897,24 @@ pub fn bank_transfer_next_steps_check( let bank_transfer_next_step = if let Some(diesel_models::enums::PaymentMethod::BankTransfer) = payment_attempt.payment_method { - let bank_transfer_next_steps: Option = - payment_attempt - .connector_metadata - .map(|metadata| { - metadata - .parse_value("NextStepsRequirements") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to parse the Value to NextRequirements struct") - }) - .transpose()?; - bank_transfer_next_steps + if payment_attempt.payment_method_type != Some(diesel_models::enums::PaymentMethodType::Pix) + { + let bank_transfer_next_steps: Option = + payment_attempt + .connector_metadata + .map(|metadata| { + metadata + .parse_value("NextStepsRequirements") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to parse the Value to NextRequirements struct", + ) + }) + .transpose()?; + bank_transfer_next_steps + } else { + None + } } else { None }; @@ -957,6 +960,38 @@ pub fn change_order_details_to_new_type( }]) } +impl ForeignFrom for api_models::payments::NextActionData { + fn foreign_from(qr_info: api_models::payments::QrCodeInformation) -> Self { + match qr_info { + api_models::payments::QrCodeInformation::QrCodeUrl { + image_data_url, + qr_code_url, + display_to_timestamp, + } => Self::QrCodeInformation { + image_data_url: Some(image_data_url), + qr_code_url: Some(qr_code_url), + display_to_timestamp, + }, + api_models::payments::QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp, + } => Self::QrCodeInformation { + image_data_url: Some(image_data_url), + display_to_timestamp, + qr_code_url: None, + }, + api_models::payments::QrCodeInformation::QrCodeImageUrl { + qr_code_url, + display_to_timestamp, + } => Self::QrCodeInformation { + qr_code_url: Some(qr_code_url), + display_to_timestamp, + image_data_url: None, + }, + } + } +} + #[derive(Clone)] pub struct PaymentAdditionalData<'a, F> where diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index c50f687a1810..61e2cf9e5d6a 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -8136,17 +8136,23 @@ "description": "Contains url for Qr code image, this qr code has to be shown in sdk", "required": [ "image_data_url", + "qr_code_url", "type" ], "properties": { "image_data_url": { - "type": "string" + "type": "string", + "description": "Hyperswitch generated image data source url" }, "display_to_timestamp": { "type": "integer", "format": "int64", "nullable": true }, + "qr_code_url": { + "type": "string", + "description": "The url for Qr code given by the connector" + }, "type": { "type": "string", "enum": [