mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(connector): [bitpay] Add new crypto connector bitpay & testcases for all crypto connectors (#919)
Co-authored-by: arvindpatel24 <arvind.patel@juspay.in>
This commit is contained in:
		| @ -359,6 +359,7 @@ pub struct Connectors { | ||||
|     pub applepay: ConnectorParams, | ||||
|     pub authorizedotnet: ConnectorParams, | ||||
|     pub bambora: ConnectorParams, | ||||
|     pub bitpay: ConnectorParams, | ||||
|     pub bluesnap: ConnectorParams, | ||||
|     pub braintree: ConnectorParams, | ||||
|     pub checkout: ConnectorParams, | ||||
|  | ||||
| @ -3,6 +3,7 @@ pub mod adyen; | ||||
| pub mod airwallex; | ||||
| pub mod authorizedotnet; | ||||
| pub mod bambora; | ||||
| pub mod bitpay; | ||||
| pub mod bluesnap; | ||||
| pub mod braintree; | ||||
| pub mod checkout; | ||||
| @ -38,7 +39,7 @@ pub mod mollie; | ||||
| pub use self::dummyconnector::DummyConnector; | ||||
| pub use self::{ | ||||
|     aci::Aci, adyen::Adyen, airwallex::Airwallex, authorizedotnet::Authorizedotnet, | ||||
|     bambora::Bambora, bluesnap::Bluesnap, braintree::Braintree, checkout::Checkout, | ||||
|     bambora::Bambora, bitpay::Bitpay, bluesnap::Bluesnap, braintree::Braintree, checkout::Checkout, | ||||
|     coinbase::Coinbase, cybersource::Cybersource, dlocal::Dlocal, fiserv::Fiserv, forte::Forte, | ||||
|     globalpay::Globalpay, iatapay::Iatapay, klarna::Klarna, mollie::Mollie, | ||||
|     multisafepay::Multisafepay, nexinets::Nexinets, nuvei::Nuvei, opennode::Opennode, | ||||
|  | ||||
							
								
								
									
										533
									
								
								crates/router/src/connector/bitpay.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										533
									
								
								crates/router/src/connector/bitpay.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,533 @@ | ||||
| mod transformers; | ||||
|  | ||||
| use std::fmt::Debug; | ||||
|  | ||||
| use common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt}; | ||||
| use error_stack::ResultExt; | ||||
| use transformers as bitpay; | ||||
|  | ||||
| use self::bitpay::BitpayWebhookDetails; | ||||
| use crate::{ | ||||
|     configs::settings, | ||||
|     core::errors::{self, CustomResult}, | ||||
|     headers, | ||||
|     services::{self, ConnectorIntegration}, | ||||
|     types::{ | ||||
|         self, | ||||
|         api::{self, ConnectorCommon, ConnectorCommonExt}, | ||||
|         ErrorResponse, Response, | ||||
|     }, | ||||
|     utils::{self, BytesExt, Encode}, | ||||
| }; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Bitpay; | ||||
|  | ||||
| impl api::Payment for Bitpay {} | ||||
| impl api::PaymentToken for Bitpay {} | ||||
| impl api::PaymentSession for Bitpay {} | ||||
| impl api::ConnectorAccessToken for Bitpay {} | ||||
| impl api::PreVerify for Bitpay {} | ||||
| impl api::PaymentAuthorize for Bitpay {} | ||||
| impl api::PaymentSync for Bitpay {} | ||||
| impl api::PaymentCapture for Bitpay {} | ||||
| impl api::PaymentVoid for Bitpay {} | ||||
| impl api::Refund for Bitpay {} | ||||
| impl api::RefundExecute for Bitpay {} | ||||
| impl api::RefundSync for Bitpay {} | ||||
|  | ||||
| impl | ||||
|     ConnectorIntegration< | ||||
|         api::PaymentMethodToken, | ||||
|         types::PaymentMethodTokenizationData, | ||||
|         types::PaymentsResponseData, | ||||
|     > for Bitpay | ||||
| { | ||||
|     // Not Implemented (R) | ||||
| } | ||||
|  | ||||
| impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Bitpay | ||||
| where | ||||
|     Self: ConnectorIntegration<Flow, Request, Response>, | ||||
| { | ||||
|     fn build_headers( | ||||
|         &self, | ||||
|         _req: &types::RouterData<Flow, Request, Response>, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         let header = vec![ | ||||
|             ( | ||||
|                 headers::CONTENT_TYPE.to_string(), | ||||
|                 types::PaymentsAuthorizeType::get_content_type(self).to_string(), | ||||
|             ), | ||||
|             (headers::X_ACCEPT_VERSION.to_string(), "2.0.0".to_string()), | ||||
|         ]; | ||||
|         Ok(header) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorCommon for Bitpay { | ||||
|     fn id(&self) -> &'static str { | ||||
|         "bitpay" | ||||
|     } | ||||
|  | ||||
|     fn common_get_content_type(&self) -> &'static str { | ||||
|         "application/json" | ||||
|     } | ||||
|  | ||||
|     fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { | ||||
|         connectors.bitpay.base_url.as_ref() | ||||
|     } | ||||
|  | ||||
|     fn get_auth_header( | ||||
|         &self, | ||||
|         auth_type: &types::ConnectorAuthType, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         let auth = bitpay::BitpayAuthType::try_from(auth_type) | ||||
|             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; | ||||
|         Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)]) | ||||
|     } | ||||
|  | ||||
|     fn build_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         let response: bitpay::BitpayErrorResponse = | ||||
|             res.response.parse_struct("BitpayErrorResponse").switch()?; | ||||
|  | ||||
|         Ok(ErrorResponse { | ||||
|             status_code: res.status_code, | ||||
|             code: response.code, | ||||
|             message: response.message, | ||||
|             reason: response.reason, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
|     //TODO: implement sessions flow | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken> | ||||
|     for Bitpay | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsAuthorizeRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Ok(format!("{}/invoices", self.base_url(connectors))) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::PaymentsAuthorizeRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let req_obj = bitpay::BitpayPaymentsRequest::try_from(req)?; | ||||
|         let bitpay_req = | ||||
|             utils::Encode::<bitpay::BitpayPaymentsRequest>::encode_to_string_of_json(&req_obj) | ||||
|                 .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(bitpay_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, | ||||
|                 )?) | ||||
|                 .body(types::PaymentsAuthorizeType::get_request_body(self, req)?) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsAuthorizeRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> { | ||||
|         let response: bitpay::BitpayPaymentsResponse = res | ||||
|             .response | ||||
|             .parse_struct("Bitpay PaymentsAuthorizeResponse") | ||||
|             .switch()?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         req: &types::PaymentsSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         let auth = bitpay::BitpayAuthType::try_from(&req.connector_auth_type) | ||||
|             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; | ||||
|         let connector_id = req | ||||
|             .request | ||||
|             .connector_transaction_id | ||||
|             .get_connector_transaction_id() | ||||
|             .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; | ||||
|         Ok(format!( | ||||
|             "{}/invoices/{}?token={}", | ||||
|             self.base_url(connectors), | ||||
|             connector_id, | ||||
|             auth.api_key | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     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)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsSyncRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> { | ||||
|         let response: bitpay::BitpayPaymentsResponse = res | ||||
|             .response | ||||
|             .parse_struct("bitpay PaymentsSyncResponse") | ||||
|             .switch()?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsCaptureRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::PaymentsCaptureRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         _req: &types::PaymentsCaptureRouterData, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::PaymentsCaptureRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Post) | ||||
|                 .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::PaymentsCaptureType::get_headers( | ||||
|                     self, req, connectors, | ||||
|                 )?) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsCaptureRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> { | ||||
|         let response: bitpay::BitpayPaymentsResponse = res | ||||
|             .response | ||||
|             .parse_struct("Bitpay PaymentsCaptureResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseHandlingFailed)?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData> | ||||
|     for Bitpay | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData> for Bitpay { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::RefundsRouterData<api::Execute>, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::RefundsRouterData<api::Execute>, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::RefundsRouterData<api::Execute>, | ||||
|     ) -> CustomResult<Option<String>, errors::ConnectorError> { | ||||
|         let req_obj = bitpay::BitpayRefundRequest::try_from(req)?; | ||||
|         let bitpay_req = | ||||
|             utils::Encode::<bitpay::BitpayRefundRequest>::encode_to_string_of_json(&req_obj) | ||||
|                 .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(bitpay_req)) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::RefundsRouterData<api::Execute>, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         let request = services::RequestBuilder::new() | ||||
|             .method(services::Method::Post) | ||||
|             .url(&types::RefundExecuteType::get_url(self, req, connectors)?) | ||||
|             .attach_default_headers() | ||||
|             .headers(types::RefundExecuteType::get_headers( | ||||
|                 self, req, connectors, | ||||
|             )?) | ||||
|             .body(types::RefundExecuteType::get_request_body(self, req)?) | ||||
|             .build(); | ||||
|         Ok(Some(request)) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::RefundsRouterData<api::Execute>, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> { | ||||
|         let response: bitpay::RefundResponse = | ||||
|             res.response | ||||
|                 .parse_struct("bitpay RefundResponse") | ||||
|                 .change_context(errors::ConnectorError::ResponseHandlingFailed)?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Bitpay { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::RefundSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> { | ||||
|         self.build_headers(req, connectors) | ||||
|     } | ||||
|  | ||||
|     fn get_content_type(&self) -> &'static str { | ||||
|         self.common_get_content_type() | ||||
|     } | ||||
|  | ||||
|     fn get_url( | ||||
|         &self, | ||||
|         _req: &types::RefundSyncRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<String, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) | ||||
|     } | ||||
|  | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         req: &types::RefundSyncRouterData, | ||||
|         connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Ok(Some( | ||||
|             services::RequestBuilder::new() | ||||
|                 .method(services::Method::Get) | ||||
|                 .url(&types::RefundSyncType::get_url(self, req, connectors)?) | ||||
|                 .attach_default_headers() | ||||
|                 .headers(types::RefundSyncType::get_headers(self, req, connectors)?) | ||||
|                 .body(types::RefundSyncType::get_request_body(self, req)?) | ||||
|                 .build(), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::RefundSyncRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> { | ||||
|         let response: bitpay::RefundResponse = res | ||||
|             .response | ||||
|             .parse_struct("bitpay RefundSyncResponse") | ||||
|             .switch()?; | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response, | ||||
|             data: data.clone(), | ||||
|             http_code: res.status_code, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn get_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         self.build_error_response(res) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl api::IncomingWebhook for Bitpay { | ||||
|     fn get_webhook_object_reference_id( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> { | ||||
|         let notif: BitpayWebhookDetails = request | ||||
|             .body | ||||
|             .parse_struct("BitpayWebhookDetails") | ||||
|             .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||
|         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( | ||||
|             api_models::payments::PaymentIdType::ConnectorTransactionId(notif.data.id), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_event_type( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> { | ||||
|         let notif: BitpayWebhookDetails = request | ||||
|             .body | ||||
|             .parse_struct("BitpayWebhookDetails") | ||||
|             .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; | ||||
|         match notif.event.name { | ||||
|             bitpay::WebhookEventType::Confirmed | bitpay::WebhookEventType::Completed => { | ||||
|                 Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) | ||||
|             } | ||||
|             bitpay::WebhookEventType::Paid => { | ||||
|                 Ok(api::IncomingWebhookEvent::PaymentIntentProcessing) | ||||
|             } | ||||
|             bitpay::WebhookEventType::Declined => { | ||||
|                 Ok(api::IncomingWebhookEvent::PaymentIntentFailure) | ||||
|             } | ||||
|             _ => Ok(api::IncomingWebhookEvent::EventNotSupported), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_resource_object( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<serde_json::Value, errors::ConnectorError> { | ||||
|         let notif: BitpayWebhookDetails = request | ||||
|             .body | ||||
|             .parse_struct("BitpayWebhookDetails") | ||||
|             .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; | ||||
|         Encode::<BitpayWebhookDetails>::encode_to_value(¬if) | ||||
|             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										287
									
								
								crates/router/src/connector/bitpay/transformers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								crates/router/src/connector/bitpay/transformers.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,287 @@ | ||||
| use reqwest::Url; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     connector::utils::PaymentsAuthorizeRequestData, | ||||
|     core::errors, | ||||
|     services, | ||||
|     types::{self, api, storage::enums, ConnectorAuthType}, | ||||
| }; | ||||
|  | ||||
| #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| pub enum TransactionSpeed { | ||||
|     Low, | ||||
|     #[default] | ||||
|     Medium, | ||||
|     High, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Eq, PartialEq)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct BitpayPaymentsRequest { | ||||
|     price: i64, | ||||
|     currency: String, | ||||
|     #[serde(rename = "redirectURL")] | ||||
|     redirect_url: String, | ||||
|     #[serde(rename = "notificationURL")] | ||||
|     notification_url: String, | ||||
|     transaction_speed: TransactionSpeed, | ||||
|     token: String, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsAuthorizeRouterData> for BitpayPaymentsRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { | ||||
|         get_crypto_specific_payment_data(item) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Auth Struct | ||||
| pub struct BitpayAuthType { | ||||
|     pub(super) api_key: String, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&ConnectorAuthType> for BitpayAuthType { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(auth_type: &ConnectorAuthType) -> Result<Self, Self::Error> { | ||||
|         match auth_type { | ||||
|             types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { | ||||
|                 api_key: api_key.to_string(), | ||||
|             }), | ||||
|             _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| // PaymentsResponse | ||||
| #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| pub enum BitpayPaymentStatus { | ||||
|     #[default] | ||||
|     New, | ||||
|     Paid, | ||||
|     Confirmed, | ||||
|     Complete, | ||||
|     Expired, | ||||
|     Invalid, | ||||
| } | ||||
|  | ||||
| impl From<BitpayPaymentStatus> for enums::AttemptStatus { | ||||
|     fn from(item: BitpayPaymentStatus) -> Self { | ||||
|         match item { | ||||
|             BitpayPaymentStatus::New => Self::AuthenticationPending, | ||||
|             BitpayPaymentStatus::Complete | BitpayPaymentStatus::Confirmed => Self::Charged, | ||||
|             BitpayPaymentStatus::Expired => Self::Failure, | ||||
|             _ => Self::Pending, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| #[serde(untagged)] | ||||
| pub enum ExceptionStatus { | ||||
|     #[default] | ||||
|     Unit, | ||||
|     Bool(bool), | ||||
|     String(String), | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct BitpayPaymentResponseData { | ||||
|     pub url: Option<Url>, | ||||
|     pub status: BitpayPaymentStatus, | ||||
|     pub price: i64, | ||||
|     pub currency: String, | ||||
|     pub amount_paid: i64, | ||||
|     pub invoice_time: Option<i64>, | ||||
|     pub rate_refresh_time: Option<i64>, | ||||
|     pub expiration_time: Option<i64>, | ||||
|     pub current_time: Option<i64>, | ||||
|     pub id: String, | ||||
|     pub low_fee_detected: Option<bool>, | ||||
|     pub display_amount_paid: Option<String>, | ||||
|     pub exception_status: ExceptionStatus, | ||||
|     pub redirect_url: Option<String>, | ||||
|     pub refund_address_request_pending: Option<bool>, | ||||
|     pub merchant_name: Option<String>, | ||||
|     pub token: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct BitpayPaymentsResponse { | ||||
|     data: BitpayPaymentResponseData, | ||||
|     facade: Option<String>, | ||||
| } | ||||
|  | ||||
| impl<F, T> | ||||
|     TryFrom<types::ResponseRouterData<F, BitpayPaymentsResponse, T, types::PaymentsResponseData>> | ||||
|     for types::RouterData<F, T, types::PaymentsResponseData> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::ResponseRouterData<F, BitpayPaymentsResponse, T, types::PaymentsResponseData>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let redirection_data = item | ||||
|             .response | ||||
|             .data | ||||
|             .url | ||||
|             .map(|x| services::RedirectForm::from((x, services::Method::Get))); | ||||
|         let connector_id = types::ResponseId::ConnectorTransactionId(item.response.data.id); | ||||
|         let attempt_status = item.response.data.status; | ||||
|         Ok(Self { | ||||
|             status: enums::AttemptStatus::from(attempt_status), | ||||
|             response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: connector_id, | ||||
|                 redirection_data, | ||||
|                 mandate_reference: None, | ||||
|                 connector_metadata: None, | ||||
|                 network_txn_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // REFUND : | ||||
| // Type definition for RefundRequest | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| pub struct BitpayRefundRequest { | ||||
|     pub amount: i64, | ||||
| } | ||||
|  | ||||
| impl<F> TryFrom<&types::RefundsRouterData<F>> for BitpayRefundRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             amount: item.request.amount, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Type definition for Refund Response | ||||
|  | ||||
| #[allow(dead_code)] | ||||
| #[derive(Debug, Serialize, Default, Deserialize, Clone)] | ||||
| pub enum RefundStatus { | ||||
|     Succeeded, | ||||
|     Failed, | ||||
|     #[default] | ||||
|     Processing, | ||||
| } | ||||
|  | ||||
| impl From<RefundStatus> for enums::RefundStatus { | ||||
|     fn from(item: RefundStatus) -> Self { | ||||
|         match item { | ||||
|             RefundStatus::Succeeded => Self::Success, | ||||
|             RefundStatus::Failed => Self::Failure, | ||||
|             RefundStatus::Processing => Self::Pending, | ||||
|             //TODO: Review mapping | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| //TODO: Fill the struct with respective fields | ||||
| #[derive(Default, Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct RefundResponse { | ||||
|     id: String, | ||||
|     status: RefundStatus, | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>> | ||||
|     for types::RefundsRouterData<api::Execute> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::RefundsResponseRouterData<api::Execute, RefundResponse>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             response: Ok(types::RefundsResponseData { | ||||
|                 connector_refund_id: item.response.id.to_string(), | ||||
|                 refund_status: enums::RefundStatus::from(item.response.status), | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundResponse>> | ||||
|     for types::RefundsRouterData<api::RSync> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::RefundsResponseRouterData<api::RSync, RefundResponse>, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             response: Ok(types::RefundsResponseData { | ||||
|                 connector_refund_id: item.response.id.to_string(), | ||||
|                 refund_status: enums::RefundStatus::from(item.response.status), | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] | ||||
| pub struct BitpayErrorResponse { | ||||
|     pub status_code: u16, | ||||
|     pub code: String, | ||||
|     pub message: String, | ||||
|     pub reason: Option<String>, | ||||
| } | ||||
|  | ||||
| fn get_crypto_specific_payment_data( | ||||
|     item: &types::PaymentsAuthorizeRouterData, | ||||
| ) -> Result<BitpayPaymentsRequest, error_stack::Report<errors::ConnectorError>> { | ||||
|     let price = item.request.amount; | ||||
|     let currency = item.request.currency.to_string(); | ||||
|     let redirect_url = item.request.get_return_url()?; | ||||
|     let notification_url = item.request.get_webhook_url()?; | ||||
|     let transaction_speed = TransactionSpeed::Medium; | ||||
|     let auth_type = item.connector_auth_type.clone(); | ||||
|     let token = match auth_type { | ||||
|         ConnectorAuthType::HeaderKey { api_key } => api_key, | ||||
|         _ => String::default(), | ||||
|     }; | ||||
|  | ||||
|     Ok(BitpayPaymentsRequest { | ||||
|         price, | ||||
|         currency, | ||||
|         redirect_url, | ||||
|         notification_url, | ||||
|         transaction_speed, | ||||
|         token, | ||||
|     }) | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct BitpayWebhookDetails { | ||||
|     pub event: Event, | ||||
|     pub data: BitpayPaymentResponseData, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct Event { | ||||
|     pub code: i64, | ||||
|     pub name: WebhookEventType, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub enum WebhookEventType { | ||||
|     #[serde(rename = "invoice_paidInFull")] | ||||
|     Paid, | ||||
|     #[serde(rename = "invoice_confirmed")] | ||||
|     Confirmed, | ||||
|     #[serde(rename = "invoice_completed")] | ||||
|     Completed, | ||||
|     #[serde(rename = "invoice_expired")] | ||||
|     Expired, | ||||
|     #[serde(rename = "invoice_failedToConfirm")] | ||||
|     Invalid, | ||||
|     #[serde(rename = "invoice_declined")] | ||||
|     Declined, | ||||
|     #[serde(rename = "invoice_refundComplete")] | ||||
|     Refunded, | ||||
|     #[serde(rename = "invoice_manuallyNotified")] | ||||
|     Resent, | ||||
| } | ||||
| @ -107,6 +107,7 @@ default_imp_for_complete_authorize!( | ||||
|     connector::Aci, | ||||
|     connector::Adyen, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
| @ -153,6 +154,7 @@ default_imp_for_create_customer!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
| @ -203,6 +205,7 @@ default_imp_for_connector_redirect_response!( | ||||
|     connector::Aci, | ||||
|     connector::Adyen, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
| @ -239,6 +242,7 @@ default_imp_for_connector_request_id!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Checkout, | ||||
| @ -290,6 +294,7 @@ default_imp_for_accept_dispute!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Coinbase, | ||||
| @ -350,6 +355,7 @@ default_imp_for_file_upload!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Coinbase, | ||||
| @ -400,6 +406,7 @@ default_imp_for_submit_evidence!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cybersource, | ||||
| @ -450,6 +457,7 @@ default_imp_for_defend_dispute!( | ||||
|     connector::Airwallex, | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cybersource, | ||||
|  | ||||
| @ -61,6 +61,7 @@ pub mod headers { | ||||
|     pub const X_TRANS_KEY: &str = "X-Trans-Key"; | ||||
|     pub const X_VERSION: &str = "X-Version"; | ||||
|     pub const X_CC_VERSION: &str = "X-CC-Version"; | ||||
|     pub const X_ACCEPT_VERSION: &str = "X-Accept-Version"; | ||||
|     pub const X_DATE: &str = "X-Date"; | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -203,6 +203,7 @@ impl ConnectorData { | ||||
|             "airwallex" => Ok(Box::new(&connector::Airwallex)), | ||||
|             "authorizedotnet" => Ok(Box::new(&connector::Authorizedotnet)), | ||||
|             "bambora" => Ok(Box::new(&connector::Bambora)), | ||||
|             "bitpay" => Ok(Box::new(&connector::Bitpay)), | ||||
|             "bluesnap" => Ok(Box::new(&connector::Bluesnap)), | ||||
|             "braintree" => Ok(Box::new(&connector::Braintree)), | ||||
|             "checkout" => Ok(Box::new(&connector::Checkout)), | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Arvind Patel
					Arvind Patel