-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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): add support for relay refund incoming webhooks #6974
base: main
Are you sure you want to change the base?
Conversation
Changed Files
|
) -> impl Responder { | ||
let flow = Flow::IncomingWebhookReceive; | ||
let (merchant_id, connector_id_or_name) = path.into_inner(); | ||
let is_relay_webhook = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this to a const
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we consider using an enum instead of a bool
maybe?
…perswitch into relay/incoming-webhook
…ay/incoming-webhook
) -> StorageResult<Self> { | ||
generics::generic_find_one::<<Self as HasTable>::Table, _, _>( | ||
conn, | ||
dsl::id.eq(connector_reference_id.to_owned()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use dsl::connector_reference_id
right?
|
||
pub async fn find_by_connector_reference_id( | ||
conn: &PgPooledConn, | ||
connector_reference_id: &str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As, it's not globally unique we may have to query this along with profile_id. You can add a index in DB too
|
||
// Using early return ensures unsupported webhooks are acknowledged to the connector | ||
if let Some(error) = relay_webhook_response.as_ref().err() { | ||
if let errors::ApiErrorResponse::NotSupported { .. } = error.current_context() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can avoid this nested if, by getting the current context in the outer if statement
)) | ||
.await | ||
.attach_printable("Incoming webhook flow for disputes failed"), | ||
metrics::WEBHOOK_EVENT_TYPE_IDENTIFICATION_FAILURE_COUNT.add( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This metric is not relevant in current case, this metric indicates event type identification failure where as our case is event type being identified correctly but not supported
.get_webhook_api_response(&request_details, None) | ||
.switch() | ||
.attach_printable( | ||
"Failed while early return in case of event type parsing", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Failed while early return in case of event type parsing", | |
"Failed while early return in case of not supported event types in relay webhooks", |
)) | ||
.await | ||
.change_context(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Failed to update relay")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message's incorrect here
response | ||
} else { | ||
Err(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Failed to force sync relay")? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.attach_printable("Failed to force sync relay")? | |
.attach_printable("Unexpcted response from force sync relay")? |
crates/router/src/routes/app.rs
Outdated
impl RelayWebhooks { | ||
pub fn server(state: AppState) -> Scope { | ||
use api_models::webhooks as webhook_type; | ||
web::scope("/webhooks") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
web::scope("/webhooks") | |
web::scope("/webhooks/relay") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scope for RelayWebhooks would always be /webhooks/relay
…perswitch into relay/incoming-webhook
…ay/incoming-webhook
pub async fn find_by_connector_reference_id( | ||
conn: &PgPooledConn, | ||
connector_reference_id: &str, | ||
profile_id: &common_utils::id_type::ProfileId, | ||
) -> StorageResult<Self> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you rename this accordingly:
Either:
pub async fn find_by_connector_reference_id( | |
conn: &PgPooledConn, | |
connector_reference_id: &str, | |
profile_id: &common_utils::id_type::ProfileId, | |
) -> StorageResult<Self> { | |
pub async fn find_by_profile_id_connector_reference_id( | |
conn: &PgPooledConn, | |
profile_id: &common_utils::id_type::ProfileId, | |
connector_reference_id: &str, | |
) -> StorageResult<Self> { |
Or:
pub async fn find_by_connector_reference_id( | |
conn: &PgPooledConn, | |
connector_reference_id: &str, | |
profile_id: &common_utils::id_type::ProfileId, | |
) -> StorageResult<Self> { | |
pub async fn find_by_connector_reference_id_profile_id( | |
conn: &PgPooledConn, | |
connector_reference_id: &str, | |
profile_id: &common_utils::id_type::ProfileId, | |
) -> StorageResult<Self> { |
I'd prefer the former. Then fix the param order in the usages as well.
async fn find_relay_by_connector_reference_id( | ||
&self, | ||
key_manager_state: &KeyManagerState, | ||
merchant_key_store: &domain::MerchantKeyStore, | ||
connector_reference_id: &str, | ||
profile_id: &common_utils::id_type::ProfileId, | ||
) -> CustomResult<hyperswitch_domain_models::relay::Relay, errors::StorageError>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here:
async fn find_relay_by_connector_reference_id( | |
&self, | |
key_manager_state: &KeyManagerState, | |
merchant_key_store: &domain::MerchantKeyStore, | |
connector_reference_id: &str, | |
profile_id: &common_utils::id_type::ProfileId, | |
) -> CustomResult<hyperswitch_domain_models::relay::Relay, errors::StorageError>; | |
async fn find_relay_by_profile_id_connector_reference_id( | |
&self, | |
key_manager_state: &KeyManagerState, | |
merchant_key_store: &domain::MerchantKeyStore, | |
profile_id: &common_utils::id_type::ProfileId, | |
connector_reference_id: &str, | |
) -> CustomResult<hyperswitch_domain_models::relay::Relay, errors::StorageError>; |
And fix it in trait implementations and usages.
impl RelayWebhooks { | ||
pub fn server(state: AppState) -> Scope { | ||
use api_models::webhooks as webhook_type; | ||
web::scope("/webhooks/relay") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we confirmed that this doesn't affect the existing incoming webhooks flows, due to the possibly conflicting routes?
) -> impl Responder { | ||
let flow = Flow::IncomingWebhookReceive; | ||
let (merchant_id, connector_id_or_name) = path.into_inner(); | ||
let is_relay_webhook = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we consider using an enum instead of a bool
maybe?
path: web::Path<(common_utils::id_type::MerchantId, String)>, | ||
) -> impl Responder { | ||
let flow = Flow::IncomingWebhookReceive; | ||
let (merchant_id, connector_id_or_name) = path.into_inner(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let (merchant_id, connector_id_or_name) = path.into_inner(); | |
let (merchant_id, connector_id) = path.into_inner(); |
state: web::Data<AppState>, | ||
req: HttpRequest, | ||
body: web::Bytes, | ||
path: web::Path<(common_utils::id_type::MerchantId, String)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use MerchantConnectorAccountId
instead of a String
, if we only expect a MCA ID in the path?
path: web::Path<(common_utils::id_type::MerchantId, String)>, | |
path: web::Path<(common_utils::id_type::MerchantId, common_utils::id_type::MerchantConnectorAccountId)>, |
@@ -0,0 +1,2 @@ | |||
-- Your SQL goes here | |||
CREATE UNIQUE INDEX relay_profile_id_connector_reference_id ON relay (profile_id, connector_reference_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
CREATE UNIQUE INDEX relay_profile_id_connector_reference_id ON relay (profile_id, connector_reference_id); | |
CREATE UNIQUE INDEX relay_profile_id_connector_reference_id_index ON relay (profile_id, connector_reference_id); |
Update the down.sql
file as well if you're taking up this change.
Type of Change
Description
Some connectors, like Adyen, do not support a refund sync API, so we need to rely on refund webhooks for the status. To address this, support for relay refund webhooks is being added.
To support this use case, a new webhook endpoint needs to be introduced for relay webhooks:
/webhooks/relay/{merchant_id}/{connector_id_or_name}
Once the webhook is identified as a relay webhook, it should be handled based on the flow type, such as refunds, payments, etc. For now, only support for relay refund webhooks has been added.
Additional Changes
Motivation and Context
How did you test it?
Checklist
cargo +nightly fmt --all
cargo clippy