feat(core): add sessions api endpoint (#104)

This commit is contained in:
Narayan Bhat
2022-12-10 21:32:03 +05:30
committed by GitHub
parent 031c073e79
commit dff8b22489
30 changed files with 353 additions and 112 deletions

View File

@ -36,6 +36,10 @@ locker_encryption_key2 = ""
locker_decryption_key1 = "" locker_decryption_key1 = ""
locker_decryption_key2 = "" locker_decryption_key2 = ""
[connectors.supported]
wallets = ["klarna","braintree"]
cards = ["stripe","adyen","authorizedotnet","checkout","braintree"]
[eph_key] [eph_key]
validity = 1 validity = 1

View File

@ -99,6 +99,11 @@ pub struct Database {
pub pool_size: u32, pub pool_size: u32,
} }
#[derive(Debug, Deserialize, Clone)]
pub struct SupportedConnectors {
pub wallets: Vec<String>,
}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct Connectors { pub struct Connectors {
pub aci: ConnectorParams, pub aci: ConnectorParams,
@ -107,6 +112,7 @@ pub struct Connectors {
pub checkout: ConnectorParams, pub checkout: ConnectorParams,
pub stripe: ConnectorParams, pub stripe: ConnectorParams,
pub braintree: ConnectorParams, pub braintree: ConnectorParams,
pub supported: SupportedConnectors,
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]

View File

@ -11,7 +11,7 @@ use time;
pub use self::operations::{ pub use self::operations::{
PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentMethodValidate, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, PaymentMethodValidate,
PaymentResponse, PaymentStatus, PaymentUpdate, PaymentResponse, PaymentSession, PaymentStatus, PaymentUpdate,
}; };
use self::{ use self::{
flows::{ConstructFlowSpecificData, Feature}, flows::{ConstructFlowSpecificData, Feature},
@ -61,9 +61,6 @@ where
// To perform router related operation for PaymentResponse // To perform router related operation for PaymentResponse
PaymentResponse: Operation<F, FData>, PaymentResponse: Operation<F, FData>,
{ {
let connector = api::ConnectorData::construct(&state.conf.connectors, &merchant_account)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let operation: BoxedOperation<F, Req> = Box::new(operation); let operation: BoxedOperation<F, Req> = Box::new(operation);
let (operation, validate_result) = operation let (operation, validate_result) = operation
@ -78,7 +75,6 @@ where
state, state,
&validate_result.payment_id, &validate_result.payment_id,
validate_result.merchant_id, validate_result.merchant_id,
connector.connector_name,
&req, &req,
validate_result.mandate_type, validate_result.mandate_type,
validate_result.storage_scheme, validate_result.storage_scheme,
@ -110,6 +106,16 @@ where
.await?; .await?;
payment_data.payment_method_data = payment_method_data; payment_data.payment_method_data = payment_method_data;
let connector_details = operation
.to_domain()?
.get_connector(&merchant_account, state)
.await?;
if let api::ConnectorCallType::Single(ref connector) = connector_details {
payment_data.payment_attempt.connector =
Some(connector.connector_name.to_owned().to_string());
}
let (operation, mut payment_data) = operation let (operation, mut payment_data) = operation
.to_update_tracker()? .to_update_tracker()?
.update_trackers( .update_trackers(
@ -127,17 +133,32 @@ where
.await?; .await?;
if should_call_connector(&operation, &payment_data) { if should_call_connector(&operation, &payment_data) {
payment_data = call_connector_service( payment_data = match connector_details {
state, api::ConnectorCallType::Single(connector) => {
&merchant_account, call_connector_service(
&validate_result.payment_id, state,
connector, &merchant_account,
&operation, &validate_result.payment_id,
payment_data, connector,
&customer, &operation,
call_connector_action, payment_data,
) &customer,
.await?; call_connector_action,
)
.await?
}
api::ConnectorCallType::Multiple(connectors) => {
call_multiple_connectors_service(
state,
&merchant_account,
connectors,
&operation,
payment_data,
&customer,
)
.await?
}
}
} }
Ok((payment_data, req, customer)) Ok((payment_data, req, customer))
} }

View File

@ -21,13 +21,14 @@ use crate::{
routes::AppState, routes::AppState,
services, services,
types::{ types::{
self,
api::{self, enums as api_enums}, api::{self, enums as api_enums},
storage::{self, enums as storage_enums, ephemeral_key}, storage::{self, enums as storage_enums, ephemeral_key},
}, },
utils::{ utils::{
self, self,
crypto::{self, SignMessage}, crypto::{self, SignMessage},
OptionExt, OptionExt, ValueExt,
}, },
}; };
@ -313,13 +314,14 @@ pub fn create_startpay_url(
) )
} }
pub fn create_redirect_url(server: &Server, payment_attempt: &storage::PaymentAttempt) -> String { pub fn create_redirect_url(
server: &Server,
payment_attempt: &storage::PaymentAttempt,
connector_name: &String,
) -> String {
format!( format!(
"{}/payments/{}/{}/response/{}", "{}/payments/{}/{}/response/{}",
server.base_url, server.base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name
payment_attempt.payment_id,
payment_attempt.merchant_id,
payment_attempt.connector
) )
} }
fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult<()> { fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult<()> {
@ -522,6 +524,44 @@ pub async fn get_customer_from_details(
} }
} }
pub async fn get_connector_default(
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
let connectors = &state.conf.connectors;
let vec_val: Vec<serde_json::Value> = merchant_account
.custom_routing_rules
.clone()
.parse_value("CustomRoutingRulesVec")
.change_context(errors::ConnectorError::RoutingRulesParsingError)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let custom_routing_rules: api::CustomRoutingRules = vec_val[0]
.clone()
.parse_value("CustomRoutingRules")
.change_context(errors::ConnectorError::RoutingRulesParsingError)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let connector_names = custom_routing_rules
.connectors_pecking_order
.unwrap_or_else(|| vec!["stripe".to_string()]);
//use routing rules if configured by merchant else query MCA as per PM
let connector_list: types::ConnectorsList = types::ConnectorsList {
connectors: connector_names,
};
let connector_name = connector_list
.connectors
.first()
.get_required_value("connectors")
.change_context(errors::ConnectorError::FailedToObtainPreferredConnector)
.change_context(errors::ApiErrorResponse::InternalServerError)?
.as_str();
let connector_data = api::ConnectorData::get_connector_by_name(connectors, connector_name)?;
Ok(api::ConnectorCallType::Single(connector_data))
}
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn create_customer_if_not_exist<'a, F: Clone, R>( pub async fn create_customer_if_not_exist<'a, F: Clone, R>(
operation: BoxedOperation<'a, F, R>, operation: BoxedOperation<'a, F, R>,

View File

@ -92,7 +92,6 @@ pub trait GetTracker<F, D, R>: Send {
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
request: &R, request: &R,
mandate_type: Option<api::MandateTxnType>, mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
@ -129,6 +128,12 @@ pub trait Domain<F: Clone, R>: Send + Sync {
) -> CustomResult<(), errors::ApiErrorResponse> { ) -> CustomResult<(), errors::ApiErrorResponse> {
Ok(()) Ok(())
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse>;
} }
#[async_trait] #[async_trait]
@ -224,9 +229,14 @@ where
if helpers::check_if_operation_confirm(self) { if helpers::check_if_operation_confirm(self) {
metrics::TASKS_ADDED_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics metrics::TASKS_ADDED_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics
let connector_name = payment_attempt
.connector
.clone()
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
let schedule_time = payment_sync::get_sync_process_schedule_time( let schedule_time = payment_sync::get_sync_process_schedule_time(
&*state.store, &*state.store,
&payment_attempt.connector, &connector_name,
&payment_attempt.merchant_id, &payment_attempt.merchant_id,
0, 0,
) )
@ -245,6 +255,14 @@ where
Ok(()) Ok(())
} }
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
} }
#[async_trait] #[async_trait]
@ -278,6 +296,14 @@ where
)) ))
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
#[instrument(skip_all)] #[instrument(skip_all)]
async fn make_pm_data<'a>( async fn make_pm_data<'a>(
&'a self, &'a self,
@ -351,6 +377,14 @@ where
)> { )> {
Ok((Box::new(self), None)) Ok((Box::new(self), None))
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
} }
#[async_trait] #[async_trait]
@ -400,4 +434,12 @@ where
)> { )> {
Ok((Box::new(self), None)) Ok((Box::new(self), None))
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
} }

