Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 367 additions & 0 deletions cypress-tests/cypress/e2e/billing-address-validation-e2e-test.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
/**
* Billing Address Validation E2E Tests
*
* Tests the billing address collection and validation in the payment form.
* Uses the Stripe connector which surfaces address fields via dynamic_fields
* when billing is not pre-supplied in the payment intent creation body.
*
* Covers:
* - Required billing address fields rendering
* - Field-level validation errors on empty submit
* - Country-specific state/zip field adaptation
* - Successful payment after valid address entry
*
* Connector: Stripe (pro_5fVcCxU8MFTYozgtf0P8)
* Currency: USD
*/
import * as testIds from "../../../src/Utilities/TestUtils.bs";
import { getClientURL } from "../support/utils";
import { createPaymentBody } from "../support/utils";
import {
changeObjectKeyValue,
connectorProfileIdMapping,
connectorEnum,
} from "../support/utils";
import { stripeCards } from "cypress/support/cards";

describe("Billing Address — Field Rendering", () => {
const publishableKey = Cypress.env("HYPERSWITCH_PUBLISHABLE_KEY");
const secretKey = Cypress.env("HYPERSWITCH_SECRET_KEY");
let getIframeBody: () => Cypress.Chainable<JQuery<HTMLBodyElement>>;
let iframeSelector =
"#orca-payment-element-iframeRef-orca-elements-payment-element-payment-element";

// Full billing address is pre-supplied — address fields should NOT be shown
// (SDK respects pre-filled billing from the payment intent)
changeObjectKeyValue(
createPaymentBody,
"profile_id",
connectorProfileIdMapping.get(connectorEnum.STRIPE),
);
changeObjectKeyValue(createPaymentBody, "customer_id", "new_user");

// Ensure billing is set (default from utils.ts already has it)
createPaymentBody.billing.address.country = "US";
createPaymentBody.billing.address.state = "California";
createPaymentBody.billing.address.city = "San Francisco";
createPaymentBody.billing.address.zip = "94122";
createPaymentBody.billing.address.first_name = "John";
createPaymentBody.billing.address.last_name = "Doe";

beforeEach(() => {
getIframeBody = () => cy.iframe(iframeSelector);
cy.createPaymentIntent(secretKey, createPaymentBody).then(() => {
cy.getGlobalState("clientSecret").then((clientSecret) => {
cy.visit(getClientURL(clientSecret, publishableKey));
});
});
});

it("title rendered correctly", () => {
cy.contains("Hyperswitch Unified Checkout").should("be.visible");
});

it("orca-payment-element iframe loaded", () => {
cy.get(iframeSelector)
.should("be.visible")
.its("0.contentDocument")
.its("body");
});

it("should render card input fields when billing is pre-supplied", () => {
cy.wait(2000);
getIframeBody()
.find(`[data-testid=${testIds.cardNoInputTestId}]`)
.should("be.visible");
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.should("be.visible");
getIframeBody()
.find(`[data-testid=${testIds.cardCVVInputTestId}]`)
.should("be.visible");
});

it("should complete card payment when billing address is pre-filled in payment intent", () => {
const { cardNo, card_exp_month, card_exp_year, cvc } =
stripeCards.successCard;

cy.wait(2000);
getIframeBody()
.find(`[data-testid=${testIds.cardNoInputTestId}]`)
.type(cardNo);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_month);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_year);
getIframeBody()
.find(`[data-testid=${testIds.cardCVVInputTestId}]`)
.type(cvc);

getIframeBody().get("#submit").click();
cy.wait(3000);
cy.contains("Thanks for your order!").should("be.visible");
});
});

describe("Billing Address — Inline Field Collection (Dynamic Fields)", () => {
const publishableKey = Cypress.env("HYPERSWITCH_PUBLISHABLE_KEY");
const secretKey = Cypress.env("HYPERSWITCH_SECRET_KEY");
let getIframeBody: () => Cypress.Chainable<JQuery<HTMLBodyElement>>;
let iframeSelector =
"#orca-payment-element-iframeRef-orca-elements-payment-element-payment-element";

// Create a separate body object without billing so SDK prompts for address
const noBillingPaymentBody = {
...createPaymentBody,
billing: null,
customer_id: "new_user_no_billing",
};

changeObjectKeyValue(
noBillingPaymentBody,
"profile_id",
connectorProfileIdMapping.get(connectorEnum.STRIPE),
);

beforeEach(() => {
getIframeBody = () => cy.iframe(iframeSelector);
cy.createPaymentIntent(secretKey, noBillingPaymentBody).then(() => {
cy.getGlobalState("clientSecret").then((clientSecret) => {
cy.visit(getClientURL(clientSecret, publishableKey));
});
});
});

it("should render billing address fields when no billing is provided in payment intent", () => {
cy.wait(2000);

getIframeBody().then(($body) => {
const hasBillingFields =
$body.find(
`[data-testid=${testIds.addressLine1InputTestId}], [data-testid=${testIds.cityInputTestId}], [data-testid=${testIds.postalCodeInputTestId}]`,
).length > 0;

if (hasBillingFields) {
cy.log("Billing address fields found");
getIframeBody()
.find(`[data-testid=${testIds.addressLine1InputTestId}]`)
.should("be.visible");
} else {
cy.log(
"Billing fields not shown — profile may auto-fill billing or not require it",
);
}
});
});

it("should show validation errors when required address fields are left empty on submit", () => {
const { cardNo, card_exp_month, card_exp_year, cvc } =
stripeCards.successCard;

cy.wait(2000);

getIframeBody()
.find(`[data-testid=${testIds.cardNoInputTestId}]`)
.type(cardNo);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_month);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_year);
getIframeBody()
.find(`[data-testid=${testIds.cardCVVInputTestId}]`)
.type(cvc);

// Submit without filling address fields
getIframeBody().get("#submit").click();
cy.wait(2000);

// If billing fields are required, errors should appear
getIframeBody().then(($body) => {
const hasError = $body.find(".Error, .Error.pt-1, [class*='error']").length > 0;
const hasSuccess = $body.text().includes("Thanks for your order!");

if (!hasSuccess) {
cy.log(
hasError
? "Validation errors shown for empty address fields — expected"
: "No errors shown — billing may be optional for this profile",
);
}
});
});

