feat(connector): [Airwallex] add multiple redirect support for 3DS (#811)

Co-authored-by: Narayan Bhat <narayan.bhat@juspay.in>
Co-authored-by: Jagan Elavarasan <jaganelavarasan@gmail.com>
This commit is contained in:
SamraatBansal
2023-04-13 13:40:30 +05:30
committed by GitHub
parent 01bc162d25
commit d1d58e33b7
19 changed files with 374 additions and 84 deletions

View File

@ -40,6 +40,7 @@ pub enum AttemptStatus {
Failure,
PaymentMethodAwaited,
ConfirmationAwaited,
DeviceDataCollectionPending,
}
#[derive(
@ -767,9 +768,10 @@ impl From<AttemptStatus> for IntentStatus {
AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod,
AttemptStatus::Authorized => Self::RequiresCapture,
AttemptStatus::AuthenticationPending => Self::RequiresCustomerAction,
AttemptStatus::AuthenticationPending | AttemptStatus::DeviceDataCollectionPending => {
Self::RequiresCustomerAction
}
AttemptStatus::Unresolved => Self::RequiresMerchantAction,
AttemptStatus::PartialCharged
| AttemptStatus::Started
| AttemptStatus::AuthenticationSuccessful

View File

@ -1222,6 +1222,8 @@ pub struct Metadata {
#[schema(value_type = Object, example = r#"{ "city": "NY", "unit": "245" }"#)]
#[serde(flatten)]
pub data: pii::SecretSerdeValue,
/// Payload coming in request as a metadata field
pub payload: Option<pii::SecretSerdeValue>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]

View File

@ -88,7 +88,7 @@ impl ConnectorCommon for Airwallex {
}
impl api::Payment for Airwallex {}
impl api::PaymentsCompleteAuthorize for Airwallex {}
impl api::PreVerify for Airwallex {}
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
for Airwallex
@ -461,6 +461,95 @@ impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsRe
}
}
impl
ConnectorIntegration<
api::CompleteAuthorize,
types::CompleteAuthorizeData,
types::PaymentsResponseData,
> for Airwallex
{
fn get_headers(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
self.build_headers(req, connectors)
}
fn get_content_type(&self) -> &'static str {
self.common_get_content_type()
}
fn get_url(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<String, errors::ConnectorError> {
let connector_payment_id = req
.request
.connector_transaction_id
.clone()
.ok_or(errors::ConnectorError::MissingConnectorTransactionID)?;
Ok(format!(
"{}api/v1/pa/payment_intents/{}/confirm_continue",
self.base_url(connectors),
connector_payment_id,
))
}
fn get_request_body(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
) -> CustomResult<Option<String>, errors::ConnectorError> {
let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?;
let req = utils::Encode::<airwallex::AirwallexCompleteRequest>::encode_to_string_of_json(
&req_obj,
)
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
Ok(Some(req))
}
fn build_request(
&self,
req: &types::PaymentsCompleteAuthorizeRouterData,
connectors: &settings::Connectors,
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
Ok(Some(
services::RequestBuilder::new()
.method(services::Method::Post)
.url(&types::PaymentsComeplteAuthorizeType::get_url(
self, req, connectors,
)?)
.headers(types::PaymentsComeplteAuthorizeType::get_headers(
self, req, connectors,
)?)
.body(types::PaymentsComeplteAuthorizeType::get_request_body(
self, req,
)?)
.build(),
))
}
fn handle_response(
&self,
data: &types::PaymentsCompleteAuthorizeRouterData,
res: Response,
) -> CustomResult<types::PaymentsCompleteAuthorizeRouterData, errors::ConnectorError> {
let response: airwallex::AirwallexPaymentsResponse = res
.response
.parse_struct("AirwallexPaymentsResponse")
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
types::RouterData::try_from(types::ResponseRouterData {
response,
data: data.clone(),
http_code: res.status_code,
})
.change_context(errors::ConnectorError::ResponseHandlingFailed)
}
fn get_error_response(
&self,
res: Response,
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
self.build_error_response(res)
}
}
impl api::PaymentCapture for Airwallex {}
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
for Airwallex
@ -906,3 +995,18 @@ impl api::IncomingWebhook for Airwallex {
Ok(details.data.object)
}
}
impl services::ConnectorRedirectResponse for Airwallex {
fn get_flow_type(
&self,
_query_params: &str,
_json_payload: Option<serde_json::Value>,
action: services::PaymentAction,
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
match action {
services::PaymentAction::PSync | services::PaymentAction::CompleteAuthorize => {
Ok(payments::CallConnectorAction::Trigger)
}
}
}
}

