feat(core): accept gateway credentials in the request body in payments and refunds (#766)

This commit is contained in:
Abhishek
2023-03-21 14:37:41 +05:30
committed by GitHub
parent d302b286b8
commit cb188f92a6
35 changed files with 748 additions and 130 deletions

View File

@ -413,3 +413,31 @@ pub struct ToggleKVRequest {
#[schema(example = true)]
pub kv_enabled: bool,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct MerchantConnectorDetailsWrap {
/// Creds Identifier is to uniquely identify the credentials. Do not send any sensitive info in this field. And do not send the string "null".
pub creds_identifier: String,
/// Merchant connector details type type. Base64 Encode the credentials and send it in this type and send as a string.
#[schema(value_type = Option<MerchantConnectorDetails>, example = r#"{
"connector_account_details": {
"auth_type": "HeaderKey",
"api_key":"sk_test_xxxxxexamplexxxxxx12345"
},
"metadata": {
"user_defined_field_1": "sample_1",
"user_defined_field_2": "sample_2",
},
}"#)]
pub encoded_data: Option<Secret<String>>,
}
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
pub struct MerchantConnectorDetails {
/// Account details of the Connector. You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>,example = json!({ "auth_type": "HeaderKey","api_key": "Basic MyVerySecretApiKey" }))]
pub connector_account_details: pii::SecretSerdeValue,
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>,max_length = 255,example = json!({ "city": "NY", "unit": "245" }))]
pub metadata: Option<pii::SecretSerdeValue>,
}

View File

@ -6,7 +6,7 @@ use router_derive::Setter;
use time::PrimitiveDateTime;
use utoipa::ToSchema;
use crate::{enums as api_enums, refunds};
use crate::{admin, enums as api_enums, refunds};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PaymentOp {
@ -193,6 +193,9 @@ pub struct PaymentsRequest {
/// Payment Method Type
#[schema(value_type = Option<PaymentMethodType>, example = "google_pay")]
pub payment_method_type: Option<api_enums::PaymentMethodType>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
@ -244,6 +247,7 @@ pub struct VerifyRequest {
pub setup_future_usage: Option<api_enums::FutureUsage>,
pub off_session: Option<bool>,
pub client_secret: Option<String>,
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
impl From<PaymentsRequest> for VerifyRequest {
@ -262,6 +266,7 @@ impl From<PaymentsRequest> for VerifyRequest {
mandate_data: item.mandate_data,
setup_future_usage: item.setup_future_usage,
off_session: item.off_session,
merchant_connector_details: item.merchant_connector_details,
}
}
}
@ -625,19 +630,6 @@ impl Default for PaymentIdType {
}
}
//#[derive(Debug, serde::Deserialize, serde::Serialize)]
//#[serde(untagged)]
//pub enum enums::CaptureMethod {
//Automatic,
//Manual,
//}
//impl Default for enums::CaptureMethod {
//fn default() -> Self {
//enums::CaptureMethod::Manual
//}
//}
#[derive(
Default,
Clone,
@ -742,6 +734,8 @@ pub struct PaymentsCaptureRequest {
pub statement_descriptor_suffix: Option<String>,
/// Concatenated with the statement descriptor suffix thats set on the account to form the complete statement descriptor.
pub statement_descriptor_prefix: Option<String>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Serialize)]
@ -1165,6 +1159,8 @@ pub struct PaymentsRetrieveRequest {
pub param: Option<String>,
/// The name of the connector
pub connector: Option<String>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
@ -1196,6 +1192,8 @@ pub struct PaymentsSessionRequest {
/// The list of the supported wallets
#[schema(value_type = Vec<SupportedWallets>)]
pub wallets: Vec<api_enums::SupportedWallets>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)]
@ -1383,6 +1381,19 @@ pub struct PaymentRetrieveBody {
/// Decider to enable or disable the connector call for retrieve request
pub force_sync: Option<bool>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PaymentRetrieveBodyWithCredentials {
/// The identifier for payment.
pub payment_id: String,
/// The identifier for the Merchant Account.
pub merchant_id: Option<String>,
/// Decider to enable or disable the connector call for retrieve request
pub force_sync: Option<bool>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PaymentsCancelRequest {
/// The identifier for the payment
@ -1390,6 +1401,8 @@ pub struct PaymentsCancelRequest {
pub payment_id: String,
/// The reason for the payment cancel
pub cancellation_reason: Option<String>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, ToSchema)]

View File

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;
use crate::enums;
use crate::{admin, enums};
#[derive(Default, Debug, ToSchema, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
@ -43,6 +43,23 @@ pub struct RefundRequest {
/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>, example = r#"{ "city": "NY", "unit": "245" }"#)]
pub metadata: Option<pii::SecretSerdeValue>,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Debug, ToSchema, Clone, Deserialize)]
pub struct RefundsRetrieveRequest {
/// Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refund initiated against the same payment. If the identifiers is not defined by the merchant, this filed shall be auto generated and provide in the API response. It is recommended to generate uuid(v4) as the refund_id.
#[schema(
max_length = 30,
min_length = 30,
example = "ref_mbabizu24mvu3mela5njyhpit4"
)]
pub refund_id: String,
/// Merchant connector details used to make payments.
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Default, Debug, ToSchema, Clone, Deserialize)]

View File

