mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 09:38:33 +08:00
feat(connector): [Cybersource] Add payout flows for Card (#4511)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -348,6 +348,7 @@ pub enum PayoutConnectors {
|
|||||||
Wise,
|
Wise,
|
||||||
Paypal,
|
Paypal,
|
||||||
Ebanx,
|
Ebanx,
|
||||||
|
Cybersource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
@ -359,6 +360,7 @@ impl From<PayoutConnectors> for RoutableConnectors {
|
|||||||
PayoutConnectors::Wise => Self::Wise,
|
PayoutConnectors::Wise => Self::Wise,
|
||||||
PayoutConnectors::Paypal => Self::Paypal,
|
PayoutConnectors::Paypal => Self::Paypal,
|
||||||
PayoutConnectors::Ebanx => Self::Ebanx,
|
PayoutConnectors::Ebanx => Self::Ebanx,
|
||||||
|
PayoutConnectors::Cybersource => Self::Cybersource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -372,6 +374,7 @@ impl From<PayoutConnectors> for Connector {
|
|||||||
PayoutConnectors::Wise => Self::Wise,
|
PayoutConnectors::Wise => Self::Wise,
|
||||||
PayoutConnectors::Paypal => Self::Paypal,
|
PayoutConnectors::Paypal => Self::Paypal,
|
||||||
PayoutConnectors::Ebanx => Self::Ebanx,
|
PayoutConnectors::Ebanx => Self::Ebanx,
|
||||||
|
PayoutConnectors::Cybersource => Self::Cybersource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -386,6 +389,7 @@ impl TryFrom<Connector> for PayoutConnectors {
|
|||||||
Connector::Wise => Ok(Self::Wise),
|
Connector::Wise => Ok(Self::Wise),
|
||||||
Connector::Paypal => Ok(Self::Paypal),
|
Connector::Paypal => Ok(Self::Paypal),
|
||||||
Connector::Ebanx => Ok(Self::Ebanx),
|
Connector::Ebanx => Ok(Self::Ebanx),
|
||||||
|
Connector::Cybersource => Ok(Self::Cybersource),
|
||||||
_ => Err(format!("Invalid payout connector {}", value)),
|
_ => Err(format!("Invalid payout connector {}", value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,6 +133,8 @@ pub struct ConnectorConfig {
|
|||||||
pub coinbase: Option<ConnectorTomlConfig>,
|
pub coinbase: Option<ConnectorTomlConfig>,
|
||||||
pub cryptopay: Option<ConnectorTomlConfig>,
|
pub cryptopay: Option<ConnectorTomlConfig>,
|
||||||
pub cybersource: Option<ConnectorTomlConfig>,
|
pub cybersource: Option<ConnectorTomlConfig>,
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
pub cybersource_payout: Option<ConnectorTomlConfig>,
|
||||||
pub iatapay: Option<ConnectorTomlConfig>,
|
pub iatapay: Option<ConnectorTomlConfig>,
|
||||||
pub opennode: Option<ConnectorTomlConfig>,
|
pub opennode: Option<ConnectorTomlConfig>,
|
||||||
pub bambora: Option<ConnectorTomlConfig>,
|
pub bambora: Option<ConnectorTomlConfig>,
|
||||||
@ -223,6 +225,7 @@ impl ConnectorConfig {
|
|||||||
PayoutConnectors::Wise => Ok(connector_data.wise_payout),
|
PayoutConnectors::Wise => Ok(connector_data.wise_payout),
|
||||||
PayoutConnectors::Paypal => Ok(connector_data.paypal_payout),
|
PayoutConnectors::Paypal => Ok(connector_data.paypal_payout),
|
||||||
PayoutConnectors::Ebanx => Ok(connector_data.ebanx_payout),
|
PayoutConnectors::Ebanx => Ok(connector_data.ebanx_payout),
|
||||||
|
PayoutConnectors::Cybersource => Ok(connector_data.cybersource_payout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -304,6 +304,10 @@ impl api::PaymentsPreProcessing for Cybersource {}
|
|||||||
impl api::PaymentsCompleteAuthorize for Cybersource {}
|
impl api::PaymentsCompleteAuthorize for Cybersource {}
|
||||||
impl api::ConnectorMandateRevoke for Cybersource {}
|
impl api::ConnectorMandateRevoke for Cybersource {}
|
||||||
|
|
||||||
|
impl api::Payouts for Cybersource {}
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl api::PayoutFulfill for Cybersource {}
|
||||||
|
|
||||||
impl
|
impl
|
||||||
ConnectorIntegration<
|
ConnectorIntegration<
|
||||||
api::PaymentMethodToken,
|
api::PaymentMethodToken,
|
||||||
@ -965,6 +969,124 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::PayoutsResponseData>
|
||||||
|
for Cybersource
|
||||||
|
{
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
_req: &types::PayoutsRouterData<api::PoFulfill>,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
Ok(format!("{}pts/v2/payouts", self.base_url(connectors)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PayoutsRouterData<api::PoFulfill>,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PayoutsRouterData<api::PoFulfill>,
|
||||||
|
_connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||||
|
let connector_router_data = cybersource::CybersourceRouterData::try_from((
|
||||||
|
&self.get_currency_unit(),
|
||||||
|
req.request.destination_currency,
|
||||||
|
req.request.amount,
|
||||||
|
req,
|
||||||
|
))?;
|
||||||
|
let connector_req =
|
||||||
|
cybersource::CybersourcePayoutFulfillRequest::try_from(&connector_router_data)?;
|
||||||
|
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PayoutsRouterData<api::PoFulfill>,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
let request = services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PayoutFulfillType::get_url(self, req, connectors)?)
|
||||||
|
.attach_default_headers()
|
||||||
|
.headers(types::PayoutFulfillType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.set_body(types::PayoutFulfillType::get_request_body(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Ok(Some(request))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PayoutsRouterData<api::PoFulfill>,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PayoutsRouterData<api::PoFulfill>, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourceFulfillResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("CybersourceFulfillResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
|
||||||
|
event_builder.map(|i| i.set_response_body(&response));
|
||||||
|
router_env::logger::info!(connector_response=?response);
|
||||||
|
|
||||||
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: types::Response,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res, event_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_5xx_error_response(
|
||||||
|
&self,
|
||||||
|
res: types::Response,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourceServerErrorResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("CybersourceServerErrorResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
|
||||||
|
event_builder.map(|event| event.set_response_body(&response));
|
||||||
|
router_env::logger::info!(error_response=?response);
|
||||||
|
|
||||||
|
let attempt_status = match response.reason {
|
||||||
|
Some(reason) => match reason {
|
||||||
|
transformers::Reason::SystemError => Some(enums::AttemptStatus::Failure),
|
||||||
|
transformers::Reason::ServerTimeout | transformers::Reason::ServiceTimeout => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
Ok(types::ErrorResponse {
|
||||||
|
status_code: res.status_code,
|
||||||
|
reason: response.status.clone(),
|
||||||
|
code: response.status.unwrap_or(consts::NO_ERROR_CODE.to_string()),
|
||||||
|
message: response
|
||||||
|
.message
|
||||||
|
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
|
||||||
|
attempt_status,
|
||||||
|
connector_transaction_id: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl
|
impl
|
||||||
ConnectorIntegration<
|
ConnectorIntegration<
|
||||||
api::CompleteAuthorize,
|
api::CompleteAuthorize,
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
use api_models::payments;
|
use api_models::payments;
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
use api_models::{
|
||||||
|
payments::{AddressDetails, PhoneDetails},
|
||||||
|
payouts::PayoutMethodData,
|
||||||
|
};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use common_enums::FutureUsage;
|
use common_enums::FutureUsage;
|
||||||
use common_utils::{ext_traits::ValueExt, pii};
|
use common_utils::{ext_traits::ValueExt, pii};
|
||||||
@ -111,7 +116,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest {
|
|||||||
number: ccard.card_number,
|
number: ccard.card_number,
|
||||||
expiration_month: ccard.card_exp_month,
|
expiration_month: ccard.card_exp_month,
|
||||||
expiration_year: ccard.card_exp_year,
|
expiration_year: ccard.card_exp_year,
|
||||||
security_code: ccard.card_cvc,
|
security_code: Some(ccard.card_cvc),
|
||||||
card_type,
|
card_type,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -404,7 +409,7 @@ pub struct Card {
|
|||||||
number: cards::CardNumber,
|
number: cards::CardNumber,
|
||||||
expiration_month: Secret<String>,
|
expiration_month: Secret<String>,
|
||||||
expiration_year: Secret<String>,
|
expiration_year: Secret<String>,
|
||||||
security_code: Secret<String>,
|
security_code: Option<Secret<String>>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
card_type: Option<String>,
|
card_type: Option<String>,
|
||||||
}
|
}
|
||||||
@ -849,7 +854,7 @@ impl
|
|||||||
number: ccard.card_number,
|
number: ccard.card_number,
|
||||||
expiration_month: ccard.card_exp_month,
|
expiration_month: ccard.card_exp_month,
|
||||||
expiration_year: ccard.card_exp_year,
|
expiration_year: ccard.card_exp_year,
|
||||||
security_code: ccard.card_cvc,
|
security_code: Some(ccard.card_cvc),
|
||||||
card_type: card_type.clone(),
|
card_type: card_type.clone(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -900,7 +905,7 @@ impl
|
|||||||
number: ccard.card_number,
|
number: ccard.card_number,
|
||||||
expiration_month: ccard.card_exp_month,
|
expiration_month: ccard.card_exp_month,
|
||||||
expiration_year: ccard.card_exp_year,
|
expiration_year: ccard.card_exp_year,
|
||||||
security_code: ccard.card_cvc,
|
security_code: Some(ccard.card_cvc),
|
||||||
card_type,
|
card_type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -1278,7 +1283,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>>
|
|||||||
number: ccard.card_number,
|
number: ccard.card_number,
|
||||||
expiration_month: ccard.card_exp_month,
|
expiration_month: ccard.card_exp_month,
|
||||||
expiration_year: ccard.card_exp_year,
|
expiration_year: ccard.card_exp_year,
|
||||||
security_code: ccard.card_cvc,
|
security_code: Some(ccard.card_cvc),
|
||||||
card_type,
|
card_type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -1982,7 +1987,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>>
|
|||||||
number: ccard.card_number,
|
number: ccard.card_number,
|
||||||
expiration_month: ccard.card_exp_month,
|
expiration_month: ccard.card_exp_month,
|
||||||
expiration_year: ccard.card_exp_year,
|
expiration_year: ccard.card_exp_year,
|
||||||
security_code: ccard.card_cvc,
|
security_code: Some(ccard.card_cvc),
|
||||||
card_type,
|
card_type,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@ -2775,6 +2780,223 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, CybersourceRsyncRespon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourcePayoutFulfillRequest {
|
||||||
|
client_reference_information: ClientReferenceInformation,
|
||||||
|
order_information: OrderInformation,
|
||||||
|
recipient_information: CybersourceRecipientInfo,
|
||||||
|
sender_information: CybersourceSenderInfo,
|
||||||
|
processing_information: CybersourceProcessingInfo,
|
||||||
|
payment_information: PaymentInformation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceRecipientInfo {
|
||||||
|
first_name: Secret<String>,
|
||||||
|
last_name: Secret<String>,
|
||||||
|
address1: Secret<String>,
|
||||||
|
locality: String,
|
||||||
|
administrative_area: Secret<String>,
|
||||||
|
postal_code: Secret<String>,
|
||||||
|
country: api_enums::CountryAlpha2,
|
||||||
|
phone_number: Option<Secret<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceSenderInfo {
|
||||||
|
reference_number: String,
|
||||||
|
account: CybersourceAccountInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceAccountInfo {
|
||||||
|
funds_source: CybersourcePayoutFundSourceType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub enum CybersourcePayoutFundSourceType {
|
||||||
|
#[serde(rename = "05")]
|
||||||
|
Disbursement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceProcessingInfo {
|
||||||
|
business_application_id: CybersourcePayoutBusinessType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub enum CybersourcePayoutBusinessType {
|
||||||
|
#[serde(rename = "PP")]
|
||||||
|
PersonToPerson,
|
||||||
|
#[serde(rename = "AA")]
|
||||||
|
AccountToAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl TryFrom<&CybersourceRouterData<&types::PayoutsRouterData<api::PoFulfill>>>
|
||||||
|
for CybersourcePayoutFulfillRequest
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: &CybersourceRouterData<&types::PayoutsRouterData<api::PoFulfill>>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
match item.router_data.request.payout_type {
|
||||||
|
enums::PayoutType::Card => {
|
||||||
|
let client_reference_information = ClientReferenceInformation {
|
||||||
|
code: Some(item.router_data.request.payout_id.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let order_information = OrderInformation {
|
||||||
|
amount_details: Amount {
|
||||||
|
total_amount: item.amount.to_owned(),
|
||||||
|
currency: item.router_data.request.destination_currency,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let billing_address = item.router_data.get_billing_address()?;
|
||||||
|
let phone_address = item.router_data.get_billing_phone()?;
|
||||||
|
let recipient_information =
|
||||||
|
CybersourceRecipientInfo::try_from((billing_address, phone_address))?;
|
||||||
|
|
||||||
|
let sender_information = CybersourceSenderInfo {
|
||||||
|
reference_number: item.router_data.request.payout_id.clone(),
|
||||||
|
account: CybersourceAccountInfo {
|
||||||
|
funds_source: CybersourcePayoutFundSourceType::Disbursement,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let processing_information = CybersourceProcessingInfo {
|
||||||
|
business_application_id: CybersourcePayoutBusinessType::PersonToPerson, // this means sender and receiver are different
|
||||||
|
};
|
||||||
|
|
||||||
|
let payout_method_data = item.router_data.get_payout_method_data()?;
|
||||||
|
let payment_information = PaymentInformation::try_from(payout_method_data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client_reference_information,
|
||||||
|
order_information,
|
||||||
|
recipient_information,
|
||||||
|
sender_information,
|
||||||
|
processing_information,
|
||||||
|
payment_information,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
enums::PayoutType::Bank | enums::PayoutType::Wallet => {
|
||||||
|
Err(errors::ConnectorError::NotSupported {
|
||||||
|
message: "PayoutType is not supported".to_string(),
|
||||||
|
connector: "Cybersource",
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl TryFrom<(&AddressDetails, &PhoneDetails)> for CybersourceRecipientInfo {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(item: (&AddressDetails, &PhoneDetails)) -> Result<Self, Self::Error> {
|
||||||
|
let (billing_address, phone_address) = item;
|
||||||
|
Ok(Self {
|
||||||
|
first_name: billing_address.get_first_name()?.to_owned(),
|
||||||
|
last_name: billing_address.get_last_name()?.to_owned(),
|
||||||
|
address1: billing_address.get_line1()?.to_owned(),
|
||||||
|
locality: billing_address.get_city()?.to_owned(),
|
||||||
|
administrative_area: billing_address.get_state()?.to_owned(),
|
||||||
|
postal_code: billing_address.get_zip()?.to_owned(),
|
||||||
|
country: billing_address.get_country()?.to_owned(),
|
||||||
|
phone_number: phone_address.number.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl TryFrom<PayoutMethodData> for PaymentInformation {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(item: PayoutMethodData) -> Result<Self, Self::Error> {
|
||||||
|
match item {
|
||||||
|
PayoutMethodData::Card(card_details) => {
|
||||||
|
let card_issuer = card_details.get_card_issuer().ok();
|
||||||
|
let card_type = card_issuer.map(String::from);
|
||||||
|
let card = Card {
|
||||||
|
number: card_details.card_number,
|
||||||
|
expiration_month: card_details.expiry_month,
|
||||||
|
expiration_year: card_details.expiry_year,
|
||||||
|
security_code: None,
|
||||||
|
card_type,
|
||||||
|
};
|
||||||
|
Ok(Self::Cards(CardPaymentInformation { card }))
|
||||||
|
}
|
||||||
|
PayoutMethodData::Bank(_) | PayoutMethodData::Wallet(_) => {
|
||||||
|
Err(errors::ConnectorError::NotSupported {
|
||||||
|
message: "PayoutMethod is not supported".to_string(),
|
||||||
|
connector: "Cybersource",
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceFulfillResponse {
|
||||||
|
id: String,
|
||||||
|
status: CybersourcePayoutStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum CybersourcePayoutStatus {
|
||||||
|
Accepted,
|
||||||
|
Declined,
|
||||||
|
InvalidRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl ForeignFrom<CybersourcePayoutStatus> for enums::PayoutStatus {
|
||||||
|
fn foreign_from(status: CybersourcePayoutStatus) -> Self {
|
||||||
|
match status {
|
||||||
|
CybersourcePayoutStatus::Accepted => Self::Success,
|
||||||
|
CybersourcePayoutStatus::Declined | CybersourcePayoutStatus::InvalidRequest => {
|
||||||
|
Self::Failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl<F> TryFrom<types::PayoutsResponseRouterData<F, CybersourceFulfillResponse>>
|
||||||
|
for types::PayoutsRouterData<F>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::PayoutsResponseRouterData<F, CybersourceFulfillResponse>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
response: Ok(types::PayoutsResponseData {
|
||||||
|
status: Some(enums::PayoutStatus::foreign_from(item.response.status)),
|
||||||
|
connector_payout_id: item.response.id,
|
||||||
|
payout_eligible: None,
|
||||||
|
should_add_next_step_to_process_tracker: false,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CybersourceStandardErrorResponse {
|
pub struct CybersourceStandardErrorResponse {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[cfg(feature = "payouts")]
|
#[cfg(feature = "payouts")]
|
||||||
use api_models::payouts::PayoutVendorAccountDetails;
|
use api_models::payouts::{self, PayoutVendorAccountDetails};
|
||||||
use api_models::{
|
use api_models::{
|
||||||
enums::{CanadaStatesAbbreviation, UsStatesAbbreviation},
|
enums::{CanadaStatesAbbreviation, UsStatesAbbreviation},
|
||||||
payments::{self, OrderDetailsWithAmount},
|
payments::{self, OrderDetailsWithAmount},
|
||||||
@ -1078,6 +1078,80 @@ pub trait CardData {
|
|||||||
fn get_expiry_year_as_i32(&self) -> Result<Secret<i32>, Error>;
|
fn get_expiry_year_as_i32(&self) -> Result<Secret<i32>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "payouts")]
|
||||||
|
impl CardData for payouts::Card {
|
||||||
|
fn get_card_expiry_year_2_digit(&self) -> Result<Secret<String>, errors::ConnectorError> {
|
||||||
|
let binding = self.expiry_year.clone();
|
||||||
|
let year = binding.peek();
|
||||||
|
Ok(Secret::new(
|
||||||
|
year.get(year.len() - 2..)
|
||||||
|
.ok_or(errors::ConnectorError::RequestEncodingFailed)?
|
||||||
|
.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn get_card_issuer(&self) -> Result<CardIssuer, Error> {
|
||||||
|
get_card_issuer(self.card_number.peek())
|
||||||
|
}
|
||||||
|
fn get_card_expiry_month_year_2_digit_with_delimiter(
|
||||||
|
&self,
|
||||||
|
delimiter: String,
|
||||||
|
) -> Result<Secret<String>, errors::ConnectorError> {
|
||||||
|
let year = self.get_card_expiry_year_2_digit()?;
|
||||||
|
Ok(Secret::new(format!(
|
||||||
|
"{}{}{}",
|
||||||
|
self.expiry_month.peek(),
|
||||||
|
delimiter,
|
||||||
|
year.peek()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret<String> {
|
||||||
|
let year = self.get_expiry_year_4_digit();
|
||||||
|
Secret::new(format!(
|
||||||
|
"{}{}{}",
|
||||||
|
year.peek(),
|
||||||
|
delimiter,
|
||||||
|
self.expiry_month.peek()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret<String> {
|
||||||
|
let year = self.get_expiry_year_4_digit();
|
||||||
|
Secret::new(format!(
|
||||||
|
"{}{}{}",
|
||||||
|
self.expiry_month.peek(),
|
||||||
|
delimiter,
|
||||||
|
year.peek()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn get_expiry_year_4_digit(&self) -> Secret<String> {
|
||||||
|
let mut year = self.expiry_year.peek().clone();
|
||||||
|
if year.len() == 2 {
|
||||||
|
year = format!("20{}", year);
|
||||||
|
}
|
||||||
|
Secret::new(year)
|
||||||
|
}
|
||||||
|
fn get_expiry_date_as_yymm(&self) -> Result<Secret<String>, errors::ConnectorError> {
|
||||||
|
let year = self.get_card_expiry_year_2_digit()?.expose();
|
||||||
|
let month = self.expiry_month.clone().expose();
|
||||||
|
Ok(Secret::new(format!("{year}{month}")))
|
||||||
|
}
|
||||||
|
fn get_expiry_month_as_i8(&self) -> Result<Secret<i8>, Error> {
|
||||||
|
self.expiry_month
|
||||||
|
.peek()
|
||||||
|
.clone()
|
||||||
|
.parse::<i8>()
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
||||||
|
.map(Secret::new)
|
||||||
|
}
|
||||||
|
fn get_expiry_year_as_i32(&self) -> Result<Secret<i32>, Error> {
|
||||||
|
self.expiry_year
|
||||||
|
.peek()
|
||||||
|
.clone()
|
||||||
|
.parse::<i32>()
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)
|
||||||
|
.map(Secret::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CardData for domain::Card {
|
impl CardData for domain::Card {
|
||||||
fn get_card_expiry_year_2_digit(&self) -> Result<Secret<String>, errors::ConnectorError> {
|
fn get_card_expiry_year_2_digit(&self) -> Result<Secret<String>, errors::ConnectorError> {
|
||||||
let binding = self.card_exp_year.clone();
|
let binding = self.card_exp_year.clone();
|
||||||
|
|||||||
@ -975,7 +975,6 @@ default_imp_for_payouts!(
|
|||||||
connector::Cashtocode,
|
connector::Cashtocode,
|
||||||
connector::Checkout,
|
connector::Checkout,
|
||||||
connector::Cryptopay,
|
connector::Cryptopay,
|
||||||
connector::Cybersource,
|
|
||||||
connector::Coinbase,
|
connector::Coinbase,
|
||||||
connector::Dlocal,
|
connector::Dlocal,
|
||||||
connector::Fiserv,
|
connector::Fiserv,
|
||||||
@ -1232,7 +1231,6 @@ default_imp_for_payouts_fulfill!(
|
|||||||
connector::Cashtocode,
|
connector::Cashtocode,
|
||||||
connector::Checkout,
|
connector::Checkout,
|
||||||
connector::Cryptopay,
|
connector::Cryptopay,
|
||||||
connector::Cybersource,
|
|
||||||
connector::Coinbase,
|
connector::Coinbase,
|
||||||
connector::Dlocal,
|
connector::Dlocal,
|
||||||
connector::Fiserv,
|
connector::Fiserv,
|
||||||
|
|||||||
@ -15721,7 +15721,8 @@
|
|||||||
"stripe",
|
"stripe",
|
||||||
"wise",
|
"wise",
|
||||||
"paypal",
|
"paypal",
|
||||||
"ebanx"
|
"ebanx",
|
||||||
|
"cybersource"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"PayoutCreateRequest": {
|
"PayoutCreateRequest": {
|
||||||
|
|||||||
Reference in New Issue
Block a user