diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 16856e0..89f1ece 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -30,6 +30,7 @@ struct RegistrationSetupView: View { @State private var enableTimeToConfirm: Bool @State private var isTemplate: Bool @State private var isCorporateTournament: Bool + @State private var isValidating = false // Online Payment @State private var enableOnlinePayment: Bool @@ -297,31 +298,21 @@ struct RegistrationSetupView: View { } } }) - .toolbar(content: { - if focusedField != nil { - ToolbarItem(placement: .topBarLeading) { - Button("Annuler", role: .cancel) { - focusedField = nil - } - } - } - }) .toolbar { - if focusedField != nil { + if focusedField == ._stripeAccountId, stripeAccountId.isEmpty == false { ToolbarItem(placement: .keyboard) { HStack { - if focusedField == ._stripeAccountId, stripeAccountId.isEmpty == false { - Button("Effacer") { - stripeAccountId = "" - tournament.stripeAccountId = nil - } - .buttonStyle(.borderless) - Spacer() - Button("Valider") { - focusedField = nil - } - .buttonStyle(.bordered) + Button("Effacer") { + stripeAccountId = "" + stripeAccountIdIsInvalid = false + tournament.stripeAccountId = nil + } + .buttonStyle(.borderless) + Spacer() + Button("Valider") { + focusedField = nil } + .buttonStyle(.bordered) } } } @@ -418,12 +409,24 @@ struct RegistrationSetupView: View { if isCorporateTournament == false, dataStore.user.registrationPaymentMode.requiresStripe() { VStack(alignment: .leading) { - TextField("Identifiant du compte Stripe", text: $stripeAccountId) - .frame(maxWidth: .infinity) - .keyboardType(.default) - .focused($focusedField, equals: ._stripeAccountId) + LabeledContent { + if isValidating { + ProgressView() + } else if focusedField == nil, stripeAccountIdIsInvalid == false, stripeAccountId.isEmpty == false, isValidating == false { + Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) + } + } label: { + TextField("Identifiant du compte Stripe", text: $stripeAccountId) + .frame(maxWidth: .infinity) + .keyboardType(.default) + .focused($focusedField, equals: ._stripeAccountId) + .disabled(isValidating) + } if stripeAccountIdIsInvalid { - Text("Format d'identifiant Stripe invalide.").foregroundStyle(.logoRed) + Text("Identifiant Stripe invalide. Vous ne pouvez pas activer le paiement en ligne.").foregroundStyle(.logoRed) + Button("RĂ©-essayer") { + _confirmStripeAccountId() + } } } } @@ -450,7 +453,7 @@ struct RegistrationSetupView: View { } .onChange(of: focusedField) { old, new in if old == ._stripeAccountId { - _hasChanged() + _confirmStripeAccountId() } } @@ -461,12 +464,86 @@ struct RegistrationSetupView: View { if stripeAccountId.isEmpty { tournament.stripeAccountId = nil } else if stripeAccountId.count >= 5, stripeAccountId.starts(with: "acct_") { - tournament.stripeAccountId = stripeAccountId.prefixMultilineTrimmed(255) + _checkStripeAccount(stripeAccountId.prefixMultilineTrimmed(255)) } else { stripeAccountIdIsInvalid = true } } + private func _checkStripeAccount(_ accId: String) { + Task { + isValidating = true + do { + let response = try await _validateStripeAccountID(accId) + stripeAccountId = accId + stripeAccountIdIsInvalid = response.valid == false + enableOnlinePayment = response.valid + } catch { + stripeAccountIdIsInvalid = true + enableOnlinePayment = false + } + isValidating = false + } + } + + private func _validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse { + let apiPath = "\(URLs.activationHost.rawValue)/utils/validate-stripe-account/" + let url = URL(string: apiPath)! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + let body = ["account_id": accountID] + request.httpBody = try JSONEncoder().encode(body) + + do { + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + throw ValidationError.invalidResponse + } + switch httpResponse.statusCode { + case 200...299: + let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return decodedResponse + case 400: + // Handle bad request + let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return errorResponse + case 403: + // Handle permission error + let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + return errorResponse + default: + throw ValidationError.invalidResponse + } + } catch let error as ValidationError { + throw error + } catch { + throw ValidationError.networkError(error) + } + } + + struct ValidationResponse: Codable { + let valid: Bool + let account: AccountDetails? + let error: String? + } + + struct AccountDetails: Codable { + let id: String + + enum CodingKeys: String, CodingKey { + case id + } + } + + enum ValidationError: Error { + case invalidResponse + case networkError(Error) + case invalidData + } + private func _hasChanged() { hasChanges = true } @@ -496,7 +573,11 @@ struct RegistrationSetupView: View { } tournament.enableTimeToConfirm = enableTimeToConfirm - _confirmStripeAccountId() + if stripeAccountIdIsInvalid == false { + tournament.stripeAccountId = stripeAccountId + } else { + tournament.stripeAccountId = nil + } } else { tournament.accountIsRequired = true tournament.licenseIsRequired = true