hyperswitch
hyperswitch copied to clipboard
feat(FRM): Revise post FRM core flows
Type of Change
- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD
Description
PostAuth flow
- The underlying FRM connector is invoked post authentication of the user from the bank.
- If a transaction’s capture method was set to automatic, it is updated to manual for avoiding automatic capture.
- Once the user completes the authentication, the underlying FRM connector is invoked. The decision whether or not to proceed with the txn is based on the status / score returned.
Possible actions based on the status
- Continue on Accept - Continue with the transaction
- Halt on Decline - Mark the transaction as cancelled
- Approve / Decline on Manual Review - Hold the txn in manual review state. Merchants can list and review such transactions. - If approved, payment is captured - If declined, payment is voided
This flow works same as payments flow incase of 3ds, non 3ds, manual and auto capture for Legit payments.
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?
Create merchant connector account with stripe and then create new merchant connector account with signifyd.
{
"connector_type": "payment_VAS",
"connector_name": "signifyd",
"connector_account_details": {
"auth_type": "HeaderKey",
"api_key": "YOUR_API_KEY"
},
"test_mode": false,
"disabled": false,
"business_country": "US",
"business_label": "default",
"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
}
]
}
],
"metadata": {
"city": "NY",
"unit": "245"
},
"frm_configs": [
{
"gateway": "stripe",
"payment_methods": [
{
"payment_method": "card",
"payment_method_types": [
{
"payment_method_type": "credit",
"card_networks": [
"Visa"
],
"flow": "post",
"action": "cancel_txn"
},
{
"payment_method_type": "debit",
"card_networks": [
"Visa"
],
"flow": "post",
"action": "manual_review"
}
]
}
]
}
]
}
TEST CASE 1 : Create legit card payment(amount =150) for both credit and debit. This should go as normal payment flow. Interchange CaptureMethod and CardType(3ds, non 3ds). TEST CASE 2 : Create Credit(pmt type) Payment with amount equals to 15000. This should Fail the payment .
{
"amount": 15000,
"currency": "USD",
"confirm": true,
"capture_method": "manual",
"capture_on": "2022-09-10T10:11:12Z",
"amount_to_capture": 1500,
"customer_id": "StripeCustomer2",
"email": "[email protected]",
"name": "Bob Smith",
"phone": "999999999",
"phone_country_code": "+91",
"description": "Its my first payment request",
"authentication_type": "no_three_ds",
"return_url": "https://google.com",
"payment_method": "card",
"payment_method_type": "credit",
"payment_method_data": {
"card": {
"card_number": "4242424242424242",
"card_exp_month": "10",
"card_exp_year": "25",
"card_holder_name": "joseph Doe",
"card_cvc": "123"
}
},
"billing": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "US",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
}
},
"shipping": {
"address": {
"line1": "1467",
"line2": "31 Sherwood Gardens",
"line3": "31 Sherwood Gardens",
"city": "London",
"state": "Manchester",
"zip": "E14 9wn",
"country": "GB",
"first_name": "Bob",
"last_name": "Smith"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
}
},
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
},
"order_details" : [{
"product_name" : "gillete creme",
"quantity" : 2,
"amount" : 600
},
{
"product_name" : "gillete razor",
"quantity" : 1,
"amount" : 300
}]
}
TESTCASE 3 : Create a payment with payment method type as debit
. set capture method to automatic
and use same json above. Payment status should be moved to RequiresMerchantAction
.
Response looks like this
{
"payment_id": "pay_9ZgUMXOiLv35wrF6oOyd",
"merchant_id": "merchant_1713525778",
"status": "requires_merchant_action",
"amount": 150000,
"net_amount": 150000,
"amount_capturable": 150000,
"amount_received": 0,
"connector": "stripe",
"client_secret": "pay_9ZgUMXOiLv35wrF6oOyd_secret_wU5hfAMsYjBe0fXepJmj",
"created": "2024-04-19T11:58:40.749Z",
"currency": "USD",
"customer_id": "StripeCustomer2",
"customer": {
"id": "StripeCustomer2",
"name": "Bob Smith",
"email": "[email protected]",
"phone": "999999999",
"phone_country_code": "+91"
},
"description": "Its my first payment request",
"refunds": null,
"disputes": null,
"mandate_id": null,
"mandate_data": null,
"setup_future_usage": null,
"off_session": null,
"capture_on": null,
"capture_method": "manual",
"payment_method": "card",
"payment_method_data": {
"card": {
"last4": "4242",
"card_type": null,
"card_network": null,
"card_issuer": null,
"card_issuing_country": null,
"card_isin": "424242",
"card_extended_bin": "42424242",
"card_exp_month": "10",
"card_exp_year": "25",
"card_holder_name": "joseph Doe",
"payment_checks": {
"cvc_check": "pass",
"address_line1_check": "pass",
"address_postal_code_check": "pass"
},
"authentication_data": null
},
"billing": null
},
"payment_token": null,
"shipping": {
"address": {
"city": "London",
"country": "GB",
"line1": "1467",
"line2": "31 Sherwood Gardens",
"line3": "31 Sherwood Gardens",
"zip": "E14 9wn",
"state": "Manchester",
"first_name": "Bob",
"last_name": "Smith"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"billing": {
"address": {
"city": "San Fransico",
"country": "US",
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"zip": "94122",
"state": "California",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"order_details": [
{
"brand": null,
"amount": 600,
"category": null,
"quantity": 2,
"product_id": null,
"product_name": "gillete creme",
"product_type": null,
"product_img_link": null,
"requires_shipping": null
},
{
"brand": null,
"amount": 300,
"category": null,
"quantity": 1,
"product_id": null,
"product_name": "gillete razor",
"product_type": null,
"product_img_link": null,
"requires_shipping": null
}
],
"email": "[email protected]",
"name": "Bob Smith",
"phone": "999999999",
"return_url": "https://google.com/",
"authentication_type": "no_three_ds",
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"next_action": null,
"cancellation_reason": null,
"error_code": "fraud",
"error_message": "Refund Initiated with the processor",
"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,
"ephemeral_key": {
"customer_id": "StripeCustomer2",
"created_at": 1713527920,
"expires": 1713531520,
"secret": "epk_22d24f1ef64b44a0b28b72a81c2b2392"
},
"manual_retry_allowed": null,
"connector_transaction_id": "pi_3P7G5VD5R7gDAGff0XEZqQkO",
"frm_message": {
"frm_name": "signifyd",
"frm_transaction_id": "pay_9ZgUMXOiLv35wrF6oOyd_1",
"frm_transaction_type": "post_frm",
"frm_status": "fraud",
"frm_score": 497,
"frm_reason": "SIGNIFYD_DECLINED",
"frm_error": null
},
"metadata": {
"udf1": "value1",
"login_date": "2019-09-10T10:11:12Z",
"new_customer": "true"
},
"connector_metadata": null,
"feature_metadata": null,
"reference_id": "pi_3P7G5VD5R7gDAGff0XEZqQkO",
"payment_link": null,
"profile_id": "pro_DOE6fPp88zY1M4xUyO0H",
"surcharge_details": null,
"attempt_count": 1,
"merchant_decision": null,
"merchant_connector_id": "mca_U0XVEDfTM0JMpiv9GOr5",
"incremental_authorization_allowed": null,
"authorization_count": null,
"incremental_authorizations": null,
"external_authentication_details": null,
"external_3ds_authentication_attempted": false,
"expires_on": "2024-04-19T12:13:40.749Z",
"fingerprint": null,
"browser_info": null,
"payment_method_id": null,
"payment_method_status": null,
"updated": "2024-04-19T11:58:41.929Z"
}
/approve
and /reject
should hit after above step for the same payment which will mark payment as succeeded and failed by merchant.
Request curl :
curl --location --request POST 'http://localhost:8080/payments/{{payment_id}}/approve' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key:{{api_key}}' \
--data-raw '{}'
Response json :
{
"payment_id": "pay_9ZgUMXOiLv35wrF6oOyd",
"merchant_id": "merchant_1713525778",
"status": "succeeded",
"amount": 150000,
"net_amount": 150000,
"amount_capturable": 0,
"amount_received": 150000,
"connector": "stripe",
"client_secret": "pay_9ZgUMXOiLv35wrF6oOyd_secret_wU5hfAMsYjBe0fXepJmj",
"created": "2024-04-19T11:58:40.749Z",
"currency": "USD",
"customer_id": "StripeCustomer2",
"customer": {
"id": "StripeCustomer2",
"name": "Bob Smith",
"email": "[email protected]",
"phone": "999999999",
"phone_country_code": "+91"
},
"description": "Its my first payment request",
"refunds": null,
"disputes": null,
"mandate_id": null,
"mandate_data": null,
"setup_future_usage": null,
"off_session": null,
"capture_on": null,
"capture_method": "manual",
"payment_method": "card",
"payment_method_data": {
"card": {
"last4": "4242",
"card_type": null,
"card_network": null,
"card_issuer": null,
"card_issuing_country": null,
"card_isin": "424242",
"card_extended_bin": "42424242",
"card_exp_month": "10",
"card_exp_year": "25",
"card_holder_name": "joseph Doe",
"payment_checks": {
"cvc_check": "pass",
"address_line1_check": "pass",
"address_postal_code_check": "pass"
},
"authentication_data": null
},
"billing": null
},
"payment_token": null,
"shipping": {
"address": {
"city": "London",
"country": "GB",
"line1": "1467",
"line2": "31 Sherwood Gardens",
"line3": "31 Sherwood Gardens",
"zip": "E14 9wn",
"state": "Manchester",
"first_name": "Bob",
"last_name": "Smith"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"billing": {
"address": {
"city": "San Fransico",
"country": "US",
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"zip": "94122",
"state": "California",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"order_details": [
{
"brand": null,
"amount": 600,
"category": null,
"quantity": 2,
"product_id": null,
"product_name": "gillete creme",
"product_type": null,
"product_img_link": null,
"requires_shipping": null
},
{
"brand": null,
"amount": 300,
"category": null,
"quantity": 1,
"product_id": null,
"product_name": "gillete razor",
"product_type": null,
"product_img_link": null,
"requires_shipping": null
}
],
"email": "[email protected]",
"name": "Bob Smith",
"phone": "999999999",
"return_url": "https://google.com/",
"authentication_type": "no_three_ds",
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"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,
"ephemeral_key": null,
"manual_retry_allowed": false,
"connector_transaction_id": "pi_3P7G5VD5R7gDAGff0XEZqQkO",
"frm_message": {
"frm_name": "signifyd",
"frm_transaction_id": "pay_9ZgUMXOiLv35wrF6oOyd_1",
"frm_transaction_type": "post_frm",
"frm_status": "fraud",
"frm_score": 497,
"frm_reason": "SIGNIFYD_DECLINED",
"frm_error": null
},
"metadata": {
"udf1": "value1",
"login_date": "2019-09-10T10:11:12Z",
"new_customer": "true"
},
"connector_metadata": null,
"feature_metadata": null,
"reference_id": "pi_3P7G5VD5R7gDAGff0XEZqQkO",
"payment_link": null,
"profile_id": "pro_DOE6fPp88zY1M4xUyO0H",
"surcharge_details": null,
"attempt_count": 1,
"merchant_decision": "approved",
"merchant_connector_id": "mca_U0XVEDfTM0JMpiv9GOr5",
"incremental_authorization_allowed": null,
"authorization_count": null,
"incremental_authorizations": null,
"external_authentication_details": null,
"external_3ds_authentication_attempted": false,
"expires_on": "2024-04-19T12:13:40.749Z",
"fingerprint": null,
"browser_info": null,
"payment_method_id": null,
"payment_method_status": null,
"updated": "2024-04-19T12:01:50.944Z"
}
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
- [ ] I added a CHANGELOG entry if applicable