Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core): add recurring customer support for nomupay payouts. #6687

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -16854,6 +16854,11 @@
"example": "+1",
"nullable": true,
"maxLength": 255
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
},
"additionalProperties": false
Expand Down Expand Up @@ -17093,6 +17098,11 @@
"example": "Invalid card details",
"nullable": true,
"maxLength": 1024
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
},
"additionalProperties": false
Expand Down
20 changes: 20 additions & 0 deletions api-reference/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -20478,6 +20478,11 @@
"example": "+1",
"nullable": true,
"maxLength": 255
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
}
},
Expand Down Expand Up @@ -20776,6 +20781,11 @@
"example": "Invalid card details",
"nullable": true,
"maxLength": 1024
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
},
"additionalProperties": false
Expand Down Expand Up @@ -21422,6 +21432,11 @@
"example": "+1",
"nullable": true,
"maxLength": 255
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
}
},
Expand Down Expand Up @@ -21634,6 +21649,11 @@
"example": "+1",
"nullable": true,
"maxLength": 255
},
"payout_method_id": {
"type": "string",
"description": "Identifier for payout method",
"nullable": true
}
}
},
Expand Down
111 changes: 106 additions & 5 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[derive(Debug, serde::Serialize, Clone)]
/// This struct is only used by and internal api to migrate payment method
pub struct PaymentMethodMigrate {
/// Merchant id
Expand Down Expand Up @@ -283,7 +283,7 @@
pub billing: Option<payments::Address>,

/// The connector mandate details of the payment method
pub connector_mandate_details: Option<PaymentsMandateReference>,
pub connector_mandate_details: Option<CommonMandateReference>,

// The CIT (customer initiated transaction) transaction id associated with the payment method
pub network_transaction_id: Option<String>,
Expand All @@ -307,11 +307,21 @@
pub network_transaction_id_migrated: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentsMandateReference(
pub HashMap<id_type::MerchantConnectorAccountId, PaymentsMandateReferenceRecord>,
);

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PayoutsMandateReference(
pub HashMap<id_type::MerchantConnectorAccountId, PayoutsMandateReferenceRecord>,
);

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PayoutsMandateReferenceRecord {
pub transfer_method_id: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentsMandateReferenceRecord {
pub connector_mandate_id: String,
Expand All @@ -320,6 +330,95 @@
pub original_payment_authorized_currency: Option<common_enums::Currency>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CommonMandateReference {
pub payments: Option<PaymentsMandateReference>,
pub payouts: Option<PayoutsMandateReference>,
}

impl<'de> serde::Deserialize<'de> for PaymentMethodMigrate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Debug, serde::Deserialize)]
struct InnerPaymentMethodMigrate {
pub merchant_id: id_type::MerchantId,
pub payment_method: Option<api_enums::PaymentMethod>,
pub payment_method_type: Option<api_enums::PaymentMethodType>,
pub payment_method_issuer: Option<String>,
pub payment_method_issuer_code: Option<api_enums::PaymentMethodIssuerCode>,
pub card: Option<MigrateCardDetail>,
pub network_token: Option<MigrateNetworkTokenDetail>,
pub metadata: Option<pii::SecretSerdeValue>,
pub customer_id: Option<id_type::CustomerId>,
pub card_network: Option<String>,
pub bank_transfer: Option<payouts::Bank>,
pub wallet: Option<payouts::Wallet>,
pub payment_method_data: Option<PaymentMethodCreateData>,
pub billing: Option<payments::Address>,
pub connector_mandate_details: Option<serde_json::Value>,
pub network_transaction_id: Option<String>,
}

let inner = InnerPaymentMethodMigrate::deserialize(deserializer)?;

let connector_mandate_details =
if let Some(connector_mandate_value) = inner.connector_mandate_details {
if let Ok(common_mandate) = serde_json::from_value::<CommonMandateReference>(
connector_mandate_value.clone(),
) {
Some(common_mandate)
} else if let Ok(payment_mandate_record) =
serde_json::from_value::<PaymentsMandateReference>(connector_mandate_value)
{
Some(CommonMandateReference {
payments: Some(payment_mandate_record),
payouts: None,
})
} else {
return Err(de::Error::custom("Faild to deserialize PaymentMethod_V2"));

Check warning on line 380 in crates/api_models/src/payment_methods.rs

View workflow job for this annotation

GitHub Actions / Spell check

"Faild" should be "Failed" or "Fail" or "Fails".
}
} else {
None
};

Ok(Self {
merchant_id: inner.merchant_id,
payment_method: inner.payment_method,
payment_method_type: inner.payment_method_type,
payment_method_issuer: inner.payment_method_issuer,
payment_method_issuer_code: inner.payment_method_issuer_code,
card: inner.card,
network_token: inner.network_token,
metadata: inner.metadata,
customer_id: inner.customer_id,
card_network: inner.card_network,
bank_transfer: inner.bank_transfer,
wallet: inner.wallet,
payment_method_data: inner.payment_method_data,
billing: inner.billing,
connector_mandate_details,
network_transaction_id: inner.network_transaction_id,
})
}
}

