diff --git a/cypress-tests/cypress/e2e/spec/ModularPmService/0000-CoreFlows.cy.js b/cypress-tests/cypress/e2e/spec/ModularPmService/0000-CoreFlows.cy.js new file mode 100644 index 0000000000..0f41a0e84b --- /dev/null +++ b/cypress-tests/cypress/e2e/spec/ModularPmService/0000-CoreFlows.cy.js @@ -0,0 +1,108 @@ +import * as fixtures from "../../../fixtures/imports"; +import State from "../../../utils/State"; +import { payment_methods_enabled } from "../../configs/Payment/Commons"; + +let globalState; + +describe("Core flows", () => { + context("Modular Payment Method core flows", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("merchant create call", () => { + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); + }); + + it("API key create call", () => { + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); + }); + + it("Connector create call", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + it("Modular PM Service - Customer create call", () => { + cy.customerCreateCall(globalState, fixtures.customerCreate); + }); + + it("Modular PM Service - Merchant Config create call", () => { + const key = `should_return_raw_payment_method_details_${globalState.get("merchantId")}`; + + cy.setConfigs(globalState, key, "true", "CREATE"); + }); + + it("Modular PM Service - Organization Config create call", () => { + const key = `should_call_pm_modular_service_${globalState.get("organizationId")}`; + + cy.setConfigs(globalState, key, "true", "CREATE"); + }); + it("Modular PM Service - Payment Method Create call", () => { + cy.paymentMethodCreateCall(globalState, fixtures.paymentMethodCreate); + }); + + it("Modular PM Service - Payments call with pm_id", () => { + cy.paymentWithSavedPMCall( + globalState, + fixtures.modularPmServicePaymentsCall + ); + }); + + it("Modular PM Service - Update Payment Method call", () => { + cy.updateSavedPMCall(globalState, fixtures.paymentMethodUpdate); + }); + + it("Modular PM Service - Payment Method List call", () => { + cy.listSavedPMCall(globalState); + }); + + it("Modular PM Service - Payment Method Session Create call", () => { + cy.pmSessionCreateCall(globalState, fixtures.paymentMethodSessionCreate); + }); + + it("Modular PM Service - Payment Method Session Retrieve call", () => { + cy.pmSessionRetrieveCall(globalState); + }); + + it("Modular PM Service - Payment Method Session List call", () => { + cy.pmSessionListPMCall(globalState); + }); + + it("Modular PM Service - Payment Method Session Update call", () => { + cy.pmSessionUpdatePMCall( + globalState, + fixtures.paymentMethodSessionUpdate + ); + }); + + it("Modular PM Service - Payment Method Session Confirm call", () => { + cy.pmSessionConfirmCall( + globalState, + fixtures.paymentMethodSessionConfirm + ); + }); + + it("Modular PM Service - Get Payment Method from session token call", () => { + cy.getPMFromTokenCall(globalState); + }); + + it("Modular PM Service - Payments call with pm_token", () => { + cy.paymentWithSavedPMCall( + globalState, + fixtures.modularPmServicePaymentsCall, + true + ); + }); + }); +}); diff --git a/cypress-tests/cypress/fixtures/imports.js b/cypress-tests/cypress/fixtures/imports.js index 6fe1b9a54c..99b262795d 100644 --- a/cypress-tests/cypress/fixtures/imports.js +++ b/cypress-tests/cypress/fixtures/imports.js @@ -27,7 +27,13 @@ import ntidConfirmBody from "./create-ntid-mit.json"; import blocklistCreateBody from "./blocklist-create-body.json"; import eligibilityCheckBody from "./eligibility-check-body.json"; import * as IncomingWebhookBody from "./webhooks/import"; - +import customerCreate from "./modularPmService/modularPmServiceCustomerCreate.json"; +import paymentMethodCreate from "./modularPmService/modular-pm-service-pm-create.json"; +import paymentMethodUpdate from "./modularPmService/modular-pm-service-pm-update.json"; +import paymentMethodSessionCreate from "./modularPmService/modular-pm-service-pms-create.json"; +import paymentMethodSessionUpdate from "./modularPmService/modular-pm-service-update-pms-saved-pm.json"; +import paymentMethodSessionConfirm from "./modularPmService/modular-pm-service-pms-confim.json"; +import modularPmServicePaymentsCall from "./modularPmService/modular-pm-service-payments-call.json"; export { apiKeyCreateBody, apiKeyUpdateBody, @@ -58,4 +64,11 @@ export { updateConnectorBody, voidBody, IncomingWebhookBody, + customerCreate, + paymentMethodCreate, + paymentMethodUpdate, + paymentMethodSessionCreate, + paymentMethodSessionUpdate, + paymentMethodSessionConfirm, + modularPmServicePaymentsCall, }; diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-payments-call.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-payments-call.json new file mode 100644 index 0000000000..20fd15ebce --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-payments-call.json @@ -0,0 +1,68 @@ +{ + "amount": 200, + "currency": "USD", + "confirm": true, + "capture_method": "automatic", + "capture_on": "2022-09-10T10:11:12Z", + "email": "guest@example.com", + "name": "John Doe", + "phone": "999999999", + "phone_country_code": "+65", + "description": "Its my first payment request", + "authentication_type": "no_three_ds", + "return_url": "https://google.com", + "payment_method": "card", + "billing": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "PiX", + "last_name": "ss" + } + }, + "shipping": { + "address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "John", + "last_name": "Doe" + }, + "phone": { + "number": "8056594427", + "country_code": "+91" + } + }, + "browser_info": { + "user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/70.0.3538.110 Safari\/537.36", + "accept_header": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8", + "language": "nl-NL", + "color_depth": 24, + "screen_height": 723, + "screen_width": 1536, + "time_zone": 0, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "125.0.0.1" + }, + "statement_descriptor_name": "joseph", + "statement_descriptor_suffix": "JS", + "metadata": {}, + "order_details": [ + { + "product_name": "Apple iphone 15", + "quantity": 1, + "amount": 0, + "account_name": "transaction_processing" + } + ] +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-create.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-create.json new file mode 100644 index 0000000000..f95bad8b5b --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-create.json @@ -0,0 +1,15 @@ +{ + "payment_method_type": "card", + "payment_method_subtype": "credit", + "payment_method_data": { + "card": { + "card_number": "4761360080000093", + "card_exp_month": "07", + "card_exp_year": "2030", + "card_holder_name": "Gaurav rawat", + "card_cvc": "123", + "nick_name": "abcd" + } + }, + "storage_type": "persistent" +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-update.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-update.json new file mode 100644 index 0000000000..2524087b20 --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pm-update.json @@ -0,0 +1,7 @@ +{ + "payment_method_data": { + "card": { + "card_cvc": "123" + } + } +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-confim.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-confim.json new file mode 100644 index 0000000000..eaa86926cb --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-confim.json @@ -0,0 +1,13 @@ +{ + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "01", + "card_exp_year": "27", + "card_holder_name": "John Doe", + "card_cvc": "100" + } + }, + "payment_method_type": "card", + "payment_method_subtype": "credit" +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-create.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-create.json new file mode 100644 index 0000000000..874cd2525a --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-pms-create.json @@ -0,0 +1,11 @@ +{ + "billing": { + "address": { + "first_name": "John", + "last_name": "Dough" + }, + "email": "example@example.com" + }, + "storage_type": "persistent", + "keep_alive": true +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-update-pms-saved-pm.json b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-update-pms-saved-pm.json new file mode 100644 index 0000000000..f8a490bfbd --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modular-pm-service-update-pms-saved-pm.json @@ -0,0 +1,7 @@ +{ + "payment_method_data": { + "card": { + "card_holder_name": "Gaurav Rawat" + } + } +} diff --git a/cypress-tests/cypress/fixtures/modularPmService/modularPmServiceCustomerCreate.json b/cypress-tests/cypress/fixtures/modularPmService/modularPmServiceCustomerCreate.json new file mode 100644 index 0000000000..8fbae21eef --- /dev/null +++ b/cypress-tests/cypress/fixtures/modularPmService/modularPmServiceCustomerCreate.json @@ -0,0 +1,35 @@ +{ + "merchant_reference_id": "customer_1771243192", + "name": "John Doe", + "email": "guest@example.com", + "phone": "999999999", + "phone_country_code": "+65", + "description": "First customer", + "default_billing_address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "default_shipping_address": { + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US", + "first_name": "joseph", + "last_name": "Doe" + }, + "metadata": { + "udf1": "value1", + "new_customer": "true", + "login_date": "2019-09-10T10:11:12Z" + } +} diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 52c08172ba..a9b2e00a60 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -480,6 +480,7 @@ Cypress.Commands.add( globalState.set("profileId", response.body.default_profile); globalState.set("publishableKey", response.body.publishable_key); globalState.set("merchantDetails", response.body.merchant_details); + globalState.set("organizationId", response.body.organization_id); }); }); } @@ -5211,6 +5212,378 @@ Cypress.Commands.add("IncomingWebhookTest", (globalState, webhookPayload) => { }); }); +Cypress.Commands.add( + "customerCreateCall", + (globalState, customerCreateBody) => { + const reqData = { ...customerCreateBody }; + reqData.merchant_reference_id = `customer_${Date.now()}`; + + cy.request({ + method: "POST", + url: `${globalState.get("pmServiceUrl")}/v1/customers`, + headers: { + "Content-Type": "application/json", + Authorization: `api-key=${globalState.get("apiKey")}`, + "x-profile-id": globalState.get("profileId"), + }, + body: reqData, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set("customerId", response.body.id); + expect(response.body.id, "customer_id").to.not.be.empty; + expect(customerCreateBody.email, "email").to.equal(response.body.email); + expect(customerCreateBody.name, "name").to.equal(response.body.name); + expect(customerCreateBody.phone, "phone").to.equal(response.body.phone); + expect(customerCreateBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect(customerCreateBody.address, "address").to.deep.equal( + response.body.address + ); + expect( + customerCreateBody.phone_country_code, + "phone_country_code" + ).to.equal(response.body.phone_country_code); + } else if (response.status === 400) { + if (response.body.error.message.includes("already exists")) { + expect(response.body.error.code).to.equal("IR_12"); + expect(response.body.error.message).to.equal( + "Customer with the given `customer_id` already exists" + ); + } + } else { + throw new Error( + `Customer create call failed with status: ${response.status} and message: ${response.body?.error?.message}` + ); + } + }); + } +); + +// Payment Methods Commands (v2 code - port 8082) +Cypress.Commands.add("paymentMethodCreateCall", (globalState, pmData) => { + const apiKey = globalState.get("apiKey"); + const profileId = globalState.get("profileId"); + const customerId = globalState.get("customerId"); + + const requestBody = { + ...pmData, + customer_id: customerId, + }; + + cy.request({ + method: "POST", + url: `${globalState.get("pmServiceUrl")}/v1/payment-methods`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "X-Profile-Id": profileId, + Authorization: `api-key=${apiKey}`, + }, + body: requestBody, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set("paymentMethodId", response.body.id); + expect(requestBody.customer_id, "customer_id").to.equal( + response.body.customer_id + ); + expect(requestBody.payment_method_type, "payment_method_type").to.equal( + response.body.payment_method_type + ); + expect( + requestBody.payment_method_subtype, + "payment_method_subtype" + ).to.equal(response.body.payment_method_subtype); + expect(requestBody.storage_type, "storage_type").to.equal( + response.body.storage_type + ); + } else { + throw new Error( + `Payment method create failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("updateSavedPMCall", (globalState, updateData) => { + const apiKey = globalState.get("apiKey"); + const profileId = globalState.get("profileId"); + const paymentMethodId = globalState.get("paymentMethodId"); + + cy.request({ + method: "PUT", + url: `${globalState.get("pmServiceUrl")}/v1/payment-methods/${paymentMethodId}/update-saved-payment-method`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "X-Profile-Id": profileId, + "X-Resource-Type": "payment_method", + Authorization: `api-key=${apiKey}`, + }, + body: updateData, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + expect(response.body).to.have.property("id"); + } else { + throw new Error( + `Update saved PM failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("listSavedPMCall", (globalState) => { + const apiKey = globalState.get("apiKey"); + const profileId = globalState.get("profileId"); + const customerId = globalState.get("customerId"); + + cy.request({ + method: "GET", + url: `${globalState.get("pmServiceUrl")}/v1/customers/${customerId}/saved-payment-methods`, + headers: { + "Content-Type": "application/json", + "x-profile-id": profileId, + Authorization: `api-key=${apiKey}`, + }, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + expect(response.body.customer_payment_methods).to.be.an("array"); + } else { + throw new Error( + `List saved PM failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +// Payment Method Session Commands (v2 code - port 8082) +Cypress.Commands.add("pmSessionCreateCall", (globalState, sessionData) => { + const apiKey = globalState.get("apiKey"); + const profileId = globalState.get("profileId"); + const customerId = globalState.get("customerId"); + + const requestBody = { + ...sessionData, + customer_id: customerId, + }; + + cy.request({ + method: "POST", + url: `${globalState.get("pmServiceUrl")}/v1/payment-method-sessions`, + headers: { + "Content-Type": "application/json", + "x-profile-id": profileId, + Authorization: `api-key=${apiKey}`, + }, + body: requestBody, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set("paymentMethodSessionId", response.body.id); + globalState.set("clientSecret", response.body.client_secret); + expect(response.body).to.have.property("id"); + expect(response.body).to.have.property("client_secret"); + } else { + throw new Error( + `PM session create failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("pmSessionRetrieveCall", (globalState) => { + const profileId = globalState.get("profileId"); + const sessionId = globalState.get("paymentMethodSessionId"); + const publishableKey = globalState.get("publishableKey"); + const clientSecret = globalState.get("clientSecret"); + + cy.request({ + method: "GET", + url: `${globalState.get("pmServiceUrl")}/v1/payment-method-sessions/${sessionId}`, + headers: { + "x-profile-id": profileId, + Authorization: `publishable-key=${publishableKey},client-secret=${clientSecret}`, + }, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + expect(response.body).to.have.property("id"); + } else { + throw new Error( + `PM session retrieve failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("pmSessionListPMCall", (globalState) => { + const profileId = globalState.get("profileId"); + const sessionId = globalState.get("paymentMethodSessionId"); + const publishableKey = globalState.get("publishableKey"); + const clientSecret = globalState.get("clientSecret"); + + cy.request({ + method: "GET", + url: `${globalState.get("pmServiceUrl")}/v1/payment-method-sessions/${sessionId}/list-payment-methods`, + headers: { + "x-profile-id": profileId, + Authorization: `publishable-key=${publishableKey},client-secret=${clientSecret}`, + }, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set( + "paymentMethodToken", + response.body.customer_payment_methods[0].payment_method_token + ); + } else { + throw new Error( + `PM session list PM failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("pmSessionUpdatePMCall", (globalState, updateData) => { + const profileId = globalState.get("profileId"); + const sessionId = globalState.get("paymentMethodSessionId"); + const publishableKey = globalState.get("publishableKey"); + const clientSecret = globalState.get("clientSecret"); + + const payment_method_token = globalState.get("paymentMethodToken"); + + if (!payment_method_token) { + throw new Error("Payment method token is required for PM session update"); + } + + const req_body = { + ...updateData, + payment_method_token, + }; + + cy.request({ + method: "PUT", + url: `${globalState.get("pmServiceUrl")}/v1/payment-method-sessions/${sessionId}/update-saved-payment-method`, + headers: { + "Content-Type": "application/json", + "x-profile-id": profileId, + Authorization: `publishable-key=${publishableKey},client-secret=${clientSecret}`, + }, + body: req_body, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + expect(response.body).to.have.property("id"); + } else { + throw new Error( + `PM session update PM failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("pmSessionConfirmCall", (globalState, confirmData) => { + const profileId = globalState.get("profileId"); + const sessionId = globalState.get("paymentMethodSessionId"); + const publishableKey = globalState.get("publishableKey"); + const clientSecret = globalState.get("clientSecret"); + + cy.request({ + method: "POST", + url: `${globalState.get("pmServiceUrl")}/v1/payment-method-sessions/${sessionId}/confirm`, + headers: { + "Content-Type": "application/json", + "x-profile-id": profileId, + Authorization: `publishable-key=${publishableKey},client-secret=${clientSecret}`, + }, + body: confirmData, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set( + "paymentMethodToken", + response.body.associated_payment_methods[0].payment_method_token.data + ); + expect(response.body).to.have.property("id"); + } else { + throw new Error( + `PM session confirm failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add("getPMFromTokenCall", (globalState) => { + const apiKey = globalState.get("apiKey"); + const profileId = globalState.get("profileId"); + const paymentMethodToken = globalState.get("paymentMethodToken"); + + cy.request({ + method: "GET", + url: `${globalState.get("pmServiceUrl")}/v1/payment-methods/token/${paymentMethodToken}/details`, + headers: { + "Content-Type": "application/json", + "x-profile-id": profileId, + Authorization: `api-key=${apiKey}`, + }, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + expect(response.body).to.have.property("id"); + } else { + throw new Error( + `Get PM from token failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); +}); + +Cypress.Commands.add( + "paymentWithSavedPMCall", + (globalState, paymentData, useToken = false) => { + const baseUrl = globalState.get("baseUrl"); + const apiKey = globalState.get("apiKey"); + const customerId = globalState.get("customerId"); + const profileId = globalState.get("profileId"); + + const requestBody = { + ...paymentData, + customer_id: customerId, + profile_id: profileId, + payment_token: useToken + ? globalState.get("paymentMethodToken") + : globalState.get("paymentMethodId"), + }; + + cy.request({ + method: "POST", + url: `${baseUrl}/payments`, + headers: { + "Content-Type": "application/json", + "api-key": apiKey, + }, + body: requestBody, + failOnStatusCode: false, + }).then((response) => { + if (response.status === 200) { + globalState.set("paymentId", response.body.payment_id); + expect(response.body).to.have.property("payment_id"); + expect(response.body).to.have.property("status"); + expect(response.body.amount).to.equal(paymentData.amount); + expect(response.body.currency).to.equal(paymentData.currency); + } else { + throw new Error( + `Payment with saved PM failed with status ${response.status}: ${JSON.stringify(response.body)}` + ); + } + }); + } +); + Cypress.Commands.add("step", (stepName, fn) => { cy.task("cli_log", `\nSTEP: ${stepName}`); diff --git a/cypress-tests/cypress/utils/State.js b/cypress-tests/cypress/utils/State.js index 48b31702fa..7070fb5aee 100644 --- a/cypress-tests/cypress/utils/State.js +++ b/cypress-tests/cypress/utils/State.js @@ -4,6 +4,7 @@ class State { this.data = data; this.data["connectorId"] = Cypress.env("CONNECTOR"); this.data["baseUrl"] = Cypress.env("BASEURL"); + this.data["pmServiceUrl"] = Cypress.env("PM_SERVICE_URL"); this.data["adminApiKey"] = Cypress.env("ADMINAPIKEY"); this.data["email"] = Cypress.env("HS_EMAIL"); this.data["password"] = Cypress.env("HS_PASSWORD");