hyperswitch icon indicating copy to clipboard operation
hyperswitch copied to clipboard

feat(webhooks): Provide outgoing webhook support for revenue recovery

Open NISHANTH1221 opened this issue 3 months ago • 1 comments

Type of Change

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

Description

This PR enables outgoing webhook support for revenue recovery.

When the retries get succeeded that are done using the revenue recovery system, an outgoing webhook needs to be triggered to the url in business profile. This pr adds that support.

Additional Changes

  • [x] 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?

  1. create a billing profile with webhook.site url to monitor the webhooks.
  2. Create an payment mca using this
curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id:  {{merchant-id}}'
--header 'x-profile-id:  {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
    "connector_type": "payment_processor",
    "connector_name": "stripe",
    "connector_account_details": {
        "auth_type": "HeaderKey",
        "api_key": "{{api_key}}"
    },
    "payment_methods_enabled": [
        {
            "payment_method_type": "card",
            "payment_method_subtypes": [
                {
                    "payment_method_subtype": "credit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_subtype": "debit",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                 {
                    "payment_method_subtype": "card",
                    "payment_experience": null,
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "accepted_currencies": null,
                    "accepted_countries": null,
                    "minimum_amount": -1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "frm_configs": null,
    "connector_webhook_details": {
        "merchant_secret": ""
    },
    "metadata": {
        "report_group": "Hello",
        "merchant_config_currency": "USD"
    },
    "profile_id": "{{profile_id}}"
}'
  1. create an billing mca

curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id:  {{merchant-id}}'
--header 'x-profile-id:  {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
    "connector_type": "billing_processor",
    "connector_name": "custombilling",
    "connector_account_details": {
        "auth_type": "NoKey"
    },
    "feature_metadata" : {
        "revenue_recovery" : {
            "max_retry_count" : 9,
            "billing_connector_retry_threshold" : 0,
            "billing_account_reference" :{
                "{{payment_mca}}" : "{{payment_mca}}"
            },
            "switch_payment_method_config" : {
                "retry_threshold" : 0,
                "time_threshold_after_creation": 0
            }
        }
    },
    "profile_id": "{{profile_id}}"
}'
  1. switch revenue recovery config to cascading
curl --location --request PUT 'http://localhost:8080/v2/profiles/pro_DD6ZRORqBtEwR3ITjseM' \
--header 'x-merchant-id: {{erchant-id}}' \
--header 'Authorization:  {{api-key}}' \
--header 'Content-Type: application/json' \
--header 'api-key:  {{api-key}}' \
--data '{
    "revenue_recovery_retry_algorithm_type": "cascading"
}   '
  1. Go to stripe dashboard and create a customer and attach a card for that customer
  2. Hit this recovery api with that customer_id and payment_method_id
curl --location 'http://localhost:8080/v2/payments/recovery' \
--header 'Authorization: {{api-key}}'
--header 'x-profile-id: {{profile-id}}
--header 'x-merchant-id: {{merchant-id}}'
--header 'Content-Type: application/json' \
--header 'api-key:{{api_key}}' \
--data '{
    "amount_details": {
      "order_amount": 2250,
      "currency": "USD"
    },
    "merchant_reference_id": "test_ffhgfewf3476f5",
    "connector_transaction_id": "1831984",
    "connector_customer_id": "{{customer_id_from_stripe}}",
    "error": {
      "code": "110",
      "message": "Insufficient Funds"
    },
    "billing": {
      "address": {
        "state": "CA",
        "country": "US"
      }
    },
    "payment_method_type": "card",
    "payment_method_sub_type": "credit",
    "payment_method_data": {
      "primary_processor_payment_method_token": "{{payment_method_id_from_stripe}}",
      "additional_payment_method_info": {
        "card_exp_month": "12",
        "card_exp_year": "25",
        "last_four_digits": 1997,
        "card_network": "Visa",
        "card_issuer": "Wells Fargo NA",
        "card_type": "credit"
      }
    },
    "billing_started_at": "2024-05-29T08:10:58Z",
    "transaction_created_at": "2024-05-29T08:10:58Z",
    "attempt_status": "failure",
    "action": "schedule_failed_payment",
    "billing_merchant_connector_id": "{{billing_mca}}
    "payment_merchant_connector_id": "{{payment_mca}}"
  }'
  1. There will be a process tracker entry now and wait till it get triggered. Based on the test card if we mentioned a successful card's payment method id then we will get a webhook to webhook.site url. Sample screen shot and sample webhook body are mentioned below Screenshot 2025-09-08 at 1 55 58 AM Sample body of the webhook
{
  "merchant_id": "cloth_seller_I7nS4iwZnxRDJtGfiNTy",
  "event_id": "evt_019925c838c877c282d3eaf5b86e9036",
  "event_type": "payment_succeeded",
  "content": {
    "type": "payment_details",
    "object": {
      "id": "12345_pay_019925c5959a79418301a9460b92edb1",
      "status": "succeeded",
      "amount": {
        "order_amount": 2250,
        "currency": "USD",
        "shipping_cost": null,
        "order_tax_amount": null,
        "external_tax_calculation": "skip",
        "surcharge_calculation": "skip",
        "surcharge_amount": null,
        "tax_on_surcharge": null,
        "net_amount": 2250,
        "amount_to_capture": null,
        "amount_capturable": 0,
        "amount_captured": 2250
      },
      "customer_id": null,
      "connector": "stripe",
      "created": "2025-09-07T20:02:09.947Z",
      "modified_at": "2025-09-07T20:05:02.777Z",
      "payment_method_data": {
        "billing": null
      },
      "payment_method_type": "card",
      "payment_method_subtype": "credit",
      "connector_transaction_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
      "connector_reference_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
      "merchant_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
      "browser_info": null,
      "error": null,
      "shipping": null,
      "billing": null,
      "attempts": null,
      "connector_token_details": {
        "token": "pm_1S4olu2KXBHSNjodBbiedqY9",
        "connector_token_request_reference_id": null
      },
      "payment_method_id": null,
      "next_action": null,
      "return_url": "https://google.com/success",
      "authentication_type": "no_three_ds",
      "authentication_type_applied": "no_three_ds",
      "is_iframe_redirection_enabled": null,
      "merchant_reference_id": "test_ffhgfewf3476f5",
      "raw_connector_response": null,
      "feature_metadata": {
        "redirect_response": null,
        "search_tags": null,
        "apple_pay_recurring_details": null,
        "revenue_recovery": {
          "total_retry_count": 2,
          "payment_connector_transmission": "ConnectorCallSucceeded",
          "billing_connector_id": "mca_ppEVMjRWgTiGyCiwFtg9",
          "active_attempt_payment_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
          "billing_connector_payment_details": {
            "payment_processor_token": "pm_1S4olu2KXBHSNjodBbiedqY9",
            "connector_customer_id": "cus_T0qSE723C5Xxic"
          },
          "payment_method_type": "card",
          "payment_method_subtype": "credit",
          "connector": "stripe",
          "billing_connector_payment_method_details": {
            "type": "card",
            "value": {
              "card_network": "Visa",
              "card_issuer": "Wells Fargo NA"
            }
          },
          "invoice_next_billing_time": null,
          "invoice_billing_started_at_time": null,
          "first_payment_attempt_pg_error_code": "resource_missing",
          "first_payment_attempt_network_decline_code": null,
          "first_payment_attempt_network_advice_code": null
        }
      }
    }
  },
  "timestamp": "2025-09-07T20:05:02.792Z"
}

Checklist

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

NISHANTH1221 avatar Sep 07 '25 20:09 NISHANTH1221