mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 04:04:43 +08:00
refactor(connector): [cybersource] recurring mandate flow (#3354)
This commit is contained in:
@ -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")
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>"#))
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user