View File

@ -16,7 +16,6 @@ use crate::{
types::{ types::{
api, api,
storage::{self, enums, Customer}, storage::{self, enums, Customer},
Connector,
}, },
utils::OptionExt, utils::OptionExt,
}; };
@ -33,7 +32,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsCancelRequest>
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &api::PaymentsCancelRequest, request: &api::PaymentsCancelRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,

View File

@ -16,7 +16,6 @@ use crate::{
api, api,
api::PaymentsCaptureRequest, api::PaymentsCaptureRequest,
storage::{self, enums}, storage::{self, enums},
Connector,
}, },
utils::OptionExt, utils::OptionExt,
}; };
@ -35,7 +34,6 @@ impl<F: Send + Clone> GetTracker<F, payments::PaymentData<F>, api::PaymentsCaptu
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &PaymentsCaptureRequest, request: &PaymentsCaptureRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,

View File

@ -17,7 +17,6 @@ use crate::{
types::{ types::{
self, api, self, api,
storage::{self, enums}, storage::{self, enums},
Connector,
}, },
utils::{self, OptionExt}, utils::{self, OptionExt},
}; };
@ -34,7 +33,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
mandate_type: Option<api::MandateTxnType>, mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
@ -194,6 +192,8 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
), ),
}; };
let connector = payment_data.payment_attempt.connector.clone();
payment_data.payment_attempt = db payment_data.payment_attempt = db
.update_payment_attempt( .update_payment_attempt(
payment_data.payment_attempt, payment_data.payment_attempt,
@ -201,6 +201,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
status: attempt_status, status: attempt_status,
payment_method, payment_method,
browser_info, browser_info,
connector,
}, },
storage_scheme, storage_scheme,
) )

