mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 12:15:40 +08:00
feat(connector): [PAYSTACK] Electronic Fund Transfer(EFT) Payment Flows (#7440)
Co-authored-by: Anurag Singh <anurag.singh.001@Anurag-Singh-WPMHJ5619X.local> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -2846,6 +2846,14 @@ client_id="Client ID"
|
||||
api_key="Client Secret"
|
||||
key1="Client ID"
|
||||
|
||||
[paystack]
|
||||
[[paystack.bank_redirect]]
|
||||
payment_method_type = "eft"
|
||||
[paystack.connector_auth.HeaderKey]
|
||||
api_key="API Key"
|
||||
[paystack.connector_webhook_details]
|
||||
merchant_secret="API Key"
|
||||
|
||||
[payu]
|
||||
[[payu.credit]]
|
||||
payment_method_type = "Mastercard"
|
||||
|
||||
@ -2123,6 +2123,14 @@ merchant_secret="Source verification key"
|
||||
[paypal.metadata.paypal_sdk]
|
||||
client_id="Client ID"
|
||||
|
||||
[paystack]
|
||||
[[paystack.bank_redirect]]
|
||||
payment_method_type = "eft"
|
||||
[paystack.connector_auth.HeaderKey]
|
||||
api_key="API Key"
|
||||
[paystack.connector_webhook_details]
|
||||
merchant_secret="API Key"
|
||||
|
||||
[payu]
|
||||
[[payu.credit]]
|
||||
payment_method_type = "Mastercard"
|
||||
|
||||
@ -3955,6 +3955,14 @@ api_key="Api Key"
|
||||
[paypal_test.connector_auth.HeaderKey]
|
||||
api_key="Api Key"
|
||||
|
||||
[paystack]
|
||||
[[paystack.bank_redirect]]
|
||||
payment_method_type = "eft"
|
||||
[paystack.connector_auth.HeaderKey]
|
||||
api_key="API Key"
|
||||
[paystack.connector_webhook_details]
|
||||
merchant_secret="API Key"
|
||||
|
||||
[stripe_test]
|
||||
[[stripe_test.credit]]
|
||||
payment_method_type = "Mastercard"
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
pub mod transformers;
|
||||
|
||||
use common_utils::{
|
||||
crypto,
|
||||
errors::CustomResult,
|
||||
ext_traits::BytesExt,
|
||||
ext_traits::{ByteSliceExt, BytesExt},
|
||||
request::{Method, Request, RequestBuilder, RequestContent},
|
||||
types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector},
|
||||
types::{AmountConvertor, MinorUnit, MinorUnitForConnector},
|
||||
};
|
||||
use error_stack::{report, ResultExt};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData},
|
||||
router_flow_types::{
|
||||
@ -43,13 +44,13 @@ use crate::{constants::headers, types::ResponseRouterData, utils};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Paystack {
|
||||
amount_converter: &'static (dyn AmountConvertor<Output = StringMinorUnit> + Sync),
|
||||
amount_converter: &'static (dyn AmountConvertor<Output = MinorUnit> + Sync),
|
||||
}
|
||||
|
||||
impl Paystack {
|
||||
pub fn new() -> &'static Self {
|
||||
&Self {
|
||||
amount_converter: &StringMinorUnitForConnector,
|
||||
amount_converter: &MinorUnitForConnector,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,7 +118,7 @@ impl ConnectorCommon for Paystack {
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(
|
||||
headers::AUTHORIZATION.to_string(),
|
||||
auth.api_key.expose().into_masked(),
|
||||
format!("Bearer {}", auth.api_key.expose()).into_masked(),
|
||||
)])
|
||||
}
|
||||
|
||||
@ -133,12 +134,13 @@ impl ConnectorCommon for Paystack {
|
||||
|
||||
event_builder.map(|i| i.set_response_body(&response));
|
||||
router_env::logger::info!(connector_response=?response);
|
||||
let error_message = paystack::get_error_message(response.clone());
|
||||
|
||||
Ok(ErrorResponse {
|
||||
status_code: res.status_code,
|
||||
code: response.code,
|
||||
message: response.message,
|
||||
reason: response.reason,
|
||||
message: error_message,
|
||||
reason: Some(response.message),
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
})
|
||||
@ -176,9 +178,9 @@ impl ConnectorIntegration<Authorize, PaymentsAuthorizeData, PaymentsResponseData
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &PaymentsAuthorizeRouterData,
|
||||
_connectors: &Connectors,
|
||||
connectors: &Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
Ok(format!("{}/charge", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -262,10 +264,20 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Pay
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &PaymentsSyncRouterData,
|
||||
_connectors: &Connectors,
|
||||
req: &PaymentsSyncRouterData,
|
||||
connectors: &Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
let connector_payment_id = req
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||
Ok(format!(
|
||||
"{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"/transaction/verify/",
|
||||
connector_payment_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@ -289,7 +301,7 @@ impl ConnectorIntegration<PSync, PaymentsSyncData, PaymentsResponseData> for Pay
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: paystack::PaystackPaymentsResponse = res
|
||||
let response: paystack::PaystackPSyncResponse = res
|
||||
.response
|
||||
.parse_struct("paystack PaymentsSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
@ -406,9 +418,9 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Paystac
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &RefundsRouterData<Execute>,
|
||||
_connectors: &Connectors,
|
||||
connectors: &Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
Ok(format!("{}/refund", self.base_url(connectors)))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
@ -452,7 +464,7 @@ impl ConnectorIntegration<Execute, RefundsData, RefundsResponseData> for Paystac
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<RefundsRouterData<Execute>, errors::ConnectorError> {
|
||||
let response: paystack::RefundResponse = res
|
||||
let response: paystack::PaystackRefundsResponse = res
|
||||
.response
|
||||
.parse_struct("paystack RefundResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
@ -489,10 +501,20 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Paystack
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &RefundSyncRouterData,
|
||||
_connectors: &Connectors,
|
||||
req: &RefundSyncRouterData,
|
||||
connectors: &Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into())
|
||||
let connector_refund_id = req
|
||||
.request
|
||||
.connector_refund_id
|
||||
.clone()
|
||||
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
|
||||
Ok(format!(
|
||||
"{}{}{}",
|
||||
self.base_url(connectors),
|
||||
"/refund/",
|
||||
connector_refund_id,
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
@ -519,7 +541,7 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Paystack
|
||||
event_builder: Option<&mut ConnectorEvent>,
|
||||
res: Response,
|
||||
) -> CustomResult<RefundSyncRouterData, errors::ConnectorError> {
|
||||
let response: paystack::RefundResponse = res
|
||||
let response: paystack::PaystackRefundsResponse = res
|
||||
.response
|
||||
.parse_struct("paystack RefundSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
@ -543,25 +565,87 @@ impl ConnectorIntegration<RSync, RefundsData, RefundsResponseData> for Paystack
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl webhooks::IncomingWebhook for Paystack {
|
||||
fn get_webhook_object_reference_id(
|
||||
fn get_webhook_source_verification_algorithm(
|
||||
&self,
|
||||
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
|
||||
Ok(Box::new(crypto::HmacSha512))
|
||||
}
|
||||
|
||||
fn get_webhook_source_verification_signature(
|
||||
&self,
|
||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||
let signature = utils::get_header_key_value("x-paystack-signature", request.headers)
|
||||
.change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
|
||||
|
||||
hex::decode(signature)
|
||||
.change_context(errors::ConnectorError::WebhookVerificationSecretInvalid)
|
||||
}
|
||||
|
||||
fn get_webhook_source_verification_message(
|
||||
&self,
|
||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
_merchant_id: &common_utils::id_type::MerchantId,
|
||||
_connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets,
|
||||
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||
let message = std::str::from_utf8(request.body)
|
||||
.change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;
|
||||
Ok(message.to_string().into_bytes())
|
||||
}
|
||||
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api_models::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
||||
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
|
||||
let webhook_body = request
|
||||
.body
|
||||
.parse_struct::<paystack::PaystackWebhookData>("PaystackWebhookData")
|
||||
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||
match webhook_body.data {
|
||||
paystack::PaystackWebhookEventData::Payment(data) => {
|
||||
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
|
||||
api_models::payments::PaymentIdType::ConnectorTransactionId(data.reference),
|
||||
))
|
||||
}
|
||||
paystack::PaystackWebhookEventData::Refund(data) => {
|
||||
Ok(api_models::webhooks::ObjectReferenceId::RefundId(
|
||||
api_models::webhooks::RefundIdType::ConnectorRefundId(data.id),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api_models::webhooks::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
|
||||
let webhook_body = request
|
||||
.body
|
||||
.parse_struct::<paystack::PaystackWebhookData>("PaystackWebhookData")
|
||||
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||
Ok(api_models::webhooks::IncomingWebhookEvent::from(
|
||||
webhook_body.data,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
request: &webhooks::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::ConnectorError> {
|
||||
Err(report!(errors::ConnectorError::WebhooksNotImplemented))
|
||||
let webhook_body = request
|
||||
.body
|
||||
.parse_struct::<paystack::PaystackWebhookData>("PaystackWebhookData")
|
||||
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
|
||||
Ok(match webhook_body.data {
|
||||
paystack::PaystackWebhookEventData::Payment(payment_webhook_data) => {
|
||||
Box::new(payment_webhook_data)
|
||||
}
|
||||
paystack::PaystackWebhookEventData::Refund(refund_webhook_data) => {
|
||||
Box::new(refund_webhook_data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,30 +1,31 @@
|
||||
use common_enums::enums;
|
||||
use common_utils::types::StringMinorUnit;
|
||||
use common_enums::{enums, Currency};
|
||||
use common_utils::{pii::Email, request::Method, types::MinorUnit};
|
||||
use error_stack::ResultExt;
|
||||
use hyperswitch_domain_models::{
|
||||
payment_method_data::PaymentMethodData,
|
||||
router_data::{ConnectorAuthType, RouterData},
|
||||
payment_method_data::{BankRedirectData, PaymentMethodData},
|
||||
router_data::{ConnectorAuthType, ErrorResponse, RouterData},
|
||||
router_flow_types::refunds::{Execute, RSync},
|
||||
router_request_types::ResponseId,
|
||||
router_response_types::{PaymentsResponseData, RefundsResponseData},
|
||||
router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData},
|
||||
types::{PaymentsAuthorizeRouterData, RefundsRouterData},
|
||||
};
|
||||
use hyperswitch_interfaces::errors;
|
||||
use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
types::{RefundsResponseRouterData, ResponseRouterData},
|
||||
utils::PaymentsAuthorizeRequestData,
|
||||
};
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
pub struct PaystackRouterData<T> {
|
||||
pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc.
|
||||
pub amount: MinorUnit,
|
||||
pub router_data: T,
|
||||
}
|
||||
|
||||
impl<T> From<(StringMinorUnit, T)> for PaystackRouterData<T> {
|
||||
fn from((amount, item): (StringMinorUnit, T)) -> Self {
|
||||
impl<T> From<(MinorUnit, T)> for PaystackRouterData<T> {
|
||||
fn from((amount, item): (MinorUnit, T)) -> Self {
|
||||
//Todo : use utils to convert the amount to the type of amount that a connector accepts
|
||||
Self {
|
||||
amount,
|
||||
@ -33,20 +34,17 @@ impl<T> From<(StringMinorUnit, T)> for PaystackRouterData<T> {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Serialize, PartialEq)]
|
||||
pub struct PaystackPaymentsRequest {
|
||||
amount: StringMinorUnit,
|
||||
card: PaystackCard,
|
||||
pub struct PaystackEftProvider {
|
||||
provider: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct PaystackCard {
|
||||
number: cards::CardNumber,
|
||||
expiry_month: Secret<String>,
|
||||
expiry_year: Secret<String>,
|
||||
cvc: Secret<String>,
|
||||
complete: bool,
|
||||
#[derive(Default, Debug, Serialize, PartialEq)]
|
||||
pub struct PaystackPaymentsRequest {
|
||||
amount: MinorUnit,
|
||||
currency: Currency,
|
||||
email: Email,
|
||||
eft: PaystackEftProvider,
|
||||
}
|
||||
|
||||
impl TryFrom<&PaystackRouterData<&PaymentsAuthorizeRouterData>> for PaystackPaymentsRequest {
|
||||
@ -55,17 +53,14 @@ impl TryFrom<&PaystackRouterData<&PaymentsAuthorizeRouterData>> for PaystackPaym
|
||||
item: &PaystackRouterData<&PaymentsAuthorizeRouterData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.router_data.request.payment_method_data.clone() {
|
||||
PaymentMethodData::Card(req_card) => {
|
||||
let card = PaystackCard {
|
||||
number: req_card.card_number,
|
||||
expiry_month: req_card.card_exp_month,
|
||||
expiry_year: req_card.card_exp_year,
|
||||
cvc: req_card.card_cvc,
|
||||
complete: item.router_data.request.is_auto_capture()?,
|
||||
};
|
||||
PaymentMethodData::BankRedirect(BankRedirectData::Eft { provider }) => {
|
||||
let email = item.router_data.request.get_email()?;
|
||||
let eft = PaystackEftProvider { provider };
|
||||
Ok(Self {
|
||||
amount: item.amount.clone(),
|
||||
card,
|
||||
amount: item.amount,
|
||||
currency: item.router_data.request.currency,
|
||||
email,
|
||||
eft,
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()),
|
||||
@ -73,8 +68,6 @@ impl TryFrom<&PaystackRouterData<&PaymentsAuthorizeRouterData>> for PaystackPaym
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
// Auth Struct
|
||||
pub struct PaystackAuthType {
|
||||
pub(super) api_key: Secret<String>,
|
||||
}
|
||||
@ -90,32 +83,26 @@ impl TryFrom<&ConnectorAuthType> for PaystackAuthType {
|
||||
}
|
||||
}
|
||||
}
|
||||
// PaymentsResponse
|
||||
//TODO: Append the remaining status flags
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaystackPaymentStatus {
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
}
|
||||
|
||||
impl From<PaystackPaymentStatus> for common_enums::AttemptStatus {
|
||||
fn from(item: PaystackPaymentStatus) -> Self {
|
||||
match item {
|
||||
PaystackPaymentStatus::Succeeded => Self::Charged,
|
||||
PaystackPaymentStatus::Failed => Self::Failure,
|
||||
PaystackPaymentStatus::Processing => Self::Authorizing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackPaymentsResponse {
|
||||
status: PaystackPaymentStatus,
|
||||
id: String,
|
||||
pub struct PaystackEftRedirect {
|
||||
reference: String,
|
||||
status: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackPaymentsResponseData {
|
||||
status: bool,
|
||||
message: String,
|
||||
data: PaystackEftRedirect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PaystackPaymentsResponse {
|
||||
PaystackPaymentsData(PaystackPaymentsResponseData),
|
||||
PaystackPaymentsError(PaystackErrorResponse),
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<ResponseRouterData<F, PaystackPaymentsResponse, T, PaymentsResponseData>>
|
||||
@ -125,104 +112,354 @@ impl<F, T> TryFrom<ResponseRouterData<F, PaystackPaymentsResponse, T, PaymentsRe
|
||||
fn try_from(
|
||||
item: ResponseRouterData<F, PaystackPaymentsResponse, T, PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let (status, response) = match item.response {
|
||||
PaystackPaymentsResponse::PaystackPaymentsData(resp) => {
|
||||
let redirection_url = Url::parse(resp.data.url.as_str())
|
||||
.change_context(errors::ConnectorError::ParsingFailed)?;
|
||||
let redirection_data = RedirectForm::from((redirection_url, Method::Get));
|
||||
(
|
||||
common_enums::AttemptStatus::AuthenticationPending,
|
||||
Ok(PaymentsResponseData::TransactionResponse {
|
||||
resource_id: ResponseId::ConnectorTransactionId(
|
||||
resp.data.reference.clone(),
|
||||
),
|
||||
redirection_data: Box::new(Some(redirection_data)),
|
||||
mandate_reference: Box::new(None),
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
charges: None,
|
||||
}),
|
||||
)
|
||||
}
|
||||
PaystackPaymentsResponse::PaystackPaymentsError(err) => {
|
||||
let err_msg = get_error_message(err.clone());
|
||||
(
|
||||
common_enums::AttemptStatus::Failure,
|
||||
Err(ErrorResponse {
|
||||
code: err.code,
|
||||
message: err_msg.clone(),
|
||||
reason: Some(err_msg.clone()),
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
status_code: item.http_code,
|
||||
}),
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
status: common_enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(PaymentsResponseData::TransactionResponse {
|
||||
resource_id: ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: Box::new(None),
|
||||
mandate_reference: Box::new(None),
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
charges: None,
|
||||
}),
|
||||
status,
|
||||
response,
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
// REFUND :
|
||||
// Type definition for RefundRequest
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaystackPSyncStatus {
|
||||
Abandoned,
|
||||
Failed,
|
||||
Ongoing,
|
||||
Pending,
|
||||
Processing,
|
||||
Queued,
|
||||
Reversed,
|
||||
Success,
|
||||
}
|
||||
|
||||
impl From<PaystackPSyncStatus> for common_enums::AttemptStatus {
|
||||
fn from(item: PaystackPSyncStatus) -> Self {
|
||||
match item {
|
||||
PaystackPSyncStatus::Success => Self::Charged,
|
||||
PaystackPSyncStatus::Abandoned => Self::AuthenticationPending,
|
||||
PaystackPSyncStatus::Ongoing
|
||||
| PaystackPSyncStatus::Pending
|
||||
| PaystackPSyncStatus::Processing
|
||||
| PaystackPSyncStatus::Queued => Self::Pending,
|
||||
PaystackPSyncStatus::Failed => Self::Failure,
|
||||
PaystackPSyncStatus::Reversed => Self::Voided,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackPSyncData {
|
||||
status: PaystackPSyncStatus,
|
||||
reference: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackPSyncResponseData {
|
||||
status: bool,
|
||||
message: String,
|
||||
data: PaystackPSyncData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PaystackPSyncResponse {
|
||||
PaystackPSyncData(PaystackPSyncResponseData),
|
||||
PaystackPSyncWebhook(PaystackPaymentWebhookData),
|
||||
PaystackPSyncError(PaystackErrorResponse),
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<ResponseRouterData<F, PaystackPSyncResponse, T, PaymentsResponseData>>
|
||||
for RouterData<F, T, PaymentsResponseData>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: ResponseRouterData<F, PaystackPSyncResponse, T, PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match item.response {
|
||||
PaystackPSyncResponse::PaystackPSyncData(resp) => Ok(Self {
|
||||
status: common_enums::AttemptStatus::from(resp.data.status),
|
||||
response: Ok(PaymentsResponseData::TransactionResponse {
|
||||
resource_id: ResponseId::ConnectorTransactionId(resp.data.reference.clone()),
|
||||
redirection_data: Box::new(None),
|
||||
mandate_reference: Box::new(None),
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
charges: None,
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
PaystackPSyncResponse::PaystackPSyncWebhook(resp) => Ok(Self {
|
||||
status: common_enums::AttemptStatus::from(resp.status),
|
||||
response: Ok(PaymentsResponseData::TransactionResponse {
|
||||
resource_id: ResponseId::ConnectorTransactionId(resp.reference.clone()),
|
||||
redirection_data: Box::new(None),
|
||||
mandate_reference: Box::new(None),
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
connector_response_reference_id: None,
|
||||
incremental_authorization_allowed: None,
|
||||
charges: None,
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
PaystackPSyncResponse::PaystackPSyncError(err) => {
|
||||
let err_msg = get_error_message(err.clone());
|
||||
Ok(Self {
|
||||
response: Err(ErrorResponse {
|
||||
code: err.code,
|
||||
message: err_msg.clone(),
|
||||
reason: Some(err_msg.clone()),
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
status_code: item.http_code,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackRefundRequest {
|
||||
pub amount: StringMinorUnit,
|
||||
pub transaction: String,
|
||||
pub amount: MinorUnit,
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&PaystackRouterData<&RefundsRouterData<F>>> for PaystackRefundRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &PaystackRouterData<&RefundsRouterData<F>>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
transaction: item.router_data.request.connector_transaction_id.clone(),
|
||||
amount: item.amount.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Type definition for Refund Response
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone)]
|
||||
pub enum RefundStatus {
|
||||
Succeeded,
|
||||
#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaystackRefundStatus {
|
||||
Processed,
|
||||
Failed,
|
||||
#[default]
|
||||
Processing,
|
||||
Pending,
|
||||
}
|
||||
|
||||
impl From<RefundStatus> for enums::RefundStatus {
|
||||
fn from(item: RefundStatus) -> Self {
|
||||
impl From<PaystackRefundStatus> for enums::RefundStatus {
|
||||
fn from(item: PaystackRefundStatus) -> Self {
|
||||
match item {
|
||||
RefundStatus::Succeeded => Self::Success,
|
||||
RefundStatus::Failed => Self::Failure,
|
||||
RefundStatus::Processing => Self::Pending,
|
||||
//TODO: Review mapping
|
||||
PaystackRefundStatus::Processed => Self::Success,
|
||||
PaystackRefundStatus::Failed => Self::Failure,
|
||||
PaystackRefundStatus::Processing | PaystackRefundStatus::Pending => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RefundResponse {
|
||||
id: String,
|
||||
status: RefundStatus,
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackRefundsData {
|
||||
status: PaystackRefundStatus,
|
||||
id: i64,
|
||||
}
|
||||
|
||||
impl TryFrom<RefundsResponseRouterData<Execute, RefundResponse>> for RefundsRouterData<Execute> {
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackRefundsResponseData {
|
||||
status: bool,
|
||||
message: String,
|
||||
data: PaystackRefundsData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PaystackRefundsResponse {
|
||||
PaystackRefundsData(PaystackRefundsResponseData),
|
||||
PaystackRSyncWebhook(PaystackRefundWebhookData),
|
||||
PaystackRefundsError(PaystackErrorResponse),
|
||||
}
|
||||
|
||||
impl TryFrom<RefundsResponseRouterData<Execute, PaystackRefundsResponse>>
|
||||
for RefundsRouterData<Execute>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: RefundsResponseRouterData<Execute, RefundResponse>,
|
||||
item: RefundsResponseRouterData<Execute, PaystackRefundsResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: item.response.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(item.response.status),
|
||||
match item.response {
|
||||
PaystackRefundsResponse::PaystackRefundsData(resp) => Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: resp.data.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(resp.data.status),
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
PaystackRefundsResponse::PaystackRSyncWebhook(resp) => Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: resp.id,
|
||||
refund_status: enums::RefundStatus::from(resp.status),
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
PaystackRefundsResponse::PaystackRefundsError(err) => {
|
||||
let err_msg = get_error_message(err.clone());
|
||||
Ok(Self {
|
||||
response: Err(ErrorResponse {
|
||||
code: err.code,
|
||||
message: err_msg.clone(),
|
||||
reason: Some(err_msg.clone()),
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
status_code: item.http_code,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RefundsResponseRouterData<RSync, RefundResponse>> for RefundsRouterData<RSync> {
|
||||
impl TryFrom<RefundsResponseRouterData<RSync, PaystackRefundsResponse>>
|
||||
for RefundsRouterData<RSync>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: RefundsResponseRouterData<RSync, RefundResponse>,
|
||||
item: RefundsResponseRouterData<RSync, PaystackRefundsResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: item.response.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(item.response.status),
|
||||
match item.response {
|
||||
PaystackRefundsResponse::PaystackRefundsData(resp) => Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: resp.data.id.to_string(),
|
||||
refund_status: enums::RefundStatus::from(resp.data.status),
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
PaystackRefundsResponse::PaystackRSyncWebhook(resp) => Ok(Self {
|
||||
response: Ok(RefundsResponseData {
|
||||
connector_refund_id: resp.id,
|
||||
refund_status: enums::RefundStatus::from(resp.status),
|
||||
}),
|
||||
..item.data
|
||||
}),
|
||||
PaystackRefundsResponse::PaystackRefundsError(err) => {
|
||||
let err_msg = get_error_message(err.clone());
|
||||
Ok(Self {
|
||||
response: Err(ErrorResponse {
|
||||
code: err.code,
|
||||
message: err_msg.clone(),
|
||||
reason: Some(err_msg.clone()),
|
||||
attempt_status: None,
|
||||
connector_transaction_id: None,
|
||||
status_code: item.http_code,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Fill the struct with respective fields
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaystackErrorResponse {
|
||||
pub status_code: u16,
|
||||
pub code: String,
|
||||
pub status: bool,
|
||||
pub message: String,
|
||||
pub reason: Option<String>,
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub meta: serde_json::Value,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
pub fn get_error_message(response: PaystackErrorResponse) -> String {
|
||||
if let Some(serde_json::Value::Object(err_map)) = response.data {
|
||||
err_map.get("message").map(|msg| msg.clone().to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.unwrap_or(response.message)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct PaystackPaymentWebhookData {
|
||||
pub status: PaystackPSyncStatus,
|
||||
pub reference: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct PaystackRefundWebhookData {
|
||||
pub status: PaystackRefundStatus,
|
||||
pub id: String,
|
||||
pub transaction_reference: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum PaystackWebhookEventData {
|
||||
Payment(PaystackPaymentWebhookData),
|
||||
Refund(PaystackRefundWebhookData),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
pub struct PaystackWebhookData {
|
||||
pub event: String,
|
||||
pub data: PaystackWebhookEventData,
|
||||
}
|
||||
|
||||
impl From<PaystackWebhookEventData> for api_models::webhooks::IncomingWebhookEvent {
|
||||
fn from(item: PaystackWebhookEventData) -> Self {
|
||||
match item {
|
||||
PaystackWebhookEventData::Payment(payment_data) => match payment_data.status {
|
||||
PaystackPSyncStatus::Success => Self::PaymentIntentSuccess,
|
||||
PaystackPSyncStatus::Failed => Self::PaymentIntentFailure,
|
||||
PaystackPSyncStatus::Abandoned
|
||||
| PaystackPSyncStatus::Ongoing
|
||||
| PaystackPSyncStatus::Pending
|
||||
| PaystackPSyncStatus::Processing
|
||||
| PaystackPSyncStatus::Queued => Self::PaymentIntentProcessing,
|
||||
PaystackPSyncStatus::Reversed => Self::EventNotSupported,
|
||||
},
|
||||
PaystackWebhookEventData::Refund(refund_data) => match refund_data.status {
|
||||
PaystackRefundStatus::Processed => Self::RefundSuccess,
|
||||
PaystackRefundStatus::Failed => Self::RefundFailure,
|
||||
PaystackRefundStatus::Processing | PaystackRefundStatus::Pending => {
|
||||
Self::EventNotSupported
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user