mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(connector): [Recurly] Add record back support for recurly [V2] (#7544)
Co-authored-by: Aniket Burman <aniket.burman@192.168.1.4> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Aniket Burman <aniket.burman@192.168.1.5>
This commit is contained in:
@ -24,6 +24,13 @@ use hyperswitch_domain_models::{
|
|||||||
RefundSyncRouterData, RefundsRouterData,
|
RefundSyncRouterData, RefundsRouterData,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
use hyperswitch_domain_models::{
|
||||||
|
router_flow_types::RecoveryRecordBack,
|
||||||
|
router_request_types::revenue_recovery::RevenueRecoveryRecordBackRequest,
|
||||||
|
router_response_types::revenue_recovery::RevenueRecoveryRecordBackResponse,
|
||||||
|
types::RevenueRecoveryRecordBackRouterData,
|
||||||
|
};
|
||||||
use hyperswitch_interfaces::{
|
use hyperswitch_interfaces::{
|
||||||
api::{
|
api::{
|
||||||
self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
|
self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
|
||||||
@ -38,10 +45,16 @@ use hyperswitch_interfaces::{
|
|||||||
use masking::{ExposeInterface, Mask};
|
use masking::{ExposeInterface, Mask};
|
||||||
use transformers as recurly;
|
use transformers as recurly;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
use crate::connectors::recurly::transformers::RecurlyRecordStatus;
|
||||||
use crate::{
|
use crate::{
|
||||||
connectors::recurly::transformers::RecurlyWebhookBody, constants::headers,
|
connectors::recurly::transformers::RecurlyWebhookBody, constants::headers,
|
||||||
types::ResponseRouterData, utils,
|
types::ResponseRouterData, utils,
|
||||||
};
|
};
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
const STATUS_SUCCESSFUL_ENDPOINT: &str = "mark_successful";
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
const STATUS_FAILED_ENDPOINT: &str = "mark_failed";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Recurly {
|
pub struct Recurly {
|
||||||
@ -85,7 +98,8 @@ impl api::Refund for Recurly {}
|
|||||||
impl api::RefundExecute for Recurly {}
|
impl api::RefundExecute for Recurly {}
|
||||||
impl api::RefundSync for Recurly {}
|
impl api::RefundSync for Recurly {}
|
||||||
impl api::PaymentToken for Recurly {}
|
impl api::PaymentToken for Recurly {}
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
impl api::revenue_recovery::RevenueRecoveryRecordBack for Recurly {}
|
||||||
impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
|
impl ConnectorIntegration<PaymentMethodToken, PaymentMethodTokenizationData, PaymentsResponseData>
|
||||||
for Recurly
|
for Recurly
|
||||||
{
|
{
|
||||||
@ -561,6 +575,96 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Recurly {
|
|||||||
self.build_error_response(res, event_builder)
|
self.build_error_response(res, event_builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
impl
|
||||||
|
ConnectorIntegration<
|
||||||
|
RecoveryRecordBack,
|
||||||
|
RevenueRecoveryRecordBackRequest,
|
||||||
|
RevenueRecoveryRecordBackResponse,
|
||||||
|
> for Recurly
|
||||||
|
{
|
||||||
|
fn get_headers(
|
||||||
|
&self,
|
||||||
|
req: &RevenueRecoveryRecordBackRouterData,
|
||||||
|
connectors: &Connectors,
|
||||||
|
) -> CustomResult<Vec<(String, masking::Maskable<String>)>, errors::ConnectorError> {
|
||||||
|
self.build_headers(req, connectors)
|
||||||
|
}
|
||||||
|
fn get_url(
|
||||||
|
&self,
|
||||||
|
req: &RevenueRecoveryRecordBackRouterData,
|
||||||
|
connectors: &Connectors,
|
||||||
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
|
let invoice_id = req
|
||||||
|
.request
|
||||||
|
.merchant_reference_id
|
||||||
|
.get_string_repr()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let status = RecurlyRecordStatus::try_from(req.request.attempt_status)?;
|
||||||
|
|
||||||
|
let status_endpoint = match status {
|
||||||
|
RecurlyRecordStatus::Success => STATUS_SUCCESSFUL_ENDPOINT,
|
||||||
|
RecurlyRecordStatus::Failure => STATUS_FAILED_ENDPOINT,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"{}/invoices/{invoice_id}/{status_endpoint}",
|
||||||
|
self.base_url(connectors)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_type(&self) -> &'static str {
|
||||||
|
self.common_get_content_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_request(
|
||||||
|
&self,
|
||||||
|
req: &RevenueRecoveryRecordBackRouterData,
|
||||||
|
connectors: &Connectors,
|
||||||
|
) -> CustomResult<Option<Request>, errors::ConnectorError> {
|
||||||
|
Ok(Some(
|
||||||
|
RequestBuilder::new()
|
||||||
|
.method(Method::Put)
|
||||||
|
.url(&types::RevenueRecoveryRecordBackType::get_url(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.attach_default_headers()
|
||||||
|
.headers(types::RevenueRecoveryRecordBackType::get_headers(
|
||||||
|
self, req, connectors,
|
||||||
|
)?)
|
||||||
|
.header("Content-Length", "0")
|
||||||
|
.build(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_response(
|
||||||
|
&self,
|
||||||
|
data: &RevenueRecoveryRecordBackRouterData,
|
||||||
|
event_builder: Option<&mut ConnectorEvent>,
|
||||||
|
res: Response,
|
||||||
|
) -> CustomResult<RevenueRecoveryRecordBackRouterData, errors::ConnectorError> {
|
||||||
|
let response: recurly::RecurlyRecordbackResponse = res
|
||||||
|
.response
|
||||||
|
.parse_struct("recurly RecurlyRecordbackResponse")
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl webhooks::IncomingWebhook for Recurly {
|
impl webhooks::IncomingWebhook for Recurly {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use common_enums::enums;
|
use common_enums::enums;
|
||||||
use common_utils::{errors::CustomResult, ext_traits::ByteSliceExt, types::StringMinorUnit};
|
use common_utils::{
|
||||||
|
errors::CustomResult, ext_traits::ByteSliceExt, id_type, types::StringMinorUnit,
|
||||||
|
};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use hyperswitch_domain_models::{
|
use hyperswitch_domain_models::{
|
||||||
payment_method_data::PaymentMethodData,
|
payment_method_data::PaymentMethodData,
|
||||||
@ -9,6 +11,13 @@ use hyperswitch_domain_models::{
|
|||||||
router_response_types::{PaymentsResponseData, RefundsResponseData},
|
router_response_types::{PaymentsResponseData, RefundsResponseData},
|
||||||
types::{PaymentsAuthorizeRouterData, RefundsRouterData},
|
types::{PaymentsAuthorizeRouterData, RefundsRouterData},
|
||||||
};
|
};
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
use hyperswitch_domain_models::{
|
||||||
|
router_flow_types::RecoveryRecordBack,
|
||||||
|
router_request_types::revenue_recovery::RevenueRecoveryRecordBackRequest,
|
||||||
|
router_response_types::revenue_recovery::RevenueRecoveryRecordBackResponse,
|
||||||
|
types::RevenueRecoveryRecordBackRouterData,
|
||||||
|
};
|
||||||
use hyperswitch_interfaces::errors;
|
use hyperswitch_interfaces::errors;
|
||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -251,3 +260,85 @@ impl RecurlyWebhookBody {
|
|||||||
Ok(webhook_body)
|
Ok(webhook_body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone, Copy)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RecurlyRecordStatus {
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
impl TryFrom<enums::AttemptStatus> for RecurlyRecordStatus {
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(status: enums::AttemptStatus) -> Result<Self, Self::Error> {
|
||||||
|
match status {
|
||||||
|
enums::AttemptStatus::Charged
|
||||||
|
| enums::AttemptStatus::PartialCharged
|
||||||
|
| enums::AttemptStatus::PartialChargedAndChargeable => Ok(Self::Success),
|
||||||
|
enums::AttemptStatus::Failure
|
||||||
|
| enums::AttemptStatus::CaptureFailed
|
||||||
|
| enums::AttemptStatus::RouterDeclined => Ok(Self::Failure),
|
||||||
|
enums::AttemptStatus::AuthenticationFailed
|
||||||
|
| enums::AttemptStatus::Started
|
||||||
|
| enums::AttemptStatus::AuthenticationPending
|
||||||
|
| enums::AttemptStatus::AuthenticationSuccessful
|
||||||
|
| enums::AttemptStatus::Authorized
|
||||||
|
| enums::AttemptStatus::AuthorizationFailed
|
||||||
|
| enums::AttemptStatus::Authorizing
|
||||||
|
| enums::AttemptStatus::CodInitiated
|
||||||
|
| enums::AttemptStatus::Voided
|
||||||
|
| enums::AttemptStatus::VoidInitiated
|
||||||
|
| enums::AttemptStatus::CaptureInitiated
|
||||||
|
| enums::AttemptStatus::VoidFailed
|
||||||
|
| enums::AttemptStatus::AutoRefunded
|
||||||
|
| enums::AttemptStatus::Unresolved
|
||||||
|
| enums::AttemptStatus::Pending
|
||||||
|
| enums::AttemptStatus::PaymentMethodAwaited
|
||||||
|
| enums::AttemptStatus::ConfirmationAwaited
|
||||||
|
| enums::AttemptStatus::DeviceDataCollectionPending => {
|
||||||
|
Err(errors::ConnectorError::NotSupported {
|
||||||
|
message: "Record back flow is only supported for terminal status".to_string(),
|
||||||
|
connector: "recurly",
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct RecurlyRecordbackResponse {
|
||||||
|
// inovice id
|
||||||
|
pub id: id_type::PaymentReferenceId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||||
|
impl
|
||||||
|
TryFrom<
|
||||||
|
ResponseRouterData<
|
||||||
|
RecoveryRecordBack,
|
||||||
|
RecurlyRecordbackResponse,
|
||||||
|
RevenueRecoveryRecordBackRequest,
|
||||||
|
RevenueRecoveryRecordBackResponse,
|
||||||
|
>,
|
||||||
|
> for RevenueRecoveryRecordBackRouterData
|
||||||
|
{
|
||||||
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
|
fn try_from(
|
||||||
|
item: ResponseRouterData<
|
||||||
|
RecoveryRecordBack,
|
||||||
|
RecurlyRecordbackResponse,
|
||||||
|
RevenueRecoveryRecordBackRequest,
|
||||||
|
RevenueRecoveryRecordBackResponse,
|
||||||
|
>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
let merchant_reference_id = item.response.id;
|
||||||
|
Ok(Self {
|
||||||
|
response: Ok(RevenueRecoveryRecordBackResponse {
|
||||||
|
merchant_reference_id,
|
||||||
|
}),
|
||||||
|
..item.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3738,7 +3738,6 @@ default_imp_for_revenue_recovery_record_back!(
|
|||||||
connectors::Placetopay,
|
connectors::Placetopay,
|
||||||
connectors::Rapyd,
|
connectors::Rapyd,
|
||||||
connectors::Razorpay,
|
connectors::Razorpay,
|
||||||
connectors::Recurly,
|
|
||||||
connectors::Redsys,
|
connectors::Redsys,
|
||||||
connectors::Shift4,
|
connectors::Shift4,
|
||||||
connectors::Stax,
|
connectors::Stax,
|
||||||
|
|||||||
Reference in New Issue
Block a user