diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/JSON/form_specs.json b/StripePaymentSheet/StripePaymentSheet/Resources/JSON/form_specs.json deleted file mode 100644 index cf9ab08c979f..000000000000 --- a/StripePaymentSheet/StripePaymentSheet/Resources/JSON/form_specs.json +++ /dev/null @@ -1,1113 +0,0 @@ -[ - { - "type": "afterpay_clearpay", - "async": false, - "fields": [ - { - "type": "afterpay_header" - }, - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "billing_address", - "allowed_country_codes": null - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "affirm", - "async": false, - "fields": [ - { - "type": "affirm_header" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "klarna", - "async": false, - "fields": [ - { - "type": "klarna_header" - }, - { - "type": "placeholder", - "for": "name" - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "klarna_country", - "api_path": { - "v1": "billing_details[address][country]" - } - }, - { - "type": "placeholder", - "for": "billing_address_without_country" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "ideal", - "async": false, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - }, - { - "type": "placeholder", - "for": "email" - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "selector", - "translation_id": "upe.labels.ideal.bank", - "items": [ - { - "display_text": "ABN Amro", - "api_value": "abn_amro" - }, - { - "display_text": "ASN Bank", - "api_value": "asn_bank" - }, - { - "display_text": "bunq B.V.", - "api_value": "bunq" - }, - { - "display_text": "ING Bank", - "api_value": "ing" - }, - { - "display_text": "Knab", - "api_value": "knab" - }, - { - "display_text": "N26", - "api_value": "n26" - }, - { - "display_text": "Rabobank", - "api_value": "rabobank" - }, - { - "display_text": "RegioBank", - "api_value": "regiobank" - }, - { - "display_text": "Revolut", - "api_value": "revolut" - }, - { - "display_text": "SNS Bank", - "api_value": "sns_bank" - }, - { - "display_text": "Triodos Bank", - "api_value": "triodos_bank" - }, - { - "display_text": "Van Lanschot", - "api_value": "van_lanschot" - }, - { - "display_text": "Yoursafe", - "api_value": "yoursafe" - } - ], - "api_path": { - "v1": "ideal[bank]" - } - }, - { - "type": "placeholder", - "for": "billing_address" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "bancontact", - "async": false, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "sepa_debit", - "async": true, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "iban", - "api_path": { - "v1": "sepa_debit[iban]" - } - }, - { - "type": "billing_address", - "allowed_country_codes": null - }, - { - "type": "sepa_mandate" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "processing": { - "type": "finished" - }, - "succeeded": { - "type": "finished" - } - } - } - }, - { - "type": "eps", - "async": false, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - }, - { - "type": "placeholder", - "for": "email" - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "selector", - "translation_id": "upe.labels.eps.bank", - "items": [ - { - "display_text": "Ärzte- und Apothekerbank", - "api_value": "arzte_und_apotheker_bank" - }, - { - "display_text": "Austrian Anadi Bank AG", - "api_value": "austrian_anadi_bank_ag" - }, - { - "display_text": "Bank Austria", - "api_value": "bank_austria" - }, - { - "display_text": "Bankhaus Carl Spängler & Co.AG", - "api_value": "bankhaus_carl_spangler" - }, - { - "display_text": "Bankhaus Schelhammer & Schattera AG", - "api_value": "bankhaus_schelhammer_und_schattera_ag" - }, - { - "display_text": "BAWAG P.S.K. AG", - "api_value": "bawag_psk_ag" - }, - { - "display_text": "BKS Bank AG", - "api_value": "bks_bank_ag" - }, - { - "display_text": "Brüll Kallmus Bank AG", - "api_value": "brull_kallmus_bank_ag" - }, - { - "display_text": "BTV VIER LÄNDER BANK", - "api_value": "btv_vier_lander_bank" - }, - { - "display_text": "Capital Bank Grawe Gruppe AG", - "api_value": "capital_bank_grawe_gruppe_ag" - }, - { - "display_text": "Dolomitenbank", - "api_value": "dolomitenbank" - }, - { - "display_text": "Easybank AG", - "api_value": "easybank_ag" - }, - { - "display_text": "Erste Bank und Sparkassen", - "api_value": "erste_bank_und_sparkassen" - }, - { - "display_text": "Hypo Alpe-Adria-Bank International AG", - "api_value": "hypo_alpeadriabank_international_ag" - }, - { - "display_text": "HYPO NOE LB für Niederösterreich u. Wien", - "api_value": "hypo_noe_lb_fur_niederosterreich_u_wien" - }, - { - "display_text": "HYPO Oberösterreich,Salzburg,Steiermark", - "api_value": "hypo_oberosterreich_salzburg_steiermark" - }, - { - "display_text": "Hypo Tirol Bank AG", - "api_value": "hypo_tirol_bank_ag" - }, - { - "display_text": "Hypo Vorarlberg Bank AG", - "api_value": "hypo_vorarlberg_bank_ag" - }, - { - "display_text": "HYPO-BANK BURGENLAND Aktiengesellschaft", - "api_value": "hypo_bank_burgenland_aktiengesellschaft" - }, - { - "display_text": "Marchfelder Bank", - "api_value": "marchfelder_bank" - }, - { - "display_text": "Oberbank AG", - "api_value": "oberbank_ag" - }, - { - "display_text": "Raiffeisen Bankengruppe Österreich", - "api_value": "raiffeisen_bankengruppe_osterreich" - }, - { - "display_text": "Schoellerbank AG", - "api_value": "schoellerbank_ag" - }, - { - "display_text": "Sparda-Bank Wien", - "api_value": "sparda_bank_wien" - }, - { - "display_text": "Volksbank Gruppe", - "api_value": "volksbank_gruppe" - }, - { - "display_text": "Volkskreditbank AG", - "api_value": "volkskreditbank_ag" - }, - { - "display_text": "VR-Bank Braunau", - "api_value": "vr_bank_braunau" - } - ], - "api_path": { - "v1": "eps[bank]" - } - }, - { - "type": "placeholder", - "for": "billing_address" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "p24", - "async": false, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - } - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "selector", - "translation_id": "upe.labels.p24.bank", - "items": [ - { - "display_text": "Alior Bank", - "api_value": "alior_bank" - }, - { - "display_text": "Bank Millenium", - "api_value": "bank_millennium" - }, - { - "display_text": "Bank Nowy BFG S.A.", - "api_value": "bank_nowy_bfg_sa" - }, - { - "display_text": "Bank PEKAO S.A", - "api_value": "bank_pekao_sa" - }, - { - "display_text": "Bank spółdzielczy", - "api_value": "banki_spbdzielcze" - }, - { - "display_text": "BLIK", - "api_value": "blik" - }, - { - "display_text": "BNP Paribas", - "api_value": "bnp_paribas" - }, - { - "display_text": "BOZ", - "api_value": "boz" - }, - { - "display_text": "CitiHandlowy", - "api_value": "citi_handlowy" - }, - { - "display_text": "Credit Agricole", - "api_value": "credit_agricole" - }, - { - "display_text": "e-Transfer Pocztowy24", - "api_value": "etransfer_pocztowy24" - }, - { - "display_text": "Getin Bank", - "api_value": "getin_bank" - }, - { - "display_text": "IdeaBank", - "api_value": "ideabank" - }, - { - "display_text": "ING", - "api_value": "ing" - }, - { - "display_text": "inteligo", - "api_value": "inteligo" - }, - { - "display_text": "mBank", - "api_value": "mbank_mtransfer" - }, - { - "display_text": "Nest Przelew", - "api_value": "nest_przelew" - }, - { - "display_text": "Noble Pay", - "api_value": "noble_pay" - }, - { - "display_text": "Płać z iPKO (PKO BP)", - "api_value": "pbac_z_ipko" - }, - { - "display_text": "Plus Bank", - "api_value": "plus_bank" - }, - { - "display_text": "Santander", - "api_value": "santander_przelew24" - }, - { - "display_text": "Toyota Bank", - "api_value": "toyota_bank" - }, - { - "display_text": "Volkswagen Bank", - "api_value": "volkswagen_bank" - } - ], - "api_path": { - "v1": "p24[bank]" - } - }, - { - "type": "placeholder", - "for": "billing_address" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "paypal", - "async": false, - "fields": [], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "au_becs_debit", - "async": true, - "fields": [ - { - "type": "name", - "api_path": { - "v1": "billing_details[name]" - }, - "translation_id": "upe.labels.name.onAccount" - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "au_becs_bsb_number", - "api_path": { - "v1": "au_becs_debit[bsb_number]" - } - }, - { - "type": "au_becs_account_number", - "api_path": { - "v1": "au_becs_debit[account_number]" - } - }, - { - "type": "placeholder", - "for": "billing_address" - }, - { - "type": "au_becs_mandate" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "processing": { - "type": "finished" - }, - "succeeded": { - "type": "finished" - } - } - } - }, - { - "type": "revolut_pay", - "async": false, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "amazon_pay", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-amazonpay_light-5694f2030b236ad5410b5b7e52bb538c.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-amazonpay_light-f932bc37514fd6dd1f66006767287085.svg", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-amazonpay_dark-9e42e4498e0bee515c423ef66010852f.png", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-amazonpay_dark-d3ef2eeefed43cea1f969c3a2a4118d8.svg" - }, - "fields": [] - }, - { - "type": "alma", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-alma_dark-9adee7a095478e23c76054e7fcb4c275.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-alma_dark-e4cbf3b2cec6b09cb0fd10d404fbbf44.svg", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-alma_light-41fe66ba84194788e98548aa6e749c79.png", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-alma_light-74fb7ea51cccf2e1dd530f308bed9c60.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "sunbit", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-sunbit-942781207fa04c8c6244490ed648c86d.png", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-sunbit_dark-5c93adf3d043d1510fcdd449afa032de.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-sunbit-2ebac8e19b555f2081c16591e8fe4d4a.svg", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-sunbit_dark-031a417a99538497bc892f11a15dd1cb.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "billie", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-billie-e8200232ef6c1a997eb8705685917dcd.png", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-billie_dark-81eb72c63b66f9e68b15287526918836.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-billie-12c7376f5623c109996ff8cd2f6d7a05.svg", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-billie_dark-6c8ac5db6e80d5c186b280da0ccd0a5b.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "satispay", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-satispay-97b6153a7a1e191ffdfda3ca9162343f.png", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-satispay_dark-ab245f01010002d6ff7755323c1613a2.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-satispay-eca5006db20571e80caec1509c680c27.svg", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-satispay_dark-c01fc432e3c7b334f0204d011879bb1a.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "crypto", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto@3x-94c06c199e78e6d9ff9290210912bd5e.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto-15fd4ffeafd1b13e40688c8a06d79ba4.svg", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto_dark@3x-8f7b0e91b45cb56de550af37d41aac1d.png", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto_dark-f19bb5c5400c6cde94dd53b7f1ce7217.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "mobilepay", - "async": false, - "fields": [], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url", - "native_mobile_redirect_strategy" : "follow_redirects" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "zip", - "async": false, - "fields": [], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "card", - "async": false, - "fields": [ - ] - }, - { - "type": "cashapp", - "async": false, - "fields": [], - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-cashapp@3x-a89c5d8d0651cae2a511bb49a6be1cfc.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-cashapp-981164a833e417d28a8ac2684fda2324.svg" - } - }, - { - "type": "grabpay", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-grabpay@3x-e54da1d788668a5909e4801d5c243198.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-grabpay-97ee78fdbbf1890bdf19986e997e685d.svg" - }, - "fields": [] - }, - { - "type": "fpx", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-fpx@3x-305453711338125d9cb4f86ff866ba6a.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-fpx.svg" - }, - "fields": [ - { - "type": "selector", - "translation_id": "upe.labels.fpx.bank", - "items": [ - { - "display_text": "Affin Bank", - "api_value": "affin_bank" - }, - { - "display_text": "Alliance Bank", - "api_value": "alliance_bank" - }, - { - "display_text": "AmBank", - "api_value": "ambank" - }, - { - "display_text": "Bank Islam", - "api_value": "bank_islam" - }, - { - "display_text": "Bank Muamalat", - "api_value": "bank_muamalat" - }, - { - "display_text": "Bank Rakyat", - "api_value": "bank_rakyat" - }, - { - "display_text": "BSN", - "api_value": "bsn" - }, - { - "display_text": "CIMB Clicks", - "api_value": "cimb" - }, - { - "display_text": "Hong Leong Bank", - "api_value": "hong_leong_bank" - }, - { - "display_text": "HSBC BANK", - "api_value": "hsbc" - }, - { - "display_text": "KFH", - "api_value": "kfh" - }, - { - "display_text": "Maybank2E", - "api_value": "maybank2e" - }, - { - "display_text": "Maybank2U", - "api_value": "maybank2u" - }, - { - "display_text": "OCBC Bank", - "api_value": "ocbc" - }, - { - "display_text": "Public Bank", - "api_value": "public_bank" - }, - { - "display_text": "RHB Bank", - "api_value": "rhb" - }, - { - "display_text": "Standard Chartered", - "api_value": "standard_chartered" - }, - { - "display_text": "UOB Bank", - "api_value": "uob" - } - ], - "api_path": { - "v1": "fpx[bank]" - } - }, - { - "type": "placeholder", - "for": "billing_address" - } - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "alipay", - "async": false, - "fields": [] - }, - { - "type": "paynow", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-paynow@3x-06fb143923e8492b0d3fe379ad6692a9.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-paynow-b25acf96aac928b13e6206afb8982444.svg", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-paynow_dark@3x-d49fe128abf7fe78fdfa939c445ca2cc.png", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-paynow_dark-d700c844535c608fd5fd4048f6a21146.svg" - }, - "fields": [] - }, - { - "type": "promptpay", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/promptpay.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-promptpay-8bc83c76f38f2a4d3a5f64b6229450dd.svg", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-promptpay_dark-dfd2904083152023d89a8300f04a666f.svg" - }, - "fields": [ - { - "type": "placeholder", - "for": "name" - }, - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - }, - { - "type": "placeholder", - "for": "phone" - }, - { - "type": "placeholder", - "for": "billing_address" - } - ] - }, - { - "type": "twint", - "async": false, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-twint@3x-0d33d2bf7c7037878c2a42232362accb.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-twint-d86f6ccc22b739319ca4de7c83842de0.svg" - }, - "fields": [ - ], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "processing": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - }, - { - "type": "multibanco", - "async": true, - "selector_icon": { - "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-multibanco@3x-ac6cc40479db7fa84dbb390ab85789cd.png", - "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-multibanco-5823a780cf3b97484956dbb93b9ce30e.svg", - "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-multibanco_dark@3x-787e2e370aeae073b45e0e02ad1a9e5c.png", - "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-multibanco_dark-2679e8b2bc32d66da57e66cac769d628.svg" - }, - "fields": [ - { - "type": "email", - "api_path": { - "v1": "billing_details[email]" - } - } - ] - }, - { - "type": "paypay", - "async": false, - "fields": [], - "next_action_spec": { - "confirm_response_status_specs": { - "requires_action": { - "type": "redirect_to_url" - } - }, - "post_confirm_handling_pi_status_specs": { - "succeeded": { - "type": "finished" - }, - "requires_action": { - "type": "canceled" - } - } - } - } -] diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/FormSpec/FormSpecProvider.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/FormSpec/FormSpecProvider.swift index 12d61cb9d171..d6bdaa325bdf 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/FormSpec/FormSpecProvider.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/FormSpec/FormSpecProvider.swift @@ -9,12 +9,12 @@ import Foundation @_spi(STP) import StripeCore -// Intentionally force-unwrap since PaymentSheet requires this file to exist -private let formSpecsURL = StripePaymentSheetBundleLocator.resourcesBundle.url(forResource: "form_specs", withExtension: ".json")! +// Note: Local form specs file has been removed. All form specs now come from the server via loadFrom(). +private let formSpecsURL = StripePaymentSheetBundleLocator.resourcesBundle.url(forResource: "form_specs", withExtension: ".json") /// Provides FormSpecs for a given a payment method type. -/// - Note: You must `load(completion:)` to load the specs json file into memory before calling `formSpec(for:)` -/// - To overwrite any of these specs use load(from:) +/// - Note: Form specs are now loaded from the server via `loadFrom()` which is called during PaymentSheet initialization. +/// - Form specs are no longer used for form generation, only for metadata like selector icons. class FormSpecProvider { enum Error: Swift.Error { case failedToLoadSpecs @@ -33,27 +33,31 @@ class FormSpecProvider { var hasLoadedFromDisk: Bool = false /// Loads the JSON form spec from disk into memory + /// - Note: This is now a no-op as local form specs have been removed. All specs come from the server. func load(completion: ((Bool) -> Void)? = nil) { formSpecsUpdateQueue.async { [weak self] in if self?.hasLoadedFromDisk == true { completion?(true) return } - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - do { - let data = try Data(contentsOf: formSpecsURL) - let decodedFormSpecs = try decoder.decode([FormSpec].self, from: data) - self?.formSpecs = Dictionary(uniqueKeysWithValues: decodedFormSpecs.map { ($0.type, $0) }) - self?.hasLoadedFromDisk = true - completion?(true) - } catch { - let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentSheetError, - error: Error.failedToLoadSpecs) - STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) - completion?(false) - return + + // Local form specs file has been removed. Mark as loaded to prevent repeated attempts. + self?.hasLoadedFromDisk = true + + // If there's a local file (for backwards compatibility), try to load it + if let formSpecsURL = formSpecsURL { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + do { + let data = try Data(contentsOf: formSpecsURL) + let decodedFormSpecs = try decoder.decode([FormSpec].self, from: data) + self?.formSpecs = Dictionary(uniqueKeysWithValues: decodedFormSpecs.map { ($0.type, $0) }) + } catch { + // Local file not found or invalid - this is expected. Server specs will be loaded via loadFrom(). + } } + + completion?(true) } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AUBECSDebit.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AUBECSDebit.swift new file mode 100644 index 000000000000..901e4d7e2192 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AUBECSDebit.swift @@ -0,0 +1,44 @@ +// +// PaymentSheetFormFactory+AUBECSDebit.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeAUBECSDebit() -> FormElement { + // Contact information section (name with "on account" label and email) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: true, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Bank account details (BSB and account number) + let bsbField = makeBSB() + let accountNumberField = makeAUBECSAccountNumber() + let bankAccountSection = SectionElement( + title: String.Localized.bank_account_sentence_case, + elements: [bsbField, accountNumberField], + theme: theme + ) + + // Billing address section (if needed based on configuration) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + // Mandate + let mandate = makeAUBECSMandate() + + let allElements: [Element?] = [ + contactInfoSection, + bankAccountSection, + billingAddressElement, + mandate, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Affirm.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Affirm.swift new file mode 100644 index 000000000000..e3a2a229aeb0 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Affirm.swift @@ -0,0 +1,30 @@ +// +// PaymentSheetFormFactory+Affirm.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeAffirm() -> FormElement { + // Affirm header + let header = SubtitleElement(view: AffirmCopyLabel(theme: theme), isHorizontalMode: configuration.isHorizontalMode) + + // Contact information (returns nil if config is .never for all fields) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: false, + emailRequiredByPaymentMethod: false, + phoneRequiredByPaymentMethod: false + ) + + // Billing address (returns nil unless config is .full or .automatic with requirement) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [header, contactInfoSection, billingAddressElement] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AfterpayClearpay.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AfterpayClearpay.swift new file mode 100644 index 000000000000..7ccff2a4d521 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+AfterpayClearpay.swift @@ -0,0 +1,34 @@ +// +// PaymentSheetFormFactory+AfterpayClearpay.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeAfterpayClearpay() -> FormElement { + // Afterpay/Clearpay header with price breakdown + let header = makeAfterpayClearpayHeader() + + // Contact information section (name and email) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: true, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Billing address section + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: true) + + let allElements: [Element?] = [ + header, + contactInfoSection, + billingAddressElement, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+EPS.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+EPS.swift new file mode 100644 index 000000000000..c92c9d063b9c --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+EPS.swift @@ -0,0 +1,95 @@ +// +// PaymentSheetFormFactory+EPS.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeEPS() -> FormElement { + // Contact information section (name and email) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: true, + emailRequiredByPaymentMethod: isSettingUp, + phoneRequiredByPaymentMethod: false + ) + + // Bank selector dropdown + let bankDropdown = makeEPSBankDropdown() + + // Billing address section (if needed based on configuration) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [ + contactInfoSection, + bankDropdown, + billingAddressElement, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } + + private func makeEPSBankDropdown() -> PaymentMethodElementWrapper { + let banks: [(displayText: String, apiValue: String)] = [ + ("Ärzte- und Apothekerbank", "arzte_und_apotheker_bank"), + ("Austrian Anadi Bank AG", "austrian_anadi_bank_ag"), + ("Bank Austria", "bank_austria"), + ("Bankhaus Carl Spängler & Co.AG", "bankhaus_carl_spangler"), + ("Bankhaus Schelhammer & Schattera AG", "bankhaus_schelhammer_und_schattera_ag"), + ("BAWAG P.S.K. AG", "bawag_psk_ag"), + ("BKS Bank AG", "bks_bank_ag"), + ("Brüll Kallmus Bank AG", "brull_kallmus_bank_ag"), + ("BTV VIER LÄNDER BANK", "btv_vier_lander_bank"), + ("Capital Bank Grawe Gruppe AG", "capital_bank_grawe_gruppe_ag"), + ("Dolomitenbank", "dolomitenbank"), + ("Easybank AG", "easybank_ag"), + ("Erste Bank und Sparkassen", "erste_bank_und_sparkassen"), + ("Hypo Alpe-Adria-Bank International AG", "hypo_alpeadriabank_international_ag"), + ("HYPO NOE LB für Niederösterreich u. Wien", "hypo_noe_lb_fur_niederosterreich_u_wien"), + ("HYPO Oberösterreich,Salzburg,Steiermark", "hypo_oberosterreich_salzburg_steiermark"), + ("Hypo Tirol Bank AG", "hypo_tirol_bank_ag"), + ("Hypo Vorarlberg Bank AG", "hypo_vorarlberg_bank_ag"), + ("HYPO-BANK BURGENLAND Aktiengesellschaft", "hypo_bank_burgenland_aktiengesellschaft"), + ("Marchfelder Bank", "marchfelder_bank"), + ("Oberbank AG", "oberbank_ag"), + ("Raiffeisen Bankengruppe Österreich", "raiffeisen_bankengruppe_osterreich"), + ("Schoellerbank AG", "schoellerbank_ag"), + ("Sparda-Bank Wien", "sparda_bank_wien"), + ("Volksbank Gruppe", "volksbank_gruppe"), + ("Volkskreditbank AG", "volkskreditbank_ag"), + ("VR-Bank Braunau", "vr_bank_braunau"), + ] + + let dropdownItems: [DropdownFieldElement.DropdownItem] = banks.map { + .init( + pickerDisplayName: $0.displayText, + labelDisplayName: $0.displayText, + accessibilityValue: $0.displayText, + rawData: $0.apiValue + ) + } + + // Check if there's a previous customer input for this field + let previousCustomerInputIndex = dropdownItems.firstIndex { item in + item.rawData == previousCustomerInput?.paymentMethodParams.eps?.bank + } + + let dropdownField = DropdownFieldElement( + items: dropdownItems, + defaultIndex: previousCustomerInputIndex ?? 0, + label: STPLocalizedString("EPS Bank", "Label title for EPS Bank"), + theme: theme + ) + + return PaymentMethodElementWrapper(dropdownField) { dropdown, params in + let selectedBank = dropdown.selectedItem.rawData + let eps = params.paymentMethodParams.eps ?? STPPaymentMethodEPSParams() + eps.bank = selectedBank + params.paymentMethodParams.eps = eps + return params + } + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FPX.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FPX.swift new file mode 100644 index 000000000000..bafda2071c68 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FPX.swift @@ -0,0 +1,78 @@ +// +// PaymentSheetFormFactory+FPX.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeFPX() -> FormElement { + // Bank selector dropdown + let bankDropdown = makeFPXBankDropdown() + + // Billing address section (if needed based on configuration) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [ + bankDropdown, + billingAddressElement, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } + + private func makeFPXBankDropdown() -> PaymentMethodElementWrapper { + let banks: [(displayText: String, apiValue: String)] = [ + ("Affin Bank", "affin_bank"), + ("Alliance Bank", "alliance_bank"), + ("AmBank", "ambank"), + ("Bank Islam", "bank_islam"), + ("Bank Muamalat", "bank_muamalat"), + ("Bank Rakyat", "bank_rakyat"), + ("BSN", "bsn"), + ("CIMB Clicks", "cimb"), + ("Hong Leong Bank", "hong_leong_bank"), + ("HSBC BANK", "hsbc"), + ("KFH", "kfh"), + ("Maybank2E", "maybank2e"), + ("Maybank2U", "maybank2u"), + ("OCBC Bank", "ocbc"), + ("Public Bank", "public_bank"), + ("RHB Bank", "rhb"), + ("Standard Chartered", "standard_chartered"), + ("UOB Bank", "uob"), + ] + + let dropdownItems: [DropdownFieldElement.DropdownItem] = banks.map { + .init( + pickerDisplayName: $0.displayText, + labelDisplayName: $0.displayText, + accessibilityValue: $0.displayText, + rawData: $0.apiValue + ) + } + + // Check if there's a previous customer input for this field + let previousCustomerInputIndex = dropdownItems.firstIndex { item in + item.rawData == previousCustomerInput?.paymentMethodParams.fpx?.rawBankString + } + + let dropdownField = DropdownFieldElement( + items: dropdownItems, + defaultIndex: previousCustomerInputIndex ?? 0, + label: STPLocalizedString("FPX Bank", "Select a bank dropdown for FPX"), + theme: theme + ) + + return PaymentMethodElementWrapper(dropdownField) { dropdown, params in + let selectedBank = dropdown.selectedItem.rawData + let fpx = params.paymentMethodParams.fpx ?? STPPaymentMethodFPXParams() + fpx.rawBankString = selectedBank + params.paymentMethodParams.fpx = fpx + return params + } + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FormSpec.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FormSpec.swift deleted file mode 100644 index 30b4d3fa1d40..000000000000 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+FormSpec.swift +++ /dev/null @@ -1,235 +0,0 @@ -// -// PaymentSheetFormFactory+FormSpec.swift -// StripePaymentSheet -// -// Created by Yuki Tokuhiro on 2/15/22. -// Copyright © 2022 Stripe, Inc. All rights reserved. -// - -import Foundation -@_spi(STP) import StripeCore -@_spi(STP) import StripeUICore - -extension PaymentSheetFormFactory { - func makeFormElementFromSpec( - spec: FormSpec, - additionalElements: [Element] = [] - ) -> PaymentMethodElementWrapper { - let elements = makeFormElements(from: spec) - let formElement = FormElement( - autoSectioningElements: elements + additionalElements, - theme: theme) - return makeDefaultsApplierWrapper(for: formElement) - } - - private func makeFormElements(from spec: FormSpec) -> [Element] { - // These fields may be added according to `configuration.billingDetailsCollectionConfiguration` if they - // aren't already present. - var billingDetailsFields: [FormSpec.PlaceholderSpec.PlaceholderField] = [ - .name, - .email, - .phone, - .billingAddress, - ] - - // These fields will need to be connected. - var countryElement: Element? - var billingAddressElement: Element? - var phoneElement: Element? - - var elements: [Element] = [] - for fieldSpec in spec.fields { - guard let element = fieldSpecToElement(fieldSpec: fieldSpec) else { continue } - if let fieldToRemove = fieldToRemove(from: fieldSpec) { - billingDetailsFields.remove(fieldToRemove) - } - - if fieldSpec.isCountrySpec { - countryElement = element - } - if fieldSpec.isPhoneSpec { - phoneElement = element - } - if fieldSpec.isAddressSpec { - billingAddressElement = element - } - - elements.append(element) - } - - // Add billing details fields if they are needed and not already present. - for field in billingDetailsFields { - guard let element = makeOptionalBillingDetailsField(for: field) else { continue } - - switch field { - case .phone: - phoneElement = element - case .billingAddress: - billingAddressElement = element - default: break - } - - elements.append(element) - } - - connectBillingDetailsFields( - countryElement: countryElement as? PaymentMethodElementWrapper, - addressElement: billingAddressElement as? PaymentMethodElementWrapper, - phoneElement: phoneElement as? PaymentMethodElementWrapper) - - return elements - } - - private func fieldToRemove(from fieldSpec: FormSpec.FieldSpec) -> FormSpec.PlaceholderSpec.PlaceholderField? { - switch fieldSpec { - case .name: - return .name - case .email: - return .email - case .billing_address: - return .billingAddress - case .placeholder(let placeholder): - switch placeholder.field { - case .name: - return .name - case .email: - return .email - case .phone: - return .phone - case .billingAddress, .billingAddressWithoutCountry: - return .billingAddress - default: return nil - } - default: return nil - } - } - - private func fieldSpecToElement(fieldSpec: FormSpec.FieldSpec) -> Element? { - switch fieldSpec { - case .name(let spec): - return configuration.billingDetailsCollectionConfiguration.name != .never - ? makeName(label: spec.translationId?.localizedValue, apiPath: spec.apiPath?["v1"]) - : nil - case .email(let spec): - return configuration.billingDetailsCollectionConfiguration.email != .never - ? makeEmail(apiPath: spec.apiPath?["v1"]) - : nil - case .selector(let selectorSpec): - return makeDropdown(for: selectorSpec) - case .billing_address(let countrySpec): - return configuration.billingDetailsCollectionConfiguration.address != .never - ? makeBillingAddressSection(countries: countrySpec.allowedCountryCodes) - : nil - case .country(let spec): - return makeCountry(countryCodes: spec.allowedCountryCodes, apiPath: spec.apiPath?["v1"]) - case .affirm_header: - return SubtitleElement(view: AffirmCopyLabel(theme: theme), isHorizontalMode: configuration.isHorizontalMode) - case .klarna_header: - return makeCopyLabel(text: .Localized.buy_now_or_pay_later_with_klarna) - case .klarna_country(let spec): - return makeKlarnaCountry(apiPath: spec.apiPath?["v1"])! - case .au_becs_bsb_number(let spec): - return makeBSB(apiPath: spec.apiPath?["v1"]) - case .au_becs_account_number(let spec): - return makeAUBECSAccountNumber(apiPath: spec.apiPath?["v1"]) - case .au_becs_mandate: - return makeAUBECSMandate() - case .afterpay_header: - return makeAfterpayClearpayHeader() - case .iban(let spec): - return makeIban(apiPath: spec.apiPath?["v1"]) - case .sepa_mandate: - return makeSepaMandate() - case .placeholder(let spec): - return makePlaceholder(for: spec) - case .unknown: - return nil - } - } - - func makePlaceholder(for spec: FormSpec.PlaceholderSpec) -> Element? { - let field = spec.field - guard field != .unknown else { return nil } - return makeOptionalBillingDetailsField(for: field) - } - - func makeOptionalBillingDetailsField(for field: FormSpec.PlaceholderSpec.PlaceholderField) -> Element? { - switch field { - case .name: - return configuration.billingDetailsCollectionConfiguration.name == .always ? makeName() : nil - case .email: - return configuration.billingDetailsCollectionConfiguration.email == .always ? makeEmail() : nil - case .phone: - return configuration.billingDetailsCollectionConfiguration.phone == .always ? makePhone() : nil - case .billingAddress: - return configuration.billingDetailsCollectionConfiguration.address == .full - ? makeBillingAddressSection(countries: configuration.billingDetailsCollectionConfiguration.allowedCountriesArray) - : nil - case .billingAddressWithoutCountry: - return configuration.billingDetailsCollectionConfiguration.address == .full - ? makeBillingAddressSection(collectionMode: .noCountry, countries: configuration.billingDetailsCollectionConfiguration.allowedCountriesArray) - : nil - case .unknown: return nil - } - } - - func makeDropdown(for selectorSpec: FormSpec.SelectorSpec) -> PaymentMethodElementWrapper { - if selectorSpec.apiPath?["v1"] == nil { - let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentSheetFormFactoryError, - error: Error.missingV1FromSelectorSpec, - additionalNonPIIParams: ["payment_method": paymentMethod.identifier]) - analyticsHelper?.analyticsClient.log(analytic: errorAnalytic) - } - stpAssert(selectorSpec.apiPath?["v1"] != nil) // If there's no api path, the dropdown selection is unused! - let dropdownItems: [DropdownFieldElement.DropdownItem] = selectorSpec.items.map { - .init(pickerDisplayName: $0.displayText, labelDisplayName: $0.displayText, accessibilityValue: $0.displayText, rawData: $0.apiValue ?? $0.displayText) - } - let previousCustomerInputIndex = dropdownItems.firstIndex { item in - item.rawData == getPreviousCustomerInput(for: selectorSpec.apiPath?["v1"]) - } - let dropdownField = DropdownFieldElement( - items: dropdownItems, - defaultIndex: previousCustomerInputIndex ?? 0, - label: selectorSpec.translationId.localizedValue, - theme: theme - ) - return PaymentMethodElementWrapper(dropdownField) { dropdown, params in - let selectedValue = dropdown.selectedItem.rawData - // TODO: Determine how to handle multiple versions - if let apiPathKey = selectorSpec.apiPath?["v1"] { - params.paymentMethodParams.additionalAPIParameters[apiPathKey] = selectedValue - } - return params - } - } -} - -extension FormSpec.FieldSpec { - var isCountrySpec: Bool { - switch self { - case .country, .klarna_country: return true - default: return false - } - } - - var isPhoneSpec: Bool { - if case .placeholder(let placeholderSpec) = self { - return placeholderSpec.field == .phone - } - return false - } - - var isAddressSpec: Bool { - switch self { - case .billing_address: return true - case .placeholder(let placeholderSpec): - switch placeholderSpec.field { - case .billingAddress, .billingAddressWithoutCountry: return true - default: break - } - default: break - } - - return false - } -} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Klarna.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Klarna.swift new file mode 100644 index 000000000000..d777f05806f3 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Klarna.swift @@ -0,0 +1,57 @@ +// +// PaymentSheetFormFactory+Klarna.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeKlarnaExplicit() -> FormElement { + // Klarna header + let header = makeCopyLabel(text: .Localized.buy_now_or_pay_later_with_klarna) + + // Email field + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: false, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Klarna country selector + let countryElement = makeKlarnaCountry(apiPath: "billing_details[address][country]") + + // Billing address section (without country since klarna_country handles it) + let billingAddressElement: Element? = { + if configuration.billingDetailsCollectionConfiguration.address == .full { + return makeBillingAddressSection(collectionMode: .noCountry, countries: configuration.billingDetailsCollectionConfiguration.allowedCountriesArray) + } + return nil + }() + + // Connect country and address fields + if let countryDropdown = countryElement as? PaymentMethodElementWrapper, + let addressSection = billingAddressElement as? PaymentMethodElementWrapper { + connectBillingDetailsFields( + countryElement: countryDropdown, + addressElement: addressSection, + phoneElement: nil + ) + } + + // Mandate for setup intents + let mandate: Element? = isSettingUp ? makeKlarnaMandate() : nil + + let allElements: [Element?] = [ + header, + contactInfoSection, + countryElement, + billingAddressElement, + mandate, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Multibanco.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Multibanco.swift new file mode 100644 index 000000000000..869a25df15f0 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Multibanco.swift @@ -0,0 +1,27 @@ +// +// PaymentSheetFormFactory+Multibanco.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeMultibanco() -> FormElement { + // Contact information (email required by Multibanco) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: false, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Billing address (returns nil unless config is .full or .automatic with requirement) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [contactInfoSection, billingAddressElement] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+P24.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+P24.swift new file mode 100644 index 000000000000..feac5e348243 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+P24.swift @@ -0,0 +1,91 @@ +// +// PaymentSheetFormFactory+P24.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeP24() -> FormElement { + // Contact information section (name and email) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: true, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Bank selector dropdown + let bankDropdown = makeP24BankDropdown() + + // Billing address section (if needed based on configuration) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [ + contactInfoSection, + bankDropdown, + billingAddressElement, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } + + private func makeP24BankDropdown() -> PaymentMethodElementWrapper { + let banks: [(displayText: String, apiValue: String)] = [ + ("Alior Bank", "alior_bank"), + ("Bank Millenium", "bank_millennium"), + ("Bank Nowy BFG S.A.", "bank_nowy_bfg_sa"), + ("Bank PEKAO S.A", "bank_pekao_sa"), + ("Bank spółdzielczy", "banki_spbdzielcze"), + ("BLIK", "blik"), + ("BNP Paribas", "bnp_paribas"), + ("BOZ", "boz"), + ("CitiHandlowy", "citi_handlowy"), + ("Credit Agricole", "credit_agricole"), + ("e-Transfer Pocztowy24", "etransfer_pocztowy24"), + ("Getin Bank", "getin_bank"), + ("IdeaBank", "ideabank"), + ("ING", "ing"), + ("inteligo", "inteligo"), + ("mBank", "mbank_mtransfer"), + ("Nest Przelew", "nest_przelew"), + ("Noble Pay", "noble_pay"), + ("Płać z iPKO (PKO BP)", "pbac_z_ipko"), + ("Plus Bank", "plus_bank"), + ("Santander", "santander_przelew24"), + ("Toyota Bank", "toyota_bank"), + ("Volkswagen Bank", "volkswagen_bank"), + ] + + let dropdownItems: [DropdownFieldElement.DropdownItem] = banks.map { + .init( + pickerDisplayName: $0.displayText, + labelDisplayName: $0.displayText, + accessibilityValue: $0.displayText, + rawData: $0.apiValue + ) + } + + // Check if there's a previous customer input for this field + let previousCustomerInputIndex = dropdownItems.firstIndex { item in + item.rawData == previousCustomerInput?.paymentMethodParams.przelewy24?.bank + } + + let dropdownField = DropdownFieldElement( + items: dropdownItems, + defaultIndex: previousCustomerInputIndex ?? 0, + label: STPLocalizedString("Przelewy24 Bank", "Label title for Przelewy24 Bank"), + theme: theme + ) + + return PaymentMethodElementWrapper(dropdownField) { dropdown, params in + let selectedBank = dropdown.selectedItem.rawData + let p24 = params.paymentMethodParams.przelewy24 ?? STPPaymentMethodPrzelewy24Params() + p24.bank = selectedBank + params.paymentMethodParams.przelewy24 = p24 + return params + } + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+PromptPay.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+PromptPay.swift new file mode 100644 index 000000000000..56856f50037f --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+PromptPay.swift @@ -0,0 +1,30 @@ +// +// PaymentSheetFormFactory+PromptPay.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makePromptPay() -> FormElement { + // Contact information section (all optional) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: false, + emailRequiredByPaymentMethod: true, + phoneRequiredByPaymentMethod: false + ) + + // Billing address section (optional) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + let allElements: [Element?] = [ + contactInfoSection, + billingAddressElement, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+iDEAL.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+iDEAL.swift new file mode 100644 index 000000000000..41aa945ed2c6 --- /dev/null +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+iDEAL.swift @@ -0,0 +1,87 @@ +// +// PaymentSheetFormFactory+iDEAL.swift +// StripePaymentSheet +// + +import Foundation +@_spi(STP) import StripeCore +@_spi(STP) import StripePayments +@_spi(STP) import StripeUICore + +extension PaymentSheetFormFactory { + func makeiDEAL() -> FormElement { + // Contact information section (name and email) + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: true, + emailRequiredByPaymentMethod: isSettingUp, + phoneRequiredByPaymentMethod: false + ) + + // Bank selector dropdown + let bankDropdown = makeiDEALBankDropdown() + + // Billing address section (if needed based on configuration) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + + // Mandate and checkbox for setup intents + let mandate: Element? = isSettingUp ? makeSepaMandate() : nil // Note: We show a SEPA mandate b/c iDEAL saves bank details as a SEPA Direct Debit Payment Method + let checkboxElement = makeSepaBasedPMCheckbox() + + let allElements: [Element?] = [ + contactInfoSection, + bankDropdown, + billingAddressElement, + checkboxElement, + mandate, + ] + let autoSectioningElements = allElements.compactMap { $0 } + return FormElement(autoSectioningElements: autoSectioningElements, theme: theme) + } + + private func makeiDEALBankDropdown() -> PaymentMethodElementWrapper { + let banks: [(displayText: String, apiValue: String)] = [ + ("ABN Amro", "abn_amro"), + ("ASN Bank", "asn_bank"), + ("bunq B.V.", "bunq"), + ("ING Bank", "ing"), + ("Knab", "knab"), + ("N26", "n26"), + ("Rabobank", "rabobank"), + ("RegioBank", "regiobank"), + ("Revolut", "revolut"), + ("SNS Bank", "sns_bank"), + ("Triodos Bank", "triodos_bank"), + ("Van Lanschot", "van_lanschot"), + ("Yoursafe", "yoursafe"), + ] + + let dropdownItems: [DropdownFieldElement.DropdownItem] = banks.map { + .init( + pickerDisplayName: $0.displayText, + labelDisplayName: $0.displayText, + accessibilityValue: $0.displayText, + rawData: $0.apiValue + ) + } + + // Check if there's a previous customer input for this field + let previousCustomerInputIndex = dropdownItems.firstIndex { item in + item.rawData == previousCustomerInput?.paymentMethodParams.iDEAL?.bankName + } + + let dropdownField = DropdownFieldElement( + items: dropdownItems, + defaultIndex: previousCustomerInputIndex ?? 0, + label: String.Localized.ideal_bank, + theme: theme + ) + + return PaymentMethodElementWrapper(dropdownField) { dropdown, params in + let selectedBank = dropdown.selectedItem.rawData + let ideal = params.paymentMethodParams.iDEAL ?? STPPaymentMethodiDEALParams() + ideal.bankName = selectedBank + params.paymentMethodParams.iDEAL = ideal + return params + } + } +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift index 823ef9ad36b8..5d47ac030fd2 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift @@ -208,66 +208,102 @@ class PaymentSheetFormFactory { case .instantDebits, .linkCardBrand: return makeInstantDebits() case .external(let externalPaymentOption): - return makeExternalPaymentMethodForm(subtitle: externalPaymentOption.displaySubtext, - disableBillingDetailCollection: externalPaymentOption.disableBillingDetailCollection) - case .stripe(let paymentMethod): - var additionalElements = [Element]() - - // We have two ways to create the form for a payment method - // 1. Custom, one-off forms - if paymentMethod == .card { + return makeExternalPaymentMethodForm( + subtitle: externalPaymentOption.displaySubtext, + disableBillingDetailCollection: externalPaymentOption.disableBillingDetailCollection + ) + case .stripe(let stripePaymentMethod): + switch (stripePaymentMethod, isSettingUp) { + // Payment methods with special behavior based on isSettingUp + case (.cashApp, true): + return FormElement(elements: [makeCashAppMandate()], theme: theme) + case (.cashApp, false): + return FormElement(elements: [], theme: theme) + case (.payPal, true): + return FormElement(elements: [makePaypalMandate()], theme: theme) + case (.payPal, false): + return FormElement(elements: [], theme: theme) + case (.revolutPay, true): + return FormElement(elements: [makeRevolutPayMandate()], theme: theme) + case (.revolutPay, false): + return FormElement(elements: [], theme: theme) + case (.amazonPay, true): + return FormElement(elements: [makeAmazonPayMandate()], theme: theme) + case (.amazonPay, false): + return FormElement(elements: [], theme: theme) + case (.satispay, true): + return FormElement(elements: [makeSatispayMandate()], theme: theme) + case (.satispay, false): + return FormElement(elements: [], theme: theme) + + // Payment methods with explicit form definitions (isSettingUp handled internally) + case (.card, _): return makeCard(linkAppearance: linkAppearance) - } else if paymentMethod == .USBankAccount { + case (.USBankAccount, _): return makeUSBankAccount(merchantName: configuration.merchantDisplayName) - } else if paymentMethod == .UPI { + case (.UPI, _): return makeUPI() - } else if paymentMethod == .cashApp && isSettingUp { - // special case, display mandate for Cash App when setting up or pi+sfu - additionalElements = [makeCashAppMandate()] - } else if paymentMethod == .payPal && isSettingUp { - // Paypal requires mandate when setting up - additionalElements = [makePaypalMandate()] - } else if paymentMethod == .revolutPay && isSettingUp { - // special case, display mandate for revolutPay when setting up or pi+sfu - additionalElements = [makeRevolutPayMandate()] - } else if paymentMethod == .klarna && isSettingUp { - // special case, display mandate for Klarna when setting up or pi+sfu - additionalElements = [makeKlarnaMandate()] - } else if paymentMethod == .amazonPay && isSettingUp { - // special case, display mandate for Amazon Pay when setting up or pi+sfu - additionalElements = [makeAmazonPayMandate()] - } else if paymentMethod == .satispay && isSettingUp { - // special case, display mandate for Satispay when setting up or pi+sfu - additionalElements = [makeSatispayMandate()] - } else if paymentMethod == .bancontact { + case (.bancontact, _): return makeBancontact() - } else if paymentMethod == .bacsDebit { + case (.bacsDebit, _): return makeBacsDebit() - } else if paymentMethod == .blik { + case (.blik, _): return makeBLIK() - } else if paymentMethod == .OXXO { - return makeOXXO() - } else if paymentMethod == .konbini { + case (.OXXO, _): + return makeOXXO() + case (.konbini, _): return makeKonbini() - } else if paymentMethod == .boleto { + case (.boleto, _): return makeBoleto() - } else if paymentMethod == .swish { + case (.swish, _): return makeSwish() - } + case (.przelewy24, _): + return makeP24() + case (.EPS, _): + return makeEPS() + case (.FPX, _): + return makeFPX() + case (.AUBECSDebit, _): + return makeAUBECSDebit() + case (.afterpayClearpay, _): + return makeAfterpayClearpay() + case (.affirm, _): + return makeAffirm() + case (.klarna, _): + return makeKlarnaExplicit() + case (.promptPay, _): + return makePromptPay() + case (.multibanco, _): + return makeMultibanco() + case (.iDEAL, _): + return makeiDEAL() + case (.SEPADebit, _): + return makeSepaDebit() - guard let spec = FormSpecProvider.shared.formSpec(for: paymentMethod.identifier) else { - let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentSheetFormFactoryError, error: Error.missingFormSpec, additionalNonPIIParams: ["payment_method": paymentMethod.identifier]) + // Payment methods with no required fields (but still respect .always config) + case (.alma, _), + (.sunbit, _), + (.billie, _), + (.crypto, _), + (.mobilePay, _), + (.zip, _), + (.grabPay, _), + (.alipay, _), + (.paynow, _), + (.twint, _), + (.payPay, _): + return makeFormWithOptionalBillingDetails() + + // Fallback for any payment method without an explicit definition + default: + let errorAnalytic = ErrorAnalytic( + event: .unexpectedPaymentSheetFormFactoryError, + error: Error.missingFormSpec, + additionalNonPIIParams: ["payment_method": stripePaymentMethod.identifier] + ) analyticsHelper?.analyticsClient.log(analytic: errorAnalytic) return FormElement(elements: [], theme: theme) } - if paymentMethod == .iDEAL { - return makeiDEAL(spec: spec) - } else if paymentMethod == .SEPADebit { - return makeSepaDebit() - } - - // 2. Element-based forms defined in JSON - return makeFormElementFromSpec(spec: spec, additionalElements: additionalElements) } } } @@ -634,31 +670,6 @@ extension PaymentSheetFormFactory { ) } - func makeiDEAL(spec: FormSpec) -> PaymentMethodElement { - let contactSection: Element? = makeContactInformationSection( - nameRequiredByPaymentMethod: true, - emailRequiredByPaymentMethod: isSettingUp, - phoneRequiredByPaymentMethod: false - ) - // Hack: Use the luxe spec to make the dropdown for convenience; it has the latest list of banks - let bankDropdown: Element? = spec.fields.reduce(nil) { dropdown, spec in - // Find the dropdown spec - if case .selector(let spec) = spec { - return makeDropdown(for: spec) - } - return dropdown - } - - let addressSection: Element? = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) - let mandate: Element? = isSettingUp ? makeSepaMandate() : nil // Note: We show a SEPA mandate b/c iDEAL saves bank details as a SEPA Direct Debit Payment Method - let checkboxElement = makeSepaBasedPMCheckbox() - let elements: [Element?] = [contactSection, bankDropdown, addressSection, checkboxElement, mandate] - return FormElement( - autoSectioningElements: elements.compactMap { $0 }, - theme: theme - ) - } - func makeUSBankAccount(merchantName: String) -> PaymentMethodElement { let isSaving = BoolReference() let defaultCheckbox: Element? = { @@ -760,6 +771,18 @@ extension PaymentSheetFormFactory { return FormElement(elements: [contactInfoSection, billingDetails], theme: theme) } + /// Creates a form for payment methods with no required fields, but that still respect .always billing configuration + func makeFormWithOptionalBillingDetails() -> PaymentMethodElement { + let contactInfoSection = makeContactInformationSection( + nameRequiredByPaymentMethod: false, + emailRequiredByPaymentMethod: false, + phoneRequiredByPaymentMethod: false + ) + let billingAddressElement = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false) + let elements = [contactInfoSection, billingAddressElement].compactMap { $0 } + return FormElement(elements: elements, theme: theme) + } + // Only show checkbox for PI+SFU & Setup Intent func makeSepaBasedPMCheckbox() -> Element? { let isSaving = BoolReference() diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheetFormFactoryTest.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheetFormFactoryTest.swift index 1dec209e947e..df446282c186 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheetFormFactoryTest.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/PaymentSheetFormFactoryTest.swift @@ -123,945 +123,141 @@ class PaymentSheetFormFactoryTest: XCTestCase { XCTAssertEqual(name_with_previous_customer_input.validationState, .valid) } - func testNameValueWrittenToLocationDefinedAPIPath() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.name = "someName" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let nameSpec = FormSpec.NameFieldSpec( - apiPath: ["v1": "custom_location[name]"], - translationId: nil - ) - let spec = FormSpec( - type: "grabpay", - async: false, - fields: [.name(nameSpec)], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.billingDetails?.name) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[name]"] - as! String, - "someName" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .grabPay) - } - - func testNameValueWrittenToLocationUndefinedAPIPath() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.name = "someName" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let nameSpec = FormSpec.NameFieldSpec(apiPath: nil, translationId: nil) - let spec = FormSpec( - type: "grabpay", - async: false, - fields: [.name(nameSpec)], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[name]"] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.name, "someName") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .grabPay) - } - - func testEmailOverrideApiPathBySpec() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.email = "email@stripe.com" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let email = factory.makeEmail(apiPath: "custom_location[email]") - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = email.updateParams(params: params) - - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[email]"] - as! String, - "email@stripe.com" - ) - XCTAssertNil(updatedParams?.paymentMethodParams.billingDetails?.email) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .grabPay) - - // Using the params as previous customer input... - let email_with_previous_customer_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay), - previousCustomerInput: updatedParams - ).makeName(apiPath: "custom_location[email]") - // ...should result in a valid element filled out with the previous customer input - XCTAssertEqual(email_with_previous_customer_input.element.text, "email@stripe.com") - XCTAssertEqual(email_with_previous_customer_input.validationState, .valid) - } - - func testEmailValueWrittenToDefaultLocation() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.email = "email@stripe.com" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let email = factory.makeEmail() - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = email.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.email, "email@stripe.com") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[email]"] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .grabPay) - - // Using the params as previous customer input... - let email_with_previous_customer_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay), - previousCustomerInput: updatedParams - ).makeEmail() - // ...should result in a valid element filled out with the previous customer input - XCTAssertEqual(email_with_previous_customer_input.element.text, "email@stripe.com") - XCTAssertEqual(email_with_previous_customer_input.validationState, .valid) - } - - func testEmailValueWrittenToLocationDefinedAPIPath() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.email = "email@stripe.com" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let emailSpec = FormSpec.BaseFieldSpec(apiPath: ["v1": "custom_location[email]"]) - let spec = FormSpec( - type: "mock_pm", - async: false, - fields: [.email(emailSpec)], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.billingDetails?.email) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[email]"] - as! String, - "email@stripe.com" - ) - } - - func testPhoneValueWrittenToDefaultLocation() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.phone = "+15555555555" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let phoneElement = factory.makePhone() - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = phoneElement.updateParams(params: params) - - XCTAssertEqual( - updatedParams?.paymentMethodParams.billingDetails?.phone, - "+15555555555" - ) - - // Using the params as previous customer input... - let phone_with_previous_customer_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay), - previousCustomerInput: updatedParams - ).makePhone() - // ...should result in a valid element filled out with the previous customer input - XCTAssertEqual(phone_with_previous_customer_input.element.selectedCountryCode, "US") - XCTAssertEqual(phone_with_previous_customer_input.validationState, .valid) - } - - func testEmailValueWrittenToLocationUndefinedAPIPath() { - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails.email = "email@stripe.com" - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - - let emailSpec = FormSpec.BaseFieldSpec(apiPath: nil) - let spec = FormSpec( - type: "mock_pm", - async: false, - fields: [.email(emailSpec)], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.grabPay)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.email, "email@stripe.com") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[email]"] - ) - } - - func testMakeFormElement_dropdown() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit) - ) - let selectorSpec = FormSpec.SelectorSpec( - translationId: .eps_bank, - items: [ - .init(displayText: "d1", apiValue: "123"), - .init(displayText: "d2", apiValue: "456"), - ], - apiPath: ["v1": "custom_location[selector]"] - ) - let spec = FormSpec( - type: "sepa_debit", - async: false, - fields: [.selector(selectorSpec)], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.SEPADebit)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_location[selector]"] - as! String, - "123" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .SEPADebit) - - // Given a dropdown... - let dropdown = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit) - ).makeDropdown(for: selectorSpec) - // ...with a selection *different* from the default of 0 - dropdown.element.select(index: 1) - // ...using the params as previous customer input to create a new dropdown... - let previousCustomerInput = dropdown.updateParams(params: IntentConfirmParams(type: .stripe(.SEPADebit))) - let dropdown_with_previous_customer_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit), - previousCustomerInput: previousCustomerInput - ).makeDropdown(for: selectorSpec) - - // ...should result in a valid element filled out with the previous customer input - XCTAssertEqual(dropdown_with_previous_customer_input.element.selectedIndex, 1) - XCTAssertEqual(dropdown_with_previous_customer_input.validationState, .valid) - } - - func testMakeFormElement_KlarnaCountry_UndefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.klarna) - ) - let spec = FormSpec( - type: "klarna", - async: false, - fields: [.klarna_country(.init(apiPath: nil))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.klarna)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.address?.country, "US") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "billing_details[address][country]" - ] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "klarna") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .klarna) - } - - func testMakeFormElement_KlarnaCountry_DefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.klarna) - ) - let spec = FormSpec( - type: "klarna", - async: false, - fields: [.klarna_country(.init(apiPath: ["v1": "billing_details[address][country]"]))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.klarna)) - - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.billingDetails?.address?.country) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "billing_details[address][country]" - ] as! String, - "US" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "klarna") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .klarna) - } - - func testMakeFormElement_BSBNumber() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) - ) - let bsb = factory.makeBSB(apiPath: nil) - bsb.element.setText("000-000") - - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - let updatedParams = bsb.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.auBECSDebit?.bsbNumber, "000000") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters["au_becs_debit[bsb_number]"] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) - // Using the params as previous customer input... - let bsb_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - previousCustomerInput: updatedParams - ).makeBSB() - // ...should result in a valid, filled out element - XCTAssert(bsb_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = bsb_with_previous_input.updateParams(params: .init(type: .stripe(.AUBECSDebit))) - XCTAssertEqual(updatedParams_with_previous_input?.paymentMethodParams.auBECSDebit?.bsbNumber, "000000") - } - - func testMakeFormElement_BSBNumber_withAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) - ) - let bsb = factory.makeBSB(apiPath: "custom_path[bsb_number]") - bsb.element.setText("000-000") - - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - let updatedParams = bsb.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.auBECSDebit?.bsbNumber) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["custom_path[bsb_number]"] - as! String, - "000000" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) - // Using the params as previous customer input... - let bsb_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - previousCustomerInput: updatedParams - ).makeBSB(apiPath: "custom_path[bsb_number]") - // ...should result in a valid, filled out element - XCTAssert(bsb_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = bsb_with_previous_input.updateParams(params: .init(type: .stripe(.AUBECSDebit))) - XCTAssertEqual( - updatedParams_with_previous_input?.paymentMethodParams.additionalAPIParameters["custom_path[bsb_number]"] - as! String, - "000000" - ) - } + // MARK: - Tests for explicitly defined payment method forms - func testMakeFormElement_BSBNumber_UndefinedAPIPath() { - let configuration = PaymentSheet.Configuration() + func testMakeP24() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) - ) - let spec = FormSpec( - type: "au_becs_debit", - async: false, - fields: [.au_becs_bsb_number(.init(apiPath: nil))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("000-000") - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.auBECSDebit?.bsbNumber, "000000") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters["au_becs_debit[bsb_number]"] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) - - // Using the params as previous customer input... - let bsb_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - previousCustomerInput: updatedParams - ).makeBSB() - // ...should result in a valid, filled out element - XCTAssert(bsb_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = bsb_with_previous_input.updateParams(params: .init(type: .stripe(.AUBECSDebit))) - XCTAssertEqual(updatedParams_with_previous_input?.paymentMethodParams.auBECSDebit?.bsbNumber, "000000") - } - - func testMakeFormElement_BSBNumber_DefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) - ) - let spec = FormSpec( - type: "au_becs_debit", - async: false, - fields: [.au_becs_bsb_number(.init(apiPath: ["v1": "au_becs_debit[bsb_number]"]))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("000-000") - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.auBECSDebit?.bsbNumber) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["au_becs_debit[bsb_number]"] - as! String, - "000000" + intent: ._testPaymentIntent(paymentMethodTypes: [.przelewy24]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.przelewy24) ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) - } - func testMakeFormElement_AUBECSAccountNumber_UndefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) - ) - let spec = FormSpec( - type: "au_becs_debit", - async: false, - fields: [.au_becs_account_number(.init(apiPath: nil))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("000123456") - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.auBECSDebit?.accountNumber, "000123456") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "au_becs_debit[account_number]" - ] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) + let form = factory.makeP24() + XCTAssertNotNil(form) - // Using the params as previous customer input... - let form_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - previousCustomerInput: updatedParams - ).makeFormElementFromSpec(spec: spec) - // ...should result in a valid, filled out element - XCTAssert(form_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = form_with_previous_input.updateParams(params: .init(type: .stripe(.AUBECSDebit))) - XCTAssertEqual(updatedParams_with_previous_input?.paymentMethodParams.auBECSDebit?.accountNumber, "000123456") + // Verify form contains bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("Przelewy24 Bank"), "P24 form should contain bank dropdown") } - func testMakeFormElement_AUBECSAccountNumber_DefinedAPIPath() { - let configuration = PaymentSheet.Configuration() + func testMakeEPS() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) + intent: ._testPaymentIntent(paymentMethodTypes: [.EPS]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.EPS) ) - let spec = FormSpec( - type: "au_becs_debit", - async: false, - fields: [ - .au_becs_account_number(.init(apiPath: ["v1": "au_becs_debit[account_number]"])), - ], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("000123456") - let updatedParams = formElement.updateParams(params: params) - XCTAssertNil(updatedParams?.paymentMethodParams.auBECSDebit?.accountNumber) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "au_becs_debit[account_number]" - ] as! String, - "000123456" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) + let form = factory.makeEPS() + XCTAssertNotNil(form) - // Using the params as previous customer input... - let form_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - previousCustomerInput: updatedParams - ).makeFormElementFromSpec(spec: spec) - // ...should result in a valid, filled out element - XCTAssert(form_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = form_with_previous_input.updateParams(params: .init(type: .stripe(.AUBECSDebit))) - XCTAssertEqual( - updatedParams_with_previous_input?.paymentMethodParams.additionalAPIParameters[ - "au_becs_debit[account_number]" - ] as! String, - "000123456" - ) + // Verify form contains bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("EPS Bank"), "EPS form should contain bank dropdown") } - func testMakeFormElement_AUBECSAccountNumber() { - let configuration = PaymentSheet.Configuration() + func testMakeFPX() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit) + intent: ._testPaymentIntent(paymentMethodTypes: [.FPX]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.FPX) ) - let accountNum = factory.makeAUBECSAccountNumber(apiPath: nil) - accountNum.element.setText("000123456") - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - let updatedParams = accountNum.updateParams(params: params) + let form = factory.makeFPX() + XCTAssertNotNil(form) - XCTAssertEqual(updatedParams?.paymentMethodParams.auBECSDebit?.accountNumber, "000123456") - XCTAssertNil( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "au_becs_debit[account_number]" - ] - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) + // Verify form contains bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("FPX Bank"), "FPX form should contain bank dropdown") } - func testMakeFormElement_AUBECSAccountNumber_withAPIPath() { - let configuration = PaymentSheet.Configuration() + func testMakeAUBECSDebit() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), + intent: ._testPaymentIntent(paymentMethodTypes: [.AUBECSDebit]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), paymentMethod: .stripe(.AUBECSDebit) ) - let accountNum = factory.makeAUBECSAccountNumber(apiPath: "custom_path[account_number]") - accountNum.element.setText("000123456") - - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - let updatedParams = accountNum.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.auBECSDebit?.accountNumber) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters[ - "custom_path[account_number]" - ] as! String, - "000123456" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) - } - - func testMakeFormElement_BillingAddress_UndefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.iDEAL) - ) - let spec = FormSpec( - type: "ideal", - async: false, - fields: [.country(.init(apiPath: nil, allowedCountryCodes: ["AT", "BE"]))], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.iDEAL)) - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.address?.country, "AT") - XCTAssert(updatedParams?.paymentMethodParams.additionalAPIParameters.isEmpty ?? false) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "ideal") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .iDEAL) - } - - func testMakeFormElement_Country() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.iDEAL) - ) - let country = factory.makeCountry(countryCodes: ["AT", "BE"], apiPath: nil) - (country as! PaymentMethodElementWrapper).element.select(index: 1) // select a different index than the default of 0 - - let params = IntentConfirmParams(type: .stripe(.iDEAL)) - let updatedParams = country.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.address?.country, "BE") - XCTAssert(updatedParams?.paymentMethodParams.additionalAPIParameters.isEmpty ?? false) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "ideal") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .iDEAL) - - // Using the params as previous customer input... - let country_with_previous_input = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.iDEAL), - previousCustomerInput: updatedParams - ).makeCountry(countryCodes: ["AT", "BE"], apiPath: nil) - // ...should result in a valid, filled out element - XCTAssert(country_with_previous_input.validationState == .valid) - let updatedParams_with_previous_input = country_with_previous_input.updateParams(params: .init(type: .stripe(.iDEAL))) - XCTAssertEqual(updatedParams_with_previous_input?.paymentMethodParams.billingDetails?.address?.country, "BE") - } - - func testMakeFormElement_Iban_UndefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - func makeForm(previousCustomerInput: IntentConfirmParams?) -> PaymentMethodElementWrapper { - let factory = PaymentSheetFormFactory( - intent: ._testValue(), - elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit), - previousCustomerInput: previousCustomerInput - ) - let spec = FormSpec( - type: "sepa_debit", - async: false, - fields: [.iban(.init(apiPath: nil))], - selectorIcon: nil - ) - return factory.makeFormElementFromSpec(spec: spec) - } - let formElement = makeForm(previousCustomerInput: nil) - let params = IntentConfirmParams(type: .stripe(.SEPADebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("GB33BUKB20201555555555") - let updatedParams = formElement.updateParams(params: params) - - XCTAssertEqual(updatedParams?.paymentMethodParams.sepaDebit?.iban, "GB33BUKB20201555555555") - XCTAssert(updatedParams?.paymentMethodParams.additionalAPIParameters.isEmpty ?? false) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .SEPADebit) - - // Using the params as previous customer input... - let form_with_previous_input = makeForm(previousCustomerInput: updatedParams) - // ...should result in a valid, filled out element - let updatedParams_with_previous_input = form_with_previous_input.updateParams(params: .init(type: .stripe(.SEPADebit))) - XCTAssertEqual(updatedParams_with_previous_input?.paymentMethodParams.sepaDebit?.iban, "GB33BUKB20201555555555") - } - - func testMakeFormElement_Iban_DefinedAPIPath() { - let configuration = PaymentSheet.Configuration() - func makeForm(previousCustomerInput: IntentConfirmParams?) -> PaymentMethodElementWrapper { - let factory = PaymentSheetFormFactory( - intent: ._testValue(), - elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit), - previousCustomerInput: previousCustomerInput - ) - let spec = FormSpec( - type: "sepa_debit", - async: false, - fields: [.iban(.init(apiPath: ["v1": "SEPADebit[iban]"]))], - selectorIcon: nil - ) - return factory.makeFormElementFromSpec(spec: spec) - } - - let formElement = makeForm(previousCustomerInput: nil) - let params = IntentConfirmParams(type: .stripe(.SEPADebit)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("GB33BUKB20201555555555") - let updatedParams = formElement.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.sepaDebit?.iban) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["SEPADebit[iban]"] - as! String, - "GB33BUKB20201555555555" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .SEPADebit) - - // Using the params as previous customer input... - let form_with_previous_input = makeForm(previousCustomerInput: updatedParams) - // ...should result in a valid, filled out element - let updatedParams_with_previous_input = form_with_previous_input.updateParams(params: .init(type: .stripe(.SEPADebit))) - XCTAssertEqual( - updatedParams_with_previous_input?.paymentMethodParams.additionalAPIParameters["SEPADebit[iban]"] - as! String, - "GB33BUKB20201555555555" - ) - } - - func testMakeFormElement_Iban() { - let configuration = PaymentSheet.Configuration() - let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit) - ) - let iban = factory.makeIban(apiPath: nil) - iban.element.setText("GB33BUKB20201555555555") - - let params = IntentConfirmParams(type: .stripe(.SEPADebit)) - let updatedParams = iban.updateParams(params: params) + let form = factory.makeAUBECSDebit() + XCTAssertNotNil(form) - XCTAssertEqual(updatedParams?.paymentMethodParams.sepaDebit?.iban, "GB33BUKB20201555555555") - XCTAssert(updatedParams?.paymentMethodParams.additionalAPIParameters.isEmpty ?? false) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .SEPADebit) + // Verify form contains mandate + XCTAssertNotNil(form.getAUBECSMandateElement(), "AU BECS Debit form should contain mandate") } - func testMakeFormElement_Iban_withAPIPath() { - let configuration = PaymentSheet.Configuration() + func testMakeAfterpayClearpay() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.SEPADebit) + intent: ._testPaymentIntent(paymentMethodTypes: [.afterpayClearpay]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.afterpayClearpay) ) - let iban = factory.makeIban(apiPath: "SEPADebit[iban]") - iban.element.setText("GB33BUKB20201555555555") - let params = IntentConfirmParams(type: .stripe(.SEPADebit)) - let updatedParams = iban.updateParams(params: params) - - XCTAssertNil(updatedParams?.paymentMethodParams.sepaDebit?.iban) - XCTAssertEqual( - updatedParams?.paymentMethodParams.additionalAPIParameters["SEPADebit[iban]"] - as! String, - "GB33BUKB20201555555555" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .SEPADebit) + let form = factory.makeAfterpayClearpay() + XCTAssertNotNil(form) } - func testMakeFormElement_email_with_unknownField() { - let configuration = PaymentSheet.Configuration() + func testMakeAffirm() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay) - ) - let spec = FormSpec( - type: "grabpay", - async: false, - fields: [ - .unknown("some_unknownField1"), - .email(.init(apiPath: nil)), - .unknown("some_unknownField2"), - ], - selectorIcon: nil - ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - let params = IntentConfirmParams(type: .stripe(.grabPay)) - guard let wrappedElement = firstWrappedTextFieldElement(formElement: formElement.element) else { - XCTFail("Unable to get firstElement") - return - } - - wrappedElement.element.setText("email@stripe.com") - let updatedParams = formElement.updateParams(params: params) + intent: ._testPaymentIntent(paymentMethodTypes: [.affirm]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.affirm) + ) - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.email, "email@stripe.com") - XCTAssert(updatedParams?.paymentMethodParams.additionalAPIParameters.isEmpty ?? false) + let form = factory.makeAffirm() + XCTAssertNotNil(form) } - func testMakeFormElement_BillingAddress() { - let addressSpecProvider = AddressSpecProvider() - addressSpecProvider.addressSpecs = [ - "US": AddressSpec( - format: "%N%n%O%n%A%n%C, %S %Z", - require: "ACSZ", - cityNameType: nil, - stateNameType: .state, - zip: "\\d{5}", - zipNameType: .zip - ), - ] - var configuration = PaymentSheet.Configuration() - configuration.defaultBillingDetails = PaymentSheet.BillingDetails( - address: PaymentSheet.Address( - city: "South San Francisco", - country: "US", - line1: "354 Oyster Point Blvd", - postalCode: "94080", - state: "CA" - ) - ) + func testMakeKlarna() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.AUBECSDebit), - addressSpecProvider: addressSpecProvider + intent: ._testPaymentIntent(paymentMethodTypes: [.klarna]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.klarna) ) - let accountNum = factory.makeBillingAddressSection(countries: nil) - accountNum.element.line1?.setText("123 main") - accountNum.element.line2?.setText("#501") - accountNum.element.city?.setText("AnywhereTown") - accountNum.element.state?.setRawData("California") - accountNum.element.postalCode?.setText("55555") - let params = IntentConfirmParams(type: .stripe(.AUBECSDebit)) - let updatedParams = accountNum.updateParams(params: params) + let form = factory.makeKlarnaExplicit() + XCTAssertNotNil(form) + } - XCTAssertEqual( - updatedParams?.paymentMethodParams.billingDetails?.address?.line1, - "123 main" - ) - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.address?.line2, "#501") - XCTAssertEqual(updatedParams?.paymentMethodParams.billingDetails?.address?.country, "US") - XCTAssertEqual( - updatedParams?.paymentMethodParams.billingDetails?.address?.city, - "AnywhereTown" - ) - XCTAssertEqual( - updatedParams?.paymentMethodParams.billingDetails?.address?.state, - "California" - ) - XCTAssertEqual( - updatedParams?.paymentMethodParams.billingDetails?.address?.postalCode, - "55555" + func testMakePromptPay() { + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.promptPay]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.promptPay) ) - XCTAssertEqual(updatedParams?.paymentMethodParams.rawTypeString, "au_becs_debit") - XCTAssertEqual(updatedParams?.paymentMethodParams.type, .AUBECSDebit) + + let form = factory.makePromptPay() + XCTAssertNotNil(form) } - func testMakeFormElement_AddressElementUsesDefaultCountries() { - let addressSpecProvider = addressSpecProvider(countries: ["US", "FR"]) - let configuration = PaymentSheet.Configuration() + func testMakeMultibanco() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay), - addressSpecProvider: addressSpecProvider - ) - let billingAddressSpec = FormSpec.BillingAddressSpec(allowedCountryCodes: nil) - let spec = FormSpec( - type: "grabpay", - async: false, - fields: [.billing_address(billingAddressSpec)], - selectorIcon: nil + intent: ._testPaymentIntent(paymentMethodTypes: [.multibanco]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.multibanco) ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - guard let addressSectionElement = firstAddressSectionElement(formElement: formElement.element) - else { - XCTFail("failed to get address section element") - return - } - - XCTAssertEqual(addressSectionElement.countryCodes.count, 2) - XCTAssertTrue(addressSectionElement.countryCodes.contains("US")) - XCTAssertTrue(addressSectionElement.countryCodes.contains("FR")) + let form = factory.makeMultibanco() + XCTAssertNotNil(form) } - func testMakeFormElement_AddressElementUsesAllowedCountryCodes_FR() { - let addressSpecProvider = addressSpecProvider(countries: ["US", "FR"]) - let configuration = PaymentSheet.Configuration() + func testMakeiDEAL() { let factory = PaymentSheetFormFactory( - intent: ._testValue(), elementsSession: ._testCardValue(), - configuration: .paymentElement(configuration), - paymentMethod: .stripe(.grabPay), - addressSpecProvider: addressSpecProvider - ) - let billingAddressSpec = FormSpec.BillingAddressSpec(allowedCountryCodes: ["FR"]) - let spec = FormSpec( - type: "grabpay", - async: false, - fields: [.billing_address(billingAddressSpec)], - selectorIcon: nil + intent: ._testPaymentIntent(paymentMethodTypes: [.iDEAL]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(PaymentSheet.Configuration()), + paymentMethod: .stripe(.iDEAL) ) - let formElement = factory.makeFormElementFromSpec(spec: spec) - guard let addressSectionElement = firstAddressSectionElement(formElement: formElement.element) - else { - XCTFail("failed to get address section element") - return - } + let form = factory.makeiDEAL() + XCTAssertNotNil(form) - XCTAssertEqual(addressSectionElement.countryCodes.count, 1) - XCTAssertTrue(addressSectionElement.countryCodes.contains("FR")) + // Verify form contains bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("iDEAL Bank"), "iDEAL form should contain bank dropdown") } func testNonCardsAndUSBankAccountsDontHaveSaveForFutureUseCheckbox() { @@ -2893,6 +2089,316 @@ class PaymentSheetFormFactoryTest: XCTestCase { XCTAssertTrue(hasBillingAddress, "Card form should contain billing address section when address collection is .automatic") } + // MARK: - Comprehensive tests for converted payment methods + + func testMakeP24_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + configuration.billingDetailsCollectionConfiguration.allowedCountries = ["PL", "US"] + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.przelewy24]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.przelewy24) + ) + + let form = factory.makeP24() + + // Verify name field (required by P24) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "P24 form should contain name field") + + // Verify email field (required by P24) + XCTAssertNotNil(form.getTextFieldElement("Email"), "P24 form should contain email field") + + // Verify bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("Przelewy24 Bank"), "P24 form should contain bank dropdown") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "P24 form should contain billing address section when address collection is .full") + } + + func testMakeEPS_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + configuration.billingDetailsCollectionConfiguration.allowedCountries = ["AT", "DE"] + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.EPS]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.EPS) + ) + + let form = factory.makeEPS() + + // Verify name field (required by EPS) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "EPS form should contain name field") + + // Verify email field (required by EPS when setting up) + // Note: email is only required when isSettingUp = true, but with .always config it should appear + XCTAssertNotNil(form.getTextFieldElement("Email"), "EPS form should contain email field when .always") + + // Verify bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("EPS Bank"), "EPS form should contain bank dropdown") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "EPS form should contain billing address section when address collection is .full") + } + + func testMakeFPX_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.address = .full + configuration.billingDetailsCollectionConfiguration.allowedCountries = ["MY"] + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.FPX]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.FPX) + ) + + let form = factory.makeFPX() + + // Verify bank dropdown (only required field) + XCTAssertNotNil(form.getDropdownFieldElement("FPX Bank"), "FPX form should contain bank dropdown") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "FPX form should contain billing address section when address collection is .full") + } + + func testMakeAfterpayClearpay_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.afterpayClearpay]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.afterpayClearpay) + ) + + let form = factory.makeAfterpayClearpay() + + // Verify name field (required by Afterpay) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "Afterpay form should contain name field") + + // Verify email field (required by Afterpay) + XCTAssertNotNil(form.getTextFieldElement("Email"), "Afterpay form should contain email field") + + // Verify billing address (required by Afterpay) + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "Afterpay form should contain billing address section") + } + + func testMakeiDEAL_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + configuration.billingDetailsCollectionConfiguration.allowedCountries = ["NL", "BE"] + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.iDEAL]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.iDEAL) + ) + + let form = factory.makeiDEAL() + + // Verify name field (required by iDEAL) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "iDEAL form should contain name field") + + // Verify email field (required when setting up) + XCTAssertNotNil(form.getTextFieldElement("Email"), "iDEAL form should contain email field when .always") + + // Verify bank dropdown + XCTAssertNotNil(form.getDropdownFieldElement("iDEAL Bank"), "iDEAL form should contain bank dropdown") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "iDEAL form should contain billing address section when address collection is .full") + } + + func testMakePromptPay_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.phone = .always + configuration.billingDetailsCollectionConfiguration.address = .full + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.promptPay]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.promptPay) + ) + + let form = factory.makePromptPay() + + // Verify all fields present when .always (PromptPay requires email, rest are optional) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "PromptPay form should contain name field when .always") + XCTAssertNotNil(form.getTextFieldElement("Email"), "PromptPay form should contain email field") + XCTAssertNotNil(form.getPhoneNumberElement(), "PromptPay form should contain phone field when .always") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "PromptPay form should contain billing address section when address collection is .full") + } + + func testMakeMultibanco_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + configuration.billingDetailsCollectionConfiguration.allowedCountries = ["PT"] + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.multibanco]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.multibanco) + ) + + let form = factory.makeMultibanco() + + // Verify name field when .always + XCTAssertNotNil(form.getTextFieldElement("Full name"), "Multibanco form should contain name field when .always") + + // Verify email field (required by Multibanco) + XCTAssertNotNil(form.getTextFieldElement("Email"), "Multibanco form should contain email field") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "Multibanco form should contain billing address section when address collection is .full") + } + + func testMakeAffirm_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.address = .full + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.affirm]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.affirm) + ) + + let form = factory.makeAffirm() + + // Verify all fields present when .always (Affirm has no required fields by default) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "Affirm form should contain name field when .always") + XCTAssertNotNil(form.getTextFieldElement("Email"), "Affirm form should contain email field when .always") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "Affirm form should contain billing address section when address collection is .full") + } + + func testMakeKlarna_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.phone = .always + configuration.billingDetailsCollectionConfiguration.address = .full + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.klarna]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.klarna) + ) + + let form = factory.makeKlarnaExplicit() + + // Verify name field when .always (Klarna doesn't require name by default) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "Klarna form should contain name field when .always") + + // Verify email field (required by Klarna) + XCTAssertNotNil(form.getTextFieldElement("Email"), "Klarna form should contain email field") + + // Verify phone field when .always + XCTAssertNotNil(form.getPhoneNumberElement(), "Klarna form should contain phone field when .always") + + // Verify country dropdown (Klarna-specific) + XCTAssertNotNil(form.getDropdownFieldElement("Country"), "Klarna form should contain country dropdown") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "Klarna form should contain billing address section when address collection is .full") + } + + func testMakeAUBECSDebit_withBillingDetailsCollection() { + var configuration = PaymentSheet.Configuration() + configuration.billingDetailsCollectionConfiguration.name = .always + configuration.billingDetailsCollectionConfiguration.email = .always + configuration.billingDetailsCollectionConfiguration.phone = .always + configuration.billingDetailsCollectionConfiguration.address = .full + + let factory = PaymentSheetFormFactory( + intent: ._testPaymentIntent(paymentMethodTypes: [.AUBECSDebit]), + elementsSession: ._testCardValue(), + configuration: .paymentElement(configuration), + paymentMethod: .stripe(.AUBECSDebit) + ) + + let form = factory.makeAUBECSDebit() + + // Verify name field (required by AU BECS Debit) + XCTAssertNotNil(form.getTextFieldElement("Full name"), "AU BECS Debit form should contain name field") + + // Verify email field (required by AU BECS Debit) + XCTAssertNotNil(form.getTextFieldElement("Email"), "AU BECS Debit form should contain email field") + + // Verify phone field when .always + XCTAssertNotNil(form.getPhoneNumberElement(), "AU BECS Debit form should contain phone field when .always") + + // Verify BSB number field + XCTAssertNotNil(form.getTextFieldElement("BSB number"), "AU BECS Debit form should contain BSB number field") + + // Verify account number field + XCTAssertNotNil(form.getTextFieldElement("Account number"), "AU BECS Debit form should contain account number field") + + // Verify billing address when .full + let hasBillingAddress = form.elements.contains { element in + return element is PaymentMethodElementWrapper + } + XCTAssertTrue(hasBillingAddress, "AU BECS Debit form should contain billing address section when address collection is .full") + + // Verify mandate + XCTAssertNotNil(form.getAUBECSMandateElement(), "AU BECS Debit form should contain mandate") + } + func testMakeBLIK_withAllowedCountries() { var configuration = PaymentSheet.Configuration() configuration.billingDetailsCollectionConfiguration.address = .full diff --git a/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodEPSParams.swift b/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodEPSParams.swift index c0f78b27aadc..cb3c44d1edee 100644 --- a/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodEPSParams.swift +++ b/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodEPSParams.swift @@ -10,13 +10,21 @@ import Foundation /// An object representing parameters used to create a EPS Payment Method public class STPPaymentMethodEPSParams: NSObject, STPFormEncodable { - public var additionalAPIParameters: [AnyHashable: Any] = [:] + @objc public var additionalAPIParameters: [AnyHashable: Any] = [:] + /// The customer's bank. + @objc public var bank: String? + + // MARK: - STPFormEncodable + @objc public class func rootObjectName() -> String? { return "eps" } + @objc public class func propertyNamesToFormFieldNamesMapping() -> [String: String] { - return [:] + return [ + NSStringFromSelector(#selector(getter: bank)): "bank" + ] } } diff --git a/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodPrzelewy24Params.swift b/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodPrzelewy24Params.swift index e64bb714378e..e0a9c0f25382 100644 --- a/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodPrzelewy24Params.swift +++ b/StripePayments/StripePayments/Source/API Bindings/Models/PaymentMethods/Types/STPPaymentMethodPrzelewy24Params.swift @@ -12,6 +12,10 @@ import Foundation public class STPPaymentMethodPrzelewy24Params: NSObject, STPFormEncodable { @objc public var additionalAPIParameters: [AnyHashable: Any] = [:] + /// The customer's bank. + @objc public var bank: String? + + // MARK: - STPFormEncodable @objc public class func rootObjectName() -> String? { return "p24" @@ -19,6 +23,8 @@ public class STPPaymentMethodPrzelewy24Params: NSObject, STPFormEncodable { @objc public class func propertyNamesToFormFieldNamesMapping() -> [String: String] { - return [:] + return [ + NSStringFromSelector(#selector(getter: bank)): "bank" + ] } }