mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): Add connector cashtocode (#1429)
This commit is contained in:
		| @ -385,6 +385,7 @@ pub struct Connectors { | ||||
|     pub bitpay: ConnectorParams, | ||||
|     pub bluesnap: ConnectorParams, | ||||
|     pub braintree: ConnectorParams, | ||||
|     pub cashtocode: ConnectorParams, | ||||
|     pub checkout: ConnectorParams, | ||||
|     pub coinbase: ConnectorParams, | ||||
|     pub cybersource: ConnectorParams, | ||||
|  | ||||
| @ -6,6 +6,7 @@ pub mod bambora; | ||||
| pub mod bitpay; | ||||
| pub mod bluesnap; | ||||
| pub mod braintree; | ||||
| pub mod cashtocode; | ||||
| pub mod checkout; | ||||
| pub mod coinbase; | ||||
| pub mod cybersource; | ||||
| @ -40,10 +41,11 @@ pub mod zen; | ||||
| pub use self::dummyconnector::DummyConnector; | ||||
| pub use self::{ | ||||
|     aci::Aci, adyen::Adyen, airwallex::Airwallex, authorizedotnet::Authorizedotnet, | ||||
|     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, nmi::Nmi, noon::Noon, nuvei::Nuvei, | ||||
|     opennode::Opennode, payeezy::Payeezy, paypal::Paypal, payu::Payu, rapyd::Rapyd, shift4::Shift4, | ||||
|     stripe::Stripe, trustpay::Trustpay, worldline::Worldline, worldpay::Worldpay, zen::Zen, | ||||
|     bambora::Bambora, bitpay::Bitpay, bluesnap::Bluesnap, braintree::Braintree, | ||||
|     cashtocode::Cashtocode, checkout::Checkout, coinbase::Coinbase, cybersource::Cybersource, | ||||
|     dlocal::Dlocal, fiserv::Fiserv, forte::Forte, globalpay::Globalpay, iatapay::Iatapay, | ||||
|     klarna::Klarna, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nmi::Nmi, | ||||
|     noon::Noon, nuvei::Nuvei, opennode::Opennode, payeezy::Payeezy, paypal::Paypal, payu::Payu, | ||||
|     rapyd::Rapyd, shift4::Shift4, stripe::Stripe, trustpay::Trustpay, worldline::Worldline, | ||||
|     worldpay::Worldpay, zen::Zen, | ||||
| }; | ||||
|  | ||||
| @ -298,6 +298,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for AciPaymentsRequest { | ||||
|             api::PaymentMethodData::Crypto(_) | ||||
|             | api::PaymentMethodData::BankDebit(_) | ||||
|             | api::PaymentMethodData::BankTransfer(_) | ||||
|             | api::PaymentMethodData::Reward(_) | ||||
|             | api::PaymentMethodData::MandatePayment => { | ||||
|                 Err(errors::ConnectorError::NotSupported { | ||||
|                     message: format!("{:?}", item.payment_method), | ||||
|  | ||||
| @ -139,14 +139,12 @@ fn get_pm_and_subsequent_auth_detail( | ||||
|             api::PaymentMethodData::Crypto(_) | ||||
|             | api::PaymentMethodData::BankDebit(_) | ||||
|             | api::PaymentMethodData::MandatePayment | ||||
|             | api::PaymentMethodData::BankTransfer(_) => { | ||||
|                 Err(errors::ConnectorError::NotSupported { | ||||
|                     message: format!("{:?}", item.request.payment_method_data), | ||||
|                     connector: "AuthorizeDotNet", | ||||
|                     payment_experience: api_models::enums::PaymentExperience::RedirectToUrl | ||||
|                         .to_string(), | ||||
|                 })? | ||||
|             } | ||||
|             | api::PaymentMethodData::BankTransfer(_) | ||||
|             | api::PaymentMethodData::Reward(_) => Err(errors::ConnectorError::NotSupported { | ||||
|                 message: format!("{:?}", item.request.payment_method_data), | ||||
|                 connector: "AuthorizeDotNet", | ||||
|                 payment_experience: api_models::enums::PaymentExperience::RedirectToUrl.to_string(), | ||||
|             })?, | ||||
|         }, | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										461
									
								
								crates/router/src/connector/cashtocode.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								crates/router/src/connector/cashtocode.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,461 @@ | ||||
| mod transformers; | ||||
|  | ||||
| use std::fmt::Debug; | ||||
|  | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| use transformers as cashtocode; | ||||
|  | ||||
| use crate::{ | ||||
|     configs::settings, | ||||
|     connector::utils as conn_utils, | ||||
|     core::errors::{self, CustomResult}, | ||||
|     db::StorageInterface, | ||||
|     headers, | ||||
|     services::{ | ||||
|         self, | ||||
|         request::{self, Mask}, | ||||
|         ConnectorIntegration, | ||||
|     }, | ||||
|     types::{ | ||||
|         self, | ||||
|         api::{self, ConnectorCommon, ConnectorCommonExt}, | ||||
|         storage::{self}, | ||||
|         ErrorResponse, Response, | ||||
|     }, | ||||
|     utils::{self, ByteSliceExt, BytesExt}, | ||||
| }; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct Cashtocode; | ||||
|  | ||||
| impl api::Payment for Cashtocode {} | ||||
| impl api::PaymentSession for Cashtocode {} | ||||
| impl api::ConnectorAccessToken for Cashtocode {} | ||||
| impl api::PreVerify for Cashtocode {} | ||||
| impl api::PaymentAuthorize for Cashtocode {} | ||||
| impl api::PaymentSync for Cashtocode {} | ||||
| impl api::PaymentCapture for Cashtocode {} | ||||
| impl api::PaymentVoid for Cashtocode {} | ||||
| impl api::PaymentToken for Cashtocode {} | ||||
| impl api::Refund for Cashtocode {} | ||||
| impl api::RefundExecute for Cashtocode {} | ||||
| impl api::RefundSync for Cashtocode {} | ||||
|  | ||||
| fn get_auth_cashtocode( | ||||
|     payment_method_type: &Option<storage::enums::PaymentMethodType>, | ||||
|     auth_type: &types::ConnectorAuthType, | ||||
| ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { | ||||
|     match payment_method_type | ||||
|         .clone() | ||||
|         .ok_or_else(conn_utils::missing_field_err("payment_method_type")) | ||||
|     { | ||||
|         Ok(reward_type) => match reward_type { | ||||
|             storage::enums::PaymentMethodType::ClassicReward => match auth_type { | ||||
|                 types::ConnectorAuthType::BodyKey { api_key, .. } => Ok(vec![( | ||||
|                     headers::AUTHORIZATION.to_string(), | ||||
|                     format!("Basic {api_key}").into_masked(), | ||||
|                 )]), | ||||
|                 _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|             }, | ||||
|             storage::enums::PaymentMethodType::Evoucher => match auth_type { | ||||
|                 types::ConnectorAuthType::BodyKey { key1, .. } => Ok(vec![( | ||||
|                     headers::AUTHORIZATION.to_string(), | ||||
|                     format!("Basic {key1}").into_masked(), | ||||
|                 )]), | ||||
|                 _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|             }, | ||||
|             _ => Err(error_stack::report!(errors::ConnectorError::NotSupported { | ||||
|                 message: reward_type.to_string(), | ||||
|                 connector: "cashtocode", | ||||
|                 payment_experience: "Try with a different payment method".to_string(), | ||||
|             })), | ||||
|         }, | ||||
|         Err(_) => Err(errors::ConnectorError::FailedToObtainAuthType.into()), | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl | ||||
|     ConnectorIntegration< | ||||
|         api::PaymentMethodToken, | ||||
|         types::PaymentMethodTokenizationData, | ||||
|         types::PaymentsResponseData, | ||||
|     > for Cashtocode | ||||
| { | ||||
|     // Not Implemented (R) | ||||
| } | ||||
|  | ||||
| impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Cashtocode where | ||||
|     Self: ConnectorIntegration<Flow, Request, Response> | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorCommon for Cashtocode { | ||||
|     fn id(&self) -> &'static str { | ||||
|         "cashtocode" | ||||
|     } | ||||
|  | ||||
|     fn common_get_content_type(&self) -> &'static str { | ||||
|         "application/json" | ||||
|     } | ||||
|  | ||||
|     fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { | ||||
|         connectors.cashtocode.base_url.as_ref() | ||||
|     } | ||||
|  | ||||
|     fn get_auth_header( | ||||
|         &self, | ||||
|         auth_type: &types::ConnectorAuthType, | ||||
|     ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { | ||||
|         let auth = cashtocode::CashtocodeAuthType::try_from(auth_type) | ||||
|             .change_context(errors::ConnectorError::FailedToObtainAuthType)?; | ||||
|         Ok(vec![( | ||||
|             headers::AUTHORIZATION.to_string(), | ||||
|             auth.api_key.into_masked(), | ||||
|         )]) | ||||
|     } | ||||
|  | ||||
|     fn build_error_response( | ||||
|         &self, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||
|         let response: cashtocode::CashtocodeErrorResponse = res | ||||
|             .response | ||||
|             .parse_struct("CashtocodeErrorResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         Ok(ErrorResponse { | ||||
|             status_code: res.status_code, | ||||
|             code: response.error.to_string(), | ||||
|             message: response.error_description, | ||||
|             reason: None, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
|     //TODO: implement sessions flow | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken> | ||||
|     for Cashtocode | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
|     fn get_headers( | ||||
|         &self, | ||||
|         req: &types::PaymentsAuthorizeRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> { | ||||
|         let mut header = vec![( | ||||
|             headers::CONTENT_TYPE.to_string(), | ||||
|             types::PaymentsAuthorizeType::get_content_type(self) | ||||
|                 .to_owned() | ||||
|                 .into(), | ||||
|         )]; | ||||
|         let auth_differentiator = | ||||
|             get_auth_cashtocode(&req.request.payment_method_type, &req.connector_auth_type); | ||||
|  | ||||
|         let mut api_key = match auth_differentiator { | ||||
|             Ok(auth_type) => auth_type, | ||||
|             Err(err) => return Err(err), | ||||
|         }; | ||||
|         header.append(&mut api_key); | ||||
|         Ok(header) | ||||
|     } | ||||
|  | ||||
|     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!( | ||||
|             "{}/merchant/paytokens", | ||||
|             connectors.cashtocode.base_url | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn get_request_body( | ||||
|         &self, | ||||
|         req: &types::PaymentsAuthorizeRouterData, | ||||
|     ) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> { | ||||
|         let req_obj = cashtocode::CashtocodePaymentsRequest::try_from(req)?; | ||||
|         let cashtocode_req = types::RequestBody::log_and_get_request_body( | ||||
|             &req_obj, | ||||
|             utils::Encode::<cashtocode::CashtocodePaymentsRequest>::encode_to_string_of_json, | ||||
|         ) | ||||
|         .change_context(errors::ConnectorError::RequestEncodingFailed)?; | ||||
|         Ok(Some(cashtocode_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: cashtocode::CashtocodePaymentsResponse = res | ||||
|             .response | ||||
|             .parse_struct("Cashtocode PaymentsAuthorizeResponse") | ||||
|             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||
|         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 Cashtocode | ||||
| { | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         _req: &types::PaymentsSyncRouterData, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::FlowNotSupported { | ||||
|             flow: "Payments Sync".to_string(), | ||||
|             connector: "Cashtocode".to_string(), | ||||
|         } | ||||
|         .into()) | ||||
|     } | ||||
|  | ||||
|     fn handle_response( | ||||
|         &self, | ||||
|         data: &types::PaymentsSyncRouterData, | ||||
|         res: Response, | ||||
|     ) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> { | ||||
|         types::RouterData::try_from(types::ResponseRouterData { | ||||
|             response: cashtocode::CashtocodePaymentsSyncResponse {}, | ||||
|             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 Cashtocode | ||||
| { | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         _req: &types::RouterData< | ||||
|             api::Capture, | ||||
|             types::PaymentsCaptureData, | ||||
|             types::PaymentsResponseData, | ||||
|         >, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::FlowNotSupported { | ||||
|             flow: "Capture".to_string(), | ||||
|             connector: "Cashtocode".to_string(), | ||||
|         } | ||||
|         .into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         _req: &types::RouterData<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::FlowNotSupported { | ||||
|             flow: "Payments Cancel".to_string(), | ||||
|             connector: "Cashtocode".to_string(), | ||||
|         } | ||||
|         .into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl api::IncomingWebhook for Cashtocode { | ||||
|     fn get_webhook_source_verification_signature( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         let base64_signature = conn_utils::get_header_key_value("authorization", request.headers)?; | ||||
|         let signature = base64_signature.as_bytes().to_owned(); | ||||
|         Ok(signature) | ||||
|     } | ||||
|  | ||||
|     async fn get_webhook_source_verification_merchant_secret( | ||||
|         &self, | ||||
|         db: &dyn StorageInterface, | ||||
|         merchant_id: &str, | ||||
|     ) -> CustomResult<Vec<u8>, errors::ConnectorError> { | ||||
|         let key = conn_utils::get_webhook_merchant_secret_key(self.id(), merchant_id); | ||||
|         let secret = match db.find_config_by_key(&key).await { | ||||
|             Ok(config) => Some(config), | ||||
|             Err(e) => { | ||||
|                 crate::logger::warn!("Unable to fetch merchant webhook secret from DB: {:#?}", e); | ||||
|                 None | ||||
|             } | ||||
|         }; | ||||
|         Ok(secret | ||||
|             .map(|conf| conf.config.into_bytes()) | ||||
|             .unwrap_or_default()) | ||||
|     } | ||||
|  | ||||
|     async fn verify_webhook_source( | ||||
|         &self, | ||||
|         db: &dyn StorageInterface, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|         merchant_id: &str, | ||||
|     ) -> CustomResult<bool, errors::ConnectorError> { | ||||
|         let signature = self | ||||
|             .get_webhook_source_verification_signature(request) | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|         let secret = self | ||||
|             .get_webhook_source_verification_merchant_secret(db, merchant_id) | ||||
|             .await | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||
|         let secret_auth = String::from_utf8(secret.to_vec()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) | ||||
|             .attach_printable("Could not convert secret to UTF-8")?; | ||||
|         let signature_auth = String::from_utf8(signature.to_vec()) | ||||
|             .into_report() | ||||
|             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) | ||||
|             .attach_printable("Could not convert secret to UTF-8")?; | ||||
|         Ok(signature_auth == secret_auth) | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_object_reference_id( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { | ||||
|         let webhook: transformers::CashtocodeObjectId = request | ||||
|             .body | ||||
|             .parse_struct("CashtocodeObjectId") | ||||
|             .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||
|  | ||||
|         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( | ||||
|             api_models::payments::PaymentIdType::ConnectorTransactionId(webhook.transaction_id), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_event_type( | ||||
|         &self, | ||||
|         _request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> { | ||||
|         Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_resource_object( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<serde_json::Value, errors::ConnectorError> { | ||||
|         let webhook: transformers::CashtocodeIncomingWebhook = request | ||||
|             .body | ||||
|             .parse_struct("CashtocodeIncomingWebhook") | ||||
|             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||
|         let res_json = | ||||
|             utils::Encode::<transformers::CashtocodeIncomingWebhook>::encode_to_value(&webhook) | ||||
|                 .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||
|  | ||||
|         Ok(res_json) | ||||
|     } | ||||
|  | ||||
|     fn get_webhook_api_response( | ||||
|         &self, | ||||
|         request: &api::IncomingWebhookRequestDetails<'_>, | ||||
|     ) -> CustomResult<services::api::ApplicationResponse<serde_json::Value>, errors::ConnectorError> | ||||
|     { | ||||
|         let status = "EXECUTED".to_string(); | ||||
|         let obj: transformers::CashtocodeObjectId = request | ||||
|             .body | ||||
|             .parse_struct("CashtocodeObjectId") | ||||
|             .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||
|         let response: serde_json::Value = | ||||
|             serde_json::json!({ "status": status, "transactionId" : obj.transaction_id}); | ||||
|         Ok(services::api::ApplicationResponse::Json(response)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::refunds::Execute, types::RefundsData, types::RefundsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         _req: &types::RouterData< | ||||
|             api::refunds::Execute, | ||||
|             types::RefundsData, | ||||
|             types::RefundsResponseData, | ||||
|         >, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::FlowNotSupported { | ||||
|             flow: "Refunds".to_string(), | ||||
|             connector: "Cashtocode".to_string(), | ||||
|         } | ||||
|         .into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ConnectorIntegration<api::refunds::RSync, types::RefundsData, types::RefundsResponseData> | ||||
|     for Cashtocode | ||||
| { | ||||
|     fn build_request( | ||||
|         &self, | ||||
|         _req: &types::RouterData< | ||||
|             api::refunds::RSync, | ||||
|             types::RefundsData, | ||||
|             types::RefundsResponseData, | ||||
|         >, | ||||
|         _connectors: &settings::Connectors, | ||||
|     ) -> CustomResult<Option<services::Request>, errors::ConnectorError> { | ||||
|         Err(errors::ConnectorError::FlowNotSupported { | ||||
|             flow: "Refund Sync".to_string(), | ||||
|             connector: "Cashtocode".to_string(), | ||||
|         } | ||||
|         .into()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										226
									
								
								crates/router/src/connector/cashtocode/transformers.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								crates/router/src/connector/cashtocode/transformers.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,226 @@ | ||||
| use common_utils::pii::Email; | ||||
| use masking::Secret; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     connector::utils::RouterData, | ||||
|     core::errors, | ||||
|     services, | ||||
|     types::{self, api, storage::enums}, | ||||
| }; | ||||
|  | ||||
| #[derive(Default, Debug, Serialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct CashtocodePaymentsRequest { | ||||
|     amount: i64, | ||||
|     transaction_id: String, | ||||
|     user_id: Secret<String>, | ||||
|     currency: enums::Currency, | ||||
|     first_name: Option<Secret<String>>, | ||||
|     last_name: Option<Secret<String>>, | ||||
|     user_alias: Secret<String>, | ||||
|     requested_url: String, | ||||
|     cancel_url: String, | ||||
|     email: Option<Email>, | ||||
|     mid: String, | ||||
| } | ||||
|  | ||||
| pub struct CashToCodeMandatoryParams { | ||||
|     pub user_id: Secret<String>, | ||||
|     pub user_alias: Secret<String>, | ||||
|     pub requested_url: String, | ||||
|     pub cancel_url: String, | ||||
| } | ||||
|  | ||||
| fn get_mid( | ||||
|     payment_method_data: &api::payments::PaymentMethodData, | ||||
| ) -> Result<String, errors::ConnectorError> { | ||||
|     match payment_method_data { | ||||
|         api_models::payments::PaymentMethodData::Reward(reward_data) => { | ||||
|             Ok(reward_data.merchant_id.to_string()) | ||||
|         } | ||||
|         _ => Err(errors::ConnectorError::NotImplemented( | ||||
|             "Payment methods".to_string(), | ||||
|         )), | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn get_mandatory_params( | ||||
|     item: &types::PaymentsAuthorizeRouterData, | ||||
| ) -> Result<CashToCodeMandatoryParams, error_stack::Report<errors::ConnectorError>> { | ||||
|     let customer_id = item.get_customer_id()?; | ||||
|     let url = item.get_return_url()?; | ||||
|     Ok(CashToCodeMandatoryParams { | ||||
|         user_id: Secret::new(customer_id.to_owned()), | ||||
|         user_alias: Secret::new(customer_id), | ||||
|         requested_url: url.to_owned(), | ||||
|         cancel_url: url, | ||||
|     }) | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::PaymentsAuthorizeRouterData> for CashtocodePaymentsRequest { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> { | ||||
|         let params: CashToCodeMandatoryParams = get_mandatory_params(item)?; | ||||
|         let mid = get_mid(&item.request.payment_method_data)?; | ||||
|         match item.payment_method { | ||||
|             storage_models::enums::PaymentMethod::Reward => Ok(Self { | ||||
|                 amount: item.request.amount, | ||||
|                 transaction_id: item.attempt_id.clone(), | ||||
|                 currency: item.request.currency, | ||||
|                 user_id: params.user_id, | ||||
|                 first_name: None, | ||||
|                 last_name: None, | ||||
|                 user_alias: params.user_alias, | ||||
|                 requested_url: params.requested_url, | ||||
|                 cancel_url: params.cancel_url, | ||||
|                 email: item.request.email.clone(), | ||||
|                 mid, | ||||
|             }), | ||||
|             _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct CashtocodeAuthType { | ||||
|     pub(super) api_key: String, | ||||
| } | ||||
|  | ||||
| impl TryFrom<&types::ConnectorAuthType> for CashtocodeAuthType { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from(auth_type: &types::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()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Default, Serialize, Deserialize)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| pub enum CashtocodePaymentStatus { | ||||
|     Succeeded, | ||||
|     #[default] | ||||
|     Processing, | ||||
| } | ||||
|  | ||||
| impl From<CashtocodePaymentStatus> for enums::AttemptStatus { | ||||
|     fn from(item: CashtocodePaymentStatus) -> Self { | ||||
|         match item { | ||||
|             CashtocodePaymentStatus::Succeeded => Self::Charged, | ||||
|             CashtocodePaymentStatus::Processing => Self::AuthenticationPending, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
| pub struct CashtocodeErrors { | ||||
|     pub message: String, | ||||
|     pub path: String, | ||||
|     #[serde(rename = "type")] | ||||
|     pub event_type: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct CashtocodePaymentsResponse { | ||||
|     pub pay_url: String, | ||||
| } | ||||
|  | ||||
| pub struct CashtocodePaymentsSyncResponse {} | ||||
|  | ||||
| impl<F, T> | ||||
|     TryFrom< | ||||
|         types::ResponseRouterData<F, CashtocodePaymentsResponse, T, types::PaymentsResponseData>, | ||||
|     > for types::RouterData<F, T, types::PaymentsResponseData> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::ResponseRouterData< | ||||
|             F, | ||||
|             CashtocodePaymentsResponse, | ||||
|             T, | ||||
|             types::PaymentsResponseData, | ||||
|         >, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         let redirection_data = services::RedirectForm::Form { | ||||
|             endpoint: item.response.pay_url.clone(), | ||||
|             method: services::Method::Post, | ||||
|             form_fields: Default::default(), | ||||
|         }; | ||||
|         Ok(Self { | ||||
|             status: enums::AttemptStatus::AuthenticationPending, | ||||
|             response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId( | ||||
|                     item.data.attempt_id.clone(), | ||||
|                 ), | ||||
|                 redirection_data: Some(redirection_data), | ||||
|                 mandate_reference: None, | ||||
|                 connector_metadata: None, | ||||
|                 network_txn_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, T> | ||||
|     TryFrom< | ||||
|         types::ResponseRouterData< | ||||
|             F, | ||||
|             CashtocodePaymentsSyncResponse, | ||||
|             T, | ||||
|             types::PaymentsResponseData, | ||||
|         >, | ||||
|     > for types::RouterData<F, T, types::PaymentsResponseData> | ||||
| { | ||||
|     type Error = error_stack::Report<errors::ConnectorError>; | ||||
|     fn try_from( | ||||
|         item: types::ResponseRouterData< | ||||
|             F, | ||||
|             CashtocodePaymentsSyncResponse, | ||||
|             T, | ||||
|             types::PaymentsResponseData, | ||||
|         >, | ||||
|     ) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             status: enums::AttemptStatus::Charged, | ||||
|             response: Ok(types::PaymentsResponseData::TransactionResponse { | ||||
|                 resource_id: types::ResponseId::ConnectorTransactionId( | ||||
|                     item.data.attempt_id.clone(), | ||||
|                 ), | ||||
|                 redirection_data: None, | ||||
|                 mandate_reference: None, | ||||
|                 connector_metadata: None, | ||||
|                 network_txn_id: None, | ||||
|             }), | ||||
|             ..item.data | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize)] | ||||
| pub struct CashtocodeErrorResponse { | ||||
|     pub error: String, | ||||
|     pub error_description: String, | ||||
|     pub errors: Option<Vec<CashtocodeErrors>>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct CashtocodeIncomingWebhook { | ||||
|     pub amount: i64, | ||||
|     pub currency: String, | ||||
|     pub foreign_transaction_id: String, | ||||
|     #[serde(rename = "type")] | ||||
|     pub event_type: String, | ||||
|     pub transaction_id: String, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct CashtocodeObjectId { | ||||
|     pub transaction_id: String, | ||||
| } | ||||
| @ -2566,14 +2566,13 @@ impl | ||||
|                     )), | ||||
|                 } | ||||
|             } | ||||
|             api::PaymentMethodData::MandatePayment | api::PaymentMethodData::Crypto(_) => { | ||||
|                 Err(errors::ConnectorError::NotSupported { | ||||
|                     message: format!("{pm_type:?}"), | ||||
|                     connector: "Stripe", | ||||
|                     payment_experience: api_models::enums::PaymentExperience::RedirectToUrl | ||||
|                         .to_string(), | ||||
|                 })? | ||||
|             } | ||||
|             api::PaymentMethodData::MandatePayment | ||||
|             | api::PaymentMethodData::Crypto(_) | ||||
|             | api::PaymentMethodData::Reward(_) => Err(errors::ConnectorError::NotSupported { | ||||
|                 message: format!("{pm_type:?}"), | ||||
|                 connector: "Stripe", | ||||
|                 payment_experience: api_models::enums::PaymentExperience::RedirectToUrl.to_string(), | ||||
|             })?, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -142,6 +142,7 @@ default_imp_for_complete_authorize!( | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
| @ -200,6 +201,7 @@ default_imp_for_create_customer!( | ||||
|     connector::Bambora, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
| @ -262,6 +264,7 @@ default_imp_for_connector_redirect_response!( | ||||
|     connector::Authorizedotnet, | ||||
|     connector::Bitpay, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
|     connector::Dlocal, | ||||
| @ -301,6 +304,7 @@ default_imp_for_connector_request_id!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
| @ -366,6 +370,7 @@ default_imp_for_accept_dispute!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
|     connector::Dlocal, | ||||
| @ -451,6 +456,7 @@ default_imp_for_file_upload!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
|     connector::Dlocal, | ||||
| @ -513,6 +519,7 @@ default_imp_for_submit_evidence!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Cybersource, | ||||
|     connector::Coinbase, | ||||
|     connector::Dlocal, | ||||
| @ -575,6 +582,7 @@ default_imp_for_defend_dispute!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Cybersource, | ||||
|     connector::Coinbase, | ||||
|     connector::Dlocal, | ||||
| @ -638,6 +646,7 @@ default_imp_for_pre_processing_steps!( | ||||
|     connector::Bitpay, | ||||
|     connector::Bluesnap, | ||||
|     connector::Braintree, | ||||
|     connector::Cashtocode, | ||||
|     connector::Checkout, | ||||
|     connector::Coinbase, | ||||
|     connector::Cybersource, | ||||
|  | ||||
| @ -1271,6 +1271,7 @@ pub async fn make_pm_data<'a, F: Clone, R>( | ||||
|         (pm @ Some(api::PaymentMethodData::BankRedirect(_)), _) => Ok(pm.to_owned()), | ||||
|         (pm @ Some(api::PaymentMethodData::Crypto(_)), _) => Ok(pm.to_owned()), | ||||
|         (pm @ Some(api::PaymentMethodData::BankDebit(_)), _) => Ok(pm.to_owned()), | ||||
|         (pm @ Some(api::PaymentMethodData::Reward(_)), _) => Ok(pm.to_owned()), | ||||
|         (pm_opt @ Some(pm @ api::PaymentMethodData::BankTransfer(_)), _) => { | ||||
|             let token = vault::Vault::store_payment_method_data_in_locker( | ||||
|                 state, | ||||
|  | ||||
| @ -40,7 +40,6 @@ pub async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>( | ||||
|     } else { | ||||
|         payments::CallConnectorAction::Trigger | ||||
|     }; | ||||
|  | ||||
|     let payments_response = match webhook_details.object_reference_id { | ||||
|         api_models::webhooks::ObjectReferenceId::PaymentId(id) => { | ||||
|             payments::payments_core::<api::PSync, api::PaymentsResponse, _, _, _>( | ||||
|  | ||||
| @ -169,6 +169,7 @@ Never share your secret api keys. Keep them guarded and secure. | ||||
|         api_models::payments::WeChatPayRedirection, | ||||
|         api_models::payments::BankDebitBilling, | ||||
|         api_models::payments::CryptoData, | ||||
|         api_models::payments::RewardData, | ||||
|         api_models::payments::Address, | ||||
|         api_models::payments::BankRedirectData, | ||||
|         api_models::payments::BankRedirectBilling, | ||||
|  | ||||
| @ -410,8 +410,9 @@ impl Webhooks { | ||||
|                     .route( | ||||
|                         web::post().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>), | ||||
|                     ) | ||||
|                     .route(web::get().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>)) | ||||
|                     .route( | ||||
|                         web::get().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>), | ||||
|                         web::put().to(receive_incoming_webhook::<webhook_type::OutgoingWebhook>), | ||||
|                     ), | ||||
|             ) | ||||
|     } | ||||
|  | ||||
| @ -209,6 +209,7 @@ impl ConnectorData { | ||||
|                 enums::Connector::Bitpay => Ok(Box::new(&connector::Bitpay)), | ||||
|                 enums::Connector::Bluesnap => Ok(Box::new(&connector::Bluesnap)), | ||||
|                 enums::Connector::Braintree => Ok(Box::new(&connector::Braintree)), | ||||
|                 enums::Connector::Cashtocode => Ok(Box::new(&connector::Cashtocode)), | ||||
|                 enums::Connector::Checkout => Ok(Box::new(&connector::Checkout)), | ||||
|                 enums::Connector::Coinbase => Ok(Box::new(&connector::Coinbase)), | ||||
|                 enums::Connector::Cybersource => Ok(Box::new(&connector::Cybersource)), | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 BallaNitesh
					BallaNitesh