feat(connector): [Adyen] Add support for PIX Payment Method (#3236)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Swangi Kumari
2024-01-25 12:41:54 +05:30
committed by GitHub
parent 3507ad60b2
commit fc6e68f7f0
7 changed files with 196 additions and 106 deletions

View File

@ -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<Url>,
display_to_timestamp: Option<i64>,
#[schema(value_type = String)]
/// The url for Qr code given by the connector
qr_code_url: Option<Url>,
},
/// 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<i64>,
},
QrDataUrl {
image_data_url: Url,
display_to_timestamp: Option<i64>,
},
QrCodeImageUrl {
qr_code_url: Url,
display_to_timestamp: Option<i64>,
},
}
#[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<i64>,
pub qr_code_url: Option<Url>,
}
#[derive(Clone, Debug, serde::Deserialize)]

View File

@ -794,8 +794,9 @@ pub enum StripeNextAction {
session_token: Option<payments::SessionToken>,
},
QrCodeInformation {
image_data_url: url::Url,
image_data_url: Option<url::Url>,
display_to_timestamp: Option<i64>,
qr_code_url: Option<url::Url>,
},
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 }

View File

@ -384,8 +384,9 @@ pub enum StripeNextAction {
session_token: Option<payments::SessionToken>,
},
QrCodeInformation {
image_data_url: url::Url,
image_data_url: Option<url::Url>,
display_to_timestamp: Option<i64>,
qr_code_url: Option<url::Url>,
},
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 }

View File

@ -343,6 +343,7 @@ pub struct QrCodeResponseResponse {
action: AdyenQrCodeAction,
refusal_reason: Option<String>,
refusal_reason_code: Option<String>,
additional_data: Option<QrCodeAdditionalData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -352,10 +353,17 @@ pub struct AdyenQrCodeAction {
#[serde(rename = "type")]
type_of_response: ActionType,
#[serde(rename = "url")]
mobile_redirection_url: Option<Url>,
qr_code_url: Option<Url>,
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<PrimitiveDateTime>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdyenPtsAction {
@ -402,20 +410,20 @@ pub struct Amount {
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum AdyenPaymentMethod<'a> {
AdyenAffirm(Box<AdyenPayLaterData>),
AdyenAffirm(Box<PmdForPaymentType>),
AdyenCard(Box<AdyenCard>),
AdyenKlarna(Box<AdyenPayLaterData>),
AdyenPaypal(Box<AdyenPaypal>),
AdyenKlarna(Box<PmdForPaymentType>),
AdyenPaypal(Box<PmdForPaymentType>),
#[serde(rename = "afterpaytouch")]
AfterPay(Box<AdyenPayLaterData>),
AlmaPayLater(Box<AdyenPayLaterData>),
AliPay(Box<AliPayData>),
AliPayHk(Box<AliPayHkData>),
AfterPay(Box<PmdForPaymentType>),
AlmaPayLater(Box<PmdForPaymentType>),
AliPay(Box<PmdForPaymentType>),
AliPayHk(Box<PmdForPaymentType>),
ApplePay(Box<AdyenApplePay>),
#[serde(rename = "atome")]
Atome,
BancontactCard(Box<BancontactCardData>),
Bizum(Box<BankRedirectionPMData>),
Bizum(Box<PmdForPaymentType>),
Blik(Box<BlikRedirectionData>),
#[serde(rename = "boletobancario")]
BoletoBancario,
@ -426,7 +434,7 @@ pub enum AdyenPaymentMethod<'a> {
Eps(Box<BankRedirectionWithIssuer<'a>>),
#[serde(rename = "gcash")]
Gcash(Box<GcashData>),
Giropay(Box<BankRedirectionPMData>),
Giropay(Box<PmdForPaymentType>),
Gpay(Box<AdyenGPay>),
#[serde(rename = "gopay_wallet")]
GoPay(Box<GoPayData>),
@ -435,7 +443,7 @@ pub enum AdyenPaymentMethod<'a> {
Kakaopay(Box<KakaoPayData>),
Mandate(Box<AdyenMandate>),
Mbway(Box<MbwayData>),
MobilePay(Box<MobilePayData>),
MobilePay(Box<PmdForPaymentType>),
#[serde(rename = "momo_wallet")]
Momo(Box<MomoData>),
#[serde(rename = "momo_atm")]
@ -443,7 +451,7 @@ pub enum AdyenPaymentMethod<'a> {
#[serde(rename = "touchngo")]
TouchNGo(Box<TouchNGoData>),
OnlineBankingCzechRepublic(Box<OnlineBankingCzechRepublicData>),
OnlineBankingFinland(Box<OnlineBankingFinlandData>),
OnlineBankingFinland(Box<PmdForPaymentType>),
OnlineBankingPoland(Box<OnlineBankingPolandData>),
OnlineBankingSlovakia(Box<OnlineBankingSlovakiaData>),
#[serde(rename = "molpay_ebanking_fpx_MY")]
@ -513,6 +521,13 @@ pub enum AdyenPaymentMethod<'a> {
Seicomart(Box<JCSVoucherData>),
#[serde(rename = "econtext_stores")]
PayEasy(Box<JCSVoucherData>),
Pix(Box<PmdForPaymentType>),
}
#[derive(Debug, Clone, Serialize)]
pub struct PmdForPaymentType {
#[serde(rename = "type")]
payment_type: PaymentType,
}
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
@ -592,11 +607,6 @@ pub struct BancontactCardData {
holder_name: Secret<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct MobilePayData {
#[serde(rename = "type")]
payment_type: PaymentType,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MbwayData {
@ -625,11 +635,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")]
@ -1012,13 +1017,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> {
@ -1077,23 +1075,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 {}
@ -1125,12 +1106,6 @@ pub struct AdyenApplePay {
apple_pay_token: Secret<String>,
}
#[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 {
@ -1270,6 +1245,7 @@ pub enum PaymentType {
Seicomart,
#[serde(rename = "econtext_stores")]
PayEasy,
Pix,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -1948,19 +1924,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)))
@ -1993,7 +1969,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)))
@ -2038,13 +2014,13 @@ impl<'a> TryFrom<(&api::PayLaterData, Option<api_enums::CountryAlpha2>)>
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,
})),
),
@ -2055,7 +2031,7 @@ impl<'a> TryFrom<(&api::PayLaterData, Option<api_enums::CountryAlpha2>)>
| 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,
}))),
}
@ -2072,7 +2048,7 @@ impl<'a> TryFrom<(&api::PayLaterData, Option<api_enums::CountryAlpha2>)>
Ok(AdyenPaymentMethod::Walley)
}
api_models::payments::PayLaterData::AlmaRedirect { .. } => Ok(
AdyenPaymentMethod::AlmaPayLater(Box::new(AdyenPayLaterData {
AdyenPaymentMethod::AlmaPayLater(Box::new(PmdForPaymentType {
payment_type: PaymentType::Alma,
})),
),
@ -2131,7 +2107,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,
})))
}
@ -2158,11 +2134,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,
@ -2185,7 +2161,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,
})),
),
@ -2296,8 +2272,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 { .. }
@ -3314,20 +3294,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 {
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,
display_to_timestamp: None,
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))
payments::QrCodeInformation,
>::encode_to_value(&qr_code_info))
.transpose()
.change_context(errors::ConnectorError::ResponseHandlingFailed)
} else {
Ok(None)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -3418,7 +3428,8 @@ pub fn get_wait_screen_metadata(
| PaymentType::MiniStop
| PaymentType::FamilyMart
| PaymentType::Seicomart
| PaymentType::PayEasy => Ok(None),
| PaymentType::PayEasy
| PaymentType::Pix => Ok(None),
}
}
@ -3521,7 +3532,8 @@ pub fn get_present_to_shopper_metadata(
| PaymentType::Vipps
| PaymentType::Swish
| PaymentType::PaySafeCard
| PaymentType::SevenEleven => Ok(None),
| PaymentType::SevenEleven
| PaymentType::Pix => Ok(None),
}
}

