feat(connector): [Stripe] Add support for WeChat Pay and Qr code support in next action (#1555)

Co-authored-by: AkshayaFoiger <akshaya.shankar@juspay.in>
Co-authored-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
This commit is contained in:
Sangamesh Kulkarni
2023-07-06 15:29:18 +05:30
committed by GitHub
parent ca4e242d20
commit a15a77dea3
8 changed files with 140 additions and 27 deletions

View File

@ -9,6 +9,7 @@ use common_utils::{
use masking::{PeekInterface, Secret}; use masking::{PeekInterface, Secret};
use router_derive::Setter; use router_derive::Setter;
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use url::Url;
use utoipa::ToSchema; use utoipa::ToSchema;
use crate::{ use crate::{
@ -177,7 +178,7 @@ pub struct PaymentsRequest {
/// The URL to redirect after the completion of the operation /// The URL to redirect after the completion of the operation
#[schema(value_type = Option<String>, example = "https://hyperswitch.io")] #[schema(value_type = Option<String>, example = "https://hyperswitch.io")]
pub return_url: Option<url::Url>, pub return_url: Option<Url>,
/// Indicates that you intend to make future payments with this Payments 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. /// Indicates that you intend to make future payments with this Payments 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<FutureUsage>, example = "off_session")] #[schema(value_type = Option<FutureUsage>, example = "off_session")]
pub setup_future_usage: Option<api_enums::FutureUsage>, pub setup_future_usage: Option<api_enums::FutureUsage>,
@ -920,6 +921,8 @@ pub enum WalletData {
SamsungPay(Box<SamsungPayWalletData>), SamsungPay(Box<SamsungPayWalletData>),
/// The wallet data for WeChat Pay Redirection /// The wallet data for WeChat Pay Redirection
WeChatPayRedirect(Box<WeChatPayRedirection>), WeChatPayRedirect(Box<WeChatPayRedirection>),
/// The wallet data for WeChat Pay
WeChatPay(Box<WeChatPay>),
} }
#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[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)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct WeChatPayRedirection {} 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)] #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaypalRedirection {} pub struct PaypalRedirection {}
@ -1229,8 +1235,13 @@ pub enum NextActionData {
DisplayBankTransferInformation { DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: BankTransferNextStepsData, 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<SessionToken> }, ThirdPartySdkSessionToken { session_token: Option<SessionToken> },
/// 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)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1242,6 +1253,11 @@ pub struct BankTransferNextStepsData {
pub receiver: ReceiverDetails, 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)] #[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 {

View File

@ -687,6 +687,9 @@ pub enum StripeNextAction {
ThirdPartySdkSessionToken { ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>, session_token: Option<payments::SessionToken>,
}, },
QrCodeInformation {
image_data_url: url::Url,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -710,5 +713,8 @@ pub(crate) fn into_stripe_next_action(
payments::NextActionData::ThirdPartySdkSessionToken { session_token } => { payments::NextActionData::ThirdPartySdkSessionToken { session_token } => {
StripeNextAction::ThirdPartySdkSessionToken { session_token } StripeNextAction::ThirdPartySdkSessionToken { session_token }
} }
payments::NextActionData::QrCodeInformation { image_data_url } => {
StripeNextAction::QrCodeInformation { image_data_url }
}
}) })
} }

View File

@ -345,6 +345,9 @@ pub enum StripeNextAction {
ThirdPartySdkSessionToken { ThirdPartySdkSessionToken {
session_token: Option<payments::SessionToken>, session_token: Option<payments::SessionToken>,
}, },
QrCodeInformation {
image_data_url: url::Url,
},
} }
pub(crate) fn into_stripe_next_action( pub(crate) fn into_stripe_next_action(
@ -368,6 +371,9 @@ pub(crate) fn into_stripe_next_action(
payments::NextActionData::ThirdPartySdkSessionToken { session_token } => { payments::NextActionData::ThirdPartySdkSessionToken { session_token } => {
StripeNextAction::ThirdPartySdkSessionToken { session_token } StripeNextAction::ThirdPartySdkSessionToken { session_token }
} }
payments::NextActionData::QrCodeInformation { image_data_url } => {
StripeNextAction::QrCodeInformation { image_data_url }
}
}) })
} }

View File

