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(connector): [MIFINITY] Implement payment flows and Mifinity payment method #4592

Open
wants to merge 45 commits into
base: main
Choose a base branch
from

Conversation

swangi-kumari
Copy link
Contributor

@swangi-kumari swangi-kumari commented May 8, 2024

Type of Change

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

Description

  1. Implement Payments and PSync flows for Mifinity connector.
  2. Implement Mifinity Payment Method.

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?

  1. Create a Merchant
{
  "merchant_id": "merchant_{{$timestamp}}",
  "locker_id": "m0010",
  "merchant_name": "NewAge Retailer",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "JohnTest@test.com",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "JohnTest2@test.com",
    "secondary_phone": "cillum do dolor id",
    "website": "https://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"
    }
  },
  "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": {
    "city": "NY",
    "unit": "245"
  },
  "primary_business_details": [
    {
      "country": "US",
      "business": "food"
    }
  ]
}

  1. Create API Key
{
  "name": "API Key 1",
  "description": null,
  "expiration": "2038-01-19T03:14:08.000Z"
}

  1. Payment Connector Create
{
    "connector_type": "fiz_operations",
    "connector_name": "mifinity",
    "connector_account_details": {
        "auth_type": "HeaderKey",
        "api_key": "{______}"
    },
    "test_mode": false,
    "disabled": false,
    "payment_methods_enabled": [
                 {
            "payment_method": "wallet",
            "payment_method_types": [
                {
                    "payment_method_type": "mifinity",
                    "recurring_enabled": true,
                    "installment_payment_enabled": true
                }
            ]
        }
    ],
    "metadata": {
        "city": "NY",
        "unit": "245",
        "brand_id": "001"
    },
    "business_country": "US",
    "business_label": "food"
}

  1. Create a Mifinity Wallet Payment via Mifinity
    "amount": 1000,
    "currency": "EUR",
    "confirm": true,
      "capture_method": "automatic",
      "capture_on": "2022-09-10T10:11:12Z",
      "amount_to_capture": 1000,
    "customer_id": "StripeCustomer",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
      "authentication_type": "no_three_ds",
    "return_url": "https://www.google.com/",
    "payment_method": "wallet",
    "payment_method_type": "mifinity",
    "payment_method_data": {
        "wallet": {
            "mifinity": {
                "destination_account_number": "5001000001223369",
                "dob": "2001-10-16"
            }
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "DE",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "phone": {
            "number": "8056594427",
            "country_code": "+91"
        },
        "email": "swangi@gmail.com"
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "DE",
            "first_name": "joseph",
            "last_name": "Doe"
        },
        "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"
    }
    // "business_label": "food", 
    // "business_country": "US",
    // "profile_id": "pro_vsFxMOhOqXt4FMYjVral"
}

