diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index 363ca6d922..160d15e60d 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -655,6 +655,7 @@ jobs: env: CYPRESS_BASEURL: "http://localhost:8080" ROUTER__SERVER__WORKERS: 4 + PAYMENTS_CONNECTORS: ${{ env.PAYMENTS_CONNECTORS }} shell: bash -leuo pipefail {0} continue-on-error: true # We aren't specifying `command` and `jobs` arguments currently diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js index 3f09a83e80..7090f5a140 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js @@ -5,7 +5,7 @@ import { getCustomExchange } from "./_Reusable"; const successfulNo3DSCardDetails = { card_number: "4111111111111111", card_exp_month: "08", - card_exp_year: "25", + card_exp_year: "28", card_holder_name: "joseph Doe", card_cvc: "999", }; @@ -13,7 +13,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4111111111111111", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "28", card_holder_name: "morino", card_cvc: "999", }; @@ -52,6 +52,36 @@ const multiUseMandateData = { }, }; +const billingAddress = { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "US", + first_name: "joseph", + last_name: "Doe", + }, + email: "example@example.com", +}; + +const shippingAddress = { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "US", + first_name: "joseph", + last_name: "Doe", + }, + email: "example@example.com", +}; + export const payment_methods_enabled = [ { payment_method_type: "bank_debit", @@ -368,19 +398,7 @@ export const connectorDetails = { pix: {}, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "BR", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, currency: "BRL", }, }), @@ -409,19 +427,7 @@ export const connectorDetails = { }, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "NL", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, }, }), Giropay: getCustomExchange({ @@ -439,19 +445,7 @@ export const connectorDetails = { }, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "DE", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, }, }), Sofort: getCustomExchange({ @@ -466,19 +460,7 @@ export const connectorDetails = { }, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "DE", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, }, }), Eps: getCustomExchange({ @@ -492,19 +474,7 @@ export const connectorDetails = { }, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "AT", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, }, }), Przelewy24: getCustomExchange({ @@ -545,26 +515,19 @@ export const connectorDetails = { }, }, }, - billing: { - address: { - line1: "1467", - line2: "Harrison Street", - line3: "Harrison Street", - city: "San Fransico", - state: "California", - zip: "94122", - country: "PL", - first_name: "john", - last_name: "doe", - }, - }, + billing: billingAddress, }, }), }, card_pm: { PaymentIntent: getCustomExchange({ Request: { - currency: "USD", + amount_details: { + order_amount: 1001, + currency: "USD", + }, + billing: billingAddress, + shipping: shippingAddress, }, Response: { status: 200, @@ -573,6 +536,7 @@ export const connectorDetails = { }, }, }), + PaymentIntentOffSession: getCustomExchange({ Request: { currency: "USD", @@ -583,7 +547,7 @@ export const connectorDetails = { status: "requires_payment_method", }, }, - }), + }), "3DSManualCapture": getCustomExchange({ Request: { payment_method: "card", @@ -608,24 +572,36 @@ export const connectorDetails = { }), No3DSManualCapture: getCustomExchange({ Request: { - payment_method: "card", payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", + payment_method_type: "card", + payment_method_subtype: "credit", customer_acceptance: null, - setup_future_usage: "on_session", + + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, }, }), No3DSAutoCapture: getCustomExchange({ Request: { - payment_method: "card", payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", + payment_method_type: "card", + payment_method_subtype: "credit", customer_acceptance: null, - setup_future_usage: "on_session", + shipping: shippingAddress, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, }, }), Capture: getCustomExchange({ @@ -634,7 +610,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, }, }), @@ -659,8 +634,28 @@ export const connectorDetails = { error: { type: "invalid_request", message: - "You cannot cancel this payment because it has status succeeded", - code: "IR_16", + "This Payment could not be PaymentsCancel because it has a status of requires_payment_method. The expected state is requires_capture, partially_captured_and_capturable, partially_authorized_and_requires_capture", + code: "IR_14", + }, + }, + }, + }), + VoidAfterConfirm: getCustomExchange({ + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + ResponseCustom: { + status: 400, + body: { + error: { + type: "invalid_request", + message: + "This Payment could not be PaymentsCancel because it has a status of succeeded. The expected state is requires_capture, partially_captured_and_capturable, partially_authorized_and_requires_capture", + code: "IR_14", }, }, }, @@ -671,7 +666,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, }, ResponseCustom: { @@ -970,7 +964,7 @@ export const connectorDetails = { error: { error_type: "invalid_request", message: "Json deserialize error: invalid card number length", - code: "IR_06" + code: "IR_06", }, }, }, @@ -1077,8 +1071,9 @@ export const connectorDetails = { body: { error: { error_type: "invalid_request", - message: "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", - code: "IR_06" + message: + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", + code: "IR_06", }, }, }, @@ -1105,8 +1100,9 @@ export const connectorDetails = { body: { error: { error_type: "invalid_request", - message: "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", - code: "IR_06" + message: + "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + code: "IR_06", }, }, }, @@ -1132,8 +1128,9 @@ export const connectorDetails = { body: { error: { error_type: "invalid_request", - message: "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`, `mobile_payment`", - code: "IR_06" + message: + "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`, `mobile_payment`", + code: "IR_06", }, }, }, @@ -1202,7 +1199,8 @@ export const connectorDetails = { body: { error: { type: "invalid_request", - message: "A payment token or payment method data or ctp service details is required", + message: + "A payment token or payment method data or ctp service details is required", code: "IR_06", }, }, diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js b/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js index 569b557b69..c21a3c6a65 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js @@ -5,7 +5,7 @@ const connectorDetails = { }; export default function getConnectorDetails(connectorId) { - let x = mergeDetails(connectorId); + const x = mergeDetails(connectorId); return x; } @@ -61,9 +61,9 @@ export const should_continue_further = (res_data) => { } if ( - res_data.body.error !== undefined || - res_data.body.error_code !== undefined || - res_data.body.error_message !== undefined + res_data.Response.body.error !== undefined || + res_data.Response.body.error_code !== undefined || + res_data.Response.body.error_message !== undefined ) { return false; } else { @@ -89,9 +89,12 @@ export function defaultErrorHandler(response, response_data) { if (typeof response.body.error === "object") { for (const key in response_data.body.error) { // Check if the error message is a Json deserialize error - let apiResponseContent = response.body.error[key]; - let expectedContent = response_data.body.error[key]; - if (typeof apiResponseContent === "string" && apiResponseContent.includes("Json deserialize error")) { + const apiResponseContent = response.body.error[key]; + const expectedContent = response_data.body.error[key]; + if ( + typeof apiResponseContent === "string" && + apiResponseContent.includes("Json deserialize error") + ) { expect(apiResponseContent).to.include(expectedContent); } else { expect(apiResponseContent).to.equal(expectedContent); diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js b/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js index 353d96260b..d24e96cfdf 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js @@ -46,9 +46,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: { @@ -62,9 +60,7 @@ const getDefaultExchange = () => ({ }); const getUnsupportedExchange = () => ({ - Request: { - currency: "EUR", - }, + Request: {}, Response: { status: 400, body: { diff --git a/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js b/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js index 21766617c0..06afc506bc 100644 --- a/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js +++ b/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js @@ -40,12 +40,11 @@ describe("[Payment] [No 3DS] [Payment Method: Card]", () => { let req_data = data["Request"]; let res_data = data["Response"]; cy.paymentIntentCreateCall( + globalState, fixtures.createPaymentBody, - req_data, res_data, "no_three_ds", - "automatic", - globalState + "automatic" ); }); @@ -59,7 +58,7 @@ describe("[Payment] [No 3DS] [Payment Method: Card]", () => { ]; let req_data = data["Request"]; let res_data = data["Response"]; - cy.paymentIntentConfirmCall( + cy.paymentConfirmCall( fixtures.confirmBody, req_data, res_data, @@ -97,12 +96,11 @@ describe("[Payment] [No 3DS] [Payment Method: Card]", () => { let req_data = data["Request"]; let res_data = data["Response"]; cy.paymentIntentCreateCall( + globalState, fixtures.createPaymentBody, - req_data, res_data, "no_three_ds", - "automatic", - globalState + "automatic" ); }); @@ -116,7 +114,7 @@ describe("[Payment] [No 3DS] [Payment Method: Card]", () => { ]; let req_data = data["Request"]; let res_data = data["Response"]; - cy.paymentIntentConfirmCall( + cy.paymentConfirmCall( fixtures.confirmBody, req_data, res_data, diff --git a/cypress-tests-v2/cypress/e2e/spec/Payment/0003-VoidPayments.cy.js b/cypress-tests-v2/cypress/e2e/spec/Payment/0003-VoidPayments.cy.js new file mode 100644 index 0000000000..76fb97d964 --- /dev/null +++ b/cypress-tests-v2/cypress/e2e/spec/Payment/0003-VoidPayments.cy.js @@ -0,0 +1,207 @@ +/* +V2 Void/Cancel Payment Tests +Test scenarios: +1. Void payment in requires_capture state +2. Void payment in requires_payment_method state +3. Void payment in succeeded state (should fail) +*/ + +import * as fixtures from "../../../fixtures/imports"; +import State from "../../../utils/State"; +import getConnectorDetails, { + should_continue_further, +} from "../../configs/Payment/Utils"; + +let globalState; + +describe("[Payment] [Void/Cancel] [Payment Method: Card]", () => { + context("[Payment] [Void] [Requires Capture State]", () => { + let should_continue = true; + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + const req_data = data["Request"]; + const res_data = data["Response"]; + + cy.paymentIntentCreateCall( + globalState, + req_data, + res_data, + "no_three_ds", + "manual" + ); + + if (should_continue) should_continue = should_continue_further(data); + }); + + it("Confirm payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + const req_data = data["Request"]; + + cy.paymentConfirmCall(globalState, req_data, data); + + if (should_continue) should_continue = should_continue_further(data); + }); + + it("Void payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["VoidAfterConfirm"]; + + cy.paymentVoidCall(globalState, fixtures.void_payment_body, data); + + if (should_continue) should_continue = should_continue_further(data); + }); + }); + + context( + "[Payment] [Void] [Requires Payment Method State - Should Fail]", + () => { + let should_continue = true; + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + const req_data = data["Request"]; + const res_data = data["Response"]; + + cy.paymentIntentCreateCall( + globalState, + req_data, + res_data, + "no_three_ds", + "manual" + ); + + if (should_continue) should_continue = should_continue_further(data); + }); + + it("Void payment intent - should fail", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Void"]; + + // Use the ResponseCustom which contains the error response + const void_data = { + ...data, + Response: data.ResponseCustom, + }; + + cy.paymentVoidCall( + globalState, + fixtures.void_payment_body, + void_data + ); + + if (should_continue) should_continue = should_continue_further(data); + }); + } + ); + + context("[Payment] [Void] [Succeeded State - Should Fail]", () => { + let should_continue = true; + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + const req_data = data["Request"]; + const res_data = data["Response"]; + + cy.paymentIntentCreateCall( + globalState, + req_data, + res_data, + "no_three_ds", + "automatic" + ); + + if (should_continue) should_continue = should_continue_further(data); + }); + + it("Confirm payment intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + const req_data = data["Request"]; + + cy.paymentConfirmCall(globalState, req_data, data); + + if (should_continue) should_continue = should_continue_further(data); + }); + + it("Void payment intent - should fail", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["VoidAfterConfirm"]; + + // Use the ResponseCustom which contains the error response + const void_data = { + ...data, + Response: data.ResponseCustom, + }; + + cy.paymentVoidCall( + globalState, + fixtures.void_payment_body, + void_data + ); + + if (should_continue) should_continue = should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests-v2/cypress/fixtures/imports.js b/cypress-tests-v2/cypress/fixtures/imports.js index 30f0dfb50c..4b3f8c4b89 100644 --- a/cypress-tests-v2/cypress/fixtures/imports.js +++ b/cypress-tests-v2/cypress/fixtures/imports.js @@ -4,6 +4,7 @@ import merchant_account_body from "./merchant_account.json"; import merchant_connector_account_body from "./merchant_connector_account.json"; import organization_body from "./organization.json"; import routing_body from "./routing.json"; +import void_payment_body from "./void_payment.json"; export { api_key_body, @@ -12,4 +13,5 @@ export { merchant_connector_account_body, organization_body, routing_body, + void_payment_body, }; diff --git a/cypress-tests-v2/cypress/fixtures/void_payment.json b/cypress-tests-v2/cypress/fixtures/void_payment.json new file mode 100644 index 0000000000..471d90d6c4 --- /dev/null +++ b/cypress-tests-v2/cypress/fixtures/void_payment.json @@ -0,0 +1,3 @@ +{ + "cancellation_reason": "requested_by_customer" +} diff --git a/cypress-tests-v2/cypress/support/commands.js b/cypress-tests-v2/cypress/support/commands.js index e77a0f6514..7afa35152c 100644 --- a/cypress-tests-v2/cypress/support/commands.js +++ b/cypress-tests-v2/cypress/support/commands.js @@ -27,7 +27,10 @@ // cy.task can only be used in support files (spec files or commands file) import { nanoid } from "nanoid"; -import { getValueByKey } from "../e2e/configs/Payment/Utils.js"; +import { + defaultErrorHandler, + getValueByKey, +} from "../e2e/configs/Payment/Utils.js"; import { isoTimeTomorrow, validateEnv } from "../utils/RequestBodyUtils.js"; function logRequestId(xRequestId) { @@ -55,7 +58,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, }, body: organizationCreateBody, failOnStatusCode: false, @@ -91,7 +94,7 @@ Cypress.Commands.add("organizationRetrieveCall", (globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, }, failOnStatusCode: false, }).then((response) => { @@ -135,7 +138,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, }, body: organizationUpdateBody, failOnStatusCode: false, @@ -185,7 +188,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "X-Organization-Id": organization_id, }, body: merchantAccountCreateBody, @@ -230,7 +233,7 @@ Cypress.Commands.add("merchantAccountRetrieveCall", (globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, }, failOnStatusCode: false, }).then((response) => { @@ -274,7 +277,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, }, body: merchantAccountUpdateBody, failOnStatusCode: false, @@ -324,7 +327,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "x-merchant-id": merchant_id, ...customHeaders, }, @@ -369,7 +372,7 @@ Cypress.Commands.add("businessProfileRetrieveCall", (globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, failOnStatusCode: false, @@ -411,7 +414,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, body: businessProfileUpdateBody, @@ -503,7 +506,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, body: mcaCreateBody, @@ -551,7 +554,7 @@ Cypress.Commands.add("mcaRetrieveCall", (globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, failOnStatusCode: false, @@ -611,7 +614,7 @@ Cypress.Commands.add( url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, body: mcaUpdateBody, @@ -671,7 +674,7 @@ Cypress.Commands.add("apiKeyCreateCall", (apiKeyCreateBody, globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, body: apiKeyCreateBody, @@ -718,7 +721,7 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, failOnStatusCode: false, @@ -768,7 +771,7 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { url: url, headers: { "Content-Type": "application/json", - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, ...customHeaders, }, body: apiKeyUpdateBody, @@ -1176,7 +1179,7 @@ Cypress.Commands.add("merchantAccountsListCall", (globalState) => { method: "GET", url: url, headers: { - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "Content-Type": "application/json", }, failOnStatusCode: false, @@ -1218,7 +1221,7 @@ Cypress.Commands.add("businessProfilesListCall", (globalState) => { method: "GET", url: url, headers: { - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "Content-Type": "application/json", ...customHeaders, }, @@ -1261,7 +1264,7 @@ Cypress.Commands.add("mcaListCall", (globalState, service_type) => { method: "GET", url: url, headers: { - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "Content-Type": "application/json", ...customHeaders, }, @@ -1323,7 +1326,7 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { method: "GET", url: url, headers: { - "Authorization": `admin-api-key=${api_key}`, + Authorization: `admin-api-key=${api_key}`, "Content-Type": "application/json", ...customHeaders, }, @@ -1354,23 +1357,23 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { // Payment API calls // Update the below commands while following the conventions // Below is an example of how the payment intent create call should look like (update the below command as per the need) + Cypress.Commands.add( - "paymentIntentCreateCall", - ( - globalState, - paymentRequestBody, - paymentResponseBody - /* Add more variables based on the need*/ - ) => { + "paymentVoidCall", + (globalState, voidRequestBody, data) => { + const { Request: reqData = {}, Response: resData } = data || {}; + // Define the necessary variables and constants at the top - // Also construct the URL here const api_key = globalState.get("apiKey"); const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/payments/create-intent`; + const payment_id = globalState.get("paymentID"); + const url = `${base_url}/v2/payments/${payment_id}/cancel`; - // Update request body if needed - paymentRequestBody = {}; + // Apply connector-specific request data (including cancellation_reason) + for (const key in reqData) { + voidRequestBody[key] = reqData[key]; + } // Pass Custom Headers const customHeaders = { @@ -1381,7 +1384,59 @@ Cypress.Commands.add( method: "POST", url: url, headers: { - "api-key": api_key, + Authorization: `api-key=${api_key}`, + "Content-Type": "application/json", + ...customHeaders, + }, + body: voidRequestBody, + failOnStatusCode: false, + }).then((response) => { + // Logging x-request-id is mandatory + logRequestId(response.headers["x-request-id"]); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); + } + }); + }); + } +); +Cypress.Commands.add( + "paymentIntentCreateCall", + ( + globalState, + paymentRequestBody, + paymentResponseBody, + authentication_type, + capture_method + ) => { + // Define the necessary variables and constants at the top + // Also construct the URL here + const api_key = globalState.get("apiKey"); + const base_url = globalState.get("baseUrl"); + const profile_id = globalState.get("profileId"); + const url = `${base_url}/v2/payments/create-intent`; + + // Set capture_method and authentication_type as parameters (like V1) + paymentRequestBody.authentication_type = authentication_type; + paymentRequestBody.capture_method = capture_method; + + // Pass Custom Headers + const customHeaders = { + "x-profile-id": profile_id, + }; + + cy.request({ + method: "POST", + url: url, + headers: { + Authorization: `api-key=${api_key}`, "Content-Type": "application/json", ...customHeaders, }, @@ -1391,28 +1446,113 @@ Cypress.Commands.add( // Logging x-request-id is mandatory logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - // Update the assertions based on the need - expect(response.body).to.deep.equal(paymentResponseBody); - } else if (response.status === 400) { - // Add 4xx validations here - expect(response.body).to.deep.equal(paymentResponseBody); - } else if (response.status === 500) { - // Add 5xx validations here - expect(response.body).to.deep.equal(paymentResponseBody); - } else { - // If status code is other than the ones mentioned above, default should be thrown - throw new Error( - `Payment intent create call failed with status ${response.status} and message: "${response.body.error.message}"` - ); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + // Validate the payment create response - V2 uses different ID format + expect(response.body).to.have.property("id").and.to.be.a("string").and + .not.be.empty; + expect(response.body).to.have.property("status"); + + // Store the payment ID for future use + globalState.set("paymentID", response.body.id); + + // Log payment creation success + cy.task( + "cli_log", + `Payment created with ID: ${response.body.id}, Status: ${response.body.status}` + ); + + if (paymentResponseBody && paymentResponseBody.body) { + for (const key in paymentResponseBody.body) { + if (paymentResponseBody.body[key] !== null) { + expect(response.body[key]).to.equal( + paymentResponseBody.body[key] + ); + } + } + } + } else { + defaultErrorHandler(response, paymentResponseBody); + } + }); }); } ); -Cypress.Commands.add("paymentIntentConfirmCall", (globalState) => {}); -Cypress.Commands.add("paymentIntentRetrieveCall", (globalState) => {}); +Cypress.Commands.add( + "paymentConfirmCall", + (globalState, paymentConfirmRequestBody, data) => { + const { Request: reqData = {}, Response: resData } = data || {}; -// templates for future use -Cypress.Commands.add("", () => { - cy.request({}).then((response) => {}); -}); + // Define the necessary variables and constants at the top + const api_key = globalState.get("apiKey"); + const base_url = globalState.get("baseUrl"); + const profile_id = globalState.get("profileId"); + const payment_id = globalState.get("paymentID"); + const url = `${base_url}/v2/payments/${payment_id}/confirm-intent`; + + // Apply connector-specific request data + for (const key in reqData) { + paymentConfirmRequestBody[key] = reqData[key]; + } + + // Pass Custom Headers + const customHeaders = { + "x-profile-id": profile_id, + }; + + cy.request({ + method: "POST", + url: url, + headers: { + Authorization: `api-key=${api_key}`, + "Content-Type": "application/json", + ...customHeaders, + }, + body: paymentConfirmRequestBody, + failOnStatusCode: false, + }).then((response) => { + // Logging x-request-id is mandatory + logRequestId(response.headers["x-request-id"]); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + // Validate the payment confirm response + expect(response.body).to.have.property("id").and.to.be.a("string").and + .not.be.empty; + expect(response.body).to.have.property("status"); + + globalState.set("paymentID", response.body.id); + + // Validate response body against expected data + if (resData && resData.body) { + for (const key in resData.body) { + // Skip validation if expected value is null or undefined + if (resData.body[key] == null) { + continue; + } + // Only validate if the field exists in the response and has a non-null value + if ( + response.body.hasOwnProperty(key) && + response.body[key] != null + ) { + // Use deep equal for object comparison, regular equal for primitives + if ( + typeof resData.body[key] === "object" && + typeof response.body[key] === "object" + ) { + expect(response.body[key]).to.deep.equal(resData.body[key]); + } else { + expect(response.body[key]).to.equal(resData.body[key]); + } + } + } + } + } else { + defaultErrorHandler(response, resData); + } + }); + }); + } +);