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

feat(router): Make payment_method_subtype optional in payment_attempt [V2] #7568

Merged
merged 3 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -7666,6 +7666,7 @@
"prophetpay",
"rapyd",
"razorpay",
"recurly",
"shift4",
"square",
"stax",
Expand Down Expand Up @@ -13968,7 +13969,7 @@
"PaymentConnectorTransmission": {
"type": "string",
"enum": [
"ConnectorCallFailed",
"ConnectorCallUnsuccessful",
"ConnectorCallSucceeded"
]
},
Expand Down Expand Up @@ -14583,7 +14584,6 @@
"type": "object",
"required": [
"payment_method_type",
"payment_method_subtype",
"customer_id",
"payment_method_data"
],
Expand All @@ -14592,7 +14592,12 @@
"$ref": "#/components/schemas/PaymentMethod"
},
"payment_method_subtype": {
"$ref": "#/components/schemas/PaymentMethodType"
"allOf": [
{
"$ref": "#/components/schemas/PaymentMethodType"
}
],
"nullable": true
},
"metadata": {
"type": "object",
Expand Down Expand Up @@ -16036,8 +16041,7 @@
"description": "Request for Payment Intent Confirm",
"required": [
"payment_method_data",
"payment_method_type",
"payment_method_subtype"
"payment_method_type"
],
"properties": {
"return_url": {
Expand All @@ -16053,7 +16057,12 @@
"$ref": "#/components/schemas/PaymentMethod"
},
"payment_method_subtype": {
"$ref": "#/components/schemas/PaymentMethodType"
"allOf": [
{
"$ref": "#/components/schemas/PaymentMethodType"
}
],
"nullable": true
},
"shipping": {
"allOf": [
Expand Down Expand Up @@ -16831,8 +16840,7 @@
"amount_details",
"customer_id",
"payment_method_data",
"payment_method_type",
"payment_method_subtype"
"payment_method_type"
],
"properties": {
"amount_details": {
Expand Down Expand Up @@ -17024,7 +17032,12 @@
"$ref": "#/components/schemas/PaymentMethod"
},
"payment_method_subtype": {
"$ref": "#/components/schemas/PaymentMethodType"
"allOf": [
{
"$ref": "#/components/schemas/PaymentMethodType"
}
],
"nullable": true
},
"customer_acceptance": {
"allOf": [
Expand Down Expand Up @@ -20385,6 +20398,7 @@
"prophetpay",
"rapyd",
"razorpay",
"recurly",
"riskified",
"shift4",
"signifyd",
Expand Down
6 changes: 3 additions & 3 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ pub struct PaymentMethodCreate {
pub payment_method_type: api_enums::PaymentMethod,

/// This is a sub-category of payment method.
#[schema(value_type = PaymentMethodType,example = "credit")]
pub payment_method_subtype: api_enums::PaymentMethodType,
#[schema(value_type = Option<PaymentMethodType>,example = "credit")]
pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

/// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
#[schema(value_type = Option<Object>,example = json!({ "city": "NY", "unit": "245" }))]
Expand Down Expand Up @@ -2781,7 +2781,7 @@ pub struct PaymentMethodSessionConfirmRequest {

/// The payment method subtype
#[schema(value_type = PaymentMethodType, example = "credit")]
pub payment_method_subtype: common_enums::PaymentMethodType,
pub payment_method_subtype: Option<common_enums::PaymentMethodType>,

/// The payment instrument data to be used for the payment
#[schema(value_type = PaymentMethodDataRequest)]
Expand Down
12 changes: 6 additions & 6 deletions crates/api_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5198,8 +5198,8 @@ pub struct PaymentsConfirmIntentRequest {
pub payment_method_type: api_enums::PaymentMethod,

/// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided
#[schema(value_type = PaymentMethodType, example = "apple_pay")]
pub payment_method_subtype: api_enums::PaymentMethodType,
#[schema(value_type = Option<PaymentMethodType>, example = "apple_pay")]
pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

/// The shipping address for the payment. This will override the shipping address provided in the create-intent request
pub shipping: Option<Address>,
Expand Down Expand Up @@ -5367,8 +5367,8 @@ pub struct PaymentsRequest {
pub payment_method_type: api_enums::PaymentMethod,

/// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided
#[schema(value_type = PaymentMethodType, example = "apple_pay")]
pub payment_method_subtype: api_enums::PaymentMethodType,
#[schema(value_type = Option<PaymentMethodType>, example = "apple_pay")]
pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

/// This "CustomerAcceptance" object is passed during Payments-Confirm request, it enlists the type, time, and mode of acceptance properties related to an acceptance done by the customer. The customer_acceptance sub object is usually passed by the SDK or client.
#[schema(value_type = Option<CustomerAcceptance>)]
Expand Down Expand Up @@ -8440,7 +8440,7 @@ pub struct PaymentRevenueRecoveryMetadata {
pub payment_method_type: common_enums::PaymentMethod,
/// PaymentMethod Subtype
#[schema(example = "klarna", value_type = PaymentMethodType)]
pub payment_method_subtype: common_enums::PaymentMethodType,
pub payment_method_subtype: Option<common_enums::PaymentMethodType>,
/// The name of the payment connector through which the payment attempt was made.
#[schema(value_type = Connector, example = "stripe")]
pub connector: common_enums::connector_enums::Connector,
Expand Down Expand Up @@ -8521,7 +8521,7 @@ pub struct PaymentsAttemptRecordRequest {

/// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided
#[schema(value_type = PaymentMethodType, example = "apple_pay")]
pub payment_method_subtype: api_enums::PaymentMethodType,
pub payment_method_subtype: Option<api_enums::PaymentMethodType>,

/// The payment instrument data to be used for the payment attempt.
pub payment_method_data: Option<PaymentMethodDataRequest>,
Expand Down
4 changes: 2 additions & 2 deletions crates/diesel_models/src/payment_attempt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub struct PaymentAttempt {
pub charges: Option<common_types::payments::ConnectorChargeResponseData>,
pub payment_method_type_v2: storage_enums::PaymentMethod,
pub connector_payment_id: Option<ConnectorTransactionId>,
pub payment_method_subtype: storage_enums::PaymentMethodType,
pub payment_method_subtype: Option<storage_enums::PaymentMethodType>,
pub routing_result: Option<serde_json::Value>,
pub authentication_applied: Option<common_enums::AuthenticationType>,
pub external_reference_id: Option<String>,
Expand Down Expand Up @@ -314,7 +314,7 @@ pub struct PaymentAttemptNew {
pub feature_metadata: Option<PaymentAttemptFeatureMetadata>,
pub payment_method_type_v2: storage_enums::PaymentMethod,
pub connector_payment_id: Option<ConnectorTransactionId>,
pub payment_method_subtype: storage_enums::PaymentMethodType,
pub payment_method_subtype: Option<storage_enums::PaymentMethodType>,
pub id: id_type::GlobalAttemptId,
pub connector_token_details: Option<ConnectorTokenDetails>,
pub card_discovery: Option<storage_enums::CardDiscovery>,
Expand Down
2 changes: 1 addition & 1 deletion crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ diesel::table! {
#[max_length = 128]
connector_payment_id -> Nullable<Varchar>,
#[max_length = 64]
payment_method_subtype -> Varchar,
payment_method_subtype -> Nullable<Varchar>,
routing_result -> Nullable<Jsonb>,
authentication_applied -> Nullable<AuthenticationType>,
#[max_length = 128]
Expand Down
4 changes: 2 additions & 2 deletions crates/diesel_models/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl FeatureMetadata {
pub fn get_payment_method_sub_type(&self) -> Option<common_enums::PaymentMethodType> {
self.payment_revenue_recovery_metadata
.as_ref()
.map(|rrm| rrm.payment_method_subtype)
.and_then(|val| val.payment_method_subtype)
}

pub fn get_payment_method_type(&self) -> Option<common_enums::PaymentMethod> {
Expand Down Expand Up @@ -158,7 +158,7 @@ pub struct PaymentRevenueRecoveryMetadata {
///Payment Method Type
pub payment_method_type: common_enums::enums::PaymentMethod,
/// PaymentMethod Subtype
pub payment_method_subtype: common_enums::enums::PaymentMethodType,
pub payment_method_subtype: Option<common_enums::enums::PaymentMethodType>,
/// The name of the payment connector through which the payment attempt was made.
pub connector: common_enums::connector_enums::Connector,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,9 @@ impl TryFrom<ChargebeeWebhookBody> for revenue_recovery::RevenueRecoveryAttemptD
let payment_method_details: ChargebeePaymentMethodDetails =
serde_json::from_str(&item.content.transaction.payment_method_details)
.change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?;
let payment_method_sub_type =
enums::PaymentMethodType::from(payment_method_details.card.funding_type);
let payment_method_sub_type = Some(enums::PaymentMethodType::from(
payment_method_details.card.funding_type,
));
Ok(Self {
amount,
currency,
Expand Down
2 changes: 1 addition & 1 deletion crates/hyperswitch_domain_models/src/payments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl AmountDetails {
let order_tax_amount = match self.skip_external_tax_calculation {
common_enums::TaxCalculationOverride::Skip => {
self.tax_details.as_ref().and_then(|tax_details| {
tax_details.get_tax_amount(Some(confirm_intent_request.payment_method_subtype))
tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype)
})
}
common_enums::TaxCalculationOverride::Calculate => None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ pub struct PaymentAttempt {
/// The reference to the payment at the connector side
pub connector_payment_id: Option<String>,
/// The payment method subtype for the payment attempt.
pub payment_method_subtype: storage_enums::PaymentMethodType,
pub payment_method_subtype: Option<storage_enums::PaymentMethodType>,
/// The authentication type that was applied for the payment attempt.
pub authentication_applied: Option<common_enums::AuthenticationType>,
/// A reference to the payment at connector side. This is returned by the connector
Expand Down Expand Up @@ -465,7 +465,7 @@ impl PaymentAttempt {
#[cfg(feature = "v2")]
pub fn get_payment_method_type(&self) -> Option<storage_enums::PaymentMethodType> {
// TODO: check if we can fix this
Some(self.payment_method_subtype)
self.payment_method_subtype
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -649,8 +649,7 @@ impl PaymentAttempt {
.unwrap_or(common_enums::PaymentMethod::Card),
payment_method_id: None,
connector_payment_id: None,
payment_method_subtype: payment_method_subtype_data
.unwrap_or(common_enums::PaymentMethodType::Credit),
payment_method_subtype: payment_method_subtype_data,
authentication_applied: None,
external_reference_id: None,
payment_method_billing_address,
Expand Down
2 changes: 1 addition & 1 deletion crates/hyperswitch_domain_models/src/revenue_recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct RevenueRecoveryAttemptData {
/// payment method of payment attempt.
pub payment_method_type: common_enums::PaymentMethod,
/// payment method sub type of the payment attempt.
pub payment_method_sub_type: common_enums::PaymentMethodType,
pub payment_method_sub_type: Option<common_enums::PaymentMethodType>,
}

/// This is unified struct for Revenue Recovery Invoice Data and it is constructed from billing connectors
Expand Down
4 changes: 2 additions & 2 deletions crates/router/src/core/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ pub async fn retrieve_payment_method_with_token(
pub(crate) fn get_payment_method_create_request(
payment_method_data: &api_models::payments::PaymentMethodData,
payment_method_type: storage_enums::PaymentMethod,
payment_method_subtype: storage_enums::PaymentMethodType,
payment_method_subtype: Option<storage_enums::PaymentMethodType>,
customer_id: id_type::GlobalCustomerId,
billing_address: Option<&api_models::payments::Address>,
payment_method_session: Option<&domain::payment_methods::PaymentMethodSession>,
Expand Down Expand Up @@ -971,7 +971,7 @@ pub async fn create_payment_method_core(
None,
network_tokenization_resp,
Some(req.payment_method_type),
Some(req.payment_method_subtype),
req.payment_method_subtype,
)
.await
.attach_printable("Unable to create Payment method data")?;
Expand Down
2 changes: 1 addition & 1 deletion crates/router/src/core/payments/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ pub fn make_dsl_input(
};
let payment_method_input = dsl_inputs::PaymentMethodInput {
payment_method: Some(payments_dsl_input.payment_attempt.payment_method_type),
payment_method_type: Some(payments_dsl_input.payment_attempt.payment_method_subtype),
payment_method_type: payments_dsl_input.payment_attempt.payment_method_subtype,
card_network: payments_dsl_input
.payment_method_data
.as_ref()
Expand Down
12 changes: 6 additions & 6 deletions crates/router/src/core/payments/transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>(
session_token: None,
enrolled_for_3ds: true,
related_transaction_id: None,
payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype),
payment_method_type: payment_data.payment_attempt.payment_method_subtype,
router_return_url: Some(router_return_url),
webhook_url,
complete_authorize_url,
Expand Down Expand Up @@ -607,7 +607,7 @@ pub async fn construct_router_data_for_psync<'a>(
capture_method: Some(payment_intent.capture_method),
connector_meta: attempt.connector_metadata.clone().expose_option(),
sync_type: types::SyncRequestType::SinglePaymentSync,
payment_method_type: Some(attempt.payment_method_subtype),
payment_method_type: attempt.payment_method_subtype,
currency: payment_intent.amount_details.currency,
// TODO: Get the charges object from feature metadata
split_payments: None,
Expand Down Expand Up @@ -948,7 +948,7 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>(
email,
customer_name: None,
return_url: Some(router_return_url),
payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype),
payment_method_type: payment_data.payment_attempt.payment_method_subtype,
request_incremental_authorization: matches!(
payment_data
.payment_intent
Expand Down Expand Up @@ -1666,7 +1666,7 @@ where
created: payment_intent.created_at,
payment_method_data,
payment_method_type: Some(payment_attempt.payment_method_type),
payment_method_subtype: Some(payment_attempt.payment_method_subtype),
payment_method_subtype: payment_attempt.payment_method_subtype,
next_action,
connector_transaction_id: payment_attempt.connector_payment_id.clone(),
connector_reference_id: None,
Expand Down Expand Up @@ -1770,7 +1770,7 @@ where
payment_method_subtype: self
.payment_attempt
.as_ref()
.map(|attempt| attempt.payment_method_subtype),
.and_then(|attempt| attempt.payment_method_subtype),
connector_transaction_id: self
.payment_attempt
.as_ref()
Expand Down Expand Up @@ -2859,7 +2859,7 @@ impl ForeignFrom<(storage::PaymentIntent, Option<storage::PaymentAttempt>)>
)),
created: pi.created_at,
payment_method_type: pa.as_ref().and_then(|p| p.payment_method_type.into()),
payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype.into()),
payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype),
connector: pa.as_ref().and_then(|p| p.connector.clone()),
merchant_connector_id: pa.as_ref().and_then(|p| p.merchant_connector_id.clone()),
customer: None,
Expand Down
13 changes: 9 additions & 4 deletions crates/router/src/types/api/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@ impl PaymentMethodCreateExt for PaymentMethodCreate {
impl PaymentMethodCreateExt for PaymentMethodCreate {
fn validate(&self) -> RouterResult<()> {
utils::when(
!validate_payment_method_type_against_payment_method(
self.payment_method_type,
self.payment_method_subtype,
),
!self
.payment_method_subtype
.map(|sub| {
validate_payment_method_type_against_payment_method(
self.payment_method_type,
sub,
)
})
.unwrap_or(true), // If payment_method_subtype is None, we assume it to be valid
|| {
Err(report!(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid 'payment_method_type' provided".to_string()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE payment_attempt ALTER COLUMN payment_method_subtype SET NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE payment_attempt ALTER COLUMN payment_method_subtype DROP NOT NULL;
Loading