pub fn convert_to_payments_reference(
common_mandate: Option<CommonMandateReference>,
) -> Option<PaymentsMandateReference> {
common_mandate.and_then(|cm| cm.payments)
}

pub fn convert_to_common_reference(
payments_reference: Option<PaymentsMandateReference>,
) -> Option<CommonMandateReference> {
payments_reference.map(|payments| CommonMandateReference {
payments: Some(payments),
payouts: None,
})
}

#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
Expand Down Expand Up @@ -353,7 +452,9 @@
payment_method_issuer_code: payment_method_migrate.payment_method_issuer_code,
metadata: payment_method_migrate.metadata.clone(),
payment_method_data: payment_method_migrate.payment_method_data.clone(),
connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(),
connector_mandate_details: convert_to_payments_reference(
payment_method_migrate.connector_mandate_details.clone(),
),
client_secret: None,
billing: payment_method_migrate.billing.clone(),
card: card_details,
Expand Down Expand Up @@ -2359,7 +2460,7 @@
}),
email: record.email,
}),
connector_mandate_details,
connector_mandate_details: convert_to_common_reference(connector_mandate_details),
metadata: None,
payment_method_issuer_code: None,
card_network: None,
Expand Down
6 changes: 6 additions & 0 deletions crates/api_models/src/payouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ pub struct PayoutCreateRequest {
/// Customer's phone country code. _Deprecated: Use customer object instead._
#[schema(deprecated, max_length = 255, example = "+1")]
pub phone_country_code: Option<String>,

/// Identifier for payout method
pub payout_method_id: Option<String>,
kashif-m marked this conversation as resolved.
Show resolved Hide resolved
}

impl PayoutCreateRequest {
Expand Down Expand Up @@ -568,6 +571,9 @@ pub struct PayoutCreateResponse {
#[remove_in(PayoutCreateResponse)]
#[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
pub unified_message: Option<UnifiedMessage>,

/// Identifier for payout method
pub payout_method_id: Option<String>,
}

/// The payout method information for response
Expand Down
22 changes: 22 additions & 0 deletions crates/diesel_models/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod diesel_exports {
DbIntentStatus as IntentStatus, DbMandateStatus as MandateStatus,
DbMandateType as MandateType, DbMerchantStorageScheme as MerchantStorageScheme,
DbOrderFulfillmentTimeOrigin as OrderFulfillmentTimeOrigin,
DbPaymentDirection as PaymentDirection,
DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentSource as PaymentSource,
DbPaymentType as PaymentType, DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType,
DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus,
Expand Down Expand Up @@ -325,3 +326,24 @@ pub enum UserRoleVersion {
V1,
V2,
}

#[derive(
Clone,
Copy,
Debug,
Default,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
strum::EnumString,
strum::Display,
)]
#[router_derive::diesel_enum(storage_type = "db_enum")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")]
pub enum PaymentDirection {
#[default]
Payin,
Payout,
}
Loading
Loading