fix: fixes to surface level mandate issues (#180)

This commit is contained in:
Nishant Joshi
2022-12-22 12:51:52 +05:30
committed by GitHub
parent 79a4a3addf
commit dd7e093fe3
16 changed files with 202 additions and 82 deletions

2
Cargo.lock generated
View File

@ -916,9 +916,11 @@ dependencies = [
name = "common_utils"
version = "0.1.0"
dependencies = [
"async-trait",
"bytes",
"error-stack",
"fake",
"futures",
"hex",
"masking",
"nanoid",

View File

@ -130,6 +130,21 @@ pub enum MandateTxnType {
RecurringMandateTxn,
}
#[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct MandateIds {
pub mandate_id: String,
pub connector_mandate_id: Option<String>,
}
impl MandateIds {
pub fn new(mandate_id: String) -> Self {
Self {
mandate_id,
connector_mandate_id: None,
}
}
}
#[derive(Default, Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct MandateData {

View File

@ -8,8 +8,10 @@ readme = "README.md"
license = "Apache-2.0"
[dependencies]
async-trait = "0.1.59"
bytes = "1.3.0"
error-stack = "0.2.4"
futures = "0.3.25"
hex = "0.4.3"
nanoid = "0.4.0"
once_cell = "1.16.0"

View File

@ -277,3 +277,79 @@ impl<T> StringExt<T> for String {
.attach_printable_lazy(|| format!("Unable to parse {type_name} from string"))
}
}
///
/// Extending functionalities of Wrapper types for idiomatic
///
#[async_trait::async_trait]
pub trait AsyncExt<A, B> {
/// Output type of the map function
type WrappedSelf<T>;
///
/// Extending map by allowing functions which are async
///
async fn async_map<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = B> + Send;
///
/// Extending the `and_then` by allowing functions which are async
///
async fn async_and_then<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = Self::WrappedSelf<B>> + Send;
}
#[async_trait::async_trait]
impl<A: std::marker::Send, B, E: std::marker::Send> AsyncExt<A, B> for Result<A, E> {
type WrappedSelf<T> = Result<T, E>;
async fn async_and_then<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = Self::WrappedSelf<B>> + Send,
{
match self {
Ok(a) => func(a).await,
Err(err) => Err(err),
}
}
async fn async_map<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = B> + Send,
{
match self {
Ok(a) => Ok(func(a).await),
Err(err) => Err(err),
}
}
}
#[async_trait::async_trait]
impl<A: std::marker::Send, B> AsyncExt<A, B> for Option<A> {
type WrappedSelf<T> = Option<T>;
async fn async_and_then<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = Self::WrappedSelf<B>> + Send,
{
match self {
Some(a) => func(a).await,
None => None,
}
}
async fn async_map<F, Fut>(self, func: F) -> Self::WrappedSelf<B>
where
F: FnOnce(A) -> Fut + Send,
Fut: futures::Future<Output = B> + Send,
{
match self {
Some(a) => Some(func(a).await),
None => None,
}
}
}

View File

@ -90,7 +90,6 @@ pub struct PaymentIntentRequest {
#[derive(Debug, Eq, PartialEq, Serialize)]
pub struct SetupIntentRequest {
pub statement_descriptor_suffix: Option<String>,
#[serde(rename = "metadata[order_id]")]
pub metadata_order_id: String,
#[serde(rename = "metadata[txn_id]")]
@ -98,7 +97,7 @@ pub struct SetupIntentRequest {
#[serde(rename = "metadata[txn_uuid]")]
pub metadata_txn_uuid: String,
pub confirm: bool,
pub setup_future_usage: Option<enums::FutureUsage>,
pub usage: Option<enums::FutureUsage>,
pub off_session: Option<bool>,
#[serde(flatten)]
pub payment_data: StripePaymentMethodData,
@ -153,7 +152,12 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
// let api::PaymentMethod::Card(a) = item.payment_method_data;
let (payment_data, mandate) = {
match item.request.mandate_id.clone() {
match item
.request
.mandate_id
.clone()
.and_then(|mandate_ids| mandate_ids.connector_mandate_id)
{
None => (
Some(match item.request.payment_method_data {
api::PaymentMethod::Card(ref ccard) => StripePaymentMethodData::Card({
@ -257,9 +261,8 @@ impl TryFrom<&types::VerifyRouterData> for SetupIntentRequest {
metadata_txn_id,
metadata_txn_uuid,
payment_data,
statement_descriptor_suffix: item.request.statement_descriptor_suffix.clone(),
off_session: item.request.off_session,
setup_future_usage: item.request.setup_future_usage,
usage: item.request.setup_future_usage,
})
}
}

View File

@ -93,6 +93,7 @@ where
{
match resp.request.get_mandate_id() {
Some(mandate_id) => {
let mandate_id = &mandate_id.mandate_id;
let mandate = state
.store
.find_mandate_by_merchant_id_mandate_id(resp.merchant_id.as_ref(), mandate_id)
@ -157,7 +158,10 @@ where
mandate_reference,
) {
resp.request
.set_mandate_id(new_mandate_data.mandate_id.clone());
.set_mandate_id(api_models::payments::MandateIds {
mandate_id: new_mandate_data.mandate_id.clone(),
connector_mandate_id: new_mandate_data.connector_mandate_id.clone(),
});
state
.store
.insert_mandate(new_mandate_data)
@ -178,7 +182,7 @@ where
pub trait MandateBehaviour {
fn get_amount(&self) -> i64;
fn get_setup_future_usage(&self) -> Option<storage_models::enums::FutureUsage>;
fn get_mandate_id(&self) -> Option<&String>;
fn set_mandate_id(&mut self, new_mandate_id: String);
fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds>;
fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds);
fn get_payment_method_data(&self) -> api_models::payments::PaymentMethod;
}

View File

@ -5,6 +5,7 @@ pub mod transformers;
use std::{fmt::Debug, marker::PhantomData, time::Instant};
use common_utils::ext_traits::AsyncExt;
use error_stack::{IntoReport, ResultExt};
use futures::future::join_all;
use router_env::{tracing, tracing::instrument};
@ -61,6 +62,7 @@ where
// To perform router related operation for PaymentResponse
PaymentResponse: Operation<F, FData>,
FData: std::marker::Send,
{
let operation: BoxedOperation<F, Req> = Box::new(operation);
@ -181,6 +183,7 @@ pub async fn payments_core<F, Res, Req, Op, FData>(
) -> RouterResponse<Res>
where
F: Send + Clone,
FData: std::marker::Send,
Op: Operation<F, Req> + Send + Sync + Clone,
Req: Debug,
Res: transformers::ToResponse<Req, PaymentData<F>, Op> + From<Req>,
@ -194,7 +197,6 @@ where
// To perform router related operation for PaymentResponse
PaymentResponse: Operation<F, FData>,
// To create merchant response
{
let (payment_data, req, customer) = payments_operation_core(
state,
@ -313,7 +315,7 @@ where
// To create connector flow specific interface data
PaymentData<F>: ConstructFlowSpecificData<F, Req, types::PaymentsResponseData>,
types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req>,
types::RouterData<F, Req, types::PaymentsResponseData>: Feature<F, Req> + Send,
// To construct connector flow specific api
dyn api::Connector: services::api::ConnectorIntegration<F, Req, types::PaymentsResponseData>,
@ -339,7 +341,8 @@ where
)
.await;
let response = helpers::amap(res, |response| async {
let response = res
.async_and_then(|response| async {
let operation = helpers::response_operation::<F, Req>();
let payment_data = operation
.to_post_update_tracker()?
@ -347,7 +350,7 @@ where
db,
payment_id,
payment_data,
Some(response),
response,
merchant_account.storage_scheme,
)
.await?;
@ -457,8 +460,8 @@ where
pub payment_attempt: storage::PaymentAttempt,
pub connector_response: storage::ConnectorResponse,
pub amount: api::Amount,
pub mandate_id: Option<api_models::payments::MandateIds>,
pub currency: storage_enums::Currency,
pub mandate_id: Option<String>,
pub setup_mandate: Option<api::MandateData>,
pub address: PaymentAddress,
pub token: Option<String>,

View File

@ -111,7 +111,7 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData {
fn get_amount(&self) -> i64 {
self.amount
}
fn get_mandate_id(&self) -> Option<&String> {
fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds> {
self.mandate_id.as_ref()
}
fn get_payment_method_data(&self) -> api_models::payments::PaymentMethod {
@ -120,7 +120,7 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData {
fn get_setup_future_usage(&self) -> Option<storage_models::enums::FutureUsage> {
self.setup_future_usage
}
fn set_mandate_id(&mut self, new_mandate_id: String) {
fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds) {
self.mandate_id = Some(new_mandate_id);
}
}

View File

@ -98,11 +98,11 @@ impl mandate::MandateBehaviour for types::VerifyRequestData {
self.setup_future_usage
}
fn get_mandate_id(&self) -> Option<&String> {
fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds> {
self.mandate_id.as_ref()
}
fn set_mandate_id(&mut self, new_mandate_id: String) {
fn set_mandate_id(&mut self, new_mandate_id: api_models::payments::MandateIds) {
self.mandate_id = Some(new_mandate_id);
}

View File

@ -390,7 +390,7 @@ pub fn verify_mandate_details(
mandate
.mandate_currency
.map(|mandate_currency| mandate_currency.to_string() != request_currency)
.unwrap_or(true),
.unwrap_or(false),
Err(report!(errors::ApiErrorResponse::MandateValidationFailed {
reason: "cross currency mandates not supported".to_string()
})),
@ -471,17 +471,6 @@ where
Box::new(PaymentResponse)
}
pub async fn amap<A, B, E, F, Fut>(value: Result<A, E>, func: F) -> Result<B, E>
where
F: FnOnce(A) -> Fut,
Fut: futures::Future<Output = Result<B, E>>,
{
match value {
Ok(a) => func(a).await,
Err(err) => Err(err),
}
}
#[instrument(skip_all)]
pub(crate) async fn call_payment_method(
state: &AppState,

View File

@ -164,7 +164,7 @@ pub trait PostUpdateTracker<F, D, R>: Send {
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: D,
response: Option<types::RouterData<F, R, PaymentsResponseData>>,
response: types::RouterData<F, R, PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<D>
where

View File

@ -1,6 +1,7 @@
use std::marker::PhantomData;
use async_trait::async_trait;
use common_utils::ext_traits::AsyncExt;
use error_stack::ResultExt;
use masking::Secret;
use router_derive::PaymentOperation;
@ -182,6 +183,22 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
}
}?;
let mandate_id = request
.mandate_id
.as_ref()
.async_and_then(|mandate_id| async {
let mandate = db
.find_mandate_by_merchant_id_mandate_id(merchant_id, mandate_id)
.await
.change_context(errors::ApiErrorResponse::MandateNotFound);
Some(mandate.map(|mandate_obj| api_models::payments::MandateIds {
mandate_id: mandate_obj.mandate_id,
connector_mandate_id: mandate_obj.connector_mandate_id,
}))
})
.await
.transpose()?;
let operation = payments::if_not_create_change_operation::<_, F>(
is_update,
payment_intent.status,
@ -197,7 +214,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
payment_attempt,
currency,
amount,
mandate_id: request.mandate_id.clone(),
mandate_id,
setup_mandate,
token,
address: PaymentAddress {

View File

@ -1,5 +1,5 @@
use async_trait::async_trait;
use error_stack::{report, ResultExt};
use error_stack::ResultExt;
use router_derive;
use super::{Operation, PostUpdateTracker};
@ -34,26 +34,21 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthorizeData
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
mut payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::PaymentsAuthorizeData, types::PaymentsResponseData>,
router_data: types::RouterData<
F,
types::PaymentsAuthorizeData,
types::PaymentsResponseData,
>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
F: 'b + Send,
{
let router_data = response.ok_or(report!(errors::ApiErrorResponse::InternalServerError))?;
payment_data.mandate_id = payment_data
.mandate_id
.or_else(|| router_data.request.mandate_id.clone());
payment_response_update_tracker(
db,
payment_id,
payment_data,
Some(router_data),
storage_scheme,
)
payment_response_update_tracker(db, payment_id, payment_data, router_data, storage_scheme)
.await
}
}
@ -65,9 +60,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::PaymentsSyncData, types::PaymentsResponseData>,
>,
response: types::RouterData<F, types::PaymentsSyncData, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
@ -87,9 +80,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData>
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::PaymentsSessionData, types::PaymentsResponseData>,
>,
response: types::RouterData<F, types::PaymentsSessionData, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
@ -109,9 +100,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData>
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::PaymentsCaptureData, types::PaymentsResponseData>,
>,
response: types::RouterData<F, types::PaymentsCaptureData, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
@ -129,9 +118,8 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::PaymentsCancelData, types::PaymentsResponseData>,
>,
response: types::RouterData<F, types::PaymentsCancelData, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
@ -148,16 +136,20 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::VerifyRequestData> fo
&'b self,
db: &dyn StorageInterface,
payment_id: &api::PaymentIdType,
payment_data: PaymentData<F>,
response: Option<
types::RouterData<F, types::VerifyRequestData, types::PaymentsResponseData>,
>,
mut payment_data: PaymentData<F>,
router_data: types::RouterData<F, types::VerifyRequestData, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>>
where
F: 'b + Send,
{
payment_response_update_tracker(db, payment_id, payment_data, response, storage_scheme)
payment_data.mandate_id = payment_data.mandate_id.or_else(|| {
router_data.request.mandate_id.clone()
// .map(api_models::payments::MandateIds::new)
});
payment_response_update_tracker(db, payment_id, payment_data, router_data, storage_scheme)
.await
}
}
@ -166,11 +158,9 @@ async fn payment_response_update_tracker<F: Clone, T>(
db: &dyn StorageInterface,
_payment_id: &api::PaymentIdType,
mut payment_data: PaymentData<F>,
response: Option<types::RouterData<F, T, types::PaymentsResponseData>>,
router_data: types::RouterData<F, T, types::PaymentsResponseData>,
storage_scheme: enums::MerchantStorageScheme,
) -> RouterResult<PaymentData<F>> {
let router_data = response.ok_or(report!(errors::ApiErrorResponse::InternalServerError))?;
let (payment_attempt_update, connector_response_update) = match router_data.response.clone() {
Err(err) => (
Some(storage::PaymentAttemptUpdate::ErrorUpdate {
@ -212,7 +202,10 @@ async fn payment_response_update_tracker<F: Clone, T>(
authentication_type: None,
payment_method_id: Some(router_data.payment_method_id),
redirect: Some(redirect),
mandate_id: payment_data.mandate_id.clone(),
mandate_id: payment_data
.mandate_id
.clone()
.map(|mandate| mandate.mandate_id),
};
let connector_response_update = storage::ConnectorResponseUpdate::ResponseUpdate {

View File

@ -1,6 +1,7 @@
use std::marker::PhantomData;
use async_trait::async_trait;
use common_utils::ext_traits::AsyncExt;
use error_stack::{report, ResultExt};
use masking::Secret;
use router_derive::PaymentOperation;
@ -126,6 +127,21 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.attach_printable("Database error when finding connector response")
})?;
let mandate_id = request
.mandate_id
.as_ref()
.async_and_then(|mandate_id| async {
let mandate = db
.find_mandate_by_merchant_id_mandate_id(merchant_id, mandate_id)
.await
.change_context(errors::ApiErrorResponse::MandateNotFound);
Some(mandate.map(|mandate_obj| api_models::payments::MandateIds {
mandate_id: mandate_obj.mandate_id,
connector_mandate_id: mandate_obj.connector_mandate_id,
}))
})
.await
.transpose()?;
let next_operation: BoxedOperation<'a, F, api::PaymentsRequest> =
if request.confirm.unwrap_or(false) {
Box::new(operations::PaymentConfirm)
@ -149,7 +165,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
payment_attempt,
currency,
amount,
mandate_id: request.mandate_id.clone(),
mandate_id,
token,
setup_mandate,
address: PaymentAddress {

View File

@ -197,7 +197,7 @@ where
phone: customer
.as_ref()
.and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())),
mandate_id: data.mandate_id,
mandate_id: data.mandate_id.map(|mandate_ids| mandate_ids.mandate_id),
payment_method: data
.payment_attempt
.payment_method
@ -499,8 +499,8 @@ impl<F: Clone> TryFrom<PaymentData<F>> for types::VerifyRequestData {
},
statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix,
setup_future_usage: payment_data.payment_intent.setup_future_usage,
mandate_id: payment_data.mandate_id.clone(),
off_session: payment_data.mandate_id.as_ref().map(|_| true),
mandate_id: payment_data.mandate_id.clone(),
setup_mandate_details: payment_data.setup_mandate,
})
}

View File

@ -97,7 +97,7 @@ pub struct PaymentsAuthorizeData {
pub capture_method: Option<storage_enums::CaptureMethod>,
// Mandates
pub setup_future_usage: Option<storage_enums::FutureUsage>,
pub mandate_id: Option<String>,
pub mandate_id: Option<api_models::payments::MandateIds>,
pub off_session: Option<bool>,
pub setup_mandate_details: Option<payments::MandateData>,
pub browser_info: Option<BrowserInformation>,
@ -134,7 +134,7 @@ pub struct VerifyRequestData {
pub payment_method_data: payments::PaymentMethod,
pub confirm: bool,
pub statement_descriptor_suffix: Option<String>,
pub mandate_id: Option<String>,
pub mandate_id: Option<api_models::payments::MandateIds>,
pub setup_future_usage: Option<storage_enums::FutureUsage>,
pub off_session: Option<bool>,
pub setup_mandate_details: Option<payments::MandateData>,