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(payment_methods_session_v2): update flow of payment method session #7263

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9bb2e0c
Update flow of Payment Method Session
ShivanshMathurJuspay Jan 31, 2025
09ddeff
Changes
ShivanshMathurJuspay Feb 13, 2025
b990732
openapi changes
ShivanshMathurJuspay Feb 3, 2025
a8e7b32
chore: run formatter
hyperswitch-bot[bot] Feb 13, 2025
48cfcfd
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Feb 13, 2025
4378976
Changes for better structure
ShivanshMathurJuspay Feb 20, 2025
5c1a0be
chore: run formatter
hyperswitch-bot[bot] Feb 20, 2025
ebfba33
Return value changes
ShivanshMathurJuspay Feb 20, 2025
b86c302
Merge branch 'update_payment_methods_session' of github.com:juspay/hy…
ShivanshMathurJuspay Feb 20, 2025
a8a7c62
chore: run formatter
hyperswitch-bot[bot] Feb 20, 2025
6ec0ac1
Removing unwanted Structs
ShivanshMathurJuspay Feb 21, 2025
7e6a187
chore: run formatter
hyperswitch-bot[bot] Feb 21, 2025
d81c836
Removing unwanted structs
ShivanshMathurJuspay Feb 21, 2025
e48ec06
Merge branch 'update_payment_methods_session' of github.com:juspay/hy…
ShivanshMathurJuspay Feb 21, 2025
eeb398d
Removing comments
ShivanshMathurJuspay Feb 21, 2025
68293f0
chore: run formatter
hyperswitch-bot[bot] Feb 21, 2025
798605d
correcting workflow errors
ShivanshMathurJuspay Feb 21, 2025
9d2f7a8
Merge branch 'update_payment_methods_session' of github.com:juspay/hy…
ShivanshMathurJuspay Feb 21, 2025
9166f48
chore: run formatter
hyperswitch-bot[bot] Feb 21, 2025
d223a53
Runner changes
ShivanshMathurJuspay Feb 24, 2025
d9de999
Merge branch 'update_payment_methods_session' of github.com:juspay/hy…
ShivanshMathurJuspay Feb 24, 2025
c797383
chore: run formatter
hyperswitch-bot[bot] Feb 24, 2025
bf84e2c
clippy error
ShivanshMathurJuspay Feb 24, 2025
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
29 changes: 29 additions & 0 deletions api-reference-v2/openapi_spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14831,6 +14831,35 @@
}
}
},
"PaymentMethodsSessionUpdateRequest": {
"type": "object",
"properties": {
"billing": {
"allOf": [
{
"$ref": "#/components/schemas/Address"
}
],
"nullable": true
},
"psp_tokenization": {
"allOf": [
{
"$ref": "#/components/schemas/PspTokenization"
}
],
"nullable": true
},
"network_tokenization": {
"allOf": [
{
"$ref": "#/components/schemas/NetworkTokenization"
}
],
"nullable": true
}
}
},
"PaymentProcessingDetails": {
"type": "object",
"required": [
Expand Down
3 changes: 3 additions & 0 deletions crates/api_models/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ impl ApiEventMetric for DisputeListFilters {
#[cfg(feature = "v2")]
impl ApiEventMetric for PaymentMethodSessionRequest {}

#[cfg(feature = "v2")]
impl ApiEventMetric for PaymentMethodsSessionUpdateRequest {}

#[cfg(feature = "v2")]
impl ApiEventMetric for PaymentMethodsSessionResponse {
fn get_api_event_type(&self) -> Option<ApiEventsType> {
Expand Down
16 changes: 16 additions & 0 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,22 @@ pub struct PaymentMethodSessionRequest {
pub expires_in: Option<u32>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaymentMethodsSessionUpdateRequest {
/// The billing address details of the customer. This will also be used for any new payment methods added during the session
#[schema(value_type = Option<Address>)]
pub billing: Option<payments::Address>,

/// The tokenization type to be applied
#[schema(value_type = Option<PspTokenization>)]
pub psp_tokenization: Option<common_types::payment_methods::PspTokenization>,

/// The network tokenization configuration if applicable
#[schema(value_type = Option<NetworkTokenization>)]
pub network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,
}

#[cfg(feature = "v2")]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)]
pub struct PaymentMethodSessionUpdateSavedPaymentMethod {
Expand Down
34 changes: 33 additions & 1 deletion crates/diesel_models/src/payment_methods_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,39 @@ pub struct PaymentMethodsSession {
pub customer_id: common_utils::id_type::GlobalCustomerId,
pub billing: Option<common_utils::encryption::Encryption>,
pub psp_tokenization: Option<common_types::payment_methods::PspTokenization>,
pub network_tokeinzation: Option<common_types::payment_methods::NetworkTokenization>,
pub network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,
#[serde(with = "common_utils::custom_serde::iso8601")]
pub expires_at: time::PrimitiveDateTime,
}

#[cfg(feature = "v2")]
impl PaymentMethodsSession {
pub fn apply_changeset(
self,
update_session: PaymentMethodsSessionUpdateInternal,
) -> PaymentMethodsSession {
let Self {
id,
customer_id,
billing,
psp_tokenization,
network_tokenization,
expires_at,
} = self;
PaymentMethodsSession {
id,
customer_id,
billing: update_session.billing.or(billing),
psp_tokenization: update_session.psp_tokenization.or(psp_tokenization),
network_tokenization: update_session.network_tokenization.or(network_tokenization),
expires_at,
}
}
}

#[cfg(feature = "v2")]
pub struct PaymentMethodsSessionUpdateInternal {
pub billing: Option<common_utils::encryption::Encryption>,
pub psp_tokenization: Option<common_types::payment_methods::PspTokenization>,
pub network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,
}
64 changes: 61 additions & 3 deletions crates/hyperswitch_domain_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ impl super::behaviour::Conversion for PaymentMethodsSession {
customer_id: self.customer_id,
billing: self.billing.map(|val| val.into()),
psp_tokenization: self.psp_tokenization,
network_tokeinzation: self.network_tokenization,
network_tokenization: self.network_tokenization,
expires_at: self.expires_at,
})
}
Expand Down Expand Up @@ -639,7 +639,7 @@ impl super::behaviour::Conversion for PaymentMethodsSession {
customer_id: storage_model.customer_id,
billing,
psp_tokenization: storage_model.psp_tokenization,
network_tokenization: storage_model.network_tokeinzation,
network_tokenization: storage_model.network_tokenization,
expires_at: storage_model.expires_at,
})
}
Expand All @@ -655,12 +655,70 @@ impl super::behaviour::Conversion for PaymentMethodsSession {
customer_id: self.customer_id,
billing: self.billing.map(|val| val.into()),
psp_tokenization: self.psp_tokenization,
network_tokeinzation: self.network_tokenization,
network_tokenization: self.network_tokenization,
expires_at: self.expires_at,
})
}
}

