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(payouts): Implement Smart Retries for Payout #3580

Merged
merged 40 commits into from
Feb 28, 2024
Merged

Conversation

Sakilmostak
Copy link
Contributor

@Sakilmostak Sakilmostak commented Feb 7, 2024

Type of Change

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

Description

Implement multiple connector retries for payout

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables
ALTER TABLE payouts
ADD COLUMN attempt_count SMALLINT NOT NULL DEFAULT 1;;


UPDATE payouts
SET attempt_count = payout_id_count.count
FROM (SELECT payout_id, count(payout_id) FROM payout_attempt GROUP BY payout_id) as payout_id_count
WHERE payouts.payout_id = payout_id_count.payout_id;
Config feature flag added for payout retry
payout_retry = []

How did you test it?

Tested through Postman

  • Create a Merchant Account
{
  "merchant_id": "merchant_{{$timestamp}}",
  "locker_id": "m0010",
  "merchant_name": "NewAge Retailer",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "[email protected]",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "[email protected]",
    "secondary_phone": "cillum do dolor id",
    "website": "www.example.com",
    "about_business": "Online Retail with a wide selection of organic products for North America",
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US"
    }
  },
   "primary_business_details": [
        {
            "country": "US",
            "business": "default"
        }
    ],
  "return_url": "https://google.com/success",
  "webhook_details": {
    "webhook_version": "1.0.1",
    "webhook_username": "ekart_retail",
    "webhook_password": "password_ekart@123",
    "payment_created_enabled": true,
    "payment_succeeded_enabled": true,
    "payment_failed_enabled": true
  },
  "sub_merchants_enabled": false,
  "metadata": {
    "merchant_id": "1087905",
    "name": "Sakil"
  }
}
  • Create Multiple Payout Connector (Adyen and Wise)
{
    "connector_type": "payout_processor",
    "connector_name": "adyen",
    "connector_account_details": {
        "auth_type": "SignatureKey",
        "api_key": "{{api_key}}",
        "key1": "{{key1}}",
        "api_secret": "{{api_secret}}"
    },
    "test_mode": false,
    "disabled": false,
    "payment_methods_enabled": [
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "debit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "bank_transfer",
            "payment_method_types": [
                {
                    "payment_method_type": "sepa",
                    //"payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "bacs",
                    //"payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "city": "NY",
        "unit": "245"
    },
    "business_country": "US",
    "business_label": "default"
}
{
    "connector_type": "payout_processor",
    "connector_name": "wise",
        "connector_account_details": {
        "auth_type": "BodyKey",
        "api_key": "{{api_key}}",
        "key1": "{{key1}}"
    },
    "test_mode": false,
    "disabled": false,
    "payment_methods_enabled": [
        {
            "payment_method": "card",
            "payment_method_types": [
                {
                    "payment_method_type": "credit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "debit",
                    "card_networks": [
                        "Visa",
                        "Mastercard"
                    ],
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "pay_later",
            "payment_method_types": [
                {
                    "payment_method_type": "klarna",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "affirm",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "afterpay_clearpay",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "wallet",
            "payment_method_types": [
                {
                    "payment_method_type": "paypal",
                    "payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        },
        {
            "payment_method": "bank_transfer",
            "payment_method_types": [
                {
                    "payment_method_type": "sepa",
                    //"payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                },
                {
                    "payment_method_type": "bacs",
                    //"payment_experience": "redirect_to_url",
                    "minimum_amount": 1,
                    "maximum_amount": 68607706,
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "merchant_id": "1087905",
        "name": "Mostak"
    },
    "business_country": "US",
    "business_label": "default"
}
  • Set the configs for retries
  • Create a Customer
  • Create a Payout through Adyen
{
    "amount": 1,
    "currency": "EUR",
    "customer_id": "{{customer_id}}",
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "description": "Its my first payout request",
    "payout_type": "bank",
    "payout_method_data": {
        "bank": {
            "bic": "ABNANL2A",
            "iban": "NL46TEST0136169112",
            "bank_name": "Deutsche Bank",
            "bank_country_code": "KR",
            "bank_city": "Amsterdam"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "NY",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "entity_type": "Individual",
    "recurring": false,
    "metadata": {
        "ref": "123"
    },
    "connector": ["adyen", "wise"],
    "routing": {
        "type": "single",
        "data": {
            "connector": "adyen",
            "merchant_connector_id": "mca_5ojMIclEHQ64r51aWwgn"
        }
    },
    "confirm": true,
    "auto_fulfill": false
}
  • After the request, the payout would fail for Adyen so it will be retried with Wise. Thus the response should show that the payout is initiated through Wise
{
    "payout_id": "3b0179b9-1db2-45f1-98da-66c83e5b93e2",
    "merchant_id": "merchant_1707305590",
    "amount": 1,
    "currency": "EUR",
    "connector": "wise",
    "payout_type": "bank",
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "US",
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "zip": "94122",
            "state": "NY",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        }
    },
    "customer_id": "cus_7bS105vS5bPKbC8zPO5H",
    "auto_fulfill": false,
    "email": "[email protected]",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+65",
    "client_secret": null,
    "return_url": null,
    "business_country": null,
    "business_label": null,
    "description": "Its my first payout request",
    "entity_type": "Individual",
    "recurring": false,
    "metadata": {
        "ref": "123"
    },
    "status": "requires_fulfillment",
    "error_message": null,
    "error_code": null,
    "profile_id": "pro_NAstqzq3HDJqp5Mwuo2R"
}
Screenshot 2024-02-07 at 5 36 23 PM

Note: This affects the Payout flow.

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
  • I added a CHANGELOG entry if applicable

@Sakilmostak Sakilmostak added A-core Area: Core flows C-feature Category: Feature request or enhancement M-database-changes Metadata: This PR involves database schema changes M-configuration-changes Metadata: This PR involves configuration changes labels Feb 7, 2024
@Sakilmostak Sakilmostak self-assigned this Feb 7, 2024
@Sakilmostak Sakilmostak requested review from a team as code owners February 7, 2024 12:15
crates/router/src/core/payouts/retry.rs Outdated Show resolved Hide resolved
crates/router/src/core/payouts.rs Outdated Show resolved Hide resolved
crates/router/src/core/payouts.rs Outdated Show resolved Hide resolved
crates/router/src/core/payouts/retry.rs Outdated Show resolved Hide resolved
crates/router/Cargo.toml Show resolved Hide resolved
@@ -31,6 +31,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect
external_access_dc = ["dummy_connector"]
detailed_errors = ["api_models/detailed_errors", "error-stack/serde"]
payouts = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
payouts = []
payouts = ["payout_retry"]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since, payout_retry is dependent on payout, payout is put under payout_retry

… listing based on flow (payments or payouts)

if retries.is_none() || retries == Some(0) {
metrics::AUTO_PAYOUT_RETRY_EXHAUSTED_COUNT.add(&metrics::CONTEXT, 1, &[]);
logger::info!("retries exhausted for auto_retry payment");
Copy link
Contributor

Choose a reason for hiding this comment

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

Update this message to differentiate between payment and payout retries

let payout_attempt = &payout_data.payout_attempt.to_owned();
let payouts: &diesel_models::payouts::Payouts = &payout_data.payouts.to_owned();

// update connector_name
payout_data.payout_attempt.connector = Some(connector_data.connector_name.to_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 update the connector in the current state and DB only when it is re-evaluated?

Calling this here will make a DB query every time a connector is to be called for creation / updation.

@Sakilmostak Sakilmostak removed the request for review from a team February 28, 2024 06:32
@Gnanasundari24 Gnanasundari24 added this pull request to the merge queue Feb 28, 2024
Merged via the queue into main with commit 8b32dff Feb 28, 2024
13 of 15 checks passed
@Gnanasundari24 Gnanasundari24 deleted the payout_retries branch February 28, 2024 10:08
pixincreate added a commit that referenced this pull request Feb 29, 2024
…stman-runner

* 'main' of github.com:juspay/hyperswitch:
  chore(version): 2024.02.29.0
  chore(postman): update Postman collection files
  feat(analytics): add force retrieve call for force retrieve calls (#3565)
  refactor(connector): [Mollie] Mask PII data  (#3856)
  refactor(connector): [Gocardless] Mask PII data (#3844)
  feat(analytics): adding metric api for dispute analytics (#3810)
  feat(payment_methods): Add default payment method column in customers table and last used column in payment_methods table (#3790)
  fix(tests/postman/adyen): enable sepa payment method type for payout flows (#3861)
  feat(payouts): Implement Smart Retries for Payout (#3580)
  refactor(payment_link): add Miscellaneous charges in cart (#3645)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows C-feature Category: Feature request or enhancement M-configuration-changes Metadata: This PR involves configuration changes M-database-changes Metadata: This PR involves database schema changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants