feat: add payments session operation (#42)

This commit is contained in:
Narayan Bhat
2022-12-01 17:13:40 +05:30
committed by GitHub
parent 63652a52ea
commit bad0d7eeab
5 changed files with 242 additions and 3 deletions

View File

@ -10,8 +10,8 @@ use router_env::{tracing, tracing::instrument};
use time;
pub use self::operations::{
PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentResponse, PaymentStatus,
PaymentUpdate,
PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentResponse, PaymentSession,
PaymentStatus, PaymentUpdate,
};
use self::{
flows::{ConstructFlowSpecificData, Feature},

View File

@ -3,6 +3,7 @@ mod payment_capture;
mod payment_confirm;
mod payment_create;
mod payment_response;
mod payment_session;
mod payment_start;
mod payment_status;
mod payment_update;
@ -14,6 +15,7 @@ pub use payment_capture::PaymentCapture;
pub use payment_confirm::PaymentConfirm;
pub use payment_create::PaymentCreate;
pub use payment_response::PaymentResponse;
pub use payment_session::PaymentSession;
pub use payment_start::PaymentStart;
pub use payment_status::PaymentStatus;
pub use payment_update::PaymentUpdate;

View File

@ -0,0 +1,227 @@
use std::marker::PhantomData;
use async_trait::async_trait;
use error_stack::{report, ResultExt};
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest};
use crate::{
core::{
errors::{self, RouterResult, StorageErrorExt},
payments::{self, helpers, PaymentData},
},
db::{payment_attempt::IPaymentAttempt, payment_intent::IPaymentIntent, Db},
routes::AppState,
types::{
api,
storage::{self, enums},
Connector,
},
utils::OptionExt,
};
#[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(ops = "all", flow = "session")]
pub struct PaymentSession;
#[async_trait]
impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
for PaymentSession
{
#[instrument(skip_all)]
async fn get_trackers<'a>(
&'a self,
state: &'a AppState,
payment_id: &api::PaymentIdType,
merchant_id: &str,
_connector: Connector,
request: &api::PaymentsSessionRequest,
_mandate_type: Option<api::MandateTxnType>,
) -> RouterResult<(
BoxedOperation<'a, F, api::PaymentsSessionRequest>,
PaymentData<F>,
Option<payments::CustomerDetails>,
)> {
let payment_id = payment_id
.get_payment_intent_id()
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
let db = &state.store;
let mut payment_attempt = db
.find_payment_attempt_by_payment_id_merchant_id(&payment_id, merchant_id)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})?;
let mut payment_intent = db
.find_payment_intent_by_payment_id_merchant_id(&payment_id, merchant_id)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})?;
let currency = payment_intent.currency.get_required_value("currency")?;
payment_attempt.payment_method = Some(enums::PaymentMethodType::Wallet);
let amount = payment_intent.amount;
if let Some(ref payment_intent_client_secret) = payment_intent.client_secret {
if request.client_secret.ne(payment_intent_client_secret) {
return Err(report!(errors::ApiErrorResponse::ClientSecretInvalid));
}
}
let shipping_address = helpers::get_address_for_payment_request(
db,
None,
payment_intent.shipping_address_id.as_deref(),
)
.await?;
let billing_address = helpers::get_address_for_payment_request(
db,
None,
payment_intent.billing_address_id.as_deref(),
)
.await?;
payment_intent.shipping_address_id = shipping_address.clone().map(|x| x.address_id);
payment_intent.billing_address_id = billing_address.clone().map(|x| x.address_id);
let db = db as &dyn Db;
let connector_response = db
.find_connector_response_by_payment_id_merchant_id_txn_id(
&payment_intent.payment_id,
&payment_intent.merchant_id,
&payment_attempt.txn_id,
)
.await
.map_err(|error| {
error
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Database error when finding connector response")
})?;
Ok((
Box::new(self),
PaymentData {
flow: PhantomData,
payment_intent,
payment_attempt,
currency,
amount,
mandate_id: None,
token: None,
setup_mandate: None,
address: payments::PaymentAddress {
shipping: shipping_address.as_ref().map(|a| a.into()),
billing: billing_address.as_ref().map(|a| a.into()),
},
confirm: None,
payment_method_data: None,
force_sync: None,
refunds: vec![],
connector_response,
},
None,
))
}
}
#[async_trait]
impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsSessionRequest> for PaymentSession {
#[instrument(skip_all)]
async fn update_trackers<'b>(
&'b self,
_db: &dyn Db,
_payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
_customer: Option<storage::Customer>,
) -> RouterResult<(
BoxedOperation<'b, F, api::PaymentsSessionRequest>,
PaymentData<F>,
)>
where
F: 'b + Send,
{
Ok((Box::new(self), payment_data))
}
}
impl<F: Send + Clone> ValidateRequest<F, api::PaymentsSessionRequest> for PaymentSession {
#[instrument(skip_all)]
fn validate_request<'a, 'b>(
&'b self,
request: &api::PaymentsSessionRequest,
merchant_account: &'a storage::MerchantAccount,
) -> RouterResult<(
BoxedOperation<'b, F, api::PaymentsSessionRequest>,
&'a str,
api::PaymentIdType,
Option<api::MandateTxnType>,
)> {
//paymentid is already generated and should be sent in the request
let given_payment_id = request
.payment_id
.get_payment_intent_id()
.change_context(errors::ApiErrorResponse::PaymentNotFound)?;
Ok((
Box::new(self),
&merchant_account.merchant_id,
api::PaymentIdType::PaymentIntentId(given_payment_id),
None,
))
}
}
#[async_trait]
impl<F: Clone + Send, Op: Send + Sync + Operation<F, api::PaymentsSessionRequest>>
Domain<F, api::PaymentsSessionRequest> for Op
where
for<'a> &'a Op: Operation<F, api::PaymentsSessionRequest>,
{
#[instrument(skip_all)]
async fn get_or_create_customer_details<'a>(
&'a self,
db: &dyn Db,
payment_data: &mut PaymentData<F>,
request: Option<payments::CustomerDetails>,
merchant_id: &str,
) -> errors::CustomResult<
(
BoxedOperation<'a, F, api::PaymentsSessionRequest>,
Option<api::CustomerResponse>,
),
errors::StorageError,
> {
helpers::create_customer_if_not_exist(
Box::new(self),
db,
payment_data,
request,
merchant_id,
)
.await
}
#[instrument(skip_all)]
async fn make_pm_data<'a>(
&'a self,
_state: &'a AppState,
_payment_method: Option<enums::PaymentMethodType>,
_txn_id: &str,
_payment_attempt: &storage::PaymentAttempt,
_request: &Option<api::PaymentMethod>,
_token: Option<i32>,
) -> RouterResult<(
BoxedOperation<'a, F, api::PaymentsSessionRequest>,
Option<api::PaymentMethod>,
)> {
//No payment method data for this operation
Ok((Box::new(self), None))
}
}

View File

@ -625,6 +625,12 @@ pub struct PaymentsRetrieveRequest {
pub connector: Option<String>,
}
#[derive(Default, Debug, serde::Deserialize, Clone)]
pub struct PaymentsSessionRequest {
pub payment_id: PaymentIdType,
pub client_secret: String,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PaymentRetrieveBody {
pub merchant_id: Option<String>,

View File

@ -15,6 +15,7 @@ enum Derives {
Canceldata,
Capturedata,
Start,
Session,
}
impl From<String> for Derives {
@ -29,6 +30,7 @@ impl From<String> for Derives {
"capture" => Self::Capture,
"capturedata" => Self::Capturedata,
"start" => Self::Start,
"session" => Self::Session,
_ => Self::Authorize,
}
}
@ -98,6 +100,7 @@ impl Conversion {
Derives::Capture => syn::Ident::new("PaymentsCaptureRequest", Span::call_site()),
Derives::Capturedata => syn::Ident::new("PaymentsCaptureData", Span::call_site()),
Derives::Start => syn::Ident::new("PaymentsStartRequest", Span::call_site()),
Derives::Session => syn::Ident::new("PaymentsSessionRequest", Span::call_site()),
}
}
@ -284,7 +287,8 @@ pub fn operation_derive_inner(token: proc_macro::TokenStream) -> proc_macro::Tok
PaymentsCancelRequest,
PaymentsRetrieveRequest,
PaymentsRequest,
PaymentsStartRequest
PaymentsStartRequest,
PaymentsSessionRequest
}
};
#trait_derive