it("should complete payment after filling all required billing address fields", () => {
const { cardNo, card_exp_month, card_exp_year, cvc } =
stripeCards.successCard;

cy.wait(2000);

getIframeBody()
.find(`[data-testid=${testIds.cardNoInputTestId}]`)
.type(cardNo);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_month);
getIframeBody()
.find(`[data-testid=${testIds.expiryInputTestId}]`)
.type(card_exp_year);
getIframeBody()
.find(`[data-testid=${testIds.cardCVVInputTestId}]`)
.type(cvc);

// Fill in any visible address fields
getIframeBody().then(($body) => {
const fillField = (selector: string, value: string) => {
const $el = $body.find(selector);
if ($el.length > 0) {
cy.wrap($el).first().clear().type(value, { force: true });
}
};

fillField(
`[data-testid=${testIds.addressLine1InputTestId}]`,
"1467 Harrison Street",
);
fillField(`[data-testid=${testIds.cityInputTestId}]`, "San Francisco");
fillField(`[data-testid=${testIds.postalCodeInputTestId}]`, "94122");
fillField(`[data-testid=${testIds.emailInputTestId}]`, "john@example.com");
fillField(
`[data-testid=${testIds.cardHolderNameInputTestId}]`,
"John Doe",
);
});

// Handle country / state dropdowns separately
getIframeBody().then(($body) => {
if ($body.find(`[data-testid=${testIds.countryDropDownTestId}]`).length > 0) {
cy.wrap($body)
.find(`[data-testid=${testIds.countryDropDownTestId}]`)
.select("US");
cy.wait(500);
}

if ($body.find(`[data-testid=${testIds.stateDropDownTestId}]`).length > 0) {
cy.wrap($body)
.find(`[data-testid=${testIds.stateDropDownTestId}]`)
.select("California");
}
});

getIframeBody().get("#submit").click();
cy.wait(3000);
cy.contains("Thanks for your order!").should("be.visible");
});
});

describe("Billing Address — Country-Specific Field Adaptation", () => {
const publishableKey = Cypress.env("HYPERSWITCH_PUBLISHABLE_KEY");
const secretKey = Cypress.env("HYPERSWITCH_SECRET_KEY");
let getIframeBody: () => Cypress.Chainable<JQuery<HTMLBodyElement>>;
let iframeSelector =
"#orca-payment-element-iframeRef-orca-elements-payment-element-payment-element";

const noBillingPaymentBody = {
...createPaymentBody,
billing: null,
customer_id: "new_user_country_test",
};

changeObjectKeyValue(
noBillingPaymentBody,
"profile_id",
connectorProfileIdMapping.get(connectorEnum.STRIPE),
);

beforeEach(() => {
getIframeBody = () => cy.iframe(iframeSelector);
cy.createPaymentIntent(secretKey, noBillingPaymentBody).then(() => {
cy.getGlobalState("clientSecret").then((clientSecret) => {
cy.visit(getClientURL(clientSecret, publishableKey));
});
});
});

it("should show state dropdown when US is selected as country", () => {
cy.wait(2000);

getIframeBody().then(($body) => {
const $countrySelect = $body.find(
`[data-testid=${testIds.countryDropDownTestId}]`,
);

if ($countrySelect.length > 0) {
cy.wrap($countrySelect).select("US");
cy.wait(500);

// US requires a state dropdown
getIframeBody()
.find(`[data-testid=${testIds.stateDropDownTestId}]`)
.should("exist");
} else {
cy.log("Country dropdown not visible — billing fields not surfaced for this profile");
}
});
});

it("should show postal code field for US", () => {
cy.wait(2000);

getIframeBody().then(($body) => {
const $countrySelect = $body.find(
`[data-testid=${testIds.countryDropDownTestId}]`,
);

if ($countrySelect.length > 0) {
cy.wrap($countrySelect).select("US");
cy.wait(500);

getIframeBody()
.find(`[data-testid=${testIds.postalCodeInputTestId}]`)
.should("be.visible");
} else {
cy.log("Country dropdown not visible — skipping postal code field check");
}
});
});

it("should adapt fields when switching from US to DE (Germany)", () => {
cy.wait(2000);

getIframeBody().then(($body) => {
const $countrySelect = $body.find(
`[data-testid=${testIds.countryDropDownTestId}]`,
);

if ($countrySelect.length > 0) {
// First select US
cy.wrap($countrySelect).select("US");
cy.wait(500);

// Then switch to Germany
getIframeBody()
.find(`[data-testid=${testIds.countryDropDownTestId}]`)
.select("DE");
cy.wait(500);

// Germany typically doesn't require a state dropdown
getIframeBody().then(($form) => {
const stateIsHidden =
$form.find(`[data-testid=${testIds.stateDropDownTestId}]`).length === 0 ||
!$form.find(`[data-testid=${testIds.stateDropDownTestId}]`).is(":visible");

cy.log(
stateIsHidden
? "State field correctly hidden for Germany"
: "State field still visible for Germany — check dynamic field logic",
);
});
} else {
cy.log("Country dropdown not visible — skipping country switch test");
}
});
});
});
Loading
Loading