#[cfg(feature = "v2")]
pub enum PaymentMethodsSessionUpdateEnum {
GeneralUpdate {
billing: Option<Encryptable<Address>>,
psp_tokenization: Option<common_types::payment_methods::PspTokenization>,
network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,
},
}

#[cfg(feature = "v2")]
impl From<PaymentMethodsSessionUpdateEnum> for PaymentMethodsSessionUpdateInternal {
fn from(update: PaymentMethodsSessionUpdateEnum) -> Self {
match update {
PaymentMethodsSessionUpdateEnum::GeneralUpdate {
billing,
psp_tokenization,
network_tokenization,
} => Self {
billing: billing.map(|val| val.into()),
psp_tokenization: psp_tokenization,
network_tokenization: network_tokenization,
},
}
}
}

#[cfg(feature = "v2")]
impl PaymentMethodsSession {
pub fn apply_changeset(
self,
update_session: PaymentMethodsSessionUpdateInternal,
) -> PaymentMethodsSession {
let Self {
id,
customer_id,
billing,
psp_tokenization,
network_tokenization,
expires_at,
} = self;
PaymentMethodsSession {
id,
customer_id,
billing: update_session.billing.or(billing),
psp_tokenization: update_session.psp_tokenization.or(psp_tokenization),
network_tokenization: update_session.network_tokenization.or(network_tokenization),
expires_at,
}
}
}

#[cfg(feature = "v2")]
pub struct PaymentMethodsSessionUpdateInternal {
pub billing: Option<Encryptable<Address>>,
pub psp_tokenization: Option<common_types::payment_methods::PspTokenization>,
pub network_tokenization: Option<common_types::payment_methods::NetworkTokenization>,
}