@ -1060,8 +1060,7 @@ fn create_stripe_payment_method(
StripePaymentMethodType::ApplePay, StripePaymentMethodType::ApplePay,
StripeBillingAddress::default(), StripeBillingAddress::default(),
)), )),
payments::WalletData::WeChatPay(_) => Ok((
payments::WalletData::WeChatPayRedirect(_) => Ok((
StripePaymentMethodData::Wallet(StripeWallet::WechatpayPayment(WechatpayPayment { StripePaymentMethodData::Wallet(StripeWallet::WechatpayPayment(WechatpayPayment {
client: WechatClient::Web, client: WechatClient::Web,
payment_method_types: StripePaymentMethodType::Wechatpay, payment_method_types: StripePaymentMethodType::Wechatpay,
@ -1515,6 +1514,12 @@ pub struct SepaAndBacsBankTransferInstructions {
pub receiver: SepaAndBacsReceiver, 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)] #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct SepaAndBacsReceiver { pub struct SepaAndBacsReceiver {
pub amount_received: i64, pub amount_received: i64,
@ -1699,26 +1704,47 @@ pub fn get_connector_metadata(
amount: i64, amount: i64,
) -> CustomResult<Option<serde_json::Value>, errors::ConnectorError> { ) -> CustomResult<Option<serde_json::Value>, errors::ConnectorError> {
let next_action_response = next_action let next_action_response = next_action
.and_then(|next_action_response| match next_action_response { .and_then(|next_action_response| match next_action_response {
StripeNextActionResponse::DisplayBankTransferInstructions(response) => { StripeNextActionResponse::DisplayBankTransferInstructions(response) => {
Some(SepaAndBacsBankTransferInstructions { let bank_instructions = response.financial_addresses.get(0);
sepa_bank_instructions: response.financial_addresses[0].iban.to_owned(), let (sepa_bank_instructions, bacs_bank_instructions) =
bacs_bank_instructions: response.financial_addresses[0] bank_instructions.map_or((None, None), |financial_address| {
.sort_code (
.to_owned(), financial_address.iban.to_owned(),
receiver: SepaAndBacsReceiver { financial_address.sort_code.to_owned(),
amount_received: amount - response.amount_remaining, )
amount_remaining: response.amount_remaining, });
},
}) let bank_transfer_instructions = SepaAndBacsBankTransferInstructions {
} sepa_bank_instructions,
_ => None, bacs_bank_instructions,
}).map(|response| { receiver: SepaAndBacsReceiver {
common_utils::ext_traits::Encode::<SepaAndBacsBankTransferInstructions>::encode_to_value( amount_received: amount - response.amount_remaining,
&response, amount_remaining: response.amount_remaining,
) },
.change_context(errors::ConnectorError::ResponseHandlingFailed) };
}).transpose()?;
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::<WechatPayNextInstructions>::encode_to_value(
&wechat_pay_instructions,
),
)
}
_ => None,
})
.transpose()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
Ok(next_action_response) Ok(next_action_response)
} }
@ -1848,7 +1874,7 @@ impl StripeNextActionResponse {
Self::RedirectToUrl(redirect_to_url) | Self::AlipayHandleRedirect(redirect_to_url) => { Self::RedirectToUrl(redirect_to_url) | Self::AlipayHandleRedirect(redirect_to_url) => {
Some(redirect_to_url.url.to_owned()) 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) => { Self::VerifyWithMicrodeposits(verify_with_microdeposits) => {
Some(verify_with_microdeposits.hosted_verification_url.to_owned()) Some(verify_with_microdeposits.hosted_verification_url.to_owned())
} }
@ -1885,7 +1911,11 @@ pub struct StripeRedirectToUrlResponse {
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct StripeRedirectToQr { 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, 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)] #[derive(Clone, Debug, Eq, PartialEq, Deserialize)]

View File

@ -431,7 +431,8 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
.and_then(|next_action_data| match next_action_data { .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::RedirectToUrl { redirect_to_url } => Some(redirect_to_url),
api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None, 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) .ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report() .into_report()

View File

@ -322,6 +322,9 @@ where
let bank_transfer_next_steps = let bank_transfer_next_steps =
bank_transfer_next_steps_check(payment_attempt.clone())?; 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 if payment_intent.status == enums::IntentStatus::RequiresCustomerAction
|| bank_transfer_next_steps.is_some() || bank_transfer_next_steps.is_some()
{ {
@ -331,6 +334,11 @@ where
bank_transfer_steps_and_charges_details: bank_transfer, 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 { .or(Some(api_models::payments::NextActionData::RedirectToUrl {
redirect_to_url: helpers::create_startpay_url( redirect_to_url: helpers::create_startpay_url(
server, server,
@ -554,6 +562,18 @@ where
} }
} }
pub fn qr_code_next_steps_check(
payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::QrCodeNextStepsInstruction>> {
let qr_code_steps: Option<Result<api_models::payments::QrCodeNextStepsInstruction, _>> =
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 { 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;

View File

@ -233,6 +233,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payments::SdkNextAction, api_models::payments::SdkNextAction,
api_models::payments::NextActionCall, api_models::payments::NextActionCall,
api_models::payments::SamsungPayWalletData, api_models::payments::SamsungPayWalletData,
api_models::payments::WeChatPay,
api_models::payments::GpayTokenizationData, api_models::payments::GpayTokenizationData,
api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::GooglePayPaymentMethodInfo,
api_models::payments::ApplePayWalletData, api_models::payments::ApplePayWalletData,

View File

@ -5530,7 +5530,7 @@
}, },
{ {
"type": "object", "type": "object",
"description": "contains third party sdk session token response", "description": "Contains third party sdk session token response",
"required": [ "required": [
"type" "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": { "discriminator": {
@ -8548,9 +8567,23 @@
"$ref": "#/components/schemas/WeChatPayRedirection" "$ref": "#/components/schemas/WeChatPayRedirection"
} }
} }
},
{
"type": "object",
"required": [
"we_chat_pay"
],
"properties": {
"we_chat_pay": {
"$ref": "#/components/schemas/WeChatPay"
}
}
} }
] ]
}, },
"WeChatPay": {
"type": "object"
},
"WeChatPayRedirection": { "WeChatPayRedirection": {
"type": "object" "type": "object"
}, },