Skip to content

Conversation

srujanchikke
Copy link
Contributor

@srujanchikke srujanchikke commented Jan 27, 2025

Type of Change

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

Description

Recovery Incoming Webhooks service :
Consume incoming webhooks of billing platforms to schedule revenue recovery retries in hs.
Endpoint :
v2/webhooks/revenue_recovery/{{profile_id}}/{{merchant_connector_account_id}}
Authentication :
Source verification.
Method :
POST
Request :
Accepts connector specific requests , unified fields that we are going to consume and verified across billing platforms(Chargebee, Recurly, Stripe Billing).
Response :
They expect us to send 200 back.

DB changes :

  • Triggered by : Flag to find out whether an attempt was created external or internal. Possible enums of triggered_by EXTERNAL/ INTERNAL.
  • Payment attempt
feature_metadata{
revenue_recovery :  {
triggered_by : TriggeredBy
}
}

triggered_by : Flag to find out whether an attempt was created external or internal. Possible enums of triggered_by EXTERNAL/ INTERNAL

Flow

  1. We receive invoice and transactional related incoming webhooks from billing connectors(ex: chargebee).
  2. Verify the source with source verification logic using incoming webhook trait.
  3. Fetch the intent using merchant reference id(Invoice id), if not found create new payment intent for the invoice.
  4. Fetch the attempt if event is transactional event, if not found record new attempt against the payment intent .
  5. Based on triggered_by and event type decide what actions needs to be taken for the webhook

Note: Only handler functions of payments core are called in webhooks flow to make revenue recovery flow modular. No direct DB call is made in recovery incoming webhook flow to payments(intent/attempt) table.

why do we need attempt.triggered_by and event type to take the decision?

  • Recovery event types : There can be 4 types of events that we consume from billing connector.
    • Payment Failure, payment success, payment pending, Invoice cancel
      Based on event we take the action but we need to triggered by to know whether it was created by Hyperswitch or External system.
  • Recovery Actions :
/// type of action that needs to taken after consuming recovery payload
RecoveryAction {
    /// Stops the process tracker and update the payment intent.
    CancelInvoice,
    /// Records the external transaction against payment intent.
    ScheduleFailedPayment,
    /// Records the external payment and stops the internal process tracker.
    SuccessPaymentExternal,
    /// Pending payments from billing processor.
    PendingPayment,
    /// No action required.
    NoAction,
    /// Invalid event has been received.
    InvalidAction,
}

Mapping between (event, triggered_by) and Actions

IncomingWebhookEvent::RecoveryPaymentFailure => match attempt_triggered_by {
                Some(TriggeredBy::Internal) => RecoveryAction::NoAction,
                Some(TriggeredBy::External) | None => RecoveryAction::ScheduleFailedPayment,
            },
            IncomingWebhookEvent::RecoveryPaymentSuccess => match attempt_triggered_by {
                Some(TriggeredBy::Internal) => RecoveryAction::NoAction,
                Some(TriggeredBy::External) | None => RecoveryAction::SuccessPaymentExternal,
            },
            IncomingWebhookEvent::RecoveryPaymentPending => RecoveryAction::PendingPayment,
            IncomingWebhookEvent::RecoveryInvoiceCancel => RecoveryAction::CancelInvoice,

Note : Pending Task From this PR

Additional Changes

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

Motivation and Context

Cannot test this flow, will be tested once #7236 and #7110 are merged.

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

Copy link

semanticdiff-com bot commented Jan 27, 2025

Review changes with  SemanticDiff

