parent
b95c7acb83
commit
f2f6e88d8a
@ -0,0 +1,68 @@ |
||||
// |
||||
// StripeValidationService.swift |
||||
// PadelClub |
||||
// |
||||
// Created by razmig on 12/04/2025. |
||||
// |
||||
|
||||
import Foundation |
||||
import LeStorage |
||||
|
||||
class StripeValidationService { |
||||
|
||||
static func validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse { |
||||
let service = try StoreCenter.main.service() |
||||
var urlRequest = try service._baseRequest(servicePath: "validate-stripe-account/", method: .post, requiresToken: true) |
||||
|
||||
let body = ["account_id": accountID] |
||||
urlRequest.httpBody = try JSONEncoder().encode(body) |
||||
|
||||
do { |
||||
let (data, response) = try await URLSession.shared.data(for: urlRequest) |
||||
|
||||
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 |
||||
} |
||||
|
||||
@ -0,0 +1,117 @@ |
||||
// |
||||
// RefundResultsView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by razmig on 12/04/2025. |
||||
// |
||||
|
||||
import SwiftUI |
||||
import LeStorage |
||||
|
||||
struct RefundResultsView: View { |
||||
@Binding var results: [RefundResult] |
||||
let tournament: Tournament |
||||
@Environment(\.dismiss) private var dismiss |
||||
@State private var processingRetry: Set<String> = [] |
||||
|
||||
private var hasErrors: Bool { |
||||
failedRefundResult.isEmpty == false |
||||
} |
||||
|
||||
private var failedRefundResult: [RefundResult] { |
||||
results.filter { result in |
||||
if case .failure = result.response { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
} |
||||
|
||||
private var errorCount: Int { |
||||
failedRefundResult.count |
||||
} |
||||
|
||||
var body: some View { |
||||
List { |
||||
|
||||
if hasErrors { |
||||
RowButtonView("Réessayer tous les remboursements échoués", role: .destructive) { |
||||
// Implement retry logic for failed refunds |
||||
await _retryFailedRefund() |
||||
} |
||||
} |
||||
|
||||
let sorted = results.sorted(by: \.team.weight) |
||||
|
||||
ForEach(sorted, id: \.team.id) { result in |
||||
LabeledContent { |
||||
if processingRetry.contains(result.team.id) { |
||||
ProgressView() |
||||
} else { |
||||
Button { |
||||
Task { |
||||
await _retryResult(result: result) |
||||
} |
||||
} label: { |
||||
Label("refaire", systemImage: "arrow.2.circlepath.circle").labelStyle(.iconOnly) |
||||
} |
||||
.buttonStyle(.borderedProminent) |
||||
} |
||||
} label: { |
||||
Text(result.team.teamLabel(twoLines: true)) |
||||
switch result.response { |
||||
case .success(let response): |
||||
Text(response.message) |
||||
.foregroundColor(response.success ? .green : .logoRed) |
||||
case .failure(let error): |
||||
Text(error.localizedDescription) |
||||
.foregroundColor(.logoRed) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.navigationTitle("Résultats des remboursements") |
||||
.toolbarBackground(.visible, for: .bottomBar, .navigationBar) |
||||
.navigationBarTitleDisplayMode(.inline) |
||||
.toolbar { |
||||
ToolbarItem(placement: .principal) { |
||||
if hasErrors { |
||||
Label("\(errorCount) erreur\(errorCount.pluralSuffix)", systemImage: "exclamationmark.triangle") |
||||
.foregroundColor(.red) |
||||
} else { |
||||
Label("Remboursements effectués", systemImage: "checkmark.circle.fill") |
||||
.foregroundColor(.green) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private func _retryResult(result: RefundResult) async { |
||||
processingRetry.insert(result.team.id) |
||||
do { |
||||
let response = try await RefundService.processRefund(teamRegistrationId: result.team.id) |
||||
results.removeAll(where: { $0.team.id == result.team.id }) |
||||
results.append(RefundResult(team: result.team, response: .success(response))) |
||||
} catch { |
||||
results.removeAll(where: { $0.team.id == result.team.id }) |
||||
results.append(RefundResult(team: result.team, response: .failure(error))) |
||||
} |
||||
processingRetry.remove(result.team.id) |
||||
} |
||||
|
||||
private func _retryFailedRefund() async { |
||||
let failed = failedRefundResult |
||||
await failed.concurrentForEach { result in |
||||
results.removeAll(where: { $0.team.id == result.team.id }) |
||||
do { |
||||
let response = try await RefundService.processRefund(teamRegistrationId: result.team.id) |
||||
if let players = response.players { |
||||
tournament.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) |
||||
} |
||||
results.append(RefundResult(team: result.team, response: .success(response))) |
||||
} catch { |
||||
results.append(RefundResult(team: result.team, response: .failure(error))) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue