mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-30 01:27:31 +08:00
feat(connector): [Noon] Add Card Mandates and Webhooks Support (#1243)
Co-authored-by: Arjun Karthik <m.arjunkarthik@gmail.com> Co-authored-by: Arun Raj M <jarnura47@gmail.com>
This commit is contained in:
@ -1057,7 +1057,7 @@ impl api::IncomingWebhook for Bluesnap {
|
||||
let details: bluesnap::BluesnapWebhookObjectResource =
|
||||
serde_urlencoded::from_bytes(request.body)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
||||
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
|
||||
let res_json =
|
||||
utils::Encode::<transformers::BluesnapWebhookObjectResource>::encode_to_value(&details)
|
||||
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
|
||||
|
||||
@ -3,6 +3,7 @@ mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use base64::Engine;
|
||||
use common_utils::{crypto, ext_traits::ByteSliceExt};
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use transformers as noon;
|
||||
|
||||
@ -14,6 +15,7 @@ use crate::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
db::StorageInterface,
|
||||
headers,
|
||||
services::{
|
||||
self,
|
||||
@ -585,24 +587,112 @@ impl services::ConnectorRedirectResponse for Noon {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Noon {
|
||||
fn get_webhook_object_reference_id(
|
||||
fn get_webhook_source_verification_algorithm(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<Box<dyn crypto::VerifySignature + Send>, errors::ConnectorError> {
|
||||
Ok(Box::new(crypto::HmacSha512))
|
||||
}
|
||||
|
||||
fn get_webhook_source_verification_signature(
|
||||
&self,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||
let webhook_body: noon::NoonWebhookSignature = request
|
||||
.body
|
||||
.parse_struct("NoonWebhookSignature")
|
||||
.change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
|
||||
let signature = webhook_body.signature;
|
||||
consts::BASE64_ENGINE
|
||||
.decode(signature)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::WebhookSignatureNotFound)
|
||||
}
|
||||
|
||||
fn get_webhook_source_verification_message(
|
||||
&self,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
_merchant_id: &str,
|
||||
_secret: &[u8],
|
||||
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||
let webhook_body: noon::NoonWebhookBody = request
|
||||
.body
|
||||
.parse_struct("NoonWebhookBody")
|
||||
.change_context(errors::ConnectorError::WebhookSignatureNotFound)?;
|
||||
let message = format!(
|
||||
"{},{},{},{},{}",
|
||||
webhook_body.order_id,
|
||||
webhook_body.order_status,
|
||||
webhook_body.event_id,
|
||||
webhook_body.event_type,
|
||||
webhook_body.time_stamp,
|
||||
);
|
||||
Ok(message.into_bytes())
|
||||
}
|
||||
|
||||
async fn get_webhook_source_verification_merchant_secret(
|
||||
&self,
|
||||
db: &dyn StorageInterface,
|
||||
merchant_id: &str,
|
||||
) -> CustomResult<Vec<u8>, errors::ConnectorError> {
|
||||
let key = format!("whsec_verification_{}_{}", self.id(), merchant_id);
|
||||
let secret = match db.find_config_by_key(&key).await {
|
||||
Ok(config) => Some(config),
|
||||
Err(e) => {
|
||||
crate::logger::warn!("Unable to fetch merchant webhook secret from DB: {:#?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
Ok(secret
|
||||
.map(|conf| conf.config.into_bytes())
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api::webhooks::ObjectReferenceId, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
let details: noon::NoonWebhookOrderId = request
|
||||
.body
|
||||
.parse_struct("NoonWebhookOrderId")
|
||||
.change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?;
|
||||
Ok(api_models::webhooks::ObjectReferenceId::PaymentId(
|
||||
api_models::payments::PaymentIdType::ConnectorTransactionId(
|
||||
details.order_id.to_string(),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
let details: noon::NoonWebhookEvent = request
|
||||
.body
|
||||
.parse_struct("NoonWebhookEvent")
|
||||
.change_context(errors::ConnectorError::WebhookEventTypeNotFound)?;
|
||||
|
||||
Ok(match &details.event_type {
|
||||
noon::NoonWebhookEventTypes::Sale | noon::NoonWebhookEventTypes::Capture => {
|
||||
match &details.order_status {
|
||||
noon::NoonPaymentStatus::Captured => {
|
||||
api::IncomingWebhookEvent::PaymentIntentSuccess
|
||||
}
|
||||
_ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
|
||||
}
|
||||
}
|
||||
noon::NoonWebhookEventTypes::Fail => api::IncomingWebhookEvent::PaymentIntentFailure,
|
||||
_ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
request: &api::IncomingWebhookRequestDetails<'_>,
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
let reference_object: serde_json::Value = serde_json::from_slice(request.body)
|
||||
.into_report()
|
||||
.change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?;
|
||||
Ok(reference_object)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,13 +16,27 @@ pub enum NoonChannels {
|
||||
Web,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum NoonSubscriptionType {
|
||||
Unscheduled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NoonSubscriptionData {
|
||||
#[serde(rename = "type")]
|
||||
subscription_type: NoonSubscriptionType,
|
||||
//Short description about the subscription.
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonOrder {
|
||||
amount: String,
|
||||
currency: storage_models::enums::Currency,
|
||||
currency: Option<storage_models::enums::Currency>,
|
||||
channel: NoonChannels,
|
||||
category: String,
|
||||
category: Option<String>,
|
||||
//Short description of the order.
|
||||
name: String,
|
||||
}
|
||||
@ -37,10 +51,17 @@ pub enum NoonPaymentActions {
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonConfiguration {
|
||||
tokenize_c_c: Option<bool>,
|
||||
payment_action: NoonPaymentActions,
|
||||
return_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonSubscription {
|
||||
subscription_identifier: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonCard {
|
||||
@ -55,6 +76,7 @@ pub struct NoonCard {
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum NoonPaymentData {
|
||||
Card(NoonCard),
|
||||
Subscription(NoonSubscription),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@ -72,30 +94,55 @@ pub struct NoonPaymentsRequest {
|
||||
order: NoonOrder,
|
||||
configuration: NoonConfiguration,
|
||||
payment_data: NoonPaymentData,
|
||||
subscription: Option<NoonSubscriptionData>,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
let payment_data = match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(req_card) => Ok(NoonPaymentData::Card(NoonCard {
|
||||
name_on_card: req_card.card_holder_name,
|
||||
number_plain: req_card.card_number,
|
||||
expiry_month: req_card.card_exp_month,
|
||||
expiry_year: req_card.card_exp_year,
|
||||
cvv: req_card.card_cvc,
|
||||
})),
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
"Payment methods".to_string(),
|
||||
)),
|
||||
}?;
|
||||
|
||||
let (payment_data, currency, category) = match item.request.connector_mandate_id() {
|
||||
Some(subscription_identifier) => (
|
||||
NoonPaymentData::Subscription(NoonSubscription {
|
||||
subscription_identifier,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
),
|
||||
_ => (
|
||||
match item.request.payment_method_data.clone() {
|
||||
api::PaymentMethodData::Card(req_card) => Ok(NoonPaymentData::Card(NoonCard {
|
||||
name_on_card: req_card.card_holder_name,
|
||||
number_plain: req_card.card_number,
|
||||
expiry_month: req_card.card_exp_month,
|
||||
expiry_year: req_card.card_exp_year,
|
||||
cvv: req_card.card_cvc,
|
||||
})),
|
||||
_ => Err(errors::ConnectorError::NotImplemented(
|
||||
"Payment methods".to_string(),
|
||||
)),
|
||||
}?,
|
||||
Some(item.request.currency),
|
||||
item.request.order_category.clone(),
|
||||
),
|
||||
};
|
||||
let name = item.get_description()?;
|
||||
let (subscription, tokenize_c_c) =
|
||||
match item.request.setup_future_usage.is_some().then_some((
|
||||
NoonSubscriptionData {
|
||||
subscription_type: NoonSubscriptionType::Unscheduled,
|
||||
name: name.clone(),
|
||||
},
|
||||
true,
|
||||
)) {
|
||||
Some((a, b)) => (Some(a), Some(b)),
|
||||
None => (None, None),
|
||||
};
|
||||
let order = NoonOrder {
|
||||
amount: conn_utils::to_currency_base_unit(item.request.amount, item.request.currency)?,
|
||||
currency: item.request.currency,
|
||||
currency,
|
||||
channel: NoonChannels::Web,
|
||||
category: "pay".to_string(),
|
||||
name: item.get_description()?,
|
||||
category,
|
||||
name,
|
||||
};
|
||||
let payment_action = if item.request.is_auto_capture()? {
|
||||
NoonPaymentActions::Sale
|
||||
@ -108,8 +155,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest {
|
||||
configuration: NoonConfiguration {
|
||||
payment_action,
|
||||
return_url: item.request.router_return_url.clone(),
|
||||
tokenize_c_c,
|
||||
},
|
||||
payment_data,
|
||||
subscription,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -138,13 +187,15 @@ impl TryFrom<&types::ConnectorAuthType> for NoonAuthType {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[derive(Default, Debug, Deserialize, strum::Display)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
pub enum NoonPaymentStatus {
|
||||
Authorized,
|
||||
Captured,
|
||||
PartiallyCaptured,
|
||||
Reversed,
|
||||
Cancelled,
|
||||
#[serde(rename = "3DS_ENROLL_INITIATED")]
|
||||
ThreeDsEnrollInitiated,
|
||||
Failed,
|
||||
@ -158,6 +209,7 @@ impl From<NoonPaymentStatus> for enums::AttemptStatus {
|
||||
NoonPaymentStatus::Authorized => Self::Authorized,
|
||||
NoonPaymentStatus::Captured | NoonPaymentStatus::PartiallyCaptured => Self::Charged,
|
||||
NoonPaymentStatus::Reversed => Self::Voided,
|
||||
NoonPaymentStatus::Cancelled => Self::AuthenticationFailed,
|
||||
NoonPaymentStatus::ThreeDsEnrollInitiated => Self::AuthenticationPending,
|
||||
NoonPaymentStatus::Failed => Self::Failure,
|
||||
NoonPaymentStatus::Pending => Self::Pending,
|
||||
@ -165,6 +217,11 @@ impl From<NoonPaymentStatus> for enums::AttemptStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NoonSubscriptionResponse {
|
||||
identifier: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonPaymentsOrderResponse {
|
||||
@ -183,6 +240,7 @@ pub struct NoonCheckoutData {
|
||||
pub struct NoonPaymentsResponseResult {
|
||||
order: NoonPaymentsOrderResponse,
|
||||
checkout_data: Option<NoonCheckoutData>,
|
||||
subscription: Option<NoonSubscriptionResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -205,6 +263,14 @@ impl<F, T>
|
||||
form_fields: std::collections::HashMap::new(),
|
||||
}
|
||||
});
|
||||
let mandate_reference =
|
||||
item.response
|
||||
.result
|
||||
.subscription
|
||||
.map(|subscription_data| types::MandateReference {
|
||||
connector_mandate_id: Some(subscription_data.identifier),
|
||||
payment_method_id: None,
|
||||
});
|
||||
Ok(Self {
|
||||
status: enums::AttemptStatus::from(item.response.result.order.status),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
@ -212,7 +278,7 @@ impl<F, T>
|
||||
item.response.result.order.id.to_string(),
|
||||
),
|
||||
redirection_data,
|
||||
mandate_reference: None,
|
||||
mandate_reference,
|
||||
connector_metadata: None,
|
||||
network_txn_id: None,
|
||||
}),
|
||||
@ -399,6 +465,44 @@ impl TryFrom<types::RefundsResponseRouterData<api::RSync, RefundSyncResponse>>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, strum::Display)]
|
||||
pub enum NoonWebhookEventTypes {
|
||||
Authenticate,
|
||||
Authorize,
|
||||
Capture,
|
||||
Fail,
|
||||
Refund,
|
||||
Sale,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonWebhookBody {
|
||||
pub order_id: u64,
|
||||
pub order_status: NoonPaymentStatus,
|
||||
pub event_type: NoonWebhookEventTypes,
|
||||
pub event_id: String,
|
||||
pub time_stamp: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct NoonWebhookSignature {
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonWebhookOrderId {
|
||||
pub order_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonWebhookEvent {
|
||||
pub order_status: NoonPaymentStatus,
|
||||
pub event_type: NoonWebhookEventTypes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoonErrorResponse {
|
||||
|
||||
@ -364,6 +364,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize {
|
||||
json_payload: Some(req.json_payload.unwrap_or(serde_json::json!({})).into()),
|
||||
}),
|
||||
allowed_payment_method_types: None,
|
||||
order_category: None,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -393,9 +393,13 @@ pub fn validate_request_amount_and_amount_to_capture(
|
||||
pub fn validate_mandate(
|
||||
req: impl Into<api::MandateValidationFields>,
|
||||
is_confirm_operation: bool,
|
||||
) -> RouterResult<Option<api::MandateTxnType>> {
|
||||
) -> CustomResult<Option<api::MandateTxnType>, errors::ApiErrorResponse> {
|
||||
let req: api::MandateValidationFields = req.into();
|
||||
match req.is_mandate() {
|
||||
match req.validate_and_get_mandate_type().change_context(
|
||||
errors::ApiErrorResponse::MandateValidationFailed {
|
||||
reason: "Expected one out of mandate_id and mandate_data but got both".to_string(),
|
||||
},
|
||||
)? {
|
||||
Some(api::MandateTxnType::NewMandateTxn) => {
|
||||
validate_new_mandate_request(req, is_confirm_operation)?;
|
||||
Ok(Some(api::MandateTxnType::NewMandateTxn))
|
||||
|
||||
@ -605,6 +605,9 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthoriz
|
||||
.transpose()
|
||||
.unwrap_or_default();
|
||||
|
||||
let order_category = parsed_metadata
|
||||
.as_ref()
|
||||
.and_then(|data| data.order_category.clone());
|
||||
let order_details = parsed_metadata.and_then(|data| data.order_details);
|
||||
let complete_authorize_url = Some(helpers::create_complete_authorize_url(
|
||||
router_base_url,
|
||||
@ -647,6 +650,7 @@ impl<F: Clone> TryFrom<PaymentAdditionalData<'_, F>> for types::PaymentsAuthoriz
|
||||
email: payment_data.email,
|
||||
payment_experience: payment_data.payment_attempt.payment_experience,
|
||||
order_details,
|
||||
order_category,
|
||||
session_token: None,
|
||||
enrolled_for_3ds: true,
|
||||
related_transaction_id: None,
|
||||
|
||||
@ -226,6 +226,7 @@ pub struct PaymentsAuthorizeData {
|
||||
pub setup_mandate_details: Option<payments::MandateData>,
|
||||
pub browser_info: Option<BrowserInformation>,
|
||||
pub order_details: Option<api_models::payments::OrderDetails>,
|
||||
pub order_category: Option<String>,
|
||||
pub session_token: Option<String>,
|
||||
pub enrolled_for_3ds: bool,
|
||||
pub related_transaction_id: Option<String>,
|
||||
@ -729,6 +730,7 @@ impl From<&VerifyRouterData> for PaymentsAuthorizeData {
|
||||
complete_authorize_url: None,
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
session_token: None,
|
||||
enrolled_for_3ds: true,
|
||||
related_transaction_id: None,
|
||||
|
||||
@ -113,15 +113,23 @@ impl PaymentIdTypeExt for PaymentIdType {
|
||||
}
|
||||
|
||||
pub(crate) trait MandateValidationFieldsExt {
|
||||
fn is_mandate(&self) -> Option<MandateTxnType>;
|
||||
fn validate_and_get_mandate_type(
|
||||
&self,
|
||||
) -> errors::CustomResult<Option<MandateTxnType>, errors::ValidationError>;
|
||||
}
|
||||
|
||||
impl MandateValidationFieldsExt for MandateValidationFields {
|
||||
fn is_mandate(&self) -> Option<MandateTxnType> {
|
||||
fn validate_and_get_mandate_type(
|
||||
&self,
|
||||
) -> errors::CustomResult<Option<MandateTxnType>, errors::ValidationError> {
|
||||
match (&self.mandate_data, &self.mandate_id) {
|
||||
(None, None) => None,
|
||||
(_, Some(_)) => Some(MandateTxnType::RecurringMandateTxn),
|
||||
(Some(_), _) => Some(MandateTxnType::NewMandateTxn),
|
||||
(None, None) => Ok(None),
|
||||
(Some(_), Some(_)) => Err(errors::ValidationError::InvalidValue {
|
||||
message: "Expected one out of mandate_id and mandate_data but got both".to_string(),
|
||||
})
|
||||
.into_report(),
|
||||
(_, Some(_)) => Ok(Some(MandateTxnType::RecurringMandateTxn)),
|
||||
(Some(_), _) => Ok(Some(MandateTxnType::NewMandateTxn)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
capture_method: None,
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
session_token: None,
|
||||
enrolled_for_3ds: false,
|
||||
|
||||
@ -81,6 +81,7 @@ impl AdyenTest {
|
||||
capture_method: Some(capture_method),
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
payment_experience: None,
|
||||
payment_method_type: None,
|
||||
|
||||
@ -75,6 +75,7 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
// capture_method: Some(capture_method),
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
payment_experience: None,
|
||||
payment_method_type: None,
|
||||
|
||||
@ -77,6 +77,7 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
// capture_method: Some(capture_method),
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
payment_experience: None,
|
||||
payment_method_type: None,
|
||||
|
||||
@ -76,6 +76,7 @@ fn payment_method_details() -> Option<types::PaymentsAuthorizeData> {
|
||||
// capture_method: Some(capture_method),
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
payment_experience: None,
|
||||
payment_method_type: None,
|
||||
|
||||
@ -508,6 +508,7 @@ impl Default for PaymentAuthorizeType {
|
||||
setup_mandate_details: None,
|
||||
browser_info: Some(BrowserInfoType::default().0),
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
session_token: None,
|
||||
enrolled_for_3ds: false,
|
||||
|
||||
@ -84,6 +84,7 @@ impl WorldlineTest {
|
||||
capture_method: Some(capture_method),
|
||||
browser_info: None,
|
||||
order_details: None,
|
||||
order_category: None,
|
||||
email: None,
|
||||
session_token: None,
|
||||
enrolled_for_3ds: false,
|
||||
|
||||
Reference in New Issue
Block a user