mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 12:06:56 +08:00
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
459 lines
15 KiB
Rust
459 lines
15 KiB
Rust
pub mod transformers;
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent};
|
|
use diesel_models::enums;
|
|
use error_stack::ResultExt;
|
|
use transformers as coinbase;
|
|
|
|
use self::coinbase::CoinbaseWebhookDetails;
|
|
use super::utils;
|
|
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, Response,
|
|
},
|
|
utils::BytesExt,
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Coinbase;
|
|
|
|
impl api::Payment for Coinbase {}
|
|
impl api::PaymentToken for Coinbase {}
|
|
impl api::PaymentSession for Coinbase {}
|
|
impl api::ConnectorAccessToken for Coinbase {}
|
|
impl api::MandateSetup for Coinbase {}
|
|
impl api::PaymentAuthorize for Coinbase {}
|
|
impl api::PaymentSync for Coinbase {}
|
|
impl api::PaymentCapture for Coinbase {}
|
|
impl api::PaymentVoid for Coinbase {}
|
|
impl api::Refund for Coinbase {}
|
|
impl api::RefundExecute for Coinbase {}
|
|
impl api::RefundSync for Coinbase {}
|
|
|
|
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Coinbase
|
|
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.common_get_content_type().to_string().into(),
|
|
),
|
|
(
|
|
headers::X_CC_VERSION.to_string(),
|
|
"2018-03-22".to_string().into(),
|
|
),
|
|
];
|
|
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
|
header.append(&mut api_key);
|
|
Ok(header)
|
|
}
|
|
}
|
|
|
|
impl ConnectorCommon for Coinbase {
|
|
fn id(&self) -> &'static str {
|
|
"coinbase"
|
|
}
|
|
|
|
fn common_get_content_type(&self) -> &'static str {
|
|
"application/json"
|
|
}
|
|
|
|
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
|
connectors.coinbase.base_url.as_ref()
|
|
}
|
|
|
|
fn get_auth_header(
|
|
&self,
|
|
auth_type: &types::ConnectorAuthType,
|
|
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
|
let auth: coinbase::CoinbaseAuthType = coinbase::CoinbaseAuthType::try_from(auth_type)
|
|
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
|
Ok(vec![(
|
|
headers::X_CC_API_KEY.to_string(),
|
|
auth.api_key.into_masked(),
|
|
)])
|
|
}
|
|
|
|
fn build_error_response(
|
|
&self,
|
|
res: Response,
|
|
event_builder: Option<&mut ConnectorEvent>,
|
|
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
|
let response: coinbase::CoinbaseErrorResponse = res
|
|
.response
|
|
.parse_struct("CoinbaseErrorResponse")
|
|
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
|
|
|
event_builder.map(|i| i.set_error_response_body(&response));
|
|
router_env::logger::info!(connector_response=?response);
|
|
|
|
Ok(ErrorResponse {
|
|
status_code: res.status_code,
|
|
code: response.error.error_type,
|
|
message: response.error.message,
|
|
reason: response.error.code,
|
|
attempt_status: None,
|
|
connector_transaction_id: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ConnectorValidation for Coinbase {
|
|
fn validate_capture_method(
|
|
&self,
|
|
capture_method: Option<enums::CaptureMethod>,
|
|
_pmt: Option<enums::PaymentMethodType>,
|
|
) -> CustomResult<(), errors::ConnectorError> {
|
|
let capture_method = capture_method.unwrap_or_default();
|
|
match capture_method {
|
|
enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()),
|
|
enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(
|
|
connector_utils::construct_not_supported_error_report(capture_method, self.id()),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl
|
|
ConnectorIntegration<
|
|
api::PaymentMethodToken,
|
|
types::PaymentMethodTokenizationData,
|
|
types::PaymentsResponseData,
|
|
> for Coinbase
|
|
{
|
|
// Not Implemented (R)
|
|
}
|
|
|
|
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
|
for Coinbase
|
|
{
|
|
//TODO: implement sessions flow
|
|
}
|
|
|
|
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
|
|
for Coinbase
|
|
{
|
|
}
|
|
|
|
impl
|
|
ConnectorIntegration<
|
|
api::SetupMandate,
|
|
types::SetupMandateRequestData,
|
|
types::PaymentsResponseData,
|
|
> for Coinbase
|
|
{
|
|
fn build_request(
|
|
&self,
|
|
_req: &types::RouterData<
|
|
api::SetupMandate,
|
|
types::SetupMandateRequestData,
|
|
types::PaymentsResponseData,
|
|
>,
|
|
_connectors: &settings::Connectors,
|
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
|
Err(
|
|
errors::ConnectorError::NotImplemented("Setup Mandate flow for Coinbase".to_string())
|
|
.into(),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
|
for Coinbase
|
|
{
|
|
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!("{}/charges", self.base_url(_connectors)))
|
|
}
|
|
|
|
fn get_request_body(
|
|
&self,
|
|
req: &types::PaymentsAuthorizeRouterData,
|
|
_connectors: &settings::Connectors,
|
|
) -> CustomResult<RequestContent, errors::ConnectorError> {
|
|
let connector_req = coinbase::CoinbasePaymentsRequest::try_from(req)?;
|
|
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,
|
|
)?)
|
|
.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: coinbase::CoinbasePaymentsResponse = res
|
|
.response
|
|
.parse_struct("Coinbase PaymentsAuthorizeResponse")
|
|
.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,
|
|
})
|
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
|
}
|
|
|
|
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 Coinbase
|
|
{
|
|
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_url(
|
|
&self,
|
|
_req: &types::PaymentsSyncRouterData,
|
|
_connectors: &settings::Connectors,
|
|
) -> CustomResult<String, errors::ConnectorError> {
|
|
let connector_id = _req
|
|
.request
|
|
.connector_transaction_id
|
|
.get_connector_transaction_id()
|
|
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
|
Ok(format!(
|
|
"{}/charges/{}",
|
|
self.base_url(_connectors),
|
|
connector_id
|
|
))
|
|
}
|
|
|
|
fn build_request(
|
|
&self,
|
|
req: &types::PaymentsSyncRouterData,
|
|
connectors: &settings::Connectors,
|
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
|
Ok(Some(
|
|
services::RequestBuilder::new()
|
|
.method(services::Method::Get)
|
|
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
|
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
|
.build(),
|
|
))
|
|
}
|
|
|
|
fn handle_response(
|
|
&self,
|
|
data: &types::PaymentsSyncRouterData,
|
|
event_builder: Option<&mut ConnectorEvent>,
|
|
res: Response,
|
|
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
|
let response: coinbase::CoinbasePaymentsResponse = res
|
|
.response
|
|
.parse_struct("coinbase PaymentsSyncResponse")
|
|
.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,
|
|
})
|
|
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
|
}
|
|
|
|
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 Coinbase
|
|
{
|
|
fn build_request(
|
|
&self,
|
|
_req: &types::PaymentsCaptureRouterData,
|
|
_connectors: &settings::Connectors,
|
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
|
Err(errors::ConnectorError::FlowNotSupported {
|
|
flow: "Capture".to_string(),
|
|
connector: "Coinbase".to_string(),
|
|
}
|
|
.into())
|
|
}
|
|
}
|
|
|
|
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
|
for Coinbase
|
|
{
|
|
}
|
|
|
|
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
|
for Coinbase
|
|
{
|
|
fn build_request(
|
|
&self,
|
|
_req: &types::RefundsRouterData<api::Execute>,
|
|
_connectors: &settings::Connectors,
|
|
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
|
Err(errors::ConnectorError::FlowNotSupported {
|
|
flow: "Refund".to_string(),
|
|
connector: "Coinbase".to_string(),
|
|
}
|
|
.into())
|
|
}
|
|
}
|
|
|
|
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Coinbase {
|
|
// default implementation of build_request method will be executed
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl api::IncomingWebhook for Coinbase {
|
|
fn get_webhook_source_verification_algorithm(
|
|
&self,
|
|
_request: &api::IncomingWebhookRequestDetails<'_>,
|
|
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
|
|
Ok(Box::new(crypto::HmacSha256))
|
|
}
|
|
|
|
fn get_webhook_source_verification_signature(
|
|
&self,
|
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
|
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
|
let base64_signature =
|
|
utils::get_header_key_value("X-CC-Webhook-Signature", request.headers)?;
|
|
hex::decode(base64_signature)
|
|
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
|
|
}
|
|
|
|
fn get_webhook_source_verification_message(
|
|
&self,
|
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
|
_merchant_id: &common_utils::id_type::MerchantId,
|
|
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
|
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
|
let message = std::str::from_utf8(request.body)
|
|
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
|
|
Ok(message.to_string().into_bytes())
|
|
}
|
|
|
|
fn get_webhook_object_reference_id(
|
|
&self,
|
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
|
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
|
let notif: CoinbaseWebhookDetails = request
|
|
.body
|
|
.parse_struct("CoinbaseWebhookDetails")
|
|
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
|
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
|
|
api_models::payments::PaymentIdType::ConnectorTransactionId(notif.event.data.id),
|
|
))
|
|
}
|
|
|
|
fn get_webhook_event_type(
|
|
&self,
|
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
|
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
|
let notif: CoinbaseWebhookDetails = request
|
|
.body
|
|
.parse_struct("CoinbaseWebhookDetails")
|
|
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
|
match notif.event.event_type {
|
|
coinbase::WebhookEventType::Confirmed | coinbase::WebhookEventType::Resolved => {
|
|
Ok(api::IncomingWebhookEvent::PaymentIntentSuccess)
|
|
}
|
|
coinbase::WebhookEventType::Failed => {
|
|
Ok(api::IncomingWebhookEvent::PaymentActionRequired)
|
|
}
|
|
coinbase::WebhookEventType::Pending => {
|
|
Ok(api::IncomingWebhookEvent::PaymentIntentProcessing)
|
|
}
|
|
coinbase::WebhookEventType::Unknown | coinbase::WebhookEventType::Created => {
|
|
Ok(api::IncomingWebhookEvent::EventNotSupported)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_webhook_resource_object(
|
|
&self,
|
|
request: &api::IncomingWebhookRequestDetails<'_>,
|
|
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
|
|
let notif: CoinbaseWebhookDetails = request
|
|
.body
|
|
.parse_struct("CoinbaseWebhookDetails")
|
|
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
|
|
|
Ok(Box::new(notif.event))
|
|
}
|
|
}
|