feat(payouts): make payout_type optional in payouts table (#4954)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
chikke srujan
2024-06-12 19:54:52 +05:30
committed by GitHub
parent 4651584ecc
commit b847606d66
27 changed files with 98 additions and 60 deletions

View File

@ -16613,7 +16613,6 @@
"merchant_id",
"amount",
"currency",
"payout_type",
"customer_id",
"auto_fulfill",
"client_secret",
@ -16656,7 +16655,12 @@
"nullable": true
},
"payout_type": {
"$ref": "#/components/schemas/PayoutType"
"allOf": [
{
"$ref": "#/components/schemas/PayoutType"
}
],
"nullable": true
},
"billing": {
"type": "object",

View File

@ -140,26 +140,28 @@ pub enum Connector {
impl Connector {
#[cfg(feature = "payouts")]
pub fn supports_instant_payout(&self, payout_method: PayoutType) -> bool {
pub fn supports_instant_payout(&self, payout_method: Option<PayoutType>) -> bool {
matches!(
(self, payout_method),
(Self::Paypal, PayoutType::Wallet) | (_, PayoutType::Card) | (Self::Adyenplatform, _)
(Self::Paypal, Some(PayoutType::Wallet))
| (_, Some(PayoutType::Card))
| (Self::Adyenplatform, _)
)
}
#[cfg(feature = "payouts")]
pub fn supports_create_recipient(&self, payout_method: PayoutType) -> bool {
matches!((self, payout_method), (_, PayoutType::Bank))
pub fn supports_create_recipient(&self, payout_method: Option<PayoutType>) -> bool {
matches!((self, payout_method), (_, Some(PayoutType::Bank)))
}
#[cfg(feature = "payouts")]
pub fn supports_payout_eligibility(&self, payout_method: PayoutType) -> bool {
matches!((self, payout_method), (_, PayoutType::Card))
pub fn supports_payout_eligibility(&self, payout_method: Option<PayoutType>) -> bool {
matches!((self, payout_method), (_, Some(PayoutType::Card)))
}
#[cfg(feature = "payouts")]
pub fn is_payout_quote_call_required(&self) -> bool {
matches!(self, Self::Wise)
}
#[cfg(feature = "payouts")]
pub fn supports_access_token_for_payout(&self, payout_method: PayoutType) -> bool {
pub fn supports_access_token_for_payout(&self, payout_method: Option<PayoutType>) -> bool {
matches!((self, payout_method), (Self::Paypal, _))
}
#[cfg(feature = "payouts")]

View File

@ -349,8 +349,8 @@ pub struct PayoutCreateResponse {
pub connector: Option<String>,
/// The payout method that is to be used
#[schema(value_type = PayoutType, example = "bank")]
pub payout_type: api_enums::PayoutType,
#[schema(value_type = Option<PayoutType>, example = "bank")]
pub payout_type: Option<api_enums::PayoutType>,
/// The billing address for the payout
#[schema(value_type = Option<Object>, example = json!(r#"{

View File

@ -14,7 +14,7 @@ pub struct Payouts {
pub merchant_id: String,
pub customer_id: id_type::CustomerId,
pub address_id: String,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub payout_method_id: Option<String>,
pub amount: i64,
pub destination_currency: storage_enums::Currency,
@ -53,7 +53,7 @@ pub struct PayoutsNew {
pub merchant_id: String,
pub customer_id: id_type::CustomerId,
pub address_id: String,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub payout_method_id: Option<String>,
pub amount: i64,
pub destination_currency: storage_enums::Currency,

View File

@ -194,11 +194,12 @@ impl PayoutAttempt {
let filter_payout_method = Payouts::table()
.select(payout_dsl::payout_type)
.distinct()
.get_results_async::<enums::PayoutType>(conn)
.get_results_async::<Option<enums::PayoutType>>(conn)
.await
.change_context(DatabaseError::Others)
.attach_printable("Error filtering records by payout type")?
.into_iter()
.flatten()
.collect::<Vec<enums::PayoutType>>();
Ok((

View File

@ -1004,7 +1004,7 @@ diesel::table! {
customer_id -> Varchar,
#[max_length = 64]
address_id -> Varchar,
payout_type -> PayoutType,
payout_type -> Nullable<PayoutType>,
#[max_length = 64]
payout_method_id -> Nullable<Varchar>,
amount -> Int8,

View File

@ -73,7 +73,7 @@ pub struct Payouts {
pub merchant_id: String,
pub customer_id: id_type::CustomerId,
pub address_id: String,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub payout_method_id: Option<String>,
pub amount: i64,
pub destination_currency: storage_enums::Currency,
@ -99,7 +99,7 @@ pub struct PayoutsNew {
pub merchant_id: String,
pub customer_id: id_type::CustomerId,
pub address_id: String,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub payout_method_id: Option<String>,
pub amount: i64,
pub destination_currency: storage_enums::Currency,
@ -128,7 +128,7 @@ impl Default for PayoutsNew {
merchant_id: String::default(),
customer_id: common_utils::generate_customer_id_of_default_length(),
address_id: String::default(),
payout_type: storage_enums::PayoutType::default(),
payout_type: Some(storage_enums::PayoutType::default()),
payout_method_id: Option::default(),
amount: i64::default(),
destination_currency: storage_enums::Currency::default(),

View File

@ -646,7 +646,7 @@ pub struct PayoutsData {
pub connector_payout_id: Option<String>,
pub destination_currency: storage_enums::Currency,
pub source_currency: storage_enums::Currency,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub entity_type: storage_enums::PayoutEntityType,
pub customer_details: Option<CustomerDetails>,
pub vendor_details: Option<api_models::payouts::PayoutVendorAccountDetails>,

View File

@ -116,7 +116,7 @@ pub struct StripePayoutResponse {
pub id: String,
pub amount: i64,
pub currency: String,
pub payout_type: common_enums::PayoutType,
pub payout_type: Option<common_enums::PayoutType>,
pub status: StripePayoutStatus,
pub name: Option<masking::Secret<String>>,
pub email: Option<Email>,

View File

@ -13,6 +13,8 @@ use router_env::{instrument, tracing};
use self::transformers as adyen;
use super::utils::is_mandate_supported;
#[cfg(feature = "payouts")]
use crate::connector::utils::PayoutsData;
use crate::{
capture_method_not_supported,
configs::settings,
@ -1447,11 +1449,12 @@ impl services::ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::P
req.test_mode,
&req.connector_meta_data,
)?;
let payout_type = req.request.get_payout_type()?;
Ok(format!(
"{}pal/servlet/Payout/{}/{}",
endpoint,
ADYEN_API_VERSION,
match req.request.payout_type {
match payout_type {
storage_enums::PayoutType::Bank | storage_enums::PayoutType::Wallet =>
"confirmThirdParty".to_string(),
storage_enums::PayoutType::Card => "payout".to_string(),
@ -1472,9 +1475,17 @@ impl services::ConnectorIntegration<api::PoFulfill, types::PayoutsData, types::P
)];
let auth = adyen::AdyenAuthType::try_from(&req.connector_auth_type)
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
let payout_type = req
.request
.payout_type
.to_owned()
.get_required_value("payout_type")
.change_context(errors::ConnectorError::MissingRequiredField {
field_name: "payout_type",
})?;
let mut api_key = vec![(
headers::X_API_KEY.to_string(),
match req.request.payout_type {
match payout_type {
storage_enums::PayoutType::Bank | storage_enums::PayoutType::Wallet => {
auth.review_key.unwrap_or(auth.api_key).into_masked()
}

View File

@ -9,6 +9,8 @@ use reqwest::Url;
use serde::{Deserialize, Serialize};
use time::{Duration, OffsetDateTime, PrimitiveDateTime};
#[cfg(feature = "payouts")]
use crate::{connector::utils::PayoutsData, types::api::payouts, utils::OptionExt};
use crate::{
connector::utils::{
self, AddressDetailsData, BrowserInformationData, CardData, MandateReferenceData,
@ -28,8 +30,6 @@ use crate::{
},
utils as crate_utils,
};
#[cfg(feature = "payouts")]
use crate::{types::api::payouts, utils::OptionExt};
type Error = error_stack::Report<errors::ConnectorError>;
@ -4757,7 +4757,7 @@ impl<F> TryFrom<&AdyenRouterData<&types::PayoutsRouterData<F>>> for AdyenPayoutF
type Error = Error;
fn try_from(item: &AdyenRouterData<&types::PayoutsRouterData<F>>) -> Result<Self, Self::Error> {
let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?;
let payout_type = item.router_data.request.payout_type.to_owned();
let payout_type = item.router_data.request.get_payout_type()?;
let merchant_account = auth_type.merchant_account;
match payout_type {
storage_enums::PayoutType::Bank | storage_enums::PayoutType::Wallet => {

View File

@ -7,7 +7,7 @@ use super::Error;
use crate::{
connector::{
adyen::transformers as adyen,
utils::{self, RouterData},
utils::{self, PayoutsData, RouterData},
},
core::errors,
types::{self, api::payouts, storage::enums},
@ -232,14 +232,14 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for AdyenTransferRequest {
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "priority",
})?;
let payout_type = request.get_payout_type()?;
Ok(Self {
amount: adyen::Amount {
value: request.amount,
currency: request.destination_currency,
},
balance_account_id,
category: AdyenPayoutMethod::try_from(request.payout_type)?,
category: AdyenPayoutMethod::try_from(payout_type)?,
counterparty,
priority: AdyenPayoutPriority::from(priority),
reference: request.payout_id.clone(),

View File

@ -12,6 +12,8 @@ use masking::{ExposeInterface, PeekInterface, Secret};
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[cfg(feature = "payouts")]
use crate::connector::utils::PayoutsData;
use crate::{
connector::utils::{
self, AddressDetailsData, ApplePayDecrypt, CardData, PaymentsAuthorizeRequestData,
@ -3152,7 +3154,8 @@ impl TryFrom<&CybersourceRouterData<&types::PayoutsRouterData<api::PoFulfill>>>
fn try_from(
item: &CybersourceRouterData<&types::PayoutsRouterData<api::PoFulfill>>,
) -> Result<Self, Self::Error> {
match item.router_data.request.payout_type {
let payout_type = item.router_data.request.get_payout_type()?;
match payout_type {
enums::PayoutType::Card => {
let client_reference_information = ClientReferenceInformation {
code: Some(item.router_data.request.payout_id.clone()),

View File

@ -240,7 +240,8 @@ impl<F> TryFrom<&EbanxRouterData<&types::PayoutsRouterData<F>>> for EbanxPayoutF
fn try_from(item: &EbanxRouterData<&types::PayoutsRouterData<F>>) -> Result<Self, Self::Error> {
let request = item.router_data.request.to_owned();
let ebanx_auth_type = EbanxAuthType::try_from(&item.router_data.connector_auth_type)?;
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Bank => Ok(Self {
integration_key: ebanx_auth_type.integration_key,
uid: request
@ -330,7 +331,8 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for EbanxPayoutCancelRequest {
fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
let request = item.request.to_owned();
let ebanx_auth_type = EbanxAuthType::try_from(&item.connector_auth_type)?;
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Bank => Ok(Self {
integration_key: ebanx_auth_type.integration_key,
uid: request

View File

@ -18,7 +18,7 @@ type Error = error_stack::Report<errors::ConnectorError>;
#[cfg(feature = "payouts")]
use crate::{
connector::utils::{CardData, RouterData},
connector::utils::{CardData, PayoutsData, RouterData},
core::errors,
types::{self, api, storage::enums as storage_enums, transformers::ForeignFrom},
utils::OptionExt,
@ -139,7 +139,8 @@ impl TryFrom<PayoneRouterData<&types::PayoutsRouterData<api::PoFulfill>>>
item: PayoneRouterData<&types::PayoutsRouterData<api::PoFulfill>>,
) -> Result<Self, Self::Error> {
let request = item.router_data.request.to_owned();
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Card => {
let amount_of_money: AmountOfMoney = AmountOfMoney {
amount: item.amount,

View File

@ -1169,6 +1169,8 @@ pub trait PayoutsData {
fn get_transfer_id(&self) -> Result<String, Error>;
fn get_customer_details(&self) -> Result<types::CustomerDetails, Error>;
fn get_vendor_details(&self) -> Result<PayoutVendorAccountDetails, Error>;
#[cfg(feature = "payouts")]
fn get_payout_type(&self) -> Result<storage_enums::PayoutType, Error>;
}
#[cfg(feature = "payouts")]
@ -1188,6 +1190,12 @@ impl PayoutsData for types::PayoutsData {
.clone()
.ok_or_else(missing_field_err("vendor_details"))
}
#[cfg(feature = "payouts")]
fn get_payout_type(&self) -> Result<storage_enums::PayoutType, Error> {
self.payout_type
.to_owned()
.ok_or_else(missing_field_err("payout_type"))
}
}
#[derive(Clone, Debug, serde::Serialize)]

View File

@ -9,7 +9,7 @@ type Error = error_stack::Report<errors::ConnectorError>;
#[cfg(feature = "payouts")]
use crate::{
connector::utils::{self, RouterData},
connector::utils::{self, PayoutsData, RouterData},
types::{
api::payouts,
storage::enums::{self as storage_enums, PayoutEntityType},
@ -352,7 +352,7 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for WiseRecipientCreateRequest {
type Error = Error;
fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
let request = item.request.to_owned();
let customer_details = request.customer_details;
let customer_details = request.customer_details.to_owned();
let payout_method_data = item.get_payout_method_data()?;
let bank_details = get_payout_bank_details(
payout_method_data.to_owned(),
@ -365,7 +365,8 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for WiseRecipientCreateRequest {
field_name: "source_id for PayoutRecipient creation",
}),
}?;
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Card | storage_enums::PayoutType::Wallet => {
Err(errors::ConnectorError::NotImplemented(
utils::get_unimplemented_payment_method_error_message("Wise"),
@ -421,7 +422,8 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for WisePayoutQuoteRequest {
type Error = Error;
fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
let request = item.request.to_owned();
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Bank => Ok(Self {
source_amount: Some(request.amount),
source_currency: request.source_currency.to_string(),
@ -467,7 +469,8 @@ impl<F> TryFrom<&types::PayoutsRouterData<F>> for WisePayoutCreateRequest {
type Error = Error;
fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
let request = item.request.to_owned();
match request.payout_type.to_owned() {
let payout_type = request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Bank => {
let connector_customer_id = item.get_connector_customer_id()?;
let quote_uuid = item.get_quote_id()?;
@ -529,8 +532,8 @@ impl<F> TryFrom<types::PayoutsResponseRouterData<F, WisePayoutResponse>>
impl<F> TryFrom<&types::PayoutsRouterData<F>> for WisePayoutFulfillRequest {
type Error = Error;
fn try_from(item: &types::PayoutsRouterData<F>) -> Result<Self, Self::Error> {
let request = item.request.to_owned();
match request.payout_type.to_owned() {
let payout_type = item.request.get_payout_type()?;
match payout_type {
storage_enums::PayoutType::Bank => Ok(Self {
fund_type: FundType::default(),
}),

View File

@ -138,9 +138,10 @@ pub fn make_dsl_input_for_payouts(
setup_future_usage: None,
};
let payment_method = dsl_inputs::PaymentMethodInput {
payment_method: Some(api_enums::PaymentMethod::foreign_from(
payout_data.payouts.payout_type,
)),
payment_method: payout_data
.payouts
.payout_type
.map(api_enums::PaymentMethod::foreign_from),
payment_method_type: payout_data
.payout_method_data
.clone()

View File

@ -995,7 +995,7 @@ impl ForeignFrom<(storage::Payouts, storage::PayoutAttempt, domain::Customer)>
connector: payout_attempt.connector.clone(),
error_code: payout_attempt.error_code.clone(),
error_message: payout_attempt.error_message.clone(),
payment_method: Some(payout.payout_type),
payment_method: payout.payout_type,
payout_method_type: None,
connector_transaction_id: payout_attempt.connector_payout_id,
cancellation_reason: None,

View File

@ -311,6 +311,7 @@ pub async fn payouts_create_core(
.await?;
let payout_attempt = payout_data.payout_attempt.to_owned();
let payout_type = payout_data.payouts.payout_type.to_owned();
// Persist payout method data in temp locker
payout_data.payout_method_data = helpers::make_payout_method_data(
@ -319,7 +320,7 @@ pub async fn payouts_create_core(
payout_attempt.payout_token.as_deref(),
&payout_attempt.customer_id,
&payout_attempt.merchant_id,
Some(&payout_data.payouts.payout_type.clone()),
payout_type,
&key_store,
Some(&mut payout_data),
merchant_account.storage_scheme,
@ -449,7 +450,7 @@ pub async fn payouts_update_core(
payout_attempt.payout_token.as_deref(),
&payout_attempt.customer_id,
&payout_attempt.merchant_id,
Some(&payout_data.payouts.payout_type.clone()),
payout_data.payouts.payout_type,
&key_store,
Some(&mut payout_data),
merchant_account.storage_scheme,
@ -639,7 +640,7 @@ pub async fn payouts_fulfill_core(
payout_attempt.payout_token.as_deref(),
&payout_attempt.customer_id,
&payout_attempt.merchant_id,
Some(&payout_data.payouts.payout_type.clone()),
payout_data.payouts.payout_type,
&key_store,
Some(&mut payout_data),
merchant_account.storage_scheme,
@ -892,7 +893,7 @@ pub async fn call_connector_payout(
payout_attempt.payout_token.as_deref(),
&payout_attempt.customer_id,
&payout_attempt.merchant_id,
Some(&payouts.payout_type),
payouts.payout_type,
key_store,
Some(payout_data),
merchant_account.storage_scheme,
@ -2003,10 +2004,7 @@ pub async fn payout_create_db_entries(
// Make payouts entry
let currency = req.currency.to_owned().get_required_value("currency")?;
let payout_type = req
.payout_type
.to_owned()
.get_required_value("payout_type")?;
let payout_type = req.payout_type.to_owned();
let payout_method_id = if stored_payout_method_data.is_some() {
req.payout_token.to_owned()

View File

@ -21,7 +21,7 @@ pub async fn create_access_token<F: Clone + 'static>(
connector_data: &api_types::ConnectorData,
merchant_account: &domain::MerchantAccount,
router_data: &mut types::PayoutsRouterData<F>,
payout_type: enums::PayoutType,
payout_type: Option<enums::PayoutType>,
) -> RouterResult<()> {
let connector_access_token = add_access_token_for_payout(
state,
@ -52,7 +52,7 @@ pub async fn add_access_token_for_payout<F: Clone + 'static>(
connector: &api_types::ConnectorData,
merchant_account: &domain::MerchantAccount,
router_data: &types::PayoutsRouterData<F>,
payout_type: enums::PayoutType,
payout_type: Option<enums::PayoutType>,
) -> RouterResult<types::AddAccessTokenResult> {
if connector
.connector_name

View File

@ -46,17 +46,17 @@ pub async fn make_payout_method_data<'a>(
payout_token: Option<&str>,
customer_id: &id_type::CustomerId,
merchant_id: &str,
payout_type: Option<&api_enums::PayoutType>,
payout_type: Option<api_enums::PayoutType>,
merchant_key_store: &domain::MerchantKeyStore,
payout_data: Option<&mut PayoutData>,
storage_scheme: storage::enums::MerchantStorageScheme,
) -> RouterResult<Option<api::PayoutMethodData>> {
let db = &*state.store;
let certain_payout_type = payout_type.get_required_value("payout_type")?.to_owned();
let hyperswitch_token = if let Some(payout_token) = payout_token {
if payout_token.starts_with("temporary_token_") {
Some(payout_token.to_string())
} else {
let certain_payout_type = payout_type.get_required_value("payout_type")?.to_owned();
let key = format!(
"pm_token_{}_{}_hyperswitch",
payout_token,
@ -110,7 +110,7 @@ pub async fn make_payout_method_data<'a>(
// Get operation
(None, Some(payout_token), _) => {
if payout_token.starts_with("temporary_token_")
|| certain_payout_type == api_enums::PayoutType::Bank
|| payout_type == Some(api_enums::PayoutType::Bank)
{
let (pm, supplementary_data) = vault::Vault::get_payout_method_data_from_temporary_locker(
state,

View File

@ -98,7 +98,7 @@ pub async fn validate_create_request(
Some(&payout_token),
&customer_id,
&merchant_account.merchant_id,
req.payout_type.as_ref(),
req.payout_type,
merchant_key_store,
None,
merchant_account.storage_scheme,

View File

@ -12,7 +12,7 @@ pub struct KafkaPayout<'a> {
pub address_id: &'a String,
pub profile_id: &'a String,
pub payout_method_id: Option<&'a String>,
pub payout_type: storage_enums::PayoutType,
pub payout_type: Option<storage_enums::PayoutType>,
pub amount: i64,
pub destination_currency: storage_enums::Currency,
pub source_currency: storage_enums::Currency,

View File

@ -441,7 +441,7 @@ pub trait ConnectorActions: Connector {
pi.currency.map_or(enums::Currency::EUR, |c| c)
}),
entity_type: enums::PayoutEntityType::Individual,
payout_type,
payout_type: Some(payout_type),
customer_details: Some(payments::CustomerDetails {
customer_id: Some(common_utils::generate_customer_id_of_default_length()),
name: Some(Secret::new("John Doe".to_string())),

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE payouts ALTER COLUMN payout_type SET NOT NULL;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE payouts ALTER COLUMN payout_type DROP NOT NULL;