mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(dummy_connector): Add 3DS Flow, Wallets and Pay Later for Dummy Connector (#1781)
This commit is contained in:
@ -146,6 +146,7 @@ pub struct DummyConnector {
|
||||
pub refund_tolerance: u64,
|
||||
pub refund_retrieve_duration: u64,
|
||||
pub refund_retrieve_tolerance: u64,
|
||||
pub authorize_ttl: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
||||
@ -4,7 +4,6 @@ use std::fmt::Debug;
|
||||
|
||||
use diesel_models::enums;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use transformers as dummyconnector;
|
||||
|
||||
use super::utils::RefundsRequestData;
|
||||
use crate::{
|
||||
@ -74,16 +73,7 @@ where
|
||||
|
||||
impl<const T: u8> ConnectorCommon for DummyConnector<T> {
|
||||
fn id(&self) -> &'static str {
|
||||
match T {
|
||||
1 => "phonypay",
|
||||
2 => "fauxpay",
|
||||
3 => "pretendpay",
|
||||
4 => "stripe_test",
|
||||
5 => "adyen_test",
|
||||
6 => "checkout_test",
|
||||
7 => "paypal_test",
|
||||
_ => "phonypay",
|
||||
}
|
||||
Into::<transformers::DummyConnectors>::into(T).get_dummy_connector_id()
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
@ -94,7 +84,7 @@ impl<const T: u8> ConnectorCommon for DummyConnector<T> {
|
||||
&self,
|
||||
val: &types::ConnectorAuthType,
|
||||
) -> Result<(), error_stack::Report<errors::ConnectorError>> {
|
||||
dummyconnector::DummyConnectorAuthType::try_from(val)?;
|
||||
transformers::DummyConnectorAuthType::try_from(val)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -106,7 +96,7 @@ impl<const T: u8> ConnectorCommon for DummyConnector<T> {
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, request::Maskable<String>)>, errors::ConnectorError> {
|
||||
let auth = dummyconnector::DummyConnectorAuthType::try_from(auth_type)
|
||||
let auth = transformers::DummyConnectorAuthType::try_from(auth_type)
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(
|
||||
headers::AUTHORIZATION.to_string(),
|
||||
@ -118,7 +108,7 @@ impl<const T: u8> ConnectorCommon for DummyConnector<T> {
|
||||
&self,
|
||||
res: Response,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: dummyconnector::DummyConnectorErrorResponse = res
|
||||
let response: transformers::DummyConnectorErrorResponse = res
|
||||
.response
|
||||
.parse_struct("DummyConnectorErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
@ -174,10 +164,12 @@ impl<const T: u8>
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
match req.payment_method {
|
||||
enums::PaymentMethod::Card => Ok(format!("{}/payment", self.base_url(connectors))),
|
||||
enums::PaymentMethod::Wallet => Ok(format!("{}/payment", self.base_url(connectors))),
|
||||
enums::PaymentMethod::PayLater => Ok(format!("{}/payment", self.base_url(connectors))),
|
||||
_ => Err(error_stack::report!(errors::ConnectorError::NotSupported {
|
||||
message: format!("The payment method {} is not supported", req.payment_method),
|
||||
connector: "dummyconnector",
|
||||
payment_experience: api::enums::PaymentExperience::InvokeSdkClient.to_string(),
|
||||
connector: Into::<transformers::DummyConnectors>::into(T).get_dummy_connector_id(),
|
||||
payment_experience: api::enums::PaymentExperience::RedirectToUrl.to_string(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
@ -186,12 +178,12 @@ impl<const T: u8>
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
let connector_request = dummyconnector::DummyConnectorPaymentsRequest::try_from(req)?;
|
||||
let connector_request = transformers::DummyConnectorPaymentsRequest::<T>::try_from(req)?;
|
||||
let dummmy_payments_request = types::RequestBody::log_and_get_request_body(
|
||||
&connector_request,
|
||||
utils::Encode::<dummyconnector::DummyConnectorPaymentsRequest>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
&connector_request,
|
||||
utils::Encode::<transformers::DummyConnectorPaymentsRequest::<T>>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(dummmy_payments_request))
|
||||
}
|
||||
|
||||
@ -220,10 +212,11 @@ impl<const T: u8>
|
||||
data: &types::PaymentsAuthorizeRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||
let response: dummyconnector::PaymentsResponse = res
|
||||
let response: transformers::PaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("DummyConnector PaymentsAuthorizeResponse")
|
||||
.parse_struct("DummyConnector PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
println!("handle_response: {:#?}", response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
@ -297,7 +290,7 @@ impl<const T: u8>
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: dummyconnector::PaymentsResponse = res
|
||||
let response: transformers::PaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("dummyconnector PaymentsSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
@ -370,9 +363,9 @@ impl<const T: u8>
|
||||
data: &types::PaymentsCaptureRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||
let response: dummyconnector::PaymentsResponse = res
|
||||
let response: transformers::PaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("DummyConnector PaymentsCaptureResponse")
|
||||
.parse_struct("transformers PaymentsCaptureResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -427,10 +420,10 @@ impl<const T: u8> ConnectorIntegration<api::Execute, types::RefundsData, types::
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
) -> CustomResult<Option<types::RequestBody>, errors::ConnectorError> {
|
||||
let connector_request = dummyconnector::DummyConnectorRefundRequest::try_from(req)?;
|
||||
let connector_request = transformers::DummyConnectorRefundRequest::try_from(req)?;
|
||||
let dummmy_refund_request = types::RequestBody::log_and_get_request_body(
|
||||
&connector_request,
|
||||
utils::Encode::<dummyconnector::DummyConnectorRefundRequest>::encode_to_string_of_json,
|
||||
utils::Encode::<transformers::DummyConnectorRefundRequest>::encode_to_string_of_json,
|
||||
)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(dummmy_refund_request))
|
||||
@ -458,9 +451,9 @@ impl<const T: u8> ConnectorIntegration<api::Execute, types::RefundsData, types::
|
||||
data: &types::RefundsRouterData<api::Execute>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
|
||||
let response: dummyconnector::RefundResponse = res
|
||||
let response: transformers::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("dummyconnector RefundResponse")
|
||||
.parse_struct("transformers RefundResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
@ -527,9 +520,9 @@ impl<const T: u8> ConnectorIntegration<api::RSync, types::RefundsData, types::Re
|
||||
data: &types::RefundSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||
let response: dummyconnector::RefundResponse = res
|
||||
let response: transformers::RefundResponse = res
|
||||
.response
|
||||
.parse_struct("dummyconnector RefundSyncResponse")
|
||||
.parse_struct("transformers RefundSyncResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
|
||||
@ -1,56 +1,173 @@
|
||||
use diesel_models::enums::Currency;
|
||||
use masking::Secret;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
connector::utils::PaymentsAuthorizeRequestData,
|
||||
core::errors,
|
||||
services,
|
||||
types::{self, api, storage::enums},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, strum::Display, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum DummyConnectors {
|
||||
#[serde(rename = "phonypay")]
|
||||
#[strum(serialize = "phonypay")]
|
||||
PhonyPay,
|
||||
#[serde(rename = "fauxpay")]
|
||||
#[strum(serialize = "fauxpay")]
|
||||
FauxPay,
|
||||
#[serde(rename = "pretendpay")]
|
||||
#[strum(serialize = "pretendpay")]
|
||||
PretendPay,
|
||||
StripeTest,
|
||||
AdyenTest,
|
||||
CheckoutTest,
|
||||
PaypalTest,
|
||||
}
|
||||
|
||||
impl DummyConnectors {
|
||||
pub fn get_dummy_connector_id(self) -> &'static str {
|
||||
match self {
|
||||
Self::PhonyPay => "phonypay",
|
||||
Self::FauxPay => "fauxpay",
|
||||
Self::PretendPay => "pretendpay",
|
||||
Self::StripeTest => "stripe_test",
|
||||
Self::AdyenTest => "adyen_test",
|
||||
Self::CheckoutTest => "checkout_test",
|
||||
Self::PaypalTest => "paypal_test",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for DummyConnectors {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => Self::PhonyPay,
|
||||
2 => Self::FauxPay,
|
||||
3 => Self::PretendPay,
|
||||
4 => Self::StripeTest,
|
||||
5 => Self::AdyenTest,
|
||||
6 => Self::CheckoutTest,
|
||||
7 => Self::PaypalTest,
|
||||
_ => Self::PhonyPay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
pub struct DummyConnectorPaymentsRequest {
|
||||
pub struct DummyConnectorPaymentsRequest<const T: u8> {
|
||||
amount: i64,
|
||||
currency: Currency,
|
||||
payment_method_data: PaymentMethodData,
|
||||
return_url: Option<String>,
|
||||
connector: DummyConnectors,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaymentMethodData {
|
||||
Card(DummyConnectorCard),
|
||||
Wallet(DummyConnectorWallet),
|
||||
PayLater(DummyConnectorPayLater),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct DummyConnectorCard {
|
||||
name: Secret<String>,
|
||||
number: cards::CardNumber,
|
||||
expiry_month: Secret<String>,
|
||||
expiry_year: Secret<String>,
|
||||
cvc: Secret<String>,
|
||||
complete: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for DummyConnectorPaymentsRequest {
|
||||
impl From<api_models::payments::Card> for DummyConnectorCard {
|
||||
fn from(value: api_models::payments::Card) -> Self {
|
||||
Self {
|
||||
name: value.card_holder_name,
|
||||
number: value.card_number,
|
||||
expiry_month: value.card_exp_month,
|
||||
expiry_year: value.card_exp_year,
|
||||
cvc: value.card_cvc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum DummyConnectorWallet {
|
||||
GooglePay,
|
||||
Paypal,
|
||||
WeChatPay,
|
||||
MbWay,
|
||||
AliPay,
|
||||
AliPayHK,
|
||||
}
|
||||
|
||||
impl TryFrom<api_models::payments::WalletData> for DummyConnectorWallet {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(value: api_models::payments::WalletData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
api_models::payments::WalletData::GooglePayRedirect(_) => Ok(Self::GooglePay),
|
||||
api_models::payments::WalletData::PaypalRedirect(_) => Ok(Self::Paypal),
|
||||
api_models::payments::WalletData::WeChatPay(_) => Ok(Self::WeChatPay),
|
||||
api_models::payments::WalletData::MbWayRedirect(_) => Ok(Self::MbWay),
|
||||
api_models::payments::WalletData::AliPayRedirect(_) => Ok(Self::AliPay),
|
||||
api_models::payments::WalletData::AliPayHkRedirect(_) => Ok(Self::AliPayHK),
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Dummy wallet".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum DummyConnectorPayLater {
|
||||
Klarna,
|
||||
Affirm,
|
||||
AfterPayClearPay,
|
||||
}
|
||||
|
||||
impl TryFrom<api_models::payments::PayLaterData> for DummyConnectorPayLater {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(value: api_models::payments::PayLaterData) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
api_models::payments::PayLaterData::KlarnaRedirect { .. } => Ok(Self::Klarna),
|
||||
api_models::payments::PayLaterData::AffirmRedirect {} => Ok(Self::Affirm),
|
||||
api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => {
|
||||
Ok(Self::AfterPayClearPay)
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Dummy pay later".to_string()).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const T: u8> TryFrom<&types::PaymentsAuthorizeRouterData>
|
||||
for DummyConnectorPaymentsRequest<T>
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(req_card) => {
|
||||
let card = DummyConnectorCard {
|
||||
name: req_card.card_holder_name,
|
||||
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.request.is_auto_capture()?,
|
||||
};
|
||||
Ok(Self {
|
||||
amount: item.request.amount,
|
||||
currency: item.request.currency,
|
||||
payment_method_data: PaymentMethodData::Card(card),
|
||||
})
|
||||
let payment_method_data: Result<PaymentMethodData, Self::Error> = match item
|
||||
.request
|
||||
.payment_method_data
|
||||
{
|
||||
api::PaymentMethodData::Card(ref req_card) => {
|
||||
Ok(PaymentMethodData::Card(req_card.clone().into()))
|
||||
}
|
||||
api::PaymentMethodData::Wallet(ref wallet_data) => {
|
||||
Ok(PaymentMethodData::Wallet(wallet_data.clone().try_into()?))
|
||||
}
|
||||
api::PaymentMethodData::PayLater(ref pay_later_data) => Ok(
|
||||
PaymentMethodData::PayLater(pay_later_data.clone().try_into()?),
|
||||
),
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
amount: item.request.amount,
|
||||
currency: item.request.currency,
|
||||
payment_method_data: payment_method_data?,
|
||||
return_url: item.request.router_return_url.clone(),
|
||||
connector: Into::<DummyConnectors>::into(T),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,19 +203,28 @@ impl From<DummyConnectorPaymentStatus> for enums::AttemptStatus {
|
||||
match item {
|
||||
DummyConnectorPaymentStatus::Succeeded => Self::Charged,
|
||||
DummyConnectorPaymentStatus::Failed => Self::Failure,
|
||||
DummyConnectorPaymentStatus::Processing => Self::Authorizing,
|
||||
DummyConnectorPaymentStatus::Processing => Self::AuthenticationPending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct PaymentsResponse {
|
||||
status: DummyConnectorPaymentStatus,
|
||||
id: String,
|
||||
amount: i64,
|
||||
currency: Currency,
|
||||
created: String,
|
||||
payment_method_type: String,
|
||||
payment_method_type: PaymentMethodType,
|
||||
next_action: Option<DummyConnectorNextAction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaymentMethodType {
|
||||
Card,
|
||||
Wallet(DummyConnectorWallet),
|
||||
PayLater(DummyConnectorPayLater),
|
||||
}
|
||||
|
||||
impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentsResponse, T, types::PaymentsResponseData>>
|
||||
@ -108,11 +234,18 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentsResponse, T, types::Paym
|
||||
fn try_from(
|
||||
item: types::ResponseRouterData<F, PaymentsResponse, T, types::PaymentsResponseData>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let redirection_data = item
|
||||
.response
|
||||
.next_action
|
||||
.and_then(|redirection_data| redirection_data.get_url())
|
||||
.map(|redirection_url| {
|
||||
services::RedirectForm::from((redirection_url, services::Method::Get))
|
||||
});
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.status),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),
|
||||
redirection_data: None,
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
@ -123,6 +256,20 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, PaymentsResponse, T, types::Paym
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DummyConnectorNextAction {
|
||||
RedirectToUrl(Url),
|
||||
}
|
||||
|
||||
impl DummyConnectorNextAction {
|
||||
fn get_url(&self) -> Option<Url> {
|
||||
match self {
|
||||
Self::RedirectToUrl(redirect_to_url) => Some(redirect_to_url.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REFUND :
|
||||
// Type definition for RefundRequest
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
|
||||
@ -118,12 +118,13 @@ pub struct DummyConnector;
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
impl DummyConnector {
|
||||
pub fn server(state: AppState) -> Scope {
|
||||
let mut route = web::scope("/dummy-connector").app_data(web::Data::new(state));
|
||||
let mut routes_with_restricted_access = web::scope("");
|
||||
#[cfg(not(feature = "external_access_dc"))]
|
||||
{
|
||||
route = route.guard(actix_web::guard::Host("localhost"));
|
||||
routes_with_restricted_access =
|
||||
routes_with_restricted_access.guard(actix_web::guard::Host("localhost"));
|
||||
}
|
||||
route = route
|
||||
routes_with_restricted_access = routes_with_restricted_access
|
||||
.service(web::resource("/payment").route(web::post().to(dummy_connector_payment)))
|
||||
.service(
|
||||
web::resource("/payments/{payment_id}")
|
||||
@ -136,7 +137,17 @@ impl DummyConnector {
|
||||
web::resource("/refunds/{refund_id}")
|
||||
.route(web::get().to(dummy_connector_refund_data)),
|
||||
);
|
||||
route
|
||||
web::scope("/dummy-connector")
|
||||
.app_data(web::Data::new(state))
|
||||
.service(
|
||||
web::resource("/authorize/{attempt_id}")
|
||||
.route(web::get().to(dummy_connector_authorize_payment)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/complete/{attempt_id}")
|
||||
.route(web::get().to(dummy_connector_complete_payment)),
|
||||
)
|
||||
.service(routes_with_restricted_access)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,24 +4,70 @@ use router_env::{instrument, tracing};
|
||||
use super::app;
|
||||
use crate::services::{api, authentication as auth};
|
||||
|
||||
mod consts;
|
||||
mod core;
|
||||
mod errors;
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
#[instrument(skip_all, fields(flow = ?types::Flow::DummyPaymentCreate))]
|
||||
pub async fn dummy_connector_authorize_payment(
|
||||
state: web::Data<app::AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
path: web::Path<String>,
|
||||
) -> impl actix_web::Responder {
|
||||
let flow = types::Flow::DummyPaymentAuthorize;
|
||||
let attempt_id = path.into_inner();
|
||||
let payload = types::DummyConnectorPaymentConfirmRequest { attempt_id };
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| core::payment_authorize(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(flow = ?types::Flow::DummyPaymentCreate))]
|
||||
pub async fn dummy_connector_complete_payment(
|
||||
state: web::Data<app::AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
path: web::Path<String>,
|
||||
json_payload: web::Query<types::DummyConnectorPaymentCompleteBody>,
|
||||
) -> impl actix_web::Responder {
|
||||
let flow = types::Flow::DummyPaymentComplete;
|
||||
let attempt_id = path.into_inner();
|
||||
let payload = types::DummyConnectorPaymentCompleteRequest {
|
||||
attempt_id,
|
||||
confirm: json_payload.confirm,
|
||||
};
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| core::payment_complete(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all, fields(flow = ?types::Flow::DummyPaymentCreate))]
|
||||
pub async fn dummy_connector_payment(
|
||||
state: web::Data<app::AppState>,
|
||||
req: actix_web::HttpRequest,
|
||||
json_payload: web::Json<types::DummyConnectorPaymentRequest>,
|
||||
) -> impl actix_web::Responder {
|
||||
let flow = types::Flow::DummyPaymentCreate;
|
||||
let payload = json_payload.into_inner();
|
||||
let flow = types::Flow::DummyPaymentCreate;
|
||||
api::server_wrap(
|
||||
flow,
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| utils::payment(state, req),
|
||||
|state, _, req| core::payment(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
@ -41,7 +87,7 @@ pub async fn dummy_connector_payment_data(
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| utils::payment_data(state, req),
|
||||
|state, _, req| core::payment_data(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
@ -62,7 +108,7 @@ pub async fn dummy_connector_refund(
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| utils::refund_payment(state, req),
|
||||
|state, _, req| core::refund_payment(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
@ -82,7 +128,7 @@ pub async fn dummy_connector_refund_data(
|
||||
state.get_ref(),
|
||||
&req,
|
||||
payload,
|
||||
|state, _, req| utils::refund_data(state, req),
|
||||
|state, _, req| core::refund_data(state, req),
|
||||
&auth::NoAuth,
|
||||
)
|
||||
.await
|
||||
|
||||
97
crates/router/src/routes/dummy_connector/consts.rs
Normal file
97
crates/router/src/routes/dummy_connector/consts.rs
Normal file
@ -0,0 +1,97 @@
|
||||
pub const PAYMENT_ID_PREFIX: &str = "dummy_pay";
|
||||
pub const ATTEMPT_ID_PREFIX: &str = "dummy_attempt";
|
||||
pub const REFUND_ID_PREFIX: &str = "dummy_ref";
|
||||
pub const DEFAULT_RETURN_URL: &str = "https://app.hyperswitch.io/";
|
||||
pub const THREE_DS_CSS: &str = r#"
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&display=swap');
|
||||
body {
|
||||
font-family: Inter;
|
||||
background-image: url('https://app.hyperswitch.io/images/hyperswitchImages/PostLoginBackground.svg');
|
||||
display: flex;
|
||||
background-size: cover;
|
||||
height: 100%;
|
||||
padding-top: 3rem;
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.authorize {
|
||||
color: white;
|
||||
background-color: #006DF9;
|
||||
border: 1px solid #006DF9;
|
||||
box-sizing: border-box;
|
||||
margin: 1.5rem 0.5rem 1rem 0;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.authorize:hover {
|
||||
background-color: #0099FF;
|
||||
border: 1px solid #0099FF;
|
||||
cursor: pointer;
|
||||
}
|
||||
.reject {
|
||||
background-color: #F7F7F7;
|
||||
color: black;
|
||||
border: 1px solid #E8E8E8;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0.25rem;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.reject:hover {
|
||||
cursor: pointer;
|
||||
background-color: #E8E8E8;
|
||||
border: 1px solid #E8E8E8;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
width: 33rem;
|
||||
margin: 1rem 0;
|
||||
border: 1px solid #E8E8E8;
|
||||
color: black;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem 1.4rem 1rem 1.4rem;
|
||||
font-family: Inter;
|
||||
}
|
||||
.container p {
|
||||
font-weight: 400;
|
||||
margin-top: 0.5rem 1rem 0.5rem 0.5rem;
|
||||
color: #151A1F;
|
||||
opacity: 0.5;
|
||||
}
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
.disclaimer {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500 !important;
|
||||
margin-top: 0.5rem !important;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.heading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.logo {
|
||||
width: 8rem;
|
||||
}
|
||||
.payment_details {
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
margin: 1rem 0 2rem 0;
|
||||
}
|
||||
.border {
|
||||
border-top: 1px dashed #151a1f80;
|
||||
height: 1px;
|
||||
margin: 0 1rem;
|
||||
margin-top: 1rem;
|
||||
width: 20%;
|
||||
}"#;
|
||||
205
crates/router/src/routes/dummy_connector/core.rs
Normal file
205
crates/router/src/routes/dummy_connector/core.rs
Normal file
@ -0,0 +1,205 @@
|
||||
use app::AppState;
|
||||
use common_utils::generate_id_with_default_len;
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use super::{errors, types, utils};
|
||||
use crate::{
|
||||
routes::{app, dummy_connector::consts},
|
||||
services::api,
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
pub async fn payment(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorPaymentResponse> {
|
||||
utils::tokio_mock_sleep(
|
||||
state.conf.dummy_connector.payment_duration,
|
||||
state.conf.dummy_connector.payment_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payment_attempt: types::DummyConnectorPaymentAttempt = req.into();
|
||||
let payment_data =
|
||||
types::DummyConnectorPaymentData::process_payment_attempt(state, payment_attempt)?;
|
||||
|
||||
utils::store_data_in_redis(
|
||||
state,
|
||||
payment_data.attempt_id.clone(),
|
||||
payment_data.payment_id.clone(),
|
||||
state.conf.dummy_connector.authorize_ttl,
|
||||
)
|
||||
.await?;
|
||||
utils::store_data_in_redis(
|
||||
state,
|
||||
payment_data.payment_id.clone(),
|
||||
payment_data.clone(),
|
||||
state.conf.dummy_connector.payment_ttl,
|
||||
)
|
||||
.await?;
|
||||
Ok(api::ApplicationResponse::Json(payment_data.into()))
|
||||
}
|
||||
|
||||
pub async fn payment_data(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentRetrieveRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorPaymentResponse> {
|
||||
utils::tokio_mock_sleep(
|
||||
state.conf.dummy_connector.payment_retrieve_duration,
|
||||
state.conf.dummy_connector.payment_retrieve_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payment_data = utils::get_payment_data_from_payment_id(state, req.payment_id).await?;
|
||||
Ok(api::ApplicationResponse::Json(payment_data.into()))
|
||||
}
|
||||
|
||||
pub async fn payment_authorize(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentConfirmRequest,
|
||||
) -> types::DummyConnectorResponse<String> {
|
||||
let payment_data = utils::get_payment_data_by_attempt_id(state, req.attempt_id.clone()).await;
|
||||
|
||||
if let Ok(payment_data_inner) = payment_data {
|
||||
let return_url = format!(
|
||||
"{}/dummy-connector/complete/{}",
|
||||
state.conf.server.base_url, req.attempt_id
|
||||
);
|
||||
Ok(api::ApplicationResponse::FileData((
|
||||
utils::get_authorize_page(payment_data_inner, return_url)
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
mime::TEXT_HTML,
|
||||
)))
|
||||
} else {
|
||||
Ok(api::ApplicationResponse::FileData((
|
||||
utils::get_expired_page().as_bytes().to_vec(),
|
||||
mime::TEXT_HTML,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn payment_complete(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentCompleteRequest,
|
||||
) -> types::DummyConnectorResponse<()> {
|
||||
let payment_data = utils::get_payment_data_by_attempt_id(state, req.attempt_id.clone()).await;
|
||||
|
||||
let payment_status = if req.confirm {
|
||||
types::DummyConnectorStatus::Succeeded
|
||||
} else {
|
||||
types::DummyConnectorStatus::Failed
|
||||
};
|
||||
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
let _ = redis_conn.delete_key(req.attempt_id.as_str()).await;
|
||||
|
||||
if let Ok(payment_data) = payment_data {
|
||||
let updated_payment_data = types::DummyConnectorPaymentData {
|
||||
status: payment_status,
|
||||
next_action: None,
|
||||
..payment_data
|
||||
};
|
||||
utils::store_data_in_redis(
|
||||
state,
|
||||
updated_payment_data.payment_id.clone(),
|
||||
updated_payment_data.clone(),
|
||||
state.conf.dummy_connector.payment_ttl,
|
||||
)
|
||||
.await?;
|
||||
return Ok(api::ApplicationResponse::JsonForRedirection(
|
||||
api_models::payments::RedirectionResponse {
|
||||
return_url: String::new(),
|
||||
params: vec![],
|
||||
return_url_with_query_params: updated_payment_data
|
||||
.return_url
|
||||
.unwrap_or(consts::DEFAULT_RETURN_URL.to_string()),
|
||||
http_method: "GET".to_string(),
|
||||
headers: vec![],
|
||||
},
|
||||
));
|
||||
}
|
||||
Ok(api::ApplicationResponse::JsonForRedirection(
|
||||
api_models::payments::RedirectionResponse {
|
||||
return_url: String::new(),
|
||||
params: vec![],
|
||||
return_url_with_query_params: consts::DEFAULT_RETURN_URL.to_string(),
|
||||
http_method: "GET".to_string(),
|
||||
headers: vec![],
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn refund_payment(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorRefundRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorRefundResponse> {
|
||||
utils::tokio_mock_sleep(
|
||||
state.conf.dummy_connector.refund_duration,
|
||||
state.conf.dummy_connector.refund_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payment_id = req
|
||||
.payment_id
|
||||
.get_required_value("payment_id")
|
||||
.change_context(errors::DummyConnectorErrors::MissingRequiredField {
|
||||
field_name: "payment_id",
|
||||
})?;
|
||||
|
||||
let mut payment_data =
|
||||
utils::get_payment_data_from_payment_id(state, payment_id.clone()).await?;
|
||||
|
||||
payment_data.is_eligible_for_refund(req.amount)?;
|
||||
|
||||
let refund_id = generate_id_with_default_len(consts::REFUND_ID_PREFIX);
|
||||
payment_data.eligible_amount -= req.amount;
|
||||
|
||||
utils::store_data_in_redis(
|
||||
state,
|
||||
payment_id,
|
||||
payment_data.to_owned(),
|
||||
state.conf.dummy_connector.payment_ttl,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let refund_data = types::DummyConnectorRefundResponse::new(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
refund_id.to_owned(),
|
||||
payment_data.currency,
|
||||
common_utils::date_time::now(),
|
||||
payment_data.amount,
|
||||
req.amount,
|
||||
);
|
||||
|
||||
utils::store_data_in_redis(
|
||||
state,
|
||||
refund_id,
|
||||
refund_data.to_owned(),
|
||||
state.conf.dummy_connector.refund_ttl,
|
||||
)
|
||||
.await?;
|
||||
Ok(api::ApplicationResponse::Json(refund_data))
|
||||
}
|
||||
|
||||
pub async fn refund_data(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorRefundRetrieveRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorRefundResponse> {
|
||||
let refund_id = req.refund_id;
|
||||
utils::tokio_mock_sleep(
|
||||
state.conf.dummy_connector.refund_retrieve_duration,
|
||||
state.conf.dummy_connector.refund_retrieve_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
let refund_data = redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorRefundResponse>(
|
||||
refund_id.as_str(),
|
||||
"DummyConnectorRefundResponse",
|
||||
)
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::RefundNotFound)?;
|
||||
Ok(api::ApplicationResponse::Json(refund_data))
|
||||
}
|
||||
@ -34,6 +34,9 @@ pub enum DummyConnectorErrors {
|
||||
|
||||
#[error(error_type = ErrorType::ServerNotAvailable, code = "DC_07", message = "Error occurred while storing the payment")]
|
||||
PaymentStoringError,
|
||||
|
||||
#[error(error_type = ErrorType::InvalidRequestError, code = "DC_08", message = "Payment declined: {message}")]
|
||||
PaymentDeclined { message: &'static str },
|
||||
}
|
||||
|
||||
impl core::fmt::Display for DummyConnectorErrors {
|
||||
@ -77,6 +80,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::PaymentStoringError => {
|
||||
AER::InternalServerError(ApiError::new("DC", 7, self.error_message(), None))
|
||||
}
|
||||
Self::PaymentDeclined { message: _ } => {
|
||||
AER::BadRequest(ApiError::new("DC", 8, self.error_message(), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
use api_models::enums::Currency;
|
||||
use common_utils::errors::CustomResult;
|
||||
use common_utils::{errors::CustomResult, generate_id_with_default_len};
|
||||
use error_stack::report;
|
||||
use masking::Secret;
|
||||
use router_env::types::FlowMetric;
|
||||
use strum::Display;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use super::errors::DummyConnectorErrors;
|
||||
use super::{consts, errors::DummyConnectorErrors};
|
||||
use crate::services;
|
||||
|
||||
#[derive(Debug, Display, Clone, PartialEq, Eq)]
|
||||
@ -13,13 +14,52 @@ use crate::services;
|
||||
pub enum Flow {
|
||||
DummyPaymentCreate,
|
||||
DummyPaymentRetrieve,
|
||||
DummyPaymentAuthorize,
|
||||
DummyPaymentComplete,
|
||||
DummyRefundCreate,
|
||||
DummyRefundRetrieve,
|
||||
}
|
||||
|
||||
impl FlowMetric for Flow {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum DummyConnectors {
|
||||
#[serde(rename = "phonypay")]
|
||||
#[strum(serialize = "phonypay")]
|
||||
PhonyPay,
|
||||
#[serde(rename = "fauxpay")]
|
||||
#[strum(serialize = "fauxpay")]
|
||||
FauxPay,
|
||||
#[serde(rename = "pretendpay")]
|
||||
#[strum(serialize = "pretendpay")]
|
||||
PretendPay,
|
||||
StripeTest,
|
||||
AdyenTest,
|
||||
CheckoutTest,
|
||||
PaypalTest,
|
||||
}
|
||||
|
||||
impl DummyConnectors {
|
||||
pub fn get_connector_image_link(self) -> &'static str {
|
||||
match self {
|
||||
Self::PhonyPay => "https://app.hyperswitch.io/euler-icons/Gateway/Light/PHONYPAY.svg",
|
||||
Self::FauxPay => "https://app.hyperswitch.io/euler-icons/Gateway/Light/FAUXPAY.svg",
|
||||
Self::PretendPay => {
|
||||
"https://app.hyperswitch.io/euler-icons/Gateway/Light/PRETENDPAY.svg"
|
||||
}
|
||||
Self::StripeTest => {
|
||||
"https://app.hyperswitch.io/euler-icons/Gateway/Light/STRIPE_TEST.svg"
|
||||
}
|
||||
Self::PaypalTest => {
|
||||
"https://app.hyperswitch.io/euler-icons/Gateway/Light/PAYPAL_TEST.svg"
|
||||
}
|
||||
_ => "https://app.hyperswitch.io/euler-icons/Gateway/Light/PHONYPAY.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default, serde::Serialize, serde::Deserialize, strum::Display, Clone, PartialEq, Debug, Eq,
|
||||
)]
|
||||
@ -31,16 +71,110 @@ pub enum DummyConnectorStatus {
|
||||
Failed,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentAttempt {
|
||||
pub timestamp: PrimitiveDateTime,
|
||||
pub attempt_id: String,
|
||||
pub payment_id: String,
|
||||
pub payment_request: DummyConnectorPaymentRequest,
|
||||
}
|
||||
|
||||
impl From<DummyConnectorPaymentRequest> for DummyConnectorPaymentAttempt {
|
||||
fn from(payment_request: DummyConnectorPaymentRequest) -> Self {
|
||||
let timestamp = common_utils::date_time::now();
|
||||
let payment_id = generate_id_with_default_len(consts::PAYMENT_ID_PREFIX);
|
||||
let attempt_id = generate_id_with_default_len(consts::ATTEMPT_ID_PREFIX);
|
||||
Self {
|
||||
timestamp,
|
||||
attempt_id,
|
||||
payment_id,
|
||||
payment_request,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DummyConnectorPaymentAttempt {
|
||||
pub fn build_payment_data(
|
||||
self,
|
||||
status: DummyConnectorStatus,
|
||||
next_action: Option<DummyConnectorNextAction>,
|
||||
return_url: Option<String>,
|
||||
) -> DummyConnectorPaymentData {
|
||||
DummyConnectorPaymentData {
|
||||
attempt_id: self.attempt_id,
|
||||
payment_id: self.payment_id,
|
||||
status,
|
||||
amount: self.payment_request.amount,
|
||||
eligible_amount: self.payment_request.amount,
|
||||
connector: self.payment_request.connector,
|
||||
created: self.timestamp,
|
||||
currency: self.payment_request.currency,
|
||||
payment_method_type: self.payment_request.payment_method_data.into(),
|
||||
next_action,
|
||||
return_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentRequest {
|
||||
pub amount: i64,
|
||||
pub currency: Currency,
|
||||
pub payment_method_data: DummyConnectorPaymentMethodData,
|
||||
pub return_url: Option<String>,
|
||||
pub connector: DummyConnectors,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
pub trait GetPaymentMethodDetails {
|
||||
fn get_name(&self) -> &'static str;
|
||||
fn get_image_link(&self) -> &'static str;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DummyConnectorPaymentMethodData {
|
||||
Card(DummyConnectorCard),
|
||||
Wallet(DummyConnectorWallet),
|
||||
PayLater(DummyConnectorPayLater),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default, serde::Serialize, serde::Deserialize, strum::Display, PartialEq, Debug, Clone,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DummyConnectorPaymentMethodType {
|
||||
#[default]
|
||||
Card,
|
||||
Wallet(DummyConnectorWallet),
|
||||
PayLater(DummyConnectorPayLater),
|
||||
}
|
||||
|
||||
impl From<DummyConnectorPaymentMethodData> for DummyConnectorPaymentMethodType {
|
||||
fn from(value: DummyConnectorPaymentMethodData) -> Self {
|
||||
match value {
|
||||
DummyConnectorPaymentMethodData::Card(_) => Self::Card,
|
||||
DummyConnectorPaymentMethodData::Wallet(wallet) => Self::Wallet(wallet),
|
||||
DummyConnectorPaymentMethodData::PayLater(pay_later) => Self::PayLater(pay_later),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetPaymentMethodDetails for DummyConnectorPaymentMethodType {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Card => "3D Secure",
|
||||
Self::Wallet(wallet) => wallet.get_name(),
|
||||
Self::PayLater(pay_later) => pay_later.get_name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_image_link(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Card => "https://www.svgrepo.com/show/115459/credit-card.svg",
|
||||
Self::Wallet(wallet) => wallet.get_image_link(),
|
||||
Self::PayLater(pay_later) => pay_later.get_image_link(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
@ -50,49 +184,108 @@ pub struct DummyConnectorCard {
|
||||
pub expiry_month: Secret<String>,
|
||||
pub expiry_year: Secret<String>,
|
||||
pub cvc: Secret<String>,
|
||||
pub complete: bool,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default, serde::Serialize, serde::Deserialize, strum::Display, PartialEq, Debug, Clone,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaymentMethodType {
|
||||
#[default]
|
||||
Card,
|
||||
pub enum DummyConnectorCardFlow {
|
||||
NoThreeDS(DummyConnectorStatus, Option<DummyConnectorErrors>),
|
||||
ThreeDS(DummyConnectorStatus, Option<DummyConnectorErrors>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
pub enum DummyConnectorWallet {
|
||||
GooglePay,
|
||||
Paypal,
|
||||
WeChatPay,
|
||||
MbWay,
|
||||
AliPay,
|
||||
AliPayHK,
|
||||
}
|
||||
|
||||
impl GetPaymentMethodDetails for DummyConnectorWallet {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GooglePay => "Google Pay",
|
||||
Self::Paypal => "PayPal",
|
||||
Self::WeChatPay => "WeChat Pay",
|
||||
Self::MbWay => "Mb Way",
|
||||
Self::AliPay => "Alipay",
|
||||
Self::AliPayHK => "Alipay HK",
|
||||
}
|
||||
}
|
||||
fn get_image_link(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GooglePay => "https://pay.google.com/about/static_kcs/images/logos/google-pay-logo.svg",
|
||||
Self::Paypal => "https://app.hyperswitch.io/euler-icons/Gateway/Light/PAYPAL.svg",
|
||||
Self::WeChatPay => "https://raw.githubusercontent.com/datatrans/payment-logos/master/assets/apm/wechat-pay.svg?sanitize=true",
|
||||
Self::MbWay => "https://upload.wikimedia.org/wikipedia/commons/e/e3/Logo_MBWay.svg",
|
||||
Self::AliPay => "https://www.logo.wine/a/logo/Alipay/Alipay-Logo.wine.svg",
|
||||
Self::AliPayHK => "https://www.logo.wine/a/logo/Alipay/Alipay-Logo.wine.svg",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||
pub enum DummyConnectorPayLater {
|
||||
Klarna,
|
||||
Affirm,
|
||||
AfterPayClearPay,
|
||||
}
|
||||
|
||||
impl GetPaymentMethodDetails for DummyConnectorPayLater {
|
||||
fn get_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Klarna => "Klarna",
|
||||
Self::Affirm => "Affirm",
|
||||
Self::AfterPayClearPay => "Afterpay Clearpay",
|
||||
}
|
||||
}
|
||||
fn get_image_link(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Klarna => "https://docs.klarna.com/assets/media/7404df75-d165-4eee-b33c-a9537b847952/Klarna_Logo_Primary_Black.svg",
|
||||
Self::Affirm => "https://upload.wikimedia.org/wikipedia/commons/f/ff/Affirm_logo.svg",
|
||||
Self::AfterPayClearPay => "https://upload.wikimedia.org/wikipedia/en/c/c3/Afterpay_logo.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||
pub struct DummyConnectorPaymentData {
|
||||
pub attempt_id: String,
|
||||
pub payment_id: String,
|
||||
pub status: DummyConnectorStatus,
|
||||
pub amount: i64,
|
||||
pub eligible_amount: i64,
|
||||
pub currency: Currency,
|
||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||
pub created: PrimitiveDateTime,
|
||||
pub payment_method_type: PaymentMethodType,
|
||||
pub payment_method_type: DummyConnectorPaymentMethodType,
|
||||
pub connector: DummyConnectors,
|
||||
pub next_action: Option<DummyConnectorNextAction>,
|
||||
pub return_url: Option<String>,
|
||||
}
|
||||
|
||||
impl DummyConnectorPaymentData {
|
||||
pub fn new(
|
||||
status: DummyConnectorStatus,
|
||||
amount: i64,
|
||||
eligible_amount: i64,
|
||||
currency: Currency,
|
||||
created: PrimitiveDateTime,
|
||||
payment_method_type: PaymentMethodType,
|
||||
) -> Self {
|
||||
Self {
|
||||
status,
|
||||
amount,
|
||||
eligible_amount,
|
||||
currency,
|
||||
created,
|
||||
payment_method_type,
|
||||
pub fn is_eligible_for_refund(&self, refund_amount: i64) -> DummyConnectorResult<()> {
|
||||
if self.eligible_amount < refund_amount {
|
||||
return Err(
|
||||
report!(DummyConnectorErrors::RefundAmountExceedsPaymentAmount)
|
||||
.attach_printable("Eligible amount is lesser than refund amount"),
|
||||
);
|
||||
}
|
||||
if self.status != DummyConnectorStatus::Succeeded {
|
||||
return Err(report!(DummyConnectorErrors::PaymentNotSuccessful)
|
||||
.attach_printable("Payment is not successful to process the refund"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DummyConnectorNextAction {
|
||||
RedirectToUrl(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentResponse {
|
||||
pub status: DummyConnectorStatus,
|
||||
@ -101,7 +294,22 @@ pub struct DummyConnectorPaymentResponse {
|
||||
pub currency: Currency,
|
||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||
pub created: PrimitiveDateTime,
|
||||
pub payment_method_type: PaymentMethodType,
|
||||
pub payment_method_type: DummyConnectorPaymentMethodType,
|
||||
pub next_action: Option<DummyConnectorNextAction>,
|
||||
}
|
||||
|
||||
impl From<DummyConnectorPaymentData> for DummyConnectorPaymentResponse {
|
||||
fn from(value: DummyConnectorPaymentData) -> Self {
|
||||
Self {
|
||||
status: value.status,
|
||||
id: value.payment_id,
|
||||
amount: value.amount,
|
||||
currency: value.currency,
|
||||
created: value.created,
|
||||
payment_method_type: value.payment_method_type,
|
||||
next_action: value.next_action,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@ -109,24 +317,20 @@ pub struct DummyConnectorPaymentRetrieveRequest {
|
||||
pub payment_id: String,
|
||||
}
|
||||
|
||||
impl DummyConnectorPaymentResponse {
|
||||
pub fn new(
|
||||
status: DummyConnectorStatus,
|
||||
id: String,
|
||||
amount: i64,
|
||||
currency: Currency,
|
||||
created: PrimitiveDateTime,
|
||||
payment_method_type: PaymentMethodType,
|
||||
) -> Self {
|
||||
Self {
|
||||
status,
|
||||
id,
|
||||
amount,
|
||||
currency,
|
||||
created,
|
||||
payment_method_type,
|
||||
}
|
||||
}
|
||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentConfirmRequest {
|
||||
pub attempt_id: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentCompleteRequest {
|
||||
pub attempt_id: String,
|
||||
pub confirm: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DummyConnectorPaymentCompleteBody {
|
||||
pub confirm: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, serde::Serialize, Eq, PartialEq, serde::Deserialize)]
|
||||
@ -173,3 +377,5 @@ pub struct DummyConnectorRefundRetrieveRequest {
|
||||
|
||||
pub type DummyConnectorResponse<T> =
|
||||
CustomResult<services::ApplicationResponse<T>, DummyConnectorErrors>;
|
||||
|
||||
pub type DummyConnectorResult<T> = CustomResult<T, DummyConnectorErrors>;
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use std::fmt::Debug;
|
||||
|
||||
use app::AppState;
|
||||
use common_utils::generate_id;
|
||||
use error_stack::{report, ResultExt};
|
||||
use common_utils::ext_traits::AsyncExt;
|
||||
use error_stack::{report, IntoReport, ResultExt};
|
||||
use masking::PeekInterface;
|
||||
use maud::html;
|
||||
use rand::Rng;
|
||||
use redis_interface::RedisConnectionPool;
|
||||
use tokio::time as tokio;
|
||||
|
||||
use super::{errors, types};
|
||||
use crate::{routes::app, services::api, utils::OptionExt};
|
||||
use super::{
|
||||
consts, errors,
|
||||
types::{self, GetPaymentMethodDetails},
|
||||
};
|
||||
use crate::routes::AppState;
|
||||
|
||||
pub async fn tokio_mock_sleep(delay: u64, tolerance: u64) {
|
||||
let mut rng = rand::thread_rng();
|
||||
@ -17,185 +19,14 @@ pub async fn tokio_mock_sleep(delay: u64, tolerance: u64) {
|
||||
tokio::sleep(tokio::Duration::from_millis(effective_delay)).await
|
||||
}
|
||||
|
||||
pub async fn payment(
|
||||
pub async fn store_data_in_redis(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorPaymentResponse> {
|
||||
tokio_mock_sleep(
|
||||
state.conf.dummy_connector.payment_duration,
|
||||
state.conf.dummy_connector.payment_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payment_id = generate_id(20, "dummy_pay");
|
||||
match req.payment_method_data {
|
||||
types::DummyConnectorPaymentMethodData::Card(card) => {
|
||||
let card_number = card.number.peek();
|
||||
|
||||
match card_number.as_str() {
|
||||
"4111111111111111" | "4242424242424242" => {
|
||||
let timestamp = common_utils::date_time::now();
|
||||
let payment_data = types::DummyConnectorPaymentData::new(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
req.amount,
|
||||
req.amount,
|
||||
req.currency,
|
||||
timestamp.to_owned(),
|
||||
types::PaymentMethodType::Card,
|
||||
);
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
store_data_in_redis(
|
||||
redis_conn,
|
||||
payment_id.to_owned(),
|
||||
payment_data,
|
||||
state.conf.dummy_connector.payment_ttl,
|
||||
)
|
||||
.await?;
|
||||
Ok(api::ApplicationResponse::Json(
|
||||
types::DummyConnectorPaymentResponse::new(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
payment_id,
|
||||
req.amount,
|
||||
req.currency,
|
||||
timestamp,
|
||||
types::PaymentMethodType::Card,
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => Err(report!(errors::DummyConnectorErrors::CardNotSupported)
|
||||
.attach_printable("The card is not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn payment_data(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorPaymentRetrieveRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorPaymentResponse> {
|
||||
let payment_id = req.payment_id;
|
||||
tokio_mock_sleep(
|
||||
state.conf.dummy_connector.payment_retrieve_duration,
|
||||
state.conf.dummy_connector.payment_retrieve_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
let payment_data = redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorPaymentData>(
|
||||
payment_id.as_str(),
|
||||
"DummyConnectorPaymentData",
|
||||
)
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::PaymentNotFound)?;
|
||||
|
||||
Ok(api::ApplicationResponse::Json(
|
||||
types::DummyConnectorPaymentResponse::new(
|
||||
payment_data.status,
|
||||
payment_id,
|
||||
payment_data.amount,
|
||||
payment_data.currency,
|
||||
payment_data.created,
|
||||
payment_data.payment_method_type,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn refund_payment(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorRefundRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorRefundResponse> {
|
||||
tokio_mock_sleep(
|
||||
state.conf.dummy_connector.refund_duration,
|
||||
state.conf.dummy_connector.refund_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payment_id = req
|
||||
.payment_id
|
||||
.get_required_value("payment_id")
|
||||
.change_context(errors::DummyConnectorErrors::MissingRequiredField {
|
||||
field_name: "payment_id",
|
||||
})?;
|
||||
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
let mut payment_data = redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorPaymentData>(
|
||||
payment_id.as_str(),
|
||||
"DummyConnectorPaymentData",
|
||||
)
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::PaymentNotFound)?;
|
||||
|
||||
if payment_data.eligible_amount < req.amount {
|
||||
return Err(
|
||||
report!(errors::DummyConnectorErrors::RefundAmountExceedsPaymentAmount)
|
||||
.attach_printable("Eligible amount is lesser than refund amount"),
|
||||
);
|
||||
}
|
||||
|
||||
if payment_data.status != types::DummyConnectorStatus::Succeeded {
|
||||
return Err(report!(errors::DummyConnectorErrors::PaymentNotSuccessful)
|
||||
.attach_printable("Payment is not successful to process the refund"));
|
||||
}
|
||||
|
||||
let refund_id = generate_id(20, "dummy_ref");
|
||||
payment_data.eligible_amount -= req.amount;
|
||||
store_data_in_redis(
|
||||
redis_conn.to_owned(),
|
||||
payment_id,
|
||||
payment_data.to_owned(),
|
||||
state.conf.dummy_connector.payment_ttl,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let refund_data = types::DummyConnectorRefundResponse::new(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
refund_id.to_owned(),
|
||||
payment_data.currency,
|
||||
common_utils::date_time::now(),
|
||||
payment_data.amount,
|
||||
req.amount,
|
||||
);
|
||||
|
||||
store_data_in_redis(
|
||||
redis_conn,
|
||||
refund_id,
|
||||
refund_data.to_owned(),
|
||||
state.conf.dummy_connector.refund_ttl,
|
||||
)
|
||||
.await?;
|
||||
Ok(api::ApplicationResponse::Json(refund_data))
|
||||
}
|
||||
|
||||
pub async fn refund_data(
|
||||
state: &AppState,
|
||||
req: types::DummyConnectorRefundRetrieveRequest,
|
||||
) -> types::DummyConnectorResponse<types::DummyConnectorRefundResponse> {
|
||||
let refund_id = req.refund_id;
|
||||
tokio_mock_sleep(
|
||||
state.conf.dummy_connector.refund_retrieve_duration,
|
||||
state.conf.dummy_connector.refund_retrieve_tolerance,
|
||||
)
|
||||
.await;
|
||||
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
let refund_data = redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorRefundResponse>(
|
||||
refund_id.as_str(),
|
||||
"DummyConnectorRefundResponse",
|
||||
)
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::RefundNotFound)?;
|
||||
Ok(api::ApplicationResponse::Json(refund_data))
|
||||
}
|
||||
|
||||
async fn store_data_in_redis(
|
||||
redis_conn: Arc<RedisConnectionPool>,
|
||||
key: String,
|
||||
data: impl serde::Serialize + Debug,
|
||||
ttl: i64,
|
||||
) -> Result<(), error_stack::Report<errors::DummyConnectorErrors>> {
|
||||
) -> types::DummyConnectorResult<()> {
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
|
||||
redis_conn
|
||||
.serialize_and_set_key_with_expiry(&key, data, ttl)
|
||||
.await
|
||||
@ -203,3 +34,264 @@ async fn store_data_in_redis(
|
||||
.attach_printable("Failed to add data in redis")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_payment_data_from_payment_id(
|
||||
state: &AppState,
|
||||
payment_id: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorPaymentData>(
|
||||
payment_id.as_str(),
|
||||
"types DummyConnectorPaymentData",
|
||||
)
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::PaymentNotFound)
|
||||
}
|
||||
|
||||
pub async fn get_payment_data_by_attempt_id(
|
||||
state: &AppState,
|
||||
attempt_id: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
let redis_conn = state.store.get_redis_conn();
|
||||
redis_conn
|
||||
.get_and_deserialize_key::<String>(attempt_id.as_str(), "String")
|
||||
.await
|
||||
.async_and_then(|payment_id| async move {
|
||||
redis_conn
|
||||
.get_and_deserialize_key::<types::DummyConnectorPaymentData>(
|
||||
payment_id.as_str(),
|
||||
"DummyConnectorPaymentData",
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.change_context(errors::DummyConnectorErrors::PaymentNotFound)
|
||||
}
|
||||
|
||||
pub fn get_authorize_page(
|
||||
payment_data: types::DummyConnectorPaymentData,
|
||||
return_url: String,
|
||||
) -> String {
|
||||
let mode = payment_data.payment_method_type.get_name();
|
||||
let image = payment_data.payment_method_type.get_image_link();
|
||||
let connector_image = payment_data.connector.get_connector_image_link();
|
||||
let currency = payment_data.currency.to_string();
|
||||
|
||||
html! {
|
||||
head {
|
||||
title { "Authorize Payment" }
|
||||
style { (consts::THREE_DS_CSS) }
|
||||
}
|
||||
body {
|
||||
div.heading {
|
||||
img.logo src="https://app.hyperswitch.io/assets/Dark/hyperswitchLogoIconWithText.svg" alt="Hyperswitch Logo" {}
|
||||
h1 { "Test Payment Page" }
|
||||
}
|
||||
div.container {
|
||||
div.payment_details {
|
||||
img src=(image) {}
|
||||
div.border {}
|
||||
img src=(connector_image) {}
|
||||
}
|
||||
(maud::PreEscaped(
|
||||
format!(r#"
|
||||
<p class="disclaimer">
|
||||
This is a test payment of <span id="amount"></span> {} using {}
|
||||
<script>
|
||||
document.getElementById("amount").innerHTML = ({} / 100).toFixed(2);
|
||||
</script>
|
||||
</p>
|
||||
"#, currency, mode, payment_data.amount)
|
||||
)
|
||||
)
|
||||
p { b { "Real money will not be debited for the payment." } " You can choose to simulate successful or failed payment while testing this payment."}
|
||||
div.user_action {
|
||||
button.authorize onclick=(format!("window.location.href='{}?confirm=true'", return_url))
|
||||
{ "Complete Payment" }
|
||||
button.reject onclick=(format!("window.location.href='{}?confirm=false'", return_url))
|
||||
{ "Reject Payment" }
|
||||
}
|
||||
}
|
||||
div.container {
|
||||
p.disclaimer { "What is this page?" }
|
||||
p { "This page is just a simulation for integration and testing purpose.\
|
||||
In live mode, this page will not be displayed and the user will be taken to the\
|
||||
Bank page (or) Googlepay cards popup (or) original payment method’s page. "
|
||||
a href=("https://hyperswitch.io/contact") { "Contact us" } " for any queries." }
|
||||
}
|
||||
}
|
||||
}
|
||||
.into_string()
|
||||
}
|
||||
|
||||
pub fn get_expired_page() -> String {
|
||||
html! {
|
||||
head {
|
||||
title { "Authorize Payment" }
|
||||
style { (consts::THREE_DS_CSS) }
|
||||
}
|
||||
body {
|
||||
div.heading {
|
||||
img.logo src="https://app.hyperswitch.io/assets/Dark/hyperswitchLogoIconWithText.svg" alt="Hyperswitch Logo" {}
|
||||
h1 { "Test Payment Page" }
|
||||
}
|
||||
div.container {
|
||||
p.disclaimer { "This link is not valid or it is expired" }
|
||||
}
|
||||
div.container {
|
||||
p.disclaimer { "What is this page?" }
|
||||
p { "This page is just a simulation for integration and testing purpose.\
|
||||
In live mode, this is not visible. "
|
||||
a href=("https://hyperswitch.io/contact") { "Contact us" } " for any queries." }
|
||||
}
|
||||
}
|
||||
}
|
||||
.into_string()
|
||||
}
|
||||
|
||||
pub trait ProcessPaymentAttempt {
|
||||
fn build_payment_data_from_payment_attempt(
|
||||
self,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
redirect_url: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData>;
|
||||
}
|
||||
|
||||
impl ProcessPaymentAttempt for types::DummyConnectorCard {
|
||||
fn build_payment_data_from_payment_attempt(
|
||||
self,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
redirect_url: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
match self.get_flow_from_card_number()? {
|
||||
types::DummyConnectorCardFlow::NoThreeDS(status, error) => {
|
||||
if let Some(error) = error {
|
||||
Err(error).into_report()?;
|
||||
}
|
||||
Ok(payment_attempt.build_payment_data(status, None, None))
|
||||
}
|
||||
types::DummyConnectorCardFlow::ThreeDS(_, _) => {
|
||||
Ok(payment_attempt.clone().build_payment_data(
|
||||
types::DummyConnectorStatus::Processing,
|
||||
Some(types::DummyConnectorNextAction::RedirectToUrl(redirect_url)),
|
||||
payment_attempt.payment_request.return_url,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl types::DummyConnectorCard {
|
||||
pub fn get_flow_from_card_number(
|
||||
self,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorCardFlow> {
|
||||
let card_number = self.number.peek();
|
||||
match card_number.as_str() {
|
||||
"4111111111111111" | "4242424242424242" | "5555555555554444" | "38000000000006"
|
||||
| "378282246310005" | "6011111111111117" => {
|
||||
Ok(types::DummyConnectorCardFlow::NoThreeDS(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
None,
|
||||
))
|
||||
}
|
||||
"5105105105105100" | "4000000000000002" => {
|
||||
Ok(types::DummyConnectorCardFlow::NoThreeDS(
|
||||
types::DummyConnectorStatus::Failed,
|
||||
Some(errors::DummyConnectorErrors::PaymentDeclined {
|
||||
message: "Card declined",
|
||||
}),
|
||||
))
|
||||
}
|
||||
"4000000000009995" => Ok(types::DummyConnectorCardFlow::NoThreeDS(
|
||||
types::DummyConnectorStatus::Failed,
|
||||
Some(errors::DummyConnectorErrors::PaymentDeclined {
|
||||
message: "Insufficient funds",
|
||||
}),
|
||||
)),
|
||||
"4000000000009987" => Ok(types::DummyConnectorCardFlow::NoThreeDS(
|
||||
types::DummyConnectorStatus::Failed,
|
||||
Some(errors::DummyConnectorErrors::PaymentDeclined {
|
||||
message: "Lost card",
|
||||
}),
|
||||
)),
|
||||
"4000000000009979" => Ok(types::DummyConnectorCardFlow::NoThreeDS(
|
||||
types::DummyConnectorStatus::Failed,
|
||||
Some(errors::DummyConnectorErrors::PaymentDeclined {
|
||||
message: "Stolen card",
|
||||
}),
|
||||
)),
|
||||
"4000003800000446" => Ok(types::DummyConnectorCardFlow::ThreeDS(
|
||||
types::DummyConnectorStatus::Succeeded,
|
||||
None,
|
||||
)),
|
||||
_ => Err(report!(errors::DummyConnectorErrors::CardNotSupported)
|
||||
.attach_printable("The card is not supported")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessPaymentAttempt for types::DummyConnectorWallet {
|
||||
fn build_payment_data_from_payment_attempt(
|
||||
self,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
redirect_url: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
Ok(payment_attempt.clone().build_payment_data(
|
||||
types::DummyConnectorStatus::Processing,
|
||||
Some(types::DummyConnectorNextAction::RedirectToUrl(redirect_url)),
|
||||
payment_attempt.payment_request.return_url,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessPaymentAttempt for types::DummyConnectorPayLater {
|
||||
fn build_payment_data_from_payment_attempt(
|
||||
self,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
redirect_url: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
Ok(payment_attempt.clone().build_payment_data(
|
||||
types::DummyConnectorStatus::Processing,
|
||||
Some(types::DummyConnectorNextAction::RedirectToUrl(redirect_url)),
|
||||
payment_attempt.payment_request.return_url,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessPaymentAttempt for types::DummyConnectorPaymentMethodData {
|
||||
fn build_payment_data_from_payment_attempt(
|
||||
self,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
redirect_url: String,
|
||||
) -> types::DummyConnectorResult<types::DummyConnectorPaymentData> {
|
||||
match self {
|
||||
Self::Card(card) => {
|
||||
card.build_payment_data_from_payment_attempt(payment_attempt, redirect_url)
|
||||
}
|
||||
Self::Wallet(wallet) => {
|
||||
wallet.build_payment_data_from_payment_attempt(payment_attempt, redirect_url)
|
||||
}
|
||||
Self::PayLater(pay_later) => {
|
||||
pay_later.build_payment_data_from_payment_attempt(payment_attempt, redirect_url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl types::DummyConnectorPaymentData {
|
||||
pub fn process_payment_attempt(
|
||||
state: &AppState,
|
||||
payment_attempt: types::DummyConnectorPaymentAttempt,
|
||||
) -> types::DummyConnectorResult<Self> {
|
||||
let redirect_url = format!(
|
||||
"{}/dummy-connector/authorize/{}",
|
||||
state.conf.server.base_url, payment_attempt.attempt_id
|
||||
);
|
||||
payment_attempt
|
||||
.clone()
|
||||
.payment_request
|
||||
.payment_method_data
|
||||
.build_payment_data_from_payment_attempt(payment_attempt, redirect_url)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user