@ -9,6 +9,7 @@ impl PaymentIntents {
pub fn server(state: routes::AppState) -> Scope {
web::scope("/payment_intents")
.app_data(web::Data::new(state))
.service(payment_intents_retrieve_with_gateway_creds)
.service(payment_intents_create)
.service(payment_intents_retrieve)
.service(payment_intents_update)

View File

@ -75,6 +75,7 @@ pub async fn payment_intents_retrieve(
force_sync: true,
connector: None,
param: None,
merchant_connector_details: None,
};
let (auth_type, auth_flow) = match auth::get_auth_type_and_flow(req.headers()) {
@ -110,6 +111,64 @@ pub async fn payment_intents_retrieve(
.await
}
#[instrument(skip_all)]
#[post("/sync")]
pub async fn payment_intents_retrieve_with_gateway_creds(
state: web::Data<routes::AppState>,
qs_config: web::Data<serde_qs::Config>,
req: HttpRequest,
form_payload: web::Bytes,
) -> HttpResponse {
let json_payload: payment_types::PaymentRetrieveBodyWithCredentials = match qs_config
.deserialize_bytes(&form_payload)
.map_err(|err| report!(errors::StripeErrorCode::from(err)))
{
Ok(p) => p,
Err(err) => return api::log_and_return_error_response(err),
};
let payload = payment_types::PaymentsRetrieveRequest {
resource_id: payment_types::PaymentIdType::PaymentIntentId(
json_payload.payment_id.to_string(),
),
merchant_id: json_payload.merchant_id.clone(),
force_sync: json_payload.force_sync.unwrap_or(false),
merchant_connector_details: json_payload.merchant_connector_details.clone(),
..Default::default()
};
let (auth_type, _auth_flow) = match auth::get_auth_type_and_flow(req.headers()) {
Ok(auth) => auth,
Err(err) => return api::log_and_return_error_response(report!(err)),
};
wrap::compatibility_api_wrap::<
_,
_,
_,
_,
_,
_,
types::StripePaymentIntentResponse,
errors::StripeErrorCode,
>(
state.get_ref(),
&req,
payload,
|state, merchant_account, req| {
payments::payments_core::<api_types::PSync, payment_types::PaymentsResponse, _, _, _>(
state,
merchant_account,
payments::PaymentStatus,
req,
api::AuthFlow::Merchant,
payments::CallConnectorAction::Trigger,
)
},
&*auth_type,
)
.await
}
#[instrument(skip_all)]
#[post("/{payment_id}")]
pub async fn payment_intents_update(

View File

@ -1,13 +1,14 @@
use api_models::{payments, refunds};
use api_models::payments;
use common_utils::{ext_traits::StringExt, pii as secret};
use error_stack::ResultExt;
use serde::{Deserialize, Serialize};
use crate::{
compatibility::stripe::refunds::types as stripe_refunds,
core::errors,
pii::{self, PeekInterface},
types::{
api::enums as api_enums,
api::{admin, enums as api_enums},
transformers::{ForeignFrom, ForeignInto},
},
};
@ -113,8 +114,10 @@ impl From<Shipping> for payments::Address {
}
}
}
#[derive(PartialEq, Eq, Deserialize, Clone)]
#[derive(Deserialize, Clone)]
pub struct StripePaymentIntentRequest {
pub id: Option<String>,
pub amount: Option<i64>, //amount in cents, hence passed as integer
pub connector: Option<Vec<api_enums::Connector>>,
pub currency: Option<String>,
@ -129,18 +132,19 @@ pub struct StripePaymentIntentRequest {
pub return_url: Option<url::Url>,
pub setup_future_usage: Option<api_enums::FutureUsage>,
pub shipping: Option<Shipping>,
pub billing_details: Option<StripeBillingDetails>,
pub statement_descriptor: Option<String>,
pub statement_descriptor_suffix: Option<String>,
pub metadata: Option<api_models::payments::Metadata>,
pub client_secret: Option<pii::Secret<String>>,
pub payment_method_options: Option<StripePaymentMethodOptions>,
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn try_from(item: StripePaymentIntentRequest) -> errors::RouterResult<Self> {
Ok(Self {
payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId),
amount: item.amount.map(|amount| amount.into()),
connector: item.connector,
currency: item
@ -156,10 +160,6 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
confirm: item.confirm,
customer_id: item.customer,
email: item.receipt_email,
name: item
.billing_details
.as_ref()
.and_then(|b| b.name.as_ref().map(|x| masking::Secret::new(x.to_owned()))),
phone: item.shipping.as_ref().and_then(|s| s.phone.clone()),
description: item.description,
return_url: item.return_url,
@ -177,9 +177,8 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
.as_ref()
.map(|s| payments::Address::from(s.to_owned())),
billing: item
.billing_details
.as_ref()
.map(|b| payments::Address::from(b.to_owned())),
.payment_method_data
.and_then(|pmd| pmd.billing_details.map(payments::Address::from)),
statement_descriptor_name: item.statement_descriptor,
statement_descriptor_suffix: item.statement_descriptor_suffix,
metadata: item.metadata,
@ -191,6 +190,7 @@ impl TryFrom<StripePaymentIntentRequest> for payments::PaymentsRequest {
request_three_d_secure.foreign_into()
}),
merchant_connector_details: item.merchant_connector_details,
..Self::default()
})
}
@ -274,7 +274,7 @@ pub struct StripePaymentIntentResponse {
pub client_secret: Option<masking::Secret<String>>,
pub created: Option<i64>,
pub customer: Option<String>,
pub refunds: Option<Vec<refunds::RefundResponse>>,
pub refunds: Option<Vec<stripe_refunds::StripeRefundResponse>>,
pub mandate_id: Option<String>,
pub metadata: Option<secret::SecretSerdeValue>,
pub charges: Charges,
@ -283,7 +283,6 @@ pub struct StripePaymentIntentResponse {
pub mandate_data: Option<payments::MandateData>,
pub setup_future_usage: Option<api_models::enums::FutureUsage>,
pub off_session: Option<bool>,
pub authentication_type: Option<api_models::enums::AuthenticationType>,
pub next_action: Option<StripeNextAction>,
pub cancellation_reason: Option<String>,
@ -319,7 +318,9 @@ impl From<payments::PaymentsResponse> for StripePaymentIntentResponse {
currency: resp.currency.to_lowercase(),
customer: resp.customer_id,
description: resp.description,
refunds: resp.refunds,
refunds: resp
.refunds
.map(|a| a.into_iter().map(Into::into).collect()),
mandate_id: resp.mandate_id,
mandate_data: resp.mandate_data,
setup_future_usage: resp.setup_future_usage,

View File

@ -1,13 +1,14 @@
pub mod types;
use actix_web::{get, post, web, HttpRequest, HttpResponse};
use error_stack::report;
use router_env::{instrument, tracing};
use crate::{
compatibility::{stripe::errors, wrap},
core::refunds,
routes,
services::authentication as auth,
services::{api, authentication as auth},
types::api::refunds as refund_types,
};
@ -15,10 +16,18 @@ use crate::{
#[post("")]
pub async fn refund_create(
state: web::Data<routes::AppState>,
qs_config: web::Data<serde_qs::Config>,
req: HttpRequest,
form_payload: web::Form<types::StripeCreateRefundRequest>,
form_payload: web::Bytes,
) -> HttpResponse {
let payload = form_payload.into_inner();
let payload: types::StripeCreateRefundRequest = match qs_config
.deserialize_bytes(&form_payload)
.map_err(|err| report!(errors::StripeErrorCode::from(err)))
{
Ok(p) => p,
Err(err) => return api::log_and_return_error_response(err),
};
let create_refund_req: refund_types::RefundRequest = payload.into();
wrap::compatibility_api_wrap::<
@ -28,7 +37,7 @@ pub async fn refund_create(
_,
_,
_,
types::StripeCreateRefundResponse,
types::StripeRefundResponse,
errors::StripeErrorCode,
>(
state.get_ref(),
@ -47,7 +56,10 @@ pub async fn refund_retrieve(
req: HttpRequest,
path: web::Path<String>,
) -> HttpResponse {
let refund_id = path.into_inner();
let refund_request = refund_types::RefundsRetrieveRequest {
refund_id: path.into_inner(),
merchant_connector_details: None,
};
wrap::compatibility_api_wrap::<
_,
_,
@ -55,17 +67,17 @@ pub async fn refund_retrieve(
_,
_,
_,
types::StripeCreateRefundResponse,
types::StripeRefundResponse,
errors::StripeErrorCode,
>(
state.get_ref(),
&req,
refund_id,
|state, merchant_account, refund_id| {
refund_request,
|state, merchant_account, refund_request| {
refunds::refund_response_wrapper(
state,
merchant_account,
refund_id,
refund_request,
refunds::refund_retrieve_core,
)
},
@ -93,7 +105,7 @@ pub async fn refund_update(
_,
_,
_,
types::StripeCreateRefundResponse,
types::StripeRefundResponse,
errors::StripeErrorCode,
>(
state.get_ref(),

View File

@ -3,13 +3,16 @@ use std::{convert::From, default::Default};
use common_utils::pii;
use serde::{Deserialize, Serialize};
use crate::types::api::refunds;
use crate::types::api::{admin, refunds};
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct StripeCreateRefundRequest {
pub refund_id: Option<String>,
pub amount: Option<i64>,
pub payment_intent: String,
pub reason: Option<String>,
pub metadata: Option<pii::SecretSerdeValue>,
pub merchant_connector_details: Option<admin::MerchantConnectorDetailsWrap>,
}
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
@ -18,7 +21,7 @@ pub struct StripeUpdateRefundRequest {
}
#[derive(Clone, Serialize, PartialEq, Eq)]
pub struct StripeCreateRefundResponse {
pub struct StripeRefundResponse {
pub id: String,
pub amount: i64,
pub currency: String,
@ -40,10 +43,13 @@ pub enum StripeRefundStatus {
impl From<StripeCreateRefundRequest> for refunds::RefundRequest {
fn from(req: StripeCreateRefundRequest) -> Self {
Self {
refund_id: req.refund_id,
amount: req.amount,
payment_id: req.payment_intent,
reason: req.reason,
refund_type: Some(refunds::RefundType::Instant),
metadata: req.metadata,
merchant_connector_details: req.merchant_connector_details,
..Default::default()
}
}
@ -69,7 +75,7 @@ impl From<refunds::RefundStatus> for StripeRefundStatus {
}
}
impl From<refunds::RefundResponse> for StripeCreateRefundResponse {
impl From<refunds::RefundResponse> for StripeRefundResponse {
fn from(res: refunds::RefundResponse) -> Self {
Self {
id: res.refund_id,

View File

@ -72,6 +72,7 @@ pub async fn setup_intents_retrieve(
force_sync: true,
connector: None,
param: None,
merchant_connector_details: None,
};
let (auth_type, auth_flow) = match auth::get_auth_type_and_flow(req.headers()) {

View File

@ -2,7 +2,7 @@ use api_models::webhooks::{self as api};
use serde::Serialize;
use super::{
payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeCreateRefundResponse,
payment_intents::types::StripePaymentIntentResponse, refunds::types::StripeRefundResponse,
};
#[derive(Serialize)]
@ -19,7 +19,7 @@ impl api::OutgoingWebhookType for StripeOutgoingWebhook {}
#[serde(tag = "type", content = "object", rename_all = "snake_case")]
pub enum StripeWebhookObject {
PaymentIntent(StripePaymentIntentResponse),
Refund(StripeCreateRefundResponse),
Refund(StripeRefundResponse),
}
impl From<api::OutgoingWebhook> for StripeOutgoingWebhook {

View File

@ -125,7 +125,7 @@ impl ConnectorCommon for Fiserv {
code: first_error
.code
.to_owned()
.unwrap_or(consts::NO_ERROR_CODE.to_string()),
.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()),
message: first_error.message.to_owned(),
reason: first_error.field.to_owned(),
status_code: res.status_code,

View File

@ -531,7 +531,7 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
.request
.connector_refund_id
.to_owned()
.ok_or_else(|| errors::ConnectorError::MissingConnectorRefundID)?;
.ok_or(errors::ConnectorError::MissingConnectorRefundID)?;
match req.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => Ok(format!(
"{}{}/{}",

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use api_models::payments::BankRedirectData;
use common_utils::{errors::CustomResult, pii::Email};
use error_stack::ResultExt;
use error_stack::{IntoReport, ResultExt};
use masking::Secret;
use reqwest::Url;
use serde::{Deserialize, Serialize};
@ -297,7 +297,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TrustpayPaymentsRequest {
utils::to_currency_base_unit(item.request.amount, item.request.currency)?
.parse::<f64>()
.ok()
.ok_or_else(|| errors::ConnectorError::RequestEncodingFailed)?
.ok_or(errors::ConnectorError::RequestEncodingFailed)?
);
let auth = TrustpayAuthType::try_from(&item.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
@ -556,9 +556,7 @@ fn handle_cards_response(
response.payment_status.as_str(),
response.redirect_url.clone(),
)?;
let form_fields = response
.redirect_params
.unwrap_or(std::collections::HashMap::new());
let form_fields = response.redirect_params.unwrap_or_default();
let redirection_data = response.redirect_url.map(|url| services::RedirectForm {
endpoint: url.to_string(),
method: services::Method::Post,
@ -567,7 +565,7 @@ fn handle_cards_response(
let error = if msg.is_some() {
Some(types::ErrorResponse {
code: response.payment_status,
message: msg.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
message: msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code,
})
@ -626,7 +624,7 @@ fn handle_bank_redirects_error_response(
message: response
.payment_result_info
.additional_info
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code,
});
@ -661,7 +659,7 @@ fn handle_bank_redirects_sync_response(
message: reason_info
.reason
.reject_reason
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code,
})
@ -786,7 +784,7 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, TrustpayAuthUpdateResponse, T, t
.response
.result_info
.additional_info
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code: item.http_code,
}),
@ -826,8 +824,8 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for TrustpayRefundRequest {
"{:.2}",
utils::to_currency_base_unit(item.request.amount, item.request.currency)?
.parse::<f64>()
.ok()
.ok_or_else(|| errors::ConnectorError::RequestEncodingFailed)?
.into_report()
.change_context(errors::ConnectorError::RequestEncodingFailed)?
);
match item.payment_method {
storage_models::enums::PaymentMethod::BankRedirect => {
@ -896,7 +894,7 @@ fn handle_cards_refund_response(
let error = if msg.is_some() {
Some(types::ErrorResponse {
code: response.payment_status,
message: msg.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
message: msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code,
})
@ -962,7 +960,7 @@ fn handle_bank_redirects_refund_sync_response(
message: reason_info
.reason
.reject_reason
.unwrap_or(consts::NO_ERROR_MESSAGE.to_string()),
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()),
reason: None,
status_code,
})
@ -985,7 +983,7 @@ fn handle_bank_redirects_refund_sync_error_response(
message: response
.payment_result_info
.additional_info
.unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()),
.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_owned()),
reason: None,
status_code,
});

View File

@ -259,6 +259,7 @@ pub struct PaymentsRedirectResponseData {
pub json_payload: Option<serde_json::Value>,
pub resource_id: api::PaymentIdType,
pub force_sync: bool,
pub creds_identifier: Option<String>,
}
#[async_trait::async_trait]
@ -379,6 +380,12 @@ impl PaymentRedirectFlow for PaymentRedirectSync {
param: req.param,
force_sync: req.force_sync,
connector: req.connector,
merchant_connector_details: req.creds_identifier.map(|creds_id| {
api::MerchantConnectorDetailsWrap {
creds_identifier: creds_id,
encoded_data: None,
}
}),
};
payments_core::<api::PSync, api::PaymentsResponse, _, _, _>(
state,
@ -588,6 +595,7 @@ where
pub sessions_token: Vec<api::SessionToken>,
pub card_cvc: Option<pii::Secret<String>>,
pub email: Option<masking::Secret<String, pii::Email>>,
pub creds_identifier: Option<String>,
}
#[derive(Debug, Default)]
@ -734,10 +742,8 @@ pub async fn add_process_sync_task(
let tracking_data = api::PaymentsRetrieveRequest {
force_sync: true,
merchant_id: Some(payment_attempt.merchant_id.clone()),
resource_id: api::PaymentIdType::PaymentAttemptId(payment_attempt.attempt_id.clone()),
param: None,
connector: None,
..Default::default()
};
let runner = "PAYMENTS_SYNC_WORKFLOW";
let task = "PAYMENTS_SYNC";

View File

@ -1,9 +1,13 @@
use std::borrow::Cow;
use common_utils::{ext_traits::AsyncExt, fp_utils};
use base64::Engine;
use common_utils::{
ext_traits::{AsyncExt, ByteSliceExt},
fp_utils,
};
// TODO : Evaluate all the helper functions ()
use error_stack::{report, IntoReport, ResultExt};
use masking::ExposeOptionInterface;
use masking::{ExposeOptionInterface, PeekInterface};
use router_env::{instrument, tracing};
use storage_models::enums;
use uuid::Uuid;
@ -24,7 +28,7 @@ use crate::{
scheduler::{metrics as scheduler_metrics, workflows::payment_sync},
services,
types::{
api::{self, enums as api_enums, CustomerAcceptanceExt, MandateValidationFieldsExt},
api::{self, admin, enums as api_enums, CustomerAcceptanceExt, MandateValidationFieldsExt},
storage::{self, enums as storage_enums, ephemeral_key},
transformers::ForeignInto,
},
@ -345,12 +349,14 @@ pub fn create_startpay_url(
pub fn create_redirect_url(
server: &Server,
payment_attempt: &storage::PaymentAttempt,
connector_name: &String,
connector_name: &str,
creds_identifier: Option<&str>,
) -> String {
let creds_identifier_path = creds_identifier.map_or_else(String::new, |cd| format!("/{}", cd));
format!(
"{}/payments/{}/{}/response/{}",
server.base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name
)
server.base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name,
) + &creds_identifier_path
}
pub fn create_complete_authorize_url(
server: &Server,
@ -1262,3 +1268,96 @@ mod tests {
assert!(authenticate_client_secret(req_cs.as_ref(), pi_cs.as_ref()).is_err())
}
}
// This function will be removed after moving this functionality to server_wrap and using cache instead of config
pub async fn insert_merchant_connector_creds_to_config(
db: &dyn StorageInterface,
merchant_id: &str,
merchant_connector_details: admin::MerchantConnectorDetailsWrap,
) -> RouterResult<()> {
if let Some(encoded_data) = merchant_connector_details.encoded_data {
match db
.insert_config(storage::ConfigNew {
key: format!(
"mcd_{merchant_id}_{}",
merchant_connector_details.creds_identifier
),
config: encoded_data.peek().to_owned(),
})
.await
{
Ok(_) => Ok(()),
Err(err) => {
if err.current_context().is_db_unique_violation() {
Ok(())
} else {
Err(err
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to insert connector_creds to config"))
}
}
}
} else {
Ok(())
}
}
pub enum MerchantConnectorAccountType {
DbVal(storage::MerchantConnectorAccount),
CacheVal(api_models::admin::MerchantConnectorDetails),
}
impl MerchantConnectorAccountType {
pub fn get_metadata(&self) -> Option<masking::Secret<serde_json::Value>> {
match self {
Self::DbVal(val) => val.metadata.to_owned(),
Self::CacheVal(val) => val.metadata.to_owned(),
}
}
pub fn get_connector_account_details(&self) -> serde_json::Value {
match self {
Self::DbVal(val) => val.connector_account_details.to_owned(),
Self::CacheVal(val) => val.connector_account_details.peek().to_owned(),
}
}
}
pub async fn get_merchant_connector_account(
db: &dyn StorageInterface,
merchant_id: &str,
connector_id: &str,
creds_identifier: Option<String>,
) -> RouterResult<MerchantConnectorAccountType> {
match creds_identifier {
Some(creds_identifier) => {
let mca_config = db
.find_config_by_key(format!("mcd_{merchant_id}_{creds_identifier}").as_str())
.await
.map_err(|error| {
error.to_not_found_response(
errors::ApiErrorResponse::MerchantConnectorAccountNotFound,
)
})?;
let cached_mca = consts::BASE64_ENGINE
.decode(mca_config.config.as_bytes())
.into_report()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed to decode merchant_connector_details sent in request and then put in cache",
)?
.parse_struct("MerchantConnectorDetails")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"Failed to parse merchant_connector_details sent in request and then put in cache",
)?;
Ok(MerchantConnectorAccountType::CacheVal(cached_mca))
}
None => db
.find_merchant_connector_account_by_merchant_id_connector(merchant_id, connector_id)
.await
.map(MerchantConnectorAccountType::DbVal)
.change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound),
}
}

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 router_derive;
use router_env::{instrument, tracing};
@ -98,6 +99,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
payment_attempt.cancellation_reason = request.cancellation_reason.clone();
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
match payment_intent.status {
status if status != enums::IntentStatus::RequiresCapture => {
Err(errors::ApiErrorResponse::InvalidRequestData {
@ -129,6 +148,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
connector_response,
sessions_token: vec![],
card_cvc: None,
creds_identifier,
},
None,
)),

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 router_env::{instrument, tracing};
@ -116,6 +117,24 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
)
.await?;
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
Box::new(self),
payments::PaymentData {
@ -139,6 +158,7 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
connector_response,
sessions_token: vec![],
card_cvc: None,
creds_identifier,
},
None,
))

View File

@ -192,6 +192,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
refunds: vec![],
sessions_token: vec![],
card_cvc: request.card_cvc.clone(),
creds_identifier: None,
},
Some(CustomerDetails {
customer_id: request.customer_id.clone(),

View File

@ -1,7 +1,7 @@
use std::marker::PhantomData;
use async_trait::async_trait;
use common_utils::ext_traits::Encode;
use common_utils::ext_traits::{AsyncExt, Encode};
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@ -173,6 +173,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
payment_intent.billing_address_id = billing_address.clone().map(|i| i.address_id);
payment_intent.return_url = request.return_url.as_ref().map(|a| a.to_string());
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
Box::new(self),
PaymentData {
@ -196,6 +214,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
refunds: vec![],
sessions_token: vec![],
card_cvc: request.card_cvc.clone(),
creds_identifier,
},
Some(CustomerDetails {
customer_id: request.customer_id.clone(),

View File

@ -169,6 +169,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
self,
);
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
operation,
PaymentData {
@ -192,6 +210,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
connector_response,
sessions_token: vec![],
card_cvc: request.card_cvc.clone(),
creds_identifier,
},
Some(CustomerDetails {
customer_id: request.customer_id.clone(),

View File

@ -1,7 +1,7 @@
use std::marker::PhantomData;
use async_trait::async_trait;
use common_utils::{date_time, errors::CustomResult};
use common_utils::{date_time, errors::CustomResult, ext_traits::AsyncExt};
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@ -74,7 +74,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
PaymentData<F>,
Option<payments::CustomerDetails>,
)> {
let db = &state.store;
let db = &*state.store;
let merchant_id = &merchant_account.merchant_id;
let storage_scheme = merchant_account.storage_scheme;
@ -130,6 +130,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
}
}?;
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
Box::new(self),
PaymentData {
@ -151,6 +169,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
refunds: vec![],
sessions_token: vec![],
card_cvc: None,
creds_identifier,
},
Some(payments::CustomerDetails {
customer_id: request.customer_id.clone(),

View File

@ -2,7 +2,7 @@ use std::{collections::HashSet, marker::PhantomData};
use api_models::admin::PaymentMethodsEnabled;
use async_trait::async_trait;
use common_utils::ext_traits::ValueExt;
use common_utils::ext_traits::{AsyncExt, ValueExt};
use error_stack::ResultExt;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@ -135,6 +135,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
phone_country_code: None,
};
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
Box::new(self),
PaymentData {
@ -158,6 +176,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
sessions_token: vec![],
connector_response,
card_cvc: None,
creds_identifier,
},
Some(customer_details),
))

View File

@ -143,6 +143,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
refunds: vec![],
sessions_token: vec![],
card_cvc: None,
creds_identifier: None,
},
Some(customer_details),
))

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 router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@ -245,6 +246,18 @@ async fn get_tracker_for_sync<
let contains_encoded_data = connector_response.encoded_data.is_some();
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await
})
.await
.transpose()?;
Ok((
Box::new(operation),
PaymentData {
@ -274,6 +287,7 @@ async fn get_tracker_for_sync<
refunds,
sessions_token: vec![],
card_cvc: None,
creds_identifier,
},
None,
))

View File

@ -210,6 +210,24 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
.payment_experience
.map(|experience| experience.foreign_into());
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
helpers::insert_merchant_connector_creds_to_config(
db,
merchant_account.merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
Ok((
next_operation,
PaymentData {
@ -233,6 +251,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
connector_response,
sessions_token: vec![],
card_cvc: request.card_cvc.clone(),
creds_identifier,
},
Some(CustomerDetails {
customer_id: request.customer_id.clone(),

View File

@ -7,7 +7,7 @@ use super::{flows::Feature, PaymentAddress, PaymentData};
use crate::{
configs::settings::Server,
core::{
errors::{self, RouterResponse, RouterResult, StorageErrorExt},
errors::{self, RouterResponse, RouterResult},
payments::{self, helpers},
},
routes::AppState,
@ -35,18 +35,16 @@ where
{
let (merchant_connector_account, payment_method, router_data);
let db = &*state.store;
merchant_connector_account = db
.find_merchant_connector_account_by_merchant_id_connector(
&merchant_account.merchant_id,
merchant_connector_account = helpers::get_merchant_connector_account(
db,
merchant_account.merchant_id.as_str(),
connector_id,
payment_data.creds_identifier.to_owned(),
)
.await
.map_err(|error| {
error.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound)
})?;
.await?;
let auth_type: types::ConnectorAuthType = merchant_connector_account
.connector_account_details
.get_connector_account_details()
.parse_value("ConnectorAuthType")
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while parsing value for ConnectorAuthType")?;
@ -72,18 +70,19 @@ where
let router_return_url = Some(helpers::create_redirect_url(
&state.conf.server,
&payment_data.payment_attempt,
&merchant_connector_account.connector_name,
connector_id,
payment_data.creds_identifier.as_deref(),
));
let complete_authorize_url = Some(helpers::create_complete_authorize_url(
&state.conf.server,
&payment_data.payment_attempt,
&merchant_connector_account.connector_name,
&connector_id.to_owned(),
));
router_data = types::RouterData {
flow: PhantomData,
merchant_id: merchant_account.merchant_id.clone(),
connector: merchant_connector_account.connector_name,
connector: connector_id.to_owned(),
payment_id: payment_data.payment_attempt.payment_id.clone(),
attempt_id: payment_data.payment_attempt.attempt_id.clone(),
status: payment_data.payment_attempt.status,
@ -99,7 +98,7 @@ where
.payment_attempt
.authentication_type
.unwrap_or_default(),
connector_meta_data: merchant_connector_account.metadata,
connector_meta_data: merchant_connector_account.get_metadata(),
request: T::try_from(payment_data.clone())?,
response: response.map_or_else(|| Err(types::ErrorResponse::default()), Ok),
amount_captured: payment_data.payment_intent.amount_captured,

View File

@ -1,5 +1,6 @@
pub mod validator;
use common_utils::ext_traits::AsyncExt;
use error_stack::{report, IntoReport, ResultExt};
use router_env::{instrument, tracing};
@ -73,6 +74,23 @@ pub async fn refund_create_core(
},
)?;
let creds_identifier = req
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
req.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
payments::helpers::insert_merchant_connector_creds_to_config(
db,
merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
validate_and_create_refund(
state,
&merchant_account,
@ -80,6 +98,7 @@ pub async fn refund_create_core(
&payment_intent,
amount,
req,
creds_identifier,
)
.await
.map(services::ApplicationResponse::Json)
@ -92,6 +111,7 @@ pub async fn trigger_refund_to_gateway(
merchant_account: &storage::merchant_account::MerchantAccount,
payment_attempt: &storage::PaymentAttempt,
payment_intent: &storage::PaymentIntent,
creds_identifier: Option<String>,
) -> RouterResult<storage::Refund> {
let connector = payment_attempt
.connector
@ -133,6 +153,7 @@ pub async fn trigger_refund_to_gateway(
payment_intent,
payment_attempt,
refund,
creds_identifier,
)
.await?;
@ -215,19 +236,19 @@ pub async fn trigger_refund_to_gateway(
// ********************************************** REFUND SYNC **********************************************
pub async fn refund_response_wrapper<'a, F, Fut, T>(
pub async fn refund_response_wrapper<'a, F, Fut, T, Req>(
state: &'a AppState,
merchant_account: storage::MerchantAccount,
refund_id: String,
request: Req,
f: F,
) -> RouterResponse<refunds::RefundResponse>
where
F: Fn(&'a AppState, storage::MerchantAccount, String) -> Fut,
F: Fn(&'a AppState, storage::MerchantAccount, Req) -> Fut,
Fut: futures::Future<Output = RouterResult<T>>,
T: ForeignInto<refunds::RefundResponse>,
{
Ok(services::ApplicationResponse::Json(
f(state, merchant_account, refund_id).await?.foreign_into(),
f(state, merchant_account, request).await?.foreign_into(),
))
}
@ -235,8 +256,9 @@ where
pub async fn refund_retrieve_core(
state: &AppState,
merchant_account: storage::MerchantAccount,
refund_id: String,
request: refunds::RefundsRetrieveRequest,
) -> RouterResult<storage::Refund> {
let refund_id = request.refund_id;
let db = &*state.store;
let (merchant_id, payment_intent, payment_attempt, refund, response);
@ -271,12 +293,31 @@ pub async fn refund_retrieve_core(
.await
.map_err(|error| error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound))?;
let creds_identifier = request
.merchant_connector_details
.as_ref()
.map(|mcd| mcd.creds_identifier.to_owned());
request
.merchant_connector_details
.to_owned()
.async_map(|mcd| async {
payments::helpers::insert_merchant_connector_creds_to_config(
db,
merchant_id.as_str(),
mcd,
)
.await
})
.await
.transpose()?;
response = sync_refund_with_gateway(
state,
&merchant_account,
&payment_attempt,
&payment_intent,
&refund,
creds_identifier,
)
.await?;
@ -290,6 +331,7 @@ pub async fn sync_refund_with_gateway(
payment_attempt: &storage::PaymentAttempt,
payment_intent: &storage::PaymentIntent,
refund: &storage::Refund,
creds_identifier: Option<String>,
) -> RouterResult<storage::Refund> {
let connector_id = refund.connector.to_string();
let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
@ -310,6 +352,7 @@ pub async fn sync_refund_with_gateway(
payment_intent,
payment_attempt,
refund,
creds_identifier,
)
.await?;
@ -421,6 +464,7 @@ pub async fn validate_and_create_refund(
payment_intent: &storage::PaymentIntent,
refund_amount: i64,
req: refunds::RefundRequest,
creds_identifier: Option<String>,
) -> RouterResult<refunds::RefundResponse> {
let db = &*state.store;
let (refund_id, all_refunds, currency, refund_create_req, refund);
@ -539,6 +583,7 @@ pub async fn validate_and_create_refund(
merchant_account,
payment_attempt,
payment_intent,
creds_identifier,
)
.await?
}
@ -611,6 +656,7 @@ pub async fn schedule_refund_execution(
merchant_account: &storage::merchant_account::MerchantAccount,
payment_attempt: &storage::PaymentAttempt,
payment_intent: &storage::PaymentIntent,
creds_identifier: Option<String>,
) -> RouterResult<storage::Refund> {
// refunds::RefundResponse> {
let db = &*state.store;
@ -644,6 +690,7 @@ pub async fn schedule_refund_execution(
merchant_account,
payment_attempt,
payment_intent,
creds_identifier,
)
.await
}
@ -699,7 +746,10 @@ pub async fn sync_refund_with_gateway_workflow(
let response = refund_retrieve_core(
state,
merchant_account,
refund_core.refund_internal_reference_id,
refunds::RefundsRetrieveRequest {
refund_id: refund_core.refund_internal_reference_id,
merchant_connector_details: None,
},
)
.await?;
let terminal_status = vec![
@ -812,6 +862,7 @@ pub async fn trigger_refund_execute_workflow(
&merchant_account,
&payment_attempt,
&payment_intent,
None,
)
.await?;
add_refund_sync_task(db, &updated_refund, "REFUND_WORKFLOW_ROUTER").await?;

View File

@ -3,7 +3,7 @@ use std::marker::PhantomData;
use error_stack::ResultExt;
use router_env::{instrument, tracing};
use super::payments::PaymentAddress;
use super::payments::{helpers, PaymentAddress};
use crate::{
consts,
core::errors::{self, RouterResult},
@ -25,18 +25,19 @@ pub async fn construct_refund_router_data<'a, F>(
payment_intent: &'a storage::PaymentIntent,
payment_attempt: &storage::PaymentAttempt,
refund: &'a storage::Refund,
creds_identifier: Option<String>,
) -> RouterResult<types::RefundsRouterData<F>> {
let db = &*state.store;
let merchant_connector_account = db
.find_merchant_connector_account_by_merchant_id_connector(
&merchant_account.merchant_id,
let merchant_connector_account = helpers::get_merchant_connector_account(
db,
merchant_account.merchant_id.as_str(),
connector_id,
creds_identifier,
)
.await
.change_context(errors::ApiErrorResponse::MerchantAccountNotFound)?;
.await?;
let auth_type: types::ConnectorAuthType = merchant_connector_account
.connector_account_details
.get_connector_account_details()
.parse_value("ConnectorAuthType")
.change_context(errors::ApiErrorResponse::InternalServerError)?;
@ -51,7 +52,7 @@ pub async fn construct_refund_router_data<'a, F>(
let router_data = types::RouterData {
flow: PhantomData,
merchant_id: merchant_account.merchant_id.clone(),
connector: merchant_connector_account.connector_name,
connector: connector_id.to_string(),
payment_id: payment_attempt.payment_id.clone(),
attempt_id: payment_attempt.attempt_id.clone(),
status,
@ -65,7 +66,7 @@ pub async fn construct_refund_router_data<'a, F>(
// Does refund need shipping/billing address ?
address: PaymentAddress::default(),
auth_type: payment_attempt.authentication_type.unwrap_or_default(),
connector_meta_data: merchant_connector_account.metadata,
connector_meta_data: merchant_connector_account.get_metadata(),
amount_captured: payment_intent.amount_captured,
request: types::RefundsData {
refund_id: refund.refund_id.clone(),

View File

@ -50,6 +50,7 @@ async fn payments_incoming_webhook_flow<W: api::OutgoingWebhookType>(
force_sync: true,
connector: None,
param: None,
merchant_connector_details: None,
},
services::AuthFlow::Merchant,
consume_or_trigger_flow,
@ -157,7 +158,14 @@ async fn refunds_incoming_webhook_flow<W: api::OutgoingWebhookType>(
)
})?
} else {
refunds::refund_retrieve_core(&state, merchant_account.clone(), refund_id.to_owned())
refunds::refund_retrieve_core(
&state,
merchant_account.clone(),
api_models::refunds::RefundsRetrieveRequest {
refund_id: refund_id.to_owned(),
merchant_connector_details: None,
},
)
.await
.change_context(errors::WebhooksFlowError::RefundsCoreFailed)
.attach_printable_lazy(|| {

View File

@ -89,6 +89,10 @@ impl Payments {
web::resource("/session_tokens")
.route(web::post().to(payments_connector_session)),
)
.service(
web::resource("/sync")
.route(web::post().to(payments_retrieve_with_gateway_creds)),
)
.service(
web::resource("/{payment_id}")
.route(web::get().to(payments_retrieve))
@ -107,6 +111,12 @@ impl Payments {
web::resource("/start/{payment_id}/{merchant_id}/{attempt_id}")
.route(web::get().to(payments_start)),
)
.service(
web::resource(
"/{payment_id}/{merchant_id}/response/{connector}/{creds_identifier}",
)
.route(web::get().to(payments_redirect_response_with_creds_identifier)),
)
.service(
web::resource("/{payment_id}/{merchant_id}/response/{connector}")
.route(web::get().to(payments_redirect_response)),
@ -169,6 +179,7 @@ impl Refunds {
{
route = route
.service(web::resource("").route(web::post().to(refunds_create)))
.service(web::resource("/sync").route(web::post().to(refunds_retrieve_with_body)))
.service(
web::resource("/{id}")
.route(web::get().to(refunds_retrieve))

View File

@ -141,8 +141,7 @@ pub async fn payments_retrieve(
resource_id: payment_types::PaymentIdType::PaymentIntentId(path.to_string()),
merchant_id: json_payload.merchant_id.clone(),
force_sync: json_payload.force_sync.unwrap_or(false),
param: None,
connector: None,
..Default::default()
};
let (auth_type, _auth_flow) = match auth::get_auth_type_and_flow(req.headers()) {
Ok(auth) => auth,
@ -169,6 +168,62 @@ pub async fn payments_retrieve(
.await
}
/// Payments - Retrieve with gateway credentials
///
/// To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment
#[utoipa::path(
post,
path = "/sync",
request_body=PaymentRetrieveBodyWithCredentials,
responses(
(status = 200, description = "Gets the payment with final status", body = PaymentsResponse),
(status = 404, description = "No payment found")
),
tag = "Payments",
operation_id = "Retrieve a Payment",
security(("api_key" = []))
)]
#[instrument(skip(state), fields(flow = ?Flow::PaymentsRetrieve))]
// #[post("/sync")]
pub async fn payments_retrieve_with_gateway_creds(
state: web::Data<app::AppState>,
req: actix_web::HttpRequest,
json_payload: web::Json<payment_types::PaymentRetrieveBodyWithCredentials>,
) -> impl Responder {
let (auth_type, _auth_flow) = match auth::get_auth_type_and_flow(req.headers()) {
Ok(auth) => auth,
Err(err) => return api::log_and_return_error_response(report!(err)),
};
let payload = payment_types::PaymentsRetrieveRequest {
resource_id: payment_types::PaymentIdType::PaymentIntentId(
json_payload.payment_id.to_string(),
),
merchant_id: json_payload.merchant_id.clone(),
force_sync: json_payload.force_sync.unwrap_or(false),
merchant_connector_details: json_payload.merchant_connector_details.clone(),
..Default::default()
};
let flow = Flow::PaymentsRetrieve;
api::server_wrap(
flow,
state.get_ref(),
&req,
payload,
|state, merchant_account, req| {
payments::payments_core::<api_types::PSync, payment_types::PaymentsResponse, _, _, _>(
state,
merchant_account,
payments::PaymentStatus,
req,
api::AuthFlow::Merchant,
payments::CallConnectorAction::Trigger,
)
},
&*auth_type,
)
.await
}
/// Payments - Update
///
/// To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created
@ -429,6 +484,7 @@ pub async fn payments_redirect_response(
json_payload: None,
param: Some(param_string.to_string()),
connector: Some(connector),
creds_identifier: None,
};
api::server_wrap(
flow,
@ -447,6 +503,60 @@ pub async fn payments_redirect_response(
.await
}
// /// Payments - Redirect response with creds_identifier
// ///
// /// To get the payment response for redirect flows
// #[utoipa::path(
// post,
// path = "/payments/{payment_id}/{merchant_id}/response/{connector}/{cred_identifier}",
// params(
// ("payment_id" = String, Path, description = "The identifier for payment"),
// ("merchant_id" = String, Path, description = "The identifier for merchant"),
// ("connector" = String, Path, description = "The name of the connector")
// ),
// responses(
// (status = 302, description = "Received payment redirect response"),
// (status = 400, description = "Missing mandatory fields")
// ),
// tag = "Payments",
// operation_id = "Get Redirect Response for a Payment"
// )]
#[instrument(skip_all)]
pub async fn payments_redirect_response_with_creds_identifier(
state: web::Data<app::AppState>,
req: actix_web::HttpRequest,
path: web::Path<(String, String, String, String)>,
) -> impl Responder {
let (payment_id, merchant_id, connector, creds_identifier) = path.into_inner();
let param_string = req.query_string();
let payload = payments::PaymentsRedirectResponseData {
resource_id: payment_types::PaymentIdType::PaymentIntentId(payment_id),
merchant_id: Some(merchant_id.clone()),
force_sync: true,
json_payload: None,
param: Some(param_string.to_string()),
connector: Some(connector),
creds_identifier: Some(creds_identifier),
};
let flow = Flow::PaymentsRedirect;
api::server_wrap(
flow,
state.get_ref(),
&req,
payload,
|state, merchant_account, req| {
payments::PaymentRedirectSync {}.handle_payments_redirect_response(
state,
merchant_account,
req,
)
},
&auth::MerchantIdAuth(merchant_id),
)
.await
}
#[instrument(skip_all)]
pub async fn payments_complete_authorize(
state: web::Data<app::AppState>,
@ -465,6 +575,7 @@ pub async fn payments_complete_authorize(
json_payload: Some(json_payload.0),
force_sync: false,
connector: Some(connector),
creds_identifier: None,
};
api::server_wrap(
flow,

View File

@ -42,7 +42,7 @@ pub async fn refunds_create(
.await
}
/// Refunds - Retrieve
/// Refunds - Retrieve (GET)
///
/// To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment
#[utoipa::path(
@ -66,16 +66,59 @@ pub async fn refunds_retrieve(
req: HttpRequest,
path: web::Path<String>,
) -> HttpResponse {
let refund_request = refunds::RefundsRetrieveRequest {
refund_id: path.into_inner(),
merchant_connector_details: None,
};
let flow = Flow::RefundsRetrieve;
let refund_id = path.into_inner();
api::server_wrap(
flow,
state.get_ref(),
&req,
refund_id,
|state, merchant_account, refund_id| {
refund_response_wrapper(state, merchant_account, refund_id, refund_retrieve_core)
refund_request,
|state, merchant_account, refund_request| {
refund_response_wrapper(
state,
merchant_account,
refund_request,
refund_retrieve_core,
)
},
&auth::ApiKeyAuth,
)
.await
}
/// Refunds - Retrieve (POST)
///
/// To retrieve the properties of a Refund. This may be used to get the status of a previously initiated payment or next action for an ongoing payment
#[utoipa::path(
get,
path = "/refunds/sync",
responses(
(status = 200, description = "Refund retrieved", body = RefundResponse),
(status = 404, description = "Refund does not exist in our records")
),
tag = "Refunds",
operation_id = "Retrieve a Refund",
security(("api_key" = []))
)]
#[instrument(skip_all, fields(flow = ?Flow::RefundsRetrieve))]
// #[post("/sync")]
pub async fn refunds_retrieve_with_body(
state: web::Data<AppState>,
req: HttpRequest,
json_payload: web::Json<refunds::RefundsRetrieveRequest>,
) -> HttpResponse {
let flow = Flow::RefundsRetrieve;
api::server_wrap(
flow,
state.get_ref(),
&req,
json_payload.into_inner(),
|state, merchant_account, req| {
refund_response_wrapper(state, merchant_account, req, refund_retrieve_core)
},
&auth::ApiKeyAuth,
)

View File

@ -1,8 +1,9 @@
pub use api_models::admin::{
MerchantAccountCreate, MerchantAccountDeleteResponse, MerchantAccountResponse,
MerchantAccountUpdate, MerchantConnector, MerchantConnectorDeleteResponse, MerchantConnectorId,
MerchantDetails, MerchantId, PaymentMethodsEnabled, RoutingAlgorithm, ToggleKVRequest,
ToggleKVResponse, WebhookDetails,
MerchantAccountUpdate, MerchantConnector, MerchantConnectorDeleteResponse,
MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId, MerchantDetails,
MerchantId, PaymentMethodsEnabled, RoutingAlgorithm, ToggleKVRequest, ToggleKVResponse,
WebhookDetails,
};
use crate::types::{storage, transformers::ForeignFrom};

View File

@ -3,11 +3,11 @@ pub use api_models::payments::{
CustomerAcceptance, MandateData, MandateTxnType, MandateType, MandateValidationFields,
NextAction, NextActionType, OnlineMandate, PayLaterData, PaymentIdType, PaymentListConstraints,
PaymentListResponse, PaymentMethodData, PaymentMethodDataResponse, PaymentOp,
PaymentRetrieveBody, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsRedirectRequest,
PaymentsRedirectionResponse, PaymentsRequest, PaymentsResponse, PaymentsResponseForm,
PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest,
PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, VerifyRequest,
VerifyResponse, WalletData,
PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsCancelRequest,
PaymentsCaptureRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRequest,
PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest,
PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails,
RedirectionResponse, SessionToken, UrlDetails, VerifyRequest, VerifyResponse, WalletData,
};
use error_stack::{IntoReport, ResultExt};
use masking::PeekInterface;

View File

@ -1,5 +1,6 @@
pub use api_models::refunds::{
RefundRequest, RefundResponse, RefundStatus, RefundType, RefundUpdateRequest,
RefundsRetrieveRequest,
};
use super::ConnectorCommon;