mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): [Adyen] Add support for Blik (#1727)
This commit is contained in:
		| @ -1353,6 +1353,7 @@ pub enum NextActionType { | |||||||
|     InvokeSdkClient, |     InvokeSdkClient, | ||||||
|     TriggerApi, |     TriggerApi, | ||||||
|     DisplayBankTransferInformation, |     DisplayBankTransferInformation, | ||||||
|  |     DisplayWaitScreen, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] | #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] | ||||||
| @ -1376,6 +1377,11 @@ pub enum NextActionData { | |||||||
|         #[schema(value_type = String)] |         #[schema(value_type = String)] | ||||||
|         voucher_details: VoucherNextStepData, |         voucher_details: VoucherNextStepData, | ||||||
|     }, |     }, | ||||||
|  |     /// Contains duration for displaying a wait screen, wait screen with timer is displayed by sdk | ||||||
|  |     WaitScreenInformation { | ||||||
|  |         display_from_timestamp: i128, | ||||||
|  |         display_to_timestamp: Option<i128>, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] | #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] | ||||||
| @ -1400,6 +1406,12 @@ pub struct QrCodeNextStepsInstruction { | |||||||
|     pub image_data_url: Url, |     pub image_data_url: Url, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Debug, serde::Deserialize)] | ||||||
|  | pub struct WaitScreenInstructions { | ||||||
|  |     pub display_from_timestamp: i128, | ||||||
|  |     pub display_to_timestamp: Option<i128>, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] | #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
| pub enum BankTransferInstructions { | pub enum BankTransferInstructions { | ||||||
|  | |||||||
| @ -840,6 +840,8 @@ pub enum PaymentExperience { | |||||||
|     LinkWallet, |     LinkWallet, | ||||||
|     /// Contains the data for invoking the sdk client for completing the payment. |     /// Contains the data for invoking the sdk client for completing the payment. | ||||||
|     InvokePaymentApp, |     InvokePaymentApp, | ||||||
|  |     /// Contains the data for displaying wait screen | ||||||
|  |     DisplayWaitScreen, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive( | #[derive( | ||||||
|  | |||||||
| @ -773,6 +773,10 @@ pub enum StripeNextAction { | |||||||
|     DisplayVoucherInformation { |     DisplayVoucherInformation { | ||||||
|         voucher_details: payments::VoucherNextStepData, |         voucher_details: payments::VoucherNextStepData, | ||||||
|     }, |     }, | ||||||
|  |     WaitScreenInformation { | ||||||
|  |         display_from_timestamp: i128, | ||||||
|  |         display_to_timestamp: Option<i128>, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) fn into_stripe_next_action( | pub(crate) fn into_stripe_next_action( | ||||||
| @ -802,6 +806,13 @@ pub(crate) fn into_stripe_next_action( | |||||||
|         payments::NextActionData::DisplayVoucherInformation { voucher_details } => { |         payments::NextActionData::DisplayVoucherInformation { voucher_details } => { | ||||||
|             StripeNextAction::DisplayVoucherInformation { voucher_details } |             StripeNextAction::DisplayVoucherInformation { voucher_details } | ||||||
|         } |         } | ||||||
|  |         payments::NextActionData::WaitScreenInformation { | ||||||
|  |             display_from_timestamp, | ||||||
|  |             display_to_timestamp, | ||||||
|  |         } => StripeNextAction::WaitScreenInformation { | ||||||
|  |             display_from_timestamp, | ||||||
|  |             display_to_timestamp, | ||||||
|  |         }, | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -374,6 +374,10 @@ pub enum StripeNextAction { | |||||||
|     DisplayVoucherInformation { |     DisplayVoucherInformation { | ||||||
|         voucher_details: payments::VoucherNextStepData, |         voucher_details: payments::VoucherNextStepData, | ||||||
|     }, |     }, | ||||||
|  |     WaitScreenInformation { | ||||||
|  |         display_from_timestamp: i128, | ||||||
|  |         display_to_timestamp: Option<i128>, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) fn into_stripe_next_action( | pub(crate) fn into_stripe_next_action( | ||||||
| @ -403,6 +407,13 @@ pub(crate) fn into_stripe_next_action( | |||||||
|         payments::NextActionData::DisplayVoucherInformation { voucher_details } => { |         payments::NextActionData::DisplayVoucherInformation { voucher_details } => { | ||||||
|             StripeNextAction::DisplayVoucherInformation { voucher_details } |             StripeNextAction::DisplayVoucherInformation { voucher_details } | ||||||
|         } |         } | ||||||
|  |         payments::NextActionData::WaitScreenInformation { | ||||||
|  |             display_from_timestamp, | ||||||
|  |             display_to_timestamp, | ||||||
|  |         } => StripeNextAction::WaitScreenInformation { | ||||||
|  |             display_from_timestamp, | ||||||
|  |             display_to_timestamp, | ||||||
|  |         }, | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ use error_stack::ResultExt; | |||||||
| use masking::PeekInterface; | use masking::PeekInterface; | ||||||
| use reqwest::Url; | use reqwest::Url; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| use time::PrimitiveDateTime; | use time::{Duration, OffsetDateTime, PrimitiveDateTime}; | ||||||
|  |  | ||||||
| #[cfg(feature = "payouts")] | #[cfg(feature = "payouts")] | ||||||
| use crate::connector::utils::AddressDetailsData; | use crate::connector::utils::AddressDetailsData; | ||||||
| @ -2367,13 +2367,10 @@ pub fn get_connector_metadata( | |||||||
|     response: &NextActionResponse, |     response: &NextActionResponse, | ||||||
| ) -> errors::CustomResult<Option<serde_json::Value>, errors::ConnectorError> { | ) -> errors::CustomResult<Option<serde_json::Value>, errors::ConnectorError> { | ||||||
|     let connector_metadata = match response.action.type_of_response { |     let connector_metadata = match response.action.type_of_response { | ||||||
|         ActionType::QrCode => { |         ActionType::QrCode => get_qr_metadata(response), | ||||||
|             let metadata = get_qr_metadata(response); |         ActionType::Await => get_wait_screen_metadata(response), | ||||||
|             Some(metadata) |         _ => Ok(None), | ||||||
|     } |     } | ||||||
|         _ => None, |  | ||||||
|     } |  | ||||||
|     .transpose() |  | ||||||
|     .change_context(errors::ConnectorError::ResponseHandlingFailed)?; |     .change_context(errors::ConnectorError::ResponseHandlingFailed)?; | ||||||
|  |  | ||||||
|     Ok(connector_metadata) |     Ok(connector_metadata) | ||||||
| @ -2381,7 +2378,7 @@ pub fn get_connector_metadata( | |||||||
|  |  | ||||||
| pub fn get_qr_metadata( | pub fn get_qr_metadata( | ||||||
|     response: &NextActionResponse, |     response: &NextActionResponse, | ||||||
| ) -> errors::CustomResult<serde_json::Value, errors::ConnectorError> { | ) -> errors::CustomResult<Option<serde_json::Value>, errors::ConnectorError> { | ||||||
|     let image_data = response |     let image_data = response | ||||||
|         .action |         .action | ||||||
|         .qr_code_data |         .qr_code_data | ||||||
| @ -2396,12 +2393,41 @@ pub fn get_qr_metadata( | |||||||
|  |  | ||||||
|     let qr_code_instructions = payments::QrCodeNextStepsInstruction { image_data_url }; |     let qr_code_instructions = payments::QrCodeNextStepsInstruction { image_data_url }; | ||||||
|  |  | ||||||
|     common_utils::ext_traits::Encode::<payments::QrCodeNextStepsInstruction>::encode_to_value( |     Some(common_utils::ext_traits::Encode::< | ||||||
|         &qr_code_instructions, |         payments::QrCodeNextStepsInstruction, | ||||||
|     ) |     >::encode_to_value(&qr_code_instructions)) | ||||||
|  |     .transpose() | ||||||
|     .change_context(errors::ConnectorError::ResponseHandlingFailed) |     .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct WaitScreenData { | ||||||
|  |     display_from_timestamp: i128, | ||||||
|  |     display_to_timestamp: Option<i128>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn get_wait_screen_metadata( | ||||||
|  |     next_action: &NextActionResponse, | ||||||
|  | ) -> errors::CustomResult<Option<serde_json::Value>, errors::ConnectorError> { | ||||||
|  |     match next_action.action.payment_method_type { | ||||||
|  |         PaymentType::Blik => { | ||||||
|  |             let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos(); | ||||||
|  |             Ok(Some(serde_json::json!(WaitScreenData { | ||||||
|  |                 display_from_timestamp: current_time, | ||||||
|  |                 display_to_timestamp: Some(current_time + Duration::minutes(1).whole_nanoseconds()) | ||||||
|  |             }))) | ||||||
|  |         } | ||||||
|  |         PaymentType::Mbway => { | ||||||
|  |             let current_time = OffsetDateTime::now_utc().unix_timestamp_nanos(); | ||||||
|  |             Ok(Some(serde_json::json!(WaitScreenData { | ||||||
|  |                 display_from_timestamp: current_time, | ||||||
|  |                 display_to_timestamp: None | ||||||
|  |             }))) | ||||||
|  |         } | ||||||
|  |         _ => Ok(None), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl<F, Req> | impl<F, Req> | ||||||
|     TryFrom<( |     TryFrom<( | ||||||
|         types::ResponseRouterData<F, AdyenPaymentResponse, Req, types::PaymentsResponseData>, |         types::ResponseRouterData<F, AdyenPaymentResponse, Req, types::PaymentsResponseData>, | ||||||
|  | |||||||
| @ -431,6 +431,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { | |||||||
|                         api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => 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, |                         api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None, | ||||||
|  |                         api_models::payments::NextActionData::WaitScreenInformation{..} => None, | ||||||
|                     }) |                     }) | ||||||
|                     .ok_or(errors::ApiErrorResponse::InternalServerError) |                     .ok_or(errors::ApiErrorResponse::InternalServerError) | ||||||
|                     .into_report() |                     .into_report() | ||||||
|  | |||||||
| @ -375,13 +375,17 @@ where | |||||||
|  |  | ||||||
|                 let next_action_voucher = voucher_next_steps_check(payment_attempt.clone())?; |                 let next_action_voucher = voucher_next_steps_check(payment_attempt.clone())?; | ||||||
|  |  | ||||||
|                 let next_action_containing_qr_code = |                 let next_action_containing_qr_code_url = | ||||||
|                     qr_code_next_steps_check(payment_attempt.clone())?; |                     qr_code_next_steps_check(payment_attempt.clone())?; | ||||||
|  |  | ||||||
|  |                 let next_action_containing_wait_screen = | ||||||
|  |                     wait_screen_next_steps_check(payment_attempt.clone())?; | ||||||
|  |  | ||||||
|                 if payment_intent.status == enums::IntentStatus::RequiresCustomerAction |                 if payment_intent.status == enums::IntentStatus::RequiresCustomerAction | ||||||
|                     || bank_transfer_next_steps.is_some() |                     || bank_transfer_next_steps.is_some() | ||||||
|                     || next_action_voucher.is_some() |                     || next_action_voucher.is_some() | ||||||
|                     || next_action_containing_qr_code.is_some() |                     || next_action_containing_qr_code_url.is_some() | ||||||
|  |                     || next_action_containing_wait_screen.is_some() | ||||||
|                 { |                 { | ||||||
|                     next_action_response = bank_transfer_next_steps |                     next_action_response = bank_transfer_next_steps | ||||||
|                         .map(|bank_transfer| { |                         .map(|bank_transfer| { | ||||||
| @ -394,11 +398,17 @@ where | |||||||
|                                 voucher_details: voucher_data, |                                 voucher_details: voucher_data, | ||||||
|                             } |                             } | ||||||
|                         })) |                         })) | ||||||
|                         .or(next_action_containing_qr_code.map(|qr_code_data| { |                         .or(next_action_containing_qr_code_url.map(|qr_code_data| { | ||||||
|                             api_models::payments::NextActionData::QrCodeInformation { |                             api_models::payments::NextActionData::QrCodeInformation { | ||||||
|                                 image_data_url: qr_code_data.image_data_url, |                                 image_data_url: qr_code_data.image_data_url, | ||||||
|                             } |                             } | ||||||
|                         })) |                         })) | ||||||
|  |                         .or(next_action_containing_wait_screen.map(|wait_screen_data| { | ||||||
|  |                             api_models::payments::NextActionData::WaitScreenInformation { | ||||||
|  |                                 display_from_timestamp: wait_screen_data.display_from_timestamp, | ||||||
|  |                                 display_to_timestamp: wait_screen_data.display_to_timestamp, | ||||||
|  |                             } | ||||||
|  |                         })) | ||||||
|                         .or(redirection_data.map(|_| { |                         .or(redirection_data.map(|_| { | ||||||
|                             api_models::payments::NextActionData::RedirectToUrl { |                             api_models::payments::NextActionData::RedirectToUrl { | ||||||
|                                 redirect_to_url: helpers::create_startpay_url( |                                 redirect_to_url: helpers::create_startpay_url( | ||||||
| @ -624,6 +634,20 @@ pub fn qr_code_next_steps_check( | |||||||
|     Ok(qr_code_instructions) |     Ok(qr_code_instructions) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn wait_screen_next_steps_check( | ||||||
|  |     payment_attempt: storage::PaymentAttempt, | ||||||
|  | ) -> RouterResult<Option<api_models::payments::WaitScreenInstructions>> { | ||||||
|  |     let display_info_with_timer_steps: Option< | ||||||
|  |         Result<api_models::payments::WaitScreenInstructions, _>, | ||||||
|  |     > = payment_attempt | ||||||
|  |         .connector_metadata | ||||||
|  |         .map(|metadata| metadata.parse_value("WaitScreenInstructions")); | ||||||
|  |  | ||||||
|  |     let display_info_with_timer_instructions = | ||||||
|  |         display_info_with_timer_steps.transpose().ok().flatten(); | ||||||
|  |     Ok(display_info_with_timer_instructions) | ||||||
|  | } | ||||||
|  |  | ||||||
| impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { | impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { | ||||||
|     fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { |     fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { | ||||||
|         let pi = item.0; |         let pi = item.0; | ||||||
|  | |||||||
| @ -332,21 +332,6 @@ async fn should_make_adyen_eps_payment(web_driver: WebDriver) -> Result<(), WebD | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn should_make_adyen_blik_payment(web_driver: WebDriver) -> Result<(), WebDriverError> { |  | ||||||
|     let conn = AdyenSeleniumTest {}; |  | ||||||
|     conn.make_redirection_payment( |  | ||||||
|         web_driver, |  | ||||||
|         vec![ |  | ||||||
|             Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/64"))), |  | ||||||
|             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), |  | ||||||
|             Event::Assert(Assert::IsPresent("Status")), |  | ||||||
|             Event::Assert(Assert::IsPresent("processing")), //final status of this payment method will remain in processing state |  | ||||||
|         ], |  | ||||||
|     ) |  | ||||||
|     .await?; |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async fn should_make_adyen_bancontact_card_payment( | async fn should_make_adyen_bancontact_card_payment( | ||||||
|     web_driver: WebDriver, |     web_driver: WebDriver, | ||||||
| ) -> Result<(), WebDriverError> { | ) -> Result<(), WebDriverError> { | ||||||
| @ -634,6 +619,21 @@ async fn should_make_adyen_swish_payment(web_driver: WebDriver) -> Result<(), We | |||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async fn should_make_adyen_blik_payment(driver: WebDriver) -> Result<(), WebDriverError> { | ||||||
|  |     let conn = AdyenSeleniumTest {}; | ||||||
|  |     conn.make_redirection_payment( | ||||||
|  |         driver, | ||||||
|  |         vec![ | ||||||
|  |             Event::Trigger(Trigger::Goto(&format!("{CHEKOUT_BASE_URL}/saved/64"))), | ||||||
|  |             Event::Trigger(Trigger::Click(By::Id("card-submit-btn"))), | ||||||
|  |             Event::Assert(Assert::IsPresent("Next Action Type")), | ||||||
|  |             Event::Assert(Assert::IsPresent("wait_screen_information")), | ||||||
|  |         ], | ||||||
|  |     ) | ||||||
|  |     .await?; | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| #[serial] | #[serial] | ||||||
| #[ignore] | #[ignore] | ||||||
| @ -752,12 +752,6 @@ fn should_make_adyen_eps_payment_test() { | |||||||
|     tester!(should_make_adyen_eps_payment); |     tester!(should_make_adyen_eps_payment); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| #[serial] |  | ||||||
| fn should_make_adyen_blik_payment_test() { |  | ||||||
|     tester!(should_make_adyen_blik_payment); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| #[serial] | #[serial] | ||||||
| fn should_make_adyen_bancontact_card_payment_test() { | fn should_make_adyen_bancontact_card_payment_test() { | ||||||
| @ -808,6 +802,12 @@ fn should_make_adyen_dana_payment_test() { | |||||||
|     tester!(should_make_adyen_dana_payment); |     tester!(should_make_adyen_dana_payment); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | #[serial] | ||||||
|  | fn should_make_adyen_blik_payment_test() { | ||||||
|  |     tester!(should_make_adyen_blik_payment); | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| #[serial] | #[serial] | ||||||
| fn should_make_adyen_online_banking_fpx_payment_test() { | fn should_make_adyen_online_banking_fpx_payment_test() { | ||||||
|  | |||||||
| @ -6395,6 +6395,29 @@ | |||||||
|                 ] |                 ] | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             "type": "object", | ||||||
|  |             "description": "Contains duration for displaying a wait screen, wait screen with timer is displayed by sdk", | ||||||
|  |             "required": [ | ||||||
|  |               "display_from_timestamp", | ||||||
|  |               "type" | ||||||
|  |             ], | ||||||
|  |             "properties": { | ||||||
|  |               "display_from_timestamp": { | ||||||
|  |                 "type": "integer" | ||||||
|  |               }, | ||||||
|  |               "display_to_timestamp": { | ||||||
|  |                 "type": "integer", | ||||||
|  |                 "nullable": true | ||||||
|  |               }, | ||||||
|  |               "type": { | ||||||
|  |                 "type": "string", | ||||||
|  |                 "enum": [ | ||||||
|  |                   "wait_screen_information" | ||||||
|  |                 ] | ||||||
|  |               } | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|         ], |         ], | ||||||
|         "discriminator": { |         "discriminator": { | ||||||
| @ -6408,7 +6431,8 @@ | |||||||
|           "display_qr_code", |           "display_qr_code", | ||||||
|           "invoke_sdk_client", |           "invoke_sdk_client", | ||||||
|           "trigger_api", |           "trigger_api", | ||||||
|           "display_bank_transfer_information" |           "display_bank_transfer_information", | ||||||
|  |           "display_wait_screen" | ||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "NoThirdPartySdkSessionResponse": { |       "NoThirdPartySdkSessionResponse": { | ||||||
| @ -6827,7 +6851,8 @@ | |||||||
|           "display_qr_code", |           "display_qr_code", | ||||||
|           "one_click", |           "one_click", | ||||||
|           "link_wallet", |           "link_wallet", | ||||||
|           "invoke_payment_app" |           "invoke_payment_app", | ||||||
|  |           "display_wait_screen" | ||||||
|         ] |         ] | ||||||
|       }, |       }, | ||||||
|       "PaymentIdType": { |       "PaymentIdType": { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 AkshayaFoiger
					AkshayaFoiger