View File

@ -37,7 +37,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
mandate_type: Option<api::MandateTxnType>, mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
@ -84,7 +83,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
Self::make_payment_attempt( Self::make_payment_attempt(
&payment_id, &payment_id,
merchant_id, merchant_id,
connector,
money, money,
payment_method_type, payment_method_type,
request, request,
@ -117,7 +115,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
Self::make_payment_intent( Self::make_payment_intent(
&payment_id, &payment_id,
merchant_id, merchant_id,
&connector.to_string(),
money, money,
request, request,
shipping_address.clone().map(|x| x.address_id), shipping_address.clone().map(|x| x.address_id),
@ -314,7 +311,6 @@ impl PaymentCreate {
fn make_payment_attempt( fn make_payment_attempt(
payment_id: &str, payment_id: &str,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
money: (api::Amount, enums::Currency), money: (api::Amount, enums::Currency),
payment_method: Option<enums::PaymentMethodType>, payment_method: Option<enums::PaymentMethodType>,
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
@ -331,7 +327,6 @@ impl PaymentCreate {
status, status,
amount: amount.into(), amount: amount.into(),
currency, currency,
connector: connector.to_string(),
payment_method, payment_method,
capture_method: request.capture_method.map(Into::into), capture_method: request.capture_method.map(Into::into),
capture_on: request.capture_on, capture_on: request.capture_on,
@ -349,7 +344,6 @@ impl PaymentCreate {
fn make_payment_intent( fn make_payment_intent(
payment_id: &str, payment_id: &str,
merchant_id: &str, merchant_id: &str,
connector_id: &str,
money: (api::Amount, enums::Currency), money: (api::Amount, enums::Currency),
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
shipping_address_id: Option<String>, shipping_address_id: Option<String>,
@ -367,7 +361,6 @@ impl PaymentCreate {
status, status,
amount: amount.into(), amount: amount.into(),
currency, currency,
connector_id: Some(connector_id.to_string()),
description: request.description.clone(), description: request.description.clone(),
created_at, created_at,
modified_at, modified_at,

View File

@ -66,7 +66,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
request: &api::VerifyRequest, request: &api::VerifyRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: storage_enums::MerchantStorageScheme, storage_scheme: storage_enums::MerchantStorageScheme,
@ -87,7 +86,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
Self::make_payment_attempt( Self::make_payment_attempt(
&payment_id, &payment_id,
merchant_id, merchant_id,
connector,
request.payment_method, request.payment_method,
request, request,
), ),
@ -103,7 +101,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::VerifyRequest> for Paym
payment_intent = match db payment_intent = match db
.insert_payment_intent( .insert_payment_intent(
Self::make_payment_intent(&payment_id, merchant_id, connector, request), Self::make_payment_intent(&payment_id, merchant_id, request),
storage_scheme, storage_scheme,
) )
.await .await
@ -256,6 +254,14 @@ where
) )
.await .await
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
} }
impl PaymentMethodValidate { impl PaymentMethodValidate {
@ -263,7 +269,6 @@ impl PaymentMethodValidate {
fn make_payment_attempt( fn make_payment_attempt(
payment_id: &str, payment_id: &str,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
payment_method: Option<api_enums::PaymentMethodType>, payment_method: Option<api_enums::PaymentMethodType>,
_request: &api::VerifyRequest, _request: &api::VerifyRequest,
) -> storage::PaymentAttemptNew { ) -> storage::PaymentAttemptNew {
@ -278,7 +283,7 @@ impl PaymentMethodValidate {
// Amount & Currency will be zero in this case // Amount & Currency will be zero in this case
amount: 0, amount: 0,
currency: Default::default(), currency: Default::default(),
connector: connector.to_string(), connector: None,
payment_method: payment_method.map(Into::into), payment_method: payment_method.map(Into::into),
confirm: true, confirm: true,
created_at, created_at,
@ -291,7 +296,6 @@ impl PaymentMethodValidate {
fn make_payment_intent( fn make_payment_intent(
payment_id: &str, payment_id: &str,
merchant_id: &str, merchant_id: &str,
connector: types::Connector,
request: &api::VerifyRequest, request: &api::VerifyRequest,
) -> storage::PaymentIntentNew { ) -> storage::PaymentIntentNew {
let created_at @ modified_at @ last_synced = Some(date_time::now()); let created_at @ modified_at @ last_synced = Some(date_time::now());
@ -305,7 +309,7 @@ impl PaymentMethodValidate {
status, status,
amount: 0, amount: 0,
currency: Default::default(), currency: Default::default(),
connector_id: Some(connector.to_string()), connector_id: None,
created_at, created_at,
modified_at, modified_at,
last_synced, last_synced,

View File

@ -20,7 +20,7 @@ use crate::{
#[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] #[derive(Debug, Clone, Copy, router_derive::PaymentOperation)]
#[operation( #[operation(
ops = "post_tracker", ops = "post_tracker",
flow = "syncdata,authorizedata,canceldata,capturedata,verifydata" flow = "syncdata,authorizedata,canceldata,capturedata,verifydata,sessiondata"
)] )]
pub struct PaymentResponse; pub struct PaymentResponse;
@ -77,6 +77,28 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for
} }
} }
#[async_trait]
impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData>
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::PaymentsSessionData, 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)
.await
}
}
#[async_trait] #[async_trait]
impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData> impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData>
for PaymentResponse for PaymentResponse
@ -169,6 +191,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
}; };
let encoded_data = payment_data.connector_response.encoded_data.clone(); let encoded_data = payment_data.connector_response.encoded_data.clone();
let connector_name = payment_data.payment_attempt.connector.clone();
let authentication_data = redirection_data let authentication_data = redirection_data
.map(|data| utils::Encode::<RedirectForm>::encode_to_value(&data)) .map(|data| utils::Encode::<RedirectForm>::encode_to_value(&data))
@ -189,6 +212,7 @@ async fn payment_response_update_tracker<F: Clone, T>(
connector_transaction_id, connector_transaction_id,
authentication_data, authentication_data,
encoded_data, encoded_data,
connector_name,
}; };
( (

View File

@ -16,7 +16,6 @@ use crate::{
types::{ types::{
api, api,
storage::{self, enums}, storage::{self, enums},
Connector,
}, },
utils::OptionExt, utils::OptionExt,
}; };
@ -35,7 +34,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsSessionRequest>
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &api::PaymentsSessionRequest, request: &api::PaymentsSessionRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
@ -235,4 +233,38 @@ where
//No payment method data for this operation //No payment method data for this operation
Ok((Box::new(self), None)) Ok((Box::new(self), None))
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> RouterResult<api::ConnectorCallType> {
let connectors = &state.conf.connectors;
let db = &state.store;
let supported_connectors: &Vec<String> = state.conf.connectors.supported.wallets.as_ref();
//FIXME: Check if merchant has enabled wallet through the connector
let connector_names = db
.find_merchant_connector_account_by_merchant_id_list(&merchant_account.merchant_id)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Database error when querying for merchant accounts")?
.iter()
.filter(|connector_account| {
supported_connectors.contains(&connector_account.connector_name)
})
.map(|filtered_connector| filtered_connector.connector_name.clone())
.collect::<Vec<String>>();
let mut connectors_data = Vec::with_capacity(connector_names.len());
for connector_name in connector_names {
let connector_data =
api::ConnectorData::get_connector_by_name(connectors, &connector_name)?;
connectors_data.push(connector_data);
}
Ok(api::ConnectorCallType::Multiple(connectors_data))
}
} }

View File

@ -16,7 +16,6 @@ use crate::{
types::{ types::{
api, api,
storage::{self, enums, Customer}, storage::{self, enums, Customer},
Connector,
}, },
utils::OptionExt, utils::OptionExt,
}; };
@ -33,7 +32,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsStartRequest> f
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
_request: &api::PaymentsStartRequest, _request: &api::PaymentsStartRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,
@ -252,4 +250,12 @@ where
) )
.await .await
} }
async fn get_connector<'a>(
&'a self,
merchant_account: &storage::MerchantAccount,
state: &AppState,
) -> CustomResult<api::ConnectorCallType, errors::ApiErrorResponse> {
helpers::get_connector_default(merchant_account, state).await
}
} }

View File

@ -16,10 +16,10 @@ use crate::{
types::{ types::{
api, api,
storage::{self, enums}, storage::{self, enums},
Connector,
}, },
utils::{self, OptionExt}, utils::{self, OptionExt},
}; };
#[derive(Debug, Clone, Copy, PaymentOperation)] #[derive(Debug, Clone, Copy, PaymentOperation)]
#[operation(ops = "all", flow = "sync")] #[operation(ops = "all", flow = "sync")]
pub struct PaymentStatus; pub struct PaymentStatus;
@ -94,7 +94,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRetrieveRequest
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &api::PaymentsRetrieveRequest, request: &api::PaymentsRetrieveRequest,
_mandate_type: Option<api::MandateTxnType>, _mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,

View File

@ -17,7 +17,6 @@ use crate::{
types::{ types::{
api, api,
storage::{self, enums}, storage::{self, enums},
Connector,
}, },
utils::{OptionExt, StringExt}, utils::{OptionExt, StringExt},
}; };
@ -33,7 +32,6 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
state: &'a AppState, state: &'a AppState,
payment_id: &api::PaymentIdType, payment_id: &api::PaymentIdType,
merchant_id: &str, merchant_id: &str,
_connector: Connector,
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
mandate_type: Option<api::MandateTxnType>, mandate_type: Option<api::MandateTxnType>,
storage_scheme: enums::MerchantStorageScheme, storage_scheme: enums::MerchantStorageScheme,

View File

@ -73,6 +73,7 @@ where
let orca_return_url = Some(helpers::create_redirect_url( let orca_return_url = Some(helpers::create_redirect_url(
&state.conf.server, &state.conf.server,
&payment_data.payment_attempt, &payment_data.payment_attempt,
&merchant_connector_account.connector_name,
)); ));
router_data = types::RouterData { router_data = types::RouterData {
@ -145,6 +146,26 @@ where
} }
} }
impl<F, Req, Op> ToResponse<Req, PaymentData<F>, Op> for api::PaymentsSessionResponse
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> {
Ok(services::BachResponse::Json(Self {
session_token: payment_data.sessions_token,
}))
}
}
impl<F, Req, Op> ToResponse<Req, PaymentData<F>, Op> for api::VerifyResponse impl<F, Req, Op> ToResponse<Req, PaymentData<F>, Op> for api::VerifyResponse
where where
Self: From<Req>, Self: From<Req>,

View File

@ -91,7 +91,11 @@ pub async fn trigger_refund_to_gateway(
payment_attempt: &storage::PaymentAttempt, payment_attempt: &storage::PaymentAttempt,
payment_intent: &storage::PaymentIntent, payment_intent: &storage::PaymentIntent,
) -> RouterResult<storage::Refund> { ) -> RouterResult<storage::Refund> {
let connector_id = payment_attempt.connector.to_string(); let connector = payment_attempt
.connector
.clone()
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
let connector_id = connector.to_string();
let connector: api::ConnectorData = let connector: api::ConnectorData =
api::ConnectorData::get_connector_by_name(&state.conf.connectors, &connector_id) api::ConnectorData::get_connector_by_name(&state.conf.connectors, &connector_id)
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
@ -390,9 +394,14 @@ pub async fn validate_and_create_refund(
validator::validate_maximum_refund_against_payment_attempt(&all_refunds) validator::validate_maximum_refund_against_payment_attempt(&all_refunds)
.change_context(errors::ApiErrorResponse::MaximumRefundCount)?; .change_context(errors::ApiErrorResponse::MaximumRefundCount)?;
let connector = payment_attempt
.connector
.clone()
.ok_or(errors::ApiErrorResponse::InternalServerError)?;
refund_create_req = mk_new_refund( refund_create_req = mk_new_refund(
req, req,
payment_attempt.connector.to_owned(), connector,
payment_attempt, payment_attempt,
currency, currency,
&refund_id, &refund_id,

View File

@ -75,6 +75,9 @@ impl Payments {
web::resource("/{payment_id}/{merchant_id}/response/{connector}") web::resource("/{payment_id}/{merchant_id}/response/{connector}")
.route(web::get().to(payments_response)), .route(web::get().to(payments_response)),
) )
.service(
web::resource("/session_tokens").route(web::get().to(payments_connector_session)),
)
} }
} }

View File

@ -14,16 +14,16 @@ use super::app::AppState;
use crate::{ use crate::{
core::{errors::http_not_implemented, payments}, core::{errors::http_not_implemented, payments},
services::api, services::api,
types::api::{ types::api::{
self as api_types, enums as api_enums, self as api_types, enums as api_enums,
payments::{ payments::{
PaymentIdType, PaymentListConstraints, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentIdType, PaymentListConstraints, PaymentsCancelRequest, PaymentsCaptureRequest,
PaymentsRequest, PaymentsRetrieveRequest, PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest,
}, },
Authorize, Capture, PSync, PaymentRetrieveBody, PaymentsResponse, PaymentsStartRequest, Authorize, Capture, PSync, PaymentRetrieveBody, PaymentsSessionRequest,
Verify, Void, PaymentsSessionResponse, PaymentsStartRequest, Session, Verify, Void,
}, // FIXME imports },
//FIXME: remove specific imports
}; };
#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate))]
@ -269,6 +269,33 @@ pub(crate) async fn payments_capture(
.await .await
} }
#[instrument(skip_all, fields(flow = ?Flow::PaymentsSessionToken))]
pub(crate) async fn payments_connector_session(
state: web::Data<AppState>,
req: HttpRequest,
json_payload: web::Json<PaymentsSessionRequest>,
) -> HttpResponse {
let sessions_payload = json_payload.into_inner();
api::server_wrap(
&state,
&req,
sessions_payload,
|state, merchant_account, payload| {
payments::payments_core::<Session, PaymentsSessionResponse, _, _, _>(
state,
merchant_account,
payments::PaymentSession,
payload,
api::AuthFlow::Merchant,
payments::CallConnectorAction::Trigger,
)
},
api::MerchantAuthentication::ApiKey,
)
.await
}
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn payments_response( pub async fn payments_response(
state: web::Data<AppState>, state: web::Data<AppState>,

View File

@ -60,9 +60,14 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow {
.await? .await?
} }
_ => { _ => {
let connector = payment_data
.payment_attempt
.connector
.clone()
.ok_or(errors::ProcessTrackerError::MissingRequiredField)?;
retry_sync_task( retry_sync_task(
db, db,
payment_data.payment_attempt.connector, connector,
payment_data.payment_attempt.merchant_id, payment_data.payment_attempt.merchant_id,
process, process,
) )

View File

@ -45,7 +45,7 @@ diesel::table! {
txn_id -> Varchar, txn_id -> Varchar,
created_at -> Timestamp, created_at -> Timestamp,
modified_at -> Timestamp, modified_at -> Timestamp,
connector_name -> Varchar, connector_name -> Nullable<Varchar>,
connector_transaction_id -> Nullable<Varchar>, connector_transaction_id -> Nullable<Varchar>,
authentication_data -> Nullable<Json>, authentication_data -> Nullable<Json>,
encoded_data -> Nullable<Text>, encoded_data -> Nullable<Text>,
@ -186,7 +186,7 @@ diesel::table! {
amount -> Int4, amount -> Int4,
currency -> Nullable<Currency>, currency -> Nullable<Currency>,
save_to_locker -> Nullable<Bool>, save_to_locker -> Nullable<Bool>,
connector -> Varchar, connector -> Nullable<Varchar>,
error_message -> Nullable<Text>, error_message -> Nullable<Text>,
offer_amount -> Nullable<Int4>, offer_amount -> Nullable<Int4>,
surcharge_amount -> Nullable<Int4>, surcharge_amount -> Nullable<Int4>,

View File

@ -121,7 +121,7 @@ pub struct PaymentsSessionData {
//TODO: Add the fields here as required //TODO: Add the fields here as required
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, serde::Serialize)]
pub struct ConnectorSessionToken { pub struct ConnectorSessionToken {
pub connector_name: String, pub connector_name: String,
pub session_token: String, pub session_token: String,

View File

@ -12,14 +12,12 @@ use std::{fmt::Debug, marker, str::FromStr};
use error_stack::{report, IntoReport, ResultExt}; use error_stack::{report, IntoReport, ResultExt};
pub use self::{admin::*, customers::*, payment_methods::*, payments::*, refunds::*, webhooks::*}; pub use self::{admin::*, customers::*, payment_methods::*, payments::*, refunds::*, webhooks::*};
use super::{storage, ConnectorsList};
use crate::{ use crate::{
configs::settings::Connectors, configs::settings::Connectors,
connector, connector,
core::errors::{self, CustomResult}, core::errors::{self, CustomResult},
services::ConnectorRedirectResponse, services::ConnectorRedirectResponse,
types::{self, api}, types,
utils::{OptionExt, ValueExt},
}; };
pub trait ConnectorCommon { pub trait ConnectorCommon {
@ -71,45 +69,12 @@ pub struct ConnectorData {
pub connector_name: types::Connector, pub connector_name: types::Connector,
} }
pub enum ConnectorCallType {
Single(ConnectorData),
Multiple(Vec<ConnectorData>),
}
impl ConnectorData { impl ConnectorData {
pub fn construct(
connectors: &Connectors,
merchant_account: &storage::MerchantAccount,
) -> CustomResult<ConnectorData, errors::ApiErrorResponse> {
// Add Validate also to ParseStruct
//FIXME: Need Proper Routing Logic
let vec_val: Vec<serde_json::Value> = merchant_account
.custom_routing_rules
.as_ref()
.cloned()
.parse_value("CustomRoutingRulesVec")
.change_context(errors::ConnectorError::RoutingRulesParsingError)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let custom_routing_rules: api::CustomRoutingRules = vec_val[0]
.clone()
.parse_value("CustomRoutingRules")
.change_context(errors::ConnectorError::RoutingRulesParsingError)
.change_context(errors::ApiErrorResponse::InternalServerError)?;
let connector_names = custom_routing_rules
.connectors_pecking_order
.unwrap_or_else(|| vec!["stripe".to_string()]);
//use routing rules if configured by merchant else query MCA as per PM
let connector_list: ConnectorsList = ConnectorsList {
connectors: connector_names,
};
let connector_name = connector_list
.connectors
.first()
.get_required_value("connectors")
.change_context(errors::ConnectorError::FailedToObtainPreferredConnector)
.change_context(errors::ApiErrorResponse::InternalServerError)?
.as_str();
Self::get_connector_by_name(connectors, connector_name)
}
pub fn get_connector_by_name( pub fn get_connector_by_name(
connectors: &Connectors, connectors: &Connectors,
name: &str, name: &str,

View File

@ -599,6 +599,28 @@ impl From<PaymentsStartRequest> for PaymentsResponse {
} }
} }
impl From<PaymentsSessionRequest> for PaymentsResponse {
fn from(item: PaymentsSessionRequest) -> Self {
let payment_id = match item.payment_id {
api_types::PaymentIdType::PaymentIntentId(id) => Some(id),
_ => None,
};
Self {
payment_id,
..Default::default()
}
}
}
impl From<PaymentsSessionRequest> for PaymentsSessionResponse {
fn from(_item: PaymentsSessionRequest) -> Self {
Self {
session_token: vec![],
}
}
}
impl From<types::storage::PaymentIntent> for PaymentsResponse { impl From<types::storage::PaymentIntent> for PaymentsResponse {
fn from(item: types::storage::PaymentIntent) -> Self { fn from(item: types::storage::PaymentIntent) -> Self {
Self { Self {
@ -774,6 +796,11 @@ pub struct PaymentsSessionRequest {
pub client_secret: String, pub client_secret: String,
} }
#[derive(Default, Debug, serde::Serialize, Clone)]
pub struct PaymentsSessionResponse {
pub session_token: Vec<types::ConnectorSessionToken>,
}
#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PaymentRetrieveBody { pub struct PaymentRetrieveBody {
pub merchant_id: Option<String>, pub merchant_id: Option<String>,

View File

@ -13,7 +13,7 @@ pub struct ConnectorResponseNew {
pub txn_id: String, pub txn_id: String,
pub created_at: PrimitiveDateTime, pub created_at: PrimitiveDateTime,
pub modified_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime,
pub connector_name: String, pub connector_name: Option<String>,
pub connector_transaction_id: Option<String>, pub connector_transaction_id: Option<String>,
pub authentication_data: Option<serde_json::Value>, pub authentication_data: Option<serde_json::Value>,
pub encoded_data: Option<String>, pub encoded_data: Option<String>,
@ -30,7 +30,7 @@ pub struct ConnectorResponse {
pub txn_id: String, pub txn_id: String,
pub created_at: PrimitiveDateTime, pub created_at: PrimitiveDateTime,
pub modified_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime,
pub connector_name: String, pub connector_name: Option<String>,
pub connector_transaction_id: Option<String>, pub connector_transaction_id: Option<String>,
pub authentication_data: Option<serde_json::Value>, pub authentication_data: Option<serde_json::Value>,
pub encoded_data: Option<String>, pub encoded_data: Option<String>,
@ -43,6 +43,7 @@ pub struct ConnectorResponseUpdateInternal {
pub authentication_data: Option<serde_json::Value>, pub authentication_data: Option<serde_json::Value>,
pub modified_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime,
pub encoded_data: Option<String>, pub encoded_data: Option<String>,
pub connector_name: Option<String>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -51,6 +52,7 @@ pub enum ConnectorResponseUpdate {
connector_transaction_id: Option<String>, connector_transaction_id: Option<String>,
authentication_data: Option<serde_json::Value>, authentication_data: Option<serde_json::Value>,
encoded_data: Option<String>, encoded_data: Option<String>,
connector_name: Option<String>,
}, },
} }
@ -74,11 +76,13 @@ impl From<ConnectorResponseUpdate> for ConnectorResponseUpdateInternal {
connector_transaction_id, connector_transaction_id,
authentication_data, authentication_data,
encoded_data, encoded_data,
connector_name,
} => Self { } => Self {
connector_transaction_id, connector_transaction_id,
authentication_data, authentication_data,
encoded_data, encoded_data,
modified_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(),
connector_name,
}, },
} }
} }

View File

@ -15,7 +15,7 @@ pub struct PaymentAttempt {
pub amount: i32, pub amount: i32,
pub currency: Option<storage_enums::Currency>, pub currency: Option<storage_enums::Currency>,
pub save_to_locker: Option<bool>, pub save_to_locker: Option<bool>,
pub connector: String, pub connector: Option<String>,
pub error_message: Option<String>, pub error_message: Option<String>,
pub offer_amount: Option<i32>, pub offer_amount: Option<i32>,
pub surcharge_amount: Option<i32>, pub surcharge_amount: Option<i32>,
@ -49,7 +49,7 @@ pub struct PaymentAttemptNew {
pub currency: Option<storage_enums::Currency>, pub currency: Option<storage_enums::Currency>,
// pub auto_capture: Option<bool>, // pub auto_capture: Option<bool>,
pub save_to_locker: Option<bool>, pub save_to_locker: Option<bool>,
pub connector: String, pub connector: Option<String>,
pub error_message: Option<String>, pub error_message: Option<String>,
pub offer_amount: Option<i32>, pub offer_amount: Option<i32>,
pub surcharge_amount: Option<i32>, pub surcharge_amount: Option<i32>,
@ -88,6 +88,7 @@ pub enum PaymentAttemptUpdate {
status: storage_enums::AttemptStatus, status: storage_enums::AttemptStatus,
payment_method: Option<storage_enums::PaymentMethodType>, payment_method: Option<storage_enums::PaymentMethodType>,
browser_info: Option<serde_json::Value>, browser_info: Option<serde_json::Value>,
connector: Option<String>,
}, },
VoidUpdate { VoidUpdate {
status: storage_enums::AttemptStatus, status: storage_enums::AttemptStatus,
@ -117,6 +118,7 @@ pub(super) struct PaymentAttemptUpdateInternal {
currency: Option<storage_enums::Currency>, currency: Option<storage_enums::Currency>,
status: Option<storage_enums::AttemptStatus>, status: Option<storage_enums::AttemptStatus>,
connector_transaction_id: Option<String>, connector_transaction_id: Option<String>,
connector: Option<String>,
authentication_type: Option<storage_enums::AuthenticationType>, authentication_type: Option<storage_enums::AuthenticationType>,
payment_method: Option<storage_enums::PaymentMethodType>, payment_method: Option<storage_enums::PaymentMethodType>,
error_message: Option<String>, error_message: Option<String>,
@ -182,11 +184,13 @@ impl From<PaymentAttemptUpdate> for PaymentAttemptUpdateInternal {
status, status,
payment_method, payment_method,
browser_info, browser_info,
connector,
} => Self { } => Self {
status: Some(status), status: Some(status),
payment_method, payment_method,
modified_at: Some(common_utils::date_time::now()), modified_at: Some(common_utils::date_time::now()),
browser_info, browser_info,
connector,
..Default::default() ..Default::default()
}, },
PaymentAttemptUpdate::VoidUpdate { PaymentAttemptUpdate::VoidUpdate {
@ -256,7 +260,7 @@ mod tests {
let current_time = common_utils::date_time::now(); let current_time = common_utils::date_time::now();
let payment_attempt = PaymentAttemptNew { let payment_attempt = PaymentAttemptNew {
payment_id: payment_id.clone(), payment_id: payment_id.clone(),
connector: types::Connector::Dummy.to_string(), connector: Some(types::Connector::Dummy.to_string()),
created_at: current_time.into(), created_at: current_time.into(),
modified_at: current_time.into(), modified_at: current_time.into(),
..PaymentAttemptNew::default() ..PaymentAttemptNew::default()
@ -287,7 +291,7 @@ mod tests {
let payment_attempt = PaymentAttemptNew { let payment_attempt = PaymentAttemptNew {
payment_id: payment_id.clone(), payment_id: payment_id.clone(),
merchant_id: merchant_id.clone(), merchant_id: merchant_id.clone(),
connector: types::Connector::Dummy.to_string(), connector: Some(types::Connector::Dummy.to_string()),
created_at: current_time.into(), created_at: current_time.into(),
modified_at: current_time.into(), modified_at: current_time.into(),
..PaymentAttemptNew::default() ..PaymentAttemptNew::default()
@ -326,7 +330,7 @@ mod tests {
let payment_attempt = PaymentAttemptNew { let payment_attempt = PaymentAttemptNew {
payment_id: uuid.clone(), payment_id: uuid.clone(),
merchant_id: "1".to_string(), merchant_id: "1".to_string(),
connector: types::Connector::Dummy.to_string(), connector: Some(types::Connector::Dummy.to_string()),
created_at: current_time.into(), created_at: current_time.into(),
modified_at: current_time.into(), modified_at: current_time.into(),
// Adding a mandate_id // Adding a mandate_id

View File

@ -18,6 +18,7 @@ enum Derives {
Start, Start,
Verify, Verify,
Session, Session,
SessionData,
} }
impl From<String> for Derives { impl From<String> for Derives {
@ -35,6 +36,7 @@ impl From<String> for Derives {
"verify" => Self::Verify, "verify" => Self::Verify,
"verifydata" => Self::VerifyData, "verifydata" => Self::VerifyData,
"session" => Self::Session, "session" => Self::Session,
"sessiondata" => Self::SessionData,
_ => Self::Authorize, _ => Self::Authorize,
} }
} }
@ -107,6 +109,7 @@ impl Conversion {
Derives::Verify => syn::Ident::new("VerifyRequest", Span::call_site()), Derives::Verify => syn::Ident::new("VerifyRequest", Span::call_site()),
Derives::VerifyData => syn::Ident::new("VerifyRequestData", Span::call_site()), Derives::VerifyData => syn::Ident::new("VerifyRequestData", Span::call_site()),
Derives::Session => syn::Ident::new("PaymentsSessionRequest", Span::call_site()), Derives::Session => syn::Ident::new("PaymentsSessionRequest", Span::call_site()),
Derives::SessionData => syn::Ident::new("PaymentsSessionData", Span::call_site()),
} }
} }
@ -288,6 +291,7 @@ pub fn operation_derive_inner(token: proc_macro::TokenStream) -> proc_macro::Tok
PaymentsCaptureData, PaymentsCaptureData,
PaymentsCancelData, PaymentsCancelData,
PaymentsAuthorizeData, PaymentsAuthorizeData,
PaymentsSessionData,
api::{ api::{
PaymentsCaptureRequest, PaymentsCaptureRequest,

View File

@ -112,6 +112,8 @@ pub enum Flow {
PaymentsCapture, PaymentsCapture,
/// Payments cancel flow. /// Payments cancel flow.
PaymentsCancel, PaymentsCancel,
/// Payments Session Token flow
PaymentsSessionToken,
/// Payments start flow. /// Payments start flow.
PaymentsStart, PaymentsStart,
/// Payments list flow. /// Payments list flow.

View File

@ -0,0 +1,2 @@
ALTER TABLE payment_attempt ALTER COLUMN connector SET NOT NULL;
ALTER TABLE connector_response ALTER COLUMN connector_name SET NOT NULL;

View File

@ -0,0 +1,2 @@
ALTER TABLE payment_attempt ALTER COLUMN connector DROP NOT NULL;
ALTER TABLE connector_response ALTER COLUMN connector_name DROP NOT NULL;