mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(connector): [facilitapay] fix refunds, add webhook and void support (#8778)
This commit is contained in:
		| @ -6157,8 +6157,8 @@ api_secret="Secret Key" | |||||||
|   key1="Username" |   key1="Username" | ||||||
| [facilitapay.metadata.destination_account_number] | [facilitapay.metadata.destination_account_number] | ||||||
|   name="destination_account_number" |   name="destination_account_number" | ||||||
|   label="Destination Account Number" |   label="Merchant Account Number" | ||||||
|   placeholder="Enter Destination Account Number" |   placeholder="Enter Merchant's (to_bank_account_id) Account Number" | ||||||
|   required=true |   required=true | ||||||
|   type="Text" |   type="Text" | ||||||
|  |  | ||||||
|  | |||||||
| @ -4725,8 +4725,8 @@ key1 = "Username" | |||||||
|  |  | ||||||
| [facilitapay.metadata.destination_account_number] | [facilitapay.metadata.destination_account_number] | ||||||
| name="destination_account_number" | name="destination_account_number" | ||||||
| label="Destination Account Number" | label="Merchant Account Number" | ||||||
| placeholder="Enter Destination Account Number" | placeholder="Enter Merchant's (to_bank_account_id) Account Number" | ||||||
| required=true | required=true | ||||||
| type="Text" | type="Text" | ||||||
|  |  | ||||||
|  | |||||||
| @ -6139,8 +6139,8 @@ key1 = "Username" | |||||||
|  |  | ||||||
| [facilitapay.metadata.destination_account_number] | [facilitapay.metadata.destination_account_number] | ||||||
| name="destination_account_number" | name="destination_account_number" | ||||||
| label="Destination Account Number" | label="Merchant Account Number" | ||||||
| placeholder="Enter Destination Account Number" | placeholder="Enter Merchant's (to_bank_account_id) Account Number" | ||||||
| required=true | required=true | ||||||
| type="Text" | type="Text" | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,12 +4,13 @@ pub mod transformers; | |||||||
|  |  | ||||||
| use common_enums::enums; | use common_enums::enums; | ||||||
| use common_utils::{ | use common_utils::{ | ||||||
|  |     crypto, | ||||||
|     errors::CustomResult, |     errors::CustomResult, | ||||||
|     ext_traits::BytesExt, |     ext_traits::{ByteSliceExt, BytesExt, ValueExt}, | ||||||
|     request::{Method, Request, RequestBuilder, RequestContent}, |     request::{Method, Request, RequestBuilder, RequestContent}, | ||||||
|     types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, |     types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, | ||||||
| }; | }; | ||||||
| use error_stack::{report, ResultExt}; | use error_stack::ResultExt; | ||||||
| use hyperswitch_domain_models::{ | use hyperswitch_domain_models::{ | ||||||
|     router_data::{AccessToken, ErrorResponse, RouterData}, |     router_data::{AccessToken, ErrorResponse, RouterData}, | ||||||
|     router_flow_types::{ |     router_flow_types::{ | ||||||
| @ -30,8 +31,8 @@ use hyperswitch_domain_models::{ | |||||||
|         SupportedPaymentMethods, SupportedPaymentMethodsExt, |         SupportedPaymentMethods, SupportedPaymentMethodsExt, | ||||||
|     }, |     }, | ||||||
|     types::{ |     types::{ | ||||||
|         ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, |         ConnectorCustomerRouterData, PaymentsAuthorizeRouterData, PaymentsCancelRouterData, | ||||||
|         PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, |         PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| use hyperswitch_interfaces::{ | use hyperswitch_interfaces::{ | ||||||
| @ -46,18 +47,19 @@ use hyperswitch_interfaces::{ | |||||||
|     webhooks, |     webhooks, | ||||||
| }; | }; | ||||||
| use lazy_static::lazy_static; | use lazy_static::lazy_static; | ||||||
| use masking::{Mask, PeekInterface}; | use masking::{ExposeInterface, Mask, PeekInterface}; | ||||||
| use requests::{ | use requests::{ | ||||||
|     FacilitapayAuthRequest, FacilitapayCustomerRequest, FacilitapayPaymentsRequest, |     FacilitapayAuthRequest, FacilitapayCustomerRequest, FacilitapayPaymentsRequest, | ||||||
|     FacilitapayRefundRequest, FacilitapayRouterData, |     FacilitapayRouterData, | ||||||
| }; | }; | ||||||
| use responses::{ | use responses::{ | ||||||
|     FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentsResponse, |     FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentsResponse, | ||||||
|     FacilitapayRefundResponse, |     FacilitapayRefundResponse, FacilitapayWebhookEventType, | ||||||
| }; | }; | ||||||
| use transformers::parse_facilitapay_error_response; | use transformers::parse_facilitapay_error_response; | ||||||
|  |  | ||||||
| use crate::{ | use crate::{ | ||||||
|  |     connectors::facilitapay::responses::FacilitapayVoidResponse, | ||||||
|     constants::headers, |     constants::headers, | ||||||
|     types::{RefreshTokenRouterData, ResponseRouterData}, |     types::{RefreshTokenRouterData, ResponseRouterData}, | ||||||
|     utils::{self, RefundsRequestData}, |     utils::{self, RefundsRequestData}, | ||||||
| @ -581,7 +583,72 @@ impl ConnectorIntegration<Capture, PaymentsCaptureData, PaymentsResponseData> fo | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Facilitapay {} | impl ConnectorIntegration<Void, PaymentsCancelData, PaymentsResponseData> for Facilitapay { | ||||||
|  |     fn get_headers( | ||||||
|  |         &self, | ||||||
|  |         req: &PaymentsCancelRouterData, | ||||||
|  |         connectors: &Connectors, | ||||||
|  |     ) -> CustomResult<Vec<(String, masking::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: &PaymentsCancelRouterData, | ||||||
|  |         connectors: &Connectors, | ||||||
|  |     ) -> CustomResult<String, errors::ConnectorError> { | ||||||
|  |         Ok(format!( | ||||||
|  |             "{}/transactions/{}/refund", | ||||||
|  |             self.base_url(connectors), | ||||||
|  |             req.request.connector_transaction_id | ||||||
|  |         )) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn build_request( | ||||||
|  |         &self, | ||||||
|  |         req: &PaymentsCancelRouterData, | ||||||
|  |         connectors: &Connectors, | ||||||
|  |     ) -> CustomResult<Option<Request>, errors::ConnectorError> { | ||||||
|  |         let request = RequestBuilder::new() | ||||||
|  |             .method(Method::Get) | ||||||
|  |             .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) | ||||||
|  |             .attach_default_headers() | ||||||
|  |             .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) | ||||||
|  |             .build(); | ||||||
|  |         Ok(Some(request)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn handle_response( | ||||||
|  |         &self, | ||||||
|  |         data: &PaymentsCancelRouterData, | ||||||
|  |         event_builder: Option<&mut ConnectorEvent>, | ||||||
|  |         res: Response, | ||||||
|  |     ) -> CustomResult<PaymentsCancelRouterData, errors::ConnectorError> { | ||||||
|  |         let response: FacilitapayVoidResponse = res | ||||||
|  |             .response | ||||||
|  |             .parse_struct("FacilitapayCancelResponse") | ||||||
|  |             .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; | ||||||
|  |         event_builder.map(|i| i.set_response_body(&response)); | ||||||
|  |         router_env::logger::info!(connector_response=?response); | ||||||
|  |         RouterData::try_from(ResponseRouterData { | ||||||
|  |             response, | ||||||
|  |             data: data.clone(), | ||||||
|  |             http_code: res.status_code, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn get_error_response( | ||||||
|  |         &self, | ||||||
|  |         res: Response, | ||||||
|  |         event_builder: Option<&mut ConnectorEvent>, | ||||||
|  |     ) -> CustomResult<ErrorResponse, errors::ConnectorError> { | ||||||
|  |         self.build_error_response(res, event_builder) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilitapay { | impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilitapay { | ||||||
|     fn get_headers( |     fn get_headers( | ||||||
| @ -608,37 +675,27 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Facilit | |||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_request_body( |  | ||||||
|         &self, |  | ||||||
|         req: &RefundsRouterData<Execute>, |  | ||||||
|         _connectors: &Connectors, |  | ||||||
|     ) -> CustomResult<RequestContent, errors::ConnectorError> { |  | ||||||
|         let refund_amount = utils::convert_amount( |  | ||||||
|             self.amount_converter, |  | ||||||
|             req.request.minor_refund_amount, |  | ||||||
|             req.request.currency, |  | ||||||
|         )?; |  | ||||||
|  |  | ||||||
|         let connector_router_data = FacilitapayRouterData::from((refund_amount, req)); |  | ||||||
|         let connector_req = FacilitapayRefundRequest::try_from(&connector_router_data)?; |  | ||||||
|         Ok(RequestContent::Json(Box::new(connector_req))) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn build_request( |     fn build_request( | ||||||
|         &self, |         &self, | ||||||
|         req: &RefundsRouterData<Execute>, |         req: &RefundsRouterData<Execute>, | ||||||
|         connectors: &Connectors, |         connectors: &Connectors, | ||||||
|     ) -> CustomResult<Option<Request>, errors::ConnectorError> { |     ) -> CustomResult<Option<Request>, errors::ConnectorError> { | ||||||
|  |         // Validate that this is a full refund | ||||||
|  |         if req.request.payment_amount != req.request.refund_amount { | ||||||
|  |             return Err(errors::ConnectorError::NotSupported { | ||||||
|  |                 message: "Partial refund not supported by Facilitapay".to_string(), | ||||||
|  |                 connector: "Facilitapay", | ||||||
|  |             } | ||||||
|  |             .into()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         let request = RequestBuilder::new() |         let request = RequestBuilder::new() | ||||||
|             .method(Method::Post) |             .method(Method::Get) | ||||||
|             .url(&types::RefundExecuteType::get_url(self, req, connectors)?) |             .url(&types::RefundExecuteType::get_url(self, req, connectors)?) | ||||||
|             .attach_default_headers() |             .attach_default_headers() | ||||||
|             .headers(types::RefundExecuteType::get_headers( |             .headers(types::RefundExecuteType::get_headers( | ||||||
|                 self, req, connectors, |                 self, req, connectors, | ||||||
|             )?) |             )?) | ||||||
|             .set_body(types::RefundExecuteType::get_request_body( |  | ||||||
|                 self, req, connectors, |  | ||||||
|             )?) |  | ||||||
|             .build(); |             .build(); | ||||||
|         Ok(Some(request)) |         Ok(Some(request)) | ||||||
|     } |     } | ||||||
| @ -743,25 +800,117 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Facilitap | |||||||
|  |  | ||||||
| #[async_trait::async_trait] | #[async_trait::async_trait] | ||||||
| impl webhooks::IncomingWebhook for Facilitapay { | impl webhooks::IncomingWebhook for Facilitapay { | ||||||
|  |     async fn verify_webhook_source( | ||||||
|  |         &self, | ||||||
|  |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|  |         _merchant_id: &common_utils::id_type::MerchantId, | ||||||
|  |         connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>, | ||||||
|  |         _connector_account_details: crypto::Encryptable<masking::Secret<serde_json::Value>>, | ||||||
|  |         _connector_name: &str, | ||||||
|  |     ) -> CustomResult<bool, errors::ConnectorError> { | ||||||
|  |         let webhook_body: responses::FacilitapayWebhookNotification = request | ||||||
|  |             .body | ||||||
|  |             .parse_struct("FacilitapayWebhookNotification") | ||||||
|  |             .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||||
|  |  | ||||||
|  |         let connector_webhook_secrets = match connector_webhook_details { | ||||||
|  |             Some(secret_value) => { | ||||||
|  |                 let secret = secret_value | ||||||
|  |                     .parse_value::<api_models::admin::MerchantConnectorWebhookDetails>( | ||||||
|  |                         "MerchantConnectorWebhookDetails", | ||||||
|  |                     ) | ||||||
|  |                     .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; | ||||||
|  |                 secret.merchant_secret.expose() | ||||||
|  |             } | ||||||
|  |             None => "default_secret".to_string(), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // FacilitaPay uses a simple 4-digit secret for verification | ||||||
|  |         Ok(webhook_body.notification.secret.peek() == &connector_webhook_secrets) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn get_webhook_object_reference_id( |     fn get_webhook_object_reference_id( | ||||||
|         &self, |         &self, | ||||||
|         _request: &webhooks::IncomingWebhookRequestDetails<'_>, |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { |     ) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> { | ||||||
|         Err(report!(errors::ConnectorError::WebhooksNotImplemented)) |         let webhook_body: responses::FacilitapayWebhookNotification = request | ||||||
|  |             .body | ||||||
|  |             .parse_struct("FacilitapayWebhookNotification") | ||||||
|  |             .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; | ||||||
|  |  | ||||||
|  |         // Extract transaction ID from the webhook data | ||||||
|  |         let transaction_id = match &webhook_body.notification.data { | ||||||
|  |             responses::FacilitapayWebhookData::Transaction { transaction_id } | ||||||
|  |             | responses::FacilitapayWebhookData::CardPayment { transaction_id, .. } => { | ||||||
|  |                 transaction_id.clone() | ||||||
|  |             } | ||||||
|  |             responses::FacilitapayWebhookData::Exchange { | ||||||
|  |                 transaction_ids, .. | ||||||
|  |             } | ||||||
|  |             | responses::FacilitapayWebhookData::Wire { | ||||||
|  |                 transaction_ids, .. | ||||||
|  |             } | ||||||
|  |             | responses::FacilitapayWebhookData::WireError { | ||||||
|  |                 transaction_ids, .. | ||||||
|  |             } => transaction_ids | ||||||
|  |                 .first() | ||||||
|  |                 .ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)? | ||||||
|  |                 .clone(), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // For refund webhooks, Facilitapay sends the original payment transaction ID | ||||||
|  |         // not the refund transaction ID | ||||||
|  |         Ok(api_models::webhooks::ObjectReferenceId::PaymentId( | ||||||
|  |             api_models::payments::PaymentIdType::ConnectorTransactionId(transaction_id), | ||||||
|  |         )) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_webhook_event_type( |     fn get_webhook_event_type( | ||||||
|         &self, |         &self, | ||||||
|         _request: &webhooks::IncomingWebhookRequestDetails<'_>, |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|     ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> { |     ) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> { | ||||||
|         Err(report!(errors::ConnectorError::WebhooksNotImplemented)) |         let webhook_body: responses::FacilitapayWebhookNotification = request | ||||||
|  |             .body | ||||||
|  |             .parse_struct("FacilitapayWebhookNotification") | ||||||
|  |             .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; | ||||||
|  |  | ||||||
|  |         // Note: For "identified" events, we need additional logic to determine if it's cross-currency | ||||||
|  |         // Since we don't have access to the payment data here, we'll default to Success for now | ||||||
|  |         // The actual status determination happens in the webhook processing flow | ||||||
|  |         let event = match webhook_body.notification.event_type { | ||||||
|  |             FacilitapayWebhookEventType::ExchangeCreated => { | ||||||
|  |                 api_models::webhooks::IncomingWebhookEvent::PaymentIntentProcessing | ||||||
|  |             } | ||||||
|  |             FacilitapayWebhookEventType::Identified | ||||||
|  |             | FacilitapayWebhookEventType::PaymentApproved | ||||||
|  |             | FacilitapayWebhookEventType::WireCreated => { | ||||||
|  |                 api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess | ||||||
|  |             } | ||||||
|  |             FacilitapayWebhookEventType::PaymentExpired | ||||||
|  |             | FacilitapayWebhookEventType::PaymentFailed => { | ||||||
|  |                 api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure | ||||||
|  |             } | ||||||
|  |             FacilitapayWebhookEventType::PaymentRefunded => { | ||||||
|  |                 api_models::webhooks::IncomingWebhookEvent::RefundSuccess | ||||||
|  |             } | ||||||
|  |             FacilitapayWebhookEventType::WireWaitingCorrection => { | ||||||
|  |                 api_models::webhooks::IncomingWebhookEvent::PaymentActionRequired | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         Ok(event) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_webhook_resource_object( |     fn get_webhook_resource_object( | ||||||
|         &self, |         &self, | ||||||
|         _request: &webhooks::IncomingWebhookRequestDetails<'_>, |         request: &webhooks::IncomingWebhookRequestDetails<'_>, | ||||||
|     ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> { |     ) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> { | ||||||
|         Err(report!(errors::ConnectorError::WebhooksNotImplemented)) |         let webhook_body: responses::FacilitapayWebhookNotification = request | ||||||
|  |             .body | ||||||
|  |             .parse_struct("FacilitapayWebhookNotification") | ||||||
|  |             .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; | ||||||
|  |  | ||||||
|  |         Ok(Box::new(webhook_body)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -794,7 +943,9 @@ lazy_static! { | |||||||
|  |  | ||||||
|         facilitapay_supported_payment_methods |         facilitapay_supported_payment_methods | ||||||
|     }; |     }; | ||||||
|     static ref FACILITAPAY_SUPPORTED_WEBHOOK_FLOWS: Vec<enums::EventClass> = Vec::new(); |     static ref FACILITAPAY_SUPPORTED_WEBHOOK_FLOWS: Vec<enums::EventClass> = vec![ | ||||||
|  |         enums::EventClass::Payments, | ||||||
|  |     ]; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl ConnectorSpecifications for Facilitapay { | impl ConnectorSpecifications for Facilitapay { | ||||||
|  | |||||||
| @ -69,12 +69,6 @@ pub struct FacilitapayPaymentsRequest { | |||||||
|     pub transaction: FacilitapayTransactionRequest, |     pub transaction: FacilitapayTransactionRequest, | ||||||
| } | } | ||||||
|  |  | ||||||
| // Type definition for RefundRequest |  | ||||||
| #[derive(Default, Debug, Serialize)] |  | ||||||
| pub struct FacilitapayRefundRequest { |  | ||||||
|     pub amount: StringMajorUnit, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Serialize, PartialEq)] | #[derive(Debug, Serialize, PartialEq)] | ||||||
| #[serde(rename_all = "snake_case")] | #[serde(rename_all = "snake_case")] | ||||||
| pub struct FacilitapayCustomerRequest { | pub struct FacilitapayCustomerRequest { | ||||||
|  | |||||||
| @ -136,6 +136,7 @@ pub struct BankAccountDetail { | |||||||
|     pub routing_number: Option<Secret<String>>, |     pub routing_number: Option<Secret<String>>, | ||||||
|     pub pix_info: Option<PixInfo>, |     pub pix_info: Option<PixInfo>, | ||||||
|     pub owner_name: Option<Secret<String>>, |     pub owner_name: Option<Secret<String>>, | ||||||
|  |     pub owner_document_type: Option<String>, | ||||||
|     pub owner_document_number: Option<Secret<String>>, |     pub owner_document_number: Option<Secret<String>>, | ||||||
|     pub owner_company: Option<OwnerCompany>, |     pub owner_company: Option<OwnerCompany>, | ||||||
|     pub internal: Option<bool>, |     pub internal: Option<bool>, | ||||||
| @ -176,7 +177,7 @@ pub struct TransactionData { | |||||||
|     pub subject_is_receiver: Option<bool>, |     pub subject_is_receiver: Option<bool>, | ||||||
|  |  | ||||||
|     // Source identification (potentially redundant with subject or card/bank info) |     // Source identification (potentially redundant with subject or card/bank info) | ||||||
|     pub source_name: Secret<String>, |     pub source_name: Option<Secret<String>>, | ||||||
|     pub source_document_type: DocumentType, |     pub source_document_type: DocumentType, | ||||||
|     pub source_document_number: Secret<String>, |     pub source_document_number: Secret<String>, | ||||||
|  |  | ||||||
| @ -204,14 +205,124 @@ pub struct TransactionData { | |||||||
|     pub meta: Option<serde_json::Value>, |     pub meta: Option<serde_json::Value>, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Void response structures (for /refund endpoint) | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct RefundData { | pub struct VoidBankTransaction { | ||||||
|     #[serde(rename = "id")] |     #[serde(rename = "id")] | ||||||
|     pub refund_id: String, |     pub transaction_id: String, | ||||||
|     pub status: FacilitapayPaymentStatus, |     pub value: StringMajorUnit, | ||||||
|  |     pub currency: api_models::enums::Currency, | ||||||
|  |     pub iof_value: Option<StringMajorUnit>, | ||||||
|  |     pub fx_value: Option<StringMajorUnit>, | ||||||
|  |     pub exchange_rate: Option<StringMajorUnit>, | ||||||
|  |     pub exchange_currency: api_models::enums::Currency, | ||||||
|  |     pub exchanged_value: StringMajorUnit, | ||||||
|  |     pub exchange_approved: bool, | ||||||
|  |     pub wire_id: Option<String>, | ||||||
|  |     pub exchange_id: Option<String>, | ||||||
|  |     pub movement_date: String, | ||||||
|  |     pub source_name: Secret<String>, | ||||||
|  |     pub source_document_number: Secret<String>, | ||||||
|  |     pub source_document_type: String, | ||||||
|  |     pub source_id: String, | ||||||
|  |     pub source_type: String, | ||||||
|  |     pub source_description: String, | ||||||
|  |     pub source_bank: Option<String>, | ||||||
|  |     pub source_branch: Option<String>, | ||||||
|  |     pub source_account: Option<String>, | ||||||
|  |     pub source_bank_ispb: Option<String>, | ||||||
|  |     pub company_id: String, | ||||||
|  |     pub company_name: String, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
| pub struct FacilitapayRefundResponse { | pub struct VoidData { | ||||||
|     pub data: RefundData, |     #[serde(rename = "id")] | ||||||
|  |     pub void_id: String, | ||||||
|  |     pub reason: Option<String>, | ||||||
|  |     pub inserted_at: String, | ||||||
|  |     pub status: FacilitapayPaymentStatus, | ||||||
|  |     pub transaction_kind: String, | ||||||
|  |     pub bank_transaction: VoidBankTransaction, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct FacilitapayVoidResponse { | ||||||
|  |     pub data: VoidData, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Refund response uses the same TransactionData structure as payments | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct FacilitapayRefundResponse { | ||||||
|  |     pub data: TransactionData, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Webhook structures | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | pub struct FacilitapayWebhookNotification { | ||||||
|  |     pub notification: FacilitapayWebhookBody, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | #[serde(rename_all = "snake_case")] | ||||||
|  | pub struct FacilitapayWebhookBody { | ||||||
|  |     #[serde(rename = "type")] | ||||||
|  |     pub event_type: FacilitapayWebhookEventType, | ||||||
|  |     pub secret: Secret<String>, | ||||||
|  |     #[serde(flatten)] | ||||||
|  |     pub data: FacilitapayWebhookData, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||||
|  | #[serde(rename_all = "snake_case")] | ||||||
|  | pub enum FacilitapayWebhookEventType { | ||||||
|  |     ExchangeCreated, | ||||||
|  |     Identified, | ||||||
|  |     PaymentApproved, | ||||||
|  |     PaymentExpired, | ||||||
|  |     PaymentFailed, | ||||||
|  |     PaymentRefunded, | ||||||
|  |     WireCreated, | ||||||
|  |     WireWaitingCorrection, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] | ||||||
|  | #[serde(rename_all = "UPPERCASE")] | ||||||
|  | pub enum FacilitapayWebhookErrorCode { | ||||||
|  |     /// Creditor account number invalid or missing (branch_number or account_number incorrect) | ||||||
|  |     Ac03, | ||||||
|  |     /// Creditor account type missing or invalid (account_type incorrect) | ||||||
|  |     Ac14, | ||||||
|  |     /// Value in Creditor Identifier is incorrect (owner_document_number incorrect) | ||||||
|  |     Ch11, | ||||||
|  |     /// Transaction type not supported/authorized on this account (account rejected the payment) | ||||||
|  |     Ag03, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Serialize, Deserialize)] | ||||||
|  | #[serde(untagged)] | ||||||
|  | pub enum FacilitapayWebhookData { | ||||||
|  |     CardPayment { | ||||||
|  |         transaction_id: String, | ||||||
|  |         checkout_id: Option<String>, | ||||||
|  |     }, | ||||||
|  |     Exchange { | ||||||
|  |         exchange_id: String, | ||||||
|  |         transaction_ids: Vec<String>, | ||||||
|  |     }, | ||||||
|  |     Transaction { | ||||||
|  |         transaction_id: String, | ||||||
|  |     }, | ||||||
|  |     Wire { | ||||||
|  |         wire_id: String, | ||||||
|  |         transaction_ids: Vec<String>, | ||||||
|  |     }, | ||||||
|  |     WireError { | ||||||
|  |         error_code: FacilitapayWebhookErrorCode, | ||||||
|  |         error_description: String, | ||||||
|  |         bank_account_owner_id: String, | ||||||
|  |         bank_account_id: String, | ||||||
|  |         transaction_ids: Vec<String>, | ||||||
|  |         wire_id: String, | ||||||
|  |     }, | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,8 +11,11 @@ use error_stack::ResultExt; | |||||||
| use hyperswitch_domain_models::{ | use hyperswitch_domain_models::{ | ||||||
|     payment_method_data::{BankTransferData, PaymentMethodData}, |     payment_method_data::{BankTransferData, PaymentMethodData}, | ||||||
|     router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, |     router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, | ||||||
|     router_flow_types::refunds::{Execute, RSync}, |     router_flow_types::{ | ||||||
|     router_request_types::ResponseId, |         payments::Void, | ||||||
|  |         refunds::{Execute, RSync}, | ||||||
|  |     }, | ||||||
|  |     router_request_types::{PaymentsCancelData, ResponseId}, | ||||||
|     router_response_types::{PaymentsResponseData, RefundsResponseData}, |     router_response_types::{PaymentsResponseData, RefundsResponseData}, | ||||||
|     types, |     types, | ||||||
| }; | }; | ||||||
| @ -27,12 +30,12 @@ use url::Url; | |||||||
| use super::{ | use super::{ | ||||||
|     requests::{ |     requests::{ | ||||||
|         DocumentType, FacilitapayAuthRequest, FacilitapayCredentials, FacilitapayCustomerRequest, |         DocumentType, FacilitapayAuthRequest, FacilitapayCredentials, FacilitapayCustomerRequest, | ||||||
|         FacilitapayPaymentsRequest, FacilitapayPerson, FacilitapayRefundRequest, |         FacilitapayPaymentsRequest, FacilitapayPerson, FacilitapayRouterData, | ||||||
|         FacilitapayRouterData, FacilitapayTransactionRequest, PixTransactionRequest, |         FacilitapayTransactionRequest, PixTransactionRequest, | ||||||
|     }, |     }, | ||||||
|     responses::{ |     responses::{ | ||||||
|         FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentStatus, |         FacilitapayAuthResponse, FacilitapayCustomerResponse, FacilitapayPaymentStatus, | ||||||
|         FacilitapayPaymentsResponse, FacilitapayRefundResponse, |         FacilitapayPaymentsResponse, FacilitapayRefundResponse, FacilitapayVoidResponse, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -509,17 +512,6 @@ fn get_qr_code_data( | |||||||
|         .change_context(errors::ConnectorError::ResponseHandlingFailed) |         .change_context(errors::ConnectorError::ResponseHandlingFailed) | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<F> TryFrom<&FacilitapayRouterData<&types::RefundsRouterData<F>>> for FacilitapayRefundRequest { |  | ||||||
|     type Error = Error; |  | ||||||
|     fn try_from( |  | ||||||
|         item: &FacilitapayRouterData<&types::RefundsRouterData<F>>, |  | ||||||
|     ) -> Result<Self, Self::Error> { |  | ||||||
|         Ok(Self { |  | ||||||
|             amount: item.amount.clone(), |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<FacilitapayPaymentStatus> for enums::RefundStatus { | impl From<FacilitapayPaymentStatus> for enums::RefundStatus { | ||||||
|     fn from(item: FacilitapayPaymentStatus) -> Self { |     fn from(item: FacilitapayPaymentStatus) -> Self { | ||||||
|         match item { |         match item { | ||||||
| @ -532,6 +524,56 @@ impl From<FacilitapayPaymentStatus> for enums::RefundStatus { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Void (cancel unprocessed payment) transformer | ||||||
|  | impl | ||||||
|  |     TryFrom< | ||||||
|  |         ResponseRouterData<Void, FacilitapayVoidResponse, PaymentsCancelData, PaymentsResponseData>, | ||||||
|  |     > for RouterData<Void, PaymentsCancelData, PaymentsResponseData> | ||||||
|  | { | ||||||
|  |     type Error = Error; | ||||||
|  |     fn try_from( | ||||||
|  |         item: ResponseRouterData< | ||||||
|  |             Void, | ||||||
|  |             FacilitapayVoidResponse, | ||||||
|  |             PaymentsCancelData, | ||||||
|  |             PaymentsResponseData, | ||||||
|  |         >, | ||||||
|  |     ) -> Result<Self, Self::Error> { | ||||||
|  |         let status = common_enums::AttemptStatus::from(item.response.data.status.clone()); | ||||||
|  |  | ||||||
|  |         Ok(Self { | ||||||
|  |             status, | ||||||
|  |             response: if is_payment_failure(status) { | ||||||
|  |                 Err(ErrorResponse { | ||||||
|  |                     code: item.response.data.status.clone().to_string(), | ||||||
|  |                     message: item.response.data.status.clone().to_string(), | ||||||
|  |                     reason: item.response.data.reason, | ||||||
|  |                     status_code: item.http_code, | ||||||
|  |                     attempt_status: None, | ||||||
|  |                     connector_transaction_id: Some(item.response.data.void_id.clone()), | ||||||
|  |                     network_decline_code: None, | ||||||
|  |                     network_advice_code: None, | ||||||
|  |                     network_error_message: None, | ||||||
|  |                 }) | ||||||
|  |             } else { | ||||||
|  |                 Ok(PaymentsResponseData::TransactionResponse { | ||||||
|  |                     resource_id: ResponseId::ConnectorTransactionId( | ||||||
|  |                         item.response.data.void_id.clone(), | ||||||
|  |                     ), | ||||||
|  |                     redirection_data: Box::new(None), | ||||||
|  |                     mandate_reference: Box::new(None), | ||||||
|  |                     connector_metadata: None, | ||||||
|  |                     network_txn_id: None, | ||||||
|  |                     connector_response_reference_id: Some(item.response.data.void_id), | ||||||
|  |                     incremental_authorization_allowed: None, | ||||||
|  |                     charges: None, | ||||||
|  |                 }) | ||||||
|  |             }, | ||||||
|  |             ..item.data | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl TryFrom<RefundsResponseRouterData<Execute, FacilitapayRefundResponse>> | impl TryFrom<RefundsResponseRouterData<Execute, FacilitapayRefundResponse>> | ||||||
|     for types::RefundsRouterData<Execute> |     for types::RefundsRouterData<Execute> | ||||||
| { | { | ||||||
| @ -541,7 +583,7 @@ impl TryFrom<RefundsResponseRouterData<Execute, FacilitapayRefundResponse>> | |||||||
|     ) -> Result<Self, Self::Error> { |     ) -> Result<Self, Self::Error> { | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|             response: Ok(RefundsResponseData { |             response: Ok(RefundsResponseData { | ||||||
|                 connector_refund_id: item.response.data.refund_id.to_string(), |                 connector_refund_id: item.response.data.transaction_id.clone(), | ||||||
|                 refund_status: enums::RefundStatus::from(item.response.data.status), |                 refund_status: enums::RefundStatus::from(item.response.data.status), | ||||||
|             }), |             }), | ||||||
|             ..item.data |             ..item.data | ||||||
| @ -558,7 +600,7 @@ impl TryFrom<RefundsResponseRouterData<RSync, FacilitapayRefundResponse>> | |||||||
|     ) -> Result<Self, Self::Error> { |     ) -> Result<Self, Self::Error> { | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|             response: Ok(RefundsResponseData { |             response: Ok(RefundsResponseData { | ||||||
|                 connector_refund_id: item.response.data.refund_id.to_string(), |                 connector_refund_id: item.response.data.transaction_id.clone(), | ||||||
|                 refund_status: enums::RefundStatus::from(item.response.data.status), |                 refund_status: enums::RefundStatus::from(item.response.data.status), | ||||||
|             }), |             }), | ||||||
|             ..item.data |             ..item.data | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Pa1NarK
					Pa1NarK