mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +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