Skip to content

Commit

Permalink
refactor(connector): [bluesnap] add refund status and webhooks (#2374)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamraatBansal authored Sep 27, 2023
1 parent 77b51d5 commit fe43458
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 95 deletions.
109 changes: 49 additions & 60 deletions crates/router/src/connector/bluesnap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,21 +903,27 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
req: &types::RefundSyncRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let meta_data: CustomResult<bluesnap::BluesnapConnectorMetaData, errors::ConnectorError> =
connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone());

match meta_data {
// if merchant_id is present, rsync can be made using merchant_transaction_id
Ok(data) => get_url_with_merchant_transaction_id(
self.base_url(connectors).to_string(),
data.merchant_id,
req.attempt_id.to_owned(),
),
// otherwise rsync is made using connector_transaction_id
Err(_) => get_rsync_url_with_connector_transaction_id(
req,
self.base_url(connectors).to_string(),
),
if req.request.payment_amount == req.request.refund_amount {
let meta_data: CustomResult<
bluesnap::BluesnapConnectorMetaData,
errors::ConnectorError,
> = connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone());

match meta_data {
// if merchant_id is present, rsync can be made using merchant_transaction_id
Ok(data) => get_url_with_merchant_transaction_id(
self.base_url(connectors).to_string(),
data.merchant_id,
req.attempt_id.to_owned(),
),
// otherwise rsync is made using connector_transaction_id
Err(_) => get_rsync_url_with_connector_refund_id(
req,
self.base_url(connectors).to_string(),
),
}
} else {
get_rsync_url_with_connector_refund_id(req, self.base_url(connectors).to_string())
}
}

Expand Down Expand Up @@ -1002,11 +1008,31 @@ impl api::IncomingWebhook for Bluesnap {
serde_urlencoded::from_bytes(request.body)
.into_report()
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::PaymentAttemptId(
webhook_body.merchant_transaction_id,
),
))
match webhook_body.transaction_type {
bluesnap::BluesnapWebhookEvents::Decline
| bluesnap::BluesnapWebhookEvents::CcChargeFailed
| bluesnap::BluesnapWebhookEvents::Charge
| bluesnap::BluesnapWebhookEvents::Chargeback
| bluesnap::BluesnapWebhookEvents::ChargebackStatusChanged => {
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
api_models::payments::PaymentIdType::PaymentAttemptId(
webhook_body.merchant_transaction_id,
),
))
}
bluesnap::BluesnapWebhookEvents::Refund => {
Ok(api_models::webhooks::ObjectReferenceId::RefundId(
api_models::webhooks::RefundIdType::ConnectorRefundId(
webhook_body
.reversal_ref_num
.ok_or(errors::ConnectorError::WebhookReferenceIdNotFound)?,
),
))
}
bluesnap::BluesnapWebhookEvents::Unknown => {
Err(errors::ConnectorError::WebhookReferenceIdNotFound).into_report()
}
}
}

fn get_webhook_event_type(
Expand Down Expand Up @@ -1049,49 +1075,12 @@ impl api::IncomingWebhook for Bluesnap {
&self,
request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
let details: bluesnap::BluesnapWebhookObjectResource =
let resource: bluesnap::BluesnapWebhookObjectResource =
serde_urlencoded::from_bytes(request.body)
.into_report()
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;

let (card_transaction_type, processing_status) = match details.transaction_type {
bluesnap::BluesnapWebhookEvents::Decline
| bluesnap::BluesnapWebhookEvents::CcChargeFailed => Ok((
bluesnap::BluesnapTxnType::Capture,
bluesnap::BluesnapProcessingStatus::Fail,
)),
bluesnap::BluesnapWebhookEvents::Charge => Ok((
bluesnap::BluesnapTxnType::Capture,
bluesnap::BluesnapProcessingStatus::Success,
)),
bluesnap::BluesnapWebhookEvents::Chargeback
| bluesnap::BluesnapWebhookEvents::ChargebackStatusChanged => {
// returning the complete incoming webhook body, It won't be consumed in dispute flow, so currently does not hold any significance
let res_json =
utils::Encode::<bluesnap::BluesnapWebhookObjectResource>::encode_to_value(
&details,
)
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
return Ok(res_json);
}
bluesnap::BluesnapWebhookEvents::Unknown => {
Err(errors::ConnectorError::WebhookResourceObjectNotFound)
}
}?;

let psync_struct = bluesnap::BluesnapPaymentsResponse {
processing_info: bluesnap::ProcessingInfoResponse {
processing_status,
authorization_code: None,
network_transaction_id: None,
},
transaction_id: details.reference_number,
card_transaction_type,
};

let res_json =
utils::Encode::<transformers::BluesnapPaymentsResponse>::encode_to_value(&psync_struct)
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
let res_json = serde_json::Value::try_from(resource)?;

Ok(res_json)
}
Expand Down Expand Up @@ -1283,7 +1272,7 @@ fn get_psync_url_with_connector_transaction_id(
))
}

