mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(connector): add support for capture, void, psync, refund and rsync for cybersource (#381)
This commit is contained in:
@ -14,8 +14,12 @@ use crate::{
|
|||||||
configs::settings,
|
configs::settings,
|
||||||
consts,
|
consts,
|
||||||
core::errors::{self, CustomResult},
|
core::errors::{self, CustomResult},
|
||||||
headers, logger, services,
|
headers, logger,
|
||||||
types::{self, api},
|
services::{self, ConnectorIntegration},
|
||||||
|
types::{
|
||||||
|
self,
|
||||||
|
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||||
|
},
|
||||||
utils::{self, BytesExt},
|
utils::{self, BytesExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,23 +37,25 @@ impl Cybersource {
|
|||||||
auth: cybersource::CybersourceAuthType,
|
auth: cybersource::CybersourceAuthType,
|
||||||
host: String,
|
host: String,
|
||||||
resource: &str,
|
resource: &str,
|
||||||
payload: &str,
|
payload: &String,
|
||||||
date: OffsetDateTime,
|
date: OffsetDateTime,
|
||||||
|
http_method: services::Method,
|
||||||
) -> CustomResult<String, errors::ConnectorError> {
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
let cybersource::CybersourceAuthType {
|
let cybersource::CybersourceAuthType {
|
||||||
api_key,
|
api_key,
|
||||||
merchant_account,
|
merchant_account,
|
||||||
api_secret,
|
api_secret,
|
||||||
} = auth;
|
} = auth;
|
||||||
|
let is_post_method = matches!(http_method, services::Method::Post);
|
||||||
let headers_for_post_method = "host date (request-target) digest v-c-merchant-id";
|
let digest_str = if is_post_method { "digest " } else { "" };
|
||||||
|
let headers = format!("host date (request-target) {digest_str}v-c-merchant-id");
|
||||||
|
let request_target = if is_post_method {
|
||||||
|
format!("(request-target): post {resource}\ndigest: SHA-256={payload}\n")
|
||||||
|
} else {
|
||||||
|
format!("(request-target): get {resource}\n")
|
||||||
|
};
|
||||||
let signature_string = format!(
|
let signature_string = format!(
|
||||||
"host: {host}\n\
|
"host: {host}\ndate: {date}\n{request_target}v-c-merchant-id: {merchant_account}"
|
||||||
date: {date}\n\
|
|
||||||
(request-target): post {resource}\n\
|
|
||||||
digest: SHA-256={}\n\
|
|
||||||
v-c-merchant-id: {merchant_account}",
|
|
||||||
self.generate_digest(payload.as_bytes())
|
|
||||||
);
|
);
|
||||||
let key_value = consts::BASE64_ENGINE
|
let key_value = consts::BASE64_ENGINE
|
||||||
.decode(api_secret)
|
.decode(api_secret)
|
||||||
@ -59,25 +65,97 @@ impl Cybersource {
|
|||||||
let signature_value =
|
let signature_value =
|
||||||
consts::BASE64_ENGINE.encode(hmac::sign(&key, signature_string.as_bytes()).as_ref());
|
consts::BASE64_ENGINE.encode(hmac::sign(&key, signature_string.as_bytes()).as_ref());
|
||||||
let signature_header = format!(
|
let signature_header = format!(
|
||||||
r#"keyid="{api_key}", algorithm="HmacSHA256", headers="{headers_for_post_method}", signature="{signature_value}""#
|
r#"keyid="{api_key}", algorithm="HmacSHA256", headers="{headers}", signature="{signature_value}""#
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(signature_header)
|
Ok(signature_header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::ConnectorCommon for Cybersource {
|
impl ConnectorCommon for Cybersource {
|
||||||
fn id(&self) -> &'static str {
|
fn id(&self) -> &'static str {
|
||||||
"cybersource"
|
"cybersource"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common_get_content_type(&self) -> &'static str {
|
fn common_get_content_type(&self) -> &'static str {
|
||||||
"application/json"
|
"application/json;charset=utf-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||||
connectors.cybersource.base_url.as_ref()
|
connectors.cybersource.base_url.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
let response: cybersource::ErrorResponse = res
|
||||||
|
.parse_struct("Cybersource ErrorResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
Ok(types::ErrorResponse {
|
||||||
|
code: consts::NO_ERROR_CODE.to_string(),
|
||||||
|
message: response.details.to_string(),
|
||||||
|
reason: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Cybersource
|
||||||
|
where
|
||||||
|
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||||
|
{
|
||||||
|
fn build_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::RouterData<Flow, Request, Response>,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
let date = OffsetDateTime::now_utc();
|
||||||
|
let cybersource_req = self.get_request_body(req)?;
|
||||||
|
let auth = cybersource::CybersourceAuthType::try_from(&req.connector_auth_type)?;
|
||||||
|
let merchant_account = auth.merchant_account.clone();
|
||||||
|
let base_url = connectors.cybersource.base_url.as_str();
|
||||||
|
let cybersource_host = Url::parse(base_url)
|
||||||
|
.into_report()
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
let host = cybersource_host
|
||||||
|
.host_str()
|
||||||
|
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
let path: String = self
|
||||||
|
.get_url(req, connectors)?
|
||||||
|
.chars()
|
||||||
|
.skip(base_url.len() - 1)
|
||||||
|
.collect();
|
||||||
|
let sha256 =
|
||||||
|
self.generate_digest(cybersource_req.map_or("{}".to_string(), |s| s).as_bytes());
|
||||||
|
let http_method = self.get_http_method();
|
||||||
|
let signature = self.generate_signature(
|
||||||
|
auth,
|
||||||
|
host.to_string(),
|
||||||
|
path.as_str(),
|
||||||
|
&sha256,
|
||||||
|
date,
|
||||||
|
http_method,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut headers = vec![
|
||||||
|
(
|
||||||
|
headers::CONTENT_TYPE.to_string(),
|
||||||
|
self.get_content_type().to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
headers::ACCEPT.to_string(),
|
||||||
|
"application/hal+json;charset=utf-8".to_string(),
|
||||||
|
),
|
||||||
|
("v-c-merchant-id".to_string(), merchant_account),
|
||||||
|
("Date".to_string(), date.to_string()),
|
||||||
|
("Host".to_string(), host.to_string()),
|
||||||
|
("Signature".to_string(), signature),
|
||||||
|
];
|
||||||
|
if matches!(http_method, services::Method::Post | services::Method::Put) {
|
||||||
|
headers.push(("Digest".to_string(), format!("SHA-256={}", sha256)));
|
||||||
|
}
|
||||||
|
Ok(headers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::Payment for Cybersource {}
|
impl api::Payment for Cybersource {}
|
||||||
@ -87,68 +165,197 @@ impl api::PaymentVoid for Cybersource {}
|
|||||||
impl api::PaymentCapture for Cybersource {}
|
impl api::PaymentCapture for Cybersource {}
|
||||||
impl api::PreVerify for Cybersource {}
|
impl api::PreVerify for Cybersource {}
|
||||||
|
|
||||||
impl
|
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||||
services::ConnectorIntegration<
|
for Cybersource
|
||||||
api::Verify,
|
|
||||||
types::VerifyRequestData,
|
|
||||||
types::PaymentsResponseData,
|
|
||||||
> for Cybersource
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::PaymentSession for Cybersource {}
|
impl api::PaymentSession for Cybersource {}
|
||||||
|
|
||||||
impl
|
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||||
services::ConnectorIntegration<
|
|
||||||
api::Session,
|
|
||||||
types::PaymentsSessionData,
|
|
||||||
types::PaymentsResponseData,
|
|
||||||
> for Cybersource
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl
|
|
||||||
services::ConnectorIntegration<
|
|
||||||
api::Capture,
|
|
||||||
types::PaymentsCaptureData,
|
|
||||||
types::PaymentsResponseData,
|
|
||||||
> for Cybersource
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl
|
|
||||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
|
||||||
for Cybersource
|
for Cybersource
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
impl
|
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||||
services::ConnectorIntegration<
|
for Cybersource
|
||||||
api::Authorize,
|
|
||||||
types::PaymentsAuthorizeData,
|
|
||||||
types::PaymentsResponseData,
|
|
||||||
> for Cybersource
|
|
||||||
{
|
{
|
||||||
fn get_headers(
|
fn get_headers(
|
||||||
&self,
|
&self,
|
||||||
_req: &types::PaymentsAuthorizeRouterData,
|
req: &types::PaymentsCaptureRouterData,
|
||||||
_connectors: &settings::Connectors,
|
connectors: &settings::Connectors,
|
||||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
let headers = vec![
|
self.build_headers(req, connectors)
|
||||||
(
|
|
||||||
headers::CONTENT_TYPE.to_string(),
|
|
||||||
types::PaymentsAuthorizeType::get_content_type(self).to_string(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
headers::ACCEPT.to_string(),
|
|
||||||
"application/hal+json;charset=utf-8".to_string(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
Ok(headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_content_type(&self) -> &'static str {
|
fn get_content_type(&self) -> &'static str {
|
||||||
"application/json;charset=utf-8"
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCaptureRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||||
|
Ok(format!(
|
||||||
|
"{}pts/v2/payments/{}/captures",
|
||||||
|
self.base_url(connectors),
|
||||||
|
connector_payment_id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCaptureRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let req_obj = cybersource::CybersourcePaymentsRequest::try_from(req)?;
|
||||||
|
let req =
|
||||||
|
utils::Encode::<cybersource::CybersourcePaymentsRequest>::encode_to_string_of_json(
|
||||||
|
&req_obj,
|
||||||
|
)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(req))
|
||||||
|
}
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCaptureRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
|
||||||
|
.headers(types::PaymentsCaptureType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsCaptureRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<
|
||||||
|
types::RouterData<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>,
|
||||||
|
errors::ConnectorError,
|
||||||
|
> {
|
||||||
|
let response: cybersource::CybersourcePaymentsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Cybersource PaymentResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
logger::debug!(cybersourcepayments_create_response=?response);
|
||||||
|
types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
|
}
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||||
|
for Cybersource
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_http_method(&self) -> services::Method {
|
||||||
|
services::Method::Get
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let connector_payment_id = req
|
||||||
|
.request
|
||||||
|
.connector_transaction_id
|
||||||
|
.get_connector_transaction_id()
|
||||||
|
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||||
|
Ok(format!(
|
||||||
|
"{}tss/v2/transactions/{}",
|
||||||
|
self.base_url(connectors),
|
||||||
|
connector_payment_id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
_req: &types::PaymentsSyncRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
Ok(Some("{}".to_string()))
|
||||||
|
}
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Get)
|
||||||
|
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
||||||
|
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsSyncRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourceTransactionResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Cybersource PaymentSyncResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
logger::debug!(cybersourcepayments_create_response=?response);
|
||||||
|
types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
|
}
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||||
|
for Cybersource
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_url(
|
fn get_url(
|
||||||
@ -162,62 +369,37 @@ impl
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let req_obj = cybersource::CybersourcePaymentsRequest::try_from(req)?;
|
||||||
|
let cybersource_req =
|
||||||
|
utils::Encode::<cybersource::CybersourcePaymentsRequest>::encode_to_string_of_json(
|
||||||
|
&req_obj,
|
||||||
|
)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(cybersource_req))
|
||||||
|
}
|
||||||
|
|
||||||
fn build_request(
|
fn build_request(
|
||||||
&self,
|
&self,
|
||||||
req: &types::PaymentsAuthorizeRouterData,
|
req: &types::PaymentsAuthorizeRouterData,
|
||||||
connectors: &settings::Connectors,
|
connectors: &settings::Connectors,
|
||||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
let date = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
let cybersource_req =
|
|
||||||
utils::Encode::<cybersource::CybersourcePaymentsRequest>::convert_and_encode(req)
|
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
let auth: cybersource::CybersourceAuthType =
|
|
||||||
cybersource::CybersourceAuthType::try_from(&req.connector_auth_type)?;
|
|
||||||
let merchant_account = auth.merchant_account.clone();
|
|
||||||
|
|
||||||
let cybersource_host = Url::parse(connectors.cybersource.base_url.as_str())
|
|
||||||
.into_report()
|
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
match cybersource_host.host_str() {
|
|
||||||
Some(host) => {
|
|
||||||
let signature = self.generate_signature(
|
|
||||||
auth,
|
|
||||||
host.to_string(),
|
|
||||||
"/pts/v2/payments/",
|
|
||||||
&cybersource_req,
|
|
||||||
date,
|
|
||||||
)?;
|
|
||||||
let headers = vec![
|
|
||||||
(
|
|
||||||
"Digest".to_string(),
|
|
||||||
format!(
|
|
||||||
"SHA-256={}",
|
|
||||||
self.generate_digest(cybersource_req.as_bytes())
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("v-c-merchant-id".to_string(), merchant_account),
|
|
||||||
("Date".to_string(), date.to_string()),
|
|
||||||
("Host".to_string(), host.to_string()),
|
|
||||||
("Signature".to_string(), signature),
|
|
||||||
];
|
|
||||||
let request = services::RequestBuilder::new()
|
let request = services::RequestBuilder::new()
|
||||||
.method(services::Method::Post)
|
.method(services::Method::Post)
|
||||||
.url(&types::PaymentsAuthorizeType::get_url(
|
.url(&types::PaymentsAuthorizeType::get_url(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.headers(headers)
|
|
||||||
.headers(types::PaymentsAuthorizeType::get_headers(
|
.headers(types::PaymentsAuthorizeType::get_headers(
|
||||||
self, req, connectors,
|
self, req, connectors,
|
||||||
)?)
|
)?)
|
||||||
.body(Some(cybersource_req))
|
.body(self.get_request_body(req)?)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(Some(request))
|
Ok(Some(request))
|
||||||
}
|
}
|
||||||
None => Err(errors::ConnectorError::RequestEncodingFailed.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_response(
|
fn handle_response(
|
||||||
&self,
|
&self,
|
||||||
@ -242,26 +424,85 @@ impl
|
|||||||
&self,
|
&self,
|
||||||
res: Bytes,
|
res: Bytes,
|
||||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
let response: cybersource::ErrorResponse = res
|
self.build_error_response(res)
|
||||||
.parse_struct("Cybersource ErrorResponse")
|
|
||||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
||||||
Ok(types::ErrorResponse {
|
|
||||||
code: consts::NO_ERROR_CODE.to_string(),
|
|
||||||
message: response
|
|
||||||
.message
|
|
||||||
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
|
|
||||||
reason: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl
|
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||||
services::ConnectorIntegration<
|
for Cybersource
|
||||||
api::Void,
|
|
||||||
types::PaymentsCancelData,
|
|
||||||
types::PaymentsResponseData,
|
|
||||||
> for Cybersource
|
|
||||||
{
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCancelRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCancelRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||||
|
Ok(format!(
|
||||||
|
"{}pts/v2/payments/{}/voids",
|
||||||
|
self.base_url(connectors),
|
||||||
|
connector_payment_id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
_req: &types::PaymentsCancelRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
Ok(Some("{}".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::PaymentsCancelRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
|
||||||
|
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
|
||||||
|
.body(self.get_request_body(req)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::PaymentsCancelRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourcePaymentsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Cybersource PaymentResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
logger::debug!(cybersourcepayments_create_response=?response);
|
||||||
|
types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl api::Refund for Cybersource {}
|
impl api::Refund for Cybersource {}
|
||||||
@ -269,15 +510,153 @@ impl api::RefundExecute for Cybersource {}
|
|||||||
impl api::RefundSync for Cybersource {}
|
impl api::RefundSync for Cybersource {}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||||
for Cybersource
|
for Cybersource
|
||||||
{
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundExecuteRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundExecuteRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||||
|
Ok(format!(
|
||||||
|
"{}pts/v2/payments/{}/refunds",
|
||||||
|
self.base_url(connectors),
|
||||||
|
connector_payment_id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_body(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundExecuteRouterData,
|
||||||
|
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||||
|
let req_obj = cybersource::CybersourceRefundRequest::try_from(req)?;
|
||||||
|
let req = utils::Encode::<cybersource::CybersourceRefundRequest>::encode_to_string_of_json(
|
||||||
|
&req_obj,
|
||||||
|
)
|
||||||
|
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||||
|
Ok(Some(req))
|
||||||
|
}
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundExecuteRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Post)
|
||||||
|
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
|
||||||
|
.headers(types::RefundExecuteType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.body(self.get_request_body(req)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::RefundExecuteRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::RefundExecuteRouterData, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourcePaymentsResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Cybersource PaymentResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
logger::debug!(cybersource_refund_response=?response);
|
||||||
|
types::RouterData::try_from(types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
})
|
||||||
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
|
}
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
||||||
for Cybersource
|
for Cybersource
|
||||||
{
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
fn get_http_method(&self) -> services::Method {
|
||||||
|
services::Method::Get
|
||||||
|
}
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
Ok(format!(
|
||||||
|
"{}tss/v2/transactions/{}",
|
||||||
|
self.base_url(connectors),
|
||||||
|
req.request.connector_transaction_id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &types::RefundSyncRouterData,
|
||||||
|
connectors: &settings::Connectors,
|
||||||
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
services::RequestBuilder::new()
|
||||||
|
.method(services::Method::Get)
|
||||||
|
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||||
|
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||||
|
.body(types::RefundSyncType::get_request_body(self, req)?)
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &types::RefundSyncRouterData,
|
||||||
|
res: types::Response,
|
||||||
|
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||||
|
let response: cybersource::CybersourceTransactionResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("Cybersource PaymentSyncResponse")
|
||||||
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||||
|
logger::debug!(cybersourcepayments_create_response=?response);
|
||||||
|
types::ResponseRouterData {
|
||||||
|
response,
|
||||||
|
data: data.clone(),
|
||||||
|
http_code: res.status_code,
|
||||||
|
}
|
||||||
|
.try_into()
|
||||||
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||||
|
}
|
||||||
|
fn get_error_response(
|
||||||
|
&self,
|
||||||
|
res: Bytes,
|
||||||
|
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||||
|
self.build_error_response(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@ -4,6 +4,8 @@ use masking::Secret;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
connector::utils::{self, AddressDetailsData, PaymentsRequestData, PhoneDetailsData},
|
||||||
|
consts,
|
||||||
core::errors,
|
core::errors,
|
||||||
pii::PeekInterface,
|
pii::PeekInterface,
|
||||||
types::{self, api, storage::enums},
|
types::{self, api, storage::enums},
|
||||||
@ -18,8 +20,17 @@ pub struct CybersourcePaymentsRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ProcessingInformation {
|
pub struct ProcessingInformation {
|
||||||
capture: bool,
|
capture: bool,
|
||||||
|
capture_options: Option<CaptureOptions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CaptureOptions {
|
||||||
|
capture_sequence_number: u32,
|
||||||
|
total_capture_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||||
@ -72,35 +83,25 @@ pub struct BillTo {
|
|||||||
|
|
||||||
// for cybersource each item in Billing is mandatory
|
// for cybersource each item in Billing is mandatory
|
||||||
fn build_bill_to(
|
fn build_bill_to(
|
||||||
address_details: payments::Address,
|
address_details: &payments::Address,
|
||||||
email: Secret<String, pii::Email>,
|
email: Secret<String, pii::Email>,
|
||||||
phone_number: Secret<String>,
|
phone_number: Secret<String>,
|
||||||
) -> Option<BillTo> {
|
) -> Result<BillTo, error_stack::Report<errors::ConnectorError>> {
|
||||||
if let Some(api_models::payments::AddressDetails {
|
let address = address_details
|
||||||
first_name: Some(f_name),
|
.address
|
||||||
last_name: Some(last_name),
|
.as_ref()
|
||||||
line1: Some(address1),
|
.ok_or_else(utils::missing_field_err("billing.address"))?;
|
||||||
city: Some(city),
|
Ok(BillTo {
|
||||||
line2: Some(administrative_area),
|
first_name: address.get_first_name()?.to_owned(),
|
||||||
zip: Some(postal_code),
|
last_name: address.get_last_name()?.to_owned(),
|
||||||
country: Some(country),
|
address1: address.get_line1()?.to_owned(),
|
||||||
..
|
locality: address.get_city()?.to_owned(),
|
||||||
}) = address_details.address
|
administrative_area: address.get_line2()?.to_owned(),
|
||||||
{
|
postal_code: address.get_zip()?.to_owned(),
|
||||||
Some(BillTo {
|
country: address.get_country()?.to_owned(),
|
||||||
first_name: f_name,
|
|
||||||
last_name,
|
|
||||||
address1,
|
|
||||||
locality: city,
|
|
||||||
administrative_area,
|
|
||||||
postal_code,
|
|
||||||
country,
|
|
||||||
email,
|
email,
|
||||||
phone_number,
|
phone_number,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for CybersourcePaymentsRequest {
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for CybersourcePaymentsRequest {
|
||||||
@ -108,30 +109,17 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CybersourcePaymentsRequest
|
|||||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
match item.request.payment_method_data {
|
match item.request.payment_method_data {
|
||||||
api::PaymentMethod::Card(ref ccard) => {
|
api::PaymentMethod::Card(ref ccard) => {
|
||||||
let address = item
|
let phone = item.get_billing_phone()?;
|
||||||
.address
|
let phone_number = phone.get_number()?;
|
||||||
.billing
|
let country_code = phone.get_country_code()?;
|
||||||
.clone()
|
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
let phone = address
|
|
||||||
.clone()
|
|
||||||
.phone
|
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
let phone_number = phone
|
|
||||||
.number
|
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
let country_code = phone
|
|
||||||
.country_code
|
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
let number_with_code =
|
let number_with_code =
|
||||||
Secret::new(format!("{}{}", country_code, phone_number.peek()));
|
Secret::new(format!("{}{}", country_code, phone_number.peek()));
|
||||||
let email = item
|
let email = item
|
||||||
.request
|
.request
|
||||||
.email
|
.email
|
||||||
.clone()
|
.clone()
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
.ok_or_else(utils::missing_field_err("email"))?;
|
||||||
let bill_to = build_bill_to(address, email, number_with_code)
|
let bill_to = build_bill_to(item.get_billing()?, email, number_with_code)?;
|
||||||
.ok_or(errors::ConnectorError::RequestEncodingFailed)?;
|
|
||||||
|
|
||||||
let order_information = OrderInformationWithBill {
|
let order_information = OrderInformationWithBill {
|
||||||
amount_details: Amount {
|
amount_details: Amount {
|
||||||
@ -155,6 +143,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CybersourcePaymentsRequest
|
|||||||
item.request.capture_method,
|
item.request.capture_method,
|
||||||
Some(enums::CaptureMethod::Automatic) | None
|
Some(enums::CaptureMethod::Automatic) | None
|
||||||
),
|
),
|
||||||
|
capture_options: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -168,6 +157,49 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for CybersourcePaymentsRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&types::PaymentsCaptureRouterData> for CybersourcePaymentsRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(value: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
processing_information: ProcessingInformation {
|
||||||
|
capture_options: Some(CaptureOptions {
|
||||||
|
capture_sequence_number: 1,
|
||||||
|
total_capture_count: 1,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
order_information: OrderInformationWithBill {
|
||||||
|
amount_details: Amount {
|
||||||
|
total_amount: value
|
||||||
|
.request
|
||||||
|
.amount_to_capture
|
||||||
|
.map(|amount| amount.to_string())
|
||||||
|
.ok_or_else(utils::missing_field_err("amount_to_capture"))?,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&types::RefundExecuteRouterData> for CybersourcePaymentsRequest {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(value: &types::RefundExecuteRouterData) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
order_information: OrderInformationWithBill {
|
||||||
|
amount_details: Amount {
|
||||||
|
total_amount: value.request.refund_amount.to_string(),
|
||||||
|
currency: value.request.currency.to_string(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CybersourceAuthType {
|
pub struct CybersourceAuthType {
|
||||||
pub(super) api_key: String,
|
pub(super) api_key: String,
|
||||||
pub(super) merchant_account: String,
|
pub(super) merchant_account: String,
|
||||||
@ -199,6 +231,11 @@ pub enum CybersourcePaymentStatus {
|
|||||||
Authorized,
|
Authorized,
|
||||||
Succeeded,
|
Succeeded,
|
||||||
Failed,
|
Failed,
|
||||||
|
Voided,
|
||||||
|
Reversed,
|
||||||
|
Pending,
|
||||||
|
Declined,
|
||||||
|
Transmitted,
|
||||||
#[default]
|
#[default]
|
||||||
Processing,
|
Processing,
|
||||||
}
|
}
|
||||||
@ -207,28 +244,110 @@ impl From<CybersourcePaymentStatus> for enums::AttemptStatus {
|
|||||||
fn from(item: CybersourcePaymentStatus) -> Self {
|
fn from(item: CybersourcePaymentStatus) -> Self {
|
||||||
match item {
|
match item {
|
||||||
CybersourcePaymentStatus::Authorized => Self::Authorized,
|
CybersourcePaymentStatus::Authorized => Self::Authorized,
|
||||||
CybersourcePaymentStatus::Succeeded => Self::Charged,
|
CybersourcePaymentStatus::Succeeded | CybersourcePaymentStatus::Transmitted => {
|
||||||
CybersourcePaymentStatus::Failed => Self::Failure,
|
Self::Charged
|
||||||
|
}
|
||||||
|
CybersourcePaymentStatus::Voided | CybersourcePaymentStatus::Reversed => Self::Voided,
|
||||||
|
CybersourcePaymentStatus::Failed | CybersourcePaymentStatus::Declined => Self::Failure,
|
||||||
CybersourcePaymentStatus::Processing => Self::Authorizing,
|
CybersourcePaymentStatus::Processing => Self::Authorizing,
|
||||||
|
CybersourcePaymentStatus::Pending => Self::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CybersourcePaymentStatus> for enums::RefundStatus {
|
||||||
|
fn from(item: CybersourcePaymentStatus) -> Self {
|
||||||
|
match item {
|
||||||
|
CybersourcePaymentStatus::Succeeded => Self::Success,
|
||||||
|
CybersourcePaymentStatus::Failed => Self::Failure,
|
||||||
|
_ => Self::Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CybersourcePaymentsResponse {
|
pub struct CybersourcePaymentsResponse {
|
||||||
id: String,
|
id: String,
|
||||||
status: CybersourcePaymentStatus,
|
status: CybersourcePaymentStatus,
|
||||||
|
error_information: Option<CybersourceErrorInformation>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<types::PaymentsResponseRouterData<CybersourcePaymentsResponse>>
|
#[derive(Default, Debug, Clone, Deserialize, Eq, PartialEq)]
|
||||||
for types::PaymentsAuthorizeRouterData
|
pub struct CybersourceErrorInformation {
|
||||||
|
reason: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, T>
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<F, CybersourcePaymentsResponse, T, types::PaymentsResponseData>,
|
||||||
|
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ParsingError>;
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::PaymentsResponseRouterData<CybersourcePaymentsResponse>,
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
CybersourcePaymentsResponse,
|
||||||
|
T,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
status: item.response.status.into(),
|
status: item.response.status.into(),
|
||||||
|
response: match item.response.error_information {
|
||||||
|
Some(error) => Err(types::ErrorResponse {
|
||||||
|
code: consts::NO_ERROR_CODE.to_string(),
|
||||||
|
message: error.message,
|
||||||
|
reason: Some(error.reason),
|
||||||
|
}),
|
||||||
|
_ => Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
|
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||||
|
redirection_data: None,
|
||||||
|
redirect: false,
|
||||||
|
mandate_reference: None,
|
||||||
|
connector_metadata: None,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CybersourceTransactionResponse {
|
||||||
|
id: String,
|
||||||
|
application_information: ApplicationInformation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ApplicationInformation {
|
||||||
|
status: CybersourcePaymentStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, T>
|
||||||
|
TryFrom<
|
||||||
|
types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
CybersourceTransactionResponse,
|
||||||
|
T,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
> for types::RouterData<F, T, types::PaymentsResponseData>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::ResponseRouterData<
|
||||||
|
F,
|
||||||
|
CybersourceTransactionResponse,
|
||||||
|
T,
|
||||||
|
types::PaymentsResponseData,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
status: item.response.application_information.status.into(),
|
||||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||||
redirection_data: None,
|
redirection_data: None,
|
||||||
@ -247,6 +366,7 @@ pub struct ErrorResponse {
|
|||||||
pub error_information: Option<ErrorInformation>,
|
pub error_information: Option<ErrorInformation>,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub message: Option<String>,
|
pub message: Option<String>,
|
||||||
|
pub details: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
@ -256,12 +376,13 @@ pub struct ErrorInformation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize)]
|
#[derive(Default, Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CybersourceRefundRequest {
|
pub struct CybersourceRefundRequest {
|
||||||
order_information: OrderInformation,
|
order_information: OrderInformation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for CybersourceRefundRequest {
|
impl<F> TryFrom<&types::RefundsRouterData<F>> for CybersourceRefundRequest {
|
||||||
type Error = error_stack::Report<errors::ParsingError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
order_information: OrderInformation {
|
order_information: OrderInformation {
|
||||||
@ -274,42 +395,37 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for CybersourceRefundRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
impl TryFrom<types::RefundsResponseRouterData<api::Execute, CybersourcePaymentsResponse>>
|
||||||
#[derive(Debug, Default, Deserialize, Clone)]
|
for types::RefundsRouterData<api::Execute>
|
||||||
pub enum RefundStatus {
|
|
||||||
Succeeded,
|
|
||||||
Failed,
|
|
||||||
#[default]
|
|
||||||
Processing,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RefundStatus> for enums::RefundStatus {
|
|
||||||
fn from(item: RefundStatus) -> Self {
|
|
||||||
match item {
|
|
||||||
self::RefundStatus::Succeeded => Self::Success,
|
|
||||||
self::RefundStatus::Failed => Self::Failure,
|
|
||||||
self::RefundStatus::Processing => Self::Pending,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize)]
|
|
||||||
pub struct CybersourceRefundResponse {
|
|
||||||
pub id: String,
|
|
||||||
pub status: RefundStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<types::RefundsResponseRouterData<api::RSync, CybersourceRefundResponse>>
|
|
||||||
for types::RefundsRouterData<api::RSync>
|
|
||||||
{
|
{
|
||||||
type Error = error_stack::Report<errors::ParsingError>;
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
fn try_from(
|
fn try_from(
|
||||||
item: types::RefundsResponseRouterData<api::RSync, CybersourceRefundResponse>,
|
item: types::RefundsResponseRouterData<api::Execute, CybersourcePaymentsResponse>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
|
let refund_status = enums::RefundStatus::from(item.response.status);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
response: Ok(types::RefundsResponseData {
|
response: Ok(types::RefundsResponseData {
|
||||||
connector_refund_id: item.response.id,
|
connector_refund_id: item.response.id,
|
||||||
refund_status: enums::RefundStatus::from(item.response.status),
|
refund_status,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<types::RefundsResponseRouterData<api::RSync, CybersourceTransactionResponse>>
|
||||||
|
for types::RefundsRouterData<api::RSync>
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ParsingError>;
|
||||||
|
fn try_from(
|
||||||
|
item: types::RefundsResponseRouterData<api::RSync, CybersourceTransactionResponse>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
response: Ok(types::RefundsResponseData {
|
||||||
|
connector_refund_id: item.response.id,
|
||||||
|
refund_status: enums::RefundStatus::from(
|
||||||
|
item.response.application_information.status,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
..item.data
|
..item.data
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use error_stack::ResultExt;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -9,7 +8,6 @@ use crate::{
|
|||||||
connector::utils::{self, CardData, PaymentsRequestData},
|
connector::utils::{self, CardData, PaymentsRequestData},
|
||||||
core::errors,
|
core::errors,
|
||||||
types::{self, api, storage::enums},
|
types::{self, api, storage::enums},
|
||||||
utils::OptionExt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest {
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest {
|
||||||
@ -18,8 +16,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for GlobalpayPaymentsRequest {
|
|||||||
let metadata = item
|
let metadata = item
|
||||||
.connector_meta_data
|
.connector_meta_data
|
||||||
.to_owned()
|
.to_owned()
|
||||||
.get_required_value("connector_meta_data")
|
.ok_or_else(utils::missing_field_err("connector_meta"))?;
|
||||||
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
|
|
||||||
let account_name = metadata
|
let account_name = metadata
|
||||||
.as_object()
|
.as_object()
|
||||||
.and_then(|o| o.get("account_name"))
|
.and_then(|o| o.get("account_name"))
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
use masking::Secret;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::errors,
|
core::errors,
|
||||||
pii::PeekInterface,
|
pii::PeekInterface,
|
||||||
@ -18,10 +20,50 @@ pub fn missing_field_err(
|
|||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
pub trait PaymentsRequestData {
|
pub trait PaymentsRequestData {
|
||||||
fn get_attempt_id(&self) -> Result<String, Error>;
|
fn get_attempt_id(&self) -> Result<String, Error>;
|
||||||
|
fn get_billing(&self) -> Result<&api::Address, Error>;
|
||||||
fn get_billing_country(&self) -> Result<String, Error>;
|
fn get_billing_country(&self) -> Result<String, Error>;
|
||||||
|
fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error>;
|
||||||
fn get_card(&self) -> Result<api::CCard, Error>;
|
fn get_card(&self) -> Result<api::CCard, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PaymentsRequestData for types::PaymentsAuthorizeRouterData {
|
||||||
|
fn get_attempt_id(&self) -> Result<String, Error> {
|
||||||
|
self.attempt_id
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(missing_field_err("attempt_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_billing_country(&self) -> Result<String, Error> {
|
||||||
|
self.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|a| a.address.as_ref())
|
||||||
|
.and_then(|ad| ad.country.clone())
|
||||||
|
.ok_or_else(missing_field_err("billing.address.country"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_card(&self) -> Result<api::CCard, Error> {
|
||||||
|
match self.request.payment_method_data.clone() {
|
||||||
|
api::PaymentMethod::Card(card) => Ok(card),
|
||||||
|
_ => Err(missing_field_err("card")()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error> {
|
||||||
|
self.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|a| a.phone.as_ref())
|
||||||
|
.ok_or_else(missing_field_err("billing.phone"))
|
||||||
|
}
|
||||||
|
fn get_billing(&self) -> Result<&api::Address, Error> {
|
||||||
|
self.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("billing"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait CardData {
|
pub trait CardData {
|
||||||
fn get_card_number(&self) -> String;
|
fn get_card_number(&self) -> String;
|
||||||
fn get_card_expiry_month(&self) -> String;
|
fn get_card_expiry_month(&self) -> String;
|
||||||
@ -29,6 +71,7 @@ pub trait CardData {
|
|||||||
fn get_card_expiry_year_2_digit(&self) -> String;
|
fn get_card_expiry_year_2_digit(&self) -> String;
|
||||||
fn get_card_cvc(&self) -> String;
|
fn get_card_cvc(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardData for api::CCard {
|
impl CardData for api::CCard {
|
||||||
fn get_card_number(&self) -> String {
|
fn get_card_number(&self) -> String {
|
||||||
self.card_number.peek().clone()
|
self.card_number.peek().clone()
|
||||||
@ -47,26 +90,74 @@ impl CardData for api::CCard {
|
|||||||
self.card_cvc.peek().clone()
|
self.card_cvc.peek().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PaymentsRequestData for types::PaymentsAuthorizeRouterData {
|
pub trait PhoneDetailsData {
|
||||||
fn get_attempt_id(&self) -> Result<String, Error> {
|
fn get_number(&self) -> Result<Secret<String>, Error>;
|
||||||
self.attempt_id
|
fn get_country_code(&self) -> Result<String, Error>;
|
||||||
.clone()
|
|
||||||
.ok_or_else(missing_field_err("attempt_id"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_billing_country(&self) -> Result<String, Error> {
|
impl PhoneDetailsData for api::PhoneDetails {
|
||||||
self.address
|
fn get_country_code(&self) -> Result<String, Error> {
|
||||||
.billing
|
self.country_code
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|a| a.address)
|
.ok_or_else(missing_field_err("billing.phone.country_code"))
|
||||||
.and_then(|ad| ad.country)
|
}
|
||||||
.ok_or_else(missing_field_err("billing.country"))
|
fn get_number(&self) -> Result<Secret<String>, Error> {
|
||||||
|
self.number
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(missing_field_err("billing.phone.number"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_card(&self) -> Result<api::CCard, Error> {
|
pub trait AddressDetailsData {
|
||||||
match self.request.payment_method_data.clone() {
|
fn get_first_name(&self) -> Result<&Secret<String>, Error>;
|
||||||
api::PaymentMethod::Card(card) => Ok(card),
|
fn get_last_name(&self) -> Result<&Secret<String>, Error>;
|
||||||
_ => Err(missing_field_err("card")()),
|
fn get_line1(&self) -> Result<&Secret<String>, Error>;
|
||||||
}
|
fn get_city(&self) -> Result<&String, Error>;
|
||||||
|
fn get_line2(&self) -> Result<&Secret<String>, Error>;
|
||||||
|
fn get_zip(&self) -> Result<&Secret<String>, Error>;
|
||||||
|
fn get_country(&self) -> Result<&String, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressDetailsData for api::AddressDetails {
|
||||||
|
fn get_first_name(&self) -> Result<&Secret<String>, Error> {
|
||||||
|
self.first_name
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.first_name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_last_name(&self) -> Result<&Secret<String>, Error> {
|
||||||
|
self.last_name
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.last_name"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line1(&self) -> Result<&Secret<String>, Error> {
|
||||||
|
self.line1
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.line1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_city(&self) -> Result<&String, Error> {
|
||||||
|
self.city
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.city"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line2(&self) -> Result<&Secret<String>, Error> {
|
||||||
|
self.line2
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.line2"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_zip(&self) -> Result<&Secret<String>, Error> {
|
||||||
|
self.zip
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.zip"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_country(&self) -> Result<&String, Error> {
|
||||||
|
self.country
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(missing_field_err("address.country"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,11 @@ pub trait ConnectorIntegration<T, Req, Resp>: ConnectorIntegrationAny<T, Req, Re
|
|||||||
mime::APPLICATION_JSON.essence_str()
|
mime::APPLICATION_JSON.essence_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// primarily used when creating signature based on request method of payment flow
|
||||||
|
fn get_http_method(&self) -> Method {
|
||||||
|
Method::Post
|
||||||
|
}
|
||||||
|
|
||||||
fn get_url(
|
fn get_url(
|
||||||
&self,
|
&self,
|
||||||
_req: &types::RouterData<T, Req, Resp>,
|
_req: &types::RouterData<T, Req, Resp>,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ pub(crate) struct ConnectorAuthentication {
|
|||||||
pub aci: Option<BodyKey>,
|
pub aci: Option<BodyKey>,
|
||||||
pub authorizedotnet: Option<BodyKey>,
|
pub authorizedotnet: Option<BodyKey>,
|
||||||
pub checkout: Option<BodyKey>,
|
pub checkout: Option<BodyKey>,
|
||||||
|
pub cybersource: Option<SignatureKey>,
|
||||||
pub fiserv: Option<SignatureKey>,
|
pub fiserv: Option<SignatureKey>,
|
||||||
pub globalpay: Option<HeaderKey>,
|
pub globalpay: Option<HeaderKey>,
|
||||||
pub payu: Option<BodyKey>,
|
pub payu: Option<BodyKey>,
|
||||||
|
|||||||
240
crates/router/tests/connectors/cybersource.rs
Normal file
240
crates/router/tests/connectors/cybersource.rs
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
use futures::future::OptionFuture;
|
||||||
|
use masking::Secret;
|
||||||
|
use router::types::{self, api, storage::enums};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
connector_auth,
|
||||||
|
utils::{self, ConnectorActions, PaymentAuthorizeType},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cybersource;
|
||||||
|
impl ConnectorActions for Cybersource {}
|
||||||
|
impl utils::Connector for Cybersource {
|
||||||
|
fn get_data(&self) -> types::api::ConnectorData {
|
||||||
|
use router::connector::Cybersource;
|
||||||
|
types::api::ConnectorData {
|
||||||
|
connector: Box::new(&Cybersource),
|
||||||
|
connector_name: types::Connector::Cybersource,
|
||||||
|
get_token: types::api::GetToken::Connector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||||
|
types::ConnectorAuthType::from(
|
||||||
|
connector_auth::ConnectorAuthentication::new()
|
||||||
|
.cybersource
|
||||||
|
.expect("Missing connector authentication configuration"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
"cybersource".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||||
|
Some(utils::PaymentInfo {
|
||||||
|
address: Some(types::PaymentAddress {
|
||||||
|
billing: Some(api::Address {
|
||||||
|
address: Some(api::AddressDetails {
|
||||||
|
first_name: Some(Secret::new("first".to_string())),
|
||||||
|
last_name: Some(Secret::new("last".to_string())),
|
||||||
|
line1: Some(Secret::new("line1".to_string())),
|
||||||
|
line2: Some(Secret::new("line2".to_string())),
|
||||||
|
city: Some("city".to_string()),
|
||||||
|
zip: Some(Secret::new("zip".to_string())),
|
||||||
|
country: Some("IN".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
phone: Some(api::PhoneDetails {
|
||||||
|
number: Some(Secret::new("1234567890".to_string())),
|
||||||
|
country_code: Some("+91".to_string()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_default_payment_authorize_data() -> Option<types::PaymentsAuthorizeData> {
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
email: Some(Secret::new("abc@gmail.com".to_string())),
|
||||||
|
..PaymentAuthorizeType::default().0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_only_authorize_payment() {
|
||||||
|
let response = Cybersource {}
|
||||||
|
.authorize_payment(
|
||||||
|
get_default_payment_authorize_data(),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_authorize_and_capture_payment() {
|
||||||
|
let connector = Cybersource {};
|
||||||
|
let response = connector
|
||||||
|
.make_payment(
|
||||||
|
get_default_payment_authorize_data(),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let sync_response = connector
|
||||||
|
.sync_payment(
|
||||||
|
Some(types::PaymentsSyncData {
|
||||||
|
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||||
|
utils::get_connector_transaction_id(response).unwrap(),
|
||||||
|
),
|
||||||
|
encoded_data: None,
|
||||||
|
}),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
//cybersource takes sometime to settle the transaction,so it will be in pending for long time
|
||||||
|
assert_eq!(sync_response.status, enums::AttemptStatus::Pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_sync_capture_payment() {
|
||||||
|
let sync_response = Cybersource {}
|
||||||
|
.sync_payment(
|
||||||
|
Some(types::PaymentsSyncData {
|
||||||
|
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||||
|
"6736046645576085004953".to_string(),
|
||||||
|
),
|
||||||
|
encoded_data: None,
|
||||||
|
}),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(sync_response.status, enums::AttemptStatus::Charged);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_capture_already_authorized_payment() {
|
||||||
|
let connector = Cybersource {};
|
||||||
|
let authorize_response = connector
|
||||||
|
.authorize_payment(
|
||||||
|
get_default_payment_authorize_data(),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
|
||||||
|
let txn_id = utils::get_connector_transaction_id(authorize_response);
|
||||||
|
let response: OptionFuture<_> = txn_id
|
||||||
|
.map(|transaction_id| async move {
|
||||||
|
connector
|
||||||
|
.capture_payment(transaction_id, None, get_default_payment_info())
|
||||||
|
.await
|
||||||
|
.status
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
//cybersource takes sometime to settle the transaction,so it will be in pending for long time
|
||||||
|
assert_eq!(response.await, Some(enums::AttemptStatus::Pending));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_void_already_authorized_payment() {
|
||||||
|
let connector = Cybersource {};
|
||||||
|
let authorize_response = connector
|
||||||
|
.authorize_payment(
|
||||||
|
get_default_payment_authorize_data(),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
|
||||||
|
let txn_id = utils::get_connector_transaction_id(authorize_response);
|
||||||
|
let response: OptionFuture<_> = txn_id
|
||||||
|
.map(|transaction_id| async move {
|
||||||
|
connector
|
||||||
|
.void_payment(transaction_id, None, get_default_payment_info())
|
||||||
|
.await
|
||||||
|
.status
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
assert_eq!(response.await, Some(enums::AttemptStatus::Voided));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_refund_succeeded_payment() {
|
||||||
|
let connector = Cybersource {};
|
||||||
|
//make a successful payment
|
||||||
|
let response = connector
|
||||||
|
.make_payment(
|
||||||
|
get_default_payment_authorize_data(),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
//try refund for previous payment
|
||||||
|
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
|
||||||
|
let response = connector
|
||||||
|
.refund_payment(transaction_id, None, get_default_payment_info())
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Pending, //cybersource takes sometime to refund the transaction,so it will be in pending state for long time
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_sync_refund() {
|
||||||
|
let connector = Cybersource {};
|
||||||
|
let response = connector
|
||||||
|
.sync_refund(
|
||||||
|
"6738063831816571404953".to_string(),
|
||||||
|
None,
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Pending, //cybersource takes sometime to refund the transaction,so it will be in pending state for long time
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_incorrect_card_number() {
|
||||||
|
let response = Cybersource {}
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::CCard {
|
||||||
|
card_number: Secret::new("424242442424242".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..get_default_payment_authorize_data().unwrap()
|
||||||
|
}),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Failure);
|
||||||
|
let x = response.response.unwrap_err();
|
||||||
|
assert_eq!(x.message, "Decline - Invalid account number".to_string(),);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_incorrect_exp_month() {
|
||||||
|
let response = Cybersource {}
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::CCard {
|
||||||
|
card_number: Secret::new("4242424242424242".to_string()),
|
||||||
|
card_exp_month: Secret::new("101".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..get_default_payment_authorize_data().unwrap()
|
||||||
|
}),
|
||||||
|
get_default_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let x = response.response.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
x.message,
|
||||||
|
r#"[{"field":"paymentInformation.card.expirationMonth","reason":"INVALID_DATA"}]"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ mod aci;
|
|||||||
mod authorizedotnet;
|
mod authorizedotnet;
|
||||||
mod checkout;
|
mod checkout;
|
||||||
mod connector_auth;
|
mod connector_auth;
|
||||||
|
mod cybersource;
|
||||||
mod fiserv;
|
mod fiserv;
|
||||||
mod globalpay;
|
mod globalpay;
|
||||||
mod payu;
|
mod payu;
|
||||||
|
|||||||
@ -13,6 +13,11 @@ key1 = "MyTransactionKey"
|
|||||||
api_key = "Bearer MyApiKey"
|
api_key = "Bearer MyApiKey"
|
||||||
key1 = "MyProcessingChannelId"
|
key1 = "MyProcessingChannelId"
|
||||||
|
|
||||||
|
[cybersource]
|
||||||
|
api_key = "Bearer MyApiKey"
|
||||||
|
key1 = "Merchant id"
|
||||||
|
api_secret = "Secret key"
|
||||||
|
|
||||||
[shift4]
|
[shift4]
|
||||||
api_key = "Bearer MyApiKey"
|
api_key = "Bearer MyApiKey"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user