feat(connector): [Prophetpay] Save card token for Refund and remove Void flow (#2927)

This commit is contained in:
Sakil Mostak
2023-11-21 21:22:50 +05:30
committed by GitHub
parent e66ccde4cf
commit 15a255ea60
2 changed files with 269 additions and 156 deletions

View File

@ -107,16 +107,15 @@ impl ConnectorCommon for Prophetpay {
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: prophetpay::ProphetpayErrorResponse = res
let response: serde_json::Value = res
.response
.parse_struct("ProphetpayErrorResponse")
.parse_struct("ProphetPayErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
Ok(ErrorResponse {
status_code: res.status_code,
code: response.status.to_string(),
message: response.title,
reason: Some(response.errors.to_string()),
code: consts::NO_ERROR_CODE.to_string(),
message: consts::NO_ERROR_MESSAGE.to_string(),
reason: Some(response.to_string()),
attempt_status: None,
})
}
@ -324,7 +323,7 @@ impl
where
types::PaymentsResponseData: Clone,
{
let response: prophetpay::ProphetpayResponse = res
let response: prophetpay::ProphetpayCompleteAuthResponse = res
.response
.parse_struct("prophetpay ProphetpayResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
@ -407,9 +406,9 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
data: &types::PaymentsSyncRouterData,
res: Response,
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
let response: prophetpay::ProphetpayResponse = res
let response: prophetpay::ProphetpaySyncResponse = res
.response
.parse_struct("prophetpay ProphetpayResponse")
.parse_struct("prophetpay PaymentsSyncResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
@ -431,9 +430,12 @@ impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::Payme
{
}
// This is Void Implementation for Prophetpay
// Since Prophetpay does not have capture this have been commented out but kept if it is required for future usage
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Prophetpay
{
/*
fn get_headers(
&self,
req: &types::PaymentsCancelRouterData,
@ -471,33 +473,25 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(prophetpay_req))
}
*/
fn build_request(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
_req: &types::PaymentsCancelRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
.body(types::PaymentsVoidType::get_request_body(
self, req, connectors,
)?)
.build(),
))
Err(errors::ConnectorError::NotImplemented("Void flow not implemented".to_string()).into())
}
/*
fn handle_response(
&self,
data: &types::PaymentsCancelRouterData,
res: Response,
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
let response: prophetpay::ProphetpayResponse = res
let response: prophetpay::ProphetpayVoidResponse = res
.response
.parse_struct("prophetpay ProphetpayResponse")
.parse_struct("prophetpay PaymentsCancelResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
@ -512,6 +506,7 @@ impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsR
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
*/
}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
@ -652,7 +647,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Get)
.method(services::Method::Post)
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
.attach_default_headers()
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
@ -668,7 +663,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
data: &types::RefundSyncRouterData,
res: Response,
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
let response: prophetpay::ProphetpayRefundResponse = res
let response: prophetpay::ProphetpayRefundSyncResponse = res
.response
.parse_struct("prophetpay ProphetpayRefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
connector::utils,
connector::utils::{self, to_connector_meta},
core::errors,
services,
types::{self, api, storage::enums},
@ -159,7 +159,11 @@ impl TryFrom<&ProphetpayRouterData<&types::PaymentsAuthorizeRouterData>>
),
}
} else {
Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into())
Err(errors::ConnectorError::CurrencyNotSupported {
message: item.router_data.request.currency.to_string(),
connector: "Prophetpay",
}
.into())
}
}
}
@ -266,10 +270,7 @@ impl TryFrom<&ProphetpayRouterData<&types::PaymentsCompleteAuthorizeRouterData>>
Ok(Self {
amount: item.amount.to_owned(),
ref_info: item.router_data.connector_request_reference_id.to_owned(),
inquiry_reference: format!(
"inquiry_{}",
item.router_data.connector_request_reference_id
),
inquiry_reference: item.router_data.connector_request_reference_id.clone(),
profile: auth_data.profile_id,
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Charge),
card_token,
@ -346,8 +347,8 @@ impl TryFrom<&types::PaymentsSyncRouterData> for ProphetpaySyncRequest {
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
Ok(Self {
transaction_id,
ref_info: item.attempt_id.to_owned(),
inquiry_reference: format!("inquiry_{}", item.attempt_id),
ref_info: item.connector_request_reference_id.to_owned(),
inquiry_reference: item.connector_request_reference_id.clone(),
profile: auth_data.profile_id,
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Inquiry),
})
@ -355,66 +356,170 @@ impl TryFrom<&types::PaymentsSyncRouterData> for ProphetpaySyncRequest {
}
#[derive(Debug, Clone, Deserialize)]
pub enum ProphetpayPaymentStatus {
Success,
#[serde(rename = "Transaction Approved")]
Charged,
Failure,
#[serde(rename = "Transaction Voided")]
Voided,
#[serde(rename = "Requires a card on file.")]
CardTokenNotFound,
#[serde(rename = "RefInfo and InquiryReference are duplicated")]
DuplicateValue,
#[serde(rename = "Profile is missing")]
MissingProfile,
#[serde(rename = "RefInfo is empty.")]
EmptyRef,
#[serde(rename_all = "camelCase")]
pub struct ProphetpayCompleteAuthResponse {
pub success: bool,
pub response_text: String,
#[serde(rename = "transactionID")]
pub transaction_id: String,
pub response_code: String,
}
impl From<ProphetpayPaymentStatus> for enums::AttemptStatus {
fn from(item: ProphetpayPaymentStatus) -> Self {
match item {
ProphetpayPaymentStatus::Success | ProphetpayPaymentStatus::Charged => Self::Charged,
ProphetpayPaymentStatus::Failure
| ProphetpayPaymentStatus::CardTokenNotFound
| ProphetpayPaymentStatus::DuplicateValue
| ProphetpayPaymentStatus::MissingProfile
| ProphetpayPaymentStatus::EmptyRef => Self::Failure,
ProphetpayPaymentStatus::Voided => Self::Voided,
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProphetpayCardTokenData {
card_token: Secret<String>,
}
impl<F>
TryFrom<
types::ResponseRouterData<
F,
ProphetpayCompleteAuthResponse,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
>,
> for types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
ProphetpayCompleteAuthResponse,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
if item.response.success {
let card_token = get_card_token(item.data.request.redirect_response.clone())?;
let card_token_data = ProphetpayCardTokenData {
card_token: Secret::from(card_token),
};
let connector_metadata = serde_json::to_value(card_token_data).ok();
Ok(Self {
status: enums::AttemptStatus::Charged,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata,
network_txn_id: None,
connector_response_reference_id: None,
}),
..item.data
})
} else {
Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(types::ErrorResponse {
code: item.response.response_code,
message: item.response.response_text.clone(),
reason: Some(item.response.response_text),
status_code: item.http_code,
attempt_status: None,
}),
..item.data
})
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayResponse {
pub response_text: ProphetpayPaymentStatus,
pub struct ProphetpaySyncResponse {
success: bool,
pub response_text: String,
#[serde(rename = "transactionID")]
pub transaction_id: String,
pub response_code: String,
}
impl<F, T> TryFrom<types::ResponseRouterData<F, ProphetpayResponse, T, types::PaymentsResponseData>>
impl<F, T>
TryFrom<types::ResponseRouterData<F, ProphetpaySyncResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, ProphetpayResponse, T, types::PaymentsResponseData>,
item: types::ResponseRouterData<F, ProphetpaySyncResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
Ok(Self {
status: enums::AttemptStatus::from(item.response.response_text),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: None,
}),
..item.data
})
if item.response.success {
Ok(Self {
status: enums::AttemptStatus::Charged,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: None,
}),
..item.data
})
} else {
Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(types::ErrorResponse {
code: item.response.response_code,
message: item.response.response_text.clone(),
reason: Some(item.response.response_text),
status_code: item.http_code,
attempt_status: None,
}),
..item.data
})
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayVoidResponse {
pub success: bool,
pub response_text: String,
#[serde(rename = "transactionID")]
pub transaction_id: String,
pub response_code: String,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, ProphetpayVoidResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, ProphetpayVoidResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
if item.response.success {
Ok(Self {
status: enums::AttemptStatus::Voided,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference: None,
connector_metadata: None,
network_txn_id: None,
connector_response_reference_id: None,
}),
..item.data
})
} else {
Ok(Self {
status: enums::AttemptStatus::VoidFailed,
response: Err(types::ErrorResponse {
code: item.response.response_code,
message: item.response.response_text.clone(),
reason: Some(item.response.response_text),
status_code: item.http_code,
attempt_status: None,
}),
..item.data
})
}
}
}
@ -435,8 +540,8 @@ impl TryFrom<&types::PaymentsCancelRouterData> for ProphetpayVoidRequest {
let transaction_id = item.request.connector_transaction_id.to_owned();
Ok(Self {
transaction_id,
ref_info: item.attempt_id.to_owned(),
inquiry_reference: format!("inquiry_{}", item.attempt_id),
ref_info: item.connector_request_reference_id.to_owned(),
inquiry_reference: item.connector_request_reference_id.clone(),
profile: auth_data.profile_id,
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Inquiry),
})
@ -447,6 +552,7 @@ impl TryFrom<&types::PaymentsCancelRouterData> for ProphetpayVoidRequest {
#[serde(rename_all = "camelCase")]
pub struct ProphetpayRefundRequest {
pub amount: f64,
pub card_token: Secret<String>,
pub transaction_id: String,
pub profile: Secret<String>,
pub ref_info: String,
@ -459,47 +565,26 @@ impl<F> TryFrom<&ProphetpayRouterData<&types::RefundsRouterData<F>>> for Prophet
fn try_from(
item: &ProphetpayRouterData<&types::RefundsRouterData<F>>,
) -> Result<Self, Self::Error> {
let auth_data = ProphetpayAuthType::try_from(&item.router_data.connector_auth_type)?;
let transaction_id = item.router_data.request.connector_transaction_id.to_owned();
Ok(Self {
transaction_id,
amount: item.amount.to_owned(),
profile: auth_data.profile_id,
ref_info: item.router_data.request.refund_id.to_owned(),
inquiry_reference: format!("inquiry_{}", item.router_data.request.refund_id),
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Refund),
})
}
}
if item.router_data.request.payment_amount == item.router_data.request.refund_amount {
let auth_data = ProphetpayAuthType::try_from(&item.router_data.connector_auth_type)?;
let transaction_id = item.router_data.request.connector_transaction_id.to_owned();
let card_token_data: ProphetpayCardTokenData =
to_connector_meta(item.router_data.request.connector_metadata.clone())?;
#[allow(dead_code)]
#[derive(Debug, Deserialize, Clone)]
pub enum RefundStatus {
Success,
Failure,
#[serde(rename = "Transaction Voided")]
Voided,
#[serde(rename = "Requires a card on file.")]
CardTokenNotFound,
#[serde(rename = "RefInfo and InquiryReference are duplicated")]
DuplicateValue,
#[serde(rename = "Profile is missing")]
MissingProfile,
#[serde(rename = "RefInfo is empty.")]
EmptyRef,
}
impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self {
match item {
RefundStatus::Success
// in retrieving refund, if it is successful, it is shown as voided
| RefundStatus::Voided => Self::Success,
RefundStatus::Failure
| RefundStatus::CardTokenNotFound
| RefundStatus::DuplicateValue
| RefundStatus::MissingProfile
| RefundStatus::EmptyRef => Self::Failure,
Ok(Self {
transaction_id,
amount: item.amount.to_owned(),
card_token: card_token_data.card_token,
profile: auth_data.profile_id,
ref_info: item.router_data.connector_request_reference_id.to_owned(),
inquiry_reference: item.router_data.connector_request_reference_id.clone(),
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Refund),
})
} else {
Err(errors::ConnectorError::NotImplemented(
"Partial Refund is Not Supported".to_string(),
)
.into())
}
}
}
@ -507,7 +592,10 @@ impl From<RefundStatus> for enums::RefundStatus {
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayRefundResponse {
pub response_text: RefundStatus,
pub success: bool,
pub response_text: String,
pub tran_seq_number: String,
pub response_code: String,
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, ProphetpayRefundResponse>>
@ -517,20 +605,75 @@ impl TryFrom<types::RefundsResponseRouterData<api::Execute, ProphetpayRefundResp
fn try_from(
item: types::RefundsResponseRouterData<api::Execute, ProphetpayRefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
// no refund id is generated, rather transaction id is used for referring to status in refund also
connector_refund_id: item.data.request.connector_transaction_id.clone(),
refund_status: enums::RefundStatus::from(item.response.response_text),
}),
..item.data
})
if item.response.success {
Ok(Self {
response: Ok(types::RefundsResponseData {
// no refund id is generated, tranSeqNumber is kept for future usage
connector_refund_id: item.response.tran_seq_number,
refund_status: enums::RefundStatus::Success,
}),
..item.data
})
} else {
Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(types::ErrorResponse {
code: item.response.response_code,
message: item.response.response_text.clone(),
reason: Some(item.response.response_text),
status_code: item.http_code,
attempt_status: None,
}),
..item.data
})
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayRefundSyncResponse {
pub success: bool,
pub response_text: String,
pub response_code: String,
}
impl<T> TryFrom<types::RefundsResponseRouterData<T, ProphetpayRefundSyncResponse>>
for types::RefundsRouterData<T>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::RefundsResponseRouterData<T, ProphetpayRefundSyncResponse>,
) -> Result<Self, Self::Error> {
if item.response.success {
Ok(Self {
response: Ok(types::RefundsResponseData {
// no refund id is generated, rather transaction id is used for referring to status in refund also
connector_refund_id: item.data.request.connector_transaction_id.clone(),
refund_status: enums::RefundStatus::Success,
}),
..item.data
})
} else {
Ok(Self {
status: enums::AttemptStatus::Failure,
response: Err(types::ErrorResponse {
code: item.response.response_code,
message: item.response.response_text.clone(),
reason: Some(item.response.response_text),
status_code: item.http_code,
attempt_status: None,
}),
..item.data
})
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayRefundSyncRequest {
transaction_id: String,
inquiry_reference: String,
ref_info: String,
profile: Secret<String>,
action_type: i8,
@ -541,36 +684,11 @@ impl TryFrom<&types::RefundSyncRouterData> for ProphetpayRefundSyncRequest {
fn try_from(item: &types::RefundSyncRouterData) -> Result<Self, Self::Error> {
let auth_data = ProphetpayAuthType::try_from(&item.connector_auth_type)?;
Ok(Self {
ref_info: item.attempt_id.to_owned(),
transaction_id: item.request.connector_transaction_id.clone(),
ref_info: item.connector_request_reference_id.to_owned(),
inquiry_reference: item.connector_request_reference_id.clone(),
profile: auth_data.profile_id,
action_type: ProphetpayActionType::get_action_type(&ProphetpayActionType::Inquiry),
})
}
}
impl TryFrom<types::RefundsResponseRouterData<api::RSync, ProphetpayRefundResponse>>
for types::RefundsRouterData<api::RSync>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::RefundsResponseRouterData<api::RSync, ProphetpayRefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.data.request.connector_transaction_id.clone(),
refund_status: enums::RefundStatus::from(item.response.response_text),
}),
..item.data
})
}
}
// Error Response body is yet to be confirmed with the connector
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProphetpayErrorResponse {
pub status: u16,
pub title: String,
pub trace_id: String,
pub errors: serde_json::Value,
}