feat(routes): add the validation API in payments route (#61)

This commit is contained in:
Nishant Joshi
2022-12-08 20:06:46 +05:30
committed by GitHub
parent 0f2afe8c62
commit bbe3bd862e
14 changed files with 288 additions and 94 deletions

View File

@ -10,8 +10,8 @@ use router_env::{tracing, tracing::instrument};
use time;
pub use self::operations::{
PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentResponse, PaymentSession,
PaymentStatus, PaymentUpdate,
PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentMethodValidate,
PaymentResponse, PaymentStatus, PaymentUpdate,
};
use self::{
flows::{ConstructFlowSpecificData, Feature},
@ -23,7 +23,6 @@ use crate::{
core::{
errors::{self, RouterResponse, RouterResult},
payments,
payments::transformers as payments_transformers,
},
db::StorageInterface,
pii::Email,
@ -136,18 +135,19 @@ where
#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn payments_core<F, Req, Op, FData>(
pub async fn payments_core<F, Res, Req, Op, FData>(
state: &AppState,
merchant_account: storage::MerchantAccount,
operation: Op,
req: Req,
auth_flow: services::AuthFlow,
call_connector_action: CallConnectorAction,
) -> RouterResponse<api::PaymentsResponse>
) -> RouterResponse<Res>
where
F: Send + Clone,
Op: Operation<F, Req> + Send + Sync + Clone,
Req: Debug,
Res: transformers::ToResponse<Req, PaymentData<F>, Op> + From<Req>,
// To create connector flow specific interface data
PaymentData<F>: ConstructFlowSpecificData<F, FData, types::PaymentsResponseData>,
@ -159,9 +159,7 @@ where
// To perform router related operation for PaymentResponse
PaymentResponse: Operation<F, FData>,
// To create merchant response
api::PaymentsResponse: From<Req>,
{
let (payment_data, req, customer) = payments_operation_core(
state,
@ -171,19 +169,12 @@ where
call_connector_action,
)
.await?;
payments_transformers::payments_to_payments_response(
Res::generate_response(
Some(req),
payment_data.payment_attempt,
payment_data.payment_intent,
payment_data.refunds,
payment_data.mandate_id,
payment_data.payment_method_data,
payment_data,
customer,
auth_flow,
payment_data.address,
&state.conf.server,
payment_data.connector_response.authentication_data,
operation,
)
}
@ -253,7 +244,7 @@ pub async fn payments_response_for_redirection_flows<'a>(
req: PaymentsRetrieveRequest,
flow_type: CallConnectorAction,
) -> RouterResponse<PaymentsResponse> {
payments_core::<api::PSync, _, _, _>(
payments_core::<api::PSync, api::PaymentsResponse, _, _, _>(
state,
merchant_account,
payments::PaymentStatus,

View File

@ -14,6 +14,7 @@ pub use payment_cancel::PaymentCancel;
pub use payment_capture::PaymentCapture;
pub use payment_confirm::PaymentConfirm;
pub use payment_create::PaymentCreate;
pub use payment_method_validate::PaymentMethodValidate;
pub use payment_response::PaymentResponse;
pub use payment_session::PaymentSession;
pub use payment_start::PaymentStart;

View File

@ -40,7 +40,7 @@ impl<F: Send + Clone> ValidateRequest<F, api::VerifyRequest> for PaymentMethodVa
api::PaymentIdType,
Option<api::MandateTxnType>,
)> {
let request_merchant_id = Some(&request.merchant_id[..]);
let request_merchant_id = request.merchant_id.as_deref();
helpers::validate_merchant_id(&merchant_account.merchant_id, request_merchant_id)
.change_context(errors::ApiErrorResponse::MerchantAccountNotFound)?;

View File

@ -20,7 +20,7 @@ use crate::{
#[derive(Debug, Clone, Copy, router_derive::PaymentOperation)]
#[operation(
ops = "post_tracker",
flow = "syncdata,authorizedata,canceldata,capturedata"
flow = "syncdata,authorizedata,canceldata,capturedata,verifydata"
)]
pub struct PaymentResponse;
@ -104,6 +104,24 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f
}
}
#[async_trait]
impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::VerifyRequestData> for PaymentResponse {
async fn update_tracker<'b>(
&'b self,
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::VerifyRequestData, types::PaymentsResponseData>,
>,
) -> RouterResult<PaymentData<F>>
where
F: 'b + Send,
{
Ok(payment_response_ut(db, payment_id, payment_data, response).await?)
}
}
async fn payment_response_ut<F: Clone, T>(
db: &dyn StorageInterface,
_payment_id: &api::PaymentIdType,

View File

@ -103,6 +103,92 @@ where
Ok((payment_data, router_data))
}
pub trait ToResponse<Req, D, Op>
where
Self: From<Req>,
Op: Debug,
{
fn generate_response(
req: Option<Req>,
data: D,
customer: Option<storage::Customer>,
auth_flow: services::AuthFlow,
server: &Server,
operation: Op,
) -> RouterResponse<Self>;
}
impl<F, Req, Op> ToResponse<Req, PaymentData<F>, Op> for api::PaymentsResponse
where
Self: From<Req>,
F: Clone,
Op: Debug,
{
fn generate_response(
req: Option<Req>,
payment_data: PaymentData<F>,
customer: Option<storage::Customer>,
auth_flow: services::AuthFlow,
server: &Server,
operation: Op,
) -> RouterResponse<Self> {
payments_to_payments_response(
req,
payment_data.payment_attempt,
payment_data.payment_intent,
payment_data.refunds,
payment_data.mandate_id,
payment_data.payment_method_data,
customer,
auth_flow,
payment_data.address,
server,
payment_data.connector_response.authentication_data,
operation,
)
}
}
impl<F, Req, Op> ToResponse<Req, PaymentData<F>, Op> for api::VerifyResponse
where
Self: From<Req>,
F: Clone,
Op: Debug,
{
fn generate_response(
_req: Option<Req>,
data: PaymentData<F>,
customer: Option<storage::Customer>,
_auth_flow: services::AuthFlow,
_server: &Server,
_operation: Op,
) -> RouterResponse<Self> {
Ok(services::BachResponse::Json(Self {
verify_id: Some(data.payment_intent.payment_id),
merchant_id: Some(data.payment_intent.merchant_id),
client_secret: data.payment_intent.client_secret.map(masking::Secret::new),
customer_id: customer.as_ref().map(|x| x.customer_id.clone()),
email: customer
.as_ref()
.and_then(|cus| cus.email.as_ref().map(|s| s.to_owned())),
name: customer
.as_ref()
.and_then(|cus| cus.name.as_ref().map(|s| s.to_owned().into())),
phone: customer
.as_ref()
.and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())),
mandate_id: data.mandate_id,
payment_method: data.payment_attempt.payment_method,
payment_method_data: data
.payment_method_data
.map(api::PaymentMethodDataResponse::from),
payment_token: data.token,
error_code: None,
error_message: data.payment_attempt.error_message,
}))
}
}
#[instrument(skip_all)]
// try to use router data here so that already validated things , we don't want to repeat the validations.
// Add internal value not found and external value not found so that we can give 500 / Internal server error for internal value not found
@ -130,7 +216,6 @@ where
.as_ref()
.get_required_value("currency")?
.to_string();
let refunds_response = if refunds.is_empty() {
None
} else {

View File

@ -37,7 +37,7 @@ async fn payments_incoming_webhook_flow(
payments::CallConnectorAction::Trigger
};
let payments_response = payments::payments_core::<api::PSync, _, _, _>(
let payments_response = payments::payments_core::<api::PSync, api::PaymentsResponse, _, _, _>(
&state,
merchant_account.clone(),
payments::operations::PaymentStatus,