feat(connector): [Iatapay] add upi qr support (#4728)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: SamraatBansal <55536657+SamraatBansal@users.noreply.github.com>
This commit is contained in:
AkshayaFoiger
2024-05-28 13:06:53 +05:30
committed by GitHub
parent d15cb31814
commit c9fa94febe
22 changed files with 257 additions and 65 deletions

View File

@ -141,10 +141,10 @@ impl From<StripeWallet> for payments::WalletData {
}
impl From<StripeUpi> for payments::UpiData {
fn from(upi: StripeUpi) -> Self {
Self {
vpa_id: Some(upi.vpa_id),
}
fn from(upi_data: StripeUpi) -> Self {
Self::UpiCollect(payments::UpiCollectData {
vpa_id: Some(upi_data.vpa_id),
})
}
}
@ -315,6 +315,18 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
let amount = item.amount.map(|amount| MinorUnit::new(amount).into());
let payment_method_data = item.payment_method_data.as_ref().map(|pmd| {
let payment_method_data = match pmd.payment_method_details.as_ref() {
Some(spmd) => Some(payments::PaymentMethodData::from(spmd.to_owned())),
None => get_pmd_based_on_payment_method_type(item.payment_method_types),
};
payments::PaymentMethodDataRequest {
payment_method_data,
billing: pmd.billing_details.clone().map(payments::Address::from),
}
});
let request = Ok(Self {
payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId),
amount,
@ -334,16 +346,7 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
phone: item.shipping.as_ref().and_then(|s| s.phone.clone()),
description: item.description,
return_url: item.return_url,
payment_method_data: item.payment_method_data.as_ref().and_then(|pmd| {
pmd.payment_method_details
.as_ref()
.map(|spmd| payments::PaymentMethodDataRequest {
payment_method_data: Some(payments::PaymentMethodData::from(
spmd.to_owned(),
)),
billing: pmd.billing_details.clone().map(payments::Address::from),
})
}),
payment_method_data,
payment_method: item
.payment_method_data
.as_ref()
@ -816,6 +819,9 @@ pub enum StripeNextAction {
display_to_timestamp: Option<i64>,
qr_code_url: Option<url::Url>,
},
FetchQrCodeInformation {
qr_code_fetch_url: url::Url,
},
DisplayVoucherInformation {
voucher_details: payments::VoucherNextStepData,
},
@ -858,6 +864,9 @@ pub(crate) fn into_stripe_next_action(
display_to_timestamp,
qr_code_url,
},
payments::NextActionData::FetchQrCodeInformation { qr_code_fetch_url } => {
StripeNextAction::FetchQrCodeInformation { qr_code_fetch_url }
}
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
StripeNextAction::DisplayVoucherInformation { voucher_details }
}
@ -884,3 +893,15 @@ pub(crate) fn into_stripe_next_action(
pub struct StripePaymentRetrieveBody {
pub client_secret: Option<String>,
}
//To handle payment types that have empty payment method data
fn get_pmd_based_on_payment_method_type(
payment_method_type: Option<api_enums::PaymentMethodType>,
) -> Option<payments::PaymentMethodData> {
match payment_method_type {
Some(api_enums::PaymentMethodType::UpiIntent) => Some(payments::PaymentMethodData::Upi(
payments::UpiData::UpiIntent(payments::UpiIntentData {}),
)),
_ => None,
}
}

View File

@ -382,6 +382,9 @@ pub enum StripeNextAction {
display_to_timestamp: Option<i64>,
qr_code_url: Option<url::Url>,
},
FetchQrCodeInformation {
qr_code_fetch_url: url::Url,
},
DisplayVoucherInformation {
voucher_details: payments::VoucherNextStepData,
},
@ -424,6 +427,9 @@ pub(crate) fn into_stripe_next_action(
display_to_timestamp,
qr_code_url,
},
payments::NextActionData::FetchQrCodeInformation { qr_code_fetch_url } => {
StripeNextAction::FetchQrCodeInformation { qr_code_fetch_url }
}
payments::NextActionData::DisplayVoucherInformation { voucher_details } => {
StripeNextAction::DisplayVoucherInformation { voucher_details }
}

View File

@ -214,7 +214,8 @@ impl ConnectorValidation for Adyen {
| PaymentMethodType::SamsungPay
| PaymentMethodType::Evoucher
| PaymentMethodType::Cashapp
| PaymentMethodType::UpiCollect => {
| PaymentMethodType::UpiCollect
| PaymentMethodType::UpiIntent => {
capture_method_not_supported!(connector, capture_method, payment_method_type)
}
},

View File

@ -1,7 +1,8 @@
use std::collections::HashMap;
use api_models::enums::PaymentMethod;
use common_utils::errors::CustomResult;
use common_utils::{errors::CustomResult, ext_traits::Encode};
use error_stack::ResultExt;
use masking::{Secret, SwitchStrategy};
use serde::{Deserialize, Serialize};
@ -84,6 +85,13 @@ pub struct PayerInfo {
token_id: Secret<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum PreferredCheckoutMethod {
Vpa,
Qr,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IatapayPaymentsRequest {
@ -95,7 +103,9 @@ pub struct IatapayPaymentsRequest {
locale: String,
redirect_urls: RedirectUrls,
notification_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
payer_info: Option<PayerInfo>,
preferred_checkout_method: Option<PreferredCheckoutMethod>,
}
impl
@ -136,24 +146,31 @@ impl
| PaymentMethod::GiftCard => item.router_data.get_billing_country()?.to_string(),
};
let return_url = item.router_data.get_return_url()?;
let payer_info = match item.router_data.request.payment_method_data.clone() {
domain::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo {
token_id: id.switch_strategy(),
}),
domain::PaymentMethodData::Card(_)
| domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::PayLater(_)
| domain::PaymentMethodData::BankRedirect(_)
| domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => None,
};
let (payer_info, preferred_checkout_method) =
match item.router_data.request.payment_method_data.clone() {
domain::PaymentMethodData::Upi(upi_type) => match upi_type {
domain::UpiData::UpiCollect(upi_data) => (
upi_data.vpa_id.map(|id| PayerInfo {
token_id: id.switch_strategy(),
}),
Some(PreferredCheckoutMethod::Vpa),
),
domain::UpiData::UpiIntent(_) => (None, Some(PreferredCheckoutMethod::Qr)),
},
domain::PaymentMethodData::Card(_)
| domain::PaymentMethodData::CardRedirect(_)
| domain::PaymentMethodData::Wallet(_)
| domain::PaymentMethodData::PayLater(_)
| domain::PaymentMethodData::BankRedirect(_)
| domain::PaymentMethodData::BankDebit(_)
| domain::PaymentMethodData::BankTransfer(_)
| domain::PaymentMethodData::Crypto(_)
| domain::PaymentMethodData::MandatePayment
| domain::PaymentMethodData::Reward
| domain::PaymentMethodData::Voucher(_)
| domain::PaymentMethodData::GiftCard(_)
| domain::PaymentMethodData::CardToken(_) => (None, None),
};
let payload = Self {
merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)?
.merchant_id,
@ -165,6 +182,7 @@ impl
redirect_urls: get_redirect_url(return_url),
payer_info,
notification_url: item.router_data.request.get_webhook_url()?,
preferred_checkout_method,
};
Ok(payload)
}
@ -291,8 +309,46 @@ fn get_iatpay_response(
};
let connector_response_reference_id = response.merchant_payment_id.or(response.iata_payment_id);
let payment_response_data = response.checkout_methods.map_or(
types::PaymentsResponseData::TransactionResponse {
let payment_response_data = match response.checkout_methods {
Some(checkout_methods) => {
let (connector_metadata, redirection_data) =
match checkout_methods.redirect.redirect_url.ends_with("qr") {
true => {
let qr_code_info = api_models::payments::FetchQrCodeInformation {
qr_code_fetch_url: url::Url::parse(
&checkout_methods.redirect.redirect_url,
)
.change_context(errors::ConnectorError::ResponseHandlingFailed)?,
};
(
Some(qr_code_info.encode_to_value())
.transpose()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?,
None,
)
}
false => (
None,
Some(services::RedirectForm::Form {
endpoint: checkout_methods.redirect.redirect_url,
method: services::Method::Get,
form_fields,
}),
),
};
types::PaymentsResponseData::TransactionResponse {
resource_id: id,
redirection_data,
mandate_reference: None,
connector_metadata,
network_txn_id: None,
connector_response_reference_id: connector_response_reference_id.clone(),
incremental_authorization_allowed: None,
charge_id: None,
}
}
None => types::PaymentsResponseData::TransactionResponse {
resource_id: id.clone(),
redirection_data: None,
mandate_reference: None,
@ -302,21 +358,8 @@ fn get_iatpay_response(
incremental_authorization_allowed: None,
charge_id: None,
},
|checkout_methods| types::PaymentsResponseData::TransactionResponse {
resource_id: id,
redirection_data: Some(services::RedirectForm::Form {
endpoint: checkout_methods.redirect.redirect_url,
method: services::Method::Get,
form_fields,
}),
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: connector_response_reference_id.clone(),
incremental_authorization_allowed: None,
charge_id: None,
},
);
};
Ok((status, error, payment_response_data))
}

View File

@ -398,6 +398,7 @@ impl
| common_enums::PaymentMethodType::Trustly
| common_enums::PaymentMethodType::Twint
| common_enums::PaymentMethodType::UpiCollect
| common_enums::PaymentMethodType::UpiIntent
| common_enums::PaymentMethodType::Venmo
| common_enums::PaymentMethodType::Vipps
| common_enums::PaymentMethodType::Walley

View File

@ -675,6 +675,7 @@ impl TryFrom<enums::PaymentMethodType> for StripePaymentMethodType {
| enums::PaymentMethodType::Paypal
| enums::PaymentMethodType::Pix
| enums::PaymentMethodType::UpiCollect
| enums::PaymentMethodType::UpiIntent
| enums::PaymentMethodType::Cashapp
| enums::PaymentMethodType::Oxxo => Err(errors::ConnectorError::NotImplemented(
connector_util::get_unimplemented_payment_method_error_message("stripe"),

View File

@ -1009,6 +1009,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
api_models::payments::NextActionData::DisplayBankTransferInformation { .. } => None,
api_models::payments::NextActionData::ThirdPartySdkSessionToken { .. } => None,
api_models::payments::NextActionData::QrCodeInformation{..} => None,
api_models::payments::NextActionData::FetchQrCodeInformation{..} => None,
api_models::payments::NextActionData::DisplayVoucherInformation{ .. } => None,
api_models::payments::NextActionData::WaitScreenInformation{..} => None,
api_models::payments::NextActionData::ThreeDsInvoke{..} => None,

View File

@ -2338,7 +2338,7 @@ pub fn validate_payment_method_type_against_payment_method(
),
api_enums::PaymentMethod::Upi => matches!(
payment_method_type,
api_enums::PaymentMethodType::UpiCollect
api_enums::PaymentMethodType::UpiCollect | api_enums::PaymentMethodType::UpiIntent
),
api_enums::PaymentMethod::Voucher => matches!(
payment_method_type,
@ -4252,9 +4252,9 @@ pub fn get_key_params_for_surcharge_details(
)),
api_models::payments::PaymentMethodData::MandatePayment => None,
api_models::payments::PaymentMethodData::Reward => None,
api_models::payments::PaymentMethodData::Upi(_) => Some((
api_models::payments::PaymentMethodData::Upi(upi_data) => Some((
common_enums::PaymentMethod::Upi,
common_enums::PaymentMethodType::UpiCollect,
upi_data.get_payment_method_type(),
None,
)),
api_models::payments::PaymentMethodData::Voucher(voucher) => Some((

View File

@ -541,6 +541,9 @@ where
let papal_sdk_next_action = paypal_sdk_next_steps_check(payment_attempt.clone())?;
let next_action_containing_fetch_qr_code_url =
fetch_qr_code_url_next_steps_check(payment_attempt.clone())?;
let next_action_containing_wait_screen =
wait_screen_next_steps_check(payment_attempt.clone())?;
@ -550,6 +553,7 @@ where
|| next_action_containing_qr_code_url.is_some()
|| next_action_containing_wait_screen.is_some()
|| papal_sdk_next_action.is_some()
|| next_action_containing_fetch_qr_code_url.is_some()
|| payment_data.authentication.is_some()
{
next_action_response = bank_transfer_next_steps
@ -566,6 +570,11 @@ where
.or(next_action_containing_qr_code_url.map(|qr_code_data| {
api_models::payments::NextActionData::foreign_from(qr_code_data)
}))
.or(next_action_containing_fetch_qr_code_url.map(|fetch_qr_code_data| {
api_models::payments::NextActionData::FetchQrCodeInformation {
qr_code_fetch_url: fetch_qr_code_data.qr_code_fetch_url
}
}))
.or(papal_sdk_next_action.map(|paypal_next_action_data| {
api_models::payments::NextActionData::InvokeSdkClient{
next_action_data: paypal_next_action_data
@ -899,6 +908,18 @@ pub fn paypal_sdk_next_steps_check(
Ok(paypal_next_steps)
}
pub fn fetch_qr_code_url_next_steps_check(
payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::FetchQrCodeInformation>> {
let qr_code_steps: Option<Result<api_models::payments::FetchQrCodeInformation, _>> =
payment_attempt
.connector_metadata
.map(|metadata| metadata.parse_value("FetchQrCodeInformation"));
let qr_code_fetch_url = qr_code_steps.transpose().ok().flatten();
Ok(qr_code_fetch_url)
}
pub fn wait_screen_next_steps_check(
payment_attempt: storage::PaymentAttempt,
) -> RouterResult<Option<api_models::payments::WaitScreenInstructions>> {
@ -1108,8 +1129,8 @@ impl ForeignFrom<api_models::payments::QrCodeInformation> for api_models::paymen
display_to_timestamp,
} => Self::QrCodeInformation {
qr_code_url: Some(qr_code_url),
display_to_timestamp,
image_data_url: None,
display_to_timestamp,
},
}
}

View File

@ -5,6 +5,6 @@ pub use hyperswitch_domain_models::payment_method_data::{
GooglePayPaymentMethodInfo, GooglePayRedirectData, GooglePayThirdPartySdkData,
GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData, KakaoPayRedirection,
MbWayRedirection, PayLaterData, PaymentMethodData, SamsungPayWalletData,
SepaAndBacsBillingDetails, SwishQrData, TouchNGoRedirection, VoucherData, WalletData,
WeChatPayQr,
SepaAndBacsBillingDetails, SwishQrData, TouchNGoRedirection, UpiCollectData, UpiData,
UpiIntentData, VoucherData, WalletData, WeChatPayQr,
};

View File

@ -461,7 +461,9 @@ impl ForeignFrom<api_enums::PaymentMethodType> for api_enums::PaymentMethod {
| api_enums::PaymentMethodType::Trustly
| api_enums::PaymentMethodType::Bizum
| api_enums::PaymentMethodType::Interac => Self::BankRedirect,
api_enums::PaymentMethodType::UpiCollect => Self::Upi,
api_enums::PaymentMethodType::UpiCollect | api_enums::PaymentMethodType::UpiIntent => {
Self::Upi
}
api_enums::PaymentMethodType::CryptoCurrency => Self::Crypto,
api_enums::PaymentMethodType::Ach
| api_enums::PaymentMethodType::Sepa