refactor(connector): [cybersource] recurring mandate flow (#3354)

This commit is contained in:
SamraatBansal
2024-01-17 15:28:49 +05:30
committed by GitHub
parent 68a3a28067
commit 387c1c491b
3 changed files with 169 additions and 131 deletions

View File

@ -785,7 +785,10 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
if req.is_three_ds() && req.request.is_card() {
if req.is_three_ds()
&& req.request.is_card()
&& req.request.connector_mandate_id().is_none()
{
Ok(format!(
"{}risk/v1/authentication-setups",
api::ConnectorCommon::base_url(self, connectors)
@ -809,7 +812,10 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
req.request.amount,
req,
))?;
if req.is_three_ds() && req.request.is_card() {
if req.is_three_ds()
&& req.request.is_card()
&& req.request.connector_mandate_id().is_none()
{
let connector_req =
cybersource::CybersourceAuthSetupRequest::try_from(&connector_router_data)?;
Ok(RequestContent::Json(Box::new(connector_req)))
@ -845,7 +851,10 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
data: &types::PaymentsAuthorizeRouterData,
res: types::Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
if data.is_three_ds() && data.request.is_card() {
if data.is_three_ds()
&& data.request.is_card()
&& data.request.connector_mandate_id().is_none()
{
let response: cybersource::CybersourceAuthSetupResponse = res
.response
.parse_struct("Cybersource AuthSetupResponse")

View File

@ -342,7 +342,7 @@ pub struct ApplePayPaymentInformation {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MandatePaymentInformation {
payment_instrument: Option<CybersoucrePaymentInstrument>,
payment_instrument: CybersoucrePaymentInstrument,
}
#[derive(Debug, Serialize)]
@ -482,7 +482,7 @@ impl
),
) -> Self {
let (action_list, action_token_types, authorization_options) =
if item.router_data.request.setup_future_usage.is_some() {
if item.router_data.request.setup_mandate_details.is_some() {
(
Some(vec![CybersourceActionsList::TokenCreate]),
Some(vec![CybersourceActionsTokenType::PaymentInstrument]),
@ -871,139 +871,168 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>>
fn try_from(
item: &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>,
) -> Result<Self, Self::Error> {
match item.router_data.request.payment_method_data.clone() {
payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
payments::WalletData::ApplePay(apple_pay_data) => {
match item.router_data.payment_method_token.clone() {
Some(payment_method_token) => match payment_method_token {
types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
Self::try_from((item, decrypt_data))
}
types::PaymentMethodToken::Token(_) => {
Err(errors::ConnectorError::InvalidWalletToken)?
}
},
None => {
let email = item.router_data.request.get_email()?;
let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
let order_information = OrderInformationWithBill::from((item, bill_to));
let processing_information = ProcessingInformation::from((
item,
Some(PaymentSolution::ApplePay),
));
let client_reference_information =
ClientReferenceInformation::from(item);
let payment_information = PaymentInformation::ApplePayToken(
ApplePayTokenPaymentInformation {
fluid_data: FluidData {
value: Secret::from(apple_pay_data.payment_data),
},
tokenized_card: ApplePayTokenizedCard {
transaction_type: TransactionType::ApplePay,
},
match item.router_data.request.connector_mandate_id() {
Some(connector_mandate_id) => Self::try_from((item, connector_mandate_id)),
None => {
match item.router_data.request.payment_method_data.clone() {
payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)),
payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data {
payments::WalletData::ApplePay(apple_pay_data) => {
match item.router_data.payment_method_token.clone() {
Some(payment_method_token) => match payment_method_token {
types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => {
Self::try_from((item, decrypt_data))
}
types::PaymentMethodToken::Token(_) => {
Err(errors::ConnectorError::InvalidWalletToken)?
}
},
);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(
metadata.peek().to_owned(),
)
});
None => {
let email = item.router_data.request.get_email()?;
let bill_to =
build_bill_to(item.router_data.get_billing()?, email)?;
let order_information =
OrderInformationWithBill::from((item, bill_to));
let processing_information = ProcessingInformation::from((
item,
Some(PaymentSolution::ApplePay),
));
let client_reference_information =
ClientReferenceInformation::from(item);
let payment_information = PaymentInformation::ApplePayToken(
ApplePayTokenPaymentInformation {
fluid_data: FluidData {
value: Secret::from(apple_pay_data.payment_data),
},
tokenized_card: ApplePayTokenizedCard {
transaction_type: TransactionType::ApplePay,
},
},
);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(
metadata.peek().to_owned(),
)
});
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
consumer_authentication_information: None,
})
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
consumer_authentication_information: None,
})
}
}
}
payments::WalletData::GooglePay(google_pay_data) => {
Self::try_from((item, google_pay_data))
}
payments::WalletData::AliPayQr(_)
| payments::WalletData::AliPayRedirect(_)
| payments::WalletData::AliPayHkRedirect(_)
| payments::WalletData::MomoRedirect(_)
| payments::WalletData::KakaoPayRedirect(_)
| payments::WalletData::GoPayRedirect(_)
| payments::WalletData::GcashRedirect(_)
| payments::WalletData::ApplePayRedirect(_)
| payments::WalletData::ApplePayThirdPartySdk(_)
| payments::WalletData::DanaRedirect {}
| payments::WalletData::GooglePayRedirect(_)
| payments::WalletData::GooglePayThirdPartySdk(_)
| payments::WalletData::MbWayRedirect(_)
| payments::WalletData::MobilePayRedirect(_)
| payments::WalletData::PaypalRedirect(_)
| payments::WalletData::PaypalSdk(_)
| payments::WalletData::SamsungPay(_)
| payments::WalletData::TwintRedirect {}
| payments::WalletData::VippsRedirect {}
| payments::WalletData::TouchNGoRedirect(_)
| payments::WalletData::WeChatPayRedirect(_)
| payments::WalletData::WeChatPayQr(_)
| payments::WalletData::CashappQr(_)
| payments::WalletData::SwishQr(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message(
"Cybersource",
),
)
.into())
}
},
// If connector_mandate_id is present MandatePayment will be the PMD, the case will be handled in the first `if` clause.
// This is a fallback implementation in the event of catastrophe.
payments::PaymentMethodData::MandatePayment => {
let connector_mandate_id =
item.router_data.request.connector_mandate_id().ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "connector_mandate_id",
},
)?;
Self::try_from((item, connector_mandate_id))
}
payments::PaymentMethodData::CardRedirect(_)
| payments::PaymentMethodData::PayLater(_)
| payments::PaymentMethodData::BankRedirect(_)
| payments::PaymentMethodData::BankDebit(_)
| payments::PaymentMethodData::BankTransfer(_)
| payments::PaymentMethodData::Crypto(_)
| payments::PaymentMethodData::Reward
| payments::PaymentMethodData::Upi(_)
| payments::PaymentMethodData::Voucher(_)
| payments::PaymentMethodData::GiftCard(_)
| payments::PaymentMethodData::CardToken(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Cybersource"),
)
.into())
}
}
payments::WalletData::GooglePay(google_pay_data) => {
Self::try_from((item, google_pay_data))
}
payments::WalletData::AliPayQr(_)
| payments::WalletData::AliPayRedirect(_)
| payments::WalletData::AliPayHkRedirect(_)
| payments::WalletData::MomoRedirect(_)
| payments::WalletData::KakaoPayRedirect(_)
| payments::WalletData::GoPayRedirect(_)
| payments::WalletData::GcashRedirect(_)
| payments::WalletData::ApplePayRedirect(_)
| payments::WalletData::ApplePayThirdPartySdk(_)
| payments::WalletData::DanaRedirect {}
| payments::WalletData::GooglePayRedirect(_)
| payments::WalletData::GooglePayThirdPartySdk(_)
| payments::WalletData::MbWayRedirect(_)
| payments::WalletData::MobilePayRedirect(_)
| payments::WalletData::PaypalRedirect(_)
| payments::WalletData::PaypalSdk(_)
| payments::WalletData::SamsungPay(_)
| payments::WalletData::TwintRedirect {}
| payments::WalletData::VippsRedirect {}
| payments::WalletData::TouchNGoRedirect(_)
| payments::WalletData::WeChatPayRedirect(_)
| payments::WalletData::WeChatPayQr(_)
| payments::WalletData::CashappQr(_)
| payments::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Cybersource"),
)
.into()),
},
payments::PaymentMethodData::MandatePayment => {
let processing_information = ProcessingInformation::from((item, None));
let payment_instrument =
item.router_data
.request
.connector_mandate_id()
.map(|mandate_token_id| CybersoucrePaymentInstrument {
id: mandate_token_id,
});
let email = item.router_data.request.get_email()?;
let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
let order_information = OrderInformationWithBill::from((item, bill_to));
let payment_information =
PaymentInformation::MandatePayment(MandatePaymentInformation {
payment_instrument,
});
let client_reference_information = ClientReferenceInformation::from(item);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
});
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
consumer_authentication_information: None,
})
}
payments::PaymentMethodData::CardRedirect(_)
| payments::PaymentMethodData::PayLater(_)
| payments::PaymentMethodData::BankRedirect(_)
| payments::PaymentMethodData::BankDebit(_)
| payments::PaymentMethodData::BankTransfer(_)
| payments::PaymentMethodData::Crypto(_)
| payments::PaymentMethodData::Reward
| payments::PaymentMethodData::Upi(_)
| payments::PaymentMethodData::Voucher(_)
| payments::PaymentMethodData::GiftCard(_)
| payments::PaymentMethodData::CardToken(_) => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Cybersource"),
)
.into())
}
}
}
}
impl
TryFrom<(
&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>,
String,
)> for CybersourcePaymentsRequest
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
(item, connector_mandate_id): (
&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>,
String,
),
) -> Result<Self, Self::Error> {
let processing_information = ProcessingInformation::from((item, None));
let payment_instrument = CybersoucrePaymentInstrument {
id: connector_mandate_id,
};
let email = item.router_data.request.get_email()?;
let bill_to = build_bill_to(item.router_data.get_billing()?, email)?;
let order_information = OrderInformationWithBill::from((item, bill_to));
let payment_information =
PaymentInformation::MandatePayment(MandatePaymentInformation { payment_instrument });
let client_reference_information = ClientReferenceInformation::from(item);
let merchant_defined_information =
item.router_data.request.metadata.clone().map(|metadata| {
Vec::<MerchantDefinedInformation>::foreign_from(metadata.peek().to_owned())
});
Ok(Self {
processing_information,
payment_information,
order_information,
client_reference_information,
merchant_defined_information,
consumer_authentication_information: None,
})
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CybersourceAuthSetupRequest {

View File

@ -1524,12 +1524,12 @@ pub fn build_redirection_form(
// This is the iframe recommended by cybersource but the redirection happens inside this iframe once otp
// is received and we lose control of the redirection on user client browser, so to avoid that we have removed this iframe and directly consumed it.
// (PreEscaped(r#"<iframe id="step_up_iframe" style="border: none; margin-left: auto; margin-right: auto; display: block" height="800px" width="400px" name="stepUpIframe"></iframe>"#))
(PreEscaped(format!("<form id=\"step_up_form\" method=\"POST\" action=\"{step_up_url}\">
<input id=\"step_up_form_jwt_input\" type=\"hidden\" name=\"JWT\" value=\"{access_token}\">
(PreEscaped(format!("<form id=\"step-up-form\" method=\"POST\" action=\"{step_up_url}\">
<input type=\"hidden\" name=\"JWT\" value=\"{access_token}\">
</form>")))
(PreEscaped(r#"<script>
window.onload = function() {
var stepUpForm = document.querySelector('#step_up_form'); if(stepUpForm) stepUpForm.submit();
var stepUpForm = document.querySelector('#step-up-form'); if(stepUpForm) stepUpForm.submit();
}
</script>"#))
}}