mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +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,
|
||||
},
|
||||
};
|
||||
#[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::{
|
||||
api::{
|
||||
self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications,
|
||||
@ -38,10 +45,16 @@ use hyperswitch_interfaces::{
|
||||
use masking::{ExposeInterface, Mask};
|
||||
use transformers as recurly;
|
||||
|
||||
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
|
||||
use crate::connectors::recurly::transformers::RecurlyRecordStatus;
|
||||
use crate::{
|
||||
connectors::recurly::transformers::RecurlyWebhookBody, constants::headers,
|
||||
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)]
|
||||
pub struct Recurly {
|
||||
@ -85,7 +98,8 @@ impl api::Refund for Recurly {}
|
||||
impl api::RefundExecute for Recurly {}
|
||||
impl api::RefundSync 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>
|
||||
for Recurly
|
||||
{
|
||||
@ -561,6 +575,96 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Recurly {
|
||||
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]
|
||||
impl webhooks::IncomingWebhook for Recurly {
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
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 hyperswitch_domain_models::{
|
||||
payment_method_data::PaymentMethodData,
|
||||
@ -9,6 +11,13 @@ use hyperswitch_domain_models::{
|
||||
router_response_types::{PaymentsResponseData, RefundsResponseData},
|
||||
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 masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -251,3 +260,85 @@ impl RecurlyWebhookBody {
|
||||
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::Rapyd,
|
||||
connectors::Razorpay,
|
||||
connectors::Recurly,
|
||||
connectors::Redsys,
|
||||
connectors::Shift4,
|
||||
connectors::Stax,
|
||||
|
||||
Reference in New Issue
Block a user