mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +08:00
feat(connector): add afterpay, klarna, affirm support in adyen connector (#516)
This commit is contained in:
@ -561,7 +561,7 @@ impl services::ConnectorIntegration<api::Execute, types::RefundsData, types::Ref
|
|||||||
) -> CustomResult<String, errors::ConnectorError> {
|
) -> CustomResult<String, errors::ConnectorError> {
|
||||||
let connector_payment_id = req.request.connector_transaction_id.clone();
|
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{}v68/payments/{}/reversals",
|
"{}v68/payments/{}/refunds",
|
||||||
self.base_url(connectors),
|
self.base_url(connectors),
|
||||||
connector_payment_id,
|
connector_payment_id,
|
||||||
))
|
))
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
use std::{collections::HashMap, str::FromStr};
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use error_stack::{IntoReport, ResultExt};
|
use error_stack::{IntoReport, ResultExt};
|
||||||
|
use masking::PeekInterface;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
connector::utils::{self, PaymentsRequestData},
|
||||||
consts,
|
consts,
|
||||||
core::errors,
|
core::errors,
|
||||||
pii, services,
|
pii::{self, Email, Secret},
|
||||||
|
services,
|
||||||
types::{
|
types::{
|
||||||
self,
|
self,
|
||||||
api::{self, enums as api_enums},
|
api::{self, enums as api_enums},
|
||||||
storage::enums as storage_enums,
|
storage::enums as storage_enums,
|
||||||
},
|
},
|
||||||
utils::OptionExt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adyen Types Definition
|
// Adyen Types Definition
|
||||||
@ -41,8 +43,39 @@ pub enum AuthType {
|
|||||||
PreAuth,
|
PreAuth,
|
||||||
}
|
}
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AdditionalData {
|
pub struct AdditionalData {
|
||||||
authorisation_type: AuthType,
|
authorisation_type: AuthType,
|
||||||
|
manual_capture: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ShopperName {
|
||||||
|
first_name: Option<Secret<String>>,
|
||||||
|
last_name: Option<Secret<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Address {
|
||||||
|
city: Option<String>,
|
||||||
|
country: Option<String>,
|
||||||
|
house_number_or_name: Option<Secret<String>>,
|
||||||
|
postal_code: Option<Secret<String>>,
|
||||||
|
state_or_province: Option<Secret<String>>,
|
||||||
|
street: Option<Secret<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LineItem {
|
||||||
|
amount_excluding_tax: Option<i64>,
|
||||||
|
amount_including_tax: Option<i64>,
|
||||||
|
description: Option<String>,
|
||||||
|
id: Option<String>,
|
||||||
|
tax_amount: Option<i64>,
|
||||||
|
quantity: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -58,6 +91,13 @@ pub struct AdyenPaymentRequest {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
recurring_processing_model: Option<AdyenRecurringModel>,
|
recurring_processing_model: Option<AdyenRecurringModel>,
|
||||||
additional_data: Option<AdditionalData>,
|
additional_data: Option<AdditionalData>,
|
||||||
|
shopper_name: Option<ShopperName>,
|
||||||
|
shopper_email: Option<Secret<String, Email>>,
|
||||||
|
telephone_number: Option<Secret<String>>,
|
||||||
|
billing_address: Option<Address>,
|
||||||
|
delivery_address: Option<Address>,
|
||||||
|
country_code: Option<String>,
|
||||||
|
line_items: Option<Vec<LineItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -175,17 +215,20 @@ pub enum AdyenPaymentMethod {
|
|||||||
AdyenPaypal(AdyenPaypal),
|
AdyenPaypal(AdyenPaypal),
|
||||||
Gpay(AdyenGPay),
|
Gpay(AdyenGPay),
|
||||||
ApplePay(AdyenApplePay),
|
ApplePay(AdyenApplePay),
|
||||||
|
AfterPay(AdyenPayLaterData),
|
||||||
|
AdyenKlarna(AdyenPayLaterData),
|
||||||
|
AdyenAffirm(AdyenPayLaterData),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AdyenCard {
|
pub struct AdyenCard {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
payment_type: String,
|
payment_type: PaymentType,
|
||||||
number: Option<pii::Secret<String, pii::CardNumber>>,
|
number: Secret<String, pii::CardNumber>,
|
||||||
expiry_month: Option<pii::Secret<String>>,
|
expiry_month: Secret<String>,
|
||||||
expiry_year: Option<pii::Secret<String>>,
|
expiry_year: Secret<String>,
|
||||||
cvc: Option<pii::Secret<String>>,
|
cvc: Option<Secret<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
@ -213,13 +256,13 @@ pub enum CancelStatus {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AdyenPaypal {
|
pub struct AdyenPaypal {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
payment_type: String,
|
payment_type: PaymentType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AdyenGPay {
|
pub struct AdyenGPay {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
payment_type: String,
|
payment_type: PaymentType,
|
||||||
#[serde(rename = "googlePayToken")]
|
#[serde(rename = "googlePayToken")]
|
||||||
google_pay_token: String,
|
google_pay_token: String,
|
||||||
}
|
}
|
||||||
@ -227,16 +270,24 @@ pub struct AdyenGPay {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AdyenApplePay {
|
pub struct AdyenApplePay {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
payment_type: String,
|
payment_type: PaymentType,
|
||||||
#[serde(rename = "applePayToken")]
|
#[serde(rename = "applePayToken")]
|
||||||
apple_pay_token: String,
|
apple_pay_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AdyenPayLaterData {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
payment_type: PaymentType,
|
||||||
|
}
|
||||||
|
|
||||||
// Refunds Request and Response
|
// Refunds Request and Response
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AdyenRefundRequest {
|
pub struct AdyenRefundRequest {
|
||||||
merchant_account: String,
|
merchant_account: String,
|
||||||
|
amount: Amount,
|
||||||
|
merchant_refund_reason: Option<String>,
|
||||||
reference: String,
|
reference: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,6 +306,18 @@ pub struct AdyenAuthType {
|
|||||||
pub(super) merchant_account: String,
|
pub(super) merchant_account: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum PaymentType {
|
||||||
|
Scheme,
|
||||||
|
Googlepay,
|
||||||
|
Applepay,
|
||||||
|
Paypal,
|
||||||
|
Klarna,
|
||||||
|
Affirm,
|
||||||
|
Afterpaytouch,
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType {
|
impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||||
@ -269,157 +332,312 @@ impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::BrowserInformation> for AdyenBrowserInfo {
|
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
|
||||||
fn try_from(item: &types::BrowserInformation) -> Result<Self, Self::Error> {
|
|
||||||
Ok(Self {
|
|
||||||
accept_header: item.accept_header.clone(),
|
|
||||||
language: item.language.clone(),
|
|
||||||
screen_height: item.screen_height,
|
|
||||||
screen_width: item.screen_width,
|
|
||||||
color_depth: item.color_depth,
|
|
||||||
user_agent: item.user_agent.clone(),
|
|
||||||
time_zone_offset: item.time_zone,
|
|
||||||
java_enabled: item.java_enabled,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payment Request Transform
|
// Payment Request Transform
|
||||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest {
|
impl TryFrom<&types::PaymentsAuthorizeRouterData> for AdyenPaymentRequest {
|
||||||
type Error = error_stack::Report<errors::ConnectorError>;
|
type Error = error_stack::Report<errors::ConnectorError>;
|
||||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||||
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
match item.payment_method {
|
||||||
let reference = item.payment_id.to_string();
|
storage_models::enums::PaymentMethodType::Card => get_card_specific_payment_data(item),
|
||||||
let amount = Amount {
|
storage_models::enums::PaymentMethodType::PayLater => {
|
||||||
currency: item.request.currency.to_string(),
|
get_paylater_specific_payment_data(item)
|
||||||
value: item.request.amount,
|
}
|
||||||
};
|
storage_models::enums::PaymentMethodType::Wallet => {
|
||||||
let ccard = match item.request.payment_method_data {
|
get_wallet_specific_payment_data(item)
|
||||||
api::PaymentMethod::Card(ref ccard) => Some(ccard),
|
}
|
||||||
api::PaymentMethod::BankTransfer
|
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||||
| api::PaymentMethod::Wallet(_)
|
}
|
||||||
| api::PaymentMethod::PayLater(_)
|
}
|
||||||
| api::PaymentMethod::Paypal => None,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let wallet_data = match item.request.payment_method_data {
|
impl From<&types::PaymentsAuthorizeRouterData> for AdyenShopperInteraction {
|
||||||
api::PaymentMethod::Wallet(ref wallet_data) => Some(wallet_data),
|
fn from(item: &types::PaymentsAuthorizeRouterData) -> Self {
|
||||||
_ => None,
|
match item.request.off_session {
|
||||||
};
|
Some(true) => Self::ContinuedAuthentication,
|
||||||
|
_ => Self::Ecommerce,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let shopper_interaction = match item.request.off_session {
|
fn get_recurring_processing_model(
|
||||||
Some(true) => AdyenShopperInteraction::ContinuedAuthentication,
|
item: &types::PaymentsAuthorizeRouterData,
|
||||||
_ => AdyenShopperInteraction::Ecommerce,
|
) -> Option<AdyenRecurringModel> {
|
||||||
};
|
match item.request.setup_future_usage {
|
||||||
|
|
||||||
let recurring_processing_model = match item.request.setup_future_usage {
|
|
||||||
Some(storage_enums::FutureUsage::OffSession) => {
|
Some(storage_enums::FutureUsage::OffSession) => {
|
||||||
Some(AdyenRecurringModel::UnscheduledCardOnFile)
|
Some(AdyenRecurringModel::UnscheduledCardOnFile)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
}
|
||||||
|
|
||||||
let payment_type = match item.payment_method {
|
|
||||||
storage_enums::PaymentMethodType::Card => "scheme".to_string(),
|
|
||||||
storage_enums::PaymentMethodType::Wallet => wallet_data
|
|
||||||
.get_required_value("wallet_data")
|
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?
|
|
||||||
.issuer_name
|
|
||||||
.to_string(),
|
|
||||||
_ => "None".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let payment_method = match item.payment_method {
|
|
||||||
storage_enums::PaymentMethodType::Card => {
|
|
||||||
let card = AdyenCard {
|
|
||||||
payment_type,
|
|
||||||
number: ccard.map(|x| x.card_number.clone()),
|
|
||||||
expiry_month: ccard.map(|x| x.card_exp_month.clone()),
|
|
||||||
expiry_year: ccard.map(|x| x.card_exp_year.clone()),
|
|
||||||
cvc: ccard.map(|x| x.card_cvc.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(AdyenPaymentMethod::AdyenCard(card))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
storage_enums::PaymentMethodType::Wallet => match wallet_data
|
fn get_browser_info(item: &types::PaymentsAuthorizeRouterData) -> Option<AdyenBrowserInfo> {
|
||||||
.get_required_value("wallet_data")
|
if matches!(item.auth_type, storage_enums::AuthenticationType::ThreeDs) {
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?
|
item.request
|
||||||
.issuer_name
|
.browser_info
|
||||||
{
|
.as_ref()
|
||||||
|
.map(|info| AdyenBrowserInfo {
|
||||||
|
accept_header: info.accept_header.clone(),
|
||||||
|
language: info.language.clone(),
|
||||||
|
screen_height: info.screen_height,
|
||||||
|
screen_width: info.screen_width,
|
||||||
|
color_depth: info.color_depth,
|
||||||
|
user_agent: info.user_agent.clone(),
|
||||||
|
time_zone_offset: info.time_zone,
|
||||||
|
java_enabled: info.java_enabled,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_additional_data(item: &types::PaymentsAuthorizeRouterData) -> Option<AdditionalData> {
|
||||||
|
match item.request.capture_method {
|
||||||
|
Some(storage_models::enums::CaptureMethod::Manual) => Some(AdditionalData {
|
||||||
|
authorisation_type: AuthType::PreAuth,
|
||||||
|
manual_capture: true,
|
||||||
|
}),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_amount_data(item: &types::PaymentsAuthorizeRouterData) -> Amount {
|
||||||
|
Amount {
|
||||||
|
currency: item.request.currency.to_string(),
|
||||||
|
value: item.request.amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_address_info(address: Option<&api_models::payments::Address>) -> Option<Address> {
|
||||||
|
address.and_then(|add| {
|
||||||
|
add.address.as_ref().map(|a| Address {
|
||||||
|
city: a.city.clone(),
|
||||||
|
country: a.country.clone(),
|
||||||
|
house_number_or_name: a.line1.clone(),
|
||||||
|
postal_code: a.zip.clone(),
|
||||||
|
state_or_province: a.state.clone(),
|
||||||
|
street: a.line2.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line_items(item: &types::PaymentsAuthorizeRouterData) -> Vec<LineItem> {
|
||||||
|
let order_details = item.request.order_details.as_ref();
|
||||||
|
let line_item = LineItem {
|
||||||
|
amount_including_tax: Some(item.request.amount),
|
||||||
|
amount_excluding_tax: None,
|
||||||
|
description: order_details.map(|details| details.product_name.clone()),
|
||||||
|
// We support only one product details in payment request as of now, therefore hard coded the id.
|
||||||
|
// If we begin to support multiple product details in future then this logic should be made to create ID dynamically
|
||||||
|
id: Some(String::from("Items #1")),
|
||||||
|
tax_amount: None,
|
||||||
|
quantity: order_details.map(|details| details.quantity),
|
||||||
|
};
|
||||||
|
vec![line_item]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_telephone_number(item: &types::PaymentsAuthorizeRouterData) -> Option<Secret<String>> {
|
||||||
|
let phone = item
|
||||||
|
.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|billing| billing.phone.as_ref());
|
||||||
|
phone.as_ref().and_then(|phone| {
|
||||||
|
phone.number.as_ref().and_then(|number| {
|
||||||
|
phone
|
||||||
|
.country_code
|
||||||
|
.as_ref()
|
||||||
|
.map(|cc| Secret::new(format!("{}{}", cc, number.peek())))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shopper_name(item: &types::PaymentsAuthorizeRouterData) -> Option<ShopperName> {
|
||||||
|
let address = item
|
||||||
|
.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|billing| billing.address.as_ref());
|
||||||
|
Some(ShopperName {
|
||||||
|
first_name: address.and_then(|address| address.first_name.clone()),
|
||||||
|
last_name: address.and_then(|address| address.last_name.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_country_code(item: &types::PaymentsAuthorizeRouterData) -> Option<String> {
|
||||||
|
let address = item
|
||||||
|
.address
|
||||||
|
.billing
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|billing| billing.address.as_ref());
|
||||||
|
address.and_then(|address| address.country.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_payment_method_data(
|
||||||
|
item: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> Result<AdyenPaymentMethod, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
match item.request.payment_method_data {
|
||||||
|
api::PaymentMethod::Card(ref card) => {
|
||||||
|
let adyen_card = AdyenCard {
|
||||||
|
payment_type: PaymentType::Scheme,
|
||||||
|
number: card.card_number.clone(),
|
||||||
|
expiry_month: card.card_exp_month.clone(),
|
||||||
|
expiry_year: card.card_exp_year.clone(),
|
||||||
|
cvc: Some(card.card_cvc.clone()),
|
||||||
|
};
|
||||||
|
Ok(AdyenPaymentMethod::AdyenCard(adyen_card))
|
||||||
|
}
|
||||||
|
api::PaymentMethod::Wallet(ref wallet_data) => match wallet_data.issuer_name {
|
||||||
api_enums::WalletIssuer::GooglePay => {
|
api_enums::WalletIssuer::GooglePay => {
|
||||||
let gpay_data = AdyenGPay {
|
let gpay_data = AdyenGPay {
|
||||||
payment_type,
|
payment_type: PaymentType::Googlepay,
|
||||||
google_pay_token: wallet_data
|
google_pay_token: wallet_data
|
||||||
.get_required_value("wallet_data")
|
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?
|
|
||||||
.token
|
.token
|
||||||
.to_owned()
|
.clone()
|
||||||
.get_required_value("token")
|
.ok_or_else(utils::missing_field_err("token"))?,
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)
|
|
||||||
.attach_printable("No token passed")?,
|
|
||||||
};
|
};
|
||||||
Ok(AdyenPaymentMethod::Gpay(gpay_data))
|
Ok(AdyenPaymentMethod::Gpay(gpay_data))
|
||||||
}
|
}
|
||||||
|
|
||||||
api_enums::WalletIssuer::ApplePay => {
|
api_enums::WalletIssuer::ApplePay => {
|
||||||
let apple_pay_data = AdyenApplePay {
|
let apple_pay_data = AdyenApplePay {
|
||||||
payment_type,
|
payment_type: PaymentType::Applepay,
|
||||||
apple_pay_token: wallet_data
|
apple_pay_token: wallet_data
|
||||||
.get_required_value("wallet_data")
|
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?
|
|
||||||
.token
|
.token
|
||||||
.to_owned()
|
.clone()
|
||||||
.get_required_value("token")
|
.ok_or_else(utils::missing_field_err("token"))?,
|
||||||
.change_context(errors::ConnectorError::RequestEncodingFailed)
|
|
||||||
.attach_printable("No token passed")?,
|
|
||||||
};
|
};
|
||||||
Ok(AdyenPaymentMethod::ApplePay(apple_pay_data))
|
Ok(AdyenPaymentMethod::ApplePay(apple_pay_data))
|
||||||
}
|
}
|
||||||
api_enums::WalletIssuer::Paypal => {
|
api_enums::WalletIssuer::Paypal => {
|
||||||
let wallet = AdyenPaypal { payment_type };
|
let wallet = AdyenPaypal {
|
||||||
|
payment_type: PaymentType::Paypal,
|
||||||
|
};
|
||||||
Ok(AdyenPaymentMethod::AdyenPaypal(wallet))
|
Ok(AdyenPaymentMethod::AdyenPaypal(wallet))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => Err(errors::ConnectorError::MissingRequiredField {
|
api_models::payments::PaymentMethod::PayLater(ref pay_later_data) => match pay_later_data {
|
||||||
field_name: "payment_method",
|
api_models::payments::PayLaterData::KlarnaRedirect { .. } => {
|
||||||
}),
|
let klarna = AdyenPayLaterData {
|
||||||
}?;
|
payment_type: PaymentType::Klarna,
|
||||||
|
|
||||||
let browser_info = if matches!(item.auth_type, storage_enums::AuthenticationType::ThreeDs) {
|
|
||||||
item.request
|
|
||||||
.browser_info
|
|
||||||
.clone()
|
|
||||||
.map(|d| AdyenBrowserInfo::try_from(&d))
|
|
||||||
.transpose()?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
Ok(AdyenPaymentMethod::AdyenKlarna(klarna))
|
||||||
|
}
|
||||||
|
api_models::payments::PayLaterData::AffirmRedirect { .. } => {
|
||||||
|
Ok(AdyenPaymentMethod::AdyenAffirm(AdyenPayLaterData {
|
||||||
|
payment_type: PaymentType::Affirm,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => {
|
||||||
|
Ok(AdyenPaymentMethod::AfterPay(AdyenPayLaterData {
|
||||||
|
payment_type: PaymentType::Afterpaytouch,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
|
||||||
|
},
|
||||||
|
api_models::payments::PaymentMethod::BankTransfer
|
||||||
|
| api_models::payments::PaymentMethod::Paypal => {
|
||||||
|
Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let additional_data = match item.request.capture_method {
|
fn get_card_specific_payment_data(
|
||||||
Some(storage_models::enums::CaptureMethod::Manual) => Some(AdditionalData {
|
item: &types::PaymentsAuthorizeRouterData,
|
||||||
authorisation_type: AuthType::PreAuth,
|
) -> Result<AdyenPaymentRequest, error_stack::Report<errors::ConnectorError>> {
|
||||||
}),
|
let amount = get_amount_data(item);
|
||||||
_ => None,
|
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
||||||
};
|
let shopper_interaction = AdyenShopperInteraction::from(item);
|
||||||
|
let recurring_processing_model = get_recurring_processing_model(item);
|
||||||
Ok(Self {
|
let browser_info = get_browser_info(item);
|
||||||
|
let additional_data = get_additional_data(item);
|
||||||
|
let return_url = item.get_return_url()?;
|
||||||
|
let payment_method = get_payment_method_data(item)?;
|
||||||
|
Ok(AdyenPaymentRequest {
|
||||||
amount,
|
amount,
|
||||||
merchant_account: auth_type.merchant_account,
|
merchant_account: auth_type.merchant_account,
|
||||||
payment_method,
|
payment_method,
|
||||||
reference,
|
reference: item.payment_id.to_string(),
|
||||||
return_url: item.router_return_url.clone().ok_or(
|
return_url,
|
||||||
errors::ConnectorError::MissingRequiredField {
|
|
||||||
field_name: "router_return_url",
|
|
||||||
},
|
|
||||||
)?,
|
|
||||||
shopper_interaction,
|
shopper_interaction,
|
||||||
recurring_processing_model,
|
recurring_processing_model,
|
||||||
browser_info,
|
browser_info,
|
||||||
additional_data,
|
additional_data,
|
||||||
|
telephone_number: None,
|
||||||
|
shopper_name: None,
|
||||||
|
shopper_email: None,
|
||||||
|
billing_address: None,
|
||||||
|
delivery_address: None,
|
||||||
|
country_code: None,
|
||||||
|
line_items: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_wallet_specific_payment_data(
|
||||||
|
item: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> Result<AdyenPaymentRequest, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
let amount = get_amount_data(item);
|
||||||
|
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
||||||
|
let browser_info = get_browser_info(item);
|
||||||
|
let additional_data = get_additional_data(item);
|
||||||
|
let payment_method = get_payment_method_data(item)?;
|
||||||
|
let shopper_interaction = AdyenShopperInteraction::from(item);
|
||||||
|
let recurring_processing_model = get_recurring_processing_model(item);
|
||||||
|
let return_url = item.get_return_url()?;
|
||||||
|
Ok(AdyenPaymentRequest {
|
||||||
|
amount,
|
||||||
|
merchant_account: auth_type.merchant_account,
|
||||||
|
payment_method,
|
||||||
|
reference: item.payment_id.to_string(),
|
||||||
|
return_url,
|
||||||
|
shopper_interaction,
|
||||||
|
recurring_processing_model,
|
||||||
|
browser_info,
|
||||||
|
additional_data,
|
||||||
|
telephone_number: None,
|
||||||
|
shopper_name: None,
|
||||||
|
shopper_email: None,
|
||||||
|
billing_address: None,
|
||||||
|
delivery_address: None,
|
||||||
|
country_code: None,
|
||||||
|
line_items: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_paylater_specific_payment_data(
|
||||||
|
item: &types::PaymentsAuthorizeRouterData,
|
||||||
|
) -> Result<AdyenPaymentRequest, error_stack::Report<errors::ConnectorError>> {
|
||||||
|
let amount = get_amount_data(item);
|
||||||
|
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
||||||
|
let browser_info = get_browser_info(item);
|
||||||
|
let additional_data = get_additional_data(item);
|
||||||
|
let payment_method = get_payment_method_data(item)?;
|
||||||
|
let shopper_interaction = AdyenShopperInteraction::from(item);
|
||||||
|
let recurring_processing_model = get_recurring_processing_model(item);
|
||||||
|
let return_url = item.get_return_url()?;
|
||||||
|
let shopper_name = get_shopper_name(item);
|
||||||
|
let shopper_email = item.request.email.clone();
|
||||||
|
let billing_address = get_address_info(item.address.billing.as_ref());
|
||||||
|
let delivery_address = get_address_info(item.address.shipping.as_ref());
|
||||||
|
let country_code = get_country_code(item);
|
||||||
|
let line_items = Some(get_line_items(item));
|
||||||
|
let telephone_number = get_telephone_number(item);
|
||||||
|
Ok(AdyenPaymentRequest {
|
||||||
|
amount,
|
||||||
|
merchant_account: auth_type.merchant_account,
|
||||||
|
payment_method,
|
||||||
|
reference: item.payment_id.to_string(),
|
||||||
|
return_url,
|
||||||
|
shopper_interaction,
|
||||||
|
recurring_processing_model,
|
||||||
|
browser_info,
|
||||||
|
additional_data,
|
||||||
|
telephone_number,
|
||||||
|
shopper_name,
|
||||||
|
shopper_email,
|
||||||
|
billing_address,
|
||||||
|
delivery_address,
|
||||||
|
country_code,
|
||||||
|
line_items,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&types::PaymentsCancelRouterData> for AdyenCancelRequest {
|
impl TryFrom<&types::PaymentsCancelRouterData> for AdyenCancelRequest {
|
||||||
@ -483,7 +701,7 @@ pub fn get_adyen_response(
|
|||||||
storage_enums::AttemptStatus::Charged
|
storage_enums::AttemptStatus::Charged
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdyenStatus::Refused => storage_enums::AttemptStatus::Failure,
|
AdyenStatus::Refused | AdyenStatus::Cancelled => storage_enums::AttemptStatus::Failure,
|
||||||
_ => storage_enums::AttemptStatus::Pending,
|
_ => storage_enums::AttemptStatus::Pending,
|
||||||
};
|
};
|
||||||
let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() {
|
let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() {
|
||||||
@ -709,6 +927,11 @@ impl<F> TryFrom<&types::RefundsRouterData<F>> for AdyenRefundRequest {
|
|||||||
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
let auth_type = AdyenAuthType::try_from(&item.connector_auth_type)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
merchant_account: auth_type.merchant_account,
|
merchant_account: auth_type.merchant_account,
|
||||||
|
amount: Amount {
|
||||||
|
currency: item.request.currency.to_string(),
|
||||||
|
value: item.request.refund_amount,
|
||||||
|
},
|
||||||
|
merchant_refund_reason: item.request.reason.clone(),
|
||||||
reference: item.request.refund_id.clone(),
|
reference: item.request.refund_id.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,7 @@ pub trait PaymentsRequestData {
|
|||||||
fn get_billing_country(&self) -> Result<String, Error>;
|
fn get_billing_country(&self) -> Result<String, Error>;
|
||||||
fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error>;
|
fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error>;
|
||||||
fn get_card(&self) -> Result<api::Card, Error>;
|
fn get_card(&self) -> Result<api::Card, Error>;
|
||||||
|
fn get_return_url(&self) -> Result<String, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RefundsRequestData {
|
pub trait RefundsRequestData {
|
||||||
@ -91,6 +92,12 @@ impl PaymentsRequestData for types::PaymentsAuthorizeRouterData {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(missing_field_err("billing"))
|
.ok_or_else(missing_field_err("billing"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_return_url(&self) -> Result<String, Error> {
|
||||||
|
self.router_return_url
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(missing_field_err("router_return_url"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CardData {
|
pub trait CardData {
|
||||||
|
|||||||
445
crates/router/tests/connectors/adyen.rs
Normal file
445
crates/router/tests/connectors/adyen.rs
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
use api_models::payments::{Address, AddressDetails};
|
||||||
|
use masking::Secret;
|
||||||
|
use router::types::{self, api, storage::enums, PaymentAddress};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
connector_auth,
|
||||||
|
utils::{self, ConnectorActions, PaymentInfo},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AdyenTest;
|
||||||
|
impl ConnectorActions for AdyenTest {}
|
||||||
|
impl utils::Connector for AdyenTest {
|
||||||
|
fn get_data(&self) -> types::api::ConnectorData {
|
||||||
|
use router::connector::Adyen;
|
||||||
|
types::api::ConnectorData {
|
||||||
|
connector: Box::new(&Adyen),
|
||||||
|
connector_name: types::Connector::Adyen,
|
||||||
|
get_token: types::api::GetToken::Connector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||||
|
types::ConnectorAuthType::from(
|
||||||
|
connector_auth::ConnectorAuthentication::new()
|
||||||
|
.adyen
|
||||||
|
.expect("Missing connector authentication configuration"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> String {
|
||||||
|
"adyen".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdyenTest {
|
||||||
|
fn get_payment_info() -> Option<PaymentInfo> {
|
||||||
|
Some(PaymentInfo {
|
||||||
|
address: Some(PaymentAddress {
|
||||||
|
billing: Some(Address {
|
||||||
|
address: Some(AddressDetails {
|
||||||
|
country: Some("US".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
phone: None,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
router_return_url: Some(String::from("http://localhost:8080")),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_payment_authorize_data(
|
||||||
|
card_number: &str,
|
||||||
|
card_exp_month: &str,
|
||||||
|
card_exp_year: &str,
|
||||||
|
card_cvc: &str,
|
||||||
|
capture_method: enums::CaptureMethod,
|
||||||
|
) -> Option<types::PaymentsAuthorizeData> {
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
amount: 3500,
|
||||||
|
currency: enums::Currency::USD,
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(types::api::Card {
|
||||||
|
card_number: Secret::new(card_number.to_string()),
|
||||||
|
card_exp_month: Secret::new(card_exp_month.to_string()),
|
||||||
|
card_exp_year: Secret::new(card_exp_year.to_string()),
|
||||||
|
card_holder_name: Secret::new("John Doe".to_string()),
|
||||||
|
card_cvc: Secret::new(card_cvc.to_string()),
|
||||||
|
}),
|
||||||
|
confirm: true,
|
||||||
|
statement_descriptor_suffix: None,
|
||||||
|
setup_future_usage: None,
|
||||||
|
mandate_id: None,
|
||||||
|
off_session: None,
|
||||||
|
setup_mandate_details: None,
|
||||||
|
capture_method: Some(capture_method),
|
||||||
|
browser_info: None,
|
||||||
|
order_details: None,
|
||||||
|
email: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONNECTOR: AdyenTest = AdyenTest {};
|
||||||
|
|
||||||
|
// Cards Positive Tests
|
||||||
|
// Creates a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_only_authorize_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.authorize_payment(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"4111111111111111",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Authorize payment response");
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captures a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_capture_authorized_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.authorize_and_capture_payment(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"370000000000002",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"7373",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Capture payment response");
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partially captures a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_partially_capture_authorized_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.authorize_and_capture_payment(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"4293189100000008",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
Some(types::PaymentsCaptureData {
|
||||||
|
amount_to_capture: Some(50),
|
||||||
|
..utils::PaymentCaptureType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Capture payment response");
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Charged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voids a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_void_authorized_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.authorize_and_void_payment(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"4293189100000008",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
Some(types::PaymentsCancelData {
|
||||||
|
connector_transaction_id: String::from(""),
|
||||||
|
cancellation_reason: Some("requested_by_customer".to_string()),
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Void payment response");
|
||||||
|
assert_eq!(response.status, enums::AttemptStatus::Voided);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refunds a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_refund_manually_captured_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.capture_payment_and_refund(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"370000000000002",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"7373",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Some(types::RefundsData {
|
||||||
|
refund_amount: 1500,
|
||||||
|
reason: Some("CUSTOMER REQUEST".to_string()),
|
||||||
|
..utils::PaymentRefundType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partially refunds a payment using the manual capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_partially_refund_manually_captured_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.capture_payment_and_refund(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"2222400070000005",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Some(types::RefundsData {
|
||||||
|
refund_amount: 1500,
|
||||||
|
reason: Some("CUSTOMER REQUEST".to_string()),
|
||||||
|
..utils::PaymentRefundType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a payment using the automatic capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_make_payment() {
|
||||||
|
let authorize_response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"2222400070000005",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Manual,
|
||||||
|
),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(authorize_response.status, enums::AttemptStatus::Charged);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refunds a payment using the automatic capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_refund_auto_captured_payment() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment_and_refund(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"2222400070000005",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Automatic,
|
||||||
|
),
|
||||||
|
Some(types::RefundsData {
|
||||||
|
refund_amount: 1000,
|
||||||
|
reason: Some("CUSTOMER REQUEST".to_string()),
|
||||||
|
..utils::PaymentRefundType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partially refunds a payment using the automatic capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_partially_refund_succeeded_payment() {
|
||||||
|
let refund_response = CONNECTOR
|
||||||
|
.make_payment_and_refund(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"4293189100000008",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Automatic,
|
||||||
|
),
|
||||||
|
Some(types::RefundsData {
|
||||||
|
refund_amount: 500,
|
||||||
|
reason: Some("CUSTOMER REQUEST".to_string()),
|
||||||
|
..utils::PaymentRefundType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
refund_response.response.unwrap().refund_status,
|
||||||
|
enums::RefundStatus::Success,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS).
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_refund_succeeded_payment_multiple_times() {
|
||||||
|
CONNECTOR
|
||||||
|
.make_payment_and_multiple_refund(
|
||||||
|
AdyenTest::get_payment_authorize_data(
|
||||||
|
"2222400070000005",
|
||||||
|
"03",
|
||||||
|
"2030",
|
||||||
|
"737",
|
||||||
|
enums::CaptureMethod::Automatic,
|
||||||
|
),
|
||||||
|
Some(types::RefundsData {
|
||||||
|
refund_amount: 100,
|
||||||
|
reason: Some("CUSTOMER REQUEST".to_string()),
|
||||||
|
..utils::PaymentRefundType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cards Negative scenerios
|
||||||
|
// Creates a payment with incorrect card number.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_incorrect_card_number() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||||
|
card_number: Secret::new("1234567891011".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..utils::PaymentAuthorizeType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap_err().message,
|
||||||
|
"Invalid card number",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a payment with empty card number.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_empty_card_number() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||||
|
card_number: Secret::new(String::from("")),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..utils::PaymentAuthorizeType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let x = response.response.unwrap_err();
|
||||||
|
assert_eq!(x.message, "Missing payment method details: number",);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a payment with incorrect CVC.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_incorrect_cvc() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||||
|
card_cvc: Secret::new("12345".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..utils::PaymentAuthorizeType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap_err().message,
|
||||||
|
"CVC is not the right length",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a payment with incorrect expiry month.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_invalid_exp_month() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||||
|
card_exp_month: Secret::new("20".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..utils::PaymentAuthorizeType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
response.response.unwrap_err().message,
|
||||||
|
"The provided Expiry Date is not valid.: Expiry month should be between 1 and 12 inclusive: 20",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a payment with incorrect expiry year.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_payment_for_incorrect_expiry_year() {
|
||||||
|
let response = CONNECTOR
|
||||||
|
.make_payment(
|
||||||
|
Some(types::PaymentsAuthorizeData {
|
||||||
|
payment_method_data: types::api::PaymentMethod::Card(api::Card {
|
||||||
|
card_exp_year: Secret::new("2000".to_string()),
|
||||||
|
..utils::CCardType::default().0
|
||||||
|
}),
|
||||||
|
..utils::PaymentAuthorizeType::default().0
|
||||||
|
}),
|
||||||
|
AdyenTest::get_payment_info(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(response.response.unwrap_err().message, "Expired Card",);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Captures a payment using invalid connector payment id.
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn should_fail_capture_for_invalid_payment() {
|
||||||
|
let capture_response = CONNECTOR
|
||||||
|
.capture_payment("123456789".to_string(), None, AdyenTest::get_payment_info())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
capture_response.response.unwrap_err().message,
|
||||||
|
String::from("Original pspReference required for this operation")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connector dependent test cases goes here
|
||||||
|
|
||||||
|
// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests
|
||||||
@ -4,6 +4,7 @@ use serde::Deserialize;
|
|||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub(crate) struct ConnectorAuthentication {
|
pub(crate) struct ConnectorAuthentication {
|
||||||
pub aci: Option<BodyKey>,
|
pub aci: Option<BodyKey>,
|
||||||
|
pub adyen: Option<BodyKey>,
|
||||||
pub authorizedotnet: Option<BodyKey>,
|
pub authorizedotnet: Option<BodyKey>,
|
||||||
pub checkout: Option<BodyKey>,
|
pub checkout: Option<BodyKey>,
|
||||||
pub cybersource: Option<SignatureKey>,
|
pub cybersource: Option<SignatureKey>,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
|
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
|
||||||
|
|
||||||
mod aci;
|
mod aci;
|
||||||
|
mod adyen;
|
||||||
mod authorizedotnet;
|
mod authorizedotnet;
|
||||||
mod checkout;
|
mod checkout;
|
||||||
mod connector_auth;
|
mod connector_auth;
|
||||||
|
|||||||
@ -5,6 +5,10 @@
|
|||||||
api_key = "Bearer MyApiKey"
|
api_key = "Bearer MyApiKey"
|
||||||
key1 = "MyEntityId"
|
key1 = "MyEntityId"
|
||||||
|
|
||||||
|
[adyen]
|
||||||
|
api_key = "Bearer MyApiKey"
|
||||||
|
key1 = "MerchantId"
|
||||||
|
|
||||||
[authorizedotnet]
|
[authorizedotnet]
|
||||||
api_key = "MyMerchantName"
|
api_key = "MyMerchantName"
|
||||||
key1 = "MyTransactionKey"
|
key1 = "MyTransactionKey"
|
||||||
|
|||||||
Reference in New Issue
Block a user