Created
May 17, 2025 02:10
-
-
Save gtdse/392545726dc49aeb8c67c4c932bae35f to your computer and use it in GitHub Desktop.
Full automation for mss merchant creation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
import random | |
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError | |
def extract_application_id_from_url(url_string): | |
""" | |
Extracts the application ID from a URL string. | |
The ID is expected to be between 'application/' and '/submit-with'. | |
Args: | |
url_string (str): The URL to process. | |
Returns: | |
str or None: The extracted application ID, or None if not found. | |
""" | |
application_id = None | |
try: | |
start_delimiter = "application/" | |
end_delimiter = "/submit-with" | |
start_index = url_string.find(start_delimiter) | |
if start_index != -1: | |
# Adjust start_index to be after the delimiter | |
actual_start_index = start_index + len(start_delimiter) | |
end_index = url_string.find(end_delimiter, actual_start_index) | |
if end_index != -1: | |
application_id = url_string[actual_start_index:end_index] | |
return application_id | |
except Exception as e: | |
print(f"Error with string find method: {e}") | |
application_id = None # Ensure it's None if an error occurs | |
def affirm_full_onboarding_flow_final_steps(url): | |
""" | |
Full onboarding flow: initial form, dynamic fields, submit, choose package, | |
handle prohibited items, fill store details, fill business information, | |
fill owner information, fill bank details, interact with submit application page, | |
and finally submit the application. | |
""" | |
print(f"--- Starting Playwright FULL ONBOARDING FLOW (FINAL STEPS including Submit Application) for {url} ---") | |
# Screenshot paths | |
screenshot_path_intake_form = "intake_form.png" | |
screenshot_path_after_offered_packages = "offered_packages.png" | |
screenshot_path_selected_package = "selected_package_card.png" | |
screenshot_path_prohibited_items_checked = "prohibited_items_checked.png" | |
screenshot_path_store_details_filled = "store_details_filled.png" | |
screenshot_path_business_info_filled = "business_info_filled.png" | |
screenshot_path_owner_info_filled = "owner_info_filled.png" | |
screenshot_path_bank_details_filled = "bank_details_filled.png" | |
screenshot_path_terms_checked = "application_terms_checked.png" | |
screenshot_path_flow_completed = "affirm_application_submitted.png" | |
screenshot_path_error = "affirm_onboarding_error.png" | |
# 1. Generate dynamic data | |
current_date_obj = datetime.datetime.now() | |
current_date_str = current_date_obj.strftime("%m.%d.%y") | |
random_id_str = f".{random.randint(100, 999):03}" | |
date_id_for_email = current_date_str + random_id_str | |
date_id_for_website_raw = current_date_str + random_id_str | |
date_id_for_website_modified = date_id_for_website_raw.replace(".", "-") | |
business_email_value = f"mae+msstest{date_id_for_email}@affirm.com" | |
business_website_value = f"www.maeinternaltest{date_id_for_website_modified}.com" | |
country_value = "United States" | |
platform_value = "Magento 2" | |
annual_volume_value = "<$2M" | |
average_order_value_value = "$100-$150" | |
industry_value = "Accessories" | |
sub_industry_value = "Optical" | |
capture_to_shipment_value = "< 7 days" | |
business_type_value = "LLC" | |
legal_name_value = f"MSS INTERNAL TEST {date_id_for_email}" | |
tax_id_ein_value = "99-9999999" | |
business_street1_value = "650 California Ave" | |
business_city_value = "San Francisco" | |
business_state_value = "California" | |
business_postal_code_value = "94108" | |
owner_first_name_value = "MAE" | |
owner_last_name_value = "Tester" | |
owner_dob_value = "01/01/1990" | |
owner_phone_value = "888-484-4282" | |
owner_ssn_value = "000-99-1111" | |
# Values for "Bank Details" page | |
routing_number_value = "072403004" | |
account_number_value = "856667" | |
print(f"Generated Business Email: {business_email_value}") | |
print(f"Generated Business Website: {business_website_value}") | |
print(f"Generated Legal Name: {legal_name_value}") | |
with sync_playwright() as p: | |
try: | |
browser = p.chromium.launch(headless=False, slow_mo=100) # Consider making headless configurable | |
page = browser.new_page() | |
page.set_viewport_size({"width": 1280, "height": 1080}) | |
print(f"Navigating to {url}...") | |
page.goto(url, timeout=60000) | |
print("Page loaded. Current URL:", page.url) | |
# --- Initial 7 Fields (Onboarding) --- | |
print("--- Filling initial 7 fields on Onboarding page ---") | |
page.locator('input[name="business_email"]').fill(business_email_value) | |
page.locator('input[name="business_website"]').fill(business_website_value) | |
page.locator('select[name="business_country"]').select_option(label=country_value) | |
page.locator('select[name="platform"]').select_option(label=platform_value) | |
page.locator('select[name="annual_volume"]').wait_for(state="visible", timeout=30000) | |
page.locator('select[name="annual_volume"]').select_option(label=annual_volume_value) | |
page.locator('select[name="average_order_value"]').wait_for(state="visible", timeout=15000) | |
page.locator('select[name="average_order_value"]').select_option(label=average_order_value_value) | |
checkbox1_input_id = "marketing-disclosure-checkbox" | |
label1_locator = page.locator(f'label[for="{checkbox1_input_id}"]') | |
label1_locator.first.wait_for(state="visible", timeout=15000) | |
label1_locator.first.click(timeout=10000) | |
page.wait_for_timeout(200) # Consider replacing with explicit waits if possible | |
print(f"Marketing Disclosure Checkbox checked: {page.locator(f'input#{checkbox1_input_id}').is_checked()}") | |
print("--- All 7 initial fields processed ---") | |
page.screenshot(path=screenshot_path_intake_form, full_page=True) | |
print(f"Screenshot {screenshot_path_intake_form} saved successfully!") | |
# --- First Submit ("Continue") -> Leads to Pricing/Package Page --- | |
print("--- Submitting initial form (to Pricing/Package page) ---") | |
page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")').click(timeout=30000) | |
print("Initial form 'Continue' button clicked.") | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Pricing/Package page loaded. Current URL: {page.url}") | |
page.locator('[data-testid="fp-cards-grid-item"]').first.wait_for(state="visible", timeout=15000) | |
page.screenshot(path=screenshot_path_after_offered_packages, full_page=True) | |
print(f"Screenshot {screenshot_path_after_offered_packages} saved successfully!") | |
# --- Choose Package -> Leads to Prohibited Items Page --- | |
print("--- Choosing the first financing package ---") | |
first_package_card_locator = page.locator('[data-testid="fp-cards-grid-item"] > div').first | |
first_package_card_locator.screenshot(path=screenshot_path_selected_package) | |
first_package_card_locator.locator('button:has-text("Choose this package")').click(timeout=15000) | |
print("'Choose this package' button clicked.") | |
# Prohibited Items Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Prohibited Items page loaded. Current URL: {page.url}") | |
page.locator('label[for="pbp-disclosure-checkbox"]').first.wait_for(state="visible", timeout=15000) | |
print("--- Interacting with 'Prohibited Items' page ---") | |
checkbox2_input_id = "pbp-disclosure-checkbox" | |
page.locator(f'label[for="{checkbox2_input_id}"]').first.click(timeout=10000) | |
page.wait_for_timeout(200) # Consider replacing | |
print(f"Prohibited items checkbox checked: {page.locator(f'input#{checkbox2_input_id}').is_checked()}") | |
page.screenshot(path=screenshot_path_prohibited_items_checked, full_page=True) | |
print(f"Screenshot {screenshot_path_prohibited_items_checked} saved successfully!") | |
page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")').click(timeout=30000) | |
print("'Continue' button on Prohibited Items page clicked.") | |
# Store Details Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Store Details page loaded. Current URL: {page.url}") | |
page.locator('select[name="industry"]').wait_for(state="visible", timeout=15000) | |
print("--- Interacting with 'Store details' page ---") | |
page.locator('select[name="industry"]').select_option(label=industry_value) | |
page.wait_for_timeout(500) # Consider replacing | |
page.locator('select[name="sub_industry"]').select_option(label=sub_industry_value) | |
page.wait_for_timeout(500) # Consider replacing | |
page.locator('select[name="capture_to_shipment"]').select_option(label=capture_to_shipment_value) | |
print("Store details fields filled.") | |
page.screenshot(path=screenshot_path_store_details_filled, full_page=True) | |
print(f"Screenshot {screenshot_path_store_details_filled} saved successfully!") | |
page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")').click(timeout=30000) | |
print("'Continue' button on Store Details page clicked.") | |
# Business Information Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Business Information page loaded. Current URL: {page.url}") | |
page.locator('select[name="business_type"]').wait_for(state="visible", timeout=15000) | |
print("--- Interacting with 'Business Information' page ---") | |
page.locator('select[name="business_type"]').select_option(label=business_type_value) | |
page.locator('input[name="legal_name"]').fill(legal_name_value) | |
page.locator('label[for="dba-checkbox"]').click() | |
page.wait_for_timeout(200) # Consider replacing | |
print(f"DBA Checkbox clicked. Is checked: {page.locator('input#dba-checkbox').is_checked()}") | |
page.locator('input[name="tax_id_ein"]').fill(tax_id_ein_value) | |
page.locator('input[name="business_street1"]').fill(business_street1_value) | |
page.locator('input[name="business_city"]').fill(business_city_value) | |
page.locator('select[name="business_state"]').select_option(label=business_state_value) | |
page.locator('input[name="business_postal_code"]').fill(business_postal_code_value) | |
print("Business information fields filled.") | |
page.screenshot(path=screenshot_path_business_info_filled, full_page=True) | |
print(f"Screenshot {screenshot_path_business_info_filled} saved successfully!") | |
page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")').click(timeout=30000) | |
print("'Continue' button on Business Information page clicked.") | |
# Owner Information Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Owner Information page loaded. Current URL: {page.url}") | |
page.locator('input[name="business_owner_first_name"]').wait_for(state="visible", timeout=15000) | |
print("--- Interacting with 'Owner Information' page ---") | |
page.locator('input[name="business_owner_first_name"]').fill(owner_first_name_value) | |
page.locator('input[name="business_owner_last_name"]').fill(owner_last_name_value) | |
page.locator('input[name="business_owner_dob"]').fill(owner_dob_value) | |
page.locator('input[name="business_owner_phone_number"]').fill(owner_phone_value) | |
page.locator('input[name="business_owner_ssn"]').fill(owner_ssn_value) | |
print("Owner information fields filled.") | |
page.screenshot(path=screenshot_path_owner_info_filled, full_page=True) | |
print(f"Screenshot {screenshot_path_owner_info_filled} saved successfully!") | |
page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")').click(timeout=30000) | |
print("'Continue' button on Owner Information page clicked.") | |
# Bank Details Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Bank Details page loaded. Current URL: {page.url}") | |
page.locator('input[name="settlement_bank_routing_number"]').wait_for(state="visible", timeout=15000) | |
print("Bank Details page elements are visible.") | |
print("--- Interacting with 'Bank Details' page ---") | |
page.locator('input[name="settlement_bank_routing_number"]').fill(routing_number_value) | |
print(f"Filled Routing Number: {routing_number_value}") | |
page.locator('input[name="settlement_bank_account_number"]').fill(account_number_value) | |
print(f"Filled Account Number: {account_number_value}") | |
print("Bank details fields filled.") | |
page.screenshot(path=screenshot_path_bank_details_filled, full_page=True) | |
print(f"Screenshot {screenshot_path_bank_details_filled} saved successfully!") | |
bank_details_continue_button = page.locator('[data-testid="step-buttons--nextbutton"] button:has-text("Continue")') | |
bank_details_continue_button.wait_for(state="visible", timeout=20000) | |
print("Attempting to click 'Continue' button on Bank Details page...") | |
bank_details_continue_button.click(timeout=30000) | |
print("'Continue' button on Bank Details page clicked.") | |
# Submit your application Page | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
print(f"Landed on page after Bank Details. Current URL: {page.url}. Expecting 'Submit your application' page.") | |
print("--- Waiting for 'Submit your application' page elements ---") | |
submit_app_content_locator = page.locator('div#ps-group-embedded-nas10a0kg-onboarding') | |
submit_app_content_locator.wait_for(state="visible", timeout=30000) | |
print("'Submit your application' page content (PactSafe agreement block) is visible.") | |
page.wait_for_timeout(3000) | |
print("--- Interacting with 'Submit your application' page (PactSafe Terms) ---") | |
terms_label_locator = page.locator( | |
'div#ps-group-embedded-nas10a0kg-onboarding label.ps-contract-label:has-text("I confirm that I am an authorized representative")' | |
) | |
terms_label_locator.wait_for(state="visible", timeout=15000) | |
print("PactSafe terms and conditions label is visible.") | |
terms_label_locator.click() | |
print("Clicked on the PactSafe terms and conditions label.") | |
page.wait_for_timeout(200) # Small pause for UI to update if needed | |
checkbox_input_locator = page.locator( | |
'div#ps-group-embedded-nas10a0kg-onboarding input.ps-contract-target[type="checkbox"]' | |
) | |
if checkbox_input_locator.count() > 0: | |
if checkbox_input_locator.is_checked(): | |
print("PactSafe Terms checkbox is confirmed checked.") | |
else: | |
print("WARNING: PactSafe Terms checkbox IS NOT checked after clicking label. Attempting direct input click...") | |
checkbox_input_locator.click() | |
page.wait_for_timeout(200) | |
if checkbox_input_locator.is_checked(): | |
print("PactSafe Terms checkbox is NOW checked after direct input click.") | |
else: | |
print("ERROR: PactSafe Terms checkbox STILL NOT checked after direct input click.") | |
else: | |
print("ERROR: Could not find the PactSafe Terms checkbox input to verify its state.") | |
page.screenshot(path=screenshot_path_terms_checked, full_page=True) | |
print(f"Screenshot {screenshot_path_terms_checked} saved successfully!") | |
print("--- 'Submit your application' page interaction complete (terms checkbox handled) ---") | |
# --- Submitting the application --- | |
print("--- Submitting the application ---") | |
page.wait_for_timeout(1000) | |
final_submit_button_locator = page.locator('button:text-is("Submit")') | |
final_submit_button_locator.wait_for(state="visible", timeout=15000) | |
print("Final 'Submit' button is visible") | |
print("Setting up to intercept submission API call and clicking 'Submit' button...") | |
extracted_info_from_payload = {} | |
with page.expect_request( | |
lambda request: "/api/v1/merchants/onboarding/application/" in request.url and \ | |
request.url.endswith("/submit-with-fp") and \ | |
request.method == "POST", | |
timeout=0 | |
) as request_info: # This still fails inexplicably at times. added additional timeouts | |
# throughout to try and mitigate. | |
page.wait_for_timeout(1000) | |
final_submit_button_locator.click() | |
captured_request = request_info.value | |
print(f"Successfully intercepted POST request to: {captured_request.url}") | |
try: | |
payload = captured_request.post_data_json | |
if payload: | |
keys = payload.keys() | |
print(f"Keys in the POST data: {keys}") | |
print("Payload successfully parsed as JSON.") | |
object_to_extract = "financing_package" | |
field_to_get = "financing_package" | |
if payload: | |
extracted_info_from_payload['finacing program uuid'] = payload.get(object_to_extract, {}).get(field_to_get, "NESTED_NOT_FOUND") | |
else: | |
print("Payload was None after JSON parsing attempt.") | |
print("--- Information Extracted from POST Payload: ---") | |
if extracted_info_from_payload: | |
for key, value in extracted_info_from_payload.items(): | |
print(f" {key}: {value}") | |
print(f"Merchant ARI: {extract_application_id_from_url(captured_request.url)}") | |
else: | |
print(" No specific data was configured for extraction, or payload was empty/None.") | |
print("-------------------------------------------------") | |
except Exception as e: | |
print(f"Could not process POST data as JSON or extract keys: {e}") | |
raw_payload = captured_request.post_data | |
print(f"Raw POST data (if available): {raw_payload}") | |
extracted_info_from_payload['error_during_payload_processing'] = str(e) | |
if raw_payload: | |
extracted_info_from_payload['raw_payload_on_error_for_debug'] = raw_payload | |
print("Waiting for response after final submission...") | |
page.wait_for_load_state("domcontentloaded", timeout=60000) | |
page.wait_for_load_state("domcontentloaded", timeout=60000) # there are multiple stages to this spinner | |
heading_locator = page.get_by_role("heading",name="We’re reviewing your application",level=2) | |
heading_locator.wait_for(state="visible", timeout=120000) | |
print(f"Application submitted. Current URL: {page.url}") | |
page.screenshot(path=screenshot_path_flow_completed, full_page=True) | |
print(f"Screenshot {screenshot_path_flow_completed} saved successfully!") | |
print("--- Application submission process finished ---") | |
except PlaywrightTimeoutError as te: | |
print(f"ERROR: A Playwright timeout occurred: {te}") | |
current_screenshot_path = screenshot_path_error | |
if 'page' in locals() and not page.is_closed(): | |
page.screenshot(path=current_screenshot_path, full_page=True) | |
print(f"Timeout error screenshot taken: {current_screenshot_path}") | |
except Exception as e: | |
error_message = f"ERROR: An unexpected error occurred: {e} (Type: {type(e).__name__})" | |
print(error_message) | |
if 'page' in locals() and not page.is_closed(): | |
page.screenshot(path="affirm_onboarding_generic_error.png", full_page=True) | |
print(f"Generic error screenshot taken.") | |
finally: | |
if 'browser' in locals() and browser.is_connected(): | |
print("Closing browser...") | |
browser.close() | |
print(f"--- Playwright FULL ONBOARDING FLOW (FINAL STEPS including Submit Application) finished ---") | |
target_url = "https://www.affirm.com/dashboard/onboarding" | |
if __name__ == "__main__": | |
affirm_full_onboarding_flow_final_steps(target_url) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment