feat(connector): [FINIX] webhook + statement descriptor#10758
Conversation
Changed Files |
There was a problem hiding this comment.
Pull request overview
This PR adds webhook support for the FINIX connector, implementing dispute and refund webhooks along with statement descriptor functionality. The implementation includes webhook signature verification using HMAC-SHA256 and proper event type mapping.
- Webhook implementation for payment, refund, and dispute events with signature verification
- Statement descriptor support in payment requests
- Error handling improvements for unsupported flow scenarios
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/hyperswitch_connectors/src/connectors/finix.rs | Added webhook handlers including signature verification, event type detection, and dispute details extraction; removed old placeholder webhook implementation; removed ApplePay from supported payment methods |
| crates/hyperswitch_connectors/src/connectors/finix/transformers.rs | Added webhook body parsing, event type mapping, dispute details extraction, and object reference ID resolution for webhooks |
| crates/hyperswitch_connectors/src/connectors/finix/transformers/request.rs | Added statement_descriptor field to payment request structure |
| crates/hyperswitch_connectors/src/connectors/finix/transformers/response.rs | Added webhook-related structs (FinixWebhookBody, FinixEmbedded, FinixDisputes, etc.) and combined payment/webhook response handling |
| crates/router/src/core/refunds.rs | Added error handling for FlowNotSupported connector error with appropriate refund update |
| crates/router/src/core/refunds_v2.rs | Added error handling for FlowNotSupported connector error with appropriate refund update |
| crates/payment_methods/src/configs/payment_connector_required_fields.rs | Added FINIX connector to required fields configuration with basic card fields |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #10758 +/- ##
=======================================
Coverage ? 6.38%
=======================================
Files ? 1264
Lines ? 317865
Branches ? 0
=======================================
Hits ? 20305
Misses ? 297560
Partials ? 0 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| )) | ||
| } | ||
| FinixEmbedded::Transfers { transfers } => { | ||
| let transfer = transfers.get_first_event()?; |
There was a problem hiding this comment.
First event is always the latest event ?
| FinixState::FAILED | FinixState::CANCELED | FinixState::UNKNOWN => { | ||
| Ok(IncomingWebhookEvent::PaymentIntentCancelFailure) | ||
| } | ||
| FinixState::PENDING => Ok(IncomingWebhookEvent::PaymentIntentCancelled), |
There was a problem hiding this comment.
Why is pending mapped to PaymentIntentCancelled
| match transfers.state { | ||
| FinixState::PENDING => Ok(IncomingWebhookEvent::PaymentIntentProcessing), | ||
| FinixState::SUCCEEDED => Ok(IncomingWebhookEvent::PaymentIntentSuccess), | ||
| FinixState::FAILED | FinixState::CANCELED | FinixState::UNKNOWN => { |
There was a problem hiding this comment.
Does UNKNOWN FinixState always mean payment intent failure ?
| let statement_descriptor = item | ||
| .router_data | ||
| .request | ||
| .billing_descriptor | ||
| .clone() | ||
| .and_then(|billing_descriptor| billing_descriptor.statement_descriptor); |
There was a problem hiding this comment.
can we move this to utils
| Ok(format!( | ||
| "{}:{}", | ||
| &security_header_kvs.timestamp, | ||
| String::from_utf8_lossy(request.body) |
There was a problem hiding this comment.
is it ok to use lossy in verification? if failure occurs due to invalid character it won't be visible.
Type of Change
Description
statement descriptor
Request
{ "amount": 400, "customer_id":"hello_world", "currency": "USD", "confirm": true, "setup_future_usage": "off_session", "capture_method": "automatic", "payment_method": "card", "payment_method_type": "credit", "authentication_type": "no_three_ds", "description": "hellow world", "customer_acceptance": { "acceptance_type": "offline", "accepted_at": "1963-05-03T04:07:52.723Z", "online": { "ip_address": "127.0.0.1", "user_agent": "amet irure esse" } }, "connector":["finix"], "billing": { "address": { "zip": "560095", "country": "IN", "first_name": "Sakil", "last_name": "Mostak", "line1": "Fasdf", "line2": "Fasdf", "city": "Fasdf" } }, "billing_descriptor":{ "statement_descriptor" :"test" }, "payment_method_data": { "card": { "card_number": "5200828282828210", "card_exp_month": "01", "card_exp_year": "2026", "card_holder_name": "John Smith", "card_cvc": "100" } } }Response
{ "payment_id": "pay_kqBhRZNOmen1b49Yqifv", "merchant_id": "merchant_1766136784", "status": "succeeded", "amount": 400, "net_amount": 400, "shipping_cost": null, "amount_capturable": 0, "amount_received": 400, "connector": "finix", "client_secret": "pay_kqBhRZNOmen1b49Yqifv_secret_1kJD7s89xJB3JD54e3Yw", "created": "2025-12-22T05:19:03.437Z", "modified_at": "2025-12-22T05:19:06.279Z", "currency": "USD", "customer_id": "hello_world", "customer": { "id": "hello_world", "name": null, "email": null, "phone": null, "phone_country_code": null }, "description": "hellow world", "refunds": null, "disputes": null, "mandate_id": null, "mandate_data": null, "setup_future_usage": "off_session", "off_session": null, "capture_on": null, "capture_method": "automatic", "payment_method": "card", "payment_method_data": { "card": { "last4": "8210", "card_type": null, "card_network": null, "card_issuer": null, "card_issuing_country": null, "card_isin": "520082", "card_extended_bin": null, "card_exp_month": "01", "card_exp_year": "2026", "card_holder_name": "John Smith", "payment_checks": null, "authentication_data": null }, "billing": null }, "payment_token": null, "shipping": null, "billing": { "address": { "city": "Fasdf", "country": "IN", "line1": "Fasdf", "line2": "Fasdf", "line3": null, "zip": "560095", "state": null, "first_name": "Sakil", "last_name": "Mostak", "origin_zip": null }, "phone": null, "email": null }, "order_details": null, "email": null, "name": null, "phone": null, "return_url": null, "authentication_type": "no_three_ds", "statement_descriptor_name": null, "statement_descriptor_suffix": null, "next_action": null, "cancellation_reason": null, "error_code": null, "error_message": null, "unified_code": null, "unified_message": null, "payment_experience": null, "payment_method_type": "credit", "connector_label": null, "business_country": null, "business_label": "default", "business_sub_label": null, "allowed_payment_method_types": null, "manual_retry_allowed": null, "connector_transaction_id": "TRpebYFVqXQFidJ1M4p9P791", "frm_message": null, "metadata": null, "connector_metadata": null, "feature_metadata": { "redirect_response": null, "search_tags": null, "apple_pay_recurring_details": null, "gateway_system": "direct" }, "reference_id": null, "payment_link": null, "profile_id": "pro_EeH8kEuAArq7n3oJ7iX0", "surcharge_details": null, "attempt_count": 1, "merchant_decision": null, "merchant_connector_id": "mca_7F6uC4TvsgC7epx2S06Q", "incremental_authorization_allowed": false, "authorization_count": null, "incremental_authorizations": null, "external_authentication_details": null, "external_3ds_authentication_attempted": false, "expires_on": "2025-12-22T05:34:03.437Z", "fingerprint": null, "browser_info": null, "payment_channel": null, "payment_method_id": "pm_qVbUZupn68KS2v7rcQxj", "network_transaction_id": null, "payment_method_status": "active", "updated": "2025-12-22T05:19:06.279Z", "split_payments": null, "frm_metadata": null, "extended_authorization_applied": null, "extended_authorization_last_applied_at": null, "request_extended_authorization": null, "capture_before": null, "merchant_order_reference_id": null, "order_tax_amount": null, "connector_mandate_id": "PIe2SHAdwd8TE2CVv7NyPJsV", "card_discovery": "manual", "force_3ds_challenge": false, "force_3ds_challenge_trigger": false, "issuer_error_code": null, "issuer_error_message": null, "is_iframe_redirection_enabled": null, "whole_connector_response": null, "enable_partial_authorization": null, "enable_overcapture": null, "is_overcapture_enabled": null, "network_details": null, "is_stored_credential": null, "mit_category": null, "billing_descriptor": { "name": null, "city": null, "phone": null, "statement_descriptor": "test", "statement_descriptor_suffix": null, "reference": null }, "tokenization": null, "partner_merchant_identifier_details": null, "payment_method_tokenization_details": { "payment_method_id": "pm_qVbUZupn68KS2v7rcQxj", "payment_method_status": "active", "psp_tokenization": false, "network_tokenization": false, "network_transaction_id": null, "is_eligible_for_mit_payment": false } }connector
INFO hyperswitch_interfaces::api_client: raw_connector_request: Object {"amount": Number(400), "currency": String("USD"), "source": String("*** alloc::string::String ***"), "merchant": String("*** alloc::string::String ***"), "tags": Null, "three_d_secure": Null, "idempotency_id": String("pay_oWSIJXJelUeZZPg5T8Dn_1"), "statement_descriptor": String("testdescription")}Webhook
Create a refund for above payment only and check webhook source verification
{ "refund_id": "ref_pOLKsxTeUuCzbXGkWEPX", "payment_id": "pay_oWSIJXJelUeZZPg5T8Dn", "amount": 400, "currency": "USD", "status": "pending", "reason": null, "metadata": { "store": "ST3298F22322955NDWVDWCWG6" }, "error_message": null, "error_code": null, "unified_code": null, "unified_message": null, "created_at": "2025-12-22T05:28:06.079Z", "updated_at": "2025-12-22T05:28:07.432Z", "connector": "finix", "profile_id": "pro_EeH8kEuAArq7n3oJ7iX0", "merchant_connector_id": "mca_7F6uC4TvsgC7epx2S06Q", "split_refunds": null, "issuer_error_code": null, "issuer_error_message": null, "raw_connector_response": null }{ "id": "event_dcxXgvYhwkMcxXKKWAoNG3", "system_generated_idempotency_id": "dcxXgvYhwkMcxXKKWAoNG3", "type": "updated", "entity": "transfer", "occurred_at": "2025-12-22T05:29:04.937754326", "_embedded": { "transfers": [ { "id": "TRjSEcM56yzdC6fX9Z2vNa9c", "created_at": "2025-12-22T05:28:07.25Z", "updated_at": "2025-12-22T05:29:03.71Z", "additional_buyer_charges": null, "additional_healthcare_data": null, "additional_purchase_data": null, "address_verification": null, "amount": 400, "amount_requested": 400, "application": "APsadfUVzGM6gWeoGCeXSCJs", "currency": "USD", "destination": "PIf8GRSdpdue3ZTFzBHdkyW5", "externally_funded": "UNKNOWN", "failure_code": null, "failure_message": null, "fee": 0, "fee_profile": "FPmtT4MYmiAs1qjLjneQmk4d", "idempotency_id": null, "ip_address_details": null, "merchant": "MU3YACSJUhFGMGSbfMPL5xd7", "merchant_identity": "ID5WZSCWbsyoBW22x9MrrPn8", "messages": [], "network_details": null, "operation_key": "CARD_NOT_PRESENT_REFUND", "parent_transfer": "TRprodxPo31w4yd549zXzjTZ", "parent_transfer_trace_id": "f0a246bd-3576-402b-b0d7-136e4eaaaaf4", "raw": null, "ready_to_settle_at": "2025-12-23T05:29:03.75Z", "receipt_last_printed_at": null, "security_code_verification": null, "source": null, "split_transfers": [], "state": "SUCCEEDED", "statement_descriptor": "FLX*TESTDESCRIPTION", "subtype": "API", "supplemental_fee": null, "tags": {}, "third_party_details": null, "tip_amount": null, "trace_id": "c16697c8-95ad-4bfb-ac3b-16ada22ac692", "type": "REVERSAL" } ] } }without doing psync check status
Additional Changes
Motivation and Context
How did you test it?
Checklist
cargo +nightly fmt --allcargo clippy