View File

@ -110,7 +110,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for AirwallexPaymentsRequest {
request_id: Uuid::new_v4().to_string(),
payment_method,
payment_method_options,
return_url: item.request.router_return_url.clone(),
return_url: item.request.complete_authorize_url.clone(),
})
}
}
@ -140,6 +140,39 @@ impl<F, T> TryFrom<types::ResponseRouterData<F, AirwallexAuthUpdateResponse, T,
}
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexCompleteRequest {
request_id: String,
three_ds: AirwallexThreeDsData,
#[serde(rename = "type")]
three_ds_type: AirwallexThreeDsType,
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexThreeDsData {
acs_response: Option<common_utils::pii::SecretSerdeValue>,
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub enum AirwallexThreeDsType {
#[default]
#[serde(rename = "3ds_continue")]
ThreeDSContinue,
}
impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for AirwallexCompleteRequest {
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result<Self, Self::Error> {
Ok(Self {
request_id: Uuid::new_v4().to_string(),
three_ds: AirwallexThreeDsData {
acs_response: item.request.payload.clone().map(Secret::new),
},
three_ds_type: AirwallexThreeDsType::ThreeDSContinue,
})
}
}
#[derive(Default, Debug, Serialize, Eq, PartialEq)]
pub struct AirwallexPaymentsCaptureRequest {
// Unique ID to be sent for each transaction/operation request to the connector
@ -191,27 +224,43 @@ pub enum AirwallexPaymentStatus {
Cancelled,
}
impl From<AirwallexPaymentStatus> for enums::AttemptStatus {
fn from(item: AirwallexPaymentStatus) -> Self {
match item {
AirwallexPaymentStatus::Succeeded => Self::Charged,
AirwallexPaymentStatus::Failed => Self::Failure,
AirwallexPaymentStatus::Pending => Self::Pending,
AirwallexPaymentStatus::RequiresPaymentMethod => Self::PaymentMethodAwaited,
AirwallexPaymentStatus::RequiresCustomerAction => Self::AuthenticationPending,
AirwallexPaymentStatus::RequiresCapture => Self::Authorized,
AirwallexPaymentStatus::Cancelled => Self::Voided,
}
fn get_payment_status(response: &AirwallexPaymentsResponse) -> enums::AttemptStatus {
match response.status.clone() {
AirwallexPaymentStatus::Succeeded => enums::AttemptStatus::Charged,
AirwallexPaymentStatus::Failed => enums::AttemptStatus::Failure,
AirwallexPaymentStatus::Pending => enums::AttemptStatus::Pending,
AirwallexPaymentStatus::RequiresPaymentMethod => enums::AttemptStatus::PaymentMethodAwaited,
AirwallexPaymentStatus::RequiresCustomerAction => response.next_action.as_ref().map_or(
enums::AttemptStatus::AuthenticationPending,
|next_action| match next_action.stage {
AirwallexNextActionStage::WaitingDeviceDataCollection => {
enums::AttemptStatus::DeviceDataCollectionPending
}
AirwallexNextActionStage::WaitingUserInfoInput => {
enums::AttemptStatus::AuthenticationPending
}
},
),
AirwallexPaymentStatus::RequiresCapture => enums::AttemptStatus::Authorized,
AirwallexPaymentStatus::Cancelled => enums::AttemptStatus::Voided,
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum AirwallexNextActionStage {
WaitingDeviceDataCollection,
WaitingUserInfoInput,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AirwallexRedirectFormData {
#[serde(rename = "JWT")]
jwt: String,
jwt: Option<String>,
#[serde(rename = "threeDSMethodData")]
three_ds_method_data: String,
token: String,
three_ds_method_data: Option<String>,
token: Option<String>,
provider: Option<String>,
version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -219,6 +268,7 @@ pub struct AirwallexPaymentsNextAction {
url: Url,
method: services::Method,
data: AirwallexRedirectFormData,
stage: AirwallexNextActionStage,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -232,11 +282,46 @@ pub struct AirwallexPaymentsResponse {
next_action: Option<AirwallexPaymentsNextAction>,
}
fn get_redirection_form(
response_url_data: AirwallexPaymentsNextAction,
) -> Option<services::RedirectForm> {
Some(services::RedirectForm {
endpoint: response_url_data.url.to_string(),
method: response_url_data.method,
form_fields: std::collections::HashMap::from([
//Some form fields might be empty based on the authentication type by the connector
(
"JWT".to_string(),
response_url_data.data.jwt.unwrap_or_default(),
),
(
"threeDSMethodData".to_string(),
response_url_data
.data
.three_ds_method_data
.unwrap_or_default(),
),
(
"token".to_string(),
response_url_data.data.token.unwrap_or_default(),
),
(
"provider".to_string(),
response_url_data.data.provider.unwrap_or_default(),
),
(
"version".to_string(),
response_url_data.data.version.unwrap_or_default(),
),
]),
})
}
impl<F, T>
TryFrom<types::ResponseRouterData<F, AirwallexPaymentsResponse, T, types::PaymentsResponseData>>
for types::RouterData<F, T, types::PaymentsResponseData>
{
type Error = error_stack::Report<errors::ParsingError>;
type Error = error_stack::Report<errors::ConnectorError>;
fn try_from(
item: types::ResponseRouterData<
F,
@ -245,23 +330,48 @@ impl<F, T>
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let redirection_data =
item.response
.next_action
.map(|response_url_data| services::RedirectForm {
endpoint: response_url_data.url.to_string(),
method: response_url_data.method,
form_fields: std::collections::HashMap::from([
("JWT".to_string(), response_url_data.data.jwt),
(
"threeDSMethodData".to_string(),
response_url_data.data.three_ds_method_data,
),
("token".to_string(), response_url_data.data.token),
]),
});
let (status, redirection_data) = item.response.next_action.clone().map_or(
// If no next action is there, map the status and set redirection form as None
(get_payment_status(&item.response), None),
|response_url_data| {
// If the connector sends a customer action response that is already under
// process from our end it can cause an infinite loop to break this this check
// is added and fail the payment
if matches!(
(
response_url_data.stage.clone(),
item.data.status,
item.response.status.clone(),
),
// If the connector sends waiting for DDC and our status is already DDC Pending
// that means we initiated the call to collect the data and now we expect a different response
(
AirwallexNextActionStage::WaitingDeviceDataCollection,
enums::AttemptStatus::DeviceDataCollectionPending,
_
)
// If the connector sends waiting for Customer Action and our status is already Authenticaition Pending
// that means we initiated the call to authenticate and now we do not expect a requires_customer action
// it will start a loop
| (
_,
enums::AttemptStatus::AuthenticationPending,
AirwallexPaymentStatus::RequiresCustomerAction,
)
) {
// Fail the payment for above conditions
(enums::AttemptStatus::AuthenticationFailed, None)
} else {
(
//Build the redirect form and update the payment status
get_payment_status(&item.response),
get_redirection_form(response_url_data),
)
}
},
);
Ok(Self {
status: enums::AttemptStatus::from(item.response.status),
status,
reference_id: Some(item.response.id.clone()),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(item.response.id),

View File

@ -6,6 +6,7 @@ pub mod transformers;
use std::{fmt::Debug, marker::PhantomData, time::Instant};
use api_models::payments::Metadata;
use error_stack::{IntoReport, ResultExt};
use futures::future::join_all;
use router_env::tracing;
@ -247,6 +248,14 @@ pub trait PaymentRedirectFlow: Sync {
fn get_payment_action(&self) -> services::PaymentAction;
fn generate_response(
&self,
payments_response: api_models::payments::PaymentsResponse,
merchant_account: storage_models::merchant_account::MerchantAccount,
payment_id: String,
connector: String,
) -> RouterResult<api::RedirectionResponse>;
#[allow(clippy::too_many_arguments)]
async fn handle_payments_redirect_response(
&self,
@ -290,13 +299,8 @@ pub trait PaymentRedirectFlow: Sync {
.attach_printable("Failed to get the response in json"),
}?;
let result = helpers::get_handle_response_url(
resource_id,
&merchant_account,
payments_response,
connector,
)
.attach_printable("No redirection response")?;
let result =
self.generate_response(payments_response, merchant_account, resource_id, connector)?;
Ok(services::ApplicationResponse::JsonForRedirection(result))
}
@ -317,6 +321,11 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
let payment_confirm_req = api::PaymentsRequest {
payment_id: Some(req.resource_id.clone()),
merchant_id: req.merchant_id.clone(),
metadata: Some(Metadata {
order_details: None,
data: masking::Secret::new("{}".into()),
payload: Some(req.json_payload.unwrap_or("{}".into()).into()),
}),
..Default::default()
};
payments_core::<api::CompleteAuthorize, api::PaymentsResponse, _, _, _>(
@ -333,6 +342,47 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
fn get_payment_action(&self) -> services::PaymentAction {
services::PaymentAction::CompleteAuthorize
}
fn generate_response(
&self,
payments_response: api_models::payments::PaymentsResponse,
merchant_account: storage_models::merchant_account::MerchantAccount,
payment_id: String,
connector: String,
) -> RouterResult<api::RedirectionResponse> {
// There might be multiple redirections needed for some flows
// If the status is requires customer action, then send the startpay url again
// The redirection data must have been provided and updated by the connector
match payments_response.status {
api_models::enums::IntentStatus::RequiresCustomerAction => {
let startpay_url = payments_response
.next_action
.and_then(|next_action| next_action.redirect_to_url)
.ok_or(errors::ApiErrorResponse::InternalServerError)
.into_report()
.attach_printable(
"did not receive redirect to url when status is requires customer action",
)?;
Ok(api::RedirectionResponse {
return_url: String::new(),
params: vec![],
return_url_with_query_params: startpay_url,
http_method: "GET".to_string(),
headers: vec![],
})
}
// If the status is terminal status, then redirect to merchant return url to provide status
api_models::enums::IntentStatus::Succeeded
| api_models::enums::IntentStatus::Failed
| api_models::enums::IntentStatus::Cancelled | api_models::enums::IntentStatus::RequiresCapture=> helpers::get_handle_response_url(
payment_id,
&merchant_account,
payments_response,
connector,
),
_ => Err(errors::ApiErrorResponse::InternalServerError).into_report().attach_printable_lazy(|| format!("Could not proceed with payment as payment status {} cannot be handled during redirection",payments_response.status))?
}
}
}
#[derive(Clone, Debug)]
@ -371,6 +421,21 @@ impl PaymentRedirectFlow for PaymentRedirectSync {
.await
}
fn generate_response(
&self,
payments_response: api_models::payments::PaymentsResponse,
merchant_account: storage_models::merchant_account::MerchantAccount,
payment_id: String,
connector: String,
) -> RouterResult<api::RedirectionResponse> {
helpers::get_handle_response_url(
payment_id,
&merchant_account,
payments_response,
connector,
)
}
fn get_payment_action(&self) -> services::PaymentAction {
services::PaymentAction::PSync
}

View File

@ -88,7 +88,6 @@ macro_rules! default_imp_for_complete_authorize{
default_imp_for_complete_authorize!(
connector::Aci,
connector::Adyen,
connector::Airwallex,
connector::Applepay,
connector::Authorizedotnet,
connector::Bambora,
@ -131,7 +130,6 @@ macro_rules! default_imp_for_connector_redirect_response{
default_imp_for_connector_redirect_response!(
connector::Aci,
connector::Adyen,
connector::Airwallex,
connector::Applepay,
connector::Authorizedotnet,
connector::Bambora,

View File

@ -339,7 +339,7 @@ pub fn create_startpay_url(
payment_intent: &storage::PaymentIntent,
) -> String {
format!(
"{}/payments/start/{}/{}/{}",
"{}/payments/redirect/{}/{}/{}",
server.base_url,
payment_intent.payment_id,
payment_intent.merchant_id,
@ -355,7 +355,7 @@ pub fn create_redirect_url(
) -> String {
let creds_identifier_path = creds_identifier.map_or_else(String::new, |cd| format!("/{}", cd));
format!(
"{}/payments/{}/{}/response/{}",
"{}/payments/{}/{}/redirect/response/{}",
router_base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name,
) + &creds_identifier_path
}
@ -376,7 +376,7 @@ pub fn create_complete_authorize_url(
connector_name: &String,
) -> String {
format!(
"{}/payments/{}/{}/complete/{}",
"{}/payments/{}/{}/redirect/complete/{}",
router_base_url, payment_attempt.payment_id, payment_attempt.merchant_id, connector_name
)
}
@ -933,6 +933,7 @@ pub fn get_handle_response_url(
connector: String,
) -> RouterResult<api::RedirectionResponse> {
let payments_return_url = response.return_url.as_ref();
let redirection_response = make_pg_redirect_response(payment_id, &response, connector);
let return_url = make_merchant_url_with_response(

View File

@ -2,6 +2,7 @@ use std::marker::PhantomData;
use async_trait::async_trait;
use error_stack::ResultExt;
use masking::ExposeOptionInterface;
use router_derive::PaymentOperation;
use router_env::{instrument, tracing};
@ -45,7 +46,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
let db = &*state.store;
let merchant_id = &merchant_account.merchant_id;
let storage_scheme = merchant_account.storage_scheme;
let (mut payment_intent, mut payment_attempt, currency, amount, connector_response);
let (mut payment_intent, mut payment_attempt, currency, amount);
let payment_id = payment_id
.get_payment_intent_id()
@ -154,7 +155,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
)
.await?;
connector_response = db
let mut connector_response = db
.find_connector_response_by_payment_id_merchant_id_attempt_id(
&payment_attempt.payment_id,
&payment_attempt.merchant_id,
@ -166,6 +167,13 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Co
error.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)
})?;
connector_response.encoded_data = request.metadata.clone().and_then(|secret_metadata| {
secret_metadata
.payload
.expose_option()
.map(|exposed_payload| exposed_payload.to_string())
});
payment_intent.shipping_address_id = shipping_address.clone().map(|i| i.address_id);
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());

View File

@ -318,17 +318,10 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
let payment_method = payment_data.payment_attempt.payment_method;
let browser_info = payment_data.payment_attempt.browser_info.clone();
let (intent_status, attempt_status) = match payment_data.payment_attempt.authentication_type
{
Some(storage_enums::AuthenticationType::NoThreeDs) => (
storage_enums::IntentStatus::Processing,
storage_enums::AttemptStatus::Pending,
),
_ => (
storage_enums::IntentStatus::RequiresCustomerAction,
storage_enums::AttemptStatus::AuthenticationPending,
),
};
let (intent_status, attempt_status) = (
storage_enums::IntentStatus::Processing,
storage_enums::AttemptStatus::Pending,
);
let connector = payment_data.payment_attempt.connector.clone();
let straight_through_algorithm = payment_data

View File

@ -1,6 +1,6 @@
use std::{fmt::Debug, marker::PhantomData};
use error_stack::ResultExt;
use error_stack::{IntoReport, ResultExt};
use router_env::{instrument, tracing};
use super::{flows::Feature, PaymentAddress, PaymentData};
@ -681,6 +681,14 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::CompleteAuthoriz
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "browser_info",
})?;
let json_payload = payment_data
.connector_response
.encoded_data
.map(serde_json::to_value)
.transpose()
.into_report()
.change_context(errors::ApiErrorResponse::InternalServerError)?;
Ok(Self {
setup_future_usage: payment_data.payment_intent.setup_future_usage,
mandate_id: payment_data.mandate_id.clone(),
@ -695,6 +703,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::CompleteAuthoriz
email: payment_data.email,
payment_method_data: payment_data.payment_method_data,
connector_transaction_id: payment_data.connector_response.connector_transaction_id,
payload: json_payload,
connector_meta: payment_data.payment_attempt.connector_metadata,
})
}

View File

@ -109,22 +109,21 @@ impl Payments {
web::resource("/{payment_id}/capture").route(web::post().to(payments_capture)),
)
.service(
web::resource("/start/{payment_id}/{merchant_id}/{attempt_id}")
web::resource("/redirect/{payment_id}/{merchant_id}/{attempt_id}")
.route(web::get().to(payments_start)),
)
.service(
web::resource(
"/{payment_id}/{merchant_id}/response/{connector}/{creds_identifier}",
"/{payment_id}/{merchant_id}/redirect/response/{connector}/{creds_identifier}",
)
.route(web::get().to(payments_redirect_response_with_creds_identifier)),
)
.service(
web::resource("/{payment_id}/{merchant_id}/response/{connector}")
web::resource("/{payment_id}/{merchant_id}/redirect/response/{connector}")
.route(web::get().to(payments_redirect_response)),
)
.service(
web::resource("/{payment_id}/{merchant_id}/complete/{connector}")
.route(web::get().to(payments_complete_authorize))
web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}")
.route(web::post().to(payments_complete_authorize)),
);
}

View File

@ -60,12 +60,12 @@ pub async fn payments_create(
.await
}
// /// Payments - Start
// /// Payments - Redirect
// ///
// /// The entry point for a payment which involves the redirection flow. This redirects the user to the authentication page
// /// For a payment which involves the redirection flow. This redirects the user to the authentication page
// #[utoipa::path(
// get,
// path = "/payments/start/{payment_id}/{merchant_id}/{attempt_id}",
// path = "/payments/redirect/{payment_id}/{merchant_id}/{attempt_id}",
// params(
// ("payment_id" = String, Path, description = "The identifier for payment"),
// ("merchant_id" = String, Path, description = "The identifier for merchant"),

View File

@ -27,7 +27,7 @@ use crate::{
logger,
routes::{app::AppStateInfo, metrics, AppState},
services::authentication as auth,
types::{self, api, storage, ErrorResponse},
types::{self, api, ErrorResponse},
};
pub type BoxedConnectorIntegration<'a, T, Req, Resp> =
@ -447,19 +447,6 @@ pub struct ApplicationRedirectResponse {
pub url: String,
}
impl From<&storage::PaymentAttempt> for ApplicationRedirectResponse {
fn from(payment_attempt: &storage::PaymentAttempt) -> Self {
Self {
url: format!(
"/payments/start/{}/{}/{}",
&payment_attempt.payment_id,
&payment_attempt.merchant_id,
&payment_attempt.attempt_id
),
}
}
}
#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct RedirectForm {
pub endpoint: String,

View File

@ -199,6 +199,7 @@ pub struct CompleteAuthorizeData {
pub mandate_id: Option<api_models::payments::MandateIds>,
pub off_session: Option<bool>,
pub setup_mandate_details: Option<payments::MandateData>,
pub payload: Option<serde_json::Value>,
pub browser_info: Option<BrowserInformation>,
pub connector_transaction_id: Option<String>,
pub connector_meta: Option<serde_json::Value>,

View File

@ -128,7 +128,10 @@ impl ForeignFrom<storage_enums::AttemptStatus> for storage_enums::IntentStatus {
storage_enums::AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod,
storage_enums::AttemptStatus::Authorized => Self::RequiresCapture,
storage_enums::AttemptStatus::AuthenticationPending => Self::RequiresCustomerAction,
storage_enums::AttemptStatus::AuthenticationPending
| storage_enums::AttemptStatus::DeviceDataCollectionPending => {
Self::RequiresCustomerAction
}
storage_enums::AttemptStatus::Unresolved => Self::RequiresMerchantAction,
storage_enums::AttemptStatus::PartialCharged

View File

@ -63,6 +63,7 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
}),
capture_method: Some(storage_models::enums::CaptureMethod::Manual),
router_return_url: Some("https://google.com".to_string()),
complete_authorize_url: Some("https://google.com".to_string()),
..utils::PaymentAuthorizeType::default().0
})
}

View File

@ -57,6 +57,7 @@ pub enum AttemptStatus {
Failure,
PaymentMethodAwaited,
ConfirmationAwaited,
DeviceDataCollectionPending,
}
#[derive(

View File

@ -0,0 +1,5 @@
DELETE FROM pg_enum
WHERE enumlabel = 'device_data_collection_pending'
AND enumtypid = (
SELECT oid FROM pg_type WHERE typname = 'AttemptStatus'
)

View File

@ -0,0 +1 @@
ALTER TYPE "AttemptStatus" ADD VALUE IF NOT EXISTS 'device_data_collection_pending';