Response

  • Using redirection_url in response complete the payment
{
    "payment_id": "pay_QoGz4Pu98CFsMxtT5HWZ",
    "merchant_id": "merchant_1716269219",
    "status": "requires_customer_action",
    "amount": 1000,
    "net_amount": 1000,
    "amount_capturable": 1000,
    "amount_received": null,
    "connector": "mifinity",
    "client_secret": "pay_QoGz4Pu98CFsMxtT5HWZ_secret_2cbvKwEt2LffCDYhFIgQ",
    "created": "2024-05-21T05:43:12.607Z",
    "currency": "EUR",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "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": "automatic",
    "payment_method": "wallet",
    "payment_method_data": {
        "wallet": {},
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "DE",
            "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
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "DE",
            "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": "swangi@gmail.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://www.google.com/",
    "authentication_type": "no_three_ds",
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "next_action": {
        "type": "redirect_to_url",
        "redirect_to_url": "http://localhost:8080/payments/redirect/pay_QoGz4Pu98CFsMxtT5HWZ/merchant_1716269219/pay_QoGz4Pu98CFsMxtT5HWZ_1"
    },
    "cancellation_reason": null,
    "error_code": null,
    "error_message": null,
    "unified_code": null,
    "unified_message": null,
    "payment_experience": null,
    "payment_method_type": "mifinity",
    "connector_label": null,
    "business_country": null,
    "business_label": "default",
    "business_sub_label": null,
    "allowed_payment_method_types": null,
    "ephemeral_key": {
        "customer_id": "StripeCustomer",
        "created_at": 1716270192,
        "expires": 1716273792,
        "secret": "epk_91cebf4aba1241c0be573e5bd807b070"
    },
    "manual_retry_allowed": null,
    "connector_transaction_id": "pay_QoGz4Pu98CFsMxtT5HWZ_1",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_QoGz4Pu98CFsMxtT5HWZ_1",
    "payment_link": null,
    "profile_id": "pro_HqWDRNDr8bL3Wi7Z5wIN",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_RkJnWGLzO9fDEYXEP1cb",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-05-21T05:58:12.607Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2024-05-21T05:43:13.983Z",
    "frm_metadata": null
}

  1. do a PSync with same payment_id
  • Response
{
    "payment_id": "pay_QoGz4Pu98CFsMxtT5HWZ",
    "merchant_id": "merchant_1716269219",
    "status": "succeeded",
    "amount": 1000,
    "net_amount": 1000,
    "amount_capturable": 0,
    "amount_received": 1000,
    "connector": "mifinity",
    "client_secret": "pay_QoGz4Pu98CFsMxtT5HWZ_secret_2cbvKwEt2LffCDYhFIgQ",
    "created": "2024-05-21T05:43:12.607Z",
    "currency": "EUR",
    "customer_id": "StripeCustomer",
    "customer": {
        "id": "StripeCustomer",
        "name": "John Doe",
        "email": "guest@example.com",
        "phone": "999999999",
        "phone_country_code": "+1"
    },
    "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": "automatic",
    "payment_method": "wallet",
    "payment_method_data": {
        "wallet": {},
        "billing": null
    },
    "payment_token": null,
    "shipping": {
        "address": {
            "city": "San Fransico",
            "country": "DE",
            "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
    },
    "billing": {
        "address": {
            "city": "San Fransico",
            "country": "DE",
            "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": "swangi@gmail.com"
    },
    "order_details": null,
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "return_url": "https://www.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": "mifinity",
    "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": "aea08e48-68cb-44f3-8fca-c3c5c659700c",
    "frm_message": null,
    "metadata": {
        "udf1": "value1",
        "login_date": "2019-09-10T10:11:12Z",
        "new_customer": "true"
    },
    "connector_metadata": null,
    "feature_metadata": null,
    "reference_id": "pay_QoGz4Pu98CFsMxtT5HWZ_1",
    "payment_link": null,
    "profile_id": "pro_HqWDRNDr8bL3Wi7Z5wIN",
    "surcharge_details": null,
    "attempt_count": 1,
    "merchant_decision": null,
    "merchant_connector_id": "mca_RkJnWGLzO9fDEYXEP1cb",
    "incremental_authorization_allowed": null,
    "authorization_count": null,
    "incremental_authorizations": null,
    "external_authentication_details": null,
    "external_3ds_authentication_attempted": false,
    "expires_on": "2024-05-21T05:58:12.607Z",
    "fingerprint": null,
    "browser_info": null,
    "payment_method_id": null,
    "payment_method_status": null,
    "updated": "2024-05-21T05:47:52.254Z",
    "frm_metadata": null
}

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

@swangi-kumari swangi-kumari added A-connector-integration Area: Connector integration C-feature Category: Feature request or enhancement labels May 8, 2024
@swangi-kumari swangi-kumari self-assigned this May 8, 2024
@swangi-kumari swangi-kumari marked this pull request as ready for review May 21, 2024 07:31
@swangi-kumari swangi-kumari requested review from a team as code owners May 21, 2024 07:31
@@ -1949,6 +1949,10 @@ pub(crate) fn validate_auth_and_metadata_type(
klarna::transformers::KlarnaAuthType::try_from(val)?;
Ok(())
}
api_enums::Connector::Mifinity => {
mifinity::transformers::MifinityAuthType::try_from(val)?;
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
mifinity::transformers::MifinityAuthType::try_from(val)?;
mifinity::transformers::MifinityAuthType::try_from(val)?;
mifinity::transformers::MifinityConnectorMetadataObject::try_from(connector_meta_data)?;

#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MifinityPaymentStatus {
Successful,
#[default]
Copy link
Contributor

Choose a reason for hiding this comment

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

default should not be implemented for payment status

refund_status: enums::RefundStatus::from(item.response.status),
status: enums::AttemptStatus::from(status),
response: Ok(types::PaymentsResponseData::TransactionResponse {
resource_id: types::ResponseId::ConnectorTransactionId(transaction_reference),
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of collecting all the reference_id, just take the first one.

}
_ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()),
}
}
}

//TODO: Fill the struct with respective fields
Copy link
Contributor

Choose a reason for hiding this comment

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

remove todo's

dialing_code: String,
nationality: api_models::enums::CountryAlpha2,
email_address: Email,
dob: Secret<Date>,
}

