diff --git a/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js index 9c9e757598b3..3d002740e60e 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js @@ -1,6 +1,7 @@ import * as fixtures from "../../fixtures/imports"; import State from "../../utils/State"; import { payment_methods_enabled } from "../PaymentUtils/Commons"; +import * as utils from "../PaymentUtils/Utils"; let globalState; describe("Connector Account Create flow test", () => { @@ -14,7 +15,7 @@ describe("Connector Account Create flow test", () => { cy.task("setGlobalState", globalState.data); }); - it("connector-create-call-test", () => { + it("Create merchant connector account", () => { cy.createConnectorCallTest( "payment_processor", fixtures.createConnectorBody, @@ -23,31 +24,27 @@ describe("Connector Account Create flow test", () => { ); }); - it("check and create multiple connectors", () => { - const multiple_connectors = Cypress.env("MULTIPLE_CONNECTORS"); - // multiple_connectors will be undefined if not set in the env - if (multiple_connectors?.status) { - // Create multiple connectors based on the count - // The first connector is already created when creating merchant account, so start from 1 - for (let i = 1; i < multiple_connectors.count; i++) { - cy.createBusinessProfileTest( + // subsequent profile and mca ids should check for the existence of multiple connectors + context( + "Create another business profile and merchant connector account if MULTIPLE_CONNECTORS flag is true", + () => { + it("Create business profile", () => { + utils.createBusinessProfile( fixtures.businessProfile.bpCreate, globalState, - "profile" + i + { nextConnector: true } ); - cy.createConnectorCallTest( + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( "payment_processor", fixtures.createConnectorBody, - payment_methods_enabled, globalState, - `profile${i}`, - `merchantConnector${i}` + payment_methods_enabled, + { nextConnector: true } ); - } - } else { - cy.log( - "Multiple connectors not enabled. Skipping creation of multiple profiles and respective MCAs" - ); + }); } - }); + ); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTIDProxy.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTID.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTIDProxy.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js index 9dc73f6223e6..28262d025c4d 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js @@ -82,4 +82,23 @@ describe("Payment Methods Tests", () => { cy.setDefaultPaymentMethodTest(globalState); }); }); + + context("Delete payment method for customer", () => { + it("Create customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Method", () => { + const data = getConnectorDetails("commons")["card_pm"]["PaymentMethod"]; + cy.createPaymentMethodTest(globalState, data); + }); + + it("List PM for customer", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("Delete Payment Method for a customer", () => { + cy.deletePaymentMethodTest(globalState); + }); + }); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js similarity index 54% rename from cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js index c5fddfdd5bf1..db5757e464cb 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnostic.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js @@ -5,6 +5,32 @@ import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; let globalState; +/* +Flow: +- Create Business Profile with connector agnostic feature disabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer -- Empty list; i.e., no payment method should be listed +- Confirm Payment with PMID from previous step (should fail as Connector Mandate ID is not present in the newly created Profile) + + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer using Client Secret (will get PMID which is same as the one from previous step along with Payment Token) +- Confirm Payment with PMID from previous step (should pass as NTID is present in the DB) +*/ + describe("Connector Agnostic Tests", () => { before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -26,19 +52,19 @@ describe("Connector Agnostic Tests", () => { } }); - it("Create Business Profile", () => { - cy.createBusinessProfileTest( + it("Create business profile", () => { + utils.createBusinessProfile( fixtures.businessProfile.bpCreate, globalState ); }); - it("connector-create-call-test", () => { - cy.createConnectorCallTest( + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( "payment_processor", fixtures.createConnectorBody, - payment_methods_enabled, - globalState + globalState, + payment_methods_enabled ); }); @@ -78,24 +104,24 @@ describe("Connector Agnostic Tests", () => { cy.listCustomerPMByClientSecret(globalState); }); - it("Create Business Profile", () => { - cy.createBusinessProfileTest( + it("Create business profile", () => { + utils.createBusinessProfile( fixtures.businessProfile.bpCreate, globalState ); }); - it("connector-create-call-test", () => { - cy.createConnectorCallTest( + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( "payment_processor", fixtures.createConnectorBody, - payment_methods_enabled, - globalState + globalState, + payment_methods_enabled ); }); it("Enable Connector Agnostic for Business Profile", () => { - cy.UpdateBusinessProfileTest( + utils.updateBusinessProfile( fixtures.businessProfile.bpUpdate, true, // is_connector_agnostic_enabled false, // collect_billing_address_from_wallet_connector @@ -126,6 +152,80 @@ describe("Connector Agnostic Tests", () => { it("List Payment Method for Customer", () => { cy.listCustomerPMByClientSecret(globalState); }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["MITAutoCapture"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + newData, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + newData, + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); } ); @@ -138,19 +238,19 @@ describe("Connector Agnostic Tests", () => { } }); - it("Create Business Profile", () => { - cy.createBusinessProfileTest( + it("Create business profile", () => { + utils.createBusinessProfile( fixtures.businessProfile.bpCreate, globalState ); }); - it("connector-create-call-test", () => { - cy.createConnectorCallTest( + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( "payment_processor", fixtures.createConnectorBody, - payment_methods_enabled, - globalState + globalState, + payment_methods_enabled ); }); @@ -159,7 +259,7 @@ describe("Connector Agnostic Tests", () => { }); it("Enable Connector Agnostic for Business Profile", () => { - cy.UpdateBusinessProfileTest( + utils.updateBusinessProfile( fixtures.businessProfile.bpUpdate, true, // is_connector_agnostic_enabled false, // collect_billing_address_from_wallet_connector @@ -200,24 +300,24 @@ describe("Connector Agnostic Tests", () => { cy.listCustomerPMByClientSecret(globalState); }); - it("Create Business Profile", () => { - cy.createBusinessProfileTest( + it("Create business profile", () => { + utils.createBusinessProfile( fixtures.businessProfile.bpCreate, globalState ); }); - it("connector-create-call-test", () => { - cy.createConnectorCallTest( + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( "payment_processor", fixtures.createConnectorBody, - payment_methods_enabled, - globalState + globalState, + payment_methods_enabled ); }); it("Enable Connector Agnostic for Business Profile", () => { - cy.UpdateBusinessProfileTest( + utils.updateBusinessProfile( fixtures.businessProfile.bpUpdate, true, // is_connector_agnostic_enabled false, // collect_billing_address_from_wallet_connector @@ -247,5 +347,54 @@ describe("Connector Agnostic Tests", () => { it("List Payment Method for Customer", () => { cy.listCustomerPMByClientSecret(globalState); }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + data, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); }); }); diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js index 4ac15ec28bdc..56efbf1f6f21 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js @@ -242,7 +242,7 @@ export const connectorDetails = { }, }, }, - VoidAfterConfirm: { + VoidAfterConfirm: getCustomExchange({ Request: {}, Response: { status: 200, @@ -256,7 +256,7 @@ export const connectorDetails = { status: "cancelled", }, }, - }, + }), Refund: { Request: { currency: "USD", @@ -789,7 +789,7 @@ export const connectorDetails = { }, }, bank_redirect_pm: { - PaymentIntent: getCustomExchange({ + PaymentIntent: { Request: { currency: "EUR", }, @@ -799,7 +799,7 @@ export const connectorDetails = { status: "requires_payment_method", }, }, - }), + }, Ideal: { Request: { payment_method: "bank_redirect", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 86e41c076391..85718d8f9ba5 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -149,9 +149,7 @@ with `getCustomExchange`, if 501 response is expected, there is no need to pass // Const to get default PaymentExchange object const getDefaultExchange = () => ({ - Request: { - currency: "EUR", - }, + Request: {}, Response: { status: 501, body: { @@ -1012,6 +1010,16 @@ export const connectorDetails = { Request: { setup_future_usage: "off_session", }, + ResponseCustom: { + status: 400, + body: { + error: { + message: + "No eligible connector was found for the current payment method configuration", + type: "invalid_request", + }, + }, + }, }), SaveCardConfirmManualCaptureOffSession: getCustomExchange({ Request: { @@ -1465,6 +1473,25 @@ export const connectorDetails = { }, }, }), + MITAutoCapture: getCustomExchange({ + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + ResponseCustom: { + status: 400, + body: { + error: { + message: + "No eligible connector was found for the current payment method configuration", + type: "invalid_request", + }, + }, + }, + }), PaymentWithoutBilling: { Request: { currency: "USD", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js index 98d8e4c1acb4..b98973250e98 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js @@ -1,3 +1,8 @@ +import { + connectorDetails as commonConnectorDetails, + getCustomExchange, +} from "./Commons"; + const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", @@ -631,20 +636,14 @@ export const connectorDetails = { }, }, }, - MITAutoCapture: { + MITAutoCapture: getCustomExchange({ Configs: { CONNECTOR_CREDENTIAL: { value: "connector_1", }, }, - Request: {}, - Response: { - status: 200, - body: { - status: "succeeded", - }, - }, - }, + ...commonConnectorDetails.card_pm.MITAutoCapture, + }), MITManualCapture: { Configs: { CONNECTOR_CREDENTIAL: { diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js index d75fcf6877b9..b8dd275afae3 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js @@ -1,4 +1,8 @@ -import { cardRequiredField, getCustomExchange } from "./Commons"; +import { + cardRequiredField, + connectorDetails as commonConnectorDetails, + getCustomExchange, +} from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4242424242424242", @@ -118,6 +122,10 @@ const requiredFields = { }; export const connectorDetails = { + multi_credential_config: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, card_pm: { PaymentIntent: { Request: { @@ -134,6 +142,12 @@ export const connectorDetails = { }, }, PaymentIntentOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { currency: "USD", customer_acceptance: null, @@ -530,15 +544,15 @@ export const connectorDetails = { }, }, }, - MITAutoCapture: { - Request: {}, - Response: { - status: 200, - body: { - status: "succeeded", + MITAutoCapture: getCustomExchange({ + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", }, }, - }, + ...commonConnectorDetails.card_pm.MITAutoCapture, + }), MITManualCapture: { Request: {}, Response: { @@ -668,6 +682,12 @@ export const connectorDetails = { }, }, SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { payment_method: "card", payment_method_type: "debit", @@ -715,6 +735,12 @@ export const connectorDetails = { }, }, SaveCardConfirmAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { setup_future_usage: "off_session", }, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index 3b4c9e5decf0..72bd6451347a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -1,4 +1,4 @@ -import { validateConfig } from "../../utils/featureFlags.js"; +import { execConfig, validateConfig } from "../../utils/featureFlags.js"; import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as bankOfAmericaConnectorDetails } from "./BankOfAmerica.js"; @@ -65,8 +65,11 @@ export function getConnectorFlowDetails(connectorData, commonData, key) { } function mergeDetails(connectorId) { - const connectorData = getValueByKey(connectorDetails, connectorId); - const fallbackData = getValueByKey(connectorDetails, "commons"); + const connectorData = getValueByKey( + connectorDetails, + connectorId + ).authDetails; + const fallbackData = getValueByKey(connectorDetails, "commons").authDetails; // Merge data, prioritizing connectorData and filling missing data from fallbackData const mergedDetails = mergeConnectorDetails(connectorData, fallbackData); return mergedDetails; @@ -99,7 +102,16 @@ function mergeConnectorDetails(source, fallback) { return merged; } -export function getValueByKey(jsonObject, key) { +export function handleMultipleConnectors(keys) { + return { + MULTIPLE_CONNECTORS: { + status: true, + count: keys.length, + }, + }; +} + +export function getValueByKey(jsonObject, key, keyNumber = 0) { const data = typeof jsonObject === "string" ? JSON.parse(jsonObject) : jsonObject; @@ -108,7 +120,7 @@ export function getValueByKey(jsonObject, key) { if (typeof data[key].connector_account_details === "undefined") { const keys = Object.keys(data[key]); - for (let i = 0; i < keys.length; i++) { + for (let i = keyNumber; i < keys.length; i++) { const currentItem = data[key][keys[i]]; if ( @@ -117,20 +129,23 @@ export function getValueByKey(jsonObject, key) { "connector_account_details" ) ) { - Cypress.env("MULTIPLE_CONNECTORS", { - status: true, - count: keys.length, - }); - - return currentItem; + // Return state update instead of setting directly + return { + authDetails: currentItem, + stateUpdate: handleMultipleConnectors(keys), + }; } } } - - return data[key]; - } else { - return null; + return { + authDetails: data[key], + stateUpdate: null, + }; } + return { + authDetails: null, + stateUpdate: null, + }; } export const should_continue_further = (data) => { @@ -183,3 +198,114 @@ export function defaultErrorHandler(response, response_data) { } } } + +export function extractIntegerAtEnd(str) { + // Match one or more digits at the end of the string + const match = str.match(/(\d+)$/); + return match ? parseInt(match[0], 10) : 0; +} + +// Common helper function to check if operation should proceed +function shouldProceedWithOperation(multipleConnector, multipleConnectors) { + return !( + multipleConnector?.nextConnector === true && + (multipleConnectors?.status === false || + typeof multipleConnectors === "undefined") + ); +} + +// Helper to get connector configuration +function getConnectorConfig( + globalState, + multipleConnector = { nextConnector: false } +) { + const multipleConnectors = globalState.get("MULTIPLE_CONNECTORS"); + const mcaConfig = getConnectorDetails(globalState.get("connectorId")); + + return { + config: { + CONNECTOR_CREDENTIAL: + multipleConnector?.nextConnector && multipleConnectors?.status + ? multipleConnector + : mcaConfig?.multi_credential_config || multipleConnector, + }, + multipleConnectors, + }; +} + +// Simplified createBusinessProfile +export function createBusinessProfile( + createBusinessProfileBody, + globalState, + multipleConnector = { nextConnector: false } +) { + const { config, multipleConnectors } = getConnectorConfig( + globalState, + multipleConnector + ); + const { profilePrefix } = execConfig(config); + + if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + cy.createBusinessProfileTest( + createBusinessProfileBody, + globalState, + profilePrefix + ); + } +} + +// Simplified createMerchantConnectorAccount +export function createMerchantConnectorAccount( + paymentType, + createMerchantConnectorAccountBody, + globalState, + paymentMethodsEnabled, + multipleConnector = { nextConnector: false } +) { + const { config, multipleConnectors } = getConnectorConfig( + globalState, + multipleConnector + ); + const { profilePrefix, merchantConnectorPrefix } = execConfig(config); + + if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + cy.createConnectorCallTest( + paymentType, + createMerchantConnectorAccountBody, + paymentMethodsEnabled, + globalState, + profilePrefix, + merchantConnectorPrefix + ); + } +} + +export function updateBusinessProfile( + updateBusinessProfileBody, + is_connector_agnostic_enabled, + collect_billing_address_from_wallet_connector, + collect_shipping_address_from_wallet_connector, + always_collect_billing_address_from_wallet_connector, + always_collect_shipping_address_from_wallet_connector, + globalState +) { + const multipleConnectors = globalState.get("MULTIPLE_CONNECTORS"); + cy.log(`MULTIPLE_CONNECTORS: ${JSON.stringify(multipleConnectors)}`); + + // Get MCA config + const mcaConfig = getConnectorDetails(globalState.get("connectorId")); + const { profilePrefix } = execConfig({ + CONNECTOR_CREDENTIAL: mcaConfig?.multi_credential_config, + }); + + cy.UpdateBusinessProfileTest( + updateBusinessProfileBody, + is_connector_agnostic_enabled, + collect_billing_address_from_wallet_connector, + collect_shipping_address_from_wallet_connector, + always_collect_billing_address_from_wallet_connector, + always_collect_shipping_address_from_wallet_connector, + globalState, + profilePrefix + ); +} diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js index 557e4b23ee43..a58bbb22f81d 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js @@ -1,3 +1,5 @@ +import { getCustomExchange } from "./Commons"; + const billing = { address: { line1: "1467", @@ -195,7 +197,7 @@ export const connectorDetails = { }, }, }, - Void: { + Void: getCustomExchange({ Request: {}, Response: { status: 200, @@ -211,7 +213,7 @@ export const connectorDetails = { code: "IR_16", }, }, - }, + }), VoidAfterConfirm: { Request: {}, Response: { diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 28590abd8b6c..6135f26934e4 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -25,7 +25,11 @@ // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // commands.js or your custom support file -import { defaultErrorHandler, getValueByKey } from "../e2e/PaymentUtils/Utils"; +import { + defaultErrorHandler, + extractIntegerAtEnd, + getValueByKey, +} from "../e2e/PaymentUtils/Utils"; import { execConfig, validateConfig } from "../utils/featureFlags"; import * as RequestBodyUtils from "../utils/RequestBodyUtils"; import { handleRedirection } from "./redirectionHandler"; @@ -197,15 +201,15 @@ Cypress.Commands.add( Cypress.Commands.add( "createBusinessProfileTest", - (createBusinessProfile, globalState, profile_prefix = "profile") => { - const api_key = globalState.get("adminApiKey"); - const base_url = globalState.get("baseUrl"); - const connector_id = globalState.get("connectorId"); - const merchant_id = globalState.get("merchantId"); - const profile_name = `${connector_id}_${profile_prefix}_${Math.random().toString(36).substring(7)}`; - const url = `${base_url}/account/${merchant_id}/business_profile`; + (createBusinessProfile, globalState, profilePrefix = "profile") => { + const apiKey = globalState.get("adminApiKey"); + const baseUrl = globalState.get("baseUrl"); + const connectorId = globalState.get("connectorId"); + const merchantId = globalState.get("merchantId"); + const profileName = `${profilePrefix}_${RequestBodyUtils.generateRandomString(connectorId)}`; + const url = `${baseUrl}/account/${merchantId}/business_profile`; - createBusinessProfile.profile_name = profile_name; + createBusinessProfile.profile_name = profileName; cy.request({ method: "POST", @@ -213,13 +217,14 @@ Cypress.Commands.add( headers: { Accept: "application/json", "Content-Type": "application/json", - "api-key": api_key, + "api-key": apiKey, }, body: createBusinessProfile, failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - globalState.set(`${profile_prefix}Id`, response.body.profile_id); + + globalState.set(`${profilePrefix}Id`, response.body.profile_id); if (response.status === 200) { expect(response.body.profile_id).to.not.to.be.null; @@ -235,35 +240,39 @@ Cypress.Commands.add( Cypress.Commands.add( "UpdateBusinessProfileTest", ( - updateBusinessProfile, + updateBusinessProfileBody, is_connector_agnostic_mit_enabled, collect_billing_details_from_wallet_connector, collect_shipping_details_from_wallet_connector, always_collect_billing_details_from_wallet_connector, always_collect_shipping_details_from_wallet_connector, - globalState + globalState, + profilePrefix = "profile" ) => { - updateBusinessProfile.is_connector_agnostic_mit_enabled = + updateBusinessProfileBody.is_connector_agnostic_mit_enabled = is_connector_agnostic_mit_enabled; - updateBusinessProfile.collect_shipping_details_from_wallet_connector = + updateBusinessProfileBody.collect_shipping_details_from_wallet_connector = collect_shipping_details_from_wallet_connector; - updateBusinessProfile.collect_billing_details_from_wallet_connector = + updateBusinessProfileBody.collect_billing_details_from_wallet_connector = collect_billing_details_from_wallet_connector; - updateBusinessProfile.always_collect_billing_details_from_wallet_connector = + updateBusinessProfileBody.always_collect_billing_details_from_wallet_connector = always_collect_billing_details_from_wallet_connector; - updateBusinessProfile.always_collect_shipping_details_from_wallet_connector = + updateBusinessProfileBody.always_collect_shipping_details_from_wallet_connector = always_collect_shipping_details_from_wallet_connector; - const merchant_id = globalState.get("merchantId"); - const profile_id = globalState.get("profileId"); + + const apiKey = globalState.get("adminApiKey"); + const merchantId = globalState.get("merchantId"); + const profileId = globalState.get(`${profilePrefix}Id`); + cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/account/${merchant_id}/business_profile/${profile_id}`, + url: `${globalState.get("baseUrl")}/account/${merchantId}/business_profile/${profileId}`, headers: { Accept: "application/json", "Content-Type": "application/json", - "api-key": globalState.get("adminApiKey"), + "api-key": apiKey, }, - body: updateBusinessProfile, + body: updateBusinessProfileBody, failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); @@ -433,7 +442,7 @@ Cypress.Commands.add( // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - const authDetails = getValueByKey( + const { authDetails } = getValueByKey( JSON.stringify(jsonContent), connectorName ); @@ -481,14 +490,14 @@ Cypress.Commands.add( createConnectorBody, payment_methods_enabled, globalState, - profile_prefix = "profile", - mca_prefix = "merchantConnector" + profilePrefix = "profile", + mcaPrefix = "merchantConnector" ) => { const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); const connector_id = globalState.get("connectorId"); const merchant_id = globalState.get("merchantId"); - const profile_id = globalState.get(`${profile_prefix}Id`); + const profile_id = globalState.get(`${profilePrefix}Id`); const url = `${base_url}/account/${merchant_id}/connectors`; createConnectorBody.connector_type = connectorType; @@ -500,11 +509,20 @@ Cypress.Commands.add( // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - const authDetails = getValueByKey( + const { authDetails, stateUpdate } = getValueByKey( JSON.stringify(jsonContent), - connector_id + connector_id, + extractIntegerAtEnd(profilePrefix) ); + if (stateUpdate) { + // cy.task("setGlobalState", stateUpdate); + globalState.set( + "MULTIPLE_CONNECTORS", + stateUpdate.MULTIPLE_CONNECTORS + ); + } + createConnectorBody.connector_account_details = authDetails.connector_account_details; @@ -533,7 +551,7 @@ Cypress.Commands.add( response.body.connector_name ); globalState.set( - `${mca_prefix}Id`, + `${mcaPrefix}Id`, response.body.merchant_connector_id ); } else { @@ -566,7 +584,7 @@ Cypress.Commands.add( // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - const authDetails = getValueByKey( + const { authDetails } = getValueByKey( JSON.stringify(jsonContent), `${connectorName}_payout` ); @@ -678,9 +696,11 @@ Cypress.Commands.add( const connector_id = globalState.get("connectorId"); const merchant_id = globalState.get("merchantId"); const merchant_connector_id = globalState.get("merchantConnectorId"); + const connectorLabel = `updated_${RequestBodyUtils.generateRandomString(connector_id)}`; const url = `${base_url}/account/${merchant_id}/connectors/${merchant_connector_id}`; updateConnectorBody.connector_type = connectorType; + updateConnectorBody.connector_label = connectorLabel; cy.request({ method: "POST", @@ -700,7 +720,7 @@ Cypress.Commands.add( expect(response.body.merchant_connector_id).to.equal( merchant_connector_id ); - expect(response.body.connector_label).to.equal("updated_connector_label"); + expect(response.body.connector_label).to.equal(connectorLabel); }); } ); @@ -1037,8 +1057,8 @@ Cypress.Commands.add( ); } - const config_info = execConfig(validateConfig(configs)); - const profile_id = globalState.get(config_info.profile_id); + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { createPaymentBody[key] = reqData[key]; @@ -1245,14 +1265,18 @@ Cypress.Commands.add("createPaymentMethodTest", (globalState, data) => { }); }); -Cypress.Commands.add("deletePaymentMethodTest", (globalState, resData) => { - const payment_method_id = globalState.get("paymentMethodId"); +Cypress.Commands.add("deletePaymentMethodTest", (globalState) => { + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const paymentMethodId = globalState.get("paymentMethodId"); + const url = `${baseUrl}/payment_methods/${paymentMethodId}`; + cy.request({ method: "DELETE", - url: `${globalState.get("baseUrl")}/payment_methods/${payment_method_id}`, + url: url, headers: { Accept: "application/json", - "api-key": globalState.get("apiKey"), + "api-key": apiKey, }, failOnStatusCode: false, }).then((response) => { @@ -1260,10 +1284,16 @@ Cypress.Commands.add("deletePaymentMethodTest", (globalState, resData) => { expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { - expect(response.body.payment_method_id).to.equal(payment_method_id); + expect(response.body.payment_method_id).to.equal(paymentMethodId); expect(response.body.deleted).to.be.true; + } else if (response.status === 500 && baseUrl.includes("localhost")) { + // delete payment method api endpoint requires tartarus (hyperswitch card vault) to be set up since it makes a call to the locker service to delete the payment method + expect(response.body.error.code).to.include("HE_00"); + expect(response.body.error.message).to.include("Something went wrong"); } else { - defaultErrorHandler(response, resData); + throw new Error( + `Payment Method Delete Call Failed with error message: ${response.body.error.message}` + ); } }); }); @@ -1271,6 +1301,7 @@ Cypress.Commands.add("deletePaymentMethodTest", (globalState, resData) => { Cypress.Commands.add("setDefaultPaymentMethodTest", (globalState) => { const payment_method_id = globalState.get("paymentMethodId"); const customer_id = globalState.get("customerId"); + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/customers/${customer_id}/payment_methods/${payment_method_id}/default`, @@ -1306,10 +1337,10 @@ Cypress.Commands.add( const baseUrl = globalState.get("baseUrl"); const configInfo = execConfig(validateConfig(configs)); const merchantConnectorId = globalState.get( - configInfo.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); const paymentIntentID = globalState.get("paymentID"); - const profileId = globalState.get(configInfo.profile_id); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); const url = `${baseUrl}/payments/${paymentIntentID}/confirm`; confirmBody.client_secret = globalState.get("clientSecret"); @@ -1424,10 +1455,10 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const connectorId = globalState.get("connectorId"); const paymentIntentId = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { confirmBody[key] = reqData[key]; @@ -1532,9 +1563,9 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const paymentIntentID = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { confirmBody[key] = reqData[key]; @@ -1616,9 +1647,9 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const paymentId = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { confirmBody[key] = reqData[key]; @@ -1692,11 +1723,11 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const merchant_connector_id = globalState.get( - config_info.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); createConfirmPaymentBody.authentication_type = authentication_type; createConfirmPaymentBody.capture_method = capture_method; @@ -1811,12 +1842,12 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const merchant_connector_id = globalState.get( - config_info.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); const paymentIntentID = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); if (reqData.setup_future_usage === "on_session") { saveCardConfirmBody.card_cvc = reqData.payment_method_data.card.card_cvc; @@ -1920,9 +1951,9 @@ Cypress.Commands.add( (requestBody, data, amount_to_capture, globalState) => { const { Configs: configs = {}, Response: resData } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const payment_id = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); requestBody.amount_to_capture = amount_to_capture; requestBody.profile_id = profile_id; @@ -1955,9 +1986,9 @@ Cypress.Commands.add( Cypress.Commands.add("voidCallTest", (requestBody, data, globalState) => { const { Configs: configs = {}, Response: resData } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const payment_id = globalState.get("paymentID"); - const profile_id = globalState.get(config_info.profile_id); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); requestBody.profile_id = profile_id; @@ -1989,9 +2020,9 @@ Cypress.Commands.add( (globalState, data, autoretries = false, attempt = 1) => { const { Configs: configs = {} } = data || {}; - const config_info = execConfig(validateConfig(configs)); + const configInfo = execConfig(validateConfig(configs)); const merchant_connector_id = globalState.get( - config_info.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); const payment_id = globalState.get("paymentID"); @@ -2138,10 +2169,10 @@ Cypress.Commands.add( Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); - const profile_id = globalState.get(config_info.profile_id); + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); const merchant_connector_id = globalState.get( - config_info.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); for (const key in reqData) { @@ -2261,15 +2292,15 @@ Cypress.Commands.add( Request: reqData, Response: resData, } = data || {}; - const config_info = execConfig(validateConfig(configs)); - const profile_id = globalState.get(config_info.profile_id); + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { requestBody[key] = reqData[key]; } const merchant_connector_id = globalState.get( - config_info.merchant_connector_id + `${configInfo.merchantConnectorPrefix}Id` ); requestBody.amount = amount; @@ -2375,34 +2406,54 @@ Cypress.Commands.add( Response: resData, } = data || {}; + const configInfo = execConfig(validateConfig(configs)); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); + + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const customerId = globalState.get("customerId"); + const paymentMethodId = globalState.get("paymentMethodId"); + const url = `${baseUrl}/payments`; + for (const key in reqData) { requestBody[key] = reqData[key]; } - const configInfo = execConfig(validateConfig(configs)); - const profileId = globalState.get(configInfo.profile_id); - requestBody.amount = amount; requestBody.capture_method = capture_method; requestBody.confirm = confirm; - requestBody.customer_id = globalState.get("customerId"); + requestBody.customer_id = customerId; requestBody.profile_id = profileId; - requestBody.recurring_details.data = globalState.get("paymentMethodId"); + requestBody.recurring_details.data = paymentMethodId; cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/payments`, + url: url, headers: { "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": apiKey, }, failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { globalState.set("paymentID", response.body.payment_id); + + expect(response.body.payment_method_id, "payment_method_id").to.include( + "pm_" + ).and.to.not.be.null; + expect( + response.body.connector_transaction_id, + "connector_transaction_id" + ).to.not.be.null; + expect( + response.body.payment_method_status, + "payment_method_status" + ).to.equal("active"); + if (response.body.capture_method === "automatic") { if (response.body.authentication_type === "three_ds") { expect(response.body) @@ -2462,7 +2513,7 @@ Cypress.Commands.add( Response: resData, } = data || {}; const configInfo = execConfig(validateConfig(configs)); - const profileId = globalState.get(configInfo.profile_id); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); for (const key in reqData) { requestBody[key] = reqData[key]; @@ -2710,6 +2761,7 @@ Cypress.Commands.add("listCustomerPMCallTest", (globalState) => { Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { const clientSecret = globalState.get("clientSecret"); + cy.request({ method: "GET", url: `${globalState.get("baseUrl")}/customers/payment_methods?client_secret=${clientSecret}`, diff --git a/cypress-tests/cypress/utils/RequestBodyUtils.js b/cypress-tests/cypress/utils/RequestBodyUtils.js index 69edff05ca75..9b9015cad83d 100644 --- a/cypress-tests/cypress/utils/RequestBodyUtils.js +++ b/cypress-tests/cypress/utils/RequestBodyUtils.js @@ -10,7 +10,7 @@ export const setApiKey = (requestBody, apiKey) => { requestBody["connector_account_details"]["api_key"] = apiKey; }; -export const generateRandomString = (prefix = "cypress_merchant_GHAction_") => { +export const generateRandomString = (prefix = "cyMerchant") => { const uuidPart = "xxxxxxxx"; const randomString = uuidPart.replace(/[xy]/g, function (c) { @@ -19,7 +19,7 @@ export const generateRandomString = (prefix = "cypress_merchant_GHAction_") => { return v.toString(16); }); - return prefix + randomString; + return `${prefix}_${randomString}`; }; export const setMerchantId = (merchantCreateBody, merchantId) => { diff --git a/cypress-tests/cypress/utils/featureFlags.js b/cypress-tests/cypress/utils/featureFlags.js index 7140aee3af7e..6d8599820f7e 100644 --- a/cypress-tests/cypress/utils/featureFlags.js +++ b/cypress-tests/cypress/utils/featureFlags.js @@ -2,14 +2,6 @@ const config_fields = ["CONNECTOR_CREDENTIAL", "DELAY", "TRIGGER_SKIP"]; const DEFAULT_CONNECTOR = "connector_1"; -const DEFAULT_CREDENTIALS = { - profile_id: "profileId", - merchant_connector_id: "merchantConnectorId", -}; -const CONNECTOR_2_CREDENTIALS = { - profile_id: "profile1Id", - merchant_connector_id: "merchantConnector1Id", -}; // Helper function for type and range validation function validateType(value, type) { @@ -53,6 +45,23 @@ function validateConfigValue(key, value) { console.error("CONNECTOR_CREDENTIAL must be an object."); return false; } + // Validate nextConnector and multipleConnectors if present + if ( + value?.nextConnector !== undefined && + typeof value.nextConnector !== "boolean" + ) { + console.error("nextConnector must be a boolean"); + return false; + } + + if ( + value?.multipleConnectors && + typeof value.multipleConnectors.status !== "boolean" + ) { + console.error("multipleConnectors.status must be a boolean"); + return false; + } + // Validate structure if ( !value.value || @@ -103,54 +112,78 @@ export function validateConfig(configObject) { return configObject; } -export function execConfig(configs) { - // Handle delay if present - if (configs?.DELAY?.STATUS) { - cy.wait(configs.DELAY.TIMEOUT); +export function getProfileAndConnectorId(connectorType) { + const credentials = { + connector_1: { + profileId: "profile", + connectorId: "merchantConnector", + }, + connector_2: { + profileId: "profile1", + connectorId: "merchantConnector1", + }, + }; + + return credentials[connectorType] || credentials.connector_1; +} + +function getSpecName() { + return Cypress.spec.name.toLowerCase() === "__all" + ? String( + Cypress.mocha.getRunner().suite.ctx.test.invocationDetails.relativeFile + ) + .split("/") + .pop() + .toLowerCase() + : Cypress.spec.name.toLowerCase(); +} + +function matchesSpecName(specName) { + if (!specName || !Array.isArray(specName) || specName.length === 0) { + return false; } + + const currentSpec = getSpecName(); + return specName.some( + (name) => name && currentSpec.includes(name.toLowerCase()) + ); +} + +export function determineConnectorConfig(connectorConfig) { + // Case 1: Multiple connectors configuration if ( - typeof configs?.CONNECTOR_CREDENTIAL === "undefined" || - configs?.CONNECTOR_CREDENTIAL.value === "null" + connectorConfig?.nextConnector && + connectorConfig?.multipleConnectors?.status ) { - return DEFAULT_CREDENTIALS; + return "connector_2"; } - // Get connector configuration - const connectorType = determineConnectorConfig(configs.CONNECTOR_CREDENTIAL); - - // Return credentials based on connector type - return connectorType === "connector_2" - ? CONNECTOR_2_CREDENTIALS - : DEFAULT_CREDENTIALS; -} - -function determineConnectorConfig(connectorConfig) { - // Return default if config is undefined or null + // Case 2: Invalid or null configuration if (!connectorConfig || connectorConfig.value === "null") { return DEFAULT_CONNECTOR; } - const { specName = null, value } = connectorConfig; + const { specName, value } = connectorConfig; - // If value is not provided, return default - if (!value) { - return DEFAULT_CONNECTOR; - } - - // If no specName or not an array, return value directly - if (!specName || !Array.isArray(specName) || specName.length === 0) { + // Case 3: No spec name matching needed + if (!specName) { return value; } - // Check if current spec matches any in specName - const currentSpec = Cypress.spec.name.toLowerCase(); - try { - const matchesSpec = specName.some( - (name) => name && currentSpec.includes(name.toLowerCase()) - ); - return matchesSpec ? value : DEFAULT_CONNECTOR; - } catch (error) { - console.error("Error matching spec names:", error); - return DEFAULT_CONNECTOR; + // Case 4: Match spec name and return appropriate connector + return matchesSpecName(specName) ? value : DEFAULT_CONNECTOR; +} + +export function execConfig(configs) { + if (configs?.DELAY?.STATUS) { + cy.wait(configs.DELAY.TIMEOUT); } + + const connectorType = determineConnectorConfig(configs?.CONNECTOR_CREDENTIAL); + const { profileId, connectorId } = getProfileAndConnectorId(connectorType); + + return { + profilePrefix: profileId, + merchantConnectorPrefix: connectorId, + }; }