fn get_rsync_url_with_connector_transaction_id(
fn get_rsync_url_with_connector_refund_id(
req: &types::RefundSyncRouterData,
base_url: String,
) -> CustomResult<String, errors::ConnectorError> {
Expand Down
111 changes: 76 additions & 35 deletions crates/router/src/connector/bluesnap/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ pub struct BluesnapPaymentsRequest {
payment_method: PaymentMethodDetails,
currency: enums::Currency,
card_transaction_type: BluesnapTxnType,
three_d_secure: Option<BluesnapThreeDSecureInfo>,
transaction_fraud_info: Option<TransactionFraudInfo>,
card_holder_info: Option<BluesnapCardHolderInfo>,
merchant_transaction_id: Option<String>,
Expand Down Expand Up @@ -400,7 +399,6 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues
payment_method,
currency: item.router_data.request.currency,
card_transaction_type: auth_mode,
three_d_secure: None,
transaction_fraud_info: Some(TransactionFraudInfo {
fraud_session_id: item.router_data.payment_id.clone(),
}),
Expand Down Expand Up @@ -700,33 +698,6 @@ impl TryFrom<&types::ConnectorAuthType> for BluesnapAuthType {
}
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BluesnapCustomerResponse {
vaulted_shopper_id: Secret<u64>,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, BluesnapCustomerResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
BluesnapCustomerResponse,
T,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::PaymentsResponseData::ConnectorCustomerResponse {
connector_customer_id: item.response.vaulted_shopper_id.expose().to_string(),
}),
..item.data
})
}
}

// PaymentsResponse
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
Expand Down Expand Up @@ -784,6 +755,15 @@ impl From<BluesnapProcessingStatus> for enums::RefundStatus {
}
}

impl From<BluesnapRefundStatus> for enums::RefundStatus {
fn from(item: BluesnapRefundStatus) -> Self {
match item {
BluesnapRefundStatus::Success => Self::Success,
BluesnapRefundStatus::Pending => Self::Pending,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BluesnapPaymentsResponse {
Expand Down Expand Up @@ -865,10 +845,18 @@ impl<F> TryFrom<&BluesnapRouterData<&types::RefundsRouterData<F>>> for BluesnapR
}
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum BluesnapRefundStatus {
Success,
#[default]
Pending,
}
#[derive(Default, Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefundResponse {
refund_transaction_id: i32,
refund_status: BluesnapRefundStatus,
}

impl TryFrom<types::RefundsResponseRouterData<api::RSync, BluesnapPaymentsResponse>>
Expand Down Expand Up @@ -900,7 +888,7 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.refund_transaction_id.to_string(),
refund_status: enums::RefundStatus::Pending,
refund_status: enums::RefundStatus::from(item.response.refund_status),
}),
..item.data
})
Expand All @@ -911,13 +899,15 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
pub struct BluesnapWebhookBody {
pub merchant_transaction_id: String,
pub reference_number: String,
pub transaction_type: BluesnapWebhookEvents,
pub reversal_ref_num: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BluesnapWebhookObjectEventType {
pub transaction_type: BluesnapWebhookEvents,
pub cb_status: Option<BluesnapChargebackStatus>,
transaction_type: BluesnapWebhookEvents,
cb_status: Option<BluesnapChargebackStatus>,
}

#[derive(Debug, Deserialize)]
Expand All @@ -937,12 +927,13 @@ pub enum BluesnapChargebackStatus {
CompletedWon,
}

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BluesnapWebhookEvents {
Decline,
CcChargeFailed,
Charge,
Refund,
Chargeback,
ChargebackStatusChanged,
#[serde(other)]
Expand All @@ -957,6 +948,7 @@ impl TryFrom<BluesnapWebhookObjectEventType> for api::IncomingWebhookEvent {
Ok(Self::PaymentIntentFailure)
}
BluesnapWebhookEvents::Charge => Ok(Self::PaymentIntentSuccess),
BluesnapWebhookEvents::Refund => Ok(Self::RefundSuccess),
BluesnapWebhookEvents::Chargeback | BluesnapWebhookEvents::ChargebackStatusChanged => {
match details
.cb_status
Expand Down Expand Up @@ -988,8 +980,57 @@ pub struct BluesnapDisputeWebhookBody {
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BluesnapWebhookObjectResource {
pub reference_number: String,
pub transaction_type: BluesnapWebhookEvents,
reference_number: String,
transaction_type: BluesnapWebhookEvents,
reversal_ref_num: Option<String>,
}

impl TryFrom<BluesnapWebhookObjectResource> for serde_json::Value {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(details: BluesnapWebhookObjectResource) -> Result<Self, Self::Error> {
let (card_transaction_type, processing_status, transaction_id) = match details
.transaction_type
{
BluesnapWebhookEvents::Decline | BluesnapWebhookEvents::CcChargeFailed => Ok((
BluesnapTxnType::Capture,
BluesnapProcessingStatus::Fail,
details.reference_number,
)),
BluesnapWebhookEvents::Charge => Ok((
BluesnapTxnType::Capture,
BluesnapProcessingStatus::Success,
details.reference_number,
)),
BluesnapWebhookEvents::Chargeback | BluesnapWebhookEvents::ChargebackStatusChanged => {
//It won't be consumed in dispute flow, so currently does not hold any significance
return serde_json::to_value(details)
.into_report()
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed);
}
BluesnapWebhookEvents::Refund => Ok((
BluesnapTxnType::Refund,
BluesnapProcessingStatus::Success,
details
.reversal_ref_num
.ok_or(errors::ConnectorError::WebhookResourceObjectNotFound)?,
)),
BluesnapWebhookEvents::Unknown => {
Err(errors::ConnectorError::WebhookResourceObjectNotFound).into_report()
}
}?;
let sync_struct = BluesnapPaymentsResponse {
processing_info: ProcessingInfoResponse {
processing_status,
authorization_code: None,
network_transaction_id: None,
},
transaction_id,
card_transaction_type,
};
serde_json::to_value(sync_struct)
.into_report()
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)
}
}

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
Expand Down

0 comments on commit fe43458

Please sign in to comment.