#[derive(Default, Debug, Serialize, Eq, PartialEq)]
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need all the derives ?

#[schema(value_type = String)]
pub destination_account_number: Secret<String>,
#[schema(value_type = Date)]
pub dob: Secret<Date>,
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
pub dob: Secret<Date>,
pub date_of_birth: Secret<Date>,

#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct MifinityData {
pub destination_account_number: Secret<String>,
pub dob: Secret<Date>,
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
pub dob: Secret<Date>,
pub date_of_birth: Secret<Date>,

),
(
auth_headers::API_VERSION.to_string(),
"1".to_string().into(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Define this as a const in this file

Comment on lines 123 to 132
code: response
.errors
.iter()
.map(|error| error.error_code.clone())
.collect(),
message: response
.errors
.iter()
.map(|error| error.message.clone())
.collect(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do a join over the errors with a delimiter like , or &

Comment on lines 133 to 139
reason: Some(
response
.errors
.iter()
.map(|error| error.message.clone())
.collect(),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

Do a join here as well

Comment on lines 250 to 258
pub struct MifinityClientResponse {
first_name: Secret<String>,
last_name: Secret<String>,
phone: Secret<String>,
dialing_code: String,
nationality: String,
email_address: String,
dob: Secret<Date>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Where do we use this?

Comment on lines 283 to 287
let trace_id = payload.map(|payload| payload.trace_id.clone()).ok_or(
errors::ConnectorError::MissingRequiredField {
field_name: "trace_id",
},
)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

If trace_id is not present, we should not throw error, as we can recover using our payment_id, also trace_id is not optional, this is not the correct way of handling

@@ -124,109 +278,147 @@ impl<F, T>
types::PaymentsResponseData,
>,
) -> Result<Self, Self::Error> {
let payload = item.response.payload.first();
Copy link
Contributor

Choose a reason for hiding this comment

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

Throw error here if payload is not present, instead of using .map on top of it

Comment on lines 354 to 357
.map(|payload| payload.to_owned().status.clone())
.ok_or(errors::ConnectorError::MissingRequiredField {
field_name: "status",
})?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should not throw 4xx, this is not what its meant for. 4xx are in case of bad request that user made to our application, we shall only throw it if such scenario happened or connector gave 4xx, you cannot use 4xx to handle your coding scenarios

Copy link
Contributor

Choose a reason for hiding this comment

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

Everywhere this practice is being used and this is wrong, please correct the PR at all the applicable places

@@ -30,6 +30,9 @@ impl SerializableSecret for u16 {}
impl SerializableSecret for i8 {}
impl SerializableSecret for i32 {}

#[cfg(feature = "time")]
impl SerializableSecret for time::Date {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Get it reviewed from @SanchithHegde , along with cargo changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-connector-integration Area: Connector integration C-feature Category: Feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants