diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index a184382e65..e89025c9f6 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1187,7 +1187,7 @@ pub struct RewardData { pub struct BoletoVoucherData { /// The shopper's social security number #[schema(value_type = Option)] - social_security_number: Option>, + pub social_security_number: Option>, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -1371,6 +1371,11 @@ pub enum NextActionData { #[schema(value_type = String)] image_data_url: Url, }, + /// Contains the download url and the reference number for transaction + DisplayVoucherInformation { + #[schema(value_type = String)] + voucher_details: VoucherNextStepData, + }, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -1382,6 +1387,14 @@ pub struct BankTransferNextStepsData { pub receiver: ReceiverDetails, } +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct VoucherNextStepData { + /// Reference number required for the transaction + pub reference: String, + /// Url to download the payment instruction + pub download_url: Option, +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct QrCodeNextStepsInstruction { pub image_data_url: Url, diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 3cd5188650..74e987d1c7 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -770,6 +770,9 @@ pub enum StripeNextAction { QrCodeInformation { image_data_url: url::Url, }, + DisplayVoucherInformation { + voucher_details: payments::VoucherNextStepData, + }, } pub(crate) fn into_stripe_next_action( @@ -796,6 +799,9 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::QrCodeInformation { image_data_url } => { StripeNextAction::QrCodeInformation { image_data_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 8901683979..845a6f7273 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -371,6 +371,9 @@ pub enum StripeNextAction { QrCodeInformation { image_data_url: url::Url, }, + DisplayVoucherInformation { + voucher_details: payments::VoucherNextStepData, + }, } pub(crate) fn into_stripe_next_action( @@ -397,6 +400,9 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::QrCodeInformation { image_data_url } => { StripeNextAction::QrCodeInformation { image_data_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 9a2d0f44f9..25697ac713 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -122,6 +122,7 @@ pub struct AdyenPaymentRequest<'a> { shopper_name: Option, shopper_locale: Option, shopper_email: Option, + social_security_number: Option>, telephone_number: Option>, billing_address: Option
, delivery_address: Option
, @@ -155,6 +156,7 @@ pub enum AdyenStatus { Received, RedirectShopper, Refused, + PresentToShopper, #[cfg(feature = "payouts")] #[serde(rename = "[payout-confirm-received]")] PayoutConfirmReceived, @@ -177,7 +179,7 @@ impl ForeignFrom<(bool, AdyenStatus)> for storage_enums::AttemptStatus { fn foreign_from((is_manual_capture, adyen_status): (bool, AdyenStatus)) -> Self { match adyen_status { AdyenStatus::AuthenticationFinished => Self::AuthenticationSuccessful, - AdyenStatus::AuthenticationNotRequired => Self::Pending, + AdyenStatus::AuthenticationNotRequired | AdyenStatus::PresentToShopper => Self::Pending, AdyenStatus::Authorised => match is_manual_capture { true => Self::Authorized, // In case of Automatic capture Authorized is the final status of the payment @@ -236,6 +238,7 @@ pub struct AdyenThreeDS { #[serde(untagged)] pub enum AdyenPaymentResponse { Response(Response), + PresentToShopper(AdyenPtsResponse), NextActionResponse(NextActionResponse), RedirectionErrorResponse(RedirectionErrorResponse), } @@ -268,6 +271,16 @@ pub struct NextActionResponse { refusal_reason_code: Option, } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenPtsResponse { + psp_reference: String, + result_code: AdyenStatus, + action: AdyenPtsAction, + refusal_reason: Option, + refusal_reason_code: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenNextAction { @@ -281,6 +294,20 @@ pub struct AdyenNextAction { qr_code_data: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenPtsAction { + reference: String, + download_url: Option, + payment_method_type: Option, + expires_at: Option, + initial_amount: Option, + pass_creation_token: Option, + total_amount: Option, + #[serde(rename = "type")] + type_of_response: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ActionType { @@ -288,6 +315,7 @@ pub enum ActionType { Await, #[serde(rename = "qrCode")] QrCode, + Voucher, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -313,6 +341,8 @@ pub enum AdyenPaymentMethod<'a> { BancontactCard(Box), Bizum(Box), Blik(Box), + #[serde(rename = "boletobancario")] + Boleto, ClearPay(Box), Dana(Box), Eps(Box>), @@ -1047,6 +1077,9 @@ impl<'a> TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest<'a api_models::payments::PaymentMethodData::BankDebit(ref bank_debit) => { AdyenPaymentRequest::try_from((item, bank_debit)) } + api_models::payments::PaymentMethodData::Voucher(ref voucher_data) => { + AdyenPaymentRequest::try_from((item, voucher_data)) + } _ => Err(errors::ConnectorError::NotSupported { message: format!("{:?}", item.request.payment_method_type), connector: "Adyen", @@ -1230,6 +1263,15 @@ fn get_payout_card_details(payout_method_data: &PayoutMethodData) -> Option Option> { + match voucher_data { + payments::VoucherData::Boleto(boleto_data) => boleto_data.social_security_number.clone(), + _ => None, + } +} + impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from( @@ -1289,6 +1331,16 @@ impl<'a> TryFrom<&api_models::payments::BankDebitData> for AdyenPaymentMethod<'a } } +impl<'a> TryFrom<&api_models::payments::VoucherData> for AdyenPaymentMethod<'a> { + type Error = Error; + fn try_from(voucher_data: &api_models::payments::VoucherData) -> Result { + match voucher_data { + payments::VoucherData::Boleto { .. } => Ok(AdyenPaymentMethod::Boleto), + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + impl<'a> TryFrom<&api::Card> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from(card: &api::Card) -> Result { @@ -1731,6 +1783,7 @@ impl<'a> shopper_name: None, shopper_email: None, shopper_locale: None, + social_security_number: None, billing_address: None, delivery_address: None, country_code: None, @@ -1770,6 +1823,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::Card)> for AdyenPay shopper_name: None, shopper_email: None, shopper_locale: None, + social_security_number: None, billing_address: None, delivery_address: None, country_code: None, @@ -1818,6 +1872,7 @@ impl<'a> shopper_name: None, shopper_locale: None, shopper_email: item.request.email.clone(), + social_security_number: None, telephone_number: None, billing_address: None, delivery_address: None, @@ -1830,6 +1885,56 @@ impl<'a> Ok(request) } } +impl<'a> + TryFrom<( + &types::PaymentsAuthorizeRouterData, + &api_models::payments::VoucherData, + )> for AdyenPaymentRequest<'a> +{ + type Error = Error; + + fn try_from( + value: ( + &types::PaymentsAuthorizeRouterData, + &api_models::payments::VoucherData, + ), + ) -> Result { + let (item, voucher_data) = value; + let amount = get_amount_data(item); + let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?; + let shopper_interaction = AdyenShopperInteraction::from(item); + let recurring_processing_model = get_recurring_processing_model(item)?.0; + let browser_info = get_browser_info(item)?; + let additional_data = get_additional_data(item); + let payment_method = AdyenPaymentMethod::try_from(voucher_data)?; + let return_url = item.request.get_return_url()?; + let social_security_number = get_social_security_number(voucher_data); + let request = AdyenPaymentRequest { + amount, + merchant_account: auth_type.merchant_account, + payment_method, + reference: item.payment_id.to_string(), + return_url, + browser_info, + shopper_interaction, + recurring_processing_model, + additional_data, + shopper_name: None, + shopper_locale: None, + shopper_email: item.request.email.clone(), + social_security_number, + telephone_number: None, + billing_address: None, + delivery_address: None, + country_code: None, + line_items: None, + shopper_reference: None, + store_payment_method: None, + channel: None, + }; + Ok(request) + } +} impl<'a> TryFrom<( @@ -1871,6 +1976,7 @@ impl<'a> shopper_name: None, shopper_email: item.request.email.clone(), shopper_locale, + social_security_number: None, billing_address: None, delivery_address: None, country_code: country, @@ -1956,6 +2062,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::WalletData)> shopper_name: None, shopper_email, shopper_locale: None, + social_security_number: None, billing_address: None, delivery_address: None, country_code: None, @@ -2005,6 +2112,7 @@ impl<'a> TryFrom<(&types::PaymentsAuthorizeRouterData, &api::PayLaterData)> shopper_name, shopper_email, shopper_locale: None, + social_security_number: None, billing_address, delivery_address, country_code, @@ -2170,6 +2278,58 @@ pub fn get_next_action_response( Ok((status, error, payments_response_data)) } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AdyenMetaData { + download_url: Option, + reference: String, +} + +pub fn get_present_to_shopper_response( + response: AdyenPtsResponse, + is_manual_capture: bool, + status_code: u16, +) -> errors::CustomResult< + ( + storage_enums::AttemptStatus, + Option, + types::PaymentsResponseData, + ), + errors::ConnectorError, +> { + let status = + storage_enums::AttemptStatus::foreign_from((is_manual_capture, response.result_code)); + let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + Some(types::ErrorResponse { + code: response + .refusal_reason_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .refusal_reason + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: None, + status_code, + }) + } else { + None + }; + + let metadata = serde_json::json!(AdyenMetaData { + download_url: response.action.download_url, + reference: response.action.reference, + }); + + let payments_response_data = types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference), + redirection_data: None, + mandate_reference: None, + connector_metadata: Some(metadata), + network_txn_id: None, + connector_response_reference_id: None, + }; + + Ok((status, error, payments_response_data)) +} + pub fn get_redirection_error_response( response: RedirectionErrorResponse, is_manual_capture: bool, @@ -2199,6 +2359,7 @@ pub fn get_redirection_error_response( network_txn_id: None, connector_response_reference_id: None, }; + Ok((status, error, payments_response_data)) } @@ -2257,6 +2418,9 @@ impl let item = items.0; let is_manual_capture = items.1; let (status, error, payment_response_data) = match item.response { + AdyenPaymentResponse::PresentToShopper(response) => { + get_present_to_shopper_response(response, is_manual_capture, item.http_code)? + } AdyenPaymentResponse::Response(response) => { get_adyen_response(response, is_manual_capture, item.http_code)? } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 3171233f13..fdebddc2bd 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -429,7 +429,8 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { api_models::payments::NextActionData::RedirectToUrl { redirect_to_url } => Some(redirect_to_url), api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None, api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None, - api_models::payments::NextActionData::QrCodeInformation{..} => None + api_models::payments::NextActionData::QrCodeInformation{..} => None, + api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None, }) .ok_or(errors::ApiErrorResponse::InternalServerError) .into_report() diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 43f39e1597..bda8aacc7c 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1442,6 +1442,9 @@ pub(crate) fn validate_payment_method_fields_present( ) | ( api_enums::PaymentMethod::Upi, api::PaymentMethodData::Upi(..) + ) | ( + api_enums::PaymentMethod::Voucher, + api::PaymentMethodData::Voucher(..) ) ) | None ), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 511d0048d2..aadc262a36 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -373,11 +373,14 @@ where let bank_transfer_next_steps = bank_transfer_next_steps_check(payment_attempt.clone())?; + let next_action_voucher = voucher_next_steps_check(payment_attempt.clone())?; + let next_action_containing_qr_code = qr_code_next_steps_check(payment_attempt.clone())?; if payment_intent.status == enums::IntentStatus::RequiresCustomerAction || bank_transfer_next_steps.is_some() + || next_action_voucher.is_some() || next_action_containing_qr_code.is_some() { next_action_response = bank_transfer_next_steps @@ -386,6 +389,11 @@ where bank_transfer_steps_and_charges_details: bank_transfer, } }) + .or(next_action_voucher.map(|voucher_data| { + api_models::payments::NextActionData::DisplayVoucherInformation { + voucher_details: voucher_data, + } + })) .or(next_action_containing_qr_code.map(|qr_code_data| { api_models::payments::NextActionData::QrCodeInformation { image_data_url: qr_code_data.image_data_url, @@ -686,6 +694,28 @@ pub fn bank_transfer_next_steps_check( Ok(bank_transfer_next_step) } +pub fn voucher_next_steps_check( + payment_attempt: storage::PaymentAttempt, +) -> RouterResult> { + let voucher_next_step = if let Some(diesel_models::enums::PaymentMethod::Voucher) = + payment_attempt.payment_method + { + let voucher_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()?; + voucher_next_steps + } else { + None + }; + Ok(voucher_next_step) +} + pub fn change_order_details_to_new_type( order_amount: i64, order_details: api_models::payments::OrderDetails, diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index 89fd29681c..b37914ba7e 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -196,6 +196,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::VoucherData, api_models::payments::BoletoVoucherData, api_models::payments::Address, + api_models::payments::VoucherData, api_models::payments::BankRedirectData, api_models::payments::BankRedirectBilling, api_models::payments::BankRedirectBilling, diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 7c7fe43784..3bd42ae7df 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -6376,6 +6376,25 @@ ] } } + }, + { + "type": "object", + "description": "Contains the download url and the reference number for transaction", + "required": [ + "voucher_details", + "type" + ], + "properties": { + "voucher_details": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "display_voucher_information" + ] + } + } } ], "discriminator": {