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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,113 changes: 0 additions & 1,113 deletions StripePaymentSheet/StripePaymentSheet/Resources/JSON/form_specs.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<DropdownFieldElement> {
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<DropdownFieldElement> {
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
}
}
}
Loading
Loading