feat(connector) : add authorize,capture,void,refunds and mandates for payeezy (#800)

Signed-off-by: chikke srujan <121822803+srujanchikke@users.noreply.github.com>
This commit is contained in:
chikke srujan
2023-04-13 16:06:27 +05:30
committed by GitHub
parent 0d047e08f9
commit 56952f281c
22 changed files with 1585 additions and 14 deletions

View File

@ -0,0 +1,530 @@
mod transformers;
use std::fmt::Debug;
use base64::Engine;
use error_stack::{IntoReport, ResultExt};
use rand::distributions::DistString;
use ring::hmac;
use transformers as payeezy;
use crate::{
configs::settings,
consts,
core::errors::{self, CustomResult},
headers,
services::{self, ConnectorIntegration},
types::{
self,
api::{self, ConnectorCommon, ConnectorCommonExt},
ErrorResponse, Response,
},
utils::{self, BytesExt},
};
#[derive(Debug, Clone)]
pub struct Payeezy;
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Payeezy
where
Self: ConnectorIntegration<Flow, Request, Response>,
{
fn build_headers(
&self,
req: &types::RouterData<Flow, Request, Response>,
_connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
let auth = payeezy::PayeezyAuthType::try_from(&req.connector_auth_type)?;
let option_request_payload = self.get_request_body(req)?;
let request_payload = option_request_payload.map_or("{}".to_string(), |s| s);
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()
.ok_or(errors::ConnectorError::RequestEncodingFailed)?
.as_millis()
.to_string();
let nonce = rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 19);
let signature_string = format!(
"{}{}{}{}{}",
auth.api_key, nonce, timestamp, auth.merchant_token, request_payload
);
let key = hmac::Key::new(hmac::HMAC_SHA256, auth.api_secret.as_bytes());
let tag = hmac::sign(&key, signature_string.as_bytes());
let hmac_sign = hex::encode(tag);
let signature_value = consts::BASE64_ENGINE_URL_SAFE.encode(hmac_sign);
Ok(vec![
(
headers::CONTENT_TYPE.to_string(),
Self.get_content_type().to_string(),
),
(headers::APIKEY.to_string(), auth.api_key),
(headers::TOKEN.to_string(), auth.merchant_token),
(headers::AUTHORIZATION.to_string(), signature_value),
(headers::NONCE.to_string(), nonce),
(headers::TIMESTAMP.to_string(), timestamp),
])
}
}
impl ConnectorCommon for Payeezy {
fn id(&self) -> &'static str {
"payeezy"
}
fn common_get_content_type(&self) -> &'static str {
"application/json"
}
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
connectors.payeezy.base_url.as_ref()
}
fn build_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
let response: payeezy::PayeezyErrorResponse = res
.response
.parse_struct("payeezy ErrorResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
let error_messages: Vec<String> = response
.error
.messages
.iter()
.map(|m| m.description.clone())
.collect();
Ok(ErrorResponse {
status_code: res.status_code,
code: response.transaction_status,
message: error_messages.join(", "),
reason: None,
})
}
}
impl api::Payment for Payeezy {}
impl api::PreVerify for Payeezy {}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Payeezy
{
}
impl api::PaymentToken for Payeezy {}
impl
ConnectorIntegration<
api::PaymentMethodToken,
types::PaymentMethodTokenizationData,
types::PaymentsResponseData,
> for Payeezy
{
// Not Implemented (R)
}
impl api::PaymentVoid for Payeezy {}
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
for Payeezy
{
fn get_headers(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, 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: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!(
"{}v1/transactions/{}",
self.base_url(connectors),
connector_payment_id
))
}
fn get_request_body(
&self,
req: &types::PaymentsCancelRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?;
let payeezy_req =
utils::Encode::<payeezy::PayeezyCaptureOrVoidRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(payeezy_req))
}
fn build_request(
&self,
req: &types::PaymentsCancelRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
.body(types::PaymentsVoidType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsCancelRouterData,
res: Response,
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError> {
let response: payeezy::PayeezyPaymentsResponse = res
.response
.parse_struct("Payeezy PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::ConnectorAccessToken for Payeezy {}
impl ConnectorIntegration<api::AccessTokenAuth, types::AccessTokenRequestData, types::AccessToken>
for Payeezy
{
}
impl api::PaymentSync for Payeezy {}
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
for Payeezy
{
fn build_request(
&self,
_req: &types::PaymentsSyncRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Err(errors::ConnectorError::FlowNotSupported {
flow: "Psync".to_owned(),
connector: "payeezy".to_owned(),
}
.into())
}
}
impl api::PaymentCapture for Payeezy {}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Payeezy
{
fn get_headers(
&self,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, 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: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!(
"{}v1/transactions/{}",
self.base_url(connectors),
connector_payment_id
))
}
fn get_request_body(
&self,
req: &types::PaymentsCaptureRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?;
let payeezy_req =
utils::Encode::<payeezy::PayeezyCaptureOrVoidRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(payeezy_req))
}
fn build_request(
&self,
req: &types::PaymentsCaptureRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
.headers(types::PaymentsCaptureType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsCaptureType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsCaptureRouterData,
res: Response,
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
let response: payeezy::PayeezyPaymentsResponse = res
.response
.parse_struct("Payeezy PaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::PaymentSession for Payeezy {}
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
for Payeezy
{
//TODO: implement sessions flow
}
impl api::PaymentAuthorize for Payeezy {}
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
for Payeezy
{
fn get_headers(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, 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: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
Ok(format!("{}v1/transactions", self.base_url(connectors)))
}
fn get_request_body(
&self,
req: &types::PaymentsAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = payeezy::PayeezyPaymentsRequest::try_from(req)?;
let payeezy_req =
utils::Encode::<payeezy::PayeezyPaymentsRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(payeezy_req))
}
fn build_request(
&self,
req: &types::PaymentsAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsAuthorizeType::get_url(
self, req, connectors,
)?)
.headers(types::PaymentsAuthorizeType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
let response: payeezy::PayeezyPaymentsResponse = res
.response
.parse_struct("payeezy Response")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::Refund for Payeezy {}
impl api::RefundExecute for Payeezy {}
impl api::RefundSync for Payeezy {}
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
for Payeezy
{
fn get_headers(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, 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: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req.request.connector_transaction_id.clone();
Ok(format!(
"{}v1/transactions/{}",
self.base_url(connectors),
connector_payment_id
))
}
fn get_request_body(
&self,
req: &types::RefundsRouterData<api::Execute>,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let connector_req = payeezy::PayeezyRefundRequest::try_from(req)?;
let payeezy_req =
utils::Encode::<payeezy::PayeezyCaptureOrVoidRequest>::encode_to_string_of_json(
&connector_req,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(payeezy_req))
}
fn build_request(
&self,
req: &types::RefundsRouterData<api::Execute>,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
let request = services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
.headers(types::RefundExecuteType::get_headers(
self, req, connectors,
)?)
.body(types::RefundExecuteType::get_request_body(self, req)?)
.build();
Ok(Some(request))
}
fn handle_response(
&self,
data: &types::RefundsRouterData<api::Execute>,
res: Response,
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
let response: payeezy::RefundResponse = res
.response
.parse_struct("payeezy RefundResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RefundsRouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Payeezy {
fn build_request(
&self,
_req: &types::RefundSyncRouterData,
_connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Err(errors::ConnectorError::FlowNotSupported {
flow: "Rsync".to_owned(),
connector: "payeezy".to_owned(),
}
.into())
}
}
#[async_trait::async_trait]
impl api::IncomingWebhook for Payeezy {
fn get_webhook_object_reference_id(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_event_type(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
fn get_webhook_resource_object(
&self,
_request: &api::IncomingWebhookRequestDetails<'_>,
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
}
}

View File

@ -0,0 +1,486 @@
use common_utils::ext_traits::Encode;
use error_stack::ResultExt;
use masking::Secret;
use serde::{Deserialize, Serialize};
use crate::{
connector::utils::{self, CardData},
core::errors,
pii::{self},
types::{self, api, storage::enums, transformers::ForeignFrom},
};
#[derive(Serialize, Debug)]
pub struct PayeezyCard {
#[serde(rename = "type")]
pub card_type: PayeezyCardType,
pub cardholder_name: Secret<String>,
pub card_number: Secret<String, pii::CardNumber>,
pub exp_date: Secret<String>,
pub cvv: Secret<String>,
}
#[derive(Serialize, Debug)]
pub enum PayeezyCardType {
#[serde(rename = "American Express")]
AmericanExpress,
Visa,
Mastercard,
Discover,
}
impl TryFrom<utils::CardIssuer> for PayeezyCardType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(issuer: utils::CardIssuer) -> Result<Self, Self::Error> {
match issuer {
utils::CardIssuer::AmericanExpress => Ok(Self::AmericanExpress),
utils::CardIssuer::Master => Ok(Self::Mastercard),
utils::CardIssuer::Discover => Ok(Self::Discover),
utils::CardIssuer::Visa => Ok(Self::Visa),
_ => Err(errors::ConnectorError::NotSupported {
payment_method: api::enums::PaymentMethod::Card.to_string(),
connector: "Payeezy",
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
}
.into()),
}
}
}
#[derive(Serialize, Debug)]
#[serde(untagged)]
pub enum PayeezyPaymentMethod {
PayeezyCard(PayeezyCard),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PayeezyPaymentMethodType {
CreditCard,
}
#[derive(Serialize, Debug)]
pub struct PayeezyPaymentsRequest {
pub merchant_ref: String,
pub transaction_type: PayeezyTransactionType,
pub method: PayeezyPaymentMethodType,
pub amount: i64,
pub currency_code: String,
pub credit_card: PayeezyPaymentMethod,
pub stored_credentials: Option<StoredCredentials>,
}
#[derive(Serialize, Debug)]
pub struct StoredCredentials {
pub sequence: Sequence,
pub initiator: Initiator,
pub is_scheduled: bool,
pub cardbrand_original_transaction_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Sequence {
First,
Subsequent,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum Initiator {
Merchant,
CardHolder,
}
impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayeezyPaymentsRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
match item.payment_method {
storage_models::enums::PaymentMethod::Card => get_card_specific_payment_data(item),
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
}
}
}
fn get_card_specific_payment_data(
item: &types::PaymentsAuthorizeRouterData,
) -> Result<PayeezyPaymentsRequest, error_stack::Report<errors::ConnectorError>> {
let merchant_ref = item.attempt_id.to_string();
let method = PayeezyPaymentMethodType::CreditCard;
let amount = item.request.amount;
let currency_code = item.request.currency.to_string();
let credit_card = get_payment_method_data(item)?;
let (transaction_type, stored_credentials) = get_transaction_type_and_stored_creds(item)?;
Ok(PayeezyPaymentsRequest {
merchant_ref,
transaction_type,
method,
amount,
currency_code,
credit_card,
stored_credentials,
})
}
fn get_transaction_type_and_stored_creds(
item: &types::PaymentsAuthorizeRouterData,
) -> Result<
(PayeezyTransactionType, Option<StoredCredentials>),
error_stack::Report<errors::ConnectorError>,
> {
let connector_mandate_id = item
.request
.mandate_id
.as_ref()
.and_then(|mandate_ids| mandate_ids.connector_mandate_id.clone());
let (transaction_type, stored_credentials) =
if is_mandate_payment(item, connector_mandate_id.as_ref()) {
// Mandate payment
(
PayeezyTransactionType::Recurring,
Some(StoredCredentials {
// connector_mandate_id is not present then it is a First payment, else it is a Subsequent mandate payment
sequence: match connector_mandate_id.is_some() {
true => Sequence::Subsequent,
false => Sequence::First,
},
// off_session true denotes the customer not present during the checkout process. In other cases customer present at the checkout.
initiator: match item.request.off_session {
Some(true) => Initiator::Merchant,
_ => Initiator::CardHolder,
},
is_scheduled: true,
// In case of first mandate payment connector_mandate_id would be None, otherwise holds some value
cardbrand_original_transaction_id: connector_mandate_id,
}),
)
} else {
match item.request.capture_method {
Some(storage_models::enums::CaptureMethod::Manual) => {
Ok((PayeezyTransactionType::Authorize, None))
}
Some(storage_models::enums::CaptureMethod::Automatic) => {
Ok((PayeezyTransactionType::Purchase, None))
}
_ => Err(errors::ConnectorError::FlowNotSupported {
flow: item.request.capture_method.unwrap_or_default().to_string(),
connector: "Payeezy".to_string(),
}),
}?
};
Ok((transaction_type, stored_credentials))
}
fn is_mandate_payment(
item: &types::PaymentsAuthorizeRouterData,
connector_mandate_id: Option<&String>,
) -> bool {
item.request.setup_mandate_details.is_some() || connector_mandate_id.is_some()
}
fn get_payment_method_data(
item: &types::PaymentsAuthorizeRouterData,
) -> Result<PayeezyPaymentMethod, error_stack::Report<errors::ConnectorError>> {
match item.request.payment_method_data {
api::PaymentMethodData::Card(ref card) => {
let card_type = PayeezyCardType::try_from(card.get_card_issuer()?)?;
let payeezy_card = PayeezyCard {
card_type,
cardholder_name: card.card_holder_name.clone(),
card_number: card.card_number.clone(),
exp_date: card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()),
cvv: card.card_cvc.clone(),
};
Ok(PayeezyPaymentMethod::PayeezyCard(payeezy_card))
}
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
}
}
// Auth Struct
pub struct PayeezyAuthType {
pub(super) api_key: String,
pub(super) api_secret: String,
pub(super) merchant_token: String,
}
impl TryFrom<&types::ConnectorAuthType> for PayeezyAuthType {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
if let types::ConnectorAuthType::SignatureKey {
api_key,
key1,
api_secret,
} = item
{
Ok(Self {
api_key: api_key.to_string(),
api_secret: api_secret.to_string(),
merchant_token: key1.to_string(),
})
} else {
Err(errors::ConnectorError::FailedToObtainAuthType.into())
}
}
}
// PaymentsResponse
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PayeezyPaymentStatus {
Approved,
Declined,
#[default]
#[serde(rename = "Not Processed")]
NotProcessed,
}
#[derive(Deserialize)]
pub struct PayeezyPaymentsResponse {
pub correlation_id: String,
pub transaction_status: PayeezyPaymentStatus,
pub validation_status: String,
pub transaction_type: PayeezyTransactionType,
pub transaction_id: String,
pub transaction_tag: Option<String>,
pub method: Option<String>,
pub amount: String,
pub currency: String,
pub bank_resp_code: String,
pub bank_message: String,
pub gateway_resp_code: String,
pub gateway_message: String,
pub stored_credentials: Option<PaymentsStoredCredentials>,
}
#[derive(Debug, Deserialize)]
pub struct PaymentsStoredCredentials {
cardbrand_original_transaction_id: String,
}
#[derive(Debug, Serialize)]
pub struct PayeezyCaptureOrVoidRequest {
transaction_tag: String,
transaction_type: PayeezyTransactionType,
amount: String,
currency_code: String,
}
impl TryFrom<&types::PaymentsCaptureRouterData> for PayeezyCaptureOrVoidRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCaptureRouterData) -> Result<Self, Self::Error> {
let metadata: PayeezyPaymentsMetadata =
utils::to_connector_meta(item.request.connector_meta.clone())
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Self {
transaction_type: PayeezyTransactionType::Capture,
amount: item.request.amount_to_capture.to_string(),
currency_code: item.request.currency.to_string(),
transaction_tag: metadata.transaction_tag,
})
}
}
impl TryFrom<&types::PaymentsCancelRouterData> for PayeezyCaptureOrVoidRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCancelRouterData) -> Result<Self, Self::Error> {
let metadata: PayeezyPaymentsMetadata =
utils::to_connector_meta(item.request.connector_meta.clone())
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Self {
transaction_type: PayeezyTransactionType::Void,
amount: item
.request
.amount
.ok_or(errors::ConnectorError::RequestEncodingFailed)?
.to_string(),
currency_code: item.request.currency.unwrap_or_default().to_string(),
transaction_tag: metadata.transaction_tag,
})
}
}
#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum PayeezyTransactionType {
Authorize,
Capture,
Purchase,
Recurring,
Void,
Refund,
#[default]
Pending,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PayeezyPaymentsMetadata {
transaction_tag: String,
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, PayeezyPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<F, PayeezyPaymentsResponse, T, types::PaymentsResponseData>,
) -> Result<Self, Self::Error> {
let metadata = item
.response
.transaction_tag
.map(|txn_tag| {
Encode::<'_, PayeezyPaymentsMetadata>::encode_to_value(
&construct_payeezy_payments_metadata(txn_tag),
)
})
.transpose()
.change_context(errors::ConnectorError::ResponseHandlingFailed)?;
let mandate_reference = item
.response
.stored_credentials
.map(|credentials| credentials.cardbrand_original_transaction_id);
let status = enums::AttemptStatus::foreign_from((
item.response.transaction_status,
item.response.transaction_type,
));
Ok(Self {
status,
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(
item.response.transaction_id,
),
redirection_data: None,
mandate_reference,
connector_metadata: metadata,
}),
..item.data
})
}
}
impl ForeignFrom<(PayeezyPaymentStatus, PayeezyTransactionType)> for enums::AttemptStatus {
fn foreign_from((status, method): (PayeezyPaymentStatus, PayeezyTransactionType)) -> Self {
match status {
PayeezyPaymentStatus::Approved => match method {
PayeezyTransactionType::Authorize => Self::Authorized,
PayeezyTransactionType::Capture
| PayeezyTransactionType::Purchase
| PayeezyTransactionType::Recurring => Self::Charged,
PayeezyTransactionType::Void => Self::Voided,
_ => Self::Pending,
},
PayeezyPaymentStatus::Declined | PayeezyPaymentStatus::NotProcessed => match method {
PayeezyTransactionType::Capture => Self::CaptureFailed,
PayeezyTransactionType::Authorize
| PayeezyTransactionType::Purchase
| PayeezyTransactionType::Recurring => Self::AuthorizationFailed,
PayeezyTransactionType::Void => Self::VoidFailed,
_ => Self::Pending,
},
}
}
}
// REFUND :
// Type definition for RefundRequest
#[derive(Debug, Serialize)]
pub struct PayeezyRefundRequest {
transaction_tag: String,
transaction_type: PayeezyTransactionType,
amount: String,
currency_code: String,
}
impl<F> TryFrom<&types::RefundsRouterData<F>> for PayeezyRefundRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
let metadata: PayeezyPaymentsMetadata =
utils::to_connector_meta(item.request.connector_metadata.clone())
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Self {
transaction_type: PayeezyTransactionType::Refund,
amount: item.request.refund_amount.to_string(),
currency_code: item.request.currency.to_string(),
transaction_tag: metadata.transaction_tag,
})
}
}
// Type definition for Refund Response
#[derive(Debug, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum RefundStatus {
Approved,
Declined,
#[default]
#[serde(rename = "Not Processed")]
NotProcessed,
}
impl From<RefundStatus> for enums::RefundStatus {
fn from(item: RefundStatus) -> Self {
match item {
RefundStatus::Approved => Self::Success,
RefundStatus::Declined => Self::Failure,
RefundStatus::NotProcessed => Self::Pending,
}
}
}
#[derive(Deserialize)]
pub struct RefundResponse {
pub correlation_id: String,
pub transaction_status: RefundStatus,
pub validation_status: String,
pub transaction_type: String,
pub transaction_id: String,
pub transaction_tag: Option<String>,
pub method: Option<String>,
pub amount: String,
pub currency: String,
pub bank_resp_code: String,
pub bank_message: String,
pub gateway_resp_code: String,
pub gateway_message: String,
}
impl TryFrom<types::RefundsResponseRouterData<api::Execute, RefundResponse>>
for types::RefundsRouterData<api::Execute>
{
type Error = error_stack::Report<errors::ParsingError>;
fn try_from(
item: types::RefundsResponseRouterData<api::Execute, RefundResponse>,
) -> Result<Self, Self::Error> {
Ok(Self {
response: Ok(types::RefundsResponseData {
connector_refund_id: item.response.transaction_id,
refund_status: enums::RefundStatus::from(item.response.transaction_status),
}),
..item.data
})
}
}
#[derive(Debug, Deserialize)]
pub struct Message {
pub code: String,
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct PayeezyError {
pub messages: Vec<Message>,
}
#[derive(Debug, Deserialize)]
pub struct PayeezyErrorResponse {
pub transaction_status: String,
#[serde(rename = "Error")]
pub error: PayeezyError,
}
fn construct_payeezy_payments_metadata(transaction_tag: String) -> PayeezyPaymentsMetadata {
PayeezyPaymentsMetadata { transaction_tag }
}