mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat: Apple pay session flow integrate (#138)
This commit is contained in:
committed by
GitHub
parent
1f0d1deb2b
commit
8b8ff8188f
@ -37,8 +37,8 @@ locker_decryption_key1 = ""
|
||||
locker_decryption_key2 = ""
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
wallets = ["klarna","braintree","applepay"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree","aci"]
|
||||
|
||||
[eph_key]
|
||||
validity = 1
|
||||
@ -64,6 +64,9 @@ base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.applepay]
|
||||
base_url = "https://apple-pay-gateway.apple.com/"
|
||||
|
||||
[scheduler]
|
||||
stream = "SCHEDULER_STREAM"
|
||||
consumer_group = "SCHEDULER_GROUP"
|
||||
|
||||
@ -116,9 +116,12 @@ base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.applepay]
|
||||
base_url = "https://apple-pay-gateway.apple.com/"
|
||||
|
||||
# This data is used to call respective connectors for wallets and cards
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
wallets = ["klarna","braintree","applepay"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
|
||||
|
||||
@ -71,7 +71,9 @@ base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
[connectors.applepay]
|
||||
base_url = "https://apple-pay-gateway.apple.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree","applepay"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
@ -703,6 +703,19 @@ pub enum SessionToken {
|
||||
Paypal {
|
||||
session_token: String,
|
||||
},
|
||||
Applepay {
|
||||
epoch_timestamp: u64,
|
||||
expires_at: u64,
|
||||
merchant_session_identifier: String,
|
||||
nonce: String,
|
||||
merchant_identifier: String,
|
||||
domain_name: String,
|
||||
display_name: String,
|
||||
signature: String,
|
||||
operational_analytics_identifier: String,
|
||||
retries: u8,
|
||||
psp_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, serde::Serialize, Clone)]
|
||||
|
||||
@ -49,7 +49,7 @@ mime = "0.3.16"
|
||||
nanoid = "0.4.0"
|
||||
once_cell = "1.16.0"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.11.12", features = ["json"] }
|
||||
reqwest = { version = "0.11.12", features = ["json", "native-tls"] }
|
||||
ring = "0.16.20"
|
||||
serde = { version = "1.0.149", features = ["derive"] }
|
||||
serde_json = "1.0.89"
|
||||
|
||||
@ -114,6 +114,7 @@ pub struct Connectors {
|
||||
pub braintree: ConnectorParams,
|
||||
pub klarna: ConnectorParams,
|
||||
pub supported: SupportedConnectors,
|
||||
pub applepay: ConnectorParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
pub mod aci;
|
||||
pub mod adyen;
|
||||
pub mod applepay;
|
||||
pub mod authorizedotnet;
|
||||
pub mod braintree;
|
||||
pub mod checkout;
|
||||
@ -7,6 +8,6 @@ pub mod klarna;
|
||||
pub mod stripe;
|
||||
|
||||
pub use self::{
|
||||
aci::Aci, adyen::Adyen, authorizedotnet::Authorizedotnet, braintree::Braintree,
|
||||
checkout::Checkout, klarna::Klarna, stripe::Stripe,
|
||||
aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
|
||||
braintree::Braintree, checkout::Checkout, klarna::Klarna, stripe::Stripe,
|
||||
};
|
||||
|
||||
246
crates/router/src/connector/applepay.rs
Normal file
246
crates/router/src/connector/applepay.rs
Normal file
@ -0,0 +1,246 @@
|
||||
mod transformers;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use common_utils::ext_traits::ValueExt;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
|
||||
use self::transformers as applepay;
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::errors::{self, CustomResult},
|
||||
headers, services,
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon},
|
||||
},
|
||||
utils::{self, BytesExt, OptionExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Applepay;
|
||||
|
||||
impl api::ConnectorCommon for Applepay {
|
||||
fn id(&self) -> &'static str {
|
||||
"applepay"
|
||||
}
|
||||
|
||||
fn base_url(&self, connectors: settings::Connectors) -> String {
|
||||
connectors.applepay.base_url
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Applepay {}
|
||||
impl api::PaymentAuthorize for Applepay {}
|
||||
impl api::PaymentSync for Applepay {}
|
||||
impl api::PaymentVoid for Applepay {}
|
||||
impl api::PaymentCapture for Applepay {}
|
||||
impl api::PreVerify for Applepay {}
|
||||
impl api::PaymentSession for Applepay {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Verify,
|
||||
types::VerifyRequestData,
|
||||
types::PaymentsResponseData,
|
||||
> for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
> for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Void,
|
||||
types::PaymentsCancelData,
|
||||
types::PaymentsResponseData,
|
||||
> for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
> for Applepay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
_req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let header = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
types::PaymentsSessionType::get_content_type(self).to_string(),
|
||||
)];
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsSessionRouterData,
|
||||
connectors: settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}{}",
|
||||
self.base_url(connectors),
|
||||
"paymentservices/paymentSession"
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let req = utils::Encode::<applepay::ApplepaySessionRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
connectors: settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
// TODO: [ORCA-346] Requestbuilder needs &str migrate get_url to send &str instead of owned string
|
||||
.url(&types::PaymentsSessionType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSessionType::get_headers(self, req)?)
|
||||
.body(types::PaymentsSessionType::get_request_body(self, req)?)
|
||||
.add_certificate(types::PaymentsSessionType::get_certificate(self, req)?)
|
||||
.add_certificate_key(types::PaymentsSessionType::get_certificate_key(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSessionRouterData,
|
||||
res: types::Response,
|
||||
) -> CustomResult<types::PaymentsSessionRouterData, errors::ConnectorError> {
|
||||
let response: applepay::ApplepaySessionResponse = res
|
||||
.response
|
||||
.parse_struct("ApplepaySessionResponse")
|
||||
.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: Bytes,
|
||||
) -> CustomResult<types::ErrorResponse, errors::ConnectorError> {
|
||||
let response: applepay::ErrorResponse = res
|
||||
.parse_struct("ErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::ErrorResponse {
|
||||
code: response.status_code,
|
||||
message: response.status_message,
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_certificate(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let metadata = req
|
||||
.connector_meta_data
|
||||
.to_owned()
|
||||
.get_required_value("connector_meta_data")
|
||||
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
|
||||
|
||||
let session_object: transformers::SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
Ok(Some(session_object.certificate))
|
||||
}
|
||||
|
||||
fn get_certificate_key(
|
||||
&self,
|
||||
req: &types::PaymentsSessionRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let metadata = req
|
||||
.connector_meta_data
|
||||
.to_owned()
|
||||
.get_required_value("connector_meta_data")
|
||||
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
|
||||
|
||||
let session_object: transformers::SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
Ok(Some(session_object.certificate_keys))
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Refund for Applepay {}
|
||||
impl api::RefundExecute for Applepay {}
|
||||
impl api::RefundSync for Applepay {}
|
||||
|
||||
impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData>
|
||||
for Applepay
|
||||
{
|
||||
}
|
||||
|
||||
impl services::ConnectorRedirectResponse for Applepay {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Applepay {
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
}
|
||||
103
crates/router/src/connector/applepay/transformers.rs
Normal file
103
crates/router/src/connector/applepay/transformers.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use common_utils::ext_traits::ValueExt;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{Deserialize, Serialize};
|
||||
|
||||
use crate::{core::errors, types, utils::OptionExt};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplepaySessionRequest {
|
||||
merchant_identifier: String,
|
||||
display_name: String,
|
||||
initiative: String,
|
||||
initiative_context: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ApplepaySessionResponse {
|
||||
epoch_timestamp: u64,
|
||||
expires_at: u64,
|
||||
merchant_session_identifier: String,
|
||||
nonce: String,
|
||||
merchant_identifier: String,
|
||||
domain_name: String,
|
||||
display_name: String,
|
||||
signature: String,
|
||||
operational_analytics_identifier: String,
|
||||
retries: u8,
|
||||
psp_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ErrorResponse {
|
||||
pub status_code: String,
|
||||
pub status_message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct SessionObject {
|
||||
pub certificate: String,
|
||||
pub certificate_keys: String,
|
||||
pub merchant_identifier: String,
|
||||
pub display_name: String,
|
||||
pub initiative: String,
|
||||
pub initiative_context: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsSessionRouterData> for ApplepaySessionRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsSessionRouterData) -> Result<Self, Self::Error> {
|
||||
let metadata = item
|
||||
.connector_meta_data
|
||||
.to_owned()
|
||||
.get_required_value("connector_meta_data")
|
||||
.change_context(errors::ConnectorError::NoConnectorMetaData)?;
|
||||
|
||||
let session_object: SessionObject = metadata
|
||||
.parse_value("SessionObject")
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
|
||||
Ok(Self {
|
||||
merchant_identifier: session_object.merchant_identifier,
|
||||
display_name: session_object.display_name,
|
||||
initiative: session_object.initiative,
|
||||
initiative_context: session_object.initiative_context,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T>
|
||||
TryFrom<types::ResponseRouterData<F, ApplepaySessionResponse, T, types::PaymentsResponseData>>
|
||||
for types::RouterData<F, T, types::PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ParsingError>;
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, ApplepaySessionResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(types::RouterData {
|
||||
//TODO : change in session response to fit apple pay session object
|
||||
response: Ok(types::PaymentsResponseData::SessionResponse {
|
||||
session_token: {
|
||||
api_models::payments::SessionToken::Applepay {
|
||||
epoch_timestamp: item.response.epoch_timestamp,
|
||||
expires_at: item.response.expires_at,
|
||||
merchant_session_identifier: item.response.merchant_session_identifier,
|
||||
nonce: item.response.nonce,
|
||||
merchant_identifier: item.response.merchant_identifier,
|
||||
domain_name: item.response.domain_name,
|
||||
display_name: item.response.display_name,
|
||||
signature: item.response.signature,
|
||||
operational_analytics_identifier: item
|
||||
.response
|
||||
.operational_analytics_identifier,
|
||||
retries: item.response.retries,
|
||||
psp_id: item.response.psp_id,
|
||||
}
|
||||
},
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -321,6 +321,7 @@ pub async fn create_payment_connector(
|
||||
payment_methods_enabled,
|
||||
test_mode: req.test_mode,
|
||||
disabled: req.disabled,
|
||||
metadata: req.metadata,
|
||||
};
|
||||
|
||||
let mca = store
|
||||
|
||||
@ -234,6 +234,8 @@ pub enum ApiClientError {
|
||||
InvalidProxyConfiguration,
|
||||
#[error("Client construction failed")]
|
||||
ClientConstructionFailed,
|
||||
#[error("Certificate decode failed")]
|
||||
CertificateDecodeFailed,
|
||||
|
||||
#[error("URL encoding of request payload failed")]
|
||||
UrlEncodingFailed,
|
||||
@ -295,6 +297,12 @@ pub enum ConnectorError {
|
||||
MissingRequiredField { field_name: String },
|
||||
#[error("Failed to obtain authentication type")]
|
||||
FailedToObtainAuthType,
|
||||
#[error("Failed to obtain certificate")]
|
||||
FailedToObtainCertificate,
|
||||
#[error("Connector meta data not found")]
|
||||
NoConnectorMetaData,
|
||||
#[error("Failed to obtain certificate key")]
|
||||
FailedToObtainCertificateKey,
|
||||
#[error("This step has not been implemented for: {0}")]
|
||||
NotImplemented(String),
|
||||
#[error("Missing connector transaction ID")]
|
||||
|
||||
@ -183,7 +183,6 @@ where
|
||||
Op: Operation<F, Req> + Send + Sync + Clone,
|
||||
Req: Debug,
|
||||
Res: transformers::ToResponse<Req, PaymentData<F>, Op> + From<Req>,
|
||||
|
||||
// To create connector flow specific interface data
|
||||
PaymentData<F>: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
|
||||
types::RouterData<F, FData, types::PaymentsResponseData>: Feature<F, FData>,
|
||||
|
||||
@ -95,7 +95,7 @@ where
|
||||
.payment_attempt
|
||||
.authentication_type
|
||||
.unwrap_or_default(),
|
||||
|
||||
connector_meta_data: merchant_connector_account.metadata,
|
||||
request: T::try_from(payment_data.clone())?,
|
||||
response: response.map_or_else(|| Err(types::ErrorResponse::default()), Ok),
|
||||
};
|
||||
|
||||
@ -71,6 +71,7 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
// Does refund need shipping/billing address ?
|
||||
address: PaymentAddress::default(),
|
||||
auth_type: payment_attempt.authentication_type.unwrap_or_default(),
|
||||
connector_meta_data: None,
|
||||
|
||||
request: types::RefundsData {
|
||||
refund_id: refund.refund_id.clone(),
|
||||
|
||||
@ -168,6 +168,7 @@ impl MerchantConnectorAccountInterface for MockDb {
|
||||
disabled: t.disabled,
|
||||
merchant_connector_id: t.merchant_connector_id.unwrap_or_default(),
|
||||
payment_methods_enabled: t.payment_methods_enabled,
|
||||
metadata: t.metadata,
|
||||
connector_type: t
|
||||
.connector_type
|
||||
.unwrap_or(crate::types::storage::enums::ConnectorType::FinOperations),
|
||||
|
||||
@ -104,6 +104,20 @@ pub trait ConnectorIntegration<T, Req, Resp>: ConnectorIntegrationExt<T, Req, Re
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
Ok(ErrorResponse::get_not_implemented())
|
||||
}
|
||||
|
||||
fn get_certificate(
|
||||
&self,
|
||||
_req: &types::RouterData<T, Req, Resp>,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_certificate_key(
|
||||
&self,
|
||||
_req: &types::RouterData<T, Req, Resp>,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
@ -196,9 +210,10 @@ async fn send_request(
|
||||
&state.conf.proxy,
|
||||
should_bypass_proxy,
|
||||
crate::consts::REQUEST_TIME_OUT,
|
||||
request.certificate,
|
||||
request.certificate_key,
|
||||
)?;
|
||||
let headers = request.headers.construct_header_map()?;
|
||||
|
||||
match request.method {
|
||||
Method::Get => client.get(url).add_headers(headers).send().await,
|
||||
Method::Post => {
|
||||
|
||||
@ -36,6 +36,8 @@ pub(super) fn create_client(
|
||||
proxy: &Proxy,
|
||||
should_bypass_proxy: bool,
|
||||
request_time_out: u64,
|
||||
client_certificate: Option<String>,
|
||||
client_certificate_key: Option<String>,
|
||||
) -> CustomResult<reqwest::Client, errors::ApiClientError> {
|
||||
let mut client_builder = reqwest::Client::builder();
|
||||
|
||||
@ -58,6 +60,36 @@ pub(super) fn create_client(
|
||||
}
|
||||
}
|
||||
|
||||
client_builder = match (client_certificate, client_certificate_key) {
|
||||
(Some(encoded_cert), Some(encoded_cert_key)) => {
|
||||
let decoded_cert = base64::decode(encoded_cert)
|
||||
.into_report()
|
||||
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
|
||||
|
||||
let decoded_cert_key = base64::decode(encoded_cert_key)
|
||||
.into_report()
|
||||
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
|
||||
|
||||
let certificate = String::from_utf8(decoded_cert)
|
||||
.into_report()
|
||||
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
|
||||
|
||||
let certificate_key = String::from_utf8(decoded_cert_key)
|
||||
.into_report()
|
||||
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
|
||||
|
||||
let identity = reqwest::Identity::from_pkcs8_pem(
|
||||
certificate.as_bytes(),
|
||||
certificate_key.as_bytes(),
|
||||
)
|
||||
.into_report()
|
||||
.change_context(errors::ApiClientError::CertificateDecodeFailed)?;
|
||||
|
||||
client_builder.identity(identity)
|
||||
}
|
||||
_ => client_builder,
|
||||
};
|
||||
|
||||
let duration = Duration::from_secs(request_time_out);
|
||||
client_builder = client_builder.timeout(duration);
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@ pub struct Request {
|
||||
pub payload: Option<Secret<String>>,
|
||||
pub method: Method,
|
||||
pub content_type: Option<ContentType>,
|
||||
pub certificate: Option<String>,
|
||||
pub certificate_key: Option<String>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
@ -47,6 +49,8 @@ impl Request {
|
||||
headers: Vec::new(),
|
||||
payload: None,
|
||||
content_type: None,
|
||||
certificate: None,
|
||||
certificate_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +66,14 @@ impl Request {
|
||||
pub fn add_content_type(&mut self, content_type: ContentType) {
|
||||
self.content_type = Some(content_type);
|
||||
}
|
||||
|
||||
pub fn add_certificate(&mut self, certificate: Option<String>) {
|
||||
self.certificate = certificate;
|
||||
}
|
||||
|
||||
pub fn add_certificate_key(&mut self, certificate_key: Option<String>) {
|
||||
self.certificate = certificate_key;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RequestBuilder {
|
||||
@ -70,6 +82,8 @@ pub struct RequestBuilder {
|
||||
pub payload: Option<Secret<String>>,
|
||||
pub method: Method,
|
||||
pub content_type: Option<ContentType>,
|
||||
pub certificate: Option<String>,
|
||||
pub certificate_key: Option<String>,
|
||||
}
|
||||
|
||||
impl RequestBuilder {
|
||||
@ -80,6 +94,8 @@ impl RequestBuilder {
|
||||
headers: Vec::new(),
|
||||
payload: None,
|
||||
content_type: None,
|
||||
certificate: None,
|
||||
certificate_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +131,16 @@ impl RequestBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_certificate(mut self, certificate: Option<String>) -> RequestBuilder {
|
||||
self.certificate = certificate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_certificate_key(mut self, certificate_key: Option<String>) -> RequestBuilder {
|
||||
self.certificate_key = certificate_key;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Request {
|
||||
Request {
|
||||
method: self.method,
|
||||
@ -122,6 +148,8 @@ impl RequestBuilder {
|
||||
headers: self.headers,
|
||||
payload: self.payload,
|
||||
content_type: self.content_type,
|
||||
certificate: self.certificate,
|
||||
certificate_key: self.certificate_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,14 +48,14 @@ pub type PaymentsSyncType =
|
||||
dyn services::ConnectorIntegration<api::PSync, PaymentsSyncData, PaymentsResponseData>;
|
||||
pub type PaymentsCaptureType =
|
||||
dyn services::ConnectorIntegration<api::Capture, PaymentsCaptureData, PaymentsResponseData>;
|
||||
pub type PaymentsSessionType =
|
||||
dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
|
||||
pub type PaymentsVoidType =
|
||||
dyn services::ConnectorIntegration<api::Void, PaymentsCancelData, PaymentsResponseData>;
|
||||
pub type RefundExecuteType =
|
||||
dyn services::ConnectorIntegration<api::Execute, RefundsData, RefundsResponseData>;
|
||||
pub type RefundSyncType =
|
||||
dyn services::ConnectorIntegration<api::RSync, RefundsData, RefundsResponseData>;
|
||||
pub type PaymentsSessionType =
|
||||
dyn services::ConnectorIntegration<api::Session, PaymentsSessionData, PaymentsResponseData>;
|
||||
|
||||
pub type VerifyRouterData = RouterData<api::Verify, VerifyRequestData, PaymentsResponseData>;
|
||||
|
||||
@ -73,6 +73,7 @@ pub struct RouterData<Flow, Request, Response> {
|
||||
pub orca_return_url: Option<String>,
|
||||
pub address: PaymentAddress,
|
||||
pub auth_type: storage_enums::AuthenticationType,
|
||||
pub connector_meta_data: Option<serde_json::Value>,
|
||||
|
||||
/// Contains flow-specific data required to construct a request and send it to the connector.
|
||||
pub request: Request,
|
||||
@ -123,17 +124,11 @@ pub struct PaymentsCancelData {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentsSessionData {
|
||||
//TODO: Add the fields here as required
|
||||
pub amount: i32,
|
||||
pub currency: storage_enums::Currency,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectorSessionToken {
|
||||
pub connector_name: String,
|
||||
pub session_id: Option<String>,
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VerifyRequestData {
|
||||
pub payment_method_data: payments::PaymentMethod,
|
||||
@ -250,7 +245,7 @@ pub struct ResponseRouterData<Flow, R, Request, Response> {
|
||||
}
|
||||
|
||||
// Different patterns of authentication.
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
#[derive(Default, Debug, Clone, serde::Deserialize)]
|
||||
#[serde(tag = "auth_type")]
|
||||
pub enum ConnectorAuthType {
|
||||
HeaderKey {
|
||||
@ -265,15 +260,10 @@ pub enum ConnectorAuthType {
|
||||
key1: String,
|
||||
api_secret: String,
|
||||
},
|
||||
#[default]
|
||||
NoKey,
|
||||
}
|
||||
|
||||
impl Default for ConnectorAuthType {
|
||||
fn default() -> Self {
|
||||
Self::HeaderKey {
|
||||
api_key: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ConnectorsList {
|
||||
pub connectors: Vec<String>,
|
||||
|
||||
@ -103,6 +103,7 @@ impl ConnectorData {
|
||||
"authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)),
|
||||
"braintree" => Ok(Box::new(&connector::Braintree)),
|
||||
"klarna" => Ok(Box::new(&connector::Klarna)),
|
||||
"applepay" => Ok(Box::new(&connector::Applepay)),
|
||||
_ => Err(report!(errors::UnexpectedError)
|
||||
.attach_printable(format!("invalid connector name: {connector_name}")))
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
|
||||
@ -8,6 +8,7 @@ pub enum Connector {
|
||||
Authorizedotnet,
|
||||
Braintree,
|
||||
Klarna,
|
||||
Applepay,
|
||||
#[default]
|
||||
Dummy,
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
response: Err(types::ErrorResponse::default()),
|
||||
payment_method_id: None,
|
||||
address: PaymentAddress::default(),
|
||||
connector_meta_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +90,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
payment_method_id: None,
|
||||
response: Err(types::ErrorResponse::default()),
|
||||
address: PaymentAddress::default(),
|
||||
connector_meta_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
payment_method_id: None,
|
||||
response: Err(types::ErrorResponse::default()),
|
||||
address: PaymentAddress::default(),
|
||||
connector_meta_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,6 +62,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
|
||||
types::RouterData {
|
||||
flow: PhantomData,
|
||||
connector_meta_data: None,
|
||||
merchant_id: String::from("authorizedotnet"),
|
||||
connector: "authorizedotnet".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
|
||||
@ -48,6 +48,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
response: Err(types::ErrorResponse::default()),
|
||||
payment_method_id: None,
|
||||
address: PaymentAddress::default(),
|
||||
connector_meta_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +59,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
|
||||
types::RouterData {
|
||||
flow: PhantomData,
|
||||
connector_meta_data: None,
|
||||
merchant_id: "checkout".to_string(),
|
||||
connector: "checkout".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
|
||||
@ -16,6 +16,7 @@ pub struct MerchantConnectorAccount {
|
||||
#[diesel(deserialize_as = super::OptionalDieselArray<serde_json::Value>)]
|
||||
pub payment_methods_enabled: Option<Vec<serde_json::Value>>,
|
||||
pub connector_type: storage_enums::ConnectorType,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Insertable, router_derive::DebugAsDisplay)]
|
||||
@ -29,6 +30,7 @@ pub struct MerchantConnectorAccountNew {
|
||||
pub disabled: Option<bool>,
|
||||
pub merchant_connector_id: Option<i32>,
|
||||
pub payment_methods_enabled: Option<Vec<serde_json::Value>>,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@ -175,6 +175,7 @@ diesel::table! {
|
||||
merchant_connector_id -> Int4,
|
||||
payment_methods_enabled -> Nullable<Array<Nullable<Json>>>,
|
||||
connector_type -> ConnectorType,
|
||||
metadata -> Nullable<Jsonb>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -58,10 +58,12 @@ base_url = "http://stripe-mock:12111/"
|
||||
[connectors.braintree]
|
||||
base_url = "https://api.sandbox.braintreegateway.com/"
|
||||
|
||||
[connectors.applepay]
|
||||
base_url = "https://apple-pay-gateway.apple.com/"
|
||||
|
||||
[connectors.klarna]
|
||||
base_url = "https://api-na.playground.klarna.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna","braintree"]
|
||||
wallets = ["klarna","braintree","applepay"]
|
||||
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
ALTER TABLE merchant_connector_account DROP COLUMN metadata;
|
||||
@ -0,0 +1 @@
|
||||
ALTER TABLE merchant_connector_account ADD COLUMN metadata JSONB DEFAULT NULL;
|
||||
Reference in New Issue
Block a user