View File

@ -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<Vec<OrderDetailsWithAmount>, Error>;

View File

@ -522,10 +522,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 {
@ -830,11 +827,10 @@ 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
) -> RouterResult<Option<api_models::payments::QrCodeInformation>> {
let qr_code_steps: Option<Result<api_models::payments::QrCodeInformation, _>> = payment_attempt
.connector_metadata
.map(|metadata| metadata.parse_value("QrCodeNextStepsInstruction"));
.map(|metadata| metadata.parse_value("QrCodeInformation"));
let qr_code_instructions = qr_code_steps.transpose().ok().flatten();
Ok(qr_code_instructions)
@ -903,6 +899,8 @@ pub fn bank_transfer_next_steps_check(
) -> RouterResult<Option<api_models::payments::BankTransferNextStepsData>> {
let bank_transfer_next_step = if let Some(diesel_models::enums::PaymentMethod::BankTransfer) =
payment_attempt.payment_method
{
if payment_attempt.payment_method_type != Some(diesel_models::enums::PaymentMethodType::Pix)
{
let bank_transfer_next_steps: Option<api_models::payments::BankTransferNextStepsData> =
payment_attempt
@ -911,12 +909,17 @@ pub fn bank_transfer_next_steps_check(
metadata
.parse_value("NextStepsRequirements")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to parse the Value to NextRequirements struct")
.attach_printable(
"Failed to parse the Value to NextRequirements struct",
)
})
.transpose()?;
bank_transfer_next_steps
} else {
None
}
} else {
None
};
Ok(bank_transfer_next_step)
}
@ -960,6 +963,38 @@ pub fn change_order_details_to_new_type(
}])
}
impl ForeignFrom<api_models::payments::QrCodeInformation> 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

View File

@ -8221,17 +8221,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": [