feat(connector): [Trustpay] add authorize (cards 3ds, no3ds and bank redirects), refund, psync, rsync (#717)

Co-authored-by: Sangamesh <sangamesh.kulkarni@juspay.in>
Co-authored-by: sai harsha <sai.harsha@sai.harsha-MacBookPro>
Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
saiharsha-juspay
2023-03-13 17:23:39 +05:30
committed by GitHub
parent 230fcdd4e1
commit e102cae76c
18 changed files with 2002 additions and 11 deletions

View File

@ -251,6 +251,7 @@ pub struct Connectors {
pub stripe: ConnectorParams,
pub worldline: ConnectorParams,
pub worldpay: ConnectorParams,
pub trustpay: ConnectorParamsWithMoreUrls,
// Keep this field separate from the remaining fields
pub supported: SupportedConnectors,
@ -262,6 +263,13 @@ pub struct ConnectorParams {
pub base_url: String,
}
#[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)]
pub struct ConnectorParamsWithMoreUrls {
pub base_url: String,
pub base_url_bank_redirects: String,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct SchedulerSettings {

View File

@ -18,6 +18,7 @@ pub mod payu;
pub mod rapyd;
pub mod shift4;
pub mod stripe;
pub mod trustpay;
pub mod utils;
pub mod worldline;
pub mod worldpay;
@ -27,5 +28,6 @@ pub use self::{
authorizedotnet::Authorizedotnet, bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree,
checkout::Checkout, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv,
globalpay::Globalpay, klarna::Klarna, multisafepay::Multisafepay, nuvei::Nuvei, payu::Payu,
rapyd::Rapyd, shift4::Shift4, stripe::Stripe, worldline::Worldline, worldpay::Worldpay,
rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay, worldline::Worldline,
worldpay::Worldpay,
};

View File

@ -346,7 +346,7 @@ impl
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let req = stripe::PaymentIntentRequest::try_from(req)?;
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::encode(&req)
let stripe_req = utils::Encode::<stripe::PaymentIntentRequest>::url_encode(&req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(stripe_req))
}

View File

@ -0,0 +1,630 @@
mod transformers;
use std::fmt::Debug;
use base64::Engine;
use error_stack::{IntoReport, ResultExt};
use transformers as trustpay;
use crate::{
configs::settings,
consts,
core::{
errors::{self, CustomResult},
payments,
},
headers,
services::{self, ConnectorIntegration},
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
ErrorResponse, Response,
},
utils::{self, BytesExt},
};
#[derive(Debug, Clone)]
pub struct Trustpay;
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Trustpay
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> {
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => {
let token = req
.access_token
.clone()
.ok_or(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![
(
headers::CONTENT_TYPE.to_string(),
"application/json".to_owned(),
),
(
headers::AUTHORIZATION.to_string(),
format!("Bearer {}", token.token),
),
])
}
_ => {
let mut header = vec![(
headers::CONTENT_TYPE.to_string(),
self.get_content_type().to_string(),
)];
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
header.append(&mut api_key);
Ok(header)
}
}
}
}
impl ConnectorCommon for Trustpay {
fn id(&self) -> &'static str {
"trustpay"
}
fn common_get_content_type(&self) -> &'static str {
"application/x-www-form-urlencoded"
}
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
connectors.trustpay.base_url.as_ref()
}
fn get_auth_header(
&self,
auth_type: &types::ConnectorAuthType,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth = trustpay::TrustpayAuthType::try_from(auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
Ok(vec![(headers::X_API_KEY.to_string(), auth.api_key)])
}
fn build_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: trustpay::TrustpayErrorResponse = res
.response
.parse_struct("trustpay ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let default_error = trustpay::Errors {
code: 0,
description: consts::NO_ERROR_CODE.to_string(),
};
Ok(ErrorResponse {
status_code: res.status_code,
code: response.status.to_string(),
message: format!("{:?}", response.errors.first().unwrap_or(&default_error)),
reason: None,
})
}
}
impl api::Payment for Trustpay {}
impl api::PreVerify for Trustpay {}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::PaymentVoid for Trustpay {}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::ConnectorAccessToken for Trustpay {}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Trustpay
{
fn get_url(
&self,
_req: &types::RefreshTokenRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!(
"{}{}",
connectors.trustpay.base_url_bank_redirects, "api/oauth2/token"
))
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_headers(
&self,
req: &types::RefreshTokenRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth = trustpay::TrustpayAuthType::try_from(&req.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let auth_value = format!(
"Basic {}",
consts::BASE64_ENGINE.encode(format!("{}:{}", auth.project_id, auth.secret_key))
);
Ok(vec![
(
headers::CONTENT_TYPE.to_string(),
types::RefreshTokenType::get_content_type(self).to_string(),
),
(headers::AUTHORIZATION.to_string(), auth_value),
])
}
fn get_request_body(
&self,
req: &types::RefreshTokenRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let trustpay_req =
utils::Encode::<trustpay::TrustpayAuthUpdateRequest>::convert_and_url_encode(req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(trustpay_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)
.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: trustpay::TrustpayAuthUpdateResponse = res
.response
.parse_struct("trustpay TrustpayAuthUpdateResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
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: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: trustpay::TrustpayAccessTokenErrorResponse = res
.response
.parse_struct("Trustpay AccessTokenErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(ErrorResponse {
status_code: res.status_code,
code: response.result_info.result_code.to_string(),
message: response.result_info.additional_info.unwrap_or_default(),
reason: None,
})
}
}
impl api::PaymentSync for Trustpay {}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::PaymentsSyncRouterData,
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::PaymentsSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let id = req.request.connector_transaction_id.clone();
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}/{}",
connectors.trustpay.base_url_bank_redirects,
"api/Payments/Payment",
id.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
)),
_ => Ok(format!(
"{}{}/{}",
self.base_url(connectors),
"api/v1/instance",
id.get_connector_transaction_id()
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?
)),
}
}
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 get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
fn handle_response(
&self,
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayPaymentsResponse = res
.response
.parse_struct("trustpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
}
impl api::PaymentCapture for Trustpay {}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::PaymentSession for Trustpay {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Trustpay
{
}
impl api::PaymentAuthorize for Trustpay {}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Trustpay
{
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(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}",
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment"
)),
_ => Ok(format!(
"{}{}",
self.base_url(connectors),
"api/v1/purchase"
)),
}
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let trustpay_req = trustpay::TrustpayPaymentsRequest::try_from(req)?;
let trustpay_req_string = match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => {
utils::Encode::<trustpay::PaymentRequestBankRedirect>::encode_to_string_of_json(
&trustpay_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?
}
_ => utils::Encode::<trustpay::PaymentRequestCards>::url_encode(&trustpay_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
};
Ok(Some(trustpay_req_string))
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsAuthorizeType::get_url(
self, req, connectors,
)?)
.headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: trustpay::TrustpayPaymentsResponse = res
.response
.parse_struct("trustpay PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
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: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::Refund for Trustpay {}
impl api::RefundExecute for Trustpay {}
impl api::RefundSync for Trustpay {}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Trustpay
{
fn get_headers(
&self,
req: &types::RefundsRouterData<api::Execute>,
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::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}{}{}",
connectors.trustpay.base_url_bank_redirects,
"api/Payments/Payment/",
req.request.connector_transaction_id,
"/Refund"
)),
_ => Ok(format!("{}{}", self.base_url(connectors), "api/v1/Reverse")),
}
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let trustpay_req = trustpay::TrustpayRefundRequest::try_from(req)?;
let trustpay_req_string = match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => utils::Encode::<
trustpay::TrustpayRefundRequestBankRedirect,
>::encode_to_string_of_json(
&trustpay_req
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
_ => utils::Encode::<trustpay::TrustpayRefundRequestCards>::url_encode(&trustpay_req)
.change_context(errors::ConnectorError::RequestEncodingFailed)?,
};
Ok(Some(trustpay_req_string))
}
fn build_request(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
.headers(types::RefundExecuteType::get_headers(
self, req, connectors,
)?)
.body(types::RefundExecuteType::get_request_body(self, req)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::RefundsRouterData<api::Execute>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: trustpay::RefundResponse = res
.response
.parse_struct("trustpay RefundResponse")
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
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: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Trustpay {
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_url(
&self,
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let id = req
.request
.connector_refund_id
.to_owned()
.ok_or_else(|| errors::ConnectorError::MissingConnectorRefundID)?;
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}/{}",
connectors.trustpay.base_url_bank_redirects, "api/Payments/Payment", id
)),
_ => Ok(format!(
"{}{}/{}",
self.base_url(connectors),
"api/v1/instance",
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)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: trustpay::RefundResponse = res
.response
.parse_struct("trustpay RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
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: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
#[async_trait::async_trait]
impl api::IncomingWebhook for Trustpay {
fn get_webhook_object_reference_id(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<String, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_event_type(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_resource_object(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
}
impl services::ConnectorRedirectResponse for Trustpay {
fn get_flow_type(
&self,
query_params: &str,
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
let query =
serde_urlencoded::from_str::<transformers::TrustpayRedirectResponse>(query_params)
.into_report()
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
crate::logger::debug!(trustpay_redirect_response=?query);
Ok(query.status.map_or(
payments::CallConnectorAction::Trigger,
|status| match status.as_str() {
"SuccessOk" => payments::CallConnectorAction::StatusUpdate(
storage_models::enums::AttemptStatus::Charged,
),
_ => payments::CallConnectorAction::Trigger,
},
))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -222,6 +222,7 @@ pub enum CardIssuer {
pub trait CardData {
fn get_card_expiry_year_2_digit(&self) -> Secret<String>;
fn get_card_issuer(&self) -> Result<CardIssuer, Error>;
fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String;
}
impl CardData for api::Card {
@ -237,6 +238,15 @@ impl CardData for api::Card {
.map(|card| card.split_whitespace().collect());
get_card_issuer(card.peek().clone().as_str())
}
fn get_card_expiry_month_year_2_digit_with_delimiter(&self, delimiter: String) -> String {
let year = self.get_card_expiry_year_2_digit();
format!(
"{}{}{}",
self.card_exp_month.peek().clone(),
delimiter,
year.peek()
)
}
}
fn get_card_issuer(card_number: &str) -> Result<CardIssuer, Error> {

View File

@ -86,7 +86,7 @@ pub fn mk_add_card_request(
name_on_card: Some("John Doe".to_string().into()), // [#256]
nickname: Some("router".to_string()), //
};
let body = utils::Encode::<AddCardRequest<'_>>::encode(&add_card_req)
let body = utils::Encode::<AddCardRequest<'_>>::url_encode(&add_card_req)
.change_context(errors::VaultError::RequestEncodingFailed)?;
let mut url = locker.host.to_owned();
url.push_str("/card/addCard");
@ -139,7 +139,7 @@ pub fn mk_get_card_request<'a>(
card_id,
};
let body = utils::Encode::<GetCard<'_>>::encode(&get_card_req)
let body = utils::Encode::<GetCard<'_>>::url_encode(&get_card_req)
.change_context(errors::VaultError::RequestEncodingFailed)?;
let mut url = locker.host.to_owned();
url.push_str("/card/getCard");
@ -158,7 +158,7 @@ pub fn mk_delete_card_request<'a>(
merchant_id,
card_id,
};
let body = utils::Encode::<GetCard<'_>>::encode(&delete_card_req)
let body = utils::Encode::<GetCard<'_>>::url_encode(&delete_card_req)
.change_context(errors::VaultError::RequestEncodingFailed)?;
let mut url = locker.host.to_owned();
url.push_str("/card/deleteCard");

View File

@ -10,7 +10,7 @@ use crate::{
},
routes::AppState,
services,
types::{self, api as api_types, storage},
types::{self, api as api_types, storage, transformers::ForeignInto},
};
/// This function replaces the request and response type of routerdata with the
@ -82,7 +82,10 @@ pub async fn add_access_token<
merchant_account: &storage::MerchantAccount,
router_data: &types::RouterData<F, Req, Res>,
) -> RouterResult<types::AddAccessTokenResult> {
if connector.connector_name.supports_access_token() {
if connector
.connector_name
.supports_access_token(router_data.payment_method.foreign_into())
{
let merchant_id = &merchant_account.merchant_id;
let store = &*state.store;
let old_access_token = store

View File

@ -182,6 +182,7 @@ impl ConnectorData {
"worldline" => Ok(Box::new(&connector::Worldline)),
"worldpay" => Ok(Box::new(&connector::Worldpay)),
"multisafepay" => Ok(Box::new(&connector::Multisafepay)),
"trustpay" => Ok(Box::new(&connector::Trustpay)),
_ => Err(report!(errors::ConnectorError::InvalidConnectorName)
.attach_printable(format!("invalid connector name: {connector_name}")))
.change_context(errors::ApiErrorResponse::InternalServerError),