Listen to Client Side Messages on Swift (iOS)
1. Define API Models
1 // Response model 2 struct CheckoutLinkResponse: Codable { 3 let link: String 4 } 5 6 // Request models 7 struct CheckoutRequest: Codable { 8 struct Subtotal: Codable { 9 let currency: String 10 let cents: Int 11 } 12 13 struct ChargebackProtectionData: Codable { 14 let productType: String 15 let productName: String 16 let quantity: Int 17 } 18 19 let subtotal: Amount 20 let chargebackProtectionData: [ChargebackProtectionData] 21 22 // ... 23 // NOTE: You may need more fields than this basic example. 24 }
2. Handle API Calls
1 class WebViewModel: ObservableObject { 2 @Published var checkoutURL: URL? 3 @Published var isLoading = false 4 @Published var errorMessage: String? 5 @Published var transactionStatus: String? 6 7 func fetchCheckoutURL() async { 8 await MainActor.run { 9 isLoading = true 10 errorMessage = nil 11 } 12 13 do { 14 guard let url = URL(string: "https://api-sandbox.coinflow.cash/api/checkout/link") else { 15 throw URLError(.badURL) 16 } 17 18 let requestBody = CheckoutRequest( 19 subtotal: CheckoutRequest.Amount(currency: "USD", cents: 100), 20 chargebackProtectionData: [ 21 CheckoutRequest.Product( 22 productType: "dataStorage", 23 productName: "test", 24 quantity: 1 25 ) 26 ] 27 ) 28 29 var request = URLRequest(url: url) 30 request.httpMethod = "POST" 31 request.addValue("application/json", forHTTPHeaderField: "Content-Type") 32 request.addValue("application/json", forHTTPHeaderField: "accept") 33 request.addValue("YOUR_AUTHORIZATION_KEY", forHTTPHeaderField: "Authorization") 34 request.addValue("YOUR_USER_ID", forHTTPHeaderField: "x-coinflow-auth-user-id") 35 36 request.httpBody = try JSONEncoder().encode(requestBody) 37 38 let (data, response) = try await URLSession.shared.data(for: request) 39 40 guard let httpResponse = response as? HTTPURLResponse, 41 (200...299).contains(httpResponse.statusCode) else { 42 throw URLError(.badServerResponse) 43 } 44 45 let checkoutResponse = try JSONDecoder().decode(CheckoutLinkResponse.self, from: data) 46 47 await MainActor.run { 48 if let url = URL(string: checkoutResponse.link) { 49 self.checkoutURL = url 50 } else { 51 self.errorMessage = "Invalid URL in response" 52 } 53 self.isLoading = false 54 } 55 } catch { 56 await MainActor.run { 57 self.errorMessage = "API Error: \(error.localizedDescription)" 58 self.isLoading = false 59 } 60 } 61 } 62 63 func handleWebMessage(_ message: String) { 64 print("Received message from web: \(message)") 65 66 switch message { 67 case "success": 68 transactionStatus = "Success" 69 case "authDeclined": 70 transactionStatus = "Failed" 71 default: 72 break 73 } 74 } 75 }
3. Create a WebView
1 struct WebViewRepresentable: UIViewRepresentable { 2 @ObservedObject var viewModel: WebViewModel 3 4 func makeUIView(context: Context) -> WKWebView { 5 let configuration = WKWebViewConfiguration() 6 let userContentController = WKUserContentController() 7 8 userContentController.add(context.coordinator, name: "ReactNativeWebView") // Very important. The name must be "ReactNativeWebView" 9 configuration.userContentController = userContentController 10 11 return WKWebView(frame: .zero, configuration: configuration) 12 } 13 14 func updateUIView(_ webView: WKWebView, context: Context) { 15 if let checkoutURL = viewModel.checkoutURL, 16 webView.url == nil || webView.url?.absoluteString != checkoutURL.absoluteString { 17 webView.load(URLRequest(url: checkoutURL)) 18 } 19 } 20 21 func makeCoordinator() -> Coordinator { 22 Coordinator(self) 23 } 24 25 class Coordinator: NSObject, WKScriptMessageHandler { 26 var parent: WebViewRepresentable 27 28 init(_ parent: WebViewRepresentable) { 29 self.parent = parent 30 } 31 32 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 33 guard let messageString = message.body as? String else { return } 34 35 DispatchQueue.main.async { 36 self.parent.viewModel.handleWebMessage(messageString) 37 } 38 } 39 } 40 }
4. Show Webview
1 struct ContentView: View { 2 @StateObject private var viewModel = WebViewModel() 3 4 var body: some View { 5 VStack { 6 if viewModel.isLoading { 7 ProgressView("Loading checkout...") 8 } else if let errorMessage = viewModel.errorMessage { 9 Text("Error: \(errorMessage)") 10 .foregroundColor(.red) 11 .padding() 12 13 Button("Retry") { 14 Task { 15 await viewModel.fetchCheckoutURL() 16 } 17 } 18 .padding() 19 } else { 20 WebViewRepresentable(viewModel: viewModel) 21 .edgesIgnoringSafeArea(.all) 22 } 23 } 24 .onAppear { 25 Task { 26 await viewModel.fetchCheckoutURL() 27 } 28 } 29 } 30 }
5. Handle Result
1 // Add this to your ContentView 2 if let status = viewModel.transactionStatus { 3 Text("Transaction status: \(status)") 4 .padding() 5 .background(status == "Success" ? Color.green.opacity(0.2) : Color.red.opacity(0.2)) 6 .cornerRadius(8) 7 }
Response Example
1 {"success":true}
Define API Models for Request and Response
Create models matching Coinflow’s API structure
Fetch a Checkout Link
Implement a ViewModel that fetches the checkout URL.
Create a WebView Representable
Implement a WebView that loads the Coinflow checkout URL and listens for messages.
Update ContentView to Show Loading State and WebView
Connect everything in your ContentView.
Handle Transaction Results
You can update the UI based on transaction status

