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:
Prajjwal Kumar
2024-08-08 19:39:46 +05:30
committed by GitHub
parent 9dda292ca2
commit 0cbbc92a43
17 changed files with 211 additions and 28 deletions

View File

@ -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(

View File

@ -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())

View File

@ -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,

View File

@ -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,
})
}

View File

@ -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,

View File

@ -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(),