feat(router): add start_redirection api for three_ds flow in v2 (#6470)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Sai Harsha Vardhan
2024-11-08 19:20:25 +05:30
committed by GitHub
parent 0389ae74e1
commit 6f24bb4ee3
19 changed files with 316 additions and 28 deletions

View File

@ -13383,6 +13383,14 @@
"payment_method_subtype": {
"$ref": "#/components/schemas/PaymentMethodType"
},
"next_action": {
"allOf": [
{
"$ref": "#/components/schemas/NextActionData"
}
],
"nullable": true
},
"connector_transaction_id": {
"type": "string",
"description": "A unique identifier for a payment provided by the connector",

View File

@ -2,8 +2,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
#[cfg(feature = "v2")]
use super::{
PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, PaymentsGetIntentRequest,
PaymentsIntentResponse,
PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest,
PaymentsGetIntentRequest, PaymentsIntentResponse,
};
#[cfg(all(
any(feature = "v2", feature = "v1"),
@ -365,3 +365,12 @@ impl ApiEventMetric for PaymentsSessionResponse {
})
}
}
#[cfg(feature = "v2")]
impl ApiEventMetric for PaymentStartRedirectionRequest {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Some(ApiEventsType::Payment {
payment_id: self.id.clone(),
})
}
}

View File

@ -3876,9 +3876,16 @@ pub enum NextActionType {
#[serde(tag = "type", rename_all = "snake_case")]
pub enum NextActionData {
/// Contains the url for redirection flow
#[cfg(feature = "v1")]
RedirectToUrl {
redirect_to_url: String,
},
/// Contains the url for redirection flow
#[cfg(feature = "v2")]
RedirectToUrl {
#[schema(value_type = String)]
redirect_to_url: Url,
},
/// Informs the next steps for bank transfer and also contains the charges details (ex: amount received, amount charged etc)
DisplayBankTransferInformation {
bank_transfer_steps_and_charges_details: BankTransferNextStepsData,
@ -4538,6 +4545,9 @@ pub struct PaymentsConfirmIntentResponse {
#[schema(value_type = PaymentMethodType, example = "apple_pay")]
pub payment_method_subtype: api_enums::PaymentMethodType,
/// Additional information required for redirection
pub next_action: Option<NextActionData>,
/// A unique identifier for a payment provided by the connector
#[schema(value_type = Option<String>, example = "993672945374576J")]
pub connector_transaction_id: Option<String>,
@ -4558,6 +4568,22 @@ pub struct PaymentsConfirmIntentResponse {
pub error: Option<ErrorDetails>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[cfg(feature = "v2")]
pub struct PaymentStartRedirectionRequest {
/// Global Payment ID
pub id: id_type::GlobalPaymentId,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[cfg(feature = "v2")]
pub struct PaymentStartRedirectionParams {
/// The identifier for the Merchant Account.
pub publishable_key: String,
/// The identifier for business profile
pub profile_id: id_type::ProfileId,
}
/// Fee information to be charged on the payment being collected
#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)]
pub struct PaymentChargeResponse {

View File

@ -770,7 +770,7 @@ pub struct PaymentAttemptUpdateInternal {
pub updated_by: String,
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
pub connector: Option<String>,
// authentication_data: Option<serde_json::Value>,
pub authentication_data: Option<pii::SecretSerdeValue>,
// encoded_data: Option<String>,
pub unified_code: Option<Option<String>>,
pub unified_message: Option<Option<String>>,

View File

@ -33,7 +33,7 @@ pub struct PaymentIntent {
pub last_synced: Option<PrimitiveDateTime>,
pub setup_future_usage: Option<storage_enums::FutureUsage>,
pub client_secret: common_utils::types::ClientSecret,
pub active_attempt_id: Option<String>,
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
#[diesel(deserialize_as = super::OptionalDieselArray<masking::Secret<OrderDetailsWithAmount>>)]
pub order_details: Option<Vec<masking::Secret<OrderDetailsWithAmount>>>,
pub allowed_payment_method_types: Option<pii::SecretSerdeValue>,
@ -250,7 +250,7 @@ pub struct PaymentIntentNew {
pub last_synced: Option<PrimitiveDateTime>,
pub setup_future_usage: Option<storage_enums::FutureUsage>,
pub client_secret: common_utils::types::ClientSecret,
pub active_attempt_id: Option<String>,
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
#[diesel(deserialize_as = super::OptionalDieselArray<masking::Secret<OrderDetailsWithAmount>>)]
pub order_details: Option<Vec<masking::Secret<OrderDetailsWithAmount>>>,
pub allowed_payment_method_types: Option<pii::SecretSerdeValue>,
@ -359,6 +359,7 @@ pub enum PaymentIntentUpdate {
ConfirmIntent {
status: storage_enums::IntentStatus,
updated_by: String,
active_attempt_id: common_utils::id_type::GlobalAttemptId,
},
/// Update the payment intent details on payment intent confirmation, after calling the connector
ConfirmIntentPostUpdate {
@ -520,7 +521,7 @@ pub struct PaymentIntentUpdateInternal {
// pub setup_future_usage: Option<storage_enums::FutureUsage>,
// pub metadata: Option<pii::SecretSerdeValue>,
pub modified_at: PrimitiveDateTime,
// pub active_attempt_id: Option<String>,
pub active_attempt_id: Option<common_utils::id_type::GlobalAttemptId>,
// pub description: Option<String>,
// pub statement_descriptor: Option<String>,
// #[diesel(deserialize_as = super::OptionalDieselArray<pii::SecretSerdeValue>)]
@ -594,7 +595,7 @@ impl PaymentIntentUpdate {
// setup_future_usage,
// metadata,
modified_at: _,
// active_attempt_id,
active_attempt_id,
// description,
// statement_descriptor,
// order_details,
@ -620,7 +621,7 @@ impl PaymentIntentUpdate {
// setup_future_usage: setup_future_usage.or(source.setup_future_usage),
// metadata: metadata.or(source.metadata),
modified_at: common_utils::date_time::now(),
// active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id),
active_attempt_id: active_attempt_id.or(source.active_attempt_id),
// description: description.or(source.description),
// statement_descriptor: statement_descriptor.or(source.statement_descriptor),
// order_details: order_details.or(source.order_details),
@ -735,15 +736,21 @@ impl PaymentIntentUpdate {
impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
fn from(payment_intent_update: PaymentIntentUpdate) -> Self {
match payment_intent_update {
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self {
PaymentIntentUpdate::ConfirmIntent {
status,
updated_by,
active_attempt_id,
} => Self {
status: Some(status),
modified_at: common_utils::date_time::now(),
updated_by,
active_attempt_id: Some(active_attempt_id),
},
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self {
status: Some(status),
modified_at: common_utils::date_time::now(),
updated_by,
active_attempt_id: None,
},
}
}

View File

@ -111,6 +111,24 @@ impl PaymentIntent {
pub fn get_id(&self) -> &id_type::GlobalPaymentId {
&self.id
}
#[cfg(feature = "v2")]
pub fn create_start_redirection_url(
&self,
base_url: &str,
publishable_key: String,
) -> CustomResult<url::Url, errors::api_error_response::ApiErrorResponse> {
let start_redirection_url = &format!(
"{}/v2/payments/{}/start_redirection?publishable_key={}&profile_id={}",
base_url,
self.get_id().get_string_repr(),
publishable_key,
self.profile_id.get_string_repr()
);
url::Url::parse(start_redirection_url)
.change_context(errors::api_error_response::ApiErrorResponse::InternalServerError)
.attach_printable("Error creating start redirection url")
}
}
#[cfg(feature = "v2")]
@ -272,8 +290,8 @@ pub struct PaymentIntent {
pub setup_future_usage: storage_enums::FutureUsage,
/// The client secret that is generated for the payment. This is used to authenticate the payment from client facing apis.
pub client_secret: common_utils::types::ClientSecret,
/// The active attempt for the payment intent. This is the payment attempt that is currently active for the payment intent.
pub active_attempt: Option<RemoteStorageObject<PaymentAttempt>>,
/// The active attempt id for the payment intent. This is the payment attempt that is currently active for the payment intent.
pub active_attempt_id: Option<id_type::GlobalAttemptId>,
/// The order details for the payment.
pub order_details: Option<Vec<Secret<OrderDetailsWithAmount>>>,
/// This is the list of payment method types that are allowed for the payment intent.
@ -421,7 +439,7 @@ impl PaymentIntent {
last_synced: None,
setup_future_usage: request.setup_future_usage.unwrap_or_default(),
client_secret,
active_attempt: None,
active_attempt_id: None,
order_details,
allowed_payment_method_types,
connector_metadata,

View File

@ -1296,6 +1296,7 @@ pub enum PaymentAttemptUpdate {
status: storage_enums::AttemptStatus,
connector_payment_id: Option<String>,
updated_by: String,
authentication_data: Option<pii::SecretSerdeValue>,
},
/// Update the payment attempt on confirming the intent, after calling the connector on error response
ConfirmIntentError {
@ -1923,6 +1924,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
unified_message: None,
connector_payment_id: None,
connector: Some(connector),
authentication_data: None,
},
PaymentAttemptUpdate::ConfirmIntentError {
status,
@ -1941,11 +1943,13 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
unified_message: None,
connector_payment_id: None,
connector: None,
authentication_data: None,
},
PaymentAttemptUpdate::ConfirmIntentResponse {
status,
connector_payment_id,
updated_by,
authentication_data,
} => Self {
status: Some(status),
error_message: None,
@ -1959,6 +1963,7 @@ impl From<PaymentAttemptUpdate> for diesel_models::PaymentAttemptUpdateInternal
unified_message: None,
connector_payment_id,
connector: None,
authentication_data,
},
}
}

View File

@ -274,6 +274,7 @@ pub enum PaymentIntentUpdate {
ConfirmIntent {
status: storage_enums::IntentStatus,
updated_by: String,
active_attempt_id: id_type::GlobalAttemptId,
},
ConfirmIntentPostUpdate {
status: storage_enums::IntentStatus,
@ -363,9 +364,14 @@ pub struct PaymentIntentUpdateInternal {
impl From<PaymentIntentUpdate> for PaymentIntentUpdateInternal {
fn from(payment_intent_update: PaymentIntentUpdate) -> Self {
match payment_intent_update {
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => Self {
PaymentIntentUpdate::ConfirmIntent {
status,
updated_by,
active_attempt_id,
} => Self {
status: Some(status),
updated_by,
active_attempt_id: Some(active_attempt_id),
..Default::default()
},
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => Self {
@ -582,9 +588,15 @@ use diesel_models::{
impl From<PaymentIntentUpdate> for DieselPaymentIntentUpdate {
fn from(value: PaymentIntentUpdate) -> Self {
match value {
PaymentIntentUpdate::ConfirmIntent { status, updated_by } => {
Self::ConfirmIntent { status, updated_by }
}
PaymentIntentUpdate::ConfirmIntent {
status,
updated_by,
active_attempt_id,
} => Self::ConfirmIntent {
status,
updated_by,
active_attempt_id,
},
PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => {
Self::ConfirmIntentPostUpdate { status, updated_by }
}
@ -1134,7 +1146,7 @@ impl behaviour::Conversion for PaymentIntent {
last_synced,
setup_future_usage,
client_secret,
active_attempt,
active_attempt_id,
order_details,
allowed_payment_method_types,
connector_metadata,
@ -1182,7 +1194,7 @@ impl behaviour::Conversion for PaymentIntent {
last_synced,
setup_future_usage: Some(setup_future_usage),
client_secret,
active_attempt_id: active_attempt.map(|attempt| attempt.get_id()),
active_attempt_id,
order_details: order_details.map(|order_details| {
order_details
.into_iter()
@ -1319,9 +1331,7 @@ impl behaviour::Conversion for PaymentIntent {
last_synced: storage_model.last_synced,
setup_future_usage: storage_model.setup_future_usage.unwrap_or_default(),
client_secret: storage_model.client_secret,
active_attempt: storage_model
.active_attempt_id
.map(RemoteStorageObject::ForeignID),
active_attempt_id: storage_model.active_attempt_id,
order_details: storage_model.order_details.map(|order_details| {
order_details
.into_iter()
@ -1395,7 +1405,7 @@ impl behaviour::Conversion for PaymentIntent {
last_synced: self.last_synced,
setup_future_usage: Some(self.setup_future_usage),
client_secret: self.client_secret,
active_attempt_id: self.active_attempt.map(|attempt| attempt.get_id()),
active_attempt_id: self.active_attempt_id,
order_details: self.order_details,
allowed_payment_method_types: self
.allowed_payment_method_types

View File

@ -37,6 +37,8 @@ use futures::future::join_all;
use helpers::{decrypt_paze_token, ApplePayData};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::payments::{PaymentConfirmData, PaymentIntentData};
#[cfg(feature = "v2")]
use hyperswitch_domain_models::router_response_types::RedirectForm;
pub use hyperswitch_domain_models::{
mandates::{CustomerAcceptance, MandateData},
payment_address::PaymentAddress,
@ -1463,7 +1465,7 @@ where
let (payment_data, _req, customer) = payments_intent_operation_core::<_, _, _, _>(
&state,
req_state,
merchant_account,
merchant_account.clone(),
profile,
key_store,
operation.clone(),
@ -1481,6 +1483,7 @@ where
None,
None,
header_payload.x_hs_latency,
&merchant_account,
)
}
@ -1521,7 +1524,7 @@ where
payments_operation_core::<_, _, _, _, _>(
&state,
req_state,
merchant_account,
merchant_account.clone(),
key_store,
profile,
operation.clone(),
@ -1541,6 +1544,7 @@ where
connector_http_status_code,
external_latency,
header_payload.x_hs_latency,
&merchant_account,
)
}
@ -5966,6 +5970,73 @@ pub async fn payment_external_authentication(
))
}
#[instrument(skip_all)]
#[cfg(feature = "v2")]
pub async fn payment_start_redirection(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
req: api_models::payments::PaymentStartRedirectionRequest,
) -> RouterResponse<serde_json::Value> {
let db = &*state.store;
let key_manager_state = &(&state).into();
let storage_scheme = merchant_account.storage_scheme;
let payment_intent = db
.find_payment_intent_by_id(key_manager_state, &req.id, &key_store, storage_scheme)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
//TODO: send valid html error pages in this case, or atleast redirect to valid html error pages
utils::when(
payment_intent.status != storage_enums::IntentStatus::RequiresCustomerAction,
|| {
Err(errors::ApiErrorResponse::PaymentUnexpectedState {
current_flow: "PaymentStartRedirection".to_string(),
field_name: "status".to_string(),
current_value: payment_intent.status.to_string(),
states: ["requires_customer_action".to_string()].join(", "),
})
},
)?;
let payment_attempt = db
.find_payment_attempt_by_id(
key_manager_state,
&key_store,
payment_intent
.active_attempt_id
.clone()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("missing active attempt in payment_intent")?
.get_string_repr(),
storage_scheme,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Error while fetching payment_attempt")?;
let redirection_data = payment_attempt
.authentication_data
.clone()
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("missing authentication_data in payment_attempt")?;
let form: RedirectForm = serde_json::from_value(redirection_data.expose()).map_err(|err| {
logger::error!(error = ?err, "Failed to deserialize redirection data");
errors::ApiErrorResponse::InternalServerError
})?;
Ok(services::ApplicationResponse::Form(Box::new(
services::RedirectionFormData {
redirect_form: form,
payment_method_data: None,
amount: payment_attempt.amount_details.net_amount.to_string(),
currency: payment_intent.amount_details.currency.to_string(),
},
)))
}
#[instrument(skip_all)]
pub async fn get_extended_card_info(
state: SessionState,

View File

@ -328,6 +328,7 @@ impl<F: Clone> UpdateTracker<F, PaymentConfirmData<F>, PaymentsConfirmIntentRequ
hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntent {
status: intent_status,
updated_by: storage_scheme.to_string(),
active_attempt_id: payment_data.payment_attempt.get_id().clone(),
};
let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent {

View File

@ -2245,6 +2245,13 @@ impl<F: Clone> PostUpdateTracker<F, PaymentConfirmData<F>, types::PaymentsAuthor
types::ResponseId::ConnectorTransactionId(id)
| types::ResponseId::EncodedData(id) => Some(id),
};
let authentication_data = (*redirection_data)
.as_ref()
.map(Encode::encode_to_value)
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Could not parse the connector response")?
.map(masking::Secret::new);
let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::ConfirmIntentPostUpdate { status: intent_status, updated_by: storage_scheme.to_string() };
let updated_payment_intent = db
@ -2260,7 +2267,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentConfirmData<F>, types::PaymentsAuthor
.attach_printable("Unable to update payment intent")?;
payment_data.payment_intent = updated_payment_intent;
let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentResponse { status: attempt_status, connector_payment_id, updated_by: storage_scheme.to_string() };
let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntentResponse { status: attempt_status, connector_payment_id, updated_by: storage_scheme.to_string(), authentication_data };
let updated_payment_attempt = db
.update_payment_attempt(
key_manager_state,

View File

@ -628,6 +628,7 @@ where
connector_http_status_code: Option<u16>,
external_latency: Option<u128>,
is_latency_header_enabled: Option<bool>,
merchant_account: &domain::MerchantAccount,
) -> RouterResponse<Self>;
}
@ -793,6 +794,7 @@ where
_connector_http_status_code: Option<u16>,
_external_latency: Option<u128>,
_is_latency_header_enabled: Option<bool>,
_merchant_account: &domain::MerchantAccount,
) -> RouterResponse<Self> {
let payment_intent = payment_data.get_payment_intent();
Ok(services::ApplicationResponse::JsonWithHeaders((
@ -862,12 +864,13 @@ where
fn generate_response(
payment_data: D,
_customer: Option<domain::Customer>,
_base_url: &str,
base_url: &str,
operation: Op,
_connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig,
_connector_http_status_code: Option<u16>,
_external_latency: Option<u128>,
_is_latency_header_enabled: Option<bool>,
merchant_account: &domain::MerchantAccount,
) -> RouterResponse<Self> {
let payment_intent = payment_data.get_payment_intent();
let payment_attempt = payment_data.get_payment_attempt();
@ -896,6 +899,14 @@ where
.clone()
.map(api_models::payments::ErrorDetails::foreign_from);
// TODO: Add support for other next actions, currently only supporting redirect to url
let redirect_to_url = payment_intent
.create_start_redirection_url(base_url, merchant_account.publishable_key.clone())?;
let next_action = payment_attempt
.authentication_data
.as_ref()
.map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url });
let response = Self {
id: payment_intent.id.clone(),
status: payment_intent.status,
@ -906,6 +917,7 @@ where
payment_method_data: None,
payment_method_type: payment_attempt.payment_method_type,
payment_method_subtype: payment_attempt.payment_method_subtype,
next_action,
connector_transaction_id: payment_attempt.connector_payment_id.clone(),
connector_reference_id: None,
merchant_connector_id,

View File

@ -535,6 +535,10 @@ impl Payments {
.service(
web::resource("/create-external-sdk-tokens")
.route(web::post().to(payments::payments_connector_session)),
)
.service(
web::resource("/start_redirection")
.route(web::get().to(payments::payments_start_redirection)),
),
);

View File

@ -140,7 +140,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::PaymentsConfirmIntent
| Flow::PaymentsCreateIntent
| Flow::PaymentsGetIntent
| Flow::PaymentsPostSessionTokens => Self::Payments,
| Flow::PaymentsPostSessionTokens
| Flow::PaymentStartRedirection => Self::Payments,
Flow::PayoutsCreate
| Flow::PayoutsRetrieve

View File

@ -2057,6 +2057,56 @@ mod internal_payload_types {
}
}
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentStartRedirection, payment_id))]
pub async fn payments_start_redirection(
state: web::Data<app::AppState>,
req: actix_web::HttpRequest,
payload: web::Query<api_models::payments::PaymentStartRedirectionParams>,
path: web::Path<common_utils::id_type::GlobalPaymentId>,
) -> impl Responder {
let flow = Flow::PaymentStartRedirection;
let global_payment_id = path.into_inner();
tracing::Span::current().record("payment_id", global_payment_id.get_string_repr());
let publishable_key = &payload.publishable_key;
let profile_id = &payload.profile_id;
let payment_start_redirection_request = api_models::payments::PaymentStartRedirectionRequest {
id: global_payment_id.clone(),
};
let internal_payload = internal_payload_types::PaymentsGenericRequestWithResourceId {
global_payment_id: global_payment_id.clone(),
payload: payment_start_redirection_request.clone(),
};
let locking_action = internal_payload.get_locking_input(flow.clone());
Box::pin(api::server_wrap(
flow,
state,
&req,
payment_start_redirection_request.clone(),
|state, auth: auth::AuthenticationData, _req, req_state| async {
payments::payment_start_redirection(
state,
auth.merchant_account,
auth.key_store,
payment_start_redirection_request.clone(),
)
.await
},
&auth::PublishableKeyAndProfileIdAuth {
publishable_key: publishable_key.clone(),
profile_id: profile_id.clone(),
},
locking_action,
))
.await
}
#[cfg(feature = "v2")]
#[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirmIntent, payment_id))]
pub async fn payment_confirm_intent(

View File

@ -1288,6 +1288,61 @@ where
}
}
#[derive(Debug)]
#[cfg(feature = "v2")]
pub struct PublishableKeyAndProfileIdAuth {
pub publishable_key: String,
pub profile_id: id_type::ProfileId,
}
#[async_trait]
#[cfg(feature = "v2")]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for PublishableKeyAndProfileIdAuth
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
_request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationData, AuthenticationType)> {
let key_manager_state = &(&state.session_state()).into();
let (merchant_account, key_store) = state
.store()
.find_merchant_account_by_publishable_key(
key_manager_state,
self.publishable_key.as_str(),
)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(errors::ApiErrorResponse::Unauthorized)
} else {
e.change_context(errors::ApiErrorResponse::InternalServerError)
}
})?;
let profile = state
.store()
.find_business_profile_by_profile_id(key_manager_state, &key_store, &self.profile_id)
.await
.to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
id: self.profile_id.get_string_repr().to_owned(),
})?;
let merchant_id = merchant_account.get_id().clone();
Ok((
AuthenticationData {
merchant_account,
key_store,
profile,
},
AuthenticationType::PublishableKey { merchant_id },
))
}
}
#[derive(Debug)]
pub struct PublishableKeyAuth;

View File

@ -512,6 +512,8 @@ pub enum Flow {
PaymentsConfirmIntent,
/// Payments post session tokens flow
PaymentsPostSessionTokens,
/// Payments start redirection flow
PaymentStartRedirection,
}
///

View File

@ -87,6 +87,7 @@ ALTER COLUMN currency DROP NOT NULL,
ALTER COLUMN client_secret DROP NOT NULL,
ALTER COLUMN profile_id DROP NOT NULL;
ALTER TABLE payment_intent ALTER COLUMN active_attempt_id SET NOT NULL;
ALTER TABLE payment_intent ALTER COLUMN active_attempt_id SET DEFAULT 'xxx';
ALTER TABLE payment_intent ALTER COLUMN session_expiry DROP NOT NULL;
------------------------ Payment Attempt -----------------------

View File

@ -130,4 +130,5 @@ ALTER TABLE payment_intent ALTER COLUMN session_expiry SET NOT NULL;
-- This migration is to make fields optional in payment_intent table
ALTER TABLE payment_intent ALTER COLUMN active_attempt_id DROP NOT NULL;
ALTER TABLE payment_intent
ALTER COLUMN active_attempt_id DROP DEFAULT;