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): add outgoing payment webhooks for v2 #6613

Open
wants to merge 68 commits into
base: main
Choose a base branch
from

Conversation

sai-harsha-vardhan
Copy link
Contributor

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

add outgoing payment webhooks for v2

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

Tested Manually
Setup incoming webhooks and trigger a successful payment, to receive an outgoing webhook
Outgoing Payload Response

{
  "merchant_id": "cloth_seller1_CqRqRX8T1Jy1QzaqVrBj",
  "event_id": "evt_01934471d1db77b3b6f719f1610e266d",
  "event_type": "payment_succeeded",
  "content": {
    "type": "payment_details",
    "object": {
      "id": "12345_pay_0193447120ac7750bcf168915d6a6a27",
      "status": "succeeded",
      "amount": {
        "order_amount": 100,
        "currency": "USD",
        "shipping_cost": null,
        "order_tax_amount": null,
        "skip_external_tax_calculation": "Skip",
        "skip_surcharge_calculation": "Skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null,
        "net_amount": 100,
        "amount_to_capture": null,
        "amount_capturable": 0,
        "amount_captured": null
      },
      "connector": "checkout",
      "client_secret": "12345_pay_0193447120ac7750bcf168915d6a6a27_secret_0193447120ad7c53a7dfcc03cb39cbc3",
      "created": "2024-11-19T12:41:24.909Z",
      "payment_method_data": null,
      "payment_method_type": "card",
      "payment_method_subtype": "credit",
      "connector_transaction_id": "pay_ll4mzsjdkkouzfmepbopl3cbuu",
      "connector_reference_id": null,
      "merchant_connector_id": "mca_e7GcpKgzDyhD2l8811xl",
      "browser_info": null,
      "error": null
    }
  },
  "timestamp": "2024-11-19T12:42:10.267Z"
}

image

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

Narayanbhat166 and others added 30 commits October 29, 2024 15:18
@sai-harsha-vardhan sai-harsha-vardhan added A-core Area: Core flows C-feature Category: Feature request or enhancement api-v2 labels Nov 19, 2024
@sai-harsha-vardhan sai-harsha-vardhan self-assigned this Nov 19, 2024
Copy link

semanticdiff-com bot commented Nov 19, 2024

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/events/outgoing_webhook_logs.rs  8% smaller
  crates/diesel_models/src/events.rs  0% smaller
  crates/router/src/core/webhooks.rs  0% smaller
  crates/router/src/core/webhooks/incoming_v2.rs  0% smaller
  crates/router/src/core/webhooks/outgoing_v2.rs  0% smaller
  crates/router/src/db/events.rs  0% smaller

}
}

fn get_outgoing_webhook_event_content_from_event_metadata(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use From or impl based function conversion for this.

Comment on lines +723 to +730
metrics::WEBHOOK_OUTGOING_NOT_RECEIVED_COUNT.add(
&metrics::CONTEXT,
1,
&[metrics::KeyValue::new(
MERCHANT_ID,
merchant_id.get_string_repr().to_owned(),
)],
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this also as a function like increment_webhook_outgoing_received_count

NoSchedule,
}

async fn update_event_if_client_error(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In these functions (update_event_in_storage, update_event_if_client_error) the logic can be merged if caller sending OutgoingWebhookResponseContent

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like

fn get_outgoing_webhook_response_content_for_client_error(..) { ... }
fn get_outgoing_webhook_response_content_for_response(..) { ... }

then call update_event with outgoing_webhook_response_content.

Even the these get_outgoing_webhook_response_content can be on impl based function on private structs.

state.event_handler().log_event(&webhook_event);
}

fn get_webhook_url_from_business_profile(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be me moved to impl function on profile, avoid using business_profile term instead usin profile

let key_manager_state: &KeyManagerState = &(&state).into();
let event_id = event.event_id;

let error = if let Err(error) = trigger_webhook_result {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use trigger_webhook_result.err().map(|..| ...)

let error = report!(errors::WebhooksFlowError::NotReceivedByMerchant);
logger::warn!(?error, ?delivery_attempt, status_code, %log_message);

//TODO: add outgoing webhook retries support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why still these are todo?

logger::debug!(outgoing_webhook_response=?response);

match delivery_attempt {
enums::WebhookDeliveryAttempt::InitialAttempt => match response {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is difference between InitialAttempt and ManualRetry?

Instead matching first on delivery_attempt, first match on response. Since current approach has more duplication than the vice-versa

Err(error) => {
if error.current_context().is_db_unique_violation() {
logger::debug!("Event with idempotent ID `{idempotent_event_id}` already exists in the database");
return Ok(());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comments why this is short circuited

primary_object_id: String,
primary_object_type: enums::EventObjectType,
content: api::OutgoingWebhookContent,
primary_object_created_at: Option<time::PrimitiveDateTime>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is option? Any cases we won't get primary_object_created_at

"Outgoing webhooks are disabled in application configuration, or merchant webhook URL \
could not be obtained; skipping outgoing webhooks for event"
);
return Ok(());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment for short circuiting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows api-v2 C-feature Category: Feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants