mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 21:07:58 +08:00
feat: payment processor token for recurring payments (#5508)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -1516,15 +1516,20 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
let merchant_recipient_data = payment_data
|
||||
.get_merchant_recipient_data(
|
||||
state,
|
||||
merchant_account,
|
||||
key_store,
|
||||
&merchant_connector_account,
|
||||
&connector,
|
||||
)
|
||||
.await?;
|
||||
let merchant_recipient_data =
|
||||
if let Some(true) = payment_data.payment_intent.is_payment_processor_token_flow {
|
||||
None
|
||||
} else {
|
||||
payment_data
|
||||
.get_merchant_recipient_data(
|
||||
state,
|
||||
merchant_account,
|
||||
key_store,
|
||||
&merchant_connector_account,
|
||||
&connector,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let mut router_data = payment_data
|
||||
.construct_router_data(
|
||||
|
||||
@ -446,6 +446,20 @@ pub async fn get_token_pm_type_mandate_details(
|
||||
Some(api::MandateTransactionType::RecurringMandateTransaction) => {
|
||||
match &request.recurring_details {
|
||||
Some(recurring_details) => match recurring_details {
|
||||
RecurringDetails::ProcessorPaymentToken(processor_payment_token) => (
|
||||
None,
|
||||
request.payment_method,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some(payments::MandateConnectorDetails {
|
||||
connector: processor_payment_token.connector.to_string(),
|
||||
merchant_connector_id: Some(
|
||||
processor_payment_token.merchant_connector_id.clone(),
|
||||
),
|
||||
}),
|
||||
None,
|
||||
),
|
||||
RecurringDetails::MandateId(mandate_id) => {
|
||||
let mandate_generic_data = get_token_for_recurring_mandate(
|
||||
state,
|
||||
@ -1168,26 +1182,31 @@ pub fn create_complete_authorize_url(
|
||||
}
|
||||
|
||||
fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult<()> {
|
||||
req.recurring_details
|
||||
.check_value_present("recurring_details")?;
|
||||
let recurring_details = req
|
||||
.recurring_details
|
||||
.get_required_value("recurring_details")?;
|
||||
|
||||
req.customer_id.check_value_present("customer_id")?;
|
||||
match recurring_details {
|
||||
RecurringDetails::ProcessorPaymentToken(_) => Ok(()),
|
||||
_ => {
|
||||
req.customer_id.check_value_present("customer_id")?;
|
||||
|
||||
let confirm = req.confirm.get_required_value("confirm")?;
|
||||
if !confirm {
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`confirm` must be `true` for mandates".into()
|
||||
}))?
|
||||
let confirm = req.confirm.get_required_value("confirm")?;
|
||||
if !confirm {
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`confirm` must be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
|
||||
let off_session = req.off_session.get_required_value("off_session")?;
|
||||
if !off_session {
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`off_session` should be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let off_session = req.off_session.get_required_value("off_session")?;
|
||||
if !off_session {
|
||||
Err(report!(errors::ApiErrorResponse::PreconditionFailed {
|
||||
message: "`off_session` should be `true` for mandates".into()
|
||||
}))?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_mandate_details(
|
||||
@ -3069,6 +3088,7 @@ mod tests {
|
||||
billing_details: None,
|
||||
merchant_order_reference_id: None,
|
||||
shipping_details: None,
|
||||
is_payment_processor_token_flow: None,
|
||||
};
|
||||
let req_cs = Some("1".to_string());
|
||||
assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_ok());
|
||||
@ -3133,6 +3153,7 @@ mod tests {
|
||||
billing_details: None,
|
||||
merchant_order_reference_id: None,
|
||||
shipping_details: None,
|
||||
is_payment_processor_token_flow: None,
|
||||
};
|
||||
let req_cs = Some("1".to_string());
|
||||
assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent,).is_err())
|
||||
@ -3195,6 +3216,7 @@ mod tests {
|
||||
billing_details: None,
|
||||
merchant_order_reference_id: None,
|
||||
shipping_details: None,
|
||||
is_payment_processor_token_flow: None,
|
||||
};
|
||||
let req_cs = Some("1".to_string());
|
||||
assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_err())
|
||||
|
||||
@ -640,6 +640,31 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
.as_ref()
|
||||
.map(|payment_method_billing| payment_method_billing.address_id.clone());
|
||||
|
||||
// If processor_payment_token is passed in request then populating the same in PaymentData
|
||||
let mandate_id = request
|
||||
.recurring_details
|
||||
.as_ref()
|
||||
.and_then(|recurring_details| match recurring_details {
|
||||
api_models::mandates::RecurringDetails::ProcessorPaymentToken(token) => {
|
||||
payment_intent.is_payment_processor_token_flow = Some(true);
|
||||
Some(api_models::payments::MandateIds {
|
||||
mandate_id: None,
|
||||
mandate_reference_id: Some(
|
||||
api_models::payments::MandateReferenceId::ConnectorMandateId(
|
||||
api_models::payments::ConnectorMandateReferenceId {
|
||||
connector_mandate_id: Some(
|
||||
token.processor_payment_token.clone(),
|
||||
),
|
||||
payment_method_id: None,
|
||||
update_history: None,
|
||||
},
|
||||
),
|
||||
),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let payment_data = PaymentData {
|
||||
flow: PhantomData,
|
||||
payment_intent,
|
||||
@ -647,7 +672,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
currency,
|
||||
amount,
|
||||
email: request.email.clone(),
|
||||
mandate_id: None,
|
||||
mandate_id: mandate_id.clone(),
|
||||
mandate_connector,
|
||||
setup_mandate,
|
||||
customer_acceptance: customer_acceptance.map(From::from),
|
||||
@ -1293,6 +1318,8 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
|
||||
let session_expiry = m_payment_data_payment_intent.session_expiry;
|
||||
let m_key_store = key_store.clone();
|
||||
let key_manager_state = state.into();
|
||||
let is_payment_processor_token_flow =
|
||||
payment_data.payment_intent.is_payment_processor_token_flow;
|
||||
|
||||
let payment_intent_fut = tokio::spawn(
|
||||
async move {
|
||||
@ -1325,6 +1352,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
|
||||
merchant_order_reference_id: None,
|
||||
billing_details,
|
||||
shipping_details,
|
||||
is_payment_processor_token_flow,
|
||||
})),
|
||||
&m_key_store,
|
||||
storage_scheme,
|
||||
|
||||
@ -410,6 +410,32 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
.await
|
||||
.transpose()?;
|
||||
|
||||
let mandate_id = if mandate_id.is_none() {
|
||||
request
|
||||
.recurring_details
|
||||
.as_ref()
|
||||
.and_then(|recurring_details| match recurring_details {
|
||||
RecurringDetails::ProcessorPaymentToken(token) => {
|
||||
Some(api_models::payments::MandateIds {
|
||||
mandate_id: None,
|
||||
mandate_reference_id: Some(
|
||||
api_models::payments::MandateReferenceId::ConnectorMandateId(
|
||||
api_models::payments::ConnectorMandateReferenceId {
|
||||
connector_mandate_id: Some(
|
||||
token.processor_payment_token.clone(),
|
||||
),
|
||||
payment_method_id: None,
|
||||
update_history: None,
|
||||
},
|
||||
),
|
||||
),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
mandate_id
|
||||
};
|
||||
let operation = payments::if_not_create_change_operation::<_, F>(
|
||||
payment_intent.status,
|
||||
request.confirm,
|
||||
@ -463,7 +489,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, api::PaymentsRequest> for Pa
|
||||
currency,
|
||||
amount,
|
||||
email: request.email.clone(),
|
||||
mandate_id,
|
||||
mandate_id: mandate_id.clone(),
|
||||
mandate_connector,
|
||||
setup_mandate,
|
||||
customer_acceptance,
|
||||
@ -1141,6 +1167,12 @@ impl PaymentCreate {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let is_payment_processor_token_flow = request.recurring_details.as_ref().and_then(
|
||||
|recurring_details| match recurring_details {
|
||||
RecurringDetails::ProcessorPaymentToken(_) => Some(true),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
|
||||
// Encrypting our Customer Details to be stored in Payment Intent
|
||||
let customer_details = raw_customer_details
|
||||
@ -1201,6 +1233,7 @@ impl PaymentCreate {
|
||||
customer_details,
|
||||
merchant_order_reference_id: request.merchant_order_reference_id.clone(),
|
||||
shipping_details,
|
||||
is_payment_processor_token_flow,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -773,6 +773,7 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, api::PaymentsRequest> for Paymen
|
||||
merchant_order_reference_id,
|
||||
billing_details,
|
||||
shipping_details,
|
||||
is_payment_processor_token_flow: None,
|
||||
})),
|
||||
key_store,
|
||||
storage_scheme,
|
||||
|
||||
@ -251,6 +251,7 @@ pub async fn generate_sample_data(
|
||||
billing_details: None,
|
||||
merchant_order_reference_id: Default::default(),
|
||||
shipping_details: None,
|
||||
is_payment_processor_token_flow: None,
|
||||
};
|
||||
let payment_attempt = PaymentAttemptBatchNew {
|
||||
attempt_id: attempt_id.clone(),
|
||||
|
||||
Reference in New Issue
Block a user