For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
RegisterLoginSandbox Login
GuidesRecipesAPI Reference
GuidesRecipesAPI Reference
  • Recipes
    • Add 3DS Challenge (Angular)
    • Add Dynamic Height to Coinflow UI Component
    • Add Dynamic Height to Apple Pay Button
    • Add Dynamic Height to Coinflow iframe
    • Add Dynamic Loading to Coinflow iframe
    • Apple Pay Payouts API Implementation
    • Complete Checkout with 3DS Challenge (React)
    • Listen for Purchase Events
    • Listen for Successful Account Link Messages
    • Listen to Client-Side Messages on Swift iOS
    • Listen to Payment Success Messages
    • PCI Compliant Card Tokenization
    • Tokenize Card Data via API for Checkout
    • Tokenize Card Data via API for Debit Card Payouts
    • Tokenize Cards for Saved Card Checkout (Vue)
    • Tokenize Debit Cards for Withdraws
    • Upload files to Coinflow storage
LogoLogo
RegisterLoginSandbox Login
On this page
  • Define API Models for Request and Response
  • Fetch a Checkout Link
  • Create a WebView Representable
  • Update ContentView to Show Loading State and WebView
  • Handle Transaction Results
Recipes

Listen to Client Side Messages on Swift (iOS)

Was this page helpful?
Previous

Listen to Payment Success Messages

Next
Built with

WebView Handler Name Requirement

When registering your WKWebView message handler, the name must be exactly "ReactNativeWebView". The Coinflow checkout page checks for window.webkit.messageHandlers.ReactNativeWebView to determine if it’s running in a supported mobile WebView. If this handler doesn’t exist with this exact name, the checkout UI will not render properly.

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