From a15a77dea36fd13e92bd64014fc25014d51a3548 Mon Sep 17 00:00:00 2001 From: Sangamesh Kulkarni <59434228+Sangamesh26@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:29:18 +0530 Subject: [PATCH] feat(connector): [Stripe] Add support for WeChat Pay and Qr code support in next action (#1555) Co-authored-by: AkshayaFoiger Co-authored-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com> --- crates/api_models/src/payments.rs | 20 ++++- .../stripe/payment_intents/types.rs | 6 ++ .../stripe/setup_intents/types.rs | 6 ++ .../src/connector/stripe/transformers.rs | 76 +++++++++++++------ crates/router/src/core/payments.rs | 3 +- .../router/src/core/payments/transformers.rs | 20 +++++ crates/router/src/openapi.rs | 1 + openapi/openapi_spec.json | 35 ++++++++- 8 files changed, 140 insertions(+), 27 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 2d4a539c1e..453d152122 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -9,6 +9,7 @@ use common_utils::{ use masking::{PeekInterface, Secret}; use router_derive::Setter; use time::PrimitiveDateTime; +use url::Url; use utoipa::ToSchema; use crate::{ @@ -177,7 +178,7 @@ pub struct PaymentsRequest { /// The URL to redirect after the completion of the operation #[schema(value_type = Option, example = "https://hyperswitch.io")] - pub return_url: Option, + pub return_url: Option, /// Indicates that you intend to make future payments with this Payment’s payment method. Providing this parameter will attach the payment method to the Customer, if present, after the Payment is confirmed and any required actions from the user are complete. #[schema(value_type = Option, example = "off_session")] pub setup_future_usage: Option, @@ -920,6 +921,8 @@ pub enum WalletData { SamsungPay(Box), /// The wallet data for WeChat Pay Redirection WeChatPayRedirect(Box), + /// The wallet data for WeChat Pay + WeChatPay(Box), } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -959,6 +962,9 @@ pub struct ApplePayThirdPartySdkData {} #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct WeChatPayRedirection {} +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct WeChatPay {} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct PaypalRedirection {} @@ -1229,8 +1235,13 @@ pub enum NextActionData { DisplayBankTransferInformation { bank_transfer_steps_and_charges_details: BankTransferNextStepsData, }, - /// contains third party sdk session token response + /// Contains third party sdk session token response ThirdPartySdkSessionToken { session_token: Option }, + /// Contains url for Qr code image, this qr code has to be shown in sdk + QrCodeInformation { + #[schema(value_type = String)] + image_data_url: Url, + }, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -1242,6 +1253,11 @@ pub struct BankTransferNextStepsData { pub receiver: ReceiverDetails, } +#[derive(Clone, Debug, serde::Deserialize)] +pub struct QrCodeNextStepsInstruction { + pub image_data_url: Url, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum BankTransferInstructions { diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 52e9219103..d592904434 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -687,6 +687,9 @@ pub enum StripeNextAction { ThirdPartySdkSessionToken { session_token: Option, }, + QrCodeInformation { + image_data_url: url::Url, + }, } pub(crate) fn into_stripe_next_action( @@ -710,5 +713,8 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::ThirdPartySdkSessionToken { session_token } => { StripeNextAction::ThirdPartySdkSessionToken { session_token } } + payments::NextActionData::QrCodeInformation { image_data_url } => { + StripeNextAction::QrCodeInformation { image_data_url } + } }) } diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 0a013300ae..d4b6aac306 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -345,6 +345,9 @@ pub enum StripeNextAction { ThirdPartySdkSessionToken { session_token: Option, }, + QrCodeInformation { + image_data_url: url::Url, + }, } pub(crate) fn into_stripe_next_action( @@ -368,6 +371,9 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::ThirdPartySdkSessionToken { session_token } => { StripeNextAction::ThirdPartySdkSessionToken { session_token } } + payments::NextActionData::QrCodeInformation { image_data_url } => { + StripeNextAction::QrCodeInformation { image_data_url } + } }) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 7a9c9ef4e2..acce44f4de 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1060,8 +1060,7 @@ fn create_stripe_payment_method( StripePaymentMethodType::ApplePay, StripeBillingAddress::default(), )), - - payments::WalletData::WeChatPayRedirect(_) => Ok(( + payments::WalletData::WeChatPay(_) => Ok(( StripePaymentMethodData::Wallet(StripeWallet::WechatpayPayment(WechatpayPayment { client: WechatClient::Web, payment_method_types: StripePaymentMethodType::Wechatpay, @@ -1515,6 +1514,12 @@ pub struct SepaAndBacsBankTransferInstructions { pub receiver: SepaAndBacsReceiver, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Serialize)] +pub struct WechatPayNextInstructions { + pub image_data_url: Url, +} + #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct SepaAndBacsReceiver { pub amount_received: i64, @@ -1699,26 +1704,47 @@ pub fn get_connector_metadata( amount: i64, ) -> CustomResult, errors::ConnectorError> { let next_action_response = next_action - .and_then(|next_action_response| match next_action_response { - StripeNextActionResponse::DisplayBankTransferInstructions(response) => { - Some(SepaAndBacsBankTransferInstructions { - sepa_bank_instructions: response.financial_addresses[0].iban.to_owned(), - bacs_bank_instructions: response.financial_addresses[0] - .sort_code - .to_owned(), - receiver: SepaAndBacsReceiver { - amount_received: amount - response.amount_remaining, - amount_remaining: response.amount_remaining, - }, - }) - } - _ => None, - }).map(|response| { - common_utils::ext_traits::Encode::::encode_to_value( - &response, - ) - .change_context(errors::ConnectorError::ResponseHandlingFailed) - }).transpose()?; + .and_then(|next_action_response| match next_action_response { + StripeNextActionResponse::DisplayBankTransferInstructions(response) => { + let bank_instructions = response.financial_addresses.get(0); + let (sepa_bank_instructions, bacs_bank_instructions) = + bank_instructions.map_or((None, None), |financial_address| { + ( + financial_address.iban.to_owned(), + financial_address.sort_code.to_owned(), + ) + }); + + let bank_transfer_instructions = SepaAndBacsBankTransferInstructions { + sepa_bank_instructions, + bacs_bank_instructions, + receiver: SepaAndBacsReceiver { + amount_received: amount - response.amount_remaining, + amount_remaining: response.amount_remaining, + }, + }; + + Some(common_utils::ext_traits::Encode::< + SepaAndBacsBankTransferInstructions, + >::encode_to_value( + &bank_transfer_instructions + )) + } + StripeNextActionResponse::WechatPayDisplayQrCode(response) => { + let wechat_pay_instructions = WechatPayNextInstructions { + image_data_url: response.image_data_url.to_owned(), + }; + + Some( + common_utils::ext_traits::Encode::::encode_to_value( + &wechat_pay_instructions, + ), + ) + } + _ => None, + }) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; Ok(next_action_response) } @@ -1848,7 +1874,7 @@ impl StripeNextActionResponse { Self::RedirectToUrl(redirect_to_url) | Self::AlipayHandleRedirect(redirect_to_url) => { Some(redirect_to_url.url.to_owned()) } - Self::WechatPayDisplayQrCode(redirect_to_url) => Some(redirect_to_url.data.to_owned()), + Self::WechatPayDisplayQrCode(_) => None, Self::VerifyWithMicrodeposits(verify_with_microdeposits) => { Some(verify_with_microdeposits.hosted_verification_url.to_owned()) } @@ -1885,7 +1911,11 @@ pub struct StripeRedirectToUrlResponse { #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct StripeRedirectToQr { + // This data contains url, it should be converted to QR code. + // Note: The url in this data is not redirection url data: Url, + // This is the image source, this image_data_url can directly be used by sdk to show the QR code + image_data_url: Url, } #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c5a25b993e..384a223038 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -431,7 +431,8 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { .and_then(|next_action_data| match next_action_data { 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::ThirdPartySdkSessionToken { .. } => None, + api_models::payments::NextActionData::QrCodeInformation{..} => None }) .ok_or(errors::ApiErrorResponse::InternalServerError) .into_report() diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 64cb0bc5e2..f807fc6c82 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -322,6 +322,9 @@ where let bank_transfer_next_steps = bank_transfer_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() { @@ -331,6 +334,11 @@ where bank_transfer_steps_and_charges_details: bank_transfer, } }) + .or(next_action_containing_qr_code.map(|qr_code_data| { + api_models::payments::NextActionData::QrCodeInformation { + image_data_url: qr_code_data.image_data_url, + } + })) .or(Some(api_models::payments::NextActionData::RedirectToUrl { redirect_to_url: helpers::create_startpay_url( server, @@ -554,6 +562,18 @@ 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")); + + let qr_code_instructions = qr_code_steps.transpose().ok().flatten(); + Ok(qr_code_instructions) +} + impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { fn foreign_from(item: (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { let pi = item.0; diff --git a/crates/router/src/openapi.rs b/crates/router/src/openapi.rs index a53c57bc05..9d1473be92 100644 --- a/crates/router/src/openapi.rs +++ b/crates/router/src/openapi.rs @@ -233,6 +233,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SdkNextAction, api_models::payments::NextActionCall, api_models::payments::SamsungPayWalletData, + api_models::payments::WeChatPay, api_models::payments::GpayTokenizationData, api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 53bc3c7c29..650f6a4f5d 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -5530,7 +5530,7 @@ }, { "type": "object", - "description": "contains third party sdk session token response", + "description": "Contains third party sdk session token response", "required": [ "type" ], @@ -5550,6 +5550,25 @@ ] } } + }, + { + "type": "object", + "description": "Contains url for Qr code image, this qr code has to be shown in sdk", + "required": [ + "image_data_url", + "type" + ], + "properties": { + "image_data_url": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "qr_code_information" + ] + } + } } ], "discriminator": { @@ -8548,9 +8567,23 @@ "$ref": "#/components/schemas/WeChatPayRedirection" } } + }, + { + "type": "object", + "required": [ + "we_chat_pay" + ], + "properties": { + "we_chat_pay": { + "$ref": "#/components/schemas/WeChatPay" + } + } } ] }, + "WeChatPay": { + "type": "object" + }, "WeChatPayRedirection": { "type": "object" },