Changed Files
File Status
  crates/router/src/routes/lock_utils.rs  82% smaller
  crates/openapi/src/openapi.rs  28% smaller
  crates/router/src/core/payments.rs  25% smaller
  crates/openapi/src/openapi_v2.rs  9% smaller
  api-reference-v2/openapi_spec.json  9% smaller
  crates/common_utils/src/ext_traits.rs  4% smaller
  crates/api_models/src/payments.rs  1% smaller
  api-reference/openapi_spec.json  0% smaller
  crates/api_models/Cargo.toml Unsupported file format
  crates/api_models/src/webhooks.rs  0% smaller
  crates/common_enums/src/enums.rs  0% smaller
  crates/diesel_models/src/payment_attempt.rs  0% smaller
  crates/diesel_models/src/schema_v2.rs  0% smaller
  crates/hyperswitch_domain_models/Cargo.toml Unsupported file format
  crates/hyperswitch_domain_models/src/lib.rs  0% smaller
  crates/hyperswitch_domain_models/src/payments/payment_attempt.rs  0% smaller
  crates/hyperswitch_domain_models/src/revenue_recovery.rs  0% smaller
  crates/hyperswitch_interfaces/Cargo.toml Unsupported file format
  crates/hyperswitch_interfaces/src/webhooks.rs  0% smaller
  crates/router/Cargo.toml Unsupported file format
  crates/router/src/core/errors.rs  0% smaller
  crates/router/src/core/payments/transformers.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/recovery_incoming.rs  0% smaller
  crates/router/src/routes.rs  0% smaller
  crates/router/src/routes/app.rs  0% smaller
  crates/router/src/routes/recovery_webhooks.rs  0% smaller
  crates/router/src/services/connector_integration_interface.rs Unsupported file format
  crates/router_env/src/logger/types.rs  0% smaller
  v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql Unsupported file format
  v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql Unsupported file format

@hyperswitch-bot hyperswitch-bot bot added the M-database-changes Metadata: This PR involves database schema changes label Jan 27, 2025
Base automatically changed from chargebee_integration to main January 29, 2025 11:47
@srujanchikke srujanchikke self-assigned this Feb 5, 2025
@srujanchikke srujanchikke marked this pull request as ready for review February 18, 2025 05:55
jarnura
jarnura previously approved these changes Feb 19, 2025
#[cfg(all(feature = "v2", feature = "revenue_recovery"))]
impl IncomingWebhookEvent {
pub fn is_recovery_transaction_event(&self) -> bool {
match self {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can use matches!

#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum TriggeredBy {
/// Denotes payment attempt is been created by hyperswitch system.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: prefer not refer hyperswitch in codebase

/// merchant reference id at billing connector. ex: invoice_id
pub merchant_reference_id: common_utils::id_type::PaymentReferenceId,
/// transaction id reference at payment connector
pub connector_transaction_id: Option<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we please use ConnectorTransactionId domain type here?

/// error message sent by billing connector.
pub error_message: Option<String>,
/// mandate token at payment processor end.
pub processor_payment_token: Option<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

mandate token, is more of processor_payment_method_token right? not payment token?

/// customer id at payment connector for which mandate is attached.
pub connector_customer_id: Option<String>,
/// payment merchant connnector account reference id at billing connector.
pub connector_account_reference_id: Option<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

what exactly is merchant connnector account reference id?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Payment gateway identifier id at billing processor. Add the same comment.

{
Ok(None)
}
Ok(_) | Err(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be logging Err(err) here, and any other Ok() response is unexpected

hyperswitch_domain_models::payments::HeaderPayload::default(),
))
.await;
router_env::logger::info!(?attempt_response);
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these loggers required? If yes, please add relevant keys if not there's no use of this loggers

});
Ok(payment_attempt)
}
Ok(_) | Err(_) => Err(errors::RevenueRecoveryError::PaymentAttemptFetchFailed)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

#[cfg(all(feature = "revenue_recovery", feature = "v2"))]
{
route = route.service(
web::resource("/recovery/{merchant_id}/{profile_id}/{connector_id}").route(
Copy link
Contributor

Choose a reason for hiding this comment

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

preferably stricter routes should be on top of generic routes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This not required here since, since number of parameters vary for payment webhooks and recovery webhooks

@@ -310,6 +310,7 @@ impl From<Flow> for ApiIdentifier {
Flow::RetrievePollStatus => Self::Poll,

Flow::FeatureMatrix => Self::Documentation,
Flow::RecoveryIncomingWebhookReceive => Self::Webhooks,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can you move this to existing webhooks match statement instead

@likhinbopanna likhinbopanna added this pull request to the merge queue Feb 21, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to no response for status checks Feb 21, 2025
@likhinbopanna likhinbopanna added this pull request to the merge queue Feb 22, 2025
Merged via the queue into main with commit 0688972 Feb 22, 2025
16 of 20 checks passed
@likhinbopanna likhinbopanna deleted the recovery_webhooks branch February 22, 2025 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
M-api-contract-changes Metadata: This PR involves API contract changes M-database-changes Metadata: This PR involves database schema changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants