mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(connector): Plaid connector Integration (#3952)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -233,6 +233,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -488,6 +489,9 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = { country = "IN", currency = "INR" }
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.zen]
|
||||
credit = { not_available_flows = { capture_method = "manual" } }
|
||||
debit = { not_available_flows = { capture_method = "manual" } }
|
||||
|
||||
@ -72,6 +72,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -293,6 +294,9 @@ open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = {country = "IN", currency = "INR"}
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.worldpay]
|
||||
apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US"
|
||||
google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN"
|
||||
|
||||
@ -76,6 +76,7 @@ payone.base_url = "https://payment.payone.com/"
|
||||
paypal.base_url = "https://api-m.paypal.com/"
|
||||
payu.base_url = "https://secure.payu.com/api/"
|
||||
placetopay.base_url = "https://checkout.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://production.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -312,6 +313,9 @@ open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = {country = "IN", currency = "INR"}
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.worldpay]
|
||||
apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US"
|
||||
google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN"
|
||||
|
||||
@ -76,6 +76,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -316,6 +317,9 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = {country = "IN", currency = "INR"}
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.worldpay]
|
||||
apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US"
|
||||
google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN"
|
||||
|
||||
@ -141,6 +141,7 @@ cards = [
|
||||
"paypal",
|
||||
"payu",
|
||||
"placetopay",
|
||||
"plaid",
|
||||
"powertranz",
|
||||
"prophetpay",
|
||||
"shift4",
|
||||
@ -227,6 +228,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -345,6 +347,9 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = {country = "IN", currency = "INR"}
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.adyen]
|
||||
google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,GB,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" }
|
||||
apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" }
|
||||
|
||||
@ -162,6 +162,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -239,6 +240,7 @@ cards = [
|
||||
"paypal",
|
||||
"payu",
|
||||
"placetopay",
|
||||
"plaid",
|
||||
"powertranz",
|
||||
"prophetpay",
|
||||
"shift4",
|
||||
@ -355,6 +357,9 @@ open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,M
|
||||
[pm_filters.razorpay]
|
||||
upi_collect = { country = "IN", currency = "INR" }
|
||||
|
||||
[pm_filters.plaid]
|
||||
open_banking_pis = {currency = "EUR,GBP"}
|
||||
|
||||
[pm_filters.zen]
|
||||
credit = { not_available_flows = { capture_method = "manual" } }
|
||||
debit = { not_available_flows = { capture_method = "manual" } }
|
||||
|
||||
@ -342,6 +342,7 @@ pub struct PaymentsPostProcessingData {
|
||||
pub payment_method_data: PaymentMethodData,
|
||||
pub customer_id: Option<id_type::CustomerId>,
|
||||
pub connector_transaction_id: Option<String>,
|
||||
pub country: Option<common_enums::CountryAlpha2>,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<RouterData<F, PaymentsAuthorizeData, response_types::PaymentsResponseData>>
|
||||
@ -362,6 +363,11 @@ impl<F> TryFrom<RouterData<F, PaymentsAuthorizeData, response_types::PaymentsRes
|
||||
_ => None,
|
||||
},
|
||||
customer_id: data.request.customer_id,
|
||||
country: data
|
||||
.address
|
||||
.get_payment_billing()
|
||||
.and_then(|bl| bl.address.as_ref())
|
||||
.and_then(|address| address.country),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ pub struct Connectors {
|
||||
pub paypal: ConnectorParams,
|
||||
pub payu: ConnectorParams,
|
||||
pub placetopay: ConnectorParams,
|
||||
pub plaid: ConnectorParams,
|
||||
pub powertranz: ConnectorParams,
|
||||
pub prophetpay: ConnectorParams,
|
||||
pub rapyd: ConnectorParams,
|
||||
|
||||
@ -448,7 +448,6 @@ impl<F, T>
|
||||
pub struct PlaidAuthType {
|
||||
pub client_id: Secret<String>,
|
||||
pub secret: Secret<String>,
|
||||
pub merchant_data: Option<types::MerchantRecipientData>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType {
|
||||
@ -458,16 +457,6 @@ impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType {
|
||||
types::ConnectorAuthType::BodyKey { client_id, secret } => Ok(Self {
|
||||
client_id: client_id.to_owned(),
|
||||
secret: secret.to_owned(),
|
||||
merchant_data: None,
|
||||
}),
|
||||
types::ConnectorAuthType::OpenBankingAuth {
|
||||
api_key,
|
||||
key1,
|
||||
merchant_data,
|
||||
} => Ok(Self {
|
||||
client_id: api_key.to_owned(),
|
||||
secret: key1.to_owned(),
|
||||
merchant_data: Some(merchant_data.clone()),
|
||||
}),
|
||||
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
|
||||
}
|
||||
|
||||
@ -218,11 +218,6 @@ pub enum ConnectorAuthType {
|
||||
client_id: Secret<String>,
|
||||
secret: Secret<String>,
|
||||
},
|
||||
OpenBankingAuth {
|
||||
api_key: Secret<String>,
|
||||
key1: Secret<String>,
|
||||
merchant_data: MerchantRecipientData,
|
||||
},
|
||||
#[default]
|
||||
NoKey,
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ pub mod payone;
|
||||
pub mod paypal;
|
||||
pub mod payu;
|
||||
pub mod placetopay;
|
||||
pub mod plaid;
|
||||
pub mod powertranz;
|
||||
pub mod prophetpay;
|
||||
pub mod rapyd;
|
||||
@ -81,7 +82,7 @@ pub use self::{
|
||||
iatapay::Iatapay, itaubank::Itaubank, klarna::Klarna, mifinity::Mifinity, mollie::Mollie,
|
||||
multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nmi::Nmi, noon::Noon,
|
||||
nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, payone::Payone,
|
||||
paypal::Paypal, payu::Payu, placetopay::Placetopay, powertranz::Powertranz,
|
||||
paypal::Paypal, payu::Payu, placetopay::Placetopay, plaid::Plaid, powertranz::Powertranz,
|
||||
prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, riskified::Riskified, shift4::Shift4,
|
||||
signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, threedsecureio::Threedsecureio,
|
||||
trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline,
|
||||
|
||||
458
crates/router/src/connector/plaid.rs
Normal file
458
crates/router/src/connector/plaid.rs
Normal file
@ -0,0 +1,458 @@
|
||||
pub mod transformers;
|
||||
|
||||
use common_utils::types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector};
|
||||
use error_stack::ResultExt;
|
||||
use transformers as plaid;
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
connector::utils as connector_utils,
|
||||
core::errors::{self, CustomResult},
|
||||
events::connector_api_logs::ConnectorEvent,
|
||||
headers,
|
||||
services::{
|
||||
self,
|
||||
request::{self, Mask},
|
||||
ConnectorIntegration, ConnectorValidation,
|
||||
},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
ErrorResponse, RequestContent, Response,
|
||||
},
|
||||
utils::BytesExt,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Plaid {
|
||||
amount_converter: &'static (dyn AmountConvertor<Output = FloatMajorUnit> + Sync),
|
||||
}
|
||||
|
||||
impl Plaid {
|
||||
pub fn new() -> &'static Self {
|
||||
&Self {
|
||||
amount_converter: &FloatMajorUnitForConnector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Plaid {}
|
||||
impl api::PaymentSession for Plaid {}
|
||||
impl api::ConnectorAccessToken for Plaid {}
|
||||
impl api::MandateSetup for Plaid {}
|
||||
impl api::PaymentAuthorize for Plaid {}
|
||||
impl api::PaymentSync for Plaid {}
|
||||
impl api::PaymentCapture for Plaid {}
|
||||
impl api::PaymentVoid for Plaid {}
|
||||
impl api::Refund for Plaid {}
|
||||
impl api::RefundExecute for Plaid {}
|
||||
impl api::RefundSync for Plaid {}
|
||||
impl api::PaymentToken for Plaid {}
|
||||
impl api::PaymentsPostProcessing for Plaid {}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::PaymentMethodToken,
|
||||
types::PaymentMethodTokenizationData,
|
||||
types::PaymentsResponseData,
|
||||
> for Plaid
|
||||
{
|
||||
// Not Implemented (R)
|
||||
}
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Plaid
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
{
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let mut header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
self.get_content_type().to_string().into(),
|
||||
)];
|
||||
let mut auth = self.get_auth_header(&req.connector_auth_type)?;
|
||||
header.append(&mut auth);
|
||||
Ok(header)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Plaid {
|
||||
fn id(&self) -> &'static str {
|
||||
"plaid"
|
||||
}
|
||||
|
||||
fn get_currency_unit(&self) -> api::CurrencyUnit {
|
||||
api::CurrencyUnit::Base
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
"application/json"
|
||||
}
|
||||
|
||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||
connectors.plaid.base_url.as_ref()
|
||||
}
|
||||
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
|
||||
let auth = plaid::PlaidAuthType::try_from(auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
let client_id = auth.client_id.into_masked();
|
||||
let secret = auth.secret.into_masked();
|
||||
|
||||
Ok(vec![
|
||||
("PLAID-CLIENT-ID".to_string(), client_id),
|
||||
("PLAID-SECRET".to_string(), secret),
|
||||
])
|
||||
}
|
||||
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: plaid::PlaidErrorResponse =
|
||||
res.response
|
||||
.parse_struct("PlaidErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response
|
||||
.error_code
|
||||
.unwrap_or(crate::consts::NO_ERROR_CODE.to_string()),
|
||||
message: response.error_message,
|
||||
reason: response.display_message,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorValidation for Plaid {
|
||||
//TODO: implement functions when support enabled
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Plaid
|
||||
{
|
||||
//TODO: implement sessions flow
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
||||
for Plaid
|
||||
{
|
||||
}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::SetupMandate,
|
||||
types::SetupMandateRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> for Plaid
|
||||
{
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Plaid
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
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::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}/payment_initiation/payment/create",
|
||||
self.base_url(connectors)
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let amount = connector_utils::convert_amount(
|
||||
self.amount_converter,
|
||||
req.request.minor_amount,
|
||||
req.request.currency,
|
||||
)?;
|
||||
let connector_router_data = plaid::PlaidRouterData::from((amount, req));
|
||||
let connector_req = plaid::PlaidPaymentsRequest::try_from(&connector_router_data)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
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,
|
||||
)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsAuthorizeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::PaymentsAuthorizeType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsAuthorizeRouterData,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||
let response: plaid::PlaidPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("PlaidPaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Plaid
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
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_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = plaid::PlaidSyncRequest::try_from(req)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}/payment_initiation/payment/get",
|
||||
self.base_url(connectors)
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
||||
.set_body(types::PaymentsSyncType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: plaid::PlaidSyncResponse = res
|
||||
.response
|
||||
.parse_struct("PlaidSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl
|
||||
ConnectorIntegration<
|
||||
api::PostProcessing,
|
||||
types::PaymentsPostProcessingData,
|
||||
types::PaymentsResponseData,
|
||||
> for Plaid
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsPostProcessingRouterData,
|
||||
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::PaymentsPostProcessingRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!("{}/link/token/create", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsPostProcessingRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
||||
let connector_req = plaid::PlaidLinkTokenRequest::try_from(req)?;
|
||||
Ok(RequestContent::Json(Box::new(connector_req)))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsPostProcessingRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsPostProcessingType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.attach_default_headers()
|
||||
.headers(types::PaymentsPostProcessingType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.set_body(types::PaymentsPostProcessingType::get_request_body(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsPostProcessingRouterData,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsPostProcessingRouterData, errors::ConnectorError> {
|
||||
let response: plaid::PlaidLinkTokenResponse = res
|
||||
.response
|
||||
.parse_struct("PlaidLinkTokenResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Response,
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res, event_builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Plaid
|
||||
{
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Plaid
|
||||
{
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Plaid {}
|
||||
|
||||
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Plaid {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Plaid {
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
||||
Err((errors::ConnectorError::WebhooksNotImplemented).into())
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err((errors::ConnectorError::WebhooksNotImplemented).into())
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
|
||||
Err((errors::ConnectorError::WebhooksNotImplemented).into())
|
||||
}
|
||||
}
|
||||
389
crates/router/src/connector/plaid/transformers.rs
Normal file
389
crates/router/src/connector/plaid/transformers.rs
Normal file
@ -0,0 +1,389 @@
|
||||
use common_enums::Currency;
|
||||
use common_utils::types::FloatMajorUnit;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{PeekInterface, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
connector::utils::is_payment_failure,
|
||||
core::errors,
|
||||
types::{self, api, domain, storage::enums},
|
||||
};
|
||||
|
||||
pub struct PlaidRouterData<T> {
|
||||
pub amount: FloatMajorUnit,
|
||||
pub router_data: T,
|
||||
}
|
||||
|
||||
impl<T> From<(FloatMajorUnit, T)> for PlaidRouterData<T> {
|
||||
fn from((amount, item): (FloatMajorUnit, T)) -> Self {
|
||||
Self {
|
||||
amount,
|
||||
router_data: item,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct PlaidPaymentsRequest {
|
||||
amount: PlaidAmount,
|
||||
recipient_id: String,
|
||||
reference: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
schedule: Option<PlaidSchedule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
options: Option<PlaidOptions>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidAmount {
|
||||
currency: Currency,
|
||||
value: FloatMajorUnit,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidSchedule {
|
||||
interval: String,
|
||||
interval_execution_day: String,
|
||||
start_date: String,
|
||||
end_date: Option<String>,
|
||||
adjusted_start_date: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidOptions {
|
||||
request_refund_details: bool,
|
||||
iban: Option<Secret<String>>,
|
||||
bacs: Option<PlaidBacs>,
|
||||
scheme: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidBacs {
|
||||
account: Secret<String>,
|
||||
sort_code: Secret<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PlaidLinkTokenRequest {
|
||||
client_name: String,
|
||||
country_codes: Vec<String>,
|
||||
language: String,
|
||||
products: Vec<String>,
|
||||
user: User,
|
||||
payment_initiation: PlaidPaymentInitiation,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub client_user_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidPaymentInitiation {
|
||||
payment_id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&PlaidRouterData<&types::PaymentsAuthorizeRouterData>> for PlaidPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: &PlaidRouterData<&types::PaymentsAuthorizeRouterData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.router_data.request.payment_method_data.clone() {
|
||||
domain::PaymentMethodData::OpenBanking(ref data) => match data {
|
||||
domain::OpenBankingData::OpenBankingPIS { .. } => {
|
||||
let amount = item.amount;
|
||||
let currency = item.router_data.request.currency;
|
||||
let payment_id = item.router_data.payment_id.clone();
|
||||
let id_len = payment_id.len();
|
||||
let reference = if id_len > 18 {
|
||||
payment_id.get(id_len - 18..id_len).map(|id| id.to_string())
|
||||
} else {
|
||||
Some(payment_id)
|
||||
}
|
||||
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "payment_id",
|
||||
})?;
|
||||
let recipient_val = item
|
||||
.router_data
|
||||
.connector_meta_data
|
||||
.as_ref()
|
||||
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "connector_customer",
|
||||
})?
|
||||
.peek()
|
||||
.clone();
|
||||
|
||||
let recipient_type =
|
||||
serde_json::from_value::<types::MerchantRecipientData>(recipient_val)
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
let recipient_id = match recipient_type {
|
||||
types::MerchantRecipientData::ConnectorRecipientId(id) => {
|
||||
Ok(id.peek().to_string())
|
||||
}
|
||||
_ => Err(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "ConnectorRecipientId",
|
||||
}),
|
||||
}?;
|
||||
|
||||
Ok(Self {
|
||||
amount: PlaidAmount {
|
||||
currency,
|
||||
value: amount,
|
||||
},
|
||||
reference,
|
||||
recipient_id,
|
||||
schedule: None,
|
||||
options: None,
|
||||
})
|
||||
}
|
||||
},
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSyncRouterData> for PlaidSyncRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSyncRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.connector_transaction_id {
|
||||
types::ResponseId::ConnectorTransactionId(ref id) => Ok(Self {
|
||||
payment_id: id.clone(),
|
||||
}),
|
||||
_ => Err((errors::ConnectorError::MissingConnectorTransactionID).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsPostProcessingRouterData> for PlaidLinkTokenRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsPostProcessingRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.payment_method_data.clone() {
|
||||
domain::PaymentMethodData::OpenBanking(ref data) => match data {
|
||||
domain::OpenBankingData::OpenBankingPIS { .. } => Ok(Self {
|
||||
client_name: "Hyperswitch".to_string(),
|
||||
country_codes: item
|
||||
.request
|
||||
.country
|
||||
.map(|code| vec![code.to_string()])
|
||||
.ok_or(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "billing.address.country",
|
||||
})?,
|
||||
language: "en".to_string(),
|
||||
products: vec!["payment_initiation".to_string()],
|
||||
user: User {
|
||||
client_user_id: item
|
||||
.request
|
||||
.customer_id
|
||||
.clone()
|
||||
.map(|id| id.get_string_repr().to_string())
|
||||
.unwrap_or("default cust".to_string()),
|
||||
},
|
||||
payment_initiation: PlaidPaymentInitiation {
|
||||
payment_id: item
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?,
|
||||
},
|
||||
}),
|
||||
},
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PlaidAuthType {
|
||||
pub client_id: Secret<String>,
|
||||
pub secret: Secret<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::ConnectorAuthType> for PlaidAuthType {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
match auth_type {
|
||||
types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self {
|
||||
client_id: api_key.to_owned(),
|
||||
secret: key1.to_owned(),
|
||||
}),
|
||||
_ => Err(errors::ConnectorError::FailedToObtainAuthType.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[derive(strum::Display)]
|
||||
pub enum PlaidPaymentStatus {
|
||||
PaymentStatusInputNeeded,
|
||||
PaymentStatusInitiated,
|
||||
PaymentStatusInsufficientFunds,
|
||||
PaymentStatusFailed,
|
||||
PaymentStatusBlocked,
|
||||
PaymentStatusCancelled,
|
||||
PaymentStatusExecuted,
|
||||
PaymentStatusSettled,
|
||||
PaymentStatusEstablished,
|
||||
PaymentStatusRejected,
|
||||
PaymentStatusAuthorising,
|
||||
}
|
||||
|
||||
impl From<PlaidPaymentStatus> for enums::AttemptStatus {
|
||||
fn from(item: PlaidPaymentStatus) -> Self {
|
||||
match item {
|
||||
PlaidPaymentStatus::PaymentStatusAuthorising => Self::Authorizing,
|
||||
PlaidPaymentStatus::PaymentStatusBlocked => Self::AuthorizationFailed,
|
||||
PlaidPaymentStatus::PaymentStatusCancelled => Self::Voided,
|
||||
PlaidPaymentStatus::PaymentStatusEstablished => Self::Authorized,
|
||||
PlaidPaymentStatus::PaymentStatusExecuted => Self::Authorized,
|
||||
PlaidPaymentStatus::PaymentStatusFailed => Self::Failure,
|
||||
PlaidPaymentStatus::PaymentStatusInitiated => Self::AuthenticationPending,
|
||||
PlaidPaymentStatus::PaymentStatusInputNeeded => Self::AuthenticationPending,
|
||||
PlaidPaymentStatus::PaymentStatusInsufficientFunds => Self::AuthorizationFailed,
|
||||
PlaidPaymentStatus::PaymentStatusRejected => Self::AuthorizationFailed,
|
||||
PlaidPaymentStatus::PaymentStatusSettled => Self::Charged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PlaidPaymentsResponse {
|
||||
status: PlaidPaymentStatus,
|
||||
payment_id: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, PlaidPaymentsResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, PlaidPaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let status = enums::AttemptStatus::from(item.response.status.clone());
|
||||
Ok(Self {
|
||||
status,
|
||||
response: if is_payment_failure(status) {
|
||||
Err(types::ErrorResponse {
|
||||
// populating status everywhere as plaid only sends back a status
|
||||
code: item.response.status.clone().to_string(),
|
||||
message: item.response.status.clone().to_string(),
|
||||
reason: Some(item.response.status.to_string()),
|
||||
status_code: item.http_code,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: Some(item.response.payment_id),
|
||||
})
|
||||
} else {
|
||||
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
item.response.payment_id.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: Some(item.response.payment_id),
|
||||
incremental_authorization_allowed: None,
|
||||
charge_id: None,
|
||||
})
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidLinkTokenResponse {
|
||||
link_token: String,
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, PlaidLinkTokenResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let session_token = Some(api::OpenBankingSessionToken {
|
||||
open_banking_session_token: item.response.link_token,
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::AuthenticationPending,
|
||||
response: Ok(types::PaymentsResponseData::PostProcessingResponse { session_token }),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidSyncRequest {
|
||||
payment_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PlaidSyncResponse {
|
||||
payment_id: String,
|
||||
amount: PlaidAmount,
|
||||
status: PlaidPaymentStatus,
|
||||
recipient_id: String,
|
||||
reference: String,
|
||||
last_status_update: String,
|
||||
adjusted_reference: Option<String>,
|
||||
schedule: Option<PlaidSchedule>,
|
||||
iban: Option<Secret<String>>,
|
||||
bacs: Option<PlaidBacs>,
|
||||
scheme: Option<String>,
|
||||
adjusted_scheme: Option<String>,
|
||||
request_id: String,
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, PlaidSyncResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, PlaidSyncResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let status = enums::AttemptStatus::from(item.response.status.clone());
|
||||
Ok(Self {
|
||||
status,
|
||||
response: if is_payment_failure(status) {
|
||||
Err(types::ErrorResponse {
|
||||
// populating status everywhere as plaid only sends back a status
|
||||
code: item.response.status.clone().to_string(),
|
||||
message: item.response.status.clone().to_string(),
|
||||
reason: Some(item.response.status.to_string()),
|
||||
status_code: item.http_code,
|
||||
attempt_status: None,
|
||||
connector_transaction_id: Some(item.response.payment_id),
|
||||
})
|
||||
} else {
|
||||
Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(
|
||||
item.response.payment_id.clone(),
|
||||
),
|
||||
redirection_data: None,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: Some(item.response.payment_id),
|
||||
incremental_authorization_allowed: None,
|
||||
charge_id: None,
|
||||
})
|
||||
},
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PlaidErrorResponse {
|
||||
pub display_message: Option<String>,
|
||||
pub error_code: Option<String>,
|
||||
pub error_message: String,
|
||||
pub error_type: Option<String>,
|
||||
}
|
||||
@ -1256,6 +1256,8 @@ pub async fn create_payment_connector(
|
||||
expected_format: "auth_type and api_key".to_string(),
|
||||
})?;
|
||||
|
||||
validate_auth_and_metadata_type(req.connector_name, &auth, &req.metadata)?;
|
||||
|
||||
let merchant_recipient_data = if let Some(data) = &req.additional_merchant_data {
|
||||
Some(
|
||||
process_open_banking_connectors(
|
||||
@ -1280,8 +1282,6 @@ pub async fn create_payment_connector(
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get MerchantRecipientData")?;
|
||||
|
||||
validate_auth_and_metadata_type(req.connector_name, &auth, &req.metadata)?;
|
||||
|
||||
let frm_configs = get_frm_config_as_secret(req.frm_configs);
|
||||
|
||||
// The purpose of this merchant account update is just to update the
|
||||
|
||||
@ -699,7 +699,8 @@ default_imp_for_new_connector_integration_payment!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_refund {
|
||||
@ -784,7 +785,8 @@ default_imp_for_new_connector_integration_refund!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_connector_access_token {
|
||||
@ -864,7 +866,8 @@ default_imp_for_new_connector_integration_connector_access_token!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_accept_dispute {
|
||||
@ -966,7 +969,8 @@ default_imp_for_new_connector_integration_accept_dispute!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_defend_dispute {
|
||||
@ -1050,7 +1054,8 @@ default_imp_for_new_connector_integration_defend_dispute!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
default_imp_for_new_connector_integration_submit_evidence!(
|
||||
connector::Aci,
|
||||
@ -1118,7 +1123,8 @@ default_imp_for_new_connector_integration_submit_evidence!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_file_upload {
|
||||
@ -1213,7 +1219,8 @@ default_imp_for_new_connector_integration_file_upload!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_payouts {
|
||||
@ -1290,7 +1297,8 @@ default_imp_for_new_connector_integration_payouts!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1377,7 +1385,8 @@ default_imp_for_new_connector_integration_payouts_create!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1464,7 +1473,8 @@ default_imp_for_new_connector_integration_payouts_eligibility!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1551,7 +1561,8 @@ default_imp_for_new_connector_integration_payouts_fulfill!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1638,7 +1649,8 @@ default_imp_for_new_connector_integration_payouts_cancel!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1725,7 +1737,8 @@ default_imp_for_new_connector_integration_payouts_quote!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1812,7 +1825,8 @@ default_imp_for_new_connector_integration_payouts_recipient!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1899,7 +1913,8 @@ default_imp_for_new_connector_integration_payouts_sync!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "payouts")]
|
||||
@ -1986,7 +2001,8 @@ default_imp_for_new_connector_integration_payouts_recipient_account!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_webhook_source_verification {
|
||||
@ -2071,7 +2087,8 @@ default_imp_for_new_connector_integration_webhook_source_verification!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_frm {
|
||||
@ -2148,7 +2165,8 @@ default_imp_for_new_connector_integration_frm!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
@ -2235,7 +2253,8 @@ default_imp_for_new_connector_integration_frm_sale!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
@ -2322,7 +2341,8 @@ default_imp_for_new_connector_integration_frm_checkout!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
@ -2409,7 +2429,8 @@ default_imp_for_new_connector_integration_frm_transaction!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
@ -2496,7 +2517,8 @@ default_imp_for_new_connector_integration_frm_fulfillment!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
#[cfg(feature = "frm")]
|
||||
@ -2583,7 +2605,8 @@ default_imp_for_new_connector_integration_frm_record_return!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_revoking_mandates {
|
||||
@ -2667,7 +2690,8 @@ default_imp_for_new_connector_integration_revoking_mandates!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
macro_rules! default_imp_for_new_connector_integration_connector_authentication {
|
||||
@ -2779,5 +2803,6 @@ default_imp_for_new_connector_integration_connector_authentication!(
|
||||
connector::Worldline,
|
||||
connector::Worldpay,
|
||||
connector::Zen,
|
||||
connector::Zsl
|
||||
connector::Zsl,
|
||||
connector::Plaid
|
||||
);
|
||||
|
||||
@ -221,6 +221,7 @@ default_imp_for_complete_authorize!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Rapyd,
|
||||
connector::Razorpay,
|
||||
connector::Riskified,
|
||||
@ -311,6 +312,7 @@ default_imp_for_webhook_source_verification!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -406,6 +408,7 @@ default_imp_for_create_customer!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -489,6 +492,7 @@ default_imp_for_connector_redirect_response!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -565,6 +569,7 @@ default_imp_for_connector_request_id!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -662,6 +667,7 @@ default_imp_for_accept_dispute!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -778,6 +784,7 @@ default_imp_for_file_upload!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -872,6 +879,7 @@ default_imp_for_submit_evidence!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -966,6 +974,7 @@ default_imp_for_defend_dispute!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1070,6 +1079,7 @@ default_imp_for_pre_processing_steps!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1222,6 +1232,7 @@ default_imp_for_payouts!(
|
||||
connector::Payme,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1314,6 +1325,7 @@ default_imp_for_payouts_create!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1408,6 +1420,7 @@ default_imp_for_payouts_retrieve!(
|
||||
connector::Payone,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1506,6 +1519,7 @@ default_imp_for_payouts_eligibility!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1596,6 +1610,7 @@ default_imp_for_payouts_fulfill!(
|
||||
connector::Payme,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1689,6 +1704,7 @@ default_imp_for_payouts_cancel!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1783,6 +1799,7 @@ default_imp_for_payouts_quote!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1878,6 +1895,7 @@ default_imp_for_payouts_recipient!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -1976,6 +1994,7 @@ default_imp_for_payouts_recipient_account!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2071,6 +2090,7 @@ default_imp_for_approve!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2167,6 +2187,7 @@ default_imp_for_reject!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2247,6 +2268,7 @@ default_imp_for_fraud_check!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2343,6 +2365,7 @@ default_imp_for_frm_sale!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2439,6 +2462,7 @@ default_imp_for_frm_checkout!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2535,6 +2559,7 @@ default_imp_for_frm_transaction!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2631,6 +2656,7 @@ default_imp_for_frm_fulfillment!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2727,6 +2753,7 @@ default_imp_for_frm_record_return!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2820,6 +2847,7 @@ default_imp_for_incremental_authorization!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -2912,6 +2940,7 @@ default_imp_for_revoking_mandates!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -3065,6 +3094,7 @@ default_imp_for_connector_authentication!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
@ -3156,6 +3186,7 @@ default_imp_for_authorize_session_token!(
|
||||
connector::Paypal,
|
||||
connector::Payu,
|
||||
connector::Placetopay,
|
||||
connector::Plaid,
|
||||
connector::Powertranz,
|
||||
connector::Prophetpay,
|
||||
connector::Rapyd,
|
||||
|
||||
@ -210,7 +210,6 @@ impl ForeignTryFrom<&types::ConnectorAuthType> for PlaidAuthType {
|
||||
Ok::<Self, errors::ConnectorError>(Self {
|
||||
client_id: api_key.to_owned(),
|
||||
secret: key1.to_owned(),
|
||||
merchant_data: None,
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::FailedToObtainAuthType),
|
||||
|
||||
@ -500,8 +500,10 @@ impl ConnectorData {
|
||||
enums::Connector::Volt => Ok(ConnectorEnum::Old(Box::new(&connector::Volt))),
|
||||
enums::Connector::Zen => Ok(ConnectorEnum::Old(Box::new(&connector::Zen))),
|
||||
enums::Connector::Zsl => Ok(ConnectorEnum::Old(Box::new(&connector::Zsl))),
|
||||
enums::Connector::Plaid => {
|
||||
Ok(ConnectorEnum::Old(Box::new(connector::Plaid::new())))
|
||||
}
|
||||
enums::Connector::Signifyd
|
||||
| enums::Connector::Plaid
|
||||
| enums::Connector::Riskified
|
||||
| enums::Connector::Gpayments
|
||||
| enums::Connector::Threedsecureio => {
|
||||
|
||||
@ -4,8 +4,8 @@ pub use hyperswitch_domain_models::payment_method_data::{
|
||||
CardToken, CashappQr, CryptoData, GcashRedirection, GiftCardData, GiftCardDetails,
|
||||
GoPayRedirection, GooglePayPaymentMethodInfo, GooglePayRedirectData,
|
||||
GooglePayThirdPartySdkData, GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData,
|
||||
KakaoPayRedirection, MbWayRedirection, MifinityData, PayLaterData, PaymentMethodData,
|
||||
RealTimePaymentData, SamsungPayWalletData, SepaAndBacsBillingDetails, SwishQrData,
|
||||
TouchNGoRedirection, UpiCollectData, UpiData, UpiIntentData, VoucherData, WalletData,
|
||||
WeChatPayQr,
|
||||
KakaoPayRedirection, MbWayRedirection, MifinityData, OpenBankingData, PayLaterData,
|
||||
PaymentMethodData, RealTimePaymentData, SamsungPayWalletData, SepaAndBacsBillingDetails,
|
||||
SwishQrData, TouchNGoRedirection, UpiCollectData, UpiData, UpiIntentData, VoucherData,
|
||||
WalletData, WeChatPayQr,
|
||||
};
|
||||
|
||||
@ -274,11 +274,7 @@ impl ForeignTryFrom<api_enums::Connector> for common_enums::RoutableConnectors {
|
||||
api_enums::Connector::Paypal => Self::Paypal,
|
||||
api_enums::Connector::Payu => Self::Payu,
|
||||
api_models::enums::Connector::Placetopay => Self::Placetopay,
|
||||
api_enums::Connector::Plaid => {
|
||||
Err(common_utils::errors::ValidationError::InvalidValue {
|
||||
message: "plaid is not a routable connector".to_string(),
|
||||
})?
|
||||
}
|
||||
api_enums::Connector::Plaid => Self::Plaid,
|
||||
api_enums::Connector::Powertranz => Self::Powertranz,
|
||||
api_enums::Connector::Prophetpay => Self::Prophetpay,
|
||||
api_enums::Connector::Rapyd => Self::Rapyd,
|
||||
|
||||
@ -57,6 +57,7 @@ mod payone;
|
||||
mod paypal;
|
||||
mod payu;
|
||||
mod placetopay;
|
||||
mod plaid;
|
||||
mod powertranz;
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
mod prophetpay;
|
||||
|
||||
421
crates/router/tests/connectors/plaid.rs
Normal file
421
crates/router/tests/connectors/plaid.rs
Normal file
@ -0,0 +1,421 @@
|
||||
use masking::Secret;
|
||||
use router::types::{self, api, domain, storage::enums};
|
||||
use test_utils::connector_auth;
|
||||
|
||||
use crate::utils::{self, Connector, ConnectorActions};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct PlaidTest;
|
||||
impl ConnectorActions for PlaidTest {}
|
||||
|
||||
static CONNECTOR: PlaidTest = PlaidTest {};
|
||||
|
||||
impl Connector for PlaidTest {
|
||||
fn get_data(&self) -> api::ConnectorData {
|
||||
use router::connector::Plaid;
|
||||
utils::construct_connector_data_old(
|
||||
Box::new(Plaid::new()),
|
||||
types::Connector::Plaid,
|
||||
api::GetToken::Connector,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||
utils::to_connector_auth_type(
|
||||
connector_auth::ConnectorAuthentication::new()
|
||||
.plaid
|
||||
.expect("Missing connector authentication configuration")
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"plaid".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_payment_info() -> Option<utils::PaymentInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
None
|
||||
}
|
||||
|
||||
// Cards Positive Tests
|
||||
// Creates a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_only_authorize_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
}
|
||||
|
||||
// Captures a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info())
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Partially captures a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_capture_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_capture_payment(
|
||||
payment_method_details(),
|
||||
Some(types::PaymentsCaptureData {
|
||||
amount_to_capture: 50,
|
||||
..utils::PaymentCaptureType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.expect("Capture payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Synchronizes a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_authorized_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.authorize_payment(payment_method_details(), get_default_payment_info())
|
||||
.await
|
||||
.expect("Authorize payment response");
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Authorized,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.expect("PSync response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized,);
|
||||
}
|
||||
|
||||
// Voids a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_void_authorized_payment() {
|
||||
let response = CONNECTOR
|
||||
.authorize_and_void_payment(
|
||||
payment_method_details(),
|
||||
Some(types::PaymentsCancelData {
|
||||
connector_transaction_id: String::from(""),
|
||||
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.expect("Void payment response");
|
||||
assert_eq!(response.status, enums::AttemptStatus::Voided);
|
||||
}
|
||||
|
||||
// Refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_manually_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Partially refunds a payment using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_manually_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
None,
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Synchronizes a refund using the manual capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_manually_captured_refund() {
|
||||
let refund_response = CONNECTOR
|
||||
.capture_payment_and_refund(
|
||||
payment_method_details(),
|
||||
None,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_make_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
}
|
||||
|
||||
// Synchronizes a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_auto_captured_payment() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
let response = CONNECTOR
|
||||
.psync_retry_till_status_matches(
|
||||
enums::AttemptStatus::Charged,
|
||||
Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: types::ResponseId::ConnectorTransactionId(
|
||||
txn_id.unwrap(),
|
||||
),
|
||||
capture_method: Some(enums::CaptureMethod::Automatic),
|
||||
..Default::default()
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status, enums::AttemptStatus::Charged,);
|
||||
}
|
||||
|
||||
// Refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_auto_captured_payment() {
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Partially refunds a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_partially_refund_succeeded_payment() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
refund_response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_refund_succeeded_payment_multiple_times() {
|
||||
CONNECTOR
|
||||
.make_payment_and_multiple_refund(
|
||||
payment_method_details(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 50,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Synchronizes a refund using the automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_sync_refund() {
|
||||
let refund_response = CONNECTOR
|
||||
.make_payment_and_refund(payment_method_details(), None, get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
let response = CONNECTOR
|
||||
.rsync_retry_till_status_matches(
|
||||
enums::RefundStatus::Success,
|
||||
refund_response.response.unwrap().connector_refund_id,
|
||||
None,
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
// Cards Negative scenarios
|
||||
// Creates a payment with incorrect CVC.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_cvc() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: domain::PaymentMethodData::Card(domain::Card {
|
||||
card_cvc: Secret::new("12345".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Invalid card cvc.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect expiry month.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_invalid_exp_month() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: domain::PaymentMethodData::Card(domain::Card {
|
||||
card_exp_month: Secret::new("20".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration month is invalid.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a payment with incorrect expiry year.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||
let response = CONNECTOR
|
||||
.make_payment(
|
||||
Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: domain::PaymentMethodData::Card(domain::Card {
|
||||
card_exp_year: Secret::new("2000".to_string()),
|
||||
..utils::CCardType::default().0
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Your card's expiration year is invalid.".to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Voids a payment using automatic capture flow (Non 3DS).
|
||||
#[actix_web::test]
|
||||
async fn should_fail_void_payment_for_auto_capture() {
|
||||
let authorize_response = CONNECTOR
|
||||
.make_payment(payment_method_details(), get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response.response);
|
||||
assert_ne!(txn_id, None, "Empty connector transaction id");
|
||||
let void_response = CONNECTOR
|
||||
.void_payment(txn_id.unwrap(), None, get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
void_response.response.unwrap_err().message,
|
||||
"You cannot cancel this PaymentIntent because it has a status of succeeded."
|
||||
);
|
||||
}
|
||||
|
||||
// Captures a payment using invalid connector payment id.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_capture_for_invalid_payment() {
|
||||
let capture_response = CONNECTOR
|
||||
.capture_payment("123456789".to_string(), None, get_default_payment_info())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
capture_response.response.unwrap_err().message,
|
||||
String::from("No such payment_intent: '123456789'")
|
||||
);
|
||||
}
|
||||
|
||||
// Refunds a payment with refund amount higher than payment amount.
|
||||
#[actix_web::test]
|
||||
async fn should_fail_for_refund_amount_higher_than_payment_amount() {
|
||||
let response = CONNECTOR
|
||||
.make_payment_and_refund(
|
||||
payment_method_details(),
|
||||
Some(types::RefundsData {
|
||||
refund_amount: 150,
|
||||
..utils::PaymentRefundType::default().0
|
||||
}),
|
||||
get_default_payment_info(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.response.unwrap_err().message,
|
||||
"Refund amount (₹1.50) is greater than charge amount (₹1.00)",
|
||||
);
|
||||
}
|
||||
|
||||
// Connector dependent test cases goes here
|
||||
|
||||
// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests
|
||||
@ -195,6 +195,10 @@ api_key= "Login"
|
||||
key1= "Trankey"
|
||||
|
||||
|
||||
[plaid]
|
||||
api_key="Client Id"
|
||||
key1= "Secret"
|
||||
|
||||
[threedsecureio]
|
||||
api_key="API Key"
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ pub struct ConnectorAuthentication {
|
||||
pub paypal: Option<BodyKey>,
|
||||
pub payu: Option<BodyKey>,
|
||||
pub placetopay: Option<BodyKey>,
|
||||
pub plaid: Option<BodyKey>,
|
||||
pub powertranz: Option<BodyKey>,
|
||||
pub prophetpay: Option<HeaderKey>,
|
||||
pub rapyd: Option<BodyKey>,
|
||||
|
||||
@ -127,6 +127,7 @@ payone.base_url = "https://payment.preprod.payone.com/"
|
||||
paypal.base_url = "https://api-m.sandbox.paypal.com/"
|
||||
payu.base_url = "https://secure.snd.payu.com/"
|
||||
placetopay.base_url = "https://test.placetopay.com/rest/gateway"
|
||||
plaid.base_url = "https://sandbox.plaid.com"
|
||||
powertranz.base_url = "https://staging.ptranz.com/api/"
|
||||
prophetpay.base_url = "https://ccm-thirdparty.cps.golf/"
|
||||
rapyd.base_url = "https://sandboxapi.rapyd.net"
|
||||
@ -204,6 +205,7 @@ cards = [
|
||||
"paypal",
|
||||
"payu",
|
||||
"placetopay",
|
||||
"plaid",
|
||||
"powertranz",
|
||||
"prophetpay",
|
||||
"shift4",
|
||||
|
||||
@ -6,7 +6,7 @@ function find_prev_connector() {
|
||||
git checkout $self
|
||||
cp $self $self.tmp
|
||||
# Add new connector to existing list and sort it
|
||||
connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless gpayments helcim iatapay itaubank klarna mifinity mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme payone paypal payu placetopay powertranz prophetpay rapyd razorpay shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1")
|
||||
connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless gpayments helcim iatapay itaubank klarna mifinity mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1")
|
||||
IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS
|
||||
res=`echo ${sorted[@]}`
|
||||
sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp
|
||||
|
||||
Reference in New Issue
Block a user