Skip to content

Commit

Permalink
feat(connector): [VOLT] Implement payment flows and bank redirect pay…
Browse files Browse the repository at this point in the history
…ment method (#2582)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Prasunna Soppa <[email protected]>
  • Loading branch information
3 people authored Oct 30, 2023
1 parent 8125ea1 commit 23bd364
Show file tree
Hide file tree
Showing 94 changed files with 2,970 additions and 146 deletions.
Binary file modified .github/secrets/connector_auth.toml.gpg
Binary file not shown.
5 changes: 3 additions & 2 deletions crates/api_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub enum Connector {
Trustpay,
// Tsys,
Tsys,
//Volt, added as template code for future usage,
Volt,
Wise,
Worldline,
Worldpay,
Expand All @@ -135,6 +135,7 @@ impl Connector {
| (Self::Payu, _)
| (Self::Trustpay, PaymentMethod::BankRedirect)
| (Self::Iatapay, _)
| (Self::Volt, _)
)
}
pub fn supports_file_storage_module(&self) -> bool {
Expand Down Expand Up @@ -235,7 +236,7 @@ pub enum RoutableConnectors {
Trustpay,
// Tsys,
Tsys,
// Volt, added as template code for future usage
Volt,
Wise,
Worldline,
Worldpay,
Expand Down
197 changes: 123 additions & 74 deletions crates/router/src/connector/volt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod transformers;
use std::fmt::Debug;

use error_stack::{IntoReport, ResultExt};
use masking::ExposeInterface;
use masking::{ExposeInterface, PeekInterface};
use transformers as volt;

use crate::{
Expand Down Expand Up @@ -64,8 +64,15 @@ where
.to_string()
.into(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
let access_token = req
.access_token
.clone()
.ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
let auth_header = (
headers::AUTHORIZATION.to_string(),
format!("Bearer {}", access_token.token.peek()).into_masked(),
);
header.push(auth_header);
Ok(header)
}
}
Expand Down Expand Up @@ -95,7 +102,7 @@ impl ConnectorCommon for Volt {
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(
headers::AUTHORIZATION.to_string(),
auth.api_key.expose().into_masked(),
auth.username.expose().into_masked(),
)])
}

Expand All @@ -108,11 +115,20 @@ impl ConnectorCommon for Volt {
.parse_struct("VoltErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

let reason = match &response.exception.error_list {
Some(error_list) => error_list
.iter()
.map(|error| error.message.clone())
.collect::<Vec<String>>()
.join(" & "),
None => response.exception.message.clone(),
};

Ok(ErrorResponse {
status_code: res.status_code,
code: response.code,
message: response.message,
reason: response.reason,
code: response.exception.message.to_string(),
message: response.exception.message.clone(),
reason: Some(reason),
})
}
}
Expand All @@ -130,6 +146,84 @@ impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::Payme
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Volt
{
fn get_url(
&self,
_req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}oauth", self.base_url(connectors)))
}

fn get_content_type(&self) -> &'static str {
"application/x-www-form-urlencoded"
}
fn get_headers(
&self,
_req: &types::RefreshTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
Ok(vec![(
headers::CONTENT_TYPE.to_string(),
types::RefreshTokenType::get_content_type(self)
.to_string()
.into(),
)])
}

fn get_request_body(
&self,
req: &types::RefreshTokenRouterData,
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
let req_obj = volt::VoltAuthUpdateRequest::try_from(req)?;
let volt_req = types::RequestBody::log_and_get_request_body(
&req_obj,
utils::Encode::<volt::VoltAuthUpdateRequest>::url_encode,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;

Ok(Some(volt_req))
}

fn build_request(
&self,
req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let req = Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.attach_default_headers()
.headers(types::RefreshTokenType::get_headers(self, req, connectors)?)
.url(&types::RefreshTokenType::get_url(self, req, connectors)?)
.body(types::RefreshTokenType::get_request_body(self, req)?)
.build(),
);
Ok(req)
}

fn handle_response(
&self,
data: &types::RefreshTokenRouterData,
res: Response,
) -> CustomResult<types::RefreshTokenRouterData, errors::ConnectorError> {
let response: volt::VoltAuthUpdateResponse = res
.response
.parse_struct("Volt VoltAuthUpdateResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}

fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}

impl
Expand Down Expand Up @@ -159,9 +253,9 @@ impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::P
fn get_url(
&self,
_req: &types::PaymentsAuthorizeRouterData,
_connectors: &settings::Connectors,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
Ok(format!("{}v2/payments", self.base_url(connectors)))
}

fn get_request_body(
Expand Down Expand Up @@ -244,10 +338,18 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe

fn get_url(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
req: &types::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let connector_payment_id = req
.request
.connector_transaction_id
.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
Ok(format!(
"{}payments/{connector_payment_id}",
self.base_url(connectors)
))
}

fn build_request(
Expand All @@ -270,7 +372,7 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: volt::VoltPaymentsResponse = res
let response: volt::VoltPsyncResponse = res
.response
.parse_struct("volt PaymentsSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Expand Down Expand Up @@ -381,10 +483,14 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon

fn get_url(
&self,
_req: &types::RefundsRouterData<api::Execute>,
_connectors: &settings::Connectors,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!(
"{}payments/{connector_payment_id}/request-refund",
self.base_url(connectors),
))
}

fn get_request_body(
Expand Down Expand Up @@ -448,64 +554,7 @@ impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsRespon
}

impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Volt {
fn get_headers(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, request::Maskable<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::RefundSyncRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
}

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)?)
.attach_default_headers()
.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: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: volt::RefundResponse =
res.response
.parse_struct("volt RefundSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
}

fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
//Volt does not support Refund Sync
}

#[async_trait::async_trait]
Expand Down
Loading

0 comments on commit 23bd364

Please sign in to comment.