#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
Expand Down
1 change: 1 addition & 0 deletions crates/openapi/src/openapi_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ Never share your secret api keys. Keep them guarded and secure.
api_models::payment_methods::PaymentMethodCollectLinkResponse,
api_models::payment_methods::PaymentMethodSubtypeSpecificData,
api_models::payment_methods::PaymentMethodSessionRequest,
api_models::payment_methods::PaymentMethodsSessionUpdateRequest,
api_models::payment_methods::PaymentMethodsSessionResponse,
api_models::payments::PaymentsRetrieveResponse,
api_models::refunds::RefundListRequest,
Expand Down
112 changes: 112 additions & 0 deletions crates/router/src/core/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,56 @@ impl EncryptableData for payment_methods::PaymentMethodSessionRequest {
}
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl EncryptableData for payment_methods::PaymentMethodsSessionUpdateRequest {
type Output = hyperswitch_domain_models::payment_methods::DecryptedPaymentMethodsSession;

async fn encrypt_data(
&self,
key_manager_state: &common_utils::types::keymanager::KeyManagerState,
key_store: &domain::MerchantKeyStore,
) -> RouterResult<Self::Output> {
use common_utils::types::keymanager::ToEncryptable;

let encrypted_billing_address = self
.billing
.clone()
.map(|address| address.encode_to_value())
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encode billing address")?
.map(Secret::new);

let batch_encrypted_data = domain_types::crypto_operation(
key_manager_state,
common_utils::type_name!(hyperswitch_domain_models::payment_methods::PaymentMethodsSession),
domain_types::CryptoOperation::BatchEncrypt(
hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession::to_encryptable(
hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession {
billing: encrypted_billing_address,
},
),
),
common_utils::types::keymanager::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting payment methods session details".to_string())?;

let encrypted_data =
hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession::from_encryptable(
batch_encrypted_data,
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting payment methods session detailss")?;

Ok(encrypted_data)
}
}

#[cfg(feature = "v2")]
pub async fn payment_methods_session_create(
state: SessionState,
Expand Down Expand Up @@ -1880,6 +1930,68 @@ pub async fn payment_methods_session_create(
Ok(services::ApplicationResponse::Json(response))
}

#[cfg(feature = "v2")]
pub async fn payment_methods_session_update(
state: SessionState,
merchant_account: domain::MerchantAccount,
key_store: domain::MerchantKeyStore,
payment_method_session_id: id_type::GlobalPaymentMethodSessionId,
request: payment_methods::PaymentMethodsSessionUpdateRequest,
) -> RouterResponse<payment_methods::PaymentMethodsSessionResponse> {
let db = state.store.as_ref();
let key_manager_state = &(&state).into();

let existing_payment_method_session_state = db
.get_payment_methods_session(key_manager_state, &key_store, &payment_method_session_id)
.await
.to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError {
message: "payment methods session does not exist or has expired".to_string(),
})
.attach_printable("Failed to retrieve payment methods session from db")?;

let encrypted_data = request
.encrypt_data(key_manager_state, &key_store)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to encrypt payment methods session data")?;

let billing = encrypted_data
.billing
.as_ref()
.map(|data| {
data.clone()
.deserialize_inner_value(|value| value.parse_value("Address"))
})
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to decode billing address")?;

let payment_method_session_domain_model =
hyperswitch_domain_models::payment_methods::PaymentMethodsSessionUpdateEnum::GeneralUpdate{
billing,
psp_tokenization: request.psp_tokenization,
network_tokenization: request.network_tokenization,
};

let update_state_change = db
.update_payment_method_session(
key_manager_state,
&key_store,
&payment_method_session_id,
payment_method_session_domain_model,
existing_payment_method_session_state.clone(),
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to update payment methods session in db")?;

let response = payment_methods::PaymentMethodsSessionResponse::foreign_from((
update_state_change,
Secret::new("CLIENT_SECRET_REDACTED".to_string()),
));

Ok(services::ApplicationResponse::Json(response))
}
#[cfg(feature = "v2")]
pub async fn payment_methods_session_retrieve(
state: SessionState,
Expand Down
22 changes: 22 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3955,6 +3955,28 @@ impl db::payment_method_session::PaymentMethodsSessionInterface for KafkaStore {
.get_payment_methods_session(state, key_store, id)
.await
}

async fn update_payment_method_session(
&self,
state: &KeyManagerState,
key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore,
id: &id_type::GlobalPaymentMethodSessionId,
payment_methods_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSessionUpdateEnum,
current_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSession,
) -> CustomResult<
hyperswitch_domain_models::payment_methods::PaymentMethodsSession,
errors::StorageError,
> {
self.diesel_store
.update_payment_method_session(
state,
key_store,
id,
payment_methods_session,
current_session,
)
.await
}
}

#[async_trait::async_trait]
Expand Down
Loading
Loading