Listen to Client Side Messages on Swift (iOS)

1. Define API Models
1// Response model
2struct CheckoutLinkResponse: Codable {
3 let link: String
4}
5
6// Request models
7struct 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
1class 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
1struct